claude-memory-explorer 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-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/cli/commands/dedupe.d.ts +1 -0
- package/dist/cli/commands/dedupe.js +187 -0
- package/dist/cli/commands/dedupe.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +1 -0
- package/dist/cli/commands/doctor.js +177 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/lint.d.ts +1 -0
- package/dist/cli/commands/lint.js +139 -0
- package/dist/cli/commands/lint.js.map +1 -0
- package/dist/cli/commands/list.d.ts +1 -0
- package/dist/cli/commands/list.js +97 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +105 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/merge.d.ts +1 -0
- package/dist/cli/commands/merge.js +111 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/promote.d.ts +1 -0
- package/dist/cli/commands/promote.js +157 -0
- package/dist/cli/commands/promote.js.map +1 -0
- package/dist/cli/commands/tui.d.ts +1 -0
- package/dist/cli/commands/tui.js +60 -0
- package/dist/cli/commands/tui.js.map +1 -0
- package/dist/cli/commands/undo.d.ts +1 -0
- package/dist/cli/commands/undo.js +157 -0
- package/dist/cli/commands/undo.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +85 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui/App.d.ts +8 -0
- package/dist/cli/tui/App.js +333 -0
- package/dist/cli/tui/App.js.map +1 -0
- package/dist/core/apply.d.ts +27 -0
- package/dist/core/apply.js +191 -0
- package/dist/core/apply.js.map +1 -0
- package/dist/core/claudeMd.d.ts +27 -0
- package/dist/core/claudeMd.js +103 -0
- package/dist/core/claudeMd.js.map +1 -0
- package/dist/core/dedupe.d.ts +78 -0
- package/dist/core/dedupe.js +212 -0
- package/dist/core/dedupe.js.map +1 -0
- package/dist/core/doctor.d.ts +35 -0
- package/dist/core/doctor.js +106 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/journal.d.ts +31 -0
- package/dist/core/journal.js +64 -0
- package/dist/core/journal.js.map +1 -0
- package/dist/core/lint.d.ts +26 -0
- package/dist/core/lint.js +254 -0
- package/dist/core/lint.js.map +1 -0
- package/dist/core/memoryIndex.d.ts +42 -0
- package/dist/core/memoryIndex.js +81 -0
- package/dist/core/memoryIndex.js.map +1 -0
- package/dist/core/merge.d.ts +19 -0
- package/dist/core/merge.js +58 -0
- package/dist/core/merge.js.map +1 -0
- package/dist/core/parse.d.ts +2 -0
- package/dist/core/parse.js +84 -0
- package/dist/core/parse.js.map +1 -0
- package/dist/core/plan.d.ts +34 -0
- package/dist/core/plan.js +85 -0
- package/dist/core/plan.js.map +1 -0
- package/dist/core/promote.d.ts +29 -0
- package/dist/core/promote.js +103 -0
- package/dist/core/promote.js.map +1 -0
- package/dist/core/scan.d.ts +16 -0
- package/dist/core/scan.js +81 -0
- package/dist/core/scan.js.map +1 -0
- package/dist/core/types.d.ts +36 -0
- package/dist/core/types.js +4 -0
- package/dist/core/types.js.map +1 -0
- package/dist/mcp/server.d.ts +13 -0
- package/dist/mcp/server.js +211 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +71 -0
- package/skills/curate-memory/SKILL.md +60 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-memory-explorer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browse, dedupe, lint, and promote your Claude Code auto-memory across every project. Closes the gap anthropics/claude-code#58840 describes.",
|
|
5
|
+
"homepage": "https://github.com/hseinmoussa/claude-memory-explorer",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Hussein Moussa",
|
|
9
|
+
"url": "https://github.com/hseinmoussa"
|
|
10
|
+
},
|
|
11
|
+
"skills": ["./skills/curate-memory"],
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"memex": {
|
|
14
|
+
"type": "stdio",
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "claude-memory-explorer", "mcp"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hussein Moussa
|
|
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
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# memex — claude-memory-explorer
|
|
2
|
+
|
|
3
|
+
[](https://github.com/hseinmoussa/claude-memory-explorer/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/claude-memory-explorer)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
> Browse, dedupe, lint, and promote your Claude Code auto-memory across every project.
|
|
8
|
+
> Every mutation is journaled and reversible by `memex undo`.
|
|
9
|
+
> Closes the gap [anthropics/claude-code#58840](https://github.com/anthropics/claude-code/issues/58840) describes.
|
|
10
|
+
|
|
11
|
+
Claude Code stores per-project memory at `~/.claude/projects/<slug>/memory/`.
|
|
12
|
+
Every project is a silo. After a few months of real use:
|
|
13
|
+
|
|
14
|
+
- The same `user_*` / `feedback_*` rule ends up in 12 different projects
|
|
15
|
+
- Stale project facts keep loading into every session
|
|
16
|
+
- `user`/`feedback` memories that should live in `~/.claude/CLAUDE.md` don't
|
|
17
|
+
- `MEMORY.md` files quietly cross the documented 200-line / 25KB cutoff and the tail is dropped at load time
|
|
18
|
+
- Malformed YAML in a single memory file (which Claude itself sometimes writes) makes that memory invisible — and there's no warning
|
|
19
|
+
|
|
20
|
+
`memex` is a CLI, TUI, and MCP server that finds and fixes all of that. Install once as a Claude Code plugin and Claude can curate its own memory inside a normal session.
|
|
21
|
+
|
|
22
|
+
## What's inside
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
memex list List memories across all projects
|
|
26
|
+
memex doctor One-shot health report (run weekly)
|
|
27
|
+
memex lint Structural problems (CI-friendly)
|
|
28
|
+
memex dedupe Find duplicate memories across projects
|
|
29
|
+
memex merge Collapse a duplicate cluster down to its representative
|
|
30
|
+
memex promote Move a memory (or whole cluster) into ~/.claude/CLAUDE.md
|
|
31
|
+
memex undo Reverse the most recent mutation (any of the above)
|
|
32
|
+
memex tui Interactive three-pane browser
|
|
33
|
+
memex mcp Run as an MCP server for Claude Code plugins
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Every mutation goes through a journaled `Plan → applyPlan` layer at
|
|
37
|
+
`~/.claude/.memex/log/`. **Everything is undoable.** `memex undo` reverses
|
|
38
|
+
the most recent plan; a second undo is redo.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
### As a Claude Code plugin (recommended)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
/plugin install claude-memory-explorer@community
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If the community catalog hasn't synced this plugin yet (sync is ~once
|
|
49
|
+
a day), install directly from this repo instead:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
/plugin marketplace add hseinmoussa/claude-memory-explorer
|
|
53
|
+
/plugin install claude-memory-explorer
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Once installed, ask Claude:
|
|
57
|
+
|
|
58
|
+
> "Clean up my memory."
|
|
59
|
+
|
|
60
|
+
…and the bundled `curate-memory` skill takes over. Claude calls `doctor`,
|
|
61
|
+
shows you the findings, generates Plans for the suggested fixes, asks for
|
|
62
|
+
your OK, then applies them via `apply_plan` (all journaled, all undoable).
|
|
63
|
+
|
|
64
|
+
### As a standalone CLI
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install -g claude-memory-explorer
|
|
68
|
+
|
|
69
|
+
memex doctor # health snapshot
|
|
70
|
+
memex lint # structural issues
|
|
71
|
+
memex dedupe # cross-project duplicates
|
|
72
|
+
memex tui # interactive browser
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or one-shot via `npx`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx claude-memory-explorer doctor
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## The killer demo
|
|
82
|
+
|
|
83
|
+
Three projects, same user-preference memory written into each:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
~/.claude/projects/-Users-foo-Desktop-vida/memory/user_terse.md
|
|
87
|
+
~/.claude/projects/-Users-foo-Desktop-app/memory/user_terse.md
|
|
88
|
+
~/.claude/projects/-Users-foo-Desktop-bot/memory/user_terse.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`memex dedupe` finds the cluster and flags it as a promote-to-global candidate:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
c1 type=user count=3 distinctProjects=3 [promote-to-global candidate]
|
|
95
|
+
★ -Users-foo-Desktop-bot/user_terse.md
|
|
96
|
+
-Users-foo-Desktop-vida/user_terse.md
|
|
97
|
+
-Users-foo-Desktop-app/user_terse.md
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`memex promote --cluster c1 --apply` then:
|
|
101
|
+
|
|
102
|
+
1. Writes the body into `~/.claude/CLAUDE.md` under a managed marker block:
|
|
103
|
+
```
|
|
104
|
+
<!-- memex:start:user_terse -->
|
|
105
|
+
## Be terse
|
|
106
|
+
The user prefers terse, direct answers.
|
|
107
|
+
<!-- memex:end:user_terse -->
|
|
108
|
+
```
|
|
109
|
+
2. Deletes the 3 source files
|
|
110
|
+
3. Cleans the affected `MEMORY.md` indexes (removes pointer lines, leaves unrelated entries intact)
|
|
111
|
+
|
|
112
|
+
Result: one block in `~/.claude/CLAUDE.md` (which Claude loads in every session of every project) replaces three siloed copies.
|
|
113
|
+
|
|
114
|
+
Run `memex undo` and everything is restored byte-for-byte.
|
|
115
|
+
|
|
116
|
+
## How memex thinks
|
|
117
|
+
|
|
118
|
+
| Concept | Description |
|
|
119
|
+
|---|---|
|
|
120
|
+
| **Memory** | A markdown file at `~/.claude/projects/<slug>/memory/<topic>.md` with YAML frontmatter (`type: user\|feedback\|project\|reference`) |
|
|
121
|
+
| **Index** | `MEMORY.md` in each memory dir. Documented as a list of pointer-style links to topic files; in practice Claude writes inline content too. memex tolerates both. |
|
|
122
|
+
| **Worktree collapse** | `<slug>--claude-worktrees-*` slugs collapse to the base slug when counting distinct projects, so worktrees of one repo don't inflate the duplicate count |
|
|
123
|
+
| **Promotion candidate** | A cluster where `distinctProjects ≥ 3` AND `type ∈ {user, feedback}`. Project- and reference-typed memories stay project-scoped. |
|
|
124
|
+
| **Managed block** | `<!-- memex:start:<slug> -->` / `<!-- memex:end:<slug> -->` markers in `~/.claude/CLAUDE.md`. memex never touches content outside its own markers. |
|
|
125
|
+
| **Plan** | A JSON-serializable list of `write` / `delete` / `ensure-dir` ops. Every mutation builds a plan, then `applyPlan` is the single writer. |
|
|
126
|
+
| **Journal** | `~/.claude/.memex/log/<id>.json`. One entry per applied plan, with inverse ops captured at apply time. `memex undo` walks the journal. |
|
|
127
|
+
|
|
128
|
+
## MCP server tools
|
|
129
|
+
|
|
130
|
+
When installed as a plugin, the MCP server exposes 11 tools:
|
|
131
|
+
|
|
132
|
+
| Tool | Type | Description |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `list_memories` | read | All memories, filterable by type/project |
|
|
135
|
+
| `find_duplicates` | read | Cross-project clusters |
|
|
136
|
+
| `lint` | read | Structural issues |
|
|
137
|
+
| `doctor` | read | Aggregate health report |
|
|
138
|
+
| `journal_list` | read | All journal entries |
|
|
139
|
+
| `journal_latest` | read | Most recent journal entry |
|
|
140
|
+
| `plan_merge` | plan | Build (don't execute) a merge plan |
|
|
141
|
+
| `plan_promote_memory` | plan | Build a single-memory promote plan |
|
|
142
|
+
| `plan_promote_cluster` | plan | Build a whole-cluster promote plan |
|
|
143
|
+
| `apply_plan` | write | Execute a Plan (journaled, undoable) |
|
|
144
|
+
| `undo` | write | Reverse most recent (or specific) plan |
|
|
145
|
+
|
|
146
|
+
Plan tools never execute. Claude must explicitly call `apply_plan` — that's the confirmation surface.
|
|
147
|
+
|
|
148
|
+
## Compared to existing tools
|
|
149
|
+
|
|
150
|
+
| Tool | Scope | What it does | What it doesn't |
|
|
151
|
+
|---|---|---|---|
|
|
152
|
+
| **memex** | Cross-project | Browse / lint / dedupe / promote / merge / journal-backed undo | (this is the gap) |
|
|
153
|
+
| `consolidate-memory` (Anthropic, built-in) | One project | LLM-driven dedup inside a memory dir | Cross-project view, structural lint, undo |
|
|
154
|
+
| `dream-skill` (community) | One project | Hook-triggered intra-project consolidation | Cross-project promotion |
|
|
155
|
+
| `mem0` / `supermemory` / vector-DB MCP servers | Separate store | Bolt a different memory store onto Claude | Don't touch the native `~/.claude/projects/*/memory/` format |
|
|
156
|
+
|
|
157
|
+
`memex` is the **curator** for the native auto-memory format, not a replacement store.
|
|
158
|
+
|
|
159
|
+
## Status
|
|
160
|
+
|
|
161
|
+
Pre-1.0. The CLI commands and MCP server are stable and tested
|
|
162
|
+
(191 unit + integration tests across 19 files, real end-to-end
|
|
163
|
+
round-trips for every mutation: scan, lint, dedupe, merge, promote,
|
|
164
|
+
undo, MCP, TUI). Tested on Node 20 and 22 on macOS and Ubuntu
|
|
165
|
+
(see CI badge above). Windows untested.
|
|
166
|
+
|
|
167
|
+
## Roadmap
|
|
168
|
+
|
|
169
|
+
- [x] v0.1: scan + parse, lint, doctor, dedupe (exact-match), merge, promote, undo, TUI, MCP
|
|
170
|
+
- [ ] v0.2: near-duplicate detection (TF-IDF char-3gram cosine, ~80 lines, zero new deps)
|
|
171
|
+
- [ ] v0.3: scheduled hook (auto-`doctor` weekly), conflict detection
|
|
172
|
+
- [ ] v0.4: semantic dedup (transformers.js + local MiniLM embedding)
|
|
173
|
+
- [ ] v1.0: when MCP TypeScript SDK v2 stabilizes
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDedupe(argv: string[]): number;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// `memex dedupe` — find duplicate memories across all projects.
|
|
2
|
+
//
|
|
3
|
+
// Read-only. Mutating merge/promote ships in Phase 6.
|
|
4
|
+
//
|
|
5
|
+
// Exit codes:
|
|
6
|
+
// 0 no duplicate clusters found
|
|
7
|
+
// 1 one or more clusters found (so CI users can gate on "no dupes")
|
|
8
|
+
// 2 bad usage
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { relative } from "node:path";
|
|
11
|
+
import { findDuplicates } from "../../core/dedupe.js";
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
const out = {
|
|
14
|
+
format: "human",
|
|
15
|
+
noIntra: false,
|
|
16
|
+
promoteOnly: false,
|
|
17
|
+
verbose: false,
|
|
18
|
+
help: false,
|
|
19
|
+
};
|
|
20
|
+
for (let i = 0; i < argv.length; i++) {
|
|
21
|
+
const a = argv[i];
|
|
22
|
+
switch (a) {
|
|
23
|
+
case "--project":
|
|
24
|
+
out.project = argv[++i];
|
|
25
|
+
break;
|
|
26
|
+
case "--root":
|
|
27
|
+
out.root = argv[++i];
|
|
28
|
+
break;
|
|
29
|
+
case "--json":
|
|
30
|
+
out.format = "json";
|
|
31
|
+
break;
|
|
32
|
+
case "--format": {
|
|
33
|
+
const f = argv[++i];
|
|
34
|
+
if (f !== "human" && f !== "fdupes" && f !== "json") {
|
|
35
|
+
throw new Error(`--format must be human|fdupes|json (got: ${f})`);
|
|
36
|
+
}
|
|
37
|
+
out.format = f;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case "--no-intra":
|
|
41
|
+
out.noIntra = true;
|
|
42
|
+
break;
|
|
43
|
+
case "--promote-only":
|
|
44
|
+
out.promoteOnly = true;
|
|
45
|
+
break;
|
|
46
|
+
case "--verbose":
|
|
47
|
+
case "-v":
|
|
48
|
+
out.verbose = true;
|
|
49
|
+
break;
|
|
50
|
+
case "--help":
|
|
51
|
+
case "-h":
|
|
52
|
+
out.help = true;
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`unknown flag: ${a}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
const HELP = `memex dedupe — find duplicate memories across projects
|
|
61
|
+
|
|
62
|
+
Usage:
|
|
63
|
+
memex dedupe [--project SUBSTR] [--format human|fdupes|json] [--json]
|
|
64
|
+
[--no-intra] [--promote-only] [--verbose] [--root PATH]
|
|
65
|
+
|
|
66
|
+
Flags:
|
|
67
|
+
--project SUBSTR Restrict to projects whose slug contains SUBSTR
|
|
68
|
+
--format FORMAT human (default), fdupes (pipe-friendly), json
|
|
69
|
+
--json Shorthand for --format json
|
|
70
|
+
--no-intra Hide clusters whose members are all in one project
|
|
71
|
+
--promote-only Show only clusters flagged as promotion candidates
|
|
72
|
+
(distinctProjects >= 3 AND type in {user, feedback})
|
|
73
|
+
--verbose, -v Surface scan-level warnings on stderr
|
|
74
|
+
--root PATH Override projects root (default: ~/.claude/projects)
|
|
75
|
+
|
|
76
|
+
Exit codes:
|
|
77
|
+
0 no duplicate clusters found
|
|
78
|
+
1 one or more clusters found
|
|
79
|
+
2 bad usage
|
|
80
|
+
|
|
81
|
+
Notes:
|
|
82
|
+
v0.1 finds EXACT body-content matches after normalization (NFC,
|
|
83
|
+
smart-quote fold, lowercase, strip trivial markdown wrappers,
|
|
84
|
+
collapse whitespace). Near-duplicates with paraphrased wording are
|
|
85
|
+
not yet detected — that's planned for v0.2.
|
|
86
|
+
`;
|
|
87
|
+
function shortPath(p) {
|
|
88
|
+
const home = homedir();
|
|
89
|
+
if (p.startsWith(home))
|
|
90
|
+
return "~" + p.slice(home.length);
|
|
91
|
+
try {
|
|
92
|
+
const rel = relative(process.cwd(), p);
|
|
93
|
+
return rel.length < p.length ? rel : p;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return p;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function filterClusters(clusters, args) {
|
|
100
|
+
let out = clusters;
|
|
101
|
+
if (args.noIntra)
|
|
102
|
+
out = out.filter((c) => !c.intraProject);
|
|
103
|
+
if (args.promoteOnly)
|
|
104
|
+
out = out.filter((c) => c.promotionCandidate);
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
function renderHuman(result, clusters) {
|
|
108
|
+
const lines = [];
|
|
109
|
+
if (clusters.length === 0) {
|
|
110
|
+
lines.push(`✓ No duplicate clusters across ${result.summary.projectsScanned} projects ` +
|
|
111
|
+
`(${result.summary.memoriesScanned} memories scanned).`);
|
|
112
|
+
return lines.join("\n") + "\n";
|
|
113
|
+
}
|
|
114
|
+
for (const c of clusters) {
|
|
115
|
+
const flags = [
|
|
116
|
+
c.intraProject ? "intra-project" : null,
|
|
117
|
+
c.promotionCandidate ? "promote-to-global candidate" : null,
|
|
118
|
+
].filter(Boolean);
|
|
119
|
+
const tag = flags.length ? ` [${flags.join(", ")}]` : "";
|
|
120
|
+
lines.push(`${c.id} type=${c.type} count=${c.count} distinctProjects=${c.distinctProjects}${tag}`);
|
|
121
|
+
lines.push(` fingerprint: ${c.fingerprint.slice(0, 12)}…`);
|
|
122
|
+
for (const m of c.members) {
|
|
123
|
+
const star = m.id === c.representative.id ? "★" : " ";
|
|
124
|
+
lines.push(` ${star} ${m.id}`);
|
|
125
|
+
lines.push(` name: ${m.name ?? "(none)"}`);
|
|
126
|
+
lines.push(` ${shortPath(m.filePath)} (${m.bodyBytes}B, ${m.mtime})`);
|
|
127
|
+
}
|
|
128
|
+
lines.push("");
|
|
129
|
+
}
|
|
130
|
+
lines.push(`${clusters.length} cluster(s) shown / ${result.summary.clusterCount} total · ` +
|
|
131
|
+
`${result.summary.duplicateFileCount} duplicate files · ` +
|
|
132
|
+
`${result.summary.wastedBytes}B wasted · ` +
|
|
133
|
+
`${result.summary.promotionCandidates} promote-to-global candidates`);
|
|
134
|
+
if (result.summary.skippedEmptyBodies > 0) {
|
|
135
|
+
lines.push(`(${result.summary.skippedEmptyBodies} empty-body memorie(s) excluded from clustering)`);
|
|
136
|
+
}
|
|
137
|
+
return lines.join("\n") + "\n";
|
|
138
|
+
}
|
|
139
|
+
function renderFdupes(clusters) {
|
|
140
|
+
// Classic fdupes shape: blank line between clusters, no per-cluster header.
|
|
141
|
+
// We do prepend a `#` comment with the cluster id so the output is still
|
|
142
|
+
// human-grepable, then list filePath one per line, representative first.
|
|
143
|
+
const lines = [];
|
|
144
|
+
for (const c of clusters) {
|
|
145
|
+
lines.push(`# ${c.id} type=${c.type} count=${c.count} distinctProjects=${c.distinctProjects}`);
|
|
146
|
+
const rep = c.members.find((m) => m.id === c.representative.id);
|
|
147
|
+
lines.push(rep.filePath);
|
|
148
|
+
for (const m of c.members) {
|
|
149
|
+
if (m.id !== rep.id)
|
|
150
|
+
lines.push(m.filePath);
|
|
151
|
+
}
|
|
152
|
+
lines.push("");
|
|
153
|
+
}
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
export function runDedupe(argv) {
|
|
157
|
+
let args;
|
|
158
|
+
try {
|
|
159
|
+
args = parseArgs(argv);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
process.stderr.write(`memex dedupe: ${err instanceof Error ? err.message : String(err)}\n\n${HELP}`);
|
|
163
|
+
return 2;
|
|
164
|
+
}
|
|
165
|
+
if (args.help) {
|
|
166
|
+
process.stdout.write(HELP);
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
const result = findDuplicates({
|
|
170
|
+
projectsRoot: args.root,
|
|
171
|
+
projectFilter: args.project,
|
|
172
|
+
onWarning: args.verbose ? (m) => process.stderr.write(`${m}\n`) : undefined,
|
|
173
|
+
});
|
|
174
|
+
const visible = filterClusters(result.clusters, args);
|
|
175
|
+
if (args.format === "json") {
|
|
176
|
+
process.stdout.write(`${JSON.stringify({ ...result, clusters: visible }, null, 2)}\n`);
|
|
177
|
+
}
|
|
178
|
+
else if (args.format === "fdupes") {
|
|
179
|
+
process.stdout.write(renderFdupes(visible));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
process.stdout.write(renderHuman(result, visible));
|
|
183
|
+
}
|
|
184
|
+
// Exit code reflects the unfiltered total — filters are presentation only.
|
|
185
|
+
return result.summary.clusterCount > 0 ? 1 : 0;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=dedupe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../../src/cli/commands/dedupe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDoctorCli(argv: string[]): number;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// `memex doctor` — human-friendly health report.
|
|
2
|
+
//
|
|
3
|
+
// Exit codes:
|
|
4
|
+
// 0 no errors found
|
|
5
|
+
// 1 one or more error-level issues
|
|
6
|
+
// 2 bad usage
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { relative } from "node:path";
|
|
9
|
+
import { runDoctor } from "../../core/doctor.js";
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const out = { json: false, verbose: false, help: false };
|
|
12
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13
|
+
const a = argv[i];
|
|
14
|
+
switch (a) {
|
|
15
|
+
case "--project":
|
|
16
|
+
out.project = argv[++i];
|
|
17
|
+
break;
|
|
18
|
+
case "--root":
|
|
19
|
+
out.root = argv[++i];
|
|
20
|
+
break;
|
|
21
|
+
case "--json":
|
|
22
|
+
out.json = true;
|
|
23
|
+
break;
|
|
24
|
+
case "--verbose":
|
|
25
|
+
case "-v":
|
|
26
|
+
out.verbose = true;
|
|
27
|
+
break;
|
|
28
|
+
case "--help":
|
|
29
|
+
case "-h":
|
|
30
|
+
out.help = true;
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`unknown flag: ${a}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
const HELP = `memex doctor — health snapshot of your memory tree
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
memex doctor [--project SUBSTR] [--json] [--verbose] [--root PATH]
|
|
42
|
+
|
|
43
|
+
Flags:
|
|
44
|
+
--project SUBSTR Filter projects whose slug contains SUBSTR
|
|
45
|
+
--json Output JSON instead of formatted report
|
|
46
|
+
--verbose, -v Surface scan-level warnings on stderr
|
|
47
|
+
--root PATH Override projects root (default: ~/.claude/projects)
|
|
48
|
+
|
|
49
|
+
Exit codes:
|
|
50
|
+
0 no errors
|
|
51
|
+
1 one or more error issues found
|
|
52
|
+
2 bad usage
|
|
53
|
+
`;
|
|
54
|
+
const SEV_LABEL = {
|
|
55
|
+
error: "ERROR",
|
|
56
|
+
warn: "WARN ",
|
|
57
|
+
info: "INFO ",
|
|
58
|
+
};
|
|
59
|
+
function shortPath(p) {
|
|
60
|
+
if (!p)
|
|
61
|
+
return "";
|
|
62
|
+
const home = homedir();
|
|
63
|
+
if (p.startsWith(home))
|
|
64
|
+
return "~" + p.slice(home.length);
|
|
65
|
+
try {
|
|
66
|
+
const rel = relative(process.cwd(), p);
|
|
67
|
+
return rel.length < p.length ? rel : p;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return p;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function formatProjectLine(p) {
|
|
74
|
+
const status = p.worstSeverity === "error"
|
|
75
|
+
? "✗"
|
|
76
|
+
: p.worstSeverity === "warn"
|
|
77
|
+
? "!"
|
|
78
|
+
: p.worstSeverity === "info"
|
|
79
|
+
? "i"
|
|
80
|
+
: "✓";
|
|
81
|
+
const indexInfo = (() => {
|
|
82
|
+
if (!p.hasIndex && p.memoryCount === 0)
|
|
83
|
+
return "no memory";
|
|
84
|
+
if (!p.hasIndex)
|
|
85
|
+
return `${p.memoryCount} files, no index`;
|
|
86
|
+
const sizeStr = p.indexLineCount !== null
|
|
87
|
+
? `${p.indexLineCount}L/${p.indexByteSize ?? "?"}B`
|
|
88
|
+
: `${p.indexByteSize ?? "?"}B`;
|
|
89
|
+
const tag = p.indexOverCap ? " OVER CAP" : p.indexApproachingCap ? " near cap" : "";
|
|
90
|
+
return `index ${sizeStr}${tag}, ${p.memoryCount} files`;
|
|
91
|
+
})();
|
|
92
|
+
return ` ${status} ${p.slug} (${indexInfo})`;
|
|
93
|
+
}
|
|
94
|
+
function formatReport(report) {
|
|
95
|
+
const { totals, projects, nextActions } = report;
|
|
96
|
+
const lines = [];
|
|
97
|
+
lines.push(`memex doctor — ${report.generatedAt}`);
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push(`Scanned ${report.projectsScanned} project(s), parsed ${report.filesScanned} memory file(s).`);
|
|
100
|
+
lines.push("");
|
|
101
|
+
// Issues summary
|
|
102
|
+
lines.push("Issues:");
|
|
103
|
+
lines.push(` ${totals.issuesBySeverity.error} error · ${totals.issuesBySeverity.warn} warn · ${totals.issuesBySeverity.info} info`);
|
|
104
|
+
lines.push("");
|
|
105
|
+
// Memory counts by type
|
|
106
|
+
lines.push("Memories by type:");
|
|
107
|
+
lines.push(` ${totals.memoriesByType.user} user · ${totals.memoriesByType.feedback} feedback · ${totals.memoriesByType.project} project · ${totals.memoriesByType.reference} reference · ${totals.memoriesByType.untyped} untyped`);
|
|
108
|
+
lines.push("");
|
|
109
|
+
// Per-project breakdown
|
|
110
|
+
lines.push("Projects:");
|
|
111
|
+
if (projects.length === 0) {
|
|
112
|
+
lines.push(" (none)");
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
for (const p of projects) {
|
|
116
|
+
lines.push(formatProjectLine(p));
|
|
117
|
+
for (const issue of p.issues) {
|
|
118
|
+
lines.push(` [${SEV_LABEL[issue.severity]}] ${issue.code}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
lines.push("");
|
|
123
|
+
// Next actions
|
|
124
|
+
if (nextActions.length > 0) {
|
|
125
|
+
lines.push("Next actions:");
|
|
126
|
+
nextActions.forEach((issue, idx) => {
|
|
127
|
+
lines.push(` ${idx + 1}. [${SEV_LABEL[issue.severity]}] ${issue.code} in ${issue.projectSlug}`);
|
|
128
|
+
lines.push(` ${issue.message}`);
|
|
129
|
+
if (issue.filePath)
|
|
130
|
+
lines.push(` at: ${shortPath(issue.filePath)}`);
|
|
131
|
+
if (issue.hint)
|
|
132
|
+
lines.push(` hint: ${issue.hint}`);
|
|
133
|
+
});
|
|
134
|
+
lines.push("");
|
|
135
|
+
}
|
|
136
|
+
// Footer verdict
|
|
137
|
+
if (totals.issuesBySeverity.error > 0) {
|
|
138
|
+
lines.push(`✗ Action needed: ${totals.issuesBySeverity.error} error issue(s) — memory currently broken.`);
|
|
139
|
+
}
|
|
140
|
+
else if (totals.issuesBySeverity.warn > 0) {
|
|
141
|
+
lines.push(`! ${totals.issuesBySeverity.warn} warn(s) to look at when you have time.`);
|
|
142
|
+
}
|
|
143
|
+
else if (totals.issuesBySeverity.info > 0) {
|
|
144
|
+
lines.push(`✓ No errors or warnings. ${totals.issuesBySeverity.info} info note(s).`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
lines.push("✓ All clear.");
|
|
148
|
+
}
|
|
149
|
+
return lines.join("\n") + "\n";
|
|
150
|
+
}
|
|
151
|
+
export function runDoctorCli(argv) {
|
|
152
|
+
let args;
|
|
153
|
+
try {
|
|
154
|
+
args = parseArgs(argv);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
process.stderr.write(`memex doctor: ${err instanceof Error ? err.message : String(err)}\n\n${HELP}`);
|
|
158
|
+
return 2;
|
|
159
|
+
}
|
|
160
|
+
if (args.help) {
|
|
161
|
+
process.stdout.write(HELP);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
const report = runDoctor({
|
|
165
|
+
projectsRoot: args.root,
|
|
166
|
+
projectFilter: args.project,
|
|
167
|
+
onWarning: args.verbose ? (m) => process.stderr.write(`${m}\n`) : undefined,
|
|
168
|
+
});
|
|
169
|
+
if (args.json) {
|
|
170
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
process.stdout.write(formatReport(report));
|
|
174
|
+
}
|
|
175
|
+
return report.totals.issuesBySeverity.error > 0 ? 1 : 0;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runLint(argv: string[]): number;
|