whynot-cli 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,69 @@
1
+ # whynot — paste this block into your AGENTS.md / CLAUDE.md
2
+
3
+ The block below is the *behavioral* half of whynot. The MCP server gives the
4
+ agent the ability to read and write decisions; this text is what makes it
5
+ actually do so. Copy it verbatim.
6
+
7
+ ---
8
+
9
+ ## Recorded decisions (`.decisions/`)
10
+
11
+ This repository keeps a record of settled decisions — especially approaches
12
+ that were **tried and rejected** — in `.decisions/`.
13
+
14
+ **At the start of a session**, call `decisions_check` with no arguments. You get
15
+ a compact index (id, type, title, tags) of everything already settled. It is
16
+ short by design — read it once.
17
+
18
+ **Before proposing a technical approach**, call `decisions_check` with keywords
19
+ for what you are about to suggest, and read the full entries that come back. If
20
+ the topic is covered, do not re-propose the rejected option. You may propose it
21
+ again only if you can point to concrete evidence that the stated `revisit_when`
22
+ condition has now been met.
23
+
24
+ Matching is keyword-based, so **query with the nouns the entry would use**, and
25
+ try more than one phrasing before concluding nothing is recorded. An empty
26
+ result means "nothing found", not "nothing decided".
27
+
28
+ **When the user rejects one of your suggestions**, or when you discover that an
29
+ approach does not work, call `decisions_record` with:
30
+ - `title` — what was ruled out
31
+ - `why` — the specific reason it failed (not "it didn't work"; say what broke)
32
+ - `type: rejected`
33
+ - `evidence` — a commit sha, PR number, or URL a human can check later
34
+ - `revisit_when` — strongly preferred: the condition that would change the answer
35
+
36
+ Every entry expires (90 days for a rejection by default) and then stops being
37
+ shown to you. If a decision is genuinely permanent, say so to the user and let
38
+ them decide — do not set a long TTL yourself to work around this.
39
+
40
+ **When you are asked to plan or design**, call `decisions_stale` first and
41
+ surface anything that needs re-confirmation. Do not silently follow a stale
42
+ entry; ask. A stale entry is a question for the user, not an instruction.
43
+
44
+ Never invent an entry, and never record something the user did not actually
45
+ decide. A wrong entry is worse than a missing one, because it will be trusted
46
+ for months.
47
+
48
+ ---
49
+
50
+ ## Registering the MCP server
51
+
52
+ `decisions_check` / `decisions_record` / `decisions_stale` come from the whynot
53
+ MCP server. Add it to your agent's MCP config:
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "whynot": {
59
+ "command": "npx",
60
+ "args": ["-y", "whynot-cli", "mcp"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ If your agent reads `AGENTS.md` but not MCP, the fallback still works: the
67
+ entries are plain Markdown in `.decisions/`, so the agent can read and grep them
68
+ directly. That is intentional — the format is the product, and it keeps working
69
+ without the tool.
@@ -0,0 +1,60 @@
1
+ # whynot —— 把这段粘进你的 AGENTS.md / CLAUDE.md
2
+
3
+ 下面这段是 whynot 的**行为部分**。MCP server 只是让 agent 有能力读写;真正让它
4
+ 主动去做的,是这段文字。请原样复制。
5
+
6
+ ---
7
+
8
+ ## 已记录的决策(`.decisions/`)
9
+
10
+ 这个仓库在 `.decisions/` 里记录已经定下来的决策——尤其是**试过并否决**的方案。
11
+
12
+ **每次会话开始时**,不带参数调用 `decisions_check`。你会得到一个紧凑的索引
13
+ (id、类型、标题、标签),里面是已经定下的所有事。它故意做得很短——读一遍就行。
14
+
15
+ **在提出技术方案之前**,用描述你即将建议的关键词调用 `decisions_check`,然后把
16
+ 返回的完整条目读完。如果这个话题已经被覆盖,不要再提被否决的选项。除非你能
17
+ 指出明确证据说明 `revisit_when` 条件已经满足,否则不要重新提议。
18
+
19
+ 匹配是基于关键词的,所以**用条目里可能出现的名词去查**,并且在得出「没有记录」
20
+ 之前,换几种说法再试一次。返回空表示「没找到」,不表示「没定过」。
21
+
22
+ **当用户否决了你的某个建议**,或者你发现某个方案行不通时,调用 `decisions_record`:
23
+
24
+ - `title` —— 被否决的是什么
25
+ - `why` —— 具体是什么失败了(不要写「它不工作」;说清楚哪里崩了)
26
+ - `type: rejected`
27
+ - `evidence` —— 一条 commit sha、PR 号,或者一个 URL,让人以后能核对
28
+ - `revisit_when` —— 强烈建议填写:什么条件会改变这个结论
29
+
30
+ 每条记录都会过期(否决类默认 90 天),过期后就不再展示给你。如果某个决策确实
31
+ 永久有效,告诉用户,让他们决定——不要自己设一个超长 TTL 来绕过这个机制。
32
+
33
+ **当你被要求做规划或设计时**,先调用 `decisions_stale`,把需要重新确认的东西
34
+ 摆出来。不要默默遵循一条过期条目;先问。**过期条目是给用户的问题,不是给你的
35
+ 指令。**
36
+
37
+ 永远不要编造条目,也不要把用户没有真正决定过的事记进去。一条错误的记录比没有
38
+ 记录更糟,因为它会被信任好几个月。
39
+
40
+ ---
41
+
42
+ ## 注册 MCP server
43
+
44
+ `decisions_check` / `decisions_record` / `decisions_stale` 来自 whynot 的 MCP
45
+ server。在你的 agent 的 MCP 配置里加上:
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "whynot": {
51
+ "command": "npx",
52
+ "args": ["-y", "whynot-cli", "mcp"]
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ 如果你的 agent 读 `AGENTS.md` 但不用 MCP,降级方案依然成立:记录就是
59
+ `.decisions/` 里的普通 Markdown,agent 可以直接读、直接 grep。这是刻意的设计
60
+ ——**格式本身就是产品**,所以没有工具它也能工作。
@@ -0,0 +1,75 @@
1
+ # Contributing
2
+
3
+ Thanks for looking. This project is small on purpose, and a few design
4
+ decisions are not open for negotiation. Read this before opening a PR.
5
+
6
+ ## The four constraints
7
+
8
+ These are the reason the tool exists. A PR that breaks one will be declined,
9
+ even if it is well written and useful in isolation.
10
+
11
+ 1. **The data stays plain Markdown in git.** No database, no binary format, no
12
+ schema that requires the tool to read. If this project dies, users must be
13
+ able to `grep` their own files.
14
+
15
+ 2. **No vector store, no embeddings, no model call on the write path.**
16
+ Retrieval quality is not the bottleneck; auditability is. Vector stores and
17
+ LLM-extracted memories are the failure modes this project is a response to.
18
+
19
+ 3. **No daemon, no background process, no file watching.** The tool runs when
20
+ it is called. Background processes are how these tools become unreliable and
21
+ then abandoned.
22
+
23
+ 4. **No hosted service, no telemetry, no account.** Decisions about someone's
24
+ codebase are the most sensitive thing in their repository. They stay on the
25
+ user's machine.
26
+
27
+ If you think one of these should change, open an issue and make the argument
28
+ with evidence. The constraints are load-bearing, not aesthetic.
29
+
30
+ ## What is welcome
31
+
32
+ - Bug fixes, especially cross-platform ones.
33
+ - Better heuristics for `whynot audit`, with tests.
34
+ - Documentation that is shorter or clearer than what it replaces.
35
+ - Additional agent integrations that reuse the existing file format.
36
+ - Reproductions of the validation experiments on other models. If a model
37
+ behaves differently from what `docs/validation.md` reports, that is a useful
38
+ finding — please share it.
39
+
40
+ ## What will be declined
41
+
42
+ - Anything that makes the tool heavier without removing a reported problem.
43
+ - New configuration surface. Every option is a thing someone has to understand.
44
+ - "Make it work for teams" features that require a server.
45
+ - PRs that rewrite the README into marketing copy.
46
+
47
+ ## Before you send a PR
48
+
49
+ ```bash
50
+ node --test "test/*.test.js"
51
+ ```
52
+
53
+ There are no dependencies to install. If a test fails on your machine but not
54
+ in CI, that is a bug worth reporting — the project claims to work on Windows,
55
+ macOS, and Linux.
56
+
57
+ Keep commits focused. One change per PR. Explain *what problem it removes*, not
58
+ what feature it adds.
59
+
60
+ ## Style
61
+
62
+ - Plain language in docs. Short sentences. No superlatives.
63
+ - Comments explain *why*, not *what*. If the code needs a comment to say what it
64
+ does, rename something instead.
65
+ - No emoji in source files.
66
+ - Chinese and English docs are kept in sync. If you change one, change both or
67
+ flag it in the PR.
68
+
69
+ ## Security and privacy
70
+
71
+ Do not include personal paths, usernames, API keys, or the contents of anyone's
72
+ `~/.codex` or `~/.claude` directory in a PR or an issue. `experiments/out/` is
73
+ gitignored for this reason; keep it that way.
74
+
75
+ If you find a leak, open an issue without pasting the leaked value.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stakenshake
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.
package/README.md CHANGED
@@ -1,3 +1,247 @@
1
- # Temporary Holding Version
1
+ # whynot
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.
14
+
15
+ ---
16
+
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%** |
34
+
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
42
+
43
+ At n=20 across three families, a recorded rejection moved the recommendation by
44
+ 78–87 percentage points (Fisher p < 0.0001).
45
+
46
+ ### Two honest qualifications
47
+
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.
56
+
57
+ ### What is not tested
58
+
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.
63
+
64
+ The claim we do make: *the mechanism works under test, and the scripts are here
65
+ so you can check.*
66
+
67
+ ---
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ npx whynot-cli init
73
+ ```
74
+
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
81
+
82
+ The same shape works for Claude Code, Claude Desktop, Cursor, and most other
83
+ MCP clients:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "whynot": {
89
+ "command": "npx",
90
+ "args": ["-y", "whynot-cli", "mcp"]
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Use
99
+
100
+ ```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
108
+ ```
109
+
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
+ ```
121
+ Scanned 2 agent config files:
122
+ AGENTS.md (last changed 412d ago)
123
+ CLAUDE.md (last changed 8d ago)
124
+
125
+ 4 rules look like permanent constraints:
126
+
127
+ AGENTS.md:3
128
+ "Never use TypeScript in this repo."
129
+ → absolute rule with no stated condition; unchanged for 412 days
130
+ ```
131
+
132
+ It is a heuristic, not a verdict. If it finds nothing, that is useful too.
133
+
134
+ ---
135
+
136
+ ## The format
137
+
138
+ ```
139
+ .decisions/
140
+ └── 0001-session-not-redis.md
141
+ ```
142
+
143
+ ```markdown
144
+ ---
145
+ id: 0001
146
+ 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
151
+ revisit_when: "Redis 支持跨区域强一致时"
152
+ evidence:
153
+ - "pr:412"
154
+ tags: [infra, session]
155
+ ---
156
+
157
+ ## 结论
158
+ 不能把 session 存 Redis。
159
+
160
+ ## 为什么
161
+ 试过 Redis + 单写区域。us-east 故障时读延迟飙到 8s,SLO 要求 p99 < 500ms。
162
+ ```
163
+
164
+ `rejected` entries are the valuable ones. They are the only kind that cannot be
165
+ recovered from the code.
166
+
167
+ ### Expiry
168
+
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.
174
+
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.
177
+
178
+ If something genuinely never expires, write `expires_on: never`. It should be
179
+ rare, and it should be a deliberate decision.
180
+
181
+ ---
182
+
183
+ ## Why this shape
184
+
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.
194
+
195
+ ---
196
+
197
+ ## What this is not
198
+
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.
205
+
206
+ ---
207
+
208
+ ## Cross-platform notes
209
+
210
+ Tested on Windows, macOS, and Linux.
211
+
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`.
219
+
220
+ ---
221
+
222
+ ## Verify it yourself
223
+
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
+ ```
230
+
231
+ See [`experiments/README.md`](experiments/README.md) for all six and for the
232
+ conventions they follow.
233
+
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.
237
+
238
+ ---
239
+
240
+ ## License
241
+
242
+ MIT. No account, no telemetry, no paid tier, no closed binary.
243
+ There is nothing to upsell you.
244
+
245
+ ---
246
+
247
+ [中文说明](README.zh-CN.md) · [Design](docs/design.md) · [Validation](docs/validation.md)
@@ -0,0 +1,204 @@
1
+ # whynot
2
+
3
+ **记下你否决了什么、为什么否决——让你的编码 agent 不再重提。**
4
+
5
+ 你的 agent 每次开新会话都从零开始。你上周否决的方案,它这周还会再提一遍。三个月后,你自己也忘了当初为什么否决。
6
+
7
+ 记忆类工具记录的是「**你做了什么**」。那些在代码、commit、PR 里都已经有了。真正哪儿都不存在的是「**你为什么没做**」——它只在你脑子里,而且在慢慢腐烂。
8
+
9
+ `whynot` 就是你仓库里一个放 Markdown 的文件夹,外加一个很薄的 MCP server。
10
+
11
+ ---
12
+
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%** |
28
+
29
+ **模型不按年龄打折——它把每一条记录都当成现行的。**
30
+
31
+ 在提示词里加「这条很旧,请保持怀疑」没有用;**让过期条目不可见**才有用。这就是为什么「过期」是这个工具的核心,而不是可选项。
32
+
33
+ ### 记录一条否决,会实质改变结论
34
+
35
+ n=20、三个模型家族合并:一条被记录的否决把推荐改变 **78–87 个百分点**(Fisher p < 0.0001)。
36
+
37
+ ### 两个必须说的限定
38
+
39
+ - **不是所有模型都一样服从。** 当真实代码摆在面前时,一个模型直接从仓库推断出答案、无视了记录;另一个仍然倾向于被否决的方案,直到记录拦住它。
40
+ - **写的理由比日期更重要。** 当记录写的是「团队当时不会 TS」,约五分之一的回复会攻击这个前提、照样推荐 TypeScript;当写的是「这个项目字段变更频繁」,服从是 100%。**命名了「条件」的理由会邀请复核,陈述了「属性」的理由不会。**
41
+
42
+ ### 没有测的东西
43
+
44
+ **我们没有测过它在真实使用中是否帮得上忙。** 这里每个实验都是受控的单轮任务。真正重要的那个数字——几周真实工作里,agent 重提已否决方案的次数有没有下降——没有测,这个项目也不声称它。
45
+
46
+ 我们声称的是:**机制在测试条件下有效,而且脚本都在,你可以自己查。**
47
+
48
+ ---
49
+
50
+ ## 安装
51
+
52
+ ```bash
53
+ npx whynot-cli init
54
+ ```
55
+
56
+ 然后把 [`AGENTS.snippet.md`](AGENTS.snippet.md) 里的那一段粘进你的 `AGENTS.md` 或 `CLAUDE.md`,再注册 MCP server。
57
+
58
+ 需要 Node 20+。没有别的依赖。不用全局安装,不用注册账号,没有遥测。
59
+
60
+ ### 注册 MCP server
61
+
62
+ Claude Code、Claude Desktop、Cursor 以及大多数 MCP 客户端都用同样的格式:
63
+
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
+
79
+ ```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 # 不再成立 —— 归档
87
+ ```
88
+
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
+ ```
96
+ Scanned 2 agent config files:
97
+ AGENTS.md (last changed 412d ago)
98
+ CLAUDE.md (last changed 8d ago)
99
+
100
+ 4 rules look like permanent constraints:
101
+
102
+ AGENTS.md:3
103
+ "Never use TypeScript in this repo."
104
+ → absolute rule with no stated condition; unchanged for 412 days
105
+ ```
106
+
107
+ 这是启发式检查,不是判决。**扫出来是 0 条,也是有用的信息。**
108
+
109
+ ---
110
+
111
+ ## 格式
112
+
113
+ ```
114
+ .decisions/
115
+ └── 0001-session-不存-redis.md
116
+ ```
117
+
118
+ ```markdown
119
+ ---
120
+ id: 0001
121
+ 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
126
+ revisit_when: "Redis 支持跨区域强一致时"
127
+ evidence:
128
+ - "pr:412"
129
+ tags: [infra, session]
130
+ ---
131
+
132
+ ## 结论
133
+ 不能把 session 存 Redis。
134
+
135
+ ## 为什么
136
+ 试过 Redis + 单写区域。us-east 故障时读延迟飙到 8s,SLO 要求 p99 < 500ms。
137
+ ```
138
+
139
+ `rejected`(已否决)是最值钱的一类。它是唯一无法从代码里恢复出来的东西。
140
+
141
+ ### 过期
142
+
143
+ 每条记录都会过期:否决类 90 天,决策类 180 天,约束类 365 天。
144
+
145
+ 过期之后,它**不再展示给 agent**,但**不会被删除**。`whynot stale` 会列出来,你决定续期还是归档。
146
+
147
+ 这就是整个安全机制。过期条目是**不可见**的——因为「告诉 agent 这条很旧、请怀疑它」已经被实验证明不管用。
148
+
149
+ 如果某件事真的永远不过期,写 `expires_on: never`。这应该很少见,而且应该是一个刻意的决定。
150
+
151
+ ---
152
+
153
+ ## 为什么是这个形态
154
+
155
+ - **Markdown 放在 git 里,不是数据库。** 你能读、能 diff、能 review、能回滚。就算这个工具明天消失,`grep` 依然能用。
156
+ - **不用向量库、不用 embedding、写入时不调模型。** 这每一条都是我们在现有工具里实际看到的失败模式:索引过期、写入成本、以及没人能审计的结果。
157
+ - **默认过期。** 这是它和其他所有记忆工具最不一样的地方。实验见 [docs/validation.zh-CN.md](docs/validation.zh-CN.md)。
158
+ - **没有工具时格式依然可用。** MCP 不可用时,agent 可以直接读 `.decisions/`。这一点是观察到的,不只是设计出来的。
159
+
160
+ ---
161
+
162
+ ## 它不是什么
163
+
164
+ - 不是向量数据库,也不是检索引擎。
165
+ - 不是托管服务。你对自己代码库的推理过程留在你自己的机器上。
166
+ - 不替代 `AGENTS.md`。那个告诉 agent 该怎么做事;这个记录哪些事已经定了。
167
+ - 不是全自动的。没人记,它就什么也不做。
168
+
169
+ ---
170
+
171
+ ## 跨平台说明
172
+
173
+ Windows / macOS / Linux 都测过。
174
+
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 会把中文路径显示成八进制转义。
179
+
180
+ ---
181
+
182
+ ## 自己验证
183
+
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
+ ```
190
+
191
+ 六个实验和它们遵循的约定见 [`experiments/README.md`](experiments/README.md)。
192
+
193
+ 实验通过一个 OpenAI 兼容端点调用模型。设置 `MAGPIE_URL`,或者改每个脚本顶部的网关地址。它们不属于工具本身,是我们验证结论用的。
194
+
195
+ ---
196
+
197
+ ## 许可证
198
+
199
+ MIT。没有账号、没有遥测、没有付费档、没有闭源二进制。
200
+ **没有任何东西要卖给你。**
201
+
202
+ ---
203
+
204
+ [English](README.md) · [设计](docs/design.zh-CN.md) · [验证](docs/validation.zh-CN.md)
package/bin/whynot.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.js';
3
+
4
+ main();