@malindar/whyline 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.
- package/.claude/settings.local.json +33 -0
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +37 -0
- package/.prettierrc.json +7 -0
- package/CLAUDE.md +74 -0
- package/LICENSE +21 -0
- package/README.md +359 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +125 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/delete.d.ts +3 -0
- package/dist/commands/delete.js +42 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +111 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/edit.d.ts +1 -0
- package/dist/commands/edit.js +78 -0
- package/dist/commands/edit.js.map +1 -0
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.js +90 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/import.d.ts +1 -0
- package/dist/commands/import.js +110 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.js +23 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install-claude.d.ts +3 -0
- package/dist/commands/install-claude.js +180 -0
- package/dist/commands/install-claude.js.map +1 -0
- package/dist/commands/list.d.ts +4 -0
- package/dist/commands/list.js +35 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +10 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/save.d.ts +4 -0
- package/dist/commands/save.js +74 -0
- package/dist/commands/save.js.map +1 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +46 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/show.d.ts +3 -0
- package/dist/commands/show.js +30 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/stats.d.ts +1 -0
- package/dist/commands/stats.js +27 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/summarize.d.ts +3 -0
- package/dist/commands/summarize.js +140 -0
- package/dist/commands/summarize.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/db/connection.d.ts +2 -0
- package/dist/db/connection.js +8 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/migrations.d.ts +2 -0
- package/dist/db/migrations.js +19 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/schema.d.ts +5 -0
- package/dist/db/schema.js +64 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/git/diff.d.ts +2 -0
- package/dist/git/diff.js +45 -0
- package/dist/git/diff.js.map +1 -0
- package/dist/git/git.d.ts +3 -0
- package/dist/git/git.js +25 -0
- package/dist/git/git.js.map +1 -0
- package/dist/git/repoId.d.ts +3 -0
- package/dist/git/repoId.js +49 -0
- package/dist/git/repoId.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +296 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +119 -0
- package/dist/mcp/tools.js +43 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/memory/parseSummary.d.ts +14 -0
- package/dist/memory/parseSummary.js +53 -0
- package/dist/memory/parseSummary.js.map +1 -0
- package/dist/memory/qualityCheck.d.ts +13 -0
- package/dist/memory/qualityCheck.js +78 -0
- package/dist/memory/qualityCheck.js.map +1 -0
- package/dist/memory/redactSecrets.d.ts +7 -0
- package/dist/memory/redactSecrets.js +29 -0
- package/dist/memory/redactSecrets.js.map +1 -0
- package/dist/memory/repoContext.d.ts +2 -0
- package/dist/memory/repoContext.js +23 -0
- package/dist/memory/repoContext.js.map +1 -0
- package/dist/memory/saveMemory.d.ts +40 -0
- package/dist/memory/saveMemory.js +223 -0
- package/dist/memory/saveMemory.js.map +1 -0
- package/dist/memory/searchMemory.d.ts +17 -0
- package/dist/memory/searchMemory.js +122 -0
- package/dist/memory/searchMemory.js.map +1 -0
- package/dist/memory/types.d.ts +48 -0
- package/dist/memory/types.js +2 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/output/format.d.ts +3 -0
- package/dist/output/format.js +43 -0
- package/dist/output/format.js.map +1 -0
- package/docs/architecture.md +387 -0
- package/docs/ec6ab3bf-60cf-4629-ad9e-3048e8e3c43a.png +0 -0
- package/docs/logo.png +0 -0
- package/eslint.config.js +16 -0
- package/how-to-run/01-install.md +69 -0
- package/how-to-run/02-wire-up-your-repo.md +80 -0
- package/how-to-run/03-test-it-manually.md +91 -0
- package/how-to-run/04-test-with-claude-code.md +70 -0
- package/how-to-run/CLAUDE.md.template +72 -0
- package/how-to-run/README.md +49 -0
- package/package.json +60 -0
- package/src/cli.ts +142 -0
- package/src/commands/delete.ts +47 -0
- package/src/commands/doctor.ts +128 -0
- package/src/commands/edit.ts +80 -0
- package/src/commands/export.ts +95 -0
- package/src/commands/import.ts +119 -0
- package/src/commands/init.ts +31 -0
- package/src/commands/install-claude.ts +203 -0
- package/src/commands/list.ts +41 -0
- package/src/commands/mcp.ts +12 -0
- package/src/commands/save.ts +85 -0
- package/src/commands/search.ts +56 -0
- package/src/commands/show.ts +37 -0
- package/src/commands/stats.ts +31 -0
- package/src/commands/summarize.ts +183 -0
- package/src/config.ts +26 -0
- package/src/db/connection.ts +8 -0
- package/src/db/migrations.ts +26 -0
- package/src/db/schema.ts +68 -0
- package/src/git/diff.ts +43 -0
- package/src/git/git.ts +25 -0
- package/src/git/repoId.ts +49 -0
- package/src/hooks/post-commit.sample.sh +9 -0
- package/src/mcp/server.ts +326 -0
- package/src/mcp/tools.ts +53 -0
- package/src/memory/parseSummary.ts +72 -0
- package/src/memory/qualityCheck.ts +102 -0
- package/src/memory/redactSecrets.ts +32 -0
- package/src/memory/repoContext.ts +25 -0
- package/src/memory/saveMemory.ts +369 -0
- package/src/memory/searchMemory.ts +153 -0
- package/src/memory/types.ts +57 -0
- package/src/output/format.ts +44 -0
- package/src/skill/SKILL.md +95 -0
- package/tests/cliV02.test.ts +213 -0
- package/tests/doctor.test.ts +253 -0
- package/tests/exportImport.test.ts +248 -0
- package/tests/fileRename.test.ts +156 -0
- package/tests/gitHelpers.test.ts +94 -0
- package/tests/init.test.ts +93 -0
- package/tests/installClaude.test.ts +157 -0
- package/tests/parseSummary.test.ts +111 -0
- package/tests/qualityCheck.test.ts +182 -0
- package/tests/redactSecrets.test.ts +75 -0
- package/tests/saveMemory.test.ts +196 -0
- package/tests/searchFilters.test.ts +139 -0
- package/tests/searchMemory.test.ts +273 -0
- package/tests/stale.test.ts +47 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# Whyline — Architecture
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Whyline captures the reasoning behind code changes and makes it available to future AI coding sessions.
|
|
6
|
+
|
|
7
|
+
Git stores diffs. Whyline stores the *why* — the intent, the decision, the alternatives that were rejected, the risks that were acknowledged. It does this by sitting at two points in the development loop:
|
|
8
|
+
|
|
9
|
+
- **Session start** — searches past memories and surfaces relevant context to Claude before any code is touched
|
|
10
|
+
- **After commit** — synthesizes the conversation into a structured record and saves it to a local SQLite database
|
|
11
|
+
|
|
12
|
+
No cloud. No background daemon. No extra UI. Just a CLI and an MCP server.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## The development loop
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
sequenceDiagram
|
|
20
|
+
participant Dev as Developer
|
|
21
|
+
participant CC as Claude Code
|
|
22
|
+
participant MCP as Whyline MCP Server
|
|
23
|
+
participant DB as SQLite (~/.whyline/memory.db)
|
|
24
|
+
participant Git as Git
|
|
25
|
+
|
|
26
|
+
Dev->>CC: "Add retention policy for audit logs"
|
|
27
|
+
CC->>MCP: search_coding_memory(query, repoPath, files)
|
|
28
|
+
MCP->>DB: query memories by keyword + repo
|
|
29
|
+
DB-->>MCP: ranked results with relevanceReason + isStale flag
|
|
30
|
+
MCP-->>CC: past decisions surfaced
|
|
31
|
+
CC-->>Dev: "Last time we touched this, we chose X because Y. Still the case?"
|
|
32
|
+
|
|
33
|
+
Dev->>CC: work on task...
|
|
34
|
+
CC->>Git: git commit
|
|
35
|
+
|
|
36
|
+
Git-->>CC: commit SHA
|
|
37
|
+
CC-->>Dev: "Here's what I'm saving — let me know if you want to add anything"
|
|
38
|
+
Dev->>CC: (optional corrections)
|
|
39
|
+
CC->>MCP: save_coding_memory(intent, decision, why, files, commitSha, ...)
|
|
40
|
+
MCP->>DB: INSERT memory + junction tables
|
|
41
|
+
DB-->>MCP: saved
|
|
42
|
+
MCP-->>CC: { id, saved: true, warnings[] }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## System components
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
graph TB
|
|
51
|
+
subgraph CLI["CLI (whyline)"]
|
|
52
|
+
init[init]
|
|
53
|
+
save[save]
|
|
54
|
+
search[search]
|
|
55
|
+
show[show]
|
|
56
|
+
list[list]
|
|
57
|
+
delete[delete]
|
|
58
|
+
stats[stats]
|
|
59
|
+
edit[edit]
|
|
60
|
+
export[export]
|
|
61
|
+
import[import]
|
|
62
|
+
summarize[summarize]
|
|
63
|
+
doctor[doctor]
|
|
64
|
+
install[install-claude]
|
|
65
|
+
mcp_cmd[mcp]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
subgraph MCP["MCP Server (stdio)"]
|
|
69
|
+
search_tool[search_coding_memory]
|
|
70
|
+
save_tool[save_coding_memory]
|
|
71
|
+
commit_tool[get_commit_memory]
|
|
72
|
+
file_tool[get_file_memories]
|
|
73
|
+
recent_tool[get_recent_memories]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
subgraph Memory["Memory Layer"]
|
|
77
|
+
parseSummary[parseSummary\nmarkdown → structured]
|
|
78
|
+
redact[redactSecrets\nscans all text fields]
|
|
79
|
+
saveMemory[saveMemory\ndb.transaction]
|
|
80
|
+
searchMemory[searchMemory\nkeyword scoring + isStale]
|
|
81
|
+
qualityCheck[qualityCheck\nwarnings on save]
|
|
82
|
+
summarizeCmd[Claude API\nfield improvement]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
subgraph Git["Git Helpers"]
|
|
86
|
+
repoId[repoId\nremote URL → hash]
|
|
87
|
+
repoContext[repoContext\ncwd → RepoContext]
|
|
88
|
+
diff[diff\ngit diff-tree]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
subgraph DB["SQLite (~/.whyline/)"]
|
|
92
|
+
memories[(memories)]
|
|
93
|
+
files[(memory_files)]
|
|
94
|
+
tags[(memory_tags)]
|
|
95
|
+
alts[(memory_alternatives)]
|
|
96
|
+
risks_t[(memory_risks)]
|
|
97
|
+
followups[(memory_followups)]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
save --> parseSummary
|
|
101
|
+
save --> redact
|
|
102
|
+
save --> repoContext
|
|
103
|
+
save --> saveMemory
|
|
104
|
+
|
|
105
|
+
search --> searchMemory
|
|
106
|
+
show --> saveMemory
|
|
107
|
+
summarize --> summarizeCmd
|
|
108
|
+
summarize --> saveMemory
|
|
109
|
+
|
|
110
|
+
save_tool --> redact
|
|
111
|
+
save_tool --> saveMemory
|
|
112
|
+
save_tool --> qualityCheck
|
|
113
|
+
search_tool --> searchMemory
|
|
114
|
+
commit_tool --> saveMemory
|
|
115
|
+
file_tool --> saveMemory
|
|
116
|
+
recent_tool --> saveMemory
|
|
117
|
+
|
|
118
|
+
saveMemory --> memories
|
|
119
|
+
saveMemory --> files
|
|
120
|
+
saveMemory --> tags
|
|
121
|
+
saveMemory --> alts
|
|
122
|
+
saveMemory --> risks_t
|
|
123
|
+
saveMemory --> followups
|
|
124
|
+
|
|
125
|
+
repoContext --> repoId
|
|
126
|
+
repoContext --> diff
|
|
127
|
+
|
|
128
|
+
mcp_cmd --> MCP
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Data model
|
|
134
|
+
|
|
135
|
+
A single **memory** record captures one coding session. All list fields are stored in separate junction tables and joined at read time.
|
|
136
|
+
|
|
137
|
+
```mermaid
|
|
138
|
+
erDiagram
|
|
139
|
+
memories {
|
|
140
|
+
TEXT id PK
|
|
141
|
+
TEXT repo_id
|
|
142
|
+
TEXT repo_path
|
|
143
|
+
TEXT repo_name
|
|
144
|
+
TEXT branch
|
|
145
|
+
TEXT commit_sha
|
|
146
|
+
TEXT task
|
|
147
|
+
TEXT intent
|
|
148
|
+
TEXT summary
|
|
149
|
+
TEXT decision
|
|
150
|
+
TEXT why
|
|
151
|
+
TEXT source
|
|
152
|
+
TEXT embedding_text
|
|
153
|
+
TEXT created_at
|
|
154
|
+
TEXT updated_at
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
memory_files {
|
|
158
|
+
TEXT memory_id FK
|
|
159
|
+
TEXT file_path
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
memory_tags {
|
|
163
|
+
TEXT memory_id FK
|
|
164
|
+
TEXT tag
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
memory_alternatives {
|
|
168
|
+
TEXT memory_id FK
|
|
169
|
+
TEXT value
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
memory_risks {
|
|
173
|
+
TEXT memory_id FK
|
|
174
|
+
TEXT value
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
memory_followups {
|
|
178
|
+
TEXT memory_id FK
|
|
179
|
+
TEXT value
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
memories ||--o{ memory_files : "files touched"
|
|
183
|
+
memories ||--o{ memory_tags : "tags"
|
|
184
|
+
memories ||--o{ memory_alternatives : "alternatives rejected"
|
|
185
|
+
memories ||--o{ memory_risks : "known risks"
|
|
186
|
+
memories ||--o{ memory_followups : "follow-up tasks"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Save pipeline
|
|
192
|
+
|
|
193
|
+
When a memory is saved (via CLI or MCP), every text field passes through the same pipeline before touching the database.
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
flowchart LR
|
|
197
|
+
A[Raw input\nmarkdown or MCP fields] --> B[parseSummary\nextract structured fields]
|
|
198
|
+
B --> C[redactSecrets\nreplace tokens / keys / PEM blocks]
|
|
199
|
+
C --> D[buildEmbeddingText\nconcatenate all fields]
|
|
200
|
+
D --> E[qualityCheck\nwarn if fields too short or filler]
|
|
201
|
+
E --> F[saveMemory\ndb.transaction INSERT]
|
|
202
|
+
F --> G[(SQLite)]
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Secret patterns redacted:**
|
|
206
|
+
- GitHub tokens (`ghp_`, `gho_`, `ghs_`)
|
|
207
|
+
- npm tokens (`npm_`)
|
|
208
|
+
- AWS access keys (`AKIA...`)
|
|
209
|
+
- Bearer tokens
|
|
210
|
+
- `.env`-style assignments (`API_KEY=value`)
|
|
211
|
+
- PEM private key blocks
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Search pipeline
|
|
216
|
+
|
|
217
|
+
Search is deterministic keyword scoring — no embeddings, no external service. Results include an `isStale` flag for memories older than 90 days.
|
|
218
|
+
|
|
219
|
+
```mermaid
|
|
220
|
+
flowchart TD
|
|
221
|
+
A[query + repoPath + files] --> B[resolve repo_id from repoPath]
|
|
222
|
+
B --> C{repo_id results?}
|
|
223
|
+
C -- yes --> D[fetch memories by repo_id]
|
|
224
|
+
C -- no --> E[fallback: fetch by repo_path LIKE]
|
|
225
|
+
D --> F[apply date + tag filters]
|
|
226
|
+
E --> F
|
|
227
|
+
F --> G[scoreMemory for each result]
|
|
228
|
+
G --> H{query provided?}
|
|
229
|
+
H -- yes --> I{contentScore > 0?}
|
|
230
|
+
I -- yes --> J[keep result]
|
|
231
|
+
I -- no --> K[discard — same-repo bonus alone not enough]
|
|
232
|
+
H -- no --> J
|
|
233
|
+
J --> L[sort by total score desc]
|
|
234
|
+
L --> M[limit results]
|
|
235
|
+
M --> N[explainRelevance → relevanceReason]
|
|
236
|
+
N --> O[isStale: age > 90 days]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Score weights:**
|
|
240
|
+
|
|
241
|
+
| Component | Points |
|
|
242
|
+
|-----------|--------|
|
|
243
|
+
| Same repo | +10 |
|
|
244
|
+
| File overlap | +8 |
|
|
245
|
+
| Tag match | +5 |
|
|
246
|
+
| Decision match | +4 |
|
|
247
|
+
| Why match | +3 |
|
|
248
|
+
| Summary match | +2 |
|
|
249
|
+
| File path match | +2 |
|
|
250
|
+
| Recency (≤30 days) | +1 |
|
|
251
|
+
|
|
252
|
+
The `relevanceReason` field describes which components fired, e.g. `"Matched: same repo (+10), tag match (+5)"`. The `isStale` flag is set for memories older than 90 days — Claude surfaces a verification prompt when it's true.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Summarize pipeline
|
|
257
|
+
|
|
258
|
+
`whyline summarize <id>` improves a saved memory's text quality using the Claude API.
|
|
259
|
+
|
|
260
|
+
```mermaid
|
|
261
|
+
flowchart LR
|
|
262
|
+
A[memory ID] --> B[load from SQLite]
|
|
263
|
+
B --> C[build prompt\nwith all 8 text fields]
|
|
264
|
+
C --> D[call claude-haiku\nvia HTTPS]
|
|
265
|
+
D --> E[parse JSON response]
|
|
266
|
+
E --> F[show before/after diff\nof changed fields only]
|
|
267
|
+
F --> G{user confirms\nor --force?}
|
|
268
|
+
G -- yes --> H[updateMemory\nrebuild embeddingText]
|
|
269
|
+
G -- no --> I[cancelled]
|
|
270
|
+
H --> J[(SQLite)]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Requires `ANTHROPIC_API_KEY` in the environment. Uses `claude-haiku-4-5-20251001` — no new npm dependency (uses Node's built-in `https` module).
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Repo identity
|
|
278
|
+
|
|
279
|
+
Whyline needs a stable identifier for each repo so memories can be scoped and searched correctly even if the repo is cloned to a different local path.
|
|
280
|
+
|
|
281
|
+
```mermaid
|
|
282
|
+
flowchart TD
|
|
283
|
+
A[getRepoId] --> B{remote URL available?}
|
|
284
|
+
B -- yes --> C[normalise URL\nstrip .git, git@ → https://]
|
|
285
|
+
C --> D[sha256 → truncate to 32 chars]
|
|
286
|
+
B -- no --> E[hash of absolute repo path]
|
|
287
|
+
D --> F[repo_id stored in DB]
|
|
288
|
+
E --> F
|
|
289
|
+
F --> G[also store repo_path for fallback search]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
This means memories survive repo renames and remain queryable by path if the remote changes.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## MCP integration
|
|
297
|
+
|
|
298
|
+
The MCP server runs as a stdio process, launched by Claude Code when a session starts in a repo that has `.mcp.json`.
|
|
299
|
+
|
|
300
|
+
```mermaid
|
|
301
|
+
sequenceDiagram
|
|
302
|
+
participant CC as Claude Code
|
|
303
|
+
participant Server as Whyline MCP (stdio)
|
|
304
|
+
participant DB as SQLite
|
|
305
|
+
|
|
306
|
+
CC->>Server: spawn process (node dist/cli.js mcp)
|
|
307
|
+
CC->>Server: ListTools request
|
|
308
|
+
Server-->>CC: 5 tool descriptors
|
|
309
|
+
|
|
310
|
+
CC->>Server: CallTool: search_coding_memory
|
|
311
|
+
Server->>DB: openDb → searchMemory → db.close
|
|
312
|
+
Server-->>CC: JSON results with relevanceReason + isStale
|
|
313
|
+
|
|
314
|
+
CC->>Server: CallTool: save_coding_memory
|
|
315
|
+
Server->>DB: openDb → redactSecrets → qualityCheck → saveMemory → db.close
|
|
316
|
+
Server-->>CC: { id, saved: true, warnings[] }
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The DB connection is opened and closed per request — no persistent connection. All stdout is reserved for JSON-RPC; logs go to stderr only.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Directory structure
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
src/
|
|
327
|
+
cli.ts entry point, commander, shebang
|
|
328
|
+
config.ts DATA_DIR, DB_PATH, resolveConfig(), isInitialized()
|
|
329
|
+
commands/
|
|
330
|
+
init.ts create ~/.whyline/, run migrations
|
|
331
|
+
save.ts parse markdown → redact → save
|
|
332
|
+
search.ts resolve repo, score, print results
|
|
333
|
+
show.ts fetch by id or commit SHA
|
|
334
|
+
list.ts list memories, --repo, --limit
|
|
335
|
+
delete.ts delete by ID with confirmation
|
|
336
|
+
stats.ts total memories, repos, top files
|
|
337
|
+
edit.ts open memory in $EDITOR
|
|
338
|
+
export.ts dump as JSON (schemaVersion envelope) or markdown
|
|
339
|
+
import.ts ingest JSON export, deduplicate, redact
|
|
340
|
+
summarize.ts improve memory fields via Claude API
|
|
341
|
+
doctor.ts 7-check setup diagnostic, exits 1 on failure
|
|
342
|
+
install-claude.ts create/update .mcp.json, CLAUDE.md, settings.local.json
|
|
343
|
+
mcp.ts start MCP stdio server
|
|
344
|
+
db/
|
|
345
|
+
connection.ts openDb() — WAL + foreign_keys
|
|
346
|
+
schema.ts MIGRATIONS[] — versioned SQL
|
|
347
|
+
migrations.ts runMigrations() — idempotent
|
|
348
|
+
git/
|
|
349
|
+
git.ts getRepoRoot, getCurrentBranch, resolveCommit
|
|
350
|
+
repoId.ts getRepoId(), getRepoName(), normalizeRemoteUrl()
|
|
351
|
+
diff.ts getChangedFilesForCommit(), getFileRenameHistory()
|
|
352
|
+
repoContext.ts getRepoContext() → RepoContext
|
|
353
|
+
memory/
|
|
354
|
+
types.ts CodingMemory, ScoreBreakdown, SearchResult, RepoContext
|
|
355
|
+
parseSummary.ts markdown → structured fields (9 headings)
|
|
356
|
+
redactSecrets.ts SECRET_PATTERNS[], redactSecrets()
|
|
357
|
+
saveMemory.ts saveMemory(), updateMemory(), getMemoryById(), listMemories(), …
|
|
358
|
+
searchMemory.ts scoreMemory(), explainRelevance(), searchMemory(), isStale()
|
|
359
|
+
qualityCheck.ts checkQuality(), checkDuplicates() → warnings[]
|
|
360
|
+
repoContext.ts assembles RepoContext from cwd + ref
|
|
361
|
+
mcp/
|
|
362
|
+
server.ts createMcpServer() — 5 tools, stdio transport
|
|
363
|
+
tools.ts Zod schemas for all tool inputs
|
|
364
|
+
output/
|
|
365
|
+
format.ts formatMemory() with [STALE] note, formatSearchResult()
|
|
366
|
+
skill/
|
|
367
|
+
SKILL.md Claude Code skill instructions (keep in sync with CLAUDE.md.template)
|
|
368
|
+
hooks/
|
|
369
|
+
post-commit.sample.sh reminder hook template
|
|
370
|
+
|
|
371
|
+
how-to-run/
|
|
372
|
+
CLAUDE.md.template injected into user repos by install-claude (keep in sync with SKILL.md)
|
|
373
|
+
01-install.md
|
|
374
|
+
02-wire-up-your-repo.md
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Keeping instruction files in sync
|
|
380
|
+
|
|
381
|
+
Three files describe how Claude should behave. They must always be consistent — see `CLAUDE.md` for the full sync rule.
|
|
382
|
+
|
|
383
|
+
| File | Purpose |
|
|
384
|
+
|------|---------|
|
|
385
|
+
| `src/skill/SKILL.md` | Used when Whyline is loaded as a Claude Code skill |
|
|
386
|
+
| `how-to-run/CLAUDE.md.template` | Injected into user repos by `whyline install-claude` |
|
|
387
|
+
| Deployed `CLAUDE.md` files | Already-wired repos — patch manually after changes |
|
|
Binary file
|
package/docs/logo.png
ADDED
|
Binary file
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import tseslint from "typescript-eslint";
|
|
3
|
+
|
|
4
|
+
export default tseslint.config(
|
|
5
|
+
eslint.configs.recommended,
|
|
6
|
+
...tseslint.configs.recommended,
|
|
7
|
+
{
|
|
8
|
+
rules: {
|
|
9
|
+
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
10
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
ignores: ["dist/**", "node_modules/**"],
|
|
15
|
+
}
|
|
16
|
+
);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Step 1 — Install Whyline
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Node.js 18 or later (Node 22 recommended)
|
|
6
|
+
- npm
|
|
7
|
+
- git
|
|
8
|
+
|
|
9
|
+
Check your versions:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
node --version # should be v18+
|
|
13
|
+
npm --version
|
|
14
|
+
git --version
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Clone and build
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git clone <whyline-repo-url>
|
|
23
|
+
cd whyline
|
|
24
|
+
npm install
|
|
25
|
+
npm run build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Rebuild native bindings (required on Node 20+)
|
|
29
|
+
|
|
30
|
+
`better-sqlite3` uses native C++ bindings. You must compile them for your Node version:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm rebuild better-sqlite3
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If `npm rebuild` fails or produces no `.node` file (common on Node 22), run manually:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx node-gyp rebuild
|
|
40
|
+
mkdir -p node_modules/better-sqlite3/build/Release
|
|
41
|
+
cp build/Release/better_sqlite3.node node_modules/better-sqlite3/build/Release/
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Make the CLI available globally
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm link
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Verify:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
which whyline # should print a path
|
|
54
|
+
whyline --version # should print 0.1.0
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Initialize storage
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
whyline init
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This creates `~/.whyline/` with:
|
|
66
|
+
- `memory.db` — SQLite database
|
|
67
|
+
- `config.json` — storage config
|
|
68
|
+
|
|
69
|
+
You only need to do this once per machine. Running it again is safe (idempotent).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Step 2 — Wire Up Your Repo
|
|
2
|
+
|
|
3
|
+
Run this once in each repo you want Whyline to work in:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd your-project
|
|
7
|
+
whyline install-claude
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
That's it. The command creates or updates three files:
|
|
11
|
+
|
|
12
|
+
| File | What it does |
|
|
13
|
+
|------|-------------|
|
|
14
|
+
| `.mcp.json` | Registers the `whyline` MCP server with Claude Code |
|
|
15
|
+
| `CLAUDE.md` | Adds the memory instructions so Claude searches and saves automatically |
|
|
16
|
+
| `.claude/settings.local.json` | Auto-approves the five MCP tool calls so Claude doesn't prompt on every use |
|
|
17
|
+
|
|
18
|
+
Running it again is safe — it merges, never overwrites unrelated content.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## What the files look like
|
|
23
|
+
|
|
24
|
+
### `.mcp.json`
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"whyline": {
|
|
30
|
+
"command": "whyline",
|
|
31
|
+
"args": ["mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> **If `whyline` is not on your PATH** (e.g. you didn't run `npm link`), edit this to use the absolute path:
|
|
38
|
+
> ```json
|
|
39
|
+
> {
|
|
40
|
+
> "mcpServers": {
|
|
41
|
+
> "whyline": {
|
|
42
|
+
> "command": "node",
|
|
43
|
+
> "args": ["/absolute/path/to/whyline/dist/cli.js", "mcp"]
|
|
44
|
+
> }
|
|
45
|
+
> }
|
|
46
|
+
> }
|
|
47
|
+
> ```
|
|
48
|
+
|
|
49
|
+
### `.claude/settings.local.json`
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"permissions": {
|
|
54
|
+
"allow": [
|
|
55
|
+
"mcp__whyline__save_coding_memory",
|
|
56
|
+
"mcp__whyline__search_coding_memory",
|
|
57
|
+
"mcp__whyline__get_recent_memories",
|
|
58
|
+
"mcp__whyline__get_file_memories",
|
|
59
|
+
"mcp__whyline__get_commit_memory"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"enabledMcpjsonServers": ["whyline"]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> `settings.local.json` is machine-specific. Add it to your `.gitignore` if you don't want it committed.
|
|
67
|
+
|
|
68
|
+
### `CLAUDE.md`
|
|
69
|
+
|
|
70
|
+
The Whyline section is appended to any existing `CLAUDE.md`, or a new file is created if none exists. See `CLAUDE.md.template` in this folder for the full content.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Verify the setup
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
whyline doctor
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
All seven checks should pass before you start a session.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Step 3 — Test It Manually (CLI)
|
|
2
|
+
|
|
3
|
+
Before testing with Claude Code, verify the CLI works end-to-end.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Save a test memory
|
|
8
|
+
|
|
9
|
+
Create a test summary file:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cat > /tmp/test-memory.md << 'EOF'
|
|
13
|
+
Intent:
|
|
14
|
+
Test that Whyline is working correctly.
|
|
15
|
+
|
|
16
|
+
Summary:
|
|
17
|
+
Ran a manual test save to verify the CLI and database are working.
|
|
18
|
+
|
|
19
|
+
Decision:
|
|
20
|
+
Use a hardcoded test file rather than a real commit summary.
|
|
21
|
+
|
|
22
|
+
Why:
|
|
23
|
+
Fastest way to verify the install without needing a real coding session.
|
|
24
|
+
|
|
25
|
+
Alternatives rejected:
|
|
26
|
+
- Skipping the manual test entirely.
|
|
27
|
+
|
|
28
|
+
Risks:
|
|
29
|
+
- None for this test.
|
|
30
|
+
|
|
31
|
+
Follow-ups:
|
|
32
|
+
- Delete this test memory after verifying.
|
|
33
|
+
|
|
34
|
+
Tags:
|
|
35
|
+
- test
|
|
36
|
+
- install-check
|
|
37
|
+
EOF
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Save it (run from inside any git repo, or use `--commit` with any valid SHA):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd /path/to/any-git-repo
|
|
44
|
+
whyline save --commit HEAD --summary-file /tmp/test-memory.md
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Expected output:
|
|
48
|
+
```
|
|
49
|
+
Saved memory mem_xxxxx
|
|
50
|
+
Repo: your-repo-name
|
|
51
|
+
Commit: abc12345
|
|
52
|
+
Files: 0
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Search for it
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
whyline search "install check"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Expected: one result with `[Matched: ...]` relevance reason.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Show it
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
whyline show mem_xxxxx # use the ID printed above
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Expected: full verbose output with all fields.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Show by commit
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
whyline show --commit HEAD
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Clean up
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# The test memory lives in ~/.whyline/memory.db
|
|
89
|
+
# There's no delete command in v0.1 — you can ignore it or wipe the DB:
|
|
90
|
+
rm ~/.whyline/memory.db && whyline init
|
|
91
|
+
```
|