whyfence 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
whyfence-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Md Nadeem Akhtar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: whyfence
3
+ Version: 0.1.0
4
+ Summary: Temporal memory for AI coding agents: why your code is the way it is, served over MCP.
5
+ Keywords: ai,coding-agents,mcp,model-context-protocol,claude,claude-code,llm,memory,git,code-history,tree-sitter,developer-tools,linter
6
+ Author: Md Nadeem Akhtar
7
+ Author-email: Md Nadeem Akhtar <nadeemnns2000@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Topic :: Software Development
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Classifier: Topic :: Software Development :: Version Control :: Git
21
+ Requires-Dist: mcp>=2.3.0,<3
22
+ Requires-Dist: rich>=15.0.0
23
+ Requires-Dist: tree-sitter>=0.26.0
24
+ Requires-Dist: tree-sitter-javascript>=0.25.0
25
+ Requires-Dist: tree-sitter-python>=0.25.0
26
+ Requires-Dist: tree-sitter-typescript>=0.23.2
27
+ Requires-Python: >=3.12
28
+ Project-URL: Homepage, https://github.com/NadeemAkhtar1947/whyfence
29
+ Project-URL: Repository, https://github.com/NadeemAkhtar1947/whyfence
30
+ Project-URL: Issues, https://github.com/NadeemAkhtar1947/whyfence/issues
31
+ Project-URL: Documentation, https://github.com/NadeemAkhtar1947/whyfence/blob/main/README_MCP.md
32
+ Description-Content-Type: text/markdown
33
+
34
+ # whyfence
35
+
36
+ [![CI](https://github.com/NadeemAkhtar1947/whyfence/actions/workflows/ci.yml/badge.svg)](https://github.com/NadeemAkhtar1947/whyfence/actions/workflows/ci.yml)
37
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/NadeemAkhtar1947/whyfence/blob/main/LICENSE)
38
+ ![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)
39
+
40
+ **Your coding agent knows *what* your code does. whyfence tells it *why*.**
41
+
42
+ whyfence gives AI coding agents a memory of how your code got the way it is: the
43
+ decisions behind it, the approaches that already failed, the corrections you made,
44
+ and the rules you set. Your agent can check that memory over MCP before it
45
+ "simplifies" something that was deliberately written that way.
46
+
47
+ Local-first. No server, no account, no code leaves your machine.
48
+
49
+ ---
50
+
51
+ ## Chesterton's Fence, for AI agents
52
+
53
+ > There is a fence across a road. A reformer says, "I don't see the use of this; let
54
+ > us clear it away." The wiser one replies: "If you don't see the use of it, I
55
+ > certainly won't let you clear it away. Go away and think. When you can come back
56
+ > and tell me that you *do* see the use of it, I may allow you to destroy it."
57
+ > (paraphrasing G. K. Chesterton)
58
+
59
+ Coding agents meet fences all day: a retry loop that looks redundant, a lock that
60
+ looks unnecessary, a model called `TaskFile` instead of the obvious `File`. They
61
+ can read the code, but they can't see why it is there. That knowledge lives in old
62
+ commit messages, in your head, and in last week's agent session, which the new
63
+ session has forgotten.
64
+
65
+ So agents tear fences down. They re-add the retry you removed on purpose, retry the
66
+ approach that deadlocked on Tuesday, or rename `TaskFile` back to `File` and break
67
+ every import of `fastapi.File`.
68
+
69
+ whyfence collects that "why" from places you already have it and pins it to the
70
+ function it is about:
71
+
72
+ ```text
73
+ why("app.models.TaskFile")
74
+
75
+ decision · 2026-10-31 · from a Claude Code session
76
+ "The file model is `TaskFile`, not `File`, because `File` would clash with
77
+ `fastapi.File`, which the documents and invoice routers already import."
78
+ ```
79
+
80
+ ---
81
+
82
+ ## What it remembers
83
+
84
+ | Kind | Where it comes from | Example |
85
+ |---|---|---|
86
+ | `decision` | Commit messages (`Why:`, "because", "so that"); the agent's own stated choices in sessions | "`Decimal` instead of `float` to avoid rounding errors" |
87
+ | `constraint` | Rules you state in a session or a commit | "Never call `send()` from the event loop" |
88
+ | `correction` | You pushing back on the agent | "No, don't retry inside `refresh()`; the HTTP client already retries" |
89
+ | `dead_end` | The agent realizing an approach was wrong | "Turns out `celery.current_app` is a lazy proxy, so patching it doesn't stick" |
90
+ | `incident` | `fix:` commits, "Fixes #12", bugs, crashes | "fix: refresh tokens before they expire" |
91
+ | `revert` | `Revert "…"`, "This reverts commit …" | |
92
+
93
+ Each event is linked to the **functions, methods and classes** it is about, and
94
+ those links follow the code through history. whyfence indexes every Python,
95
+ JavaScript and TypeScript symbol at every commit and keeps its identity when it
96
+ is renamed, moved to another file, or renamed and edited in the same commit.
97
+
98
+ ---
99
+
100
+ ## Try it in one minute
101
+
102
+ ```console
103
+ $ git clone https://github.com/NadeemAkhtar1947/whyfence && cd whyfence
104
+ $ ./demo.sh
105
+ ```
106
+
107
+ [`demo.sh`](https://github.com/NadeemAkhtar1947/whyfence/blob/main/demo.sh) builds a throwaway repository and a mock Claude Code session in
108
+ a temp folder, then runs `whyfence lint`, `whyfence ingest`, `whyfence why` and
109
+ `whyfence history` on them. It needs only bash, git and
110
+ [uv](https://docs.astral.sh/uv/), and it never touches your own repositories or
111
+ `~/.claude`. The `why` step prints what an agent learns before touching `refresh()`:
112
+
113
+ ```text
114
+ app.tokens.refresh function · python
115
+ constraint 2026-02-10 From now on, never call `refresh` from the request
116
+ path; the background renewer owns it.
117
+ transcript · claude-code · inferred
118
+ dead_end 2026-02-10 I see the issue now: retrying here stacked on top of
119
+ the client's retries. I'll revert the loop.
120
+ transcript · claude-code · inferred
121
+ correction 2026-02-10 No, don't retry inside refresh(). The HTTP client
122
+ already retries, so this doubles every request.
123
+ transcript · claude-code · inferred
124
+ incident 2026-02-03 fix: refresh tokens before they expire
125
+ commit c69facf
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Quick start
131
+
132
+ whyfence is alpha and needs Python 3.12 or newer.
133
+
134
+ ```console
135
+ $ pip install whyfence
136
+ $ whyfence ingest /path/to/your/repo
137
+ ```
138
+
139
+ On the small repository `demo.sh` builds, that prints:
140
+
141
+ ```text
142
+ Read 3 commits from demo-app: 3 new, 3 indexed in demo-app/.whyfence/index.db
143
+ Walked 3 commits: 2 new symbols, 5 versions, 2 renamed or moved, 0 deleted
144
+ · 2 symbols indexed
145
+ Found 1 event in commit messages and 3 events in 1 Claude Code session,
146
+ with 4 symbol links
147
+ ```
148
+
149
+ Then connect your agent. For Claude Code, add `.mcp.json` to your repository
150
+ (this uses [uvx](https://docs.astral.sh/uv/); with a plain `pip install`, use
151
+ `"command": "whyfence", "args": ["mcp"]` instead):
152
+
153
+ ```json
154
+ {
155
+ "mcpServers": {
156
+ "whyfence": {
157
+ "type": "stdio",
158
+ "command": "uvx",
159
+ "args": ["whyfence", "mcp"]
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ Claude Desktop and other clients: see [README_MCP.md](https://github.com/NadeemAkhtar1947/whyfence/blob/main/README_MCP.md).
166
+
167
+ Re-run `whyfence ingest` whenever you like. It only processes new commits and
168
+ sessions, and it rebuilds symbol history automatically if history was rewritten.
169
+
170
+ ---
171
+
172
+ ## Temporal memory over MCP
173
+
174
+ `whyfence mcp` serves two read-only tools:
175
+
176
+ **`why(symbol_name)`**: everything recorded about why a symbol is the way it is,
177
+ newest first, each with its source (commit or session) and how confident the
178
+ extraction is.
179
+
180
+ **`history(symbol_name)`**: the symbol's timeline: each version, the commit that
181
+ introduced it, the commit that replaced it, and its name, file and lines at the time.
182
+
183
+ ```json
184
+ {
185
+ "symbol": {"name": "app.tokens.refresh_token", "kind": "function"},
186
+ "versions": [
187
+ {"name": "app.auth.refresh", "file": "app/auth.py",
188
+ "valid_from": {"sha": "3f1c…", "subject": "add auth"}},
189
+ {"name": "app.session.refresh", "file": "app/session.py",
190
+ "valid_from": {"sha": "9ab2…", "subject": "rename the file"}},
191
+ {"name": "app.tokens.refresh", "file": "app/tokens.py",
192
+ "valid_from": {"sha": "c47e…", "subject": "move refresh to its own module"}},
193
+ {"name": "app.tokens.refresh_token", "file": "app/tokens.py",
194
+ "valid_from": {"sha": "e810…", "subject": "rename and edit at once"},
195
+ "current": true}
196
+ ]
197
+ }
198
+ ```
199
+
200
+ Names can be qualified (`app.auth.refresh`), bare (`refresh`) or a symbol id. An
201
+ ambiguous bare name returns the candidates instead of guessing.
202
+
203
+ The same answers are available from the command line, for you or for scripts:
204
+
205
+ ```console
206
+ $ whyfence why refresh # readable summary
207
+ $ whyfence history app.auth.refresh
208
+ $ whyfence why refresh --json # exactly what the MCP tool returns
209
+ ```
210
+
211
+ The server tells agents to call `why` before changing code that looks wrong or
212
+ redundant, and to treat recorded constraints and dead ends as binding unless they
213
+ have new information.
214
+
215
+ ---
216
+
217
+ ## `whyfence lint`: catch stale agent instructions
218
+
219
+ Agents follow `CLAUDE.md` and `AGENTS.md` literally. When a script is renamed or a
220
+ make target is deleted, every session keeps running a command that no longer
221
+ exists. `whyfence lint` checks every command in those files against what it would
222
+ actually run:
223
+
224
+ ```console
225
+ $ whyfence lint examples/stale-repo
226
+
227
+ examples/stale-repo/CLAUDE.md 7 commands · 5 stale
228
+ ✗ L3 npm run lint no `lint` script in package.json
229
+ → did you mean npm run lint:all?
230
+ ✗ L7 npm run start no `start` script in web/package.json
231
+ ✗ L8 make -C api tests no `tests` target in api/Makefile
232
+ → did you mean make -C api test?
233
+ ✗ L9 make migrate no Makefile in ./
234
+ → did you mean make -C api migrate?
235
+ ✗ L12 make deploy no Makefile in ./
236
+
237
+ ┌──────────────────────────────────────────────────────────────────────────┐
238
+ │ ✗ 5 stale ? 0 unverifiable ✓ 2 verified │
239
+ │ 7 commands across 1 file. Agents reading these files will run commands │
240
+ │ that no longer exist. │
241
+ └──────────────────────────────────────────────────────────────────────────┘
242
+ ```
243
+
244
+ | Command | Checked against |
245
+ |---|---|
246
+ | `make test`, `make -C api migrate` | Makefile targets, including `include`d files |
247
+ | `npm run dev`, `yarn lint`, `pnpm test`, `bun run build` | `package.json` scripts and package binaries |
248
+ | `uv run serve`, `poetry run serve` | `[project.scripts]`, declared dependencies, `.venv` |
249
+ | `python -m app.jobs`, `uv run -m app` | local modules (with `__main__.py`), stdlib, dependencies |
250
+ | `python scripts/seed.py`, `./scripts/release.sh` | the file on disk |
251
+
252
+ It follows `cd dir &&` and directory flags, suggests the closest match (including
253
+ targets that moved into a subdirectory), and reports anything that depends on
254
+ runtime state as *unverifiable* instead of guessing. It exits with `1` on stale
255
+ commands, so it works as a CI gate:
256
+
257
+ ```console
258
+ $ whyfence lint --json # machine-readable
259
+ $ whyfence lint --strict # also fail on unverifiable commands
260
+ $ whyfence lint --exclude docs # skip paths (examples/ is skipped by default)
261
+ ```
262
+
263
+ ---
264
+
265
+ ## How it works
266
+
267
+ ```text
268
+ git history ──► commits ──► symbol index (tree-sitter, every commit, first-parent)
269
+ │ identity: name → body fingerprint → token similarity
270
+ ▼
271
+ commit messages ─┐ symbol_versions ◄── history()
272
+ Claude Code ├─► events ──► event_links ◄── why()
273
+ transcripts ─────┘
274
+ SQLite (WAL, FTS5) in .whyfence/index.db
275
+ ```
276
+
277
+ - **Symbol identity.** Each definition is fingerprinted with its own name removed
278
+ and whitespace collapsed. A commit's definitions are matched to existing symbols
279
+ by name in the same file, then by fingerprint anywhere in the commit (renames and
280
+ moves), then by token similarity (renamed *and* edited). Only code, file or name
281
+ changes create a new version; line shifts don't.
282
+ - **Events.** Commit events link to the symbols that commit changed. Session events
283
+ link through the edits the agent made around them, located in today's code and
284
+ re-parsed, plus symbols named in the text. Broad events (a feature commit
285
+ touching 80 functions) keep only the symbols they name.
286
+ - **Precision over recall.** Extraction is deterministic pattern matching, tuned on
287
+ real sessions. It skips tool output, subagent chatter, the agent's private
288
+ reasoning and Claude Code's own compaction summaries. A missing event costs
289
+ you nothing; a wrong one teaches your agent something false.
290
+
291
+ ## Privacy
292
+
293
+ Everything stays in `.whyfence/index.db` inside your repository, which you can
294
+ gitignore and delete at any time; it is rebuilt from history. Transcripts are read
295
+ from `~/.claude/projects` for sessions in, under or above the repository
296
+ (`--no-transcripts` to skip them). Obvious secrets (API keys, tokens) are redacted
297
+ before anything is stored. whyfence makes no network calls.
298
+
299
+ ## Status and roadmap
300
+
301
+ whyfence is an early MVP. It works end to end, but its extraction heuristics have
302
+ only been tuned on a handful of real projects.
303
+
304
+ - [x] Stale-context linter for `CLAUDE.md` / `AGENTS.md`
305
+ - [x] Symbol history with rename and move tracking (Python, JavaScript, TypeScript)
306
+ - [x] Events from commit messages and Claude Code transcripts
307
+ - [x] MCP server: `why`, `history`
308
+ - [ ] Team memory: a shared, append-only event log committed to the repo
309
+ - [ ] More agents' transcripts (Codex, Cursor, Gemini CLI)
310
+ - [ ] More languages (Go, Rust, Java)
311
+ - [ ] A `rules` tool that serves every standing constraint at session start
312
+
313
+ ## Development
314
+
315
+ ```console
316
+ $ git clone https://github.com/NadeemAkhtar1947/whyfence && cd whyfence
317
+ $ uv sync
318
+ $ uv run pytest
319
+ ```
320
+
321
+ The test suite includes an end-to-end check that indexes whyfence's own git
322
+ history and verifies every recorded version against Python's `ast`.
323
+
324
+ ## License
325
+
326
+ [MIT](https://github.com/NadeemAkhtar1947/whyfence/blob/main/LICENSE)
@@ -0,0 +1,293 @@
1
+ # whyfence
2
+
3
+ [![CI](https://github.com/NadeemAkhtar1947/whyfence/actions/workflows/ci.yml/badge.svg)](https://github.com/NadeemAkhtar1947/whyfence/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/NadeemAkhtar1947/whyfence/blob/main/LICENSE)
5
+ ![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)
6
+
7
+ **Your coding agent knows *what* your code does. whyfence tells it *why*.**
8
+
9
+ whyfence gives AI coding agents a memory of how your code got the way it is: the
10
+ decisions behind it, the approaches that already failed, the corrections you made,
11
+ and the rules you set. Your agent can check that memory over MCP before it
12
+ "simplifies" something that was deliberately written that way.
13
+
14
+ Local-first. No server, no account, no code leaves your machine.
15
+
16
+ ---
17
+
18
+ ## Chesterton's Fence, for AI agents
19
+
20
+ > There is a fence across a road. A reformer says, "I don't see the use of this; let
21
+ > us clear it away." The wiser one replies: "If you don't see the use of it, I
22
+ > certainly won't let you clear it away. Go away and think. When you can come back
23
+ > and tell me that you *do* see the use of it, I may allow you to destroy it."
24
+ > (paraphrasing G. K. Chesterton)
25
+
26
+ Coding agents meet fences all day: a retry loop that looks redundant, a lock that
27
+ looks unnecessary, a model called `TaskFile` instead of the obvious `File`. They
28
+ can read the code, but they can't see why it is there. That knowledge lives in old
29
+ commit messages, in your head, and in last week's agent session, which the new
30
+ session has forgotten.
31
+
32
+ So agents tear fences down. They re-add the retry you removed on purpose, retry the
33
+ approach that deadlocked on Tuesday, or rename `TaskFile` back to `File` and break
34
+ every import of `fastapi.File`.
35
+
36
+ whyfence collects that "why" from places you already have it and pins it to the
37
+ function it is about:
38
+
39
+ ```text
40
+ why("app.models.TaskFile")
41
+
42
+ decision · 2026-10-31 · from a Claude Code session
43
+ "The file model is `TaskFile`, not `File`, because `File` would clash with
44
+ `fastapi.File`, which the documents and invoice routers already import."
45
+ ```
46
+
47
+ ---
48
+
49
+ ## What it remembers
50
+
51
+ | Kind | Where it comes from | Example |
52
+ |---|---|---|
53
+ | `decision` | Commit messages (`Why:`, "because", "so that"); the agent's own stated choices in sessions | "`Decimal` instead of `float` to avoid rounding errors" |
54
+ | `constraint` | Rules you state in a session or a commit | "Never call `send()` from the event loop" |
55
+ | `correction` | You pushing back on the agent | "No, don't retry inside `refresh()`; the HTTP client already retries" |
56
+ | `dead_end` | The agent realizing an approach was wrong | "Turns out `celery.current_app` is a lazy proxy, so patching it doesn't stick" |
57
+ | `incident` | `fix:` commits, "Fixes #12", bugs, crashes | "fix: refresh tokens before they expire" |
58
+ | `revert` | `Revert "…"`, "This reverts commit …" | |
59
+
60
+ Each event is linked to the **functions, methods and classes** it is about, and
61
+ those links follow the code through history. whyfence indexes every Python,
62
+ JavaScript and TypeScript symbol at every commit and keeps its identity when it
63
+ is renamed, moved to another file, or renamed and edited in the same commit.
64
+
65
+ ---
66
+
67
+ ## Try it in one minute
68
+
69
+ ```console
70
+ $ git clone https://github.com/NadeemAkhtar1947/whyfence && cd whyfence
71
+ $ ./demo.sh
72
+ ```
73
+
74
+ [`demo.sh`](https://github.com/NadeemAkhtar1947/whyfence/blob/main/demo.sh) builds a throwaway repository and a mock Claude Code session in
75
+ a temp folder, then runs `whyfence lint`, `whyfence ingest`, `whyfence why` and
76
+ `whyfence history` on them. It needs only bash, git and
77
+ [uv](https://docs.astral.sh/uv/), and it never touches your own repositories or
78
+ `~/.claude`. The `why` step prints what an agent learns before touching `refresh()`:
79
+
80
+ ```text
81
+ app.tokens.refresh function · python
82
+ constraint 2026-02-10 From now on, never call `refresh` from the request
83
+ path; the background renewer owns it.
84
+ transcript · claude-code · inferred
85
+ dead_end 2026-02-10 I see the issue now: retrying here stacked on top of
86
+ the client's retries. I'll revert the loop.
87
+ transcript · claude-code · inferred
88
+ correction 2026-02-10 No, don't retry inside refresh(). The HTTP client
89
+ already retries, so this doubles every request.
90
+ transcript · claude-code · inferred
91
+ incident 2026-02-03 fix: refresh tokens before they expire
92
+ commit c69facf
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Quick start
98
+
99
+ whyfence is alpha and needs Python 3.12 or newer.
100
+
101
+ ```console
102
+ $ pip install whyfence
103
+ $ whyfence ingest /path/to/your/repo
104
+ ```
105
+
106
+ On the small repository `demo.sh` builds, that prints:
107
+
108
+ ```text
109
+ Read 3 commits from demo-app: 3 new, 3 indexed in demo-app/.whyfence/index.db
110
+ Walked 3 commits: 2 new symbols, 5 versions, 2 renamed or moved, 0 deleted
111
+ · 2 symbols indexed
112
+ Found 1 event in commit messages and 3 events in 1 Claude Code session,
113
+ with 4 symbol links
114
+ ```
115
+
116
+ Then connect your agent. For Claude Code, add `.mcp.json` to your repository
117
+ (this uses [uvx](https://docs.astral.sh/uv/); with a plain `pip install`, use
118
+ `"command": "whyfence", "args": ["mcp"]` instead):
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "whyfence": {
124
+ "type": "stdio",
125
+ "command": "uvx",
126
+ "args": ["whyfence", "mcp"]
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ Claude Desktop and other clients: see [README_MCP.md](https://github.com/NadeemAkhtar1947/whyfence/blob/main/README_MCP.md).
133
+
134
+ Re-run `whyfence ingest` whenever you like. It only processes new commits and
135
+ sessions, and it rebuilds symbol history automatically if history was rewritten.
136
+
137
+ ---
138
+
139
+ ## Temporal memory over MCP
140
+
141
+ `whyfence mcp` serves two read-only tools:
142
+
143
+ **`why(symbol_name)`**: everything recorded about why a symbol is the way it is,
144
+ newest first, each with its source (commit or session) and how confident the
145
+ extraction is.
146
+
147
+ **`history(symbol_name)`**: the symbol's timeline: each version, the commit that
148
+ introduced it, the commit that replaced it, and its name, file and lines at the time.
149
+
150
+ ```json
151
+ {
152
+ "symbol": {"name": "app.tokens.refresh_token", "kind": "function"},
153
+ "versions": [
154
+ {"name": "app.auth.refresh", "file": "app/auth.py",
155
+ "valid_from": {"sha": "3f1c…", "subject": "add auth"}},
156
+ {"name": "app.session.refresh", "file": "app/session.py",
157
+ "valid_from": {"sha": "9ab2…", "subject": "rename the file"}},
158
+ {"name": "app.tokens.refresh", "file": "app/tokens.py",
159
+ "valid_from": {"sha": "c47e…", "subject": "move refresh to its own module"}},
160
+ {"name": "app.tokens.refresh_token", "file": "app/tokens.py",
161
+ "valid_from": {"sha": "e810…", "subject": "rename and edit at once"},
162
+ "current": true}
163
+ ]
164
+ }
165
+ ```
166
+
167
+ Names can be qualified (`app.auth.refresh`), bare (`refresh`) or a symbol id. An
168
+ ambiguous bare name returns the candidates instead of guessing.
169
+
170
+ The same answers are available from the command line, for you or for scripts:
171
+
172
+ ```console
173
+ $ whyfence why refresh # readable summary
174
+ $ whyfence history app.auth.refresh
175
+ $ whyfence why refresh --json # exactly what the MCP tool returns
176
+ ```
177
+
178
+ The server tells agents to call `why` before changing code that looks wrong or
179
+ redundant, and to treat recorded constraints and dead ends as binding unless they
180
+ have new information.
181
+
182
+ ---
183
+
184
+ ## `whyfence lint`: catch stale agent instructions
185
+
186
+ Agents follow `CLAUDE.md` and `AGENTS.md` literally. When a script is renamed or a
187
+ make target is deleted, every session keeps running a command that no longer
188
+ exists. `whyfence lint` checks every command in those files against what it would
189
+ actually run:
190
+
191
+ ```console
192
+ $ whyfence lint examples/stale-repo
193
+
194
+ examples/stale-repo/CLAUDE.md 7 commands · 5 stale
195
+ ✗ L3 npm run lint no `lint` script in package.json
196
+ → did you mean npm run lint:all?
197
+ ✗ L7 npm run start no `start` script in web/package.json
198
+ ✗ L8 make -C api tests no `tests` target in api/Makefile
199
+ → did you mean make -C api test?
200
+ ✗ L9 make migrate no Makefile in ./
201
+ → did you mean make -C api migrate?
202
+ ✗ L12 make deploy no Makefile in ./
203
+
204
+ ┌──────────────────────────────────────────────────────────────────────────┐
205
+ │ ✗ 5 stale ? 0 unverifiable ✓ 2 verified │
206
+ │ 7 commands across 1 file. Agents reading these files will run commands │
207
+ │ that no longer exist. │
208
+ └──────────────────────────────────────────────────────────────────────────┘
209
+ ```
210
+
211
+ | Command | Checked against |
212
+ |---|---|
213
+ | `make test`, `make -C api migrate` | Makefile targets, including `include`d files |
214
+ | `npm run dev`, `yarn lint`, `pnpm test`, `bun run build` | `package.json` scripts and package binaries |
215
+ | `uv run serve`, `poetry run serve` | `[project.scripts]`, declared dependencies, `.venv` |
216
+ | `python -m app.jobs`, `uv run -m app` | local modules (with `__main__.py`), stdlib, dependencies |
217
+ | `python scripts/seed.py`, `./scripts/release.sh` | the file on disk |
218
+
219
+ It follows `cd dir &&` and directory flags, suggests the closest match (including
220
+ targets that moved into a subdirectory), and reports anything that depends on
221
+ runtime state as *unverifiable* instead of guessing. It exits with `1` on stale
222
+ commands, so it works as a CI gate:
223
+
224
+ ```console
225
+ $ whyfence lint --json # machine-readable
226
+ $ whyfence lint --strict # also fail on unverifiable commands
227
+ $ whyfence lint --exclude docs # skip paths (examples/ is skipped by default)
228
+ ```
229
+
230
+ ---
231
+
232
+ ## How it works
233
+
234
+ ```text
235
+ git history ──► commits ──► symbol index (tree-sitter, every commit, first-parent)
236
+ │ identity: name → body fingerprint → token similarity
237
+ ▼
238
+ commit messages ─┐ symbol_versions ◄── history()
239
+ Claude Code ├─► events ──► event_links ◄── why()
240
+ transcripts ─────┘
241
+ SQLite (WAL, FTS5) in .whyfence/index.db
242
+ ```
243
+
244
+ - **Symbol identity.** Each definition is fingerprinted with its own name removed
245
+ and whitespace collapsed. A commit's definitions are matched to existing symbols
246
+ by name in the same file, then by fingerprint anywhere in the commit (renames and
247
+ moves), then by token similarity (renamed *and* edited). Only code, file or name
248
+ changes create a new version; line shifts don't.
249
+ - **Events.** Commit events link to the symbols that commit changed. Session events
250
+ link through the edits the agent made around them, located in today's code and
251
+ re-parsed, plus symbols named in the text. Broad events (a feature commit
252
+ touching 80 functions) keep only the symbols they name.
253
+ - **Precision over recall.** Extraction is deterministic pattern matching, tuned on
254
+ real sessions. It skips tool output, subagent chatter, the agent's private
255
+ reasoning and Claude Code's own compaction summaries. A missing event costs
256
+ you nothing; a wrong one teaches your agent something false.
257
+
258
+ ## Privacy
259
+
260
+ Everything stays in `.whyfence/index.db` inside your repository, which you can
261
+ gitignore and delete at any time; it is rebuilt from history. Transcripts are read
262
+ from `~/.claude/projects` for sessions in, under or above the repository
263
+ (`--no-transcripts` to skip them). Obvious secrets (API keys, tokens) are redacted
264
+ before anything is stored. whyfence makes no network calls.
265
+
266
+ ## Status and roadmap
267
+
268
+ whyfence is an early MVP. It works end to end, but its extraction heuristics have
269
+ only been tuned on a handful of real projects.
270
+
271
+ - [x] Stale-context linter for `CLAUDE.md` / `AGENTS.md`
272
+ - [x] Symbol history with rename and move tracking (Python, JavaScript, TypeScript)
273
+ - [x] Events from commit messages and Claude Code transcripts
274
+ - [x] MCP server: `why`, `history`
275
+ - [ ] Team memory: a shared, append-only event log committed to the repo
276
+ - [ ] More agents' transcripts (Codex, Cursor, Gemini CLI)
277
+ - [ ] More languages (Go, Rust, Java)
278
+ - [ ] A `rules` tool that serves every standing constraint at session start
279
+
280
+ ## Development
281
+
282
+ ```console
283
+ $ git clone https://github.com/NadeemAkhtar1947/whyfence && cd whyfence
284
+ $ uv sync
285
+ $ uv run pytest
286
+ ```
287
+
288
+ The test suite includes an end-to-end check that indexes whyfence's own git
289
+ history and verifies every recorded version against Python's `ast`.
290
+
291
+ ## License
292
+
293
+ [MIT](https://github.com/NadeemAkhtar1947/whyfence/blob/main/LICENSE)