whygit 0.1.1 → 0.1.3
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.
- package/README.md +46 -38
- package/package.json +2 -2
- package/src/cli.js +6 -1
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
3
|
# whygit
|
|
4
4
|
|
|
5
5
|
**Git blame tells you *who*. whygit tells you *why*.**
|
|
6
6
|
|
|
7
7
|
Point it at any repository. It turns the reasons already buried in git history
|
|
8
|
-
into answers you and your coding agent can cite
|
|
8
|
+
into answers you and your coding agent can cite — file, line, commit, author, date.
|
|
9
9
|
|
|
10
10
|
[](https://www.npmjs.com/package/whygit)
|
|
11
11
|
[](https://github.com/carefreebit/whygit/actions/workflows/ci.yml)
|
|
@@ -13,7 +13,7 @@ into answers you and your coding agent can cite 鈥?file, line, commit, author,
|
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](#give-it-to-your-agent)
|
|
15
15
|
|
|
16
|
-
[
|
|
16
|
+
[中文](README.zh-CN.md) · [Quickstart](#quickstart) · [Commands](#commands) · [Agents](#give-it-to-your-agent) · [How it works](#how-it-works)
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
@@ -23,9 +23,9 @@ into answers you and your coding agent can cite 鈥?file, line, commit, author,
|
|
|
23
23
|
<img src="https://raw.githubusercontent.com/carefreebit/whygit/main/docs/demo.svg" alt="whygit why "cookie jar" in a terminal" width="900">
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
|
-
<p align="center"><sub>Real output: <code>whygit why "cookie jar"</code> on <code>psf/requests</code>
|
|
26
|
+
<p align="center"><sub>Real output: <code>whygit why "cookie jar"</code> on <code>psf/requests</code> — 4,884 commits indexed in 3s, answered in 21ms.</sub></p>
|
|
27
27
|
|
|
28
|
-
That answer existed all along
|
|
28
|
+
That answer existed all along — in a commit body from 2015 that nobody has read since.
|
|
29
29
|
whygit found it in 21 milliseconds, without an LLM, an API key, or a network call.
|
|
30
30
|
|
|
31
31
|
## The problem
|
|
@@ -38,7 +38,7 @@ redundant. A guard that seems paranoid. A `sleep(2)` that looks like laziness.
|
|
|
38
38
|
- LLM summaries of history **invent plausible reasons**. In a codebase, a plausible
|
|
39
39
|
wrong reason is worse than no reason.
|
|
40
40
|
- Coding agents are the worst affected: they rewrite the guard, delete the retry, and
|
|
41
|
-
the bug comes back six months later
|
|
41
|
+
the bug comes back six months later — because nothing told them the line was load-bearing.
|
|
42
42
|
|
|
43
43
|
whygit reads what your repository already wrote down, extracts the sentences that
|
|
44
44
|
actually explain a decision, and makes them retrievable in the two places that matter:
|
|
@@ -68,7 +68,7 @@ it is incremental: new commits are picked up automatically on the next run.
|
|
|
68
68
|
| `whygit why <words>` | Why does this behaviour exist? Ranked decisions with citations. |
|
|
69
69
|
| `whygit why <file>:<line>` | Why is *this line* still here? Blame + the decision behind it + how much of that change is still alive. |
|
|
70
70
|
| `whygit story <file>` | The biography of one file: birth, commits per year, the chapters that mattered, who kept it alive. |
|
|
71
|
-
| `whygit cursed` | Ranked risk map: churn
|
|
71
|
+
| `whygit cursed` | Ranked risk map: churn × fix density × author spread × freshness. Read before refactoring. |
|
|
72
72
|
| `whygit whylog --write` | Generate `WHYLOG.md`: load-bearing decisions + cursed files, in Markdown. |
|
|
73
73
|
| `whygit check --base origin/main` | CI gate: what fraction of newly added lines trace to a commit that said why. |
|
|
74
74
|
| `whygit mcp` | MCP server over stdio for Claude Code, Cursor, Codex, and any MCP client. |
|
|
@@ -83,30 +83,30 @@ Every command takes `--json` for scripting, and `--cwd <path>` to point at anoth
|
|
|
83
83
|
$ whygit why src/requests/models.py:120
|
|
84
84
|
|
|
85
85
|
1. basics working
|
|
86
|
-
2012-12-15
|
|
86
|
+
2012-12-15 · 366e8e84 · Kenneth Reitz · change
|
|
87
87
|
line 120 is 78 lines of this change, still in HEAD
|
|
88
88
|
|
|
89
89
|
2. Add hasattr checks for remaining protocol isinstance checks (#7505)
|
|
90
|
-
2026-06-09
|
|
90
|
+
2026-06-09 · 6f66281a · change
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
The line itself is unremarkable. What matters is that 78 lines around it are still
|
|
94
|
-
the original 2012 change
|
|
94
|
+
the original 2012 change — so the surrounding shape is load-bearing, not incidental.
|
|
95
95
|
|
|
96
96
|
### `story <file>`
|
|
97
97
|
|
|
98
98
|
```console
|
|
99
99
|
$ whygit story src/requests/models.py
|
|
100
100
|
|
|
101
|
-
born 2011-05-14
|
|
101
|
+
born 2011-05-14 · package refactor
|
|
102
102
|
|
|
103
|
-
2011
|
|
104
|
-
2012
|
|
105
|
-
2013
|
|
103
|
+
2011 ██████████████████████████████████······ 217
|
|
104
|
+
2012 ████████████████████████████████████████ 254
|
|
105
|
+
2013 ██████████████·························· 86
|
|
106
106
|
...
|
|
107
107
|
|
|
108
|
-
717 commits
|
|
109
|
-
|
|
108
|
+
717 commits · 202 authors · 154 fixes · 0 TODO markers
|
|
109
|
+
─── the plot ──────────────────────────────────────────────────
|
|
110
110
|
|
|
111
111
|
2015-04-06 Copy a PreparedRequest's CookieJar reliably
|
|
112
112
|
By adding _copy_cookie_jar we can reliably copy cookie jars so that we
|
|
@@ -119,12 +119,12 @@ $ whygit story src/requests/models.py
|
|
|
119
119
|
```console
|
|
120
120
|
$ whygit cursed
|
|
121
121
|
|
|
122
|
-
1 src/requests/models.py
|
|
123
|
-
2 src/requests/sessions.py
|
|
124
|
-
3 tests/test_requests.py
|
|
122
|
+
1 src/requests/models.py ██████████████ 8.1 717 commits · 202 authors · 154 fixes
|
|
123
|
+
2 src/requests/sessions.py █████████████· 7.5 334 commits · 110 authors · 79 fixes
|
|
124
|
+
3 tests/test_requests.py ████████████·· 7.2 366 commits · 104 authors · 76 fixes
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
Not a code-quality judgement
|
|
127
|
+
Not a code-quality judgement — a map of where history is dense enough that a
|
|
128
128
|
confident rewrite is likely to be wrong.
|
|
129
129
|
|
|
130
130
|
## Give it to your agent
|
|
@@ -159,17 +159,18 @@ Before changing a line you do not understand, call `why_line` (MCP: whygit).
|
|
|
159
159
|
If a past decision contradicts your plan, say so before editing.
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
### WHYLOG.md
|
|
162
|
+
### WHYLOG.md — history your agent reads without being asked
|
|
163
163
|
|
|
164
164
|
`whygit whylog --write` produces a Markdown file that is small enough to live in a
|
|
165
165
|
repo and specific enough to change a decision:
|
|
166
166
|
|
|
167
|
-
- **Load-bearing decisions**
|
|
168
|
-
- **The cursed files**
|
|
169
|
-
- **House rule**
|
|
167
|
+
- **Load-bearing decisions** — ranked, each with the verbatim reason, date, commit, author, and the files it touches.
|
|
168
|
+
- **The cursed files** — a table of where history is thickest.
|
|
169
|
+
- **House rule** — the one-line convention (`WHY: ...`) that makes future commits explain themselves.
|
|
170
170
|
|
|
171
171
|
It is generated from git, never hand-edited, and it diffs cleanly in review.
|
|
172
|
-
Commit it, and every agent that starts in the repo gets the context for free
|
|
172
|
+
Commit it, and every agent that starts in the repo gets the context for free —
|
|
173
|
+
no MCP required.
|
|
173
174
|
|
|
174
175
|
### CI gate
|
|
175
176
|
|
|
@@ -184,18 +185,25 @@ It is a nudge, not a linter: the fix is one sentence in the commit body.
|
|
|
184
185
|
|
|
185
186
|
```text
|
|
186
187
|
git log (two passes: metadata + numstat)
|
|
187
|
-
|
|
188
|
+
│
|
|
189
|
+
▼
|
|
190
|
+
signals: strip trailers, drop bot noise, de-duplicate squash-merge subjects,
|
|
188
191
|
keep sentences that contain a reason ("because", "so that", "to avoid",
|
|
189
|
-
"
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
"由于", "避免", "回滚" ...)
|
|
193
|
+
│
|
|
194
|
+
▼
|
|
195
|
+
SQLite .git/whygit/index.db commits · files · decisions · FTS5 · risk
|
|
196
|
+
│
|
|
197
|
+
▼
|
|
198
|
+
ranking: BM25 × confidence × churn × recency × fix-density × noise penalties
|
|
199
|
+
│
|
|
200
|
+
├── whygit why / story / cursed (terminal)
|
|
201
|
+
├── whygit whylog (WHYLOG.md)
|
|
202
|
+
└── whygit mcp (agents)
|
|
195
203
|
```
|
|
196
204
|
|
|
197
205
|
**What a "decision" is:** a commit whose message contains a sentence explaining a
|
|
198
|
-
choice. whygit does not interpret that sentence
|
|
206
|
+
choice. whygit does not interpret that sentence — it quotes it. If the message says
|
|
199
207
|
nothing, the decision has low confidence and ranks below the ones that do.
|
|
200
208
|
|
|
201
209
|
**Why it can be fast:** the whole index is built from two `git log` passes, and
|
|
@@ -203,13 +211,13 @@ queries run against FTS5 in the same process. On a laptop, 4,884 commits index i
|
|
|
203
211
|
~3 seconds and a query takes ~20 ms. No network, no model, no cache to warm.
|
|
204
212
|
|
|
205
213
|
**What it will not do:** invent a reason, summarise code, or judge quality. If the
|
|
206
|
-
history is silent, whygit says so
|
|
214
|
+
history is silent, whygit says so — silence is a finding too.
|
|
207
215
|
|
|
208
216
|
## Compared to
|
|
209
217
|
|
|
210
218
|
| | Answers "why" | Cites the source | Works offline | Agent-native |
|
|
211
219
|
| --- | :---: | :---: | :---: | :---: |
|
|
212
|
-
| `git blame` | no |
|
|
220
|
+
| `git blame` | no | — | yes | no |
|
|
213
221
|
| `git log` | only if you already know where to look | yes | yes | no |
|
|
214
222
|
| LLM-generated docs | sometimes | rarely | no | partly |
|
|
215
223
|
| **whygit** | **yes** | **yes (sha + file + line)** | **yes** | **yes (MCP + WHYLOG.md)** |
|
|
@@ -238,7 +246,7 @@ index under `.git/`. History is read-only.
|
|
|
238
246
|
- Rationale can only be as good as commit messages. whygit makes silence visible; it cannot fill it.
|
|
239
247
|
- Very large histories (500k+ commits) index in minutes, not seconds.
|
|
240
248
|
- Renames are followed by path, not by content similarity: a file moved twice may have two histories.
|
|
241
|
-
- The ranking is heuristic. It is tuned to prefer cited reasons over churn, and it will still be wrong sometimes
|
|
249
|
+
- The ranking is heuristic. It is tuned to prefer cited reasons over churn, and it will still be wrong sometimes — each result shows its citations so you can check.
|
|
242
250
|
|
|
243
251
|
## Contributing
|
|
244
252
|
|
|
@@ -249,7 +257,7 @@ npm run lint
|
|
|
249
257
|
node bin/whygit.js why "..." --cwd /path/to/any/repo
|
|
250
258
|
```
|
|
251
259
|
|
|
252
|
-
Issues and PRs welcome
|
|
260
|
+
Issues and PRs welcome — especially real-world ranking failures. Paste the query, the
|
|
253
261
|
repo, and what should have ranked first.
|
|
254
262
|
|
|
255
263
|
## License
|
|
@@ -263,6 +271,6 @@ MIT. See [LICENSE](LICENSE).
|
|
|
263
271
|
If whygit saved you from deleting a load-bearing line, a star helps the next person
|
|
264
272
|
find it.
|
|
265
273
|
|
|
266
|
-
**[
|
|
274
|
+
**[⭐ Star whygit](https://github.com/carefreebit/whygit)**
|
|
267
275
|
|
|
268
276
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whygit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Git blame tells you who. whygit tells you why. Turn git history into cited, agent-readable context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"test": "node --test test/signals.test.js test/index.test.js",
|
|
21
|
+
"test": "node --test test/signals.test.js test/index.test.js test/version.test.js",
|
|
22
22
|
"lint": "node scripts/lint.js"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
package/src/cli.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** whygit command line. */
|
|
2
2
|
import process from 'node:process';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
3
6
|
import { c, width } from './util.js';
|
|
4
7
|
import { cmdWhy } from './cmd-why.js';
|
|
5
8
|
import { cmdStory } from './cmd-story.js';
|
|
@@ -12,7 +15,9 @@ import { loadContext, repoLabel } from './context.js';
|
|
|
12
15
|
import { buildWhylog, writeWhylog } from './whylog.js';
|
|
13
16
|
import { header } from './ui.js';
|
|
14
17
|
|
|
15
|
-
const VERSION =
|
|
18
|
+
const VERSION = JSON.parse(
|
|
19
|
+
readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8'),
|
|
20
|
+
).version;
|
|
16
21
|
|
|
17
22
|
const HELP = [
|
|
18
23
|
'',
|