claude-memory-admin 1.5.1 → 1.6.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.
Files changed (39) hide show
  1. package/README.md +79 -124
  2. package/package.json +1 -1
  3. package/public/api.mjs +26 -0
  4. package/public/app.mjs +35 -2270
  5. package/public/bus.mjs +7 -0
  6. package/public/dialog.mjs +7 -0
  7. package/public/dialogs/add-entry.mjs +69 -0
  8. package/public/dialogs/bulk-delete.mjs +59 -0
  9. package/public/dialogs/delete.mjs +114 -0
  10. package/public/dialogs/hook-editor.mjs +54 -0
  11. package/public/dialogs/merge.mjs +81 -0
  12. package/public/dialogs/move.mjs +43 -0
  13. package/public/dialogs/remember-path.mjs +65 -0
  14. package/public/dialogs/store-delete.mjs +64 -0
  15. package/public/dialogs/trash.mjs +53 -0
  16. package/public/index.html +1 -0
  17. package/public/parts.mjs +65 -0
  18. package/public/state.mjs +48 -0
  19. package/public/store.mjs +121 -0
  20. package/public/styles.css +1 -1
  21. package/public/ui.mjs +44 -16
  22. package/public/views/cleanup.mjs +63 -0
  23. package/public/views/context.mjs +121 -0
  24. package/public/views/cost-issue.mjs +70 -0
  25. package/public/views/environment.mjs +12 -0
  26. package/public/views/header.mjs +183 -0
  27. package/public/views/index.mjs +122 -0
  28. package/public/views/issue.mjs +262 -0
  29. package/public/views/memories.mjs +156 -0
  30. package/public/views/memory-list.mjs +121 -0
  31. package/public/views/memory.mjs +32 -0
  32. package/public/views/path-check.mjs +103 -0
  33. package/public/views/search.mjs +100 -0
  34. package/public/views/segments.mjs +37 -0
  35. package/public/views/sessions.mjs +235 -0
  36. package/public/views/settings.mjs +121 -0
  37. package/public/views/stores.mjs +65 -0
  38. package/public/views/worklist.mjs +33 -0
  39. package/src/sessions.mjs +69 -1
package/README.md CHANGED
@@ -21,13 +21,24 @@ itself, and worth saying out loud.
21
21
 
22
22
  ---
23
23
 
24
- ### Prune: see what MEMORY.md actually costs you
24
+ ### Cleanup: what MEMORY.md costs you and what is broken, in one list
25
25
 
26
- ![The Prune tab, showing how much of the MEMORY.md load limit a project uses and a sortable list of memories by age, size and inbound links](assets/prune.webp)
26
+ ![The Cleanup tab: the MEMORY.md load meter, then one worst-first list beneath it - a broken wikilink with Remove link, two over-long index hooks and two that only restate the description, each with its own Edit hook button, an empty section, and an orphan offered the MEMORY.md bullet it is missing](assets/cleanup.webp)
27
+
28
+ ### Memory: the list, the index and the graph
29
+
30
+ ![The Memory tab, List segment: memories grouped by their MEMORY.md section with age, size and inbound link count on each, a sort control and a Select toggle above them, and the opened memory beside the list with its metadata, its swept origin transcript struck through, and its wikilinks](assets/memory.webp)
27
31
 
28
32
  ### Graph: see how memories link to each other
29
33
 
30
- ![The Graph tab, showing memories as nodes coloured by type with wikilinks as edges](assets/graph.webp)
34
+ ![The Graph segment of the Memory tab, showing memories as nodes coloured by type with wikilinks as edges, orphans below a dashed line and a dashed outline where a link points at nothing](assets/graph.webp)
35
+
36
+ ### Environment: what every session loads before a project is even chosen
37
+
38
+ ![The Global entry, whose only tab is Environment, showing the estimated token cost of the user-scope instruction files, a rule file reached twice through both an import and the rules directory so its tokens are paid for twice, and the order the files load in](assets/context.webp)
39
+
40
+ There is a light theme and a dark one, toggled with `t`; the Memory shot above is
41
+ the light one and the rest are dark.
31
42
 
32
43
  ---
33
44
 
@@ -66,6 +77,17 @@ project is to the cliff, and makes it quick to get back under it.
66
77
 
67
78
  ## What it does
68
79
 
80
+ Everything lives under three tabs, each answering one question:
81
+
82
+ | Tab | The question | Holds |
83
+ | --- | --- | --- |
84
+ | **Memory** | what is in here? | the memory list, `MEMORY.md`, the graph |
85
+ | **Cleanup** | what should I fix? | the load meter and one worst-first list of fixable things |
86
+ | **Environment** | what else does Claude load? | instructions, settings, sessions - all read-only |
87
+
88
+ Undo is a button in the project header rather than a fourth tab, because it is a
89
+ safety net and not a place you browse.
90
+
69
91
  - **Projects by real path.** The directories on disk are slugified cwds
70
92
  (`-Users-me-repos-Blog`) and the slugification is lossy. The true path is
71
93
  recovered from the `cwd` field in the session transcripts stored next to each
@@ -93,23 +115,27 @@ project is to the cliff, and makes it quick to get back under it.
93
115
  with snippets and match highlighting. Press `/` to jump to it.
94
116
  - **Read each memory** with its frontmatter as structured metadata and
95
117
  `[[wikilinks]]` as clickable links. Dead links are struck through in red.
96
- - **Prune** (see below).
97
- - **Graph** the wikilinks between memories. Hovering dims everything that is not
98
- a neighbour, which is the only practical way to read a dense cluster.
118
+ - **Cleanup** (see below).
119
+ - **Graph** the wikilinks between memories, under Memory. Hovering dims everything
120
+ that is not a neighbour, which is the only practical way to read a dense cluster.
99
121
  - **Where each memory came from.** Claude Code stamps `originSessionId` into a
100
122
  memory's frontmatter, and the transcript it names sits next to the store until
101
123
  the sweep takes it. The memory reads *written in "Release process notes", on
102
124
  `main`* while that transcript is there, and says so plainly once it is gone:
103
125
  a swept id is struck through in red, like a dead wikilink, because why the
104
126
  memory exists can no longer be traced from anything on disk.
105
- - **Sessions**: the transcripts beside a store, with the retention window drawn
127
+ - **Sessions** (under Environment): the transcripts beside a store, with the retention window drawn
106
128
  the way MEMORY.md's cutoff is - each session a tick, the sweep line where it
107
- falls. Titles are the ones Claude Code generated, falling back to the session
129
+ falls. Under it, a tile per day shades how much work that day held, and
130
+ clicking one narrows the list to it; the grid spans the retention window
131
+ rather than a fixed year, because a year of empty tiles would read as *no
132
+ sessions* when it only means *already swept*. Titles are the ones Claude Code
133
+ generated, falling back to the session
108
134
  slug and then the opening prompt; a session that names itself nowhere in the
109
135
  part read is shown by id rather than given an invented name. Only the head of
110
136
  each file is ever read, so a 200MB store of transcripts costs a quarter of a
111
- second, and nothing on this tab deletes one.
112
- - **Health**: orphans, dangling pointers, broken wikilinks, files linked only
137
+ second, and nothing here deletes one.
138
+ - **What Cleanup checks**: orphans, dangling pointers, broken wikilinks, files linked only
113
139
  mid-sentence, `name` fields that disagree with the filename, two files claiming
114
140
  one `name`, a file bulleted twice, a blank `description`, a `type` outside the
115
141
  four documented ones, a memory that is frontmatter and little else, a hook that
@@ -118,19 +144,20 @@ project is to the cliff, and makes it quick to get back under it.
118
144
  been swept, a project whose last proof of its own path is about to be, and
119
145
  sessions that produced no memory at all while auto memory was on. An orphan
120
146
  can be given the `MEMORY.md` bullet it is missing without leaving the page.
121
- - **Every count is visible before you click.** Each tab carries a badge, coloured
122
- amber for something to tidy and red for something actively broken, and the
123
- sidebar gives each store a dot of its worst severity - memory, instructions and
124
- settings together. Which project is in trouble is the first thing the app tells
125
- you, not something you find by opening eight tabs each.
147
+ - **Every count is visible before you click.** Each of the three tabs carries a
148
+ badge, coloured amber for something to tidy and red for something actively
149
+ broken, and the sidebar gives each store a dot of its worst severity - memory,
150
+ instructions and settings together. Which project is in trouble is the first
151
+ thing the app tells you, not something you find by opening every tab in turn.
126
152
  - **Dates you can trust.** Claude Code stamps `modified` into frontmatter, but
127
153
  only on files that already have some, and never adds frontmatter to a file
128
154
  without it. Anything falling back to the file's mtime is labelled, because
129
155
  mtime is reset by any copy or restore.
130
- - **Context**: what a session starting in this project would load as
131
- *instructions*, which is the other half of the startup budget (see below).
132
- - **Settings**: every layer Claude Code would read, side by side, with the value
133
- that wins and the ones it shadows (see below).
156
+ - **Instructions** (under Environment): what a session starting in this project
157
+ would load as *instructions*, which is the other half of the startup budget
158
+ (see below).
159
+ - **Settings** (under Environment): every layer Claude Code would read, side by
160
+ side, with the value that wins and the ones it shadows (see below).
134
161
  - **Delete with cascade**, always reversible.
135
162
 
136
163
  Everything above works the same on both kinds of store. The load meter, graph,
@@ -140,17 +167,20 @@ memory directory is a `MEMORY.md` index plus topic files under the same 200-line
140
167
 
141
168
  ## Keeping MEMORY.md small
142
169
 
143
- The **Prune** tab exists because a bloated index costs tokens on every single
144
- session and, past the limit, silently stops loading.
170
+ The **Cleanup** tab exists because a bloated index costs tokens on every single
171
+ session and, past the limit, silently stops loading. It is one list: the load
172
+ meter, then everything worth doing something about, worst first, each row
173
+ carrying its own fix. A finding and its fix are never on different tabs.
145
174
 
146
175
  - **Load meter**. How much of the 200-line / 25KB budget the index uses, and
147
176
  which of the two is binding. Frontmatter and HTML comments are excluded,
148
177
  because Claude Code strips those before loading.
149
- - **The cutoff, drawn where it falls**. Past the limit, the MEMORY.md tab rules a
150
- line across the file and dims everything below it, and Prune names the memories
151
- that stopped being loaded. Because the stripping shifts every line, the cutoff
178
+ - **The cutoff, drawn where it falls**. Past the limit, the MEMORY.md segment rules
179
+ a line across the file and dims everything below it, and Cleanup lists the
180
+ memories that stopped being loaded at the top of the worklist, each with
181
+ **Move up**. Because the stripping shifts every line, the cutoff
152
182
  is mapped back to real line numbers rather than counted in the loaded text. The
153
- tab reads the index either **rendered** — headings, lists and clickable entries,
183
+ segment reads the index either **rendered** — headings, lists and clickable entries,
154
184
  with pointers to files that do not exist struck through — or as **source**, with
155
185
  line numbers. Either way the cutoff is drawn in the same place. The choice is
156
186
  remembered.
@@ -163,16 +193,18 @@ session and, past the limit, silently stops loading.
163
193
  - **Move above the cutoff**. Past the limit, an entry is on disk but invisible.
164
194
  **Move up** relocates its bullet, and the indented lines under it, to the end
165
195
  of any section that starts above the cutoff. It moves one entry rather than
166
- making room, so whatever is now last drops below the line instead - the tab
196
+ making room, so whatever is now last drops below the line instead - the app
167
197
  says so before you do it.
168
198
  - **Possible overlap**. Pairs of memories ranked by shared *rare* vocabulary, to
169
199
  surface the same lesson saved three times from three sessions. It is a hint,
170
- not a verdict. **Merge** folds one into the other: the body moves under a
171
- heading you name, every `[[wikilink]]` that pointed at the source is repointed
172
- rather than broken, a link the survivor had to the source becomes plain text
173
- instead of a self-link, and the two index bullets collapse to one.
174
- - **Bulk prune**. Sort by age, size, or inbound links, tick several, delete them
175
- as one restore point.
200
+ not a verdict, so it sorts last. **Merge** folds one into the other: the body
201
+ moves under a heading you name, every `[[wikilink]]` that pointed at the source
202
+ is repointed rather than broken, a link the survivor had to the source becomes
203
+ plain text instead of a self-link, and the two index bullets collapse to one.
204
+
205
+ Bulk pruning is not here, because it is browsing rather than fixing: the **Memory**
206
+ list sorts by age, size or inbound links, and **Select** turns on the checkboxes so
207
+ several can go as one restore point. One list of memories, not two.
176
208
 
177
209
  Anthropic's own guidance for the index: one line per entry, detail in the topic
178
210
  files, merge or drop stale entries.
@@ -180,13 +212,13 @@ files, merge or drop stale entries.
180
212
  ## The other half of the budget
181
213
 
182
214
  `MEMORY.md` is not the only thing loaded at the start of every session. The
183
- **Context** tab resolves what else is, in load order: managed policy, your
215
+ **Environment** tab's **Instructions** segment resolves what else is, in load order: managed policy, your
184
216
  `~/.claude/CLAUDE.md` and `~/.claude/rules/`, every `CLAUDE.md` and
185
217
  `CLAUDE.local.md` from the filesystem root down to the project, `.claude/CLAUDE.md`,
186
218
  and `.claude/rules/`, with `@path` imports expanded and `claudeMdExcludes` applied.
187
219
 
188
220
  Unlike `MEMORY.md`, none of it is truncated: `CLAUDE.md` files load in full
189
- however long they are. So the tab reports cost rather than a cliff, and separates
221
+ however long they are. So it reports cost rather than a cliff, and separates
190
222
  what every session pays for from the path-scoped rules that only load on a match.
191
223
 
192
224
  It also finds the failures that leave a file silently doing nothing:
@@ -212,7 +244,7 @@ Backticks are respected, so a `` `@README` `` in your prose is not reported as a
212
244
  import, and neither is an email address.
213
245
 
214
246
  Every file listed opens in place, so you can read what actually loads without
215
- leaving the tab. Nothing here is editable: the app never rewrites a `CLAUDE.md`.
247
+ leaving the page. Nothing here is editable: the app never rewrites a `CLAUDE.md`.
216
248
 
217
249
  ### The user scope on its own
218
250
 
@@ -226,7 +258,7 @@ It holds instructions rather than memory, so it is read-only and has no MEMORY.m
226
258
  graph or trash. Search does not reach into it.
227
259
 
228
260
  This is re-derived from the documented resolution rules rather than reported by
229
- Claude Code, and the tab says so. Run `/context` in a session for the ground
261
+ Claude Code, and the app says so. Run `/context` in a session for the ground
230
262
  truth, or the `InstructionsLoaded` hook to log exactly what loaded and why.
231
263
 
232
264
  ## The one check that reads your code
@@ -237,7 +269,7 @@ it takes the paths a memory names in a code span and asks whether anything in th
237
269
  repository still matches them. A memory whose `Foo.cs` was renamed two months ago
238
270
  still reads as authoritative, and nothing else on this page can tell.
239
271
 
240
- Turn it on from the Health tab, per store; the choice is remembered in the browser
272
+ Turn it on from the bottom of the Cleanup tab, per store; the choice is remembered in the browser
241
273
  and nothing is written to disk. It walks the project once, skipping `.git`,
242
274
  `node_modules`, build output and the like, and matches by suffix - a memory that
243
275
  says `Infrastructure/Reporting/Foo.cs` for a file that really lives under
@@ -246,13 +278,14 @@ alone reported seven real paths in ten as missing. Only a token with a real sour
246
278
  extension is treated as a claim about a file, because memories are also full of
247
279
  HTTP routes and type names that no file was ever going to match.
248
280
 
249
- It is a pointer, not a verdict, and the tab says so: a file that moved reads the
281
+ It is a pointer, not a verdict, and the app says so: a file that moved reads the
250
282
  same as one the memory never got right.
251
283
 
252
284
  ## Which settings are actually in force
253
285
 
254
286
  Five files can set the keys this tool cares about, and the one everybody edits,
255
- `~/.claude/settings.json`, is the weakest of them. The **Settings** tab reads all
287
+ `~/.claude/settings.json`, is the weakest of them. The **Environment** tab's
288
+ **Settings** segment reads all
256
289
  five and shows, per key, the value that wins and the ones it shadows, struck
257
290
  through, each labelled with the file it came from:
258
291
 
@@ -267,9 +300,10 @@ It also names the failures that are otherwise silent:
267
300
  - A file that parses but is not an object, or cannot be read at all.
268
301
  - An `autoMemoryDirectory` that is neither absolute nor `~/`-prefixed.
269
302
  - A value Claude Code accepts the key of but not the number, like a
270
- `cleanupPeriodDays` below 1: the tab shows what is written *and* what applies.
303
+ `cleanupPeriodDays` below 1: it shows what is written *and* what applies.
271
304
 
272
- The tab is read-only. It reports what is configured; it never writes a setting.
305
+ Every segment of Environment is read-only. It reports what is configured; it never
306
+ writes a setting.
273
307
 
274
308
  ## Everything it writes is reversible
275
309
 
@@ -282,12 +316,14 @@ operation, and they are trashed and restored as a single step.
282
316
  one restore point. Session transcripts (`*.jsonl`) and the project folder are
283
317
  never touched, only the contents of `memory/`.
284
318
 
285
- **Remove link** in the Health tab clears a `[[wikilink]]` whose target no longer
319
+ **Remove link** in the Cleanup tab clears a `[[wikilink]]` whose target no longer
286
320
  exists. The markup goes and the words stay, so `see [[gone]] for details` becomes
287
321
  `see gone for details`.
288
322
 
289
323
  Everything lands in `memory/.trash/` with a restore record and comes back from the
290
- Trash tab, one undo per operation however many files it touched.
324
+ **Undo** control in the project header, one undo per operation however many files
325
+ it touched. Undo is not a place you browse, so it is a button with a count rather
326
+ than a tab of its own.
291
327
 
292
328
  The app never writes a memory file of its own. It edits `MEMORY.md` a line at a
293
329
  time - a hook rewritten, a bullet added or moved - and each of those keeps every
@@ -363,87 +399,6 @@ No bundler and no build step to run it: the backend is `node:http` plus `node:fs
363
399
  and the frontend is plain ES modules the browser loads directly. Two runtime
364
400
  dependencies, `marked` and `dompurify`, both only for rendering memory bodies safely.
365
401
 
366
- Styling is the one thing that is compiled. `styles/app.css` is the Tailwind v4
367
- source and `public/styles.css` is its committed output, so an installed copy is
368
- ready to serve and never builds anything. After editing the source, run
369
- `npm run build:css` and commit the result — CI rebuilds it and fails on drift.
370
-
371
- | Path | Purpose |
372
- | --- | --- |
373
- | `bin/claude-memory-admin.mjs` | CLI entry point and argument parsing |
374
- | `server.mjs` | HTTP server: static files + JSON API |
375
- | `src/projects.mjs` | Project discovery, slug → real path resolution |
376
- | `src/settings.mjs` | Layered reads of Claude Code's settings files, and the report behind the Settings tab |
377
- | `src/pathcache.mjs` | The opt-in record of confirmed project paths |
378
- | `src/sessions.mjs` | Session transcripts: bounded head reads, retention, provenance |
379
- | `src/stores.mjs` | Store discovery: the user scope, auto memory and the three agent scopes |
380
- | `src/instructions.mjs` | CLAUDE.md chain, `@` imports and rules resolution |
381
- | `src/parse.mjs` | `MEMORY.md` and frontmatter parsers, wikilinks |
382
- | `src/checks.mjs` | The consistency checks, as pure functions over parsed data |
383
- | `src/pathcheck.mjs` | The opt-in check of paths a memory names, against the repo |
384
- | `src/model.mjs` | Joins index, files, graph and health into one model |
385
- | `src/stats.mjs` | Load-limit accounting and overlap detection |
386
- | `src/search.mjs` | Full-text search across every store |
387
- | `src/mutate.mjs` | Delete / edit / merge / restore, the only code that writes |
388
- | `styles/app.css` | Tailwind source, compiled to `public/styles.css` |
389
- | `public/` | Frontend |
390
-
391
- Tests run against committed fixtures under `test/fixtures/`: a projects store
392
- encoding the awkward shapes real memory directories contain, including one index
393
- deliberately past the load limit, an `agents/` tree covering all three subagent
394
- scopes, and an `instructions/` tree covering the import and glob edge cases. Your own
395
- `~/.claude/projects` is additionally checked when it exists, always on a
396
- throwaway copy.
397
-
398
- ### Releasing
399
-
400
- Publishing runs in CI. You do not bump anything by hand.
401
-
402
- Go to **Actions, Release, Run workflow**, pick `patch`, `minor` or `major`, and
403
- run it. The workflow tests, bumps `package.json` and `package-lock.json`, commits
404
- and tags the bump, pushes both, then publishes to npm.
405
-
406
- If you would rather cut the version locally, that still works:
407
-
408
- ```bash
409
- npm version patch
410
- git push --follow-tags
411
- ```
412
-
413
- Pushing a `v*` tag publishes whatever is in `package.json`, after checking the
414
- two agree. Either route refuses to publish a version that is already on npm,
415
- because npm never allows one to be replaced.
416
-
417
- It needs one repository secret:
418
-
419
- | Secret | Value |
420
- | --- | --- |
421
- | `NPM_TOKEN` | an npm **Automation** access token with publish rights |
422
-
423
- Add it under *Settings, Secrets and variables, Actions, New repository secret*.
424
- An Automation token is the right kind because it bypasses 2FA, which an
425
- unattended workflow cannot satisfy.
426
-
427
- Releases are published with [npm provenance](https://docs.npmjs.com/generating-provenance-statements),
428
- so every version carries a signed, verifiable record of the workflow run and
429
- commit that built it. That needs the `id-token: write` permission the workflow
430
- already requests, a public repository, and the `repository` field in
431
- `package.json` pointing at this repo.
432
-
433
- Two things that will bite if they apply to you: the workflow pushes the bump
434
- commit to the default branch, so a branch protection rule that blocks pushes
435
- will stop it; and the tag it pushes uses `GITHUB_TOKEN`, which by design does
436
- not trigger other workflows, so there is no double publish.
437
-
438
- ### Screenshots and demo data
439
-
440
- The screenshots come from an invented store, never a real one:
441
-
442
- ```bash
443
- node scripts/demo-store.mjs /tmp/demo-store
444
- npm start -- --root /tmp/demo-store
445
- ```
446
-
447
402
  ## License
448
403
 
449
404
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-admin",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Browse, audit and prune the auto memory Claude Code keeps under ~/.claude/projects",
5
5
  "keywords": [
6
6
  "claude",
package/public/api.mjs ADDED
@@ -0,0 +1,26 @@
1
+ import * as ui from '/ui.mjs';
2
+ import { el, node } from '/dom.mjs';
3
+
4
+ export async function api(path, options) {
5
+ const response = await fetch(path, {
6
+ headers: { 'content-type': 'application/json' },
7
+ ...options,
8
+ });
9
+ const data = await response.json().catch(() => ({ error: 'Bad response from server' }));
10
+ if (!response.ok) throw new Error(data.error || `Request failed (${response.status})`);
11
+ return data;
12
+ }
13
+
14
+ export function toast(message, { error = false, action } = {}) {
15
+ const element = node('div', { class: ui.toast(error) });
16
+ element.append(document.createTextNode(message));
17
+ if (action) {
18
+ element.append(node('button', {
19
+ class: ui.toastAction,
20
+ text: action.label,
21
+ onclick: () => { element.remove(); action.run(); },
22
+ }));
23
+ }
24
+ el('toast-root').append(element);
25
+ setTimeout(() => element.remove(), action ? 12000 : 4500);
26
+ }