Skip to main content
Glama
README.md
# CapabilityOS

**Searchable memory of what a codebase already knows how to do.**

Two failure modes, one index:

- A coding agent can't see your conventions, so it writes a fourth JSON helper.
- A team re-debugs the same production failure every few months.

CapabilityOS indexes two Markdown collections — the **capabilities** your code
already has, and the **incidents** it has already survived — and serves them over
[MCP](https://modelcontextprotocol.io) so an AI agent consults them *before* writing code.

Retrieval is SQLite and file reads. **No model calls, no API keys, no per-query cost.**
That's the design constraint that matters: a lookup that's free is a lookup you can
make mandatory. A lookup that costs tokens is one people skip.

```
$ capability-os find "json"

1 capability match(es) for 'json':

**batch_json_writer** [Formal] — utility — json, batch, file io — `src/shared/io.py`
    Read and write JSON with consistent encoding and paths.
    risk: LOW

REUSE these instead of writing new code. Call get_capability_detail(<name>) for
the API and usage examples, and impact_analysis(<name>) before modifying one.
```

---

## Why this isn't just grep

Three things grep doesn't do.

**1. It tells the agent what to do, not just what exists.**
Tool output is written as instruction — `REUSE these instead of writing new code` —
because a model handed a bare list of filenames will acknowledge it and then write
the duplicate anyway. The MCP tool docstrings are part of the interface, phrased as
`ALWAYS call this before writing any new function`.

**2. It knows the blast radius before you change something.**

```
$ capability-os impact progress_tracker

Blast radius: HIGH (59 caller(s) across 537 scanned files)
Declared risk in index: LOW

WARNING: real usage (HIGH) exceeds the declared risk (LOW). Update the index —
a stale risk label is how a 'safe' refactor breaks forty callers.
```

That output is from a real repository. The index claimed LOW; 59 modules disagreed.

**3. It fails CI when the knowledge base starts lying.**
`capability-os check --strict` exits non-zero when a capability marked `Formal`
has no documentation, or when declared risk has drifted below actual usage.
Documentation that isn't enforced becomes fiction; this is the enforcement.

---

## Install

```bash
pip install "capability-os[mcp]"     # from a clone: pip install -e ".[mcp]"
```

Python 3.11+. Zero required dependencies — `mcp` is needed only for the server,
and both SDK majors (`mcp` 1.x and 2.x) are supported.

## Quick start

```bash
cd your-project
capability-os init      # scaffold knowledge/ and capability-os.toml
capability-os index     # build the search index
capability-os list
```

`init` writes a working scaffold you edit in place:

```
your-project/
├── capability-os.toml              # paths + scan settings (all defaults shown)
└── knowledge/
    ├── capability-index.md         # the registry — a Markdown table
    ├── capabilities/<name>.md      # one doc per capability
    └── incidents/<slug>.md         # one postmortem per failure
```

### Register a capability

`knowledge/capability-index.md` is an ordinary Markdown table:

```markdown
| Capability | Type | Tags | Risk | File | Status |
|---|---|---|---|---|---|
| batch_json_writer | utility | json, batch, file io | LOW | src/shared/io.py | Formal |
| auth_guard | backend | jwt, auth | MEDIUM | src/backend/auth.py | Core |
```

Columns are matched **by header name, not position** — reorder them, add your own,
or use another language (`能力`, `风险`, `文件` all work). Multiple tables merge by
capability name, so a separate status or summary table lands on the same records.

### Record an incident

One file per failure. Frontmatter is optional; headings are parsed when it's absent.

```markdown
---
title: CDN served a stale audio file for a year
date: 2026-06-02
keywords: cache, cdn, immutable
lesson: Append a version query string when replacing any immutable-cached asset.
---

## Problem
Updated audio kept serving the old recording in production.

## Root cause
The CDN sent `Cache-Control: immutable` with a one-year max-age.
```

Write the `lesson` as advice to your future self at 2am, not as a summary.
It's what search surfaces first.

---

## Connect it to an AI agent

Add to your MCP client config (Claude Code, Claude Desktop, or any MCP host):

```json
{
  "mcpServers": {
    "capability-os": {
      "command": "capability-os-mcp",
      "env": { "CAPABILITY_OS_ROOT": "/absolute/path/to/your-project" }
    }
  }
}
```

Six tools become available:

| Tool | When the agent calls it |
|---|---|
| `find_capability(query)` | Before writing any new function or module |
| `find_incident(query)` | The moment something breaks, before debugging |
| `get_capability_detail(name)` | To read the API before calling it |
| `list_all_capabilities()` | Once at session start, for orientation |
| `impact_analysis(name)` | Before modifying shared code |
| `reindex()` | After editing the knowledge base |

Then make it non-optional. In `CLAUDE.md` or your agent's system prompt:

```markdown
Before writing any new function: call find_capability first.
If it returns a match, reuse it — do not reimplement.
Before modifying anything in src/shared/: call impact_analysis first.
When you hit an error: call find_incident before debugging.
```

---

## The capability lifecycle

A status isn't decoration; it's a claim about how much trust the code has earned.

```
Ghost  →  Candidate  →  Formal  →  Core  →  Deprecated  →  Archived
```

| Status | Meaning |
|---|---|
| **Ghost** | Duplicated logic spotted in the wild, not yet extracted |
| **Candidate** | Appeared in 3+ places — extraction is due |
| **Formal** | Extracted and documented |
| **Core** | Widely called; change via a new version, never in place |
| **Deprecated** | Superseded; migrate callers |
| **Archived** | No callers remain; kept for history |

`check --strict` holds `Formal` and `Core` to their claim: they must have a
document, and their declared risk must not be below actual usage.

---

## CLI

```bash
capability-os init                    # scaffold a knowledge base
capability-os index                   # rebuild the index from Markdown
capability-os find <query>            # search capabilities
capability-os incident <query>        # search postmortems
capability-os show <name>             # print one capability's docs
capability-os list                    # catalog grouped by status
capability-os impact <name> [--strict] # callers + blast radius
capability-os check [--strict]        # validate the knowledge base (CI)
capability-os serve                   # run the MCP server on stdio
```

### In CI

```yaml
- run: pip install capability-os
- run: capability-os check --strict
```

---

## Configuration

Every field in `capability-os.toml` has a working default; an empty file is valid.

```toml
[project]
name = "your-project"

[knowledge]
dir = "knowledge"
index_file = "capability-index.md"
capabilities_dir = "capabilities"
incidents_dir = "incidents"
db_file = ".capability-os/capability.db"   # generated; safe to gitignore

[scan]
source_dirs = ["src", "lib", "scripts", "backend", "app"]
source_globs = ["*.py", "*.ts", "*.tsx", "*.js", "*.go", "*.rs", "*.java"]
exclude_dirs = [".git", ".venv", "node_modules", "__pycache__", "dist", "build"]
blast_thresholds = [4, 11]   # caller counts at which risk becomes MEDIUM, then HIGH
```

---

## Design notes

**Markdown is the source of truth; SQLite is a cache.** The database is rebuilt
by full replace, never migrated — delete a row and it actually leaves the index.
Humans and models both read and edit the Markdown; nobody edits the database.

**Impact analysis is textual on purpose.** A real call graph needs a resolver per
language. The question being answered — *how many places would I have to look at?* —
is answered well enough by matching the name and its import spellings. False
positives are listed for a human to dismiss; false negatives are the dangerous
kind, so search terms are deliberately broad.

**Search degrades instead of failing.** FTS5 when available, LIKE scanning when
not, identical result shape either way. Queries are tokenized and quoted, so
`c++` and `dash-word` search instead of raising.

See [`docs/design.md`](docs/design.md) for the longer argument, and
[`docs/README.zh-CN.md`](docs/README.zh-CN.md) for Chinese documentation.

## Development

```bash
pip install -e ".[dev]"
pytest              # 75 tests
```

## License

MIT