whynot-cli 0.1.0 → 0.1.1

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 CHANGED
@@ -1,68 +1,66 @@
1
- # whynot
2
-
3
- **Record what you ruled out — and why — so your coding agent stops re-suggesting it.**
4
-
5
- Your agent starts every session from zero. The approach you rejected last week,
6
- it will propose again this week. Three months from now, you will not remember
7
- why you rejected it either.
8
-
9
- Memory tools record **what you did**. That is already in your code, your
10
- commits, and your pull requests. What exists nowhere is **why you did not do
11
- something** — and it lives only in your head, decaying.
12
-
13
- `whynot` is a folder of Markdown files in your repo, plus a thin MCP server.
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="assets/icon-dark.svg">
4
+ <img src="assets/icon-light.svg" alt="whynot" width="88">
5
+ </picture>
6
+ </p>
7
+
8
+ <h1 align="center">whynot</h1>
9
+
10
+ <p align="center">
11
+ <b>Your agent keeps re-suggesting what you already rejected.</b><br>
12
+ Record the rejection once. It expires. Your agent stops bringing it up.
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="https://www.npmjs.com/package/whynot-cli"><img src="https://img.shields.io/npm/v/whynot-cli?style=flat-square&color=111111&label=npm" alt="npm"></a>
17
+ <img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT">
18
+ <img src="https://img.shields.io/badge/node-%E2%89%A520-brightgreen?style=flat-square" alt="node 20+">
19
+ <img src="https://img.shields.io/badge/tests-29%20passing-brightgreen?style=flat-square" alt="29 tests passing">
20
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square" alt="zero dependencies">
21
+ </p>
22
+
23
+ <p align="center">
24
+ <img src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey?style=flat-square" alt="platforms">
25
+ <img src="https://img.shields.io/badge/MCP-compatible-8A2BE2?style=flat-square" alt="MCP compatible">
26
+ <img src="https://img.shields.io/badge/Claude%20Code%20%C2%B7%20Codex%20%C2%B7%20Cursor%20%C2%B7%20Gemini%20CLI-supported-informational?style=flat-square" alt="agent support">
27
+ </p>
28
+
29
+ <p align="center">
30
+ <a href="README.md">English</a> · <a href="README.zh-CN.md">简体中文</a>
31
+ </p>
14
32
 
15
33
  ---
16
34
 
17
- ## What we found
18
-
19
- Six experiments, three model families, mostly n=20 per cell. Full write-ups and
20
- scripts are in [`docs/`](docs/) and [`experiments/`](experiments/).
21
-
22
- ### Age does not matter. Presence does
23
-
24
- A recorded decision is followed at the same rate whether it was written a month
25
- ago or seven years ago:
26
-
27
- | Age of the decision | Recommends the rejected option |
28
- |---|---|
29
- | none (baseline) | 100% |
30
- | 30 days | **0%** |
31
- | 1 year | **0%** |
32
- | 7 years | **0%** |
33
- | 7 years, expired and hidden | **100%** |
35
+ <p align="center">
36
+ <a href="#the-problem">The problem</a> ·
37
+ <a href="#install">Install</a> ·
38
+ <a href="#quick-start">Quick start</a> ·
39
+ <a href="#how-it-works">How it works</a> ·
40
+ <a href="#what-the-evidence-says">Evidence</a> ·
41
+ <a href="#docs">Docs</a> ·
42
+ <a href="#license">License</a>
43
+ </p>
34
44
 
35
- Same decision text every time; only the `created` date changed. The model does
36
- not discount old records — it treats every record as current.
37
-
38
- Adding "this is old, be skeptical" to the prompt did not help. Hiding the
39
- expired entry did. That is why expiry is the core of this tool.
40
-
41
- ### Recording a rejection changes the answer
45
+ ---
42
46
 
43
- At n=20 across three families, a recorded rejection moved the recommendation by
44
- 78–87 percentage points (Fisher p < 0.0001).
47
+ ## The problem
45
48
 
46
- ### Two honest qualifications
49
+ You tried Redis for session storage. It failed at 8 seconds during a regional
50
+ outage, so you moved to Postgres.
47
51
 
48
- - **Not every model obeys equally.** With real code in context, one model
49
- inferred the answer from the repository and ignored the record; another still
50
- preferred the rejected option until the record stopped it.
51
- - **The stated reason matters more than the date.** When the record said "the
52
- team did not know TypeScript", about one in five responses attacked that
53
- premise and recommended TypeScript anyway. When it said "this project's
54
- fields change constantly", obedience was total. A reason that names a
55
- *condition* invites re-checking; a reason that states a *property* does not.
52
+ Three weeks later, in a new session, your agent suggests Redis.
56
53
 
57
- ### What is not tested
54
+ You explain again. A month after that you switch from Claude Code to Codex, and
55
+ explain a third time. Six months later you no longer remember why Redis was
56
+ rejected — and neither does anyone else on the team.
58
57
 
59
- **We have not measured whether this helps a developer in real use.** Every
60
- experiment here is a controlled, single-turn task. The number that matters —
61
- how often an agent re-proposes something you already rejected, over weeks of
62
- real work — has not been measured, and this project does not claim it.
58
+ **Code remembers what you chose. Nothing remembers what you rejected.**
63
59
 
64
- The claim we do make: *the mechanism works under test, and the scripts are here
65
- so you can check.*
60
+ Memory tools record what happened. That is already in your commits, your pull
61
+ requests, and the code itself. `whynot` records the other half: the options you
62
+ ruled out, and the reason, in a file your agent reads before it proposes
63
+ anything.
66
64
 
67
65
  ---
68
66
 
@@ -72,82 +70,63 @@ so you can check.*
72
70
  npx whynot-cli init
73
71
  ```
74
72
 
75
- Then paste the block from [`AGENTS.snippet.md`](AGENTS.snippet.md) into your
76
- `AGENTS.md` or `CLAUDE.md`, and register the MCP server.
77
-
78
- Node 20+. Nothing else. No global install, no account, no telemetry.
79
-
80
- ### Register the MCP server
73
+ Nothing to install globally, no account, no telemetry. Node 20+ is the only
74
+ requirement.
81
75
 
82
- The same shape works for Claude Code, Claude Desktop, Cursor, and most other
83
- MCP clients:
76
+ ---
84
77
 
85
- ```json
86
- {
87
- "mcpServers": {
88
- "whynot": {
89
- "command": "npx",
90
- "args": ["-y", "whynot-cli", "mcp"]
91
- }
92
- }
93
- }
94
- ```
78
+ ## Quick start
95
79
 
96
- ---
80
+ ### 1. See what will never expire
97
81
 
98
- ## Use
82
+ `audit` is read-only. It scans your existing `AGENTS.md`, `CLAUDE.md`, and
83
+ `.cursor/rules` for absolute rules that state no condition — the ones that will
84
+ be obeyed forever.
99
85
 
100
86
  ```bash
101
- npx whynot-cli audit # scan your existing rules for ones that will never expire
102
- npx whynot-cli init # create .decisions/
103
- npx whynot-cli add --title "..." --why "..."
104
- npx whynot-cli check redis # is this already settled?
105
- npx whynot-cli stale # what needs a human decision
106
- npx whynot-cli renew 0001 # it still holds — extend it
107
- npx whynot-cli retire 0002 # it no longer holds — archive it
87
+ npx whynot-cli audit
108
88
  ```
109
89
 
110
- Most of the time you will not run these by hand. The agent calls
111
- `decisions_record` when it discovers something does not work, and
112
- `decisions_check` before proposing an approach.
113
-
114
- ### Start with `audit`
115
-
116
- `audit` is read-only. It scans `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`,
117
- `.cursor/rules/*`, and `.github/copilot-instructions.md` for absolute rules
118
- that state no condition — the kind that get obeyed forever.
119
-
120
90
  ```
121
91
  Scanned 2 agent config files:
122
92
  AGENTS.md (last changed 412d ago)
123
93
  CLAUDE.md (last changed 8d ago)
124
94
 
125
- 4 rules look like permanent constraints:
95
+ 3 rules look like permanent constraints:
126
96
 
127
97
  AGENTS.md:3
128
98
  "Never use TypeScript in this repo."
129
99
  → absolute rule with no stated condition; unchanged for 412 days
130
100
  ```
131
101
 
132
- It is a heuristic, not a verdict. If it finds nothing, that is useful too.
102
+ ### 2. Create the store
133
103
 
134
- ---
104
+ ```bash
105
+ npx whynot-cli init
106
+ ```
135
107
 
136
- ## The format
108
+ This creates `.decisions/` and adds `*.md text eol=lf` to `.gitattributes`.
137
109
 
138
- ```
139
- .decisions/
140
- └── 0001-session-not-redis.md
110
+ ### 3. Record a rejection
111
+
112
+ ```bash
113
+ npx whynot-cli add \
114
+ --title "session 不存 Redis" \
115
+ --why "us-east 故障时读延迟 8s,SLO 要求 p99 < 500ms" \
116
+ --type rejected \
117
+ --revisit "Redis 支持跨区域强一致时" \
118
+ --evidence pr:412 \
119
+ --tags infra,session
141
120
  ```
142
121
 
143
122
  ```markdown
144
123
  ---
145
124
  id: 0001
146
125
  title: session 不存 Redis
147
- type: rejected # rejected | decision | constraint
148
- status: live # live | retired
149
- created: 2026-10-14
150
- expires_on: 2027-01-12
126
+ type: rejected
127
+ status: live
128
+ created: 2026-09-21
129
+ expires_on: 2026-12-20
151
130
  revisit_when: "Redis 支持跨区域强一致时"
152
131
  evidence:
153
132
  - "pr:412"
@@ -161,79 +140,126 @@ tags: [infra, session]
161
140
  试过 Redis + 单写区域。us-east 故障时读延迟飙到 8s,SLO 要求 p99 < 500ms。
162
141
  ```
163
142
 
164
- `rejected` entries are the valuable ones. They are the only kind that cannot be
165
- recovered from the code.
143
+ ### 4. Give it to your agent
166
144
 
167
- ### Expiry
145
+ Add the MCP server:
168
146
 
169
- Every entry expires: 90 days for a rejection, 180 for a decision, 365 for a
170
- constraint.
171
-
172
- When an entry expires it **stops being shown to the agent**. It is not deleted.
173
- `whynot stale` lists it, and you either renew it or retire it.
147
+ ```json
148
+ {
149
+ "mcpServers": {
150
+ "whynot": {
151
+ "command": "npx",
152
+ "args": ["-y", "whynot-cli", "mcp"]
153
+ }
154
+ }
155
+ }
156
+ ```
174
157
 
175
- This is the whole safety mechanism. An expired entry is invisible, because
176
- telling an agent "this is old, be skeptical" was tested and does not work.
158
+ Then paste the block from [`AGENTS.snippet.md`](AGENTS.snippet.md) into your
159
+ `AGENTS.md` or `CLAUDE.md`. The MCP server gives the agent the tools; the
160
+ snippet is what makes it actually use them.
177
161
 
178
- If something genuinely never expires, write `expires_on: never`. It should be
179
- rare, and it should be a deliberate decision.
162
+ > **If MCP is unavailable, the format still works.** `.decisions/` is plain
163
+ > Markdown, so the agent can read and grep it directly. This was observed in the
164
+ > real-agent test, not assumed.
180
165
 
181
166
  ---
182
167
 
183
- ## Why this shape
168
+ ## How it works
184
169
 
185
- - **Markdown in git, not a database.** You can read it, diff it, review it,
186
- revert it. If this tool disappears tomorrow, `grep` still works.
187
- - **No vector store, no embeddings, no model call when writing.** Each of those
188
- is a failure mode we saw in existing tools: index staleness, write-path cost,
189
- and results nobody can audit.
190
- - **Expiry is the default.** This is the part that differs from every other
191
- memory tool. See [docs/validation.md](docs/validation.md) for the experiment.
192
- - **The format works without the tool.** The agent can read `.decisions/`
193
- directly if MCP is unavailable. This was observed, not just designed.
170
+ ### Every entry expires
194
171
 
195
- ---
172
+ | Type | Default lifetime |
173
+ |---|---|
174
+ | `rejected` | 90 days |
175
+ | `decision` | 180 days |
176
+ | `constraint` | 365 days |
196
177
 
197
- ## What this is not
178
+ When an entry lapses it **stops reaching your agent**. Not annotated, not
179
+ warned about — absent. `whynot stale` lists it, and you either renew it or
180
+ retire it.
198
181
 
199
- - Not a vector database or a retrieval engine.
200
- - Not a hosted service. Your reasoning about your own codebase stays on your
201
- machine.
202
- - Not a replacement for `AGENTS.md`. That tells the agent how to behave; this
203
- records what has already been settled.
204
- - Not automatic. If nobody records anything, it does nothing.
182
+ This is the core of the design. Telling an agent "this note is old, be
183
+ skeptical" was tested and does not work. Removing the note from view does.
184
+
185
+ ### Three entry types
186
+
187
+ | Type | Use it for |
188
+ |---|---|
189
+ | **`rejected`** | An approach you tried and abandoned. The most valuable kind — nothing else in the repository remembers it. |
190
+ | `decision` | A choice between viable options, with the reason |
191
+ | `constraint` | A hard requirement the project must satisfy |
192
+
193
+ ### Written for humans, by design
194
+
195
+ - **Markdown in git.** Read it, diff it, review it, revert it. If this tool
196
+ disappears, `grep` still works.
197
+ - **No vector store, no embeddings, no model call on write.** Each of those is
198
+ a failure mode: index staleness, write-path cost, and results nobody can
199
+ audit.
200
+ - **No daemon, no telemetry, no hosted service.** Your reasoning about your own
201
+ codebase stays on your machine.
205
202
 
206
203
  ---
207
204
 
208
- ## Cross-platform notes
205
+ ## What the evidence says
206
+
207
+ Six experiments, three model families, mostly n=20 per cell.
208
+
209
+ | Question | Result |
210
+ |---|---|
211
+ | Does the age of a decision matter? | **No.** 30 days and 7 years were obeyed at the same rate (0/12 each). Expired → 12/12. |
212
+ | Does recording a rejection change the answer? | **Yes.** 78–87 percentage points, p < 0.0001. |
213
+ | Does an irrelevant record distort unrelated work? | **No.** 0/30. |
214
+ | Does a real agent use it across sessions? | **Yes.** Codex respected the decision in 9/9 sessions. |
215
+
216
+ Two qualifications worth knowing before you install:
209
217
 
210
- Tested on Windows, macOS, and Linux.
218
+ - **About one in five responses resisted a stale record** — by attacking its
219
+ stated reason. A reason that names a *condition* ("the team did not know
220
+ TypeScript") invites re-checking; one that states a *property* ("this project
221
+ changes constantly") does not.
222
+ - **With real code in context, some agents infer the answer from the repository**
223
+ and ignore the record. Records matter most for what code cannot express: the
224
+ option you rejected.
211
225
 
212
- - `init` writes `*.md text eol=lf` to `.gitattributes`. Without it, Windows
213
- checkouts write CRLF and every diff shows the whole file as changed.
214
- - Filenames are slugged to `[a-z0-9]` plus CJK. Windows forbids
215
- `\ / : * ? " < > |` and both Windows and macOS are case-insensitive, so
216
- filenames that work on Linux can break elsewhere.
217
- - No daemon and no file watching. The tool runs when it is called.
218
- - If you use non-ASCII filenames, run `git config core.quotepath false`.
226
+ Full results, methods, and reproduction scripts: [`docs/findings.md`](docs/findings.md).
219
227
 
220
228
  ---
221
229
 
222
- ## Verify it yourself
230
+ ## Docs
223
231
 
224
- ```bash
225
- npm test # 29 tests, no dependencies
226
- node experiments/e3/run.mjs # the statistical-power experiment
227
- node experiments/e4/run.mjs # the age / expiry experiment
228
- node experiments/e5/run.mjs # a real agent across real sessions
229
- ```
232
+ | | |
233
+ |---|---|
234
+ | [Design](docs/design.md) | Why the tool looks like this |
235
+ | [Findings](docs/findings.md) | Every experiment, what it found, what it did not |
236
+ | [Validation](docs/validation.md) | Index of the detailed write-ups |
237
+ | [Pre-registration](docs/preregistration.md) | Decision rules fixed before the data |
238
+ | [Contributing](CONTRIBUTING.md) | The four design constraints that are not negotiable |
239
+ | [AGENTS snippet](AGENTS.snippet.md) | The block that makes your agent use this |
230
240
 
231
- See [`experiments/README.md`](experiments/README.md) for all six and for the
232
- conventions they follow.
241
+ ---
242
+
243
+ ## CLI reference
233
244
 
234
- The experiments call models through an OpenAI-compatible endpoint. Set
235
- `MAGPIE_URL` or edit the gateway constant at the top of each script. They are
236
- not part of the tool; they are how we checked the claims.
245
+ ```
246
+ whynot init Create .decisions/ and the .gitattributes rule
247
+ whynot add --title T --why W Record an entry
248
+ [--type rejected|decision|constraint]
249
+ [--revisit "condition"] When to reconsider
250
+ [--ttl N] Days until it expires
251
+ [--expires never] Explicitly permanent
252
+ [--evidence pr:412] Repeatable
253
+ [--tags infra,session]
254
+ whynot list [--all] List live entries
255
+ whynot check "<query>" Show live entries matching a query
256
+ whynot stale Entries needing a human decision
257
+ whynot renew <id> [--days N] Extend an entry's life
258
+ whynot retire <id> Archive an entry (never deletes)
259
+ whynot audit [--days N] Scan agent config files (read-only)
260
+ whynot doctor Validate the store
261
+ whynot mcp Run the MCP server on stdio
262
+ ```
237
263
 
238
264
  ---
239
265
 
@@ -244,4 +270,6 @@ There is nothing to upsell you.
244
270
 
245
271
  ---
246
272
 
247
- [中文说明](README.zh-CN.md) · [Design](docs/design.md) · [Validation](docs/validation.md)
273
+ <p align="center">
274
+ <sub><a href="README.zh-CN.md">简体中文</a> · <a href="https://github.com/Stakenshake/whynot">GitHub</a> · <a href="https://www.npmjs.com/package/whynot-cli">npm</a></sub>
275
+ </p>
package/README.zh-CN.md CHANGED
@@ -1,128 +1,126 @@
1
- # whynot
2
-
3
- **记下你否决了什么、为什么否决——让你的编码 agent 不再重提。**
4
-
5
- 你的 agent 每次开新会话都从零开始。你上周否决的方案,它这周还会再提一遍。三个月后,你自己也忘了当初为什么否决。
6
-
7
- 记忆类工具记录的是「**你做了什么**」。那些在代码、commit、PR 里都已经有了。真正哪儿都不存在的是「**你为什么没做**」——它只在你脑子里,而且在慢慢腐烂。
8
-
9
- `whynot` 就是你仓库里一个放 Markdown 的文件夹,外加一个很薄的 MCP server。
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="assets/icon-dark.svg">
4
+ <img src="assets/icon-light.svg" alt="whynot" width="88">
5
+ </picture>
6
+ </p>
7
+
8
+ <h1 align="center">whynot</h1>
9
+
10
+ <p align="center">
11
+ <b>你的 agent 一直在重提你已经否决过的方案。</b><br>
12
+ 把它记一次。它会过期。agent 就不再提了。
13
+ </p>
14
+
15
+ <p align="center">
16
+ <a href="https://www.npmjs.com/package/whynot-cli"><img src="https://img.shields.io/npm/v/whynot-cli?style=flat-square&color=111111&label=npm" alt="npm"></a>
17
+ <img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT">
18
+ <img src="https://img.shields.io/badge/node-%E2%89%A520-brightgreen?style=flat-square" alt="node 20+">
19
+ <img src="https://img.shields.io/badge/tests-29%20passing-brightgreen?style=flat-square" alt="29 tests passing">
20
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square" alt="zero dependencies">
21
+ </p>
22
+
23
+ <p align="center">
24
+ <img src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey?style=flat-square" alt="platforms">
25
+ <img src="https://img.shields.io/badge/MCP-compatible-8A2BE2?style=flat-square" alt="MCP compatible">
26
+ <img src="https://img.shields.io/badge/Claude%20Code%20%C2%B7%20Codex%20%C2%B7%20Cursor%20%C2%B7%20Gemini%20CLI-supported-informational?style=flat-square" alt="agent support">
27
+ </p>
28
+
29
+ <p align="center">
30
+ <a href="README.md">English</a> · <a href="README.zh-CN.md">简体中文</a>
31
+ </p>
10
32
 
11
33
  ---
12
34
 
13
- ## 我们测到了什么
14
-
15
- 六个实验,三个模型家族,多数条件 n=20。完整写稿和脚本在 [`docs/`](docs/) 和 [`experiments/`](experiments/)。
16
-
17
- ### 年龄不重要,存在本身重要
18
-
19
- 同一条决策,只改创建日期,被遵循的比例完全一样:
20
-
21
- | 决策的年龄 | 推荐被否决的选项 |
22
- |---|---|
23
- | 没有记录(基线) | 100% |
24
- | 30 天 | **0%** |
25
- | 1 年 | **0%** |
26
- | 7 年 | **0%** |
27
- | 7 年,已过期不可见 | **100%** |
35
+ <p align="center">
36
+ <a href="#problem">问题</a> ·
37
+ <a href="#install">安装</a> ·
38
+ <a href="#quick-start">快速开始</a> ·
39
+ <a href="#how-it-works">工作原理</a> ·
40
+ <a href="#evidence">证据</a> ·
41
+ <a href="#docs">文档</a> ·
42
+ <a href="#license">许可证</a>
43
+ </p>
28
44
 
29
- **模型不按年龄打折——它把每一条记录都当成现行的。**
30
-
31
- 在提示词里加「这条很旧,请保持怀疑」没有用;**让过期条目不可见**才有用。这就是为什么「过期」是这个工具的核心,而不是可选项。
32
-
33
- ### 记录一条否决,会实质改变结论
45
+ ---
34
46
 
35
- n=20、三个模型家族合并:一条被记录的否决把推荐改变 **78–87 个百分点**(Fisher p < 0.0001)。
47
+ <a id="problem"></a>
48
+ ## 问题
36
49
 
37
- ### 两个必须说的限定
50
+ 你试过用 Redis 存 session。一次区域故障时它读到 8 秒,于是你换成了 Postgres。
38
51
 
39
- - **不是所有模型都一样服从。** 当真实代码摆在面前时,一个模型直接从仓库推断出答案、无视了记录;另一个仍然倾向于被否决的方案,直到记录拦住它。
40
- - **写的理由比日期更重要。** 当记录写的是「团队当时不会 TS」,约五分之一的回复会攻击这个前提、照样推荐 TypeScript;当写的是「这个项目字段变更频繁」,服从是 100%。**命名了「条件」的理由会邀请复核,陈述了「属性」的理由不会。**
52
+ 三周后,一个新会话里,你的 agent 建议用 Redis。
41
53
 
42
- ### 没有测的东西
54
+ 你又解释了一遍。一个月后你从 Claude Code 换到 Codex,第三次解释。半年后,你自己也记不清当初为什么否决 Redis 了——团队里其他人更不知道。
43
55
 
44
- **我们没有测过它在真实使用中是否帮得上忙。** 这里每个实验都是受控的单轮任务。真正重要的那个数字——几周真实工作里,agent 重提已否决方案的次数有没有下降——没有测,这个项目也不声称它。
56
+ **代码记得住你选了什么。没有任何东西记得住你否决了什么。**
45
57
 
46
- 我们声称的是:**机制在测试条件下有效,而且脚本都在,你可以自己查。**
58
+ 记忆类工具记录的是「发生了什么」,那些在 commit、PR 和代码里本来就有。`whynot` 记录另一半:**你排除了哪些方案、为什么**,放在一个 agent 提议任何东西之前会读的文件里。
47
59
 
48
60
  ---
49
61
 
62
+ <a id="install"></a>
50
63
  ## 安装
51
64
 
52
65
  ```bash
53
66
  npx whynot-cli init
54
67
  ```
55
68
 
56
- 然后把 [`AGENTS.snippet.md`](AGENTS.snippet.md) 里的那一段粘进你的 `AGENTS.md` 或 `CLAUDE.md`,再注册 MCP server。
69
+ 不需要全局安装,不用注册账号,没有遥测。只需要 Node 20+。
57
70
 
58
- 需要 Node 20+。没有别的依赖。不用全局安装,不用注册账号,没有遥测。
71
+ ---
59
72
 
60
- ### 注册 MCP server
73
+ <a id="quick-start"></a>
74
+ ## 快速开始
61
75
 
62
- Claude Code、Claude Desktop、Cursor 以及大多数 MCP 客户端都用同样的格式:
76
+ ### 1. 先看哪些规则永远不会过期
63
77
 
64
- ```json
65
- {
66
- "mcpServers": {
67
- "whynot": {
68
- "command": "npx",
69
- "args": ["-y", "whynot-cli", "mcp"]
70
- }
71
- }
72
- }
73
- ```
74
-
75
- ---
76
-
77
- ## 使用
78
+ `audit` 是只读的。它扫描你现有的 `AGENTS.md`、`CLAUDE.md`、`.cursor/rules`,找出那些**用绝对语气、却没写任何条件**的规则——会被永久遵守的那些。
78
79
 
79
80
  ```bash
80
- npx whynot-cli audit # 扫描你现有的规则,找出永远不会过期的那些
81
- npx whynot-cli init # 创建 .decisions/
82
- npx whynot-cli add --title "..." --why "..."
83
- npx whynot-cli check redis # 这件事是不是已经定了?
84
- npx whynot-cli stale # 哪些需要人来决定
85
- npx whynot-cli renew 0001 # 仍然成立 —— 续期
86
- npx whynot-cli retire 0002 # 不再成立 —— 归档
81
+ npx whynot-cli audit
87
82
  ```
88
83
 
89
- 大多数时候你不会手动跑这些。agent 在发现某个方案行不通时会调 `decisions_record`,在提议方案之前会调 `decisions_check`。
90
-
91
- ### 从 `audit` 开始
92
-
93
- `audit` 是只读的。它扫描 `AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`.cursor/rules/*`、`.github/copilot-instructions.md`,找出那些**用绝对语气、但没写任何条件**的规则——也就是会被永久遵守的那一类。
94
-
95
84
  ```
96
85
  Scanned 2 agent config files:
97
86
  AGENTS.md (last changed 412d ago)
98
87
  CLAUDE.md (last changed 8d ago)
99
88
 
100
- 4 rules look like permanent constraints:
89
+ 3 rules look like permanent constraints:
101
90
 
102
91
  AGENTS.md:3
103
92
  "Never use TypeScript in this repo."
104
93
  → absolute rule with no stated condition; unchanged for 412 days
105
94
  ```
106
95
 
107
- 这是启发式检查,不是判决。**扫出来是 0 条,也是有用的信息。**
96
+ ### 2. 创建记录目录
108
97
 
109
- ---
98
+ ```bash
99
+ npx whynot-cli init
100
+ ```
110
101
 
111
- ## 格式
102
+ 创建 `.decisions/`,并往 `.gitattributes` 写入 `*.md text eol=lf`。
112
103
 
113
- ```
114
- .decisions/
115
- └── 0001-session-不存-redis.md
104
+ ### 3. 记录一条否决
105
+
106
+ ```bash
107
+ npx whynot-cli add \
108
+ --title "session 不存 Redis" \
109
+ --why "us-east 故障时读延迟 8s,SLO 要求 p99 < 500ms" \
110
+ --type rejected \
111
+ --revisit "Redis 支持跨区域强一致时" \
112
+ --evidence pr:412 \
113
+ --tags infra,session
116
114
  ```
117
115
 
118
116
  ```markdown
119
117
  ---
120
118
  id: 0001
121
119
  title: session 不存 Redis
122
- type: rejected # rejected | decision | constraint
123
- status: live # live | retired
124
- created: 2026-10-14
125
- expires_on: 2027-01-12
120
+ type: rejected
121
+ status: live
122
+ created: 2026-09-21
123
+ expires_on: 2026-12-20
126
124
  revisit_when: "Redis 支持跨区域强一致时"
127
125
  evidence:
128
126
  - "pr:412"
@@ -136,64 +134,117 @@ tags: [infra, session]
136
134
  试过 Redis + 单写区域。us-east 故障时读延迟飙到 8s,SLO 要求 p99 < 500ms。
137
135
  ```
138
136
 
139
- `rejected`(已否决)是最值钱的一类。它是唯一无法从代码里恢复出来的东西。
140
-
141
- ### 过期
137
+ ### 4. 交给你的 agent
142
138
 
143
- 每条记录都会过期:否决类 90 天,决策类 180 天,约束类 365 天。
139
+ 加上 MCP server:
144
140
 
145
- 过期之后,它**不再展示给 agent**,但**不会被删除**。`whynot stale` 会列出来,你决定续期还是归档。
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "whynot": {
145
+ "command": "npx",
146
+ "args": ["-y", "whynot-cli", "mcp"]
147
+ }
148
+ }
149
+ }
150
+ ```
146
151
 
147
- 这就是整个安全机制。过期条目是**不可见**的——因为「告诉 agent 这条很旧、请怀疑它」已经被实验证明不管用。
152
+ 然后把 [`AGENTS.snippet.zh-CN.md`](AGENTS.snippet.zh-CN.md) 里的那一段粘进你的 `AGENTS.md` 或 `CLAUDE.md`。MCP server 提供工具,这段指令才让 agent 真的去用。
148
153
 
149
- 如果某件事真的永远不过期,写 `expires_on: never`。这应该很少见,而且应该是一个刻意的决定。
154
+ > **MCP 不可用时,格式依然能用。** `.decisions/` 就是普通 Markdown,agent 可以直接读、直接 grep。这一条在真实 agent 测试里被观察到,不是假设。
150
155
 
151
156
  ---
152
157
 
153
- ## 为什么是这个形态
158
+ <a id="how-it-works"></a>
159
+ ## 工作原理
154
160
 
155
- - **Markdown 放在 git 里,不是数据库。** 你能读、能 diff、能 review、能回滚。就算这个工具明天消失,`grep` 依然能用。
156
- - **不用向量库、不用 embedding、写入时不调模型。** 这每一条都是我们在现有工具里实际看到的失败模式:索引过期、写入成本、以及没人能审计的结果。
157
- - **默认过期。** 这是它和其他所有记忆工具最不一样的地方。实验见 [docs/validation.zh-CN.md](docs/validation.zh-CN.md)。
158
- - **没有工具时格式依然可用。** MCP 不可用时,agent 可以直接读 `.decisions/`。这一点是观察到的,不只是设计出来的。
161
+ ### 每条记录都会过期
159
162
 
160
- ---
163
+ | 类型 | 默认有效期 |
164
+ |---|---|
165
+ | `rejected`(已否决) | 90 天 |
166
+ | `decision`(决策) | 180 天 |
167
+ | `constraint`(约束) | 365 天 |
161
168
 
162
- ## 它不是什么
169
+ 到期后,它**不再到达你的 agent**。不加注释、不加警告——直接不存在。`whynot stale` 会列出来,你决定续期还是归档。
163
170
 
164
- - 不是向量数据库,也不是检索引擎。
165
- - 不是托管服务。你对自己代码库的推理过程留在你自己的机器上。
166
- - 不替代 `AGENTS.md`。那个告诉 agent 该怎么做事;这个记录哪些事已经定了。
167
- - 不是全自动的。没人记,它就什么也不做。
171
+ 这是整个设计的核心。让 agent「对旧笔记保持怀疑」被实测证明没有用;**把它从视野里移走**才有用。
172
+
173
+ ### 三种记录
174
+
175
+ | 类型 | 用来记什么 |
176
+ |---|---|
177
+ | **`rejected`** | 试过并放弃的方案。**最值钱的一类**——仓库里没有别的东西记得它。 |
178
+ | `decision` | 在几个可行方案之间做的选择,附理由 |
179
+ | `constraint` | 项目必须满足的硬约束 |
180
+
181
+ ### 刻意做得很轻
182
+
183
+ - **Markdown 放在 git 里。** 能读、能 diff、能 review、能回滚。工具消失了,`grep` 依然能用。
184
+ - **不用向量库、不用 embedding、写入时不调模型。** 每一条都是失败模式:索引过期、写入成本、没人能审计的结果。
185
+ - **没有守护进程、没有遥测、没有托管服务。** 你对自己代码库的推理留在你自己的机器上。
168
186
 
169
187
  ---
170
188
 
171
- ## 跨平台说明
189
+ <a id="evidence"></a>
190
+ ## 证据
191
+
192
+ 六个实验,三个模型家族,多数条件 n=20。
193
+
194
+ | 问题 | 结果 |
195
+ |---|---|
196
+ | 决策的年龄重要吗? | **不重要。** 30 天和 7 年被遵循的比例完全一样(各 0/12)。过期后 → 12/12。 |
197
+ | 记录否决会改变结论吗? | **会。** 78–87 个百分点,p < 0.0001。 |
198
+ | 不相关的记录会扭曲无关工作吗? | **不会。** 0/30。 |
199
+ | 真实 agent 会跨会话使用它吗? | **会。** Codex 在 9/9 个会话里遵守了决策。 |
200
+
201
+ 安装之前,有两条需要知道:
172
202
 
173
- Windows / macOS / Linux 都测过。
203
+ - **约五分之一的回复抵抗了陈旧记录**——方式是攻击记录里写的原因。命名了**「条件」**的理由(「团队当时不会 TS」)会邀请复核;陈述了**「属性」**的理由(「这个项目变更频繁」)不会。
204
+ - **当真实代码在上下文里时,有些 agent 会直接从仓库推断答案**、无视记录。记录最有价值的地方,是代码表达不了的东西:**你否决掉的方案**。
174
205
 
175
- - `init` 会往 `.gitattributes` 写 `*.md text eol=lf`。不写的话,Windows 上会写入 CRLF,每次 diff 都显示整个文件被改。
176
- - 文件名会被 slug 成 `[a-z0-9]` 加中日韩字符。Windows 禁止 `\ / : * ? " < > |`,而且 Windows 和 macOS 大小写不敏感——在 Linux 上正常的文件名,换个系统就崩。
177
- - 没有常驻进程,没有文件监听。只有被调用时才运行。
178
- - 如果用非 ASCII 文件名,执行 `git config core.quotepath false`,否则 git 会把中文路径显示成八进制转义。
206
+ 完整结果、方法和复现脚本:[`docs/findings.zh-CN.md`](docs/findings.zh-CN.md)。
179
207
 
180
208
  ---
181
209
 
182
- ## 自己验证
210
+ <a id="docs"></a>
211
+ ## 文档
183
212
 
184
- ```bash
185
- npm test # 29 个测试,零依赖
186
- node experiments/e3/run.mjs # 统计功效实验
187
- node experiments/e4/run.mjs # 年龄 / 过期实验
188
- node experiments/e5/run.mjs # 真实 agent 的跨会话实验
189
- ```
213
+ | | |
214
+ |---|---|
215
+ | [设计](docs/design.zh-CN.md) | 为什么是这个形态 |
216
+ | [结论汇总](docs/findings.zh-CN.md) | 每个实验发现了什么、没发现什么 |
217
+ | [验证索引](docs/validation.zh-CN.md) | 详细写稿的入口 |
218
+ | [预注册](docs/preregistration.md) | 在数据之前定好的判据 |
219
+ | [贡献指南](CONTRIBUTING.md) | 四条不可协商的设计约束 |
220
+ | [AGENTS 片段](AGENTS.snippet.zh-CN.md) | 让 agent 真正用起来的那段指令 |
190
221
 
191
- 六个实验和它们遵循的约定见 [`experiments/README.md`](experiments/README.md)。
222
+ ---
223
+
224
+ ## 命令行
192
225
 
193
- 实验通过一个 OpenAI 兼容端点调用模型。设置 `MAGPIE_URL`,或者改每个脚本顶部的网关地址。它们不属于工具本身,是我们验证结论用的。
226
+ ```
227
+ whynot init 创建 .decisions/ 和 .gitattributes 规则
228
+ whynot add --title T --why W 记录一条
229
+ [--type rejected|decision|constraint]
230
+ [--revisit "条件"] 什么时候该重新考虑
231
+ [--ttl N] 多少天后过期
232
+ [--expires never] 显式设为永久
233
+ [--evidence pr:412] 可重复
234
+ [--tags infra,session]
235
+ whynot list [--all] 列出有效记录
236
+ whynot check "<查询>" 查某件事是不是已经定了
237
+ whynot stale 需要人决定的记录
238
+ whynot renew <id> [--days N] 续期
239
+ whynot retire <id> 归档(不删除)
240
+ whynot audit [--days N] 扫描 agent 配置文件(只读)
241
+ whynot doctor 校验记录
242
+ whynot mcp 运行 MCP server(stdio)
243
+ ```
194
244
 
195
245
  ---
196
246
 
247
+ <a id="license"></a>
197
248
  ## 许可证
198
249
 
199
250
  MIT。没有账号、没有遥测、没有付费档、没有闭源二进制。
@@ -201,4 +252,6 @@ MIT。没有账号、没有遥测、没有付费档、没有闭源二进制。
201
252
 
202
253
  ---
203
254
 
204
- [English](README.md) · [设计](docs/design.zh-CN.md) · [验证](docs/validation.zh-CN.md)
255
+ <p align="center">
256
+ <sub><a href="README.md">English</a> · <a href="https://github.com/Stakenshake/whynot">GitHub</a> · <a href="https://www.npmjs.com/package/whynot-cli">npm</a></sub>
257
+ </p>
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" fill="none" role="img" aria-label="whynot">
2
+ <g stroke="#F5F5F5" stroke-width="7" stroke-linecap="round">
3
+ <path d="M20 60h30"/>
4
+ <path d="M50 60 84 32"/>
5
+ <path d="M50 60 84 88"/>
6
+ <path d="M90 26l14 14"/>
7
+ <path d="M104 26 90 40"/>
8
+ </g>
9
+ <circle cx="20" cy="60" r="8" fill="#F5F5F5"/>
10
+ <circle cx="92" cy="94" r="8" fill="#F5F5F5"/>
11
+ </svg>
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" fill="none" role="img" aria-label="whynot">
2
+ <g stroke="#111111" stroke-width="7" stroke-linecap="round">
3
+ <path d="M20 60h30"/>
4
+ <path d="M50 60 84 32"/>
5
+ <path d="M50 60 84 88"/>
6
+ <path d="M90 26l14 14"/>
7
+ <path d="M104 26 90 40"/>
8
+ </g>
9
+ <circle cx="20" cy="60" r="8" fill="#111111"/>
10
+ <circle cx="92" cy="94" r="8" fill="#111111"/>
11
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whynot-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Record what you ruled out, and why, so your coding agent stops re-suggesting it.",
5
5
  "type": "module",
6
6
  "author": "Stakenshake",
@@ -23,6 +23,7 @@
23
23
  "files": [
24
24
  "bin",
25
25
  "src",
26
+ "assets",
26
27
  "docs/design.md",
27
28
  "docs/design.zh-CN.md",
28
29
  "docs/findings.md",