Witness
Witness observes every tool call an AI coding agent makes, records structured facts into SQLite, and derives useful state from them — which tests are failing, what files were edited, what broke after a change, what depends on what.
Out of the box it's a passive observer: it records facts and lets you query them. Optionally, you can enable lint rules that warn or block the agent when it falls into bad patterns (editing without reading, committing with failing tests, thrashing on a file).
Prerequisites
Installation
As a Claude Code plugin
# From the plugin directory
claude plugin install --path /path/to/witness
# Or during development
claude --plugin-dir /path/to/witness
After installing, run bun install inside the plugin directory if you haven't already.
The plugin automatically wires up all three hooks:
- PostToolUse →
witness record (records facts after each tool call)
- PreToolUse →
witness lint (evaluates rules, if any are enabled)
- SessionStart →
witness init (creates the DB if needed)
As a pi coding agent extension (pi-mono)
# one-off run
pi -e /path/to/witness/extensions/witness.ts
# or install as a package
pi install /path/to/witness
The extension mirrors the Claude plugin behavior:
session_start / session_switch → witness init
tool_call → witness lint (can block tool execution)
tool_result → witness record
Warnings are shown in the pi TUI as notifications. Block rules return a tool block reason directly to pi.
Manual hook setup
If you prefer to configure hooks yourself, add this to your .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bun run /path/to/witness/src/main.ts lint"
}
]
}
],
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "bun run /path/to/witness/src/main.ts record"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bun run /path/to/witness/src/main.ts init"
}
]
}
]
}
}
How It Works
Witness runs as two hooks on every tool call:
- PostToolUse (
witness record): After a tool runs, parse the output and insert structured facts into SQLite.
- PreToolUse (
witness lint): Before a tool runs, evaluate any enabled lint rules against the DB state.
Both hooks read JSON from stdin and exit 0 always. They never crash the agent, even on malformed input.
Queries
The primary interface. Witness records facts passively — you query them when you need situational awareness.
witness briefing
Markdown summary of the current session: failing tests, regressions, thrashing files, untested edits, blast radius, session stats. Empty sections are omitted.
witness query <name> [arg]
| Query | Description | Argument |
|---|
failing | Currently failing tests | — |
passing | Currently passing tests | — |
regressions | Tests that broke after edits | — |
thrashing | Files in edit-fail loops | — |
history | Edit timeline for a file | <file> |
test-history | Pass/fail timeline for a test | <test> |
untested | Files edited but not tested | — |
lint | Current lint/type errors | — |
fixes | Edits that fixed tests | — |
clusters | Error clusters (same message) | — |
timeline | Last N tool calls | [n] (default 20) |
stats | Session summary | — |
blast | Reverse dependencies | <file> |
deps | Forward dependencies | <file> |
witness watch
Tail incoming hook events (lint and record) in real time.
witness watch # human-readable stream
witness watch --format json # NDJSON stream for tooling/filtering
Optional:
witness watch --poll-ms 200
Lint Rules
All rules are off by default. Enable them in .witness.json in your project root:
{
"rules": {
"no_edit_unread": "warn",
"no_commit_failing": "block"
}
}
Each rule can be "warn", "block", "off", or ["action", { options }].
| Rule | Fires when |
|---|
no_edit_unread | Editing a file you haven't read this session |
test_after_edits | N+ edits without running tests (default threshold: 3) |
fix_regressions_first | Editing new files while regressions exist |
no_pointless_rerun | Re-running tests with no edits since last run |
no_thrashing | N+ edits to the same file with failures persisting (default threshold: 3) |
no_commit_failing | Committing while tests are failing |
scope_check | Editing files outside the blast radius of current work |
Parsers
Witness automatically parses output from these tools: