decision-graph
by vietqtran
README.md
# decision-graph
> **English** · [Tiếng Việt](README.vi.md)
[](https://github.com/vietqtran/decision-graph/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.python.org/downloads/)
**Decision memory for codebases worked on by AI agents.**
A code graph tells you *what* the code does. `decision-graph` tells you *why it is that way* — when a business rule appeared, who decided it, what alternatives were rejected, and whether the decision still holds.
`git blame` gives you a commit message. It does not tell you that a customer's CTO asked for a second approval tier in a meeting, that a separate module was considered and rejected as unmergeable, or that the threshold was lowered six months later by someone else.
Language- and framework-agnostic. Works in any repo.
## Why
The pain is sharpest when one base product is extended per customer — but it shows up anywhere business logic accumulates:
- An agent "improves" a rule that was deliberately written that way for one tenant.
- Nobody remembers whether an odd-looking branch is a bug or a requirement.
- The same rejected approach gets proposed again every few months.
- Decisions live in Slack threads, ticket comments, and people's heads.
AI agents make this worse, because they have no implicit memory of the last six months of meetings — but they *will* follow a written record if one exists.
## Design principles
- **Markdown is the source of truth.** One `.md` file per decision, in git, reviewable in a PR.
- **SQLite is only an index.** Delete `decisions/_index/` and rebuild any time.
- **Agents may not invent `decided_by`.** An agent can only create a `draft`; promoting it to `active` requires a human.
- **Recording is a side effect of coding**, not a chore to remember — hooks prompt at the right moment.
- **Detection reads git, not tool events.** Edits made with `sed`, heredocs, `git apply`, or a plain editor are caught just like `Edit`/`Write` tool calls.
## Install
Not on PyPI yet — install straight from GitHub:
```bash
uv tool install "decision-graph[mcp] @ git+https://github.com/vietqtran/decision-graph"
```
<details>
<summary>Other ways</summary>
```bash
# pipx
pipx install "decision-graph[mcp] @ git+https://github.com/vietqtran/decision-graph"
# one-off, no install
uvx --from "git+https://github.com/vietqtran/decision-graph" decision-graph --help
# local development
git clone https://github.com/vietqtran/decision-graph && cd decision-graph
uv venv && uv pip install -e ".[mcp]"
```
</details>
Requires Python 3.10+ and a SQLite build with FTS5 (standard on macOS, Debian/Ubuntu, and the official Python images).
## Quick start
```bash
cd /path/to/your/repo
decision-graph init
decision-graph add --scope acme --module deals/approval \
--title "Second approval tier for deals over 500M" \
--file src/approval.py --tag override-base --stdin < body.md
# a human confirms who decided — the agent cannot do this step
decision-graph confirm acme-2026-08-26-second-approval-tier --by "Jane Doe (CTO, Acme)"
decision-graph search "two-tier approval" --scope acme
decision-graph history src/approval.py # every decision that touched this file
decision-graph overlay acme # how acme differs from base, and why
```
## Core concept: `scope`
`scope` is the axis that separates contexts — a customer, a product line, a team, or `_base` for decisions that apply everywhere.
Filtering by scope happens *before* full-text ranking, which is what stops one tenant's rules from bleeding into answers about another. In a repo serving many customers, this is the single most important field.
## Layout
```
decisions/
_template.md
_base/ # applies to every scope
acme/2026-08-26-approval.md
viettel/2026-05-20-inventory.md
_index/decisions.db # generated — gitignored
.decision-graph.yml # per-repo watch/ignore patterns
```
A record's frontmatter:
```yaml
id: acme-2026-08-26-second-approval-tier
scope: acme
module: deals/approval
title: "Second approval tier for deals over 500M"
status: active # draft | active | superseded | deprecated
lifecycle_stage: maintenance # design | dev | uat | golive | maintenance
supersedes: acme-2026-03-12-single-tier
decided_by: "Jane Doe (CTO, Acme)"
requested_by: "John Smith (PM)"
decided_at: 2026-08-26
linked_files: [src/approval.py]
linked_commit: 8f3a2c1
session_id: abc-123 # agent session that produced the change
ticket: JIRA-482
tags: [approval, override-base]
```
The body follows `decisions/_template.md`: Context / Alternatives considered / Decision / Impact / Open risks.
**Alternatives considered matters more to agents than to humans** — it is what stops an agent from re-proposing the approach that was already rejected.
`supersedes` chains records instead of deleting them, so the audit trail survives.
## Search
Metadata filters first (`scope`, `module`, `status`, `lifecycle_stage`, `file`, `tag`), then SQLite FTS5 ranking. Diacritics are folded, so `duyet don hang` matches `Duyệt đơn hàng`.
```bash
decision-graph search "approval" # active decisions only, by default
decision-graph search --scope acme --status any
decision-graph search --file src/approval.py
decision-graph search "pricing" --tag override-base --json
```
## Agent integration
### Which trigger to use
| Situation | Trigger |
|---|---|
| Claude Code runs on the **same machine** as the repo | Claude Code hooks — can block, strongest |
| Claude Code runs **elsewhere** (SSH / VM / remote) | Git hook — reminds, cannot block |
| Humans committing without an agent | Git hook + `check` in CI |
Installing both is fine; each goes quiet once a decision is recorded.
### Claude Code hooks
```bash
decision-graph hooks install --target /path/to/repo
```
- `PostToolUse(Edit|Write|MultiEdit)` records which files a session touched.
- `Stop` inspects git plus that record. If business-relevant files changed and no decision was written, it returns `decision: "block"` with instructions — the agent must act instead of finishing.
The agent gets exactly two ways out:
```bash
decision-graph skip --session <id> --reason "renamed variables only"
decision-graph add ... --session <id> # then ask the human, then confirm
```
`stop_hook_active` is honoured, so this never loops.
> **Install hooks where Claude Code actually runs**, not where the code lives. If you run Claude Code on a VM and only proxy shell commands to the machine holding the repo, that machine's `.claude/settings.json` is never read — use the git hook instead.
### Git hook
```bash
decision-graph hooks install --git --target /path/to/repo
```
Installs `.git/hooks/post-commit` calling `decision-graph remind`. It never blocks a commit. Because agents run git through their shell and read stdout, the reminder lands in the agent's context anyway.
It stays silent once a decision links to that commit.
### MCP server
One entry, declared once at user level — the server follows whichever repo the session has open, so there is no path to keep in sync:
```json
{
"mcpServers": {
"decision-graph": {
"command": "uvx",
"args": ["--from", "git+https://github.com/vietqtran/decision-graph",
"decision-graph-mcp"]
}
}
}
```
It asks the client which directory the session is working in (MCP roots), falling back to the working directory. Repos without a `decisions/` directory are skipped rather than guessed at. Add `--path /path/to/repo` only to pin the server to one repo.
Tools: `search_decisions`, `get_decision`, `get_decision_history`, `get_decision_chain`, `get_overlay_map`, `list_scopes`, `add_decision`.
See [docs/MCP.md](docs/MCP.md).
## Filtering noise
Not every commit is a business decision. Typos and pure refactors should not generate records.
Defaults ignore tests, lockfiles, `node_modules`, build output, coverage reports, assets, and i18n files. Narrow further per repo:
```yaml
# .decision-graph.yml
watch:
- "src/domain/**"
- "app/services/**"
```
An empty `watch` means everything not in `ignore` counts.
## CI
```bash
decision-graph check --git
```
Emits `{"needs_decision": bool, "watched_files": [...], ...}` — wire it into a PR warning.
## Documentation
- [CLI reference](docs/CLI.md)
- [Architecture](docs/ARCHITECTURE.md)
- [MCP server](docs/MCP.md)
- [Troubleshooting](docs/TROUBLESHOOTING.md)
- [Contributing](CONTRIBUTING.md)
## Development
```bash
uv venv && uv pip install -e ".[mcp]"
.venv/bin/python -m unittest discover -s tests
```
No runtime dependency beyond PyYAML; `mcp` is an optional extra.
## Prior art
`decision-graph` is an [ADR](https://adr.github.io/) variant. ADRs record *architectural* decisions for humans; this records *business* decisions, per tenant, in a form agents can query — with automatic capture and a human confirmation gate.
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues