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 +183 -155
- package/README.zh-CN.md +165 -112
- package/assets/icon-dark.svg +11 -0
- package/assets/icon-light.svg +11 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,68 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
78–87 percentage points (Fisher p < 0.0001).
|
|
47
|
+
## The problem
|
|
45
48
|
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
83
|
-
MCP clients:
|
|
76
|
+
---
|
|
84
77
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
102
|
+
### 2. Create the store
|
|
133
103
|
|
|
134
|
-
|
|
104
|
+
```bash
|
|
105
|
+
npx whynot-cli init
|
|
106
|
+
```
|
|
135
107
|
|
|
136
|
-
|
|
108
|
+
This creates `.decisions/` and adds `*.md text eol=lf` to `.gitattributes`.
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
|
148
|
-
status: live
|
|
149
|
-
created: 2026-
|
|
150
|
-
expires_on:
|
|
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
|
-
|
|
165
|
-
recovered from the code.
|
|
143
|
+
### 4. Give it to your agent
|
|
166
144
|
|
|
167
|
-
|
|
145
|
+
Add the MCP server:
|
|
168
146
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"mcpServers": {
|
|
150
|
+
"whynot": {
|
|
151
|
+
"command": "npx",
|
|
152
|
+
"args": ["-y", "whynot-cli", "mcp"]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
174
157
|
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
179
|
-
|
|
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
|
-
##
|
|
168
|
+
## How it works
|
|
184
169
|
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
230
|
+
## Docs
|
|
223
231
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## CLI reference
|
|
233
244
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
+
不需要全局安装,不用注册账号,没有遥测。只需要 Node 20+。
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
---
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
<a id="quick-start"></a>
|
|
74
|
+
## 快速开始
|
|
61
75
|
|
|
62
|
-
|
|
76
|
+
### 1. 先看哪些规则永远不会过期
|
|
63
77
|
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
|
123
|
-
status: live
|
|
124
|
-
created: 2026-
|
|
125
|
-
expires_on:
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
### 过期
|
|
137
|
+
### 4. 交给你的 agent
|
|
142
138
|
|
|
143
|
-
|
|
139
|
+
加上 MCP server:
|
|
144
140
|
|
|
145
|
-
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"mcpServers": {
|
|
144
|
+
"whynot": {
|
|
145
|
+
"command": "npx",
|
|
146
|
+
"args": ["-y", "whynot-cli", "mcp"]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
146
151
|
|
|
147
|
-
|
|
152
|
+
然后把 [`AGENTS.snippet.zh-CN.md`](AGENTS.snippet.zh-CN.md) 里的那一段粘进你的 `AGENTS.md` 或 `CLAUDE.md`。MCP server 提供工具,这段指令才让 agent 真的去用。
|
|
148
153
|
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
+
- **约五分之一的回复抵抗了陈旧记录**——方式是攻击记录里写的原因。命名了**「条件」**的理由(「团队当时不会 TS」)会邀请复核;陈述了**「属性」**的理由(「这个项目变更频繁」)不会。
|
|
204
|
+
- **当真实代码在上下文里时,有些 agent 会直接从仓库推断答案**、无视记录。记录最有价值的地方,是代码表达不了的东西:**你否决掉的方案**。
|
|
174
205
|
|
|
175
|
-
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 命令行
|
|
192
225
|
|
|
193
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|