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,85 @@
|
|
|
1
|
+
// Mutation primitives — pure data, no I/O.
|
|
2
|
+
//
|
|
3
|
+
// Every change `memex` makes to disk is expressed as a `Plan`: an ordered list
|
|
4
|
+
// of small `Operation`s (write, delete, ensure-dir). The plan is the only
|
|
5
|
+
// thing that gets serialized to JSON, journaled to ~/.claude/.memex/log/, and
|
|
6
|
+
// passed to `applyPlan` (the single writer).
|
|
7
|
+
//
|
|
8
|
+
// Why this shape:
|
|
9
|
+
// - Plans are reviewable before they execute — `--dry-run` prints the plan
|
|
10
|
+
// and asks for confirmation.
|
|
11
|
+
// - Plans are reversible — every op carries enough information to compute its
|
|
12
|
+
// inverse from current disk state, captured inside `applyPlan` at execution
|
|
13
|
+
// time (we don't compute inverses ahead of time because the on-disk state
|
|
14
|
+
// may not exist yet when the plan is built).
|
|
15
|
+
// - Plans are composable — the merge command in Phase 6 builds a multi-op
|
|
16
|
+
// plan; promote builds another; the user (or Claude via MCP) can chain them.
|
|
17
|
+
// Monotonic counter — guarantees journal filenames sort by creation order
|
|
18
|
+
// even when two plans land in the same millisecond. Without this, `memex
|
|
19
|
+
// undo` (which uses the lex-sorted filename to pick the "newest") can flip
|
|
20
|
+
// the wrong direction when apply + undo happen in quick succession.
|
|
21
|
+
let lastTimeMs = 0;
|
|
22
|
+
let counter = 0;
|
|
23
|
+
function shortId() {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
if (now === lastTimeMs) {
|
|
26
|
+
counter++;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
lastTimeMs = now;
|
|
30
|
+
counter = 0;
|
|
31
|
+
}
|
|
32
|
+
const iso = new Date(now).toISOString().replace(/[:.]/g, "-");
|
|
33
|
+
// 4-digit counter keeps the field width fixed so lex sort tracks time order.
|
|
34
|
+
const counterStr = counter.toString().padStart(4, "0");
|
|
35
|
+
const hex = Math.random().toString(16).slice(2, 8);
|
|
36
|
+
return `${iso}-${counterStr}-${hex}`;
|
|
37
|
+
}
|
|
38
|
+
export function planBuilder(label, description = "") {
|
|
39
|
+
const ops = [];
|
|
40
|
+
return {
|
|
41
|
+
write(path, content) {
|
|
42
|
+
ops.push({ kind: "write", path, content });
|
|
43
|
+
return this;
|
|
44
|
+
},
|
|
45
|
+
delete(path) {
|
|
46
|
+
ops.push({ kind: "delete", path });
|
|
47
|
+
return this;
|
|
48
|
+
},
|
|
49
|
+
ensureDir(path) {
|
|
50
|
+
ops.push({ kind: "ensure-dir", path });
|
|
51
|
+
return this;
|
|
52
|
+
},
|
|
53
|
+
build() {
|
|
54
|
+
return {
|
|
55
|
+
id: shortId(),
|
|
56
|
+
label,
|
|
57
|
+
description: description || label,
|
|
58
|
+
createdAt: new Date().toISOString(),
|
|
59
|
+
ops: [...ops],
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Pretty-print a plan for `--dry-run` output. */
|
|
65
|
+
export function describePlan(plan) {
|
|
66
|
+
const lines = [
|
|
67
|
+
`Plan: ${plan.label}`,
|
|
68
|
+
` id: ${plan.id}`,
|
|
69
|
+
` description: ${plan.description}`,
|
|
70
|
+
` ${plan.ops.length} op(s):`,
|
|
71
|
+
];
|
|
72
|
+
for (const op of plan.ops) {
|
|
73
|
+
if (op.kind === "write") {
|
|
74
|
+
lines.push(` write ${op.path} (${Buffer.byteLength(op.content, "utf8")} bytes)`);
|
|
75
|
+
}
|
|
76
|
+
else if (op.kind === "delete") {
|
|
77
|
+
lines.push(` delete ${op.path}`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
lines.push(` ensure-dir ${op.path}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/core/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Plan } from "./plan.js";
|
|
2
|
+
import type { DuplicateCluster } from "./dedupe.js";
|
|
3
|
+
import type { Memory } from "./types.js";
|
|
4
|
+
export interface PromotePlanOptions {
|
|
5
|
+
/** Override the destination path (testing). */
|
|
6
|
+
claudeMdPath?: string;
|
|
7
|
+
/** When true, MEMORY.md indexes are not modified. */
|
|
8
|
+
skipIndexUpdate?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface PromoteSummary {
|
|
11
|
+
blockSlug: string;
|
|
12
|
+
claudeMdPath: string;
|
|
13
|
+
deletedIds: string[];
|
|
14
|
+
indexesUpdated: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface PromotePlanResult {
|
|
17
|
+
plan: Plan;
|
|
18
|
+
summary: PromoteSummary;
|
|
19
|
+
}
|
|
20
|
+
/** Promote a single memory file. */
|
|
21
|
+
export declare function buildPromoteMemoryPlan(memory: Memory, opts?: PromotePlanOptions): PromotePlanResult;
|
|
22
|
+
/**
|
|
23
|
+
* Promote a whole duplicate cluster: write the representative's body into
|
|
24
|
+
* CLAUDE.md as one managed block, then delete EVERY cluster member (since the
|
|
25
|
+
* block now replaces all copies).
|
|
26
|
+
*
|
|
27
|
+
* Used when `memex dedupe` flags a cluster as `promotionCandidate`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildPromoteClusterPlan(cluster: DuplicateCluster, representativeBody: string, opts?: PromotePlanOptions): PromotePlanResult;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Build a Plan that promotes a memory (or whole cluster) into
|
|
2
|
+
// `~/.claude/CLAUDE.md` as a managed block.
|
|
3
|
+
//
|
|
4
|
+
// memex never touches content in CLAUDE.md outside its own `<!-- memex:start
|
|
5
|
+
// -->` / `<!-- memex:end -->` markers. The upsert is idempotent — promoting
|
|
6
|
+
// the same slug twice replaces the existing block instead of appending.
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { basename, dirname, join } from "node:path";
|
|
9
|
+
import { planBuilder } from "./plan.js";
|
|
10
|
+
import { defaultClaudeMdPath, toBlockSlug, upsertBlock, } from "./claudeMd.js";
|
|
11
|
+
import { removeIndexEntry } from "./memoryIndex.js";
|
|
12
|
+
function indexUpdateOps(builder, memoryDir, fileName, indexesUpdated) {
|
|
13
|
+
const indexPath = join(memoryDir, "MEMORY.md");
|
|
14
|
+
if (!existsSync(indexPath))
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
const current = readFileSync(indexPath, "utf8");
|
|
18
|
+
const { content, removedLines } = removeIndexEntry(current, fileName);
|
|
19
|
+
if (removedLines > 0) {
|
|
20
|
+
builder.write(indexPath, content);
|
|
21
|
+
indexesUpdated.push(indexPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// ignore — lint will catch any resulting dangling ref
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function blockFromMemory(memory) {
|
|
29
|
+
const heading = memory.name ?? memory.fileName.replace(/\.md$/, "").replace(/_/g, " ");
|
|
30
|
+
return {
|
|
31
|
+
slug: toBlockSlug(memory.fileName),
|
|
32
|
+
heading,
|
|
33
|
+
body: memory.body,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function blockFromClusterMember(member, body) {
|
|
37
|
+
const heading = member.name ?? basename(member.filePath, ".md").replace(/_/g, " ");
|
|
38
|
+
return {
|
|
39
|
+
slug: toBlockSlug(member.filePath.split("/").pop()),
|
|
40
|
+
heading,
|
|
41
|
+
body,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Promote a single memory file. */
|
|
45
|
+
export function buildPromoteMemoryPlan(memory, opts = {}) {
|
|
46
|
+
const claudeMdPath = opts.claudeMdPath ?? defaultClaudeMdPath();
|
|
47
|
+
const block = blockFromMemory(memory);
|
|
48
|
+
const current = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, "utf8") : "";
|
|
49
|
+
const next = upsertBlock(current, block);
|
|
50
|
+
const builder = planBuilder(`promote:${block.slug}`, `Promote ${memory.id} to ${claudeMdPath} as block "${block.slug}"`);
|
|
51
|
+
builder.write(claudeMdPath, next);
|
|
52
|
+
builder.delete(memory.filePath);
|
|
53
|
+
const indexesUpdated = [];
|
|
54
|
+
if (!opts.skipIndexUpdate) {
|
|
55
|
+
indexUpdateOps(builder, dirname(memory.filePath), memory.fileName, indexesUpdated);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
plan: builder.build(),
|
|
59
|
+
summary: {
|
|
60
|
+
blockSlug: block.slug,
|
|
61
|
+
claudeMdPath,
|
|
62
|
+
deletedIds: [memory.id],
|
|
63
|
+
indexesUpdated,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Promote a whole duplicate cluster: write the representative's body into
|
|
69
|
+
* CLAUDE.md as one managed block, then delete EVERY cluster member (since the
|
|
70
|
+
* block now replaces all copies).
|
|
71
|
+
*
|
|
72
|
+
* Used when `memex dedupe` flags a cluster as `promotionCandidate`.
|
|
73
|
+
*/
|
|
74
|
+
export function buildPromoteClusterPlan(cluster, representativeBody, opts = {}) {
|
|
75
|
+
const claudeMdPath = opts.claudeMdPath ?? defaultClaudeMdPath();
|
|
76
|
+
const rep = cluster.members.find((m) => m.id === cluster.representative.id);
|
|
77
|
+
if (!rep) {
|
|
78
|
+
throw new Error(`cluster ${cluster.id} representative not found in members`);
|
|
79
|
+
}
|
|
80
|
+
const block = blockFromClusterMember(rep, representativeBody);
|
|
81
|
+
const current = existsSync(claudeMdPath) ? readFileSync(claudeMdPath, "utf8") : "";
|
|
82
|
+
const next = upsertBlock(current, block);
|
|
83
|
+
const builder = planBuilder(`promote-cluster:${cluster.id}`, `Promote cluster ${cluster.id} (${cluster.count} members → 1 block in ${claudeMdPath})`);
|
|
84
|
+
builder.write(claudeMdPath, next);
|
|
85
|
+
for (const m of cluster.members)
|
|
86
|
+
builder.delete(m.filePath);
|
|
87
|
+
const indexesUpdated = [];
|
|
88
|
+
if (!opts.skipIndexUpdate) {
|
|
89
|
+
for (const m of cluster.members) {
|
|
90
|
+
indexUpdateOps(builder, dirname(m.filePath), m.filePath.split("/").pop(), indexesUpdated);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
plan: builder.build(),
|
|
95
|
+
summary: {
|
|
96
|
+
blockSlug: block.slug,
|
|
97
|
+
claudeMdPath,
|
|
98
|
+
deletedIds: cluster.members.map((m) => m.id),
|
|
99
|
+
indexesUpdated,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=promote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promote.js","sourceRoot":"","sources":["../../src/core/promote.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Memory, ProjectMemoryDir } from "./types.js";
|
|
2
|
+
export interface ScanOptions {
|
|
3
|
+
/** Override the projects root. Defaults to `~/.claude/projects/`. */
|
|
4
|
+
projectsRoot?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Sink for non-fatal warnings (unreadable files, broken frontmatter, etc.).
|
|
7
|
+
* Defaults to a no-op — callers that need this info (the lint rules) should
|
|
8
|
+
* pass an explicit sink. The CLI surfaces a `--verbose` flag for users who
|
|
9
|
+
* want to see scan-level warnings on stderr.
|
|
10
|
+
*/
|
|
11
|
+
onWarning?: (msg: string) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function defaultProjectsRoot(): string;
|
|
14
|
+
export declare function scanAll(opts?: ScanOptions): ProjectMemoryDir[];
|
|
15
|
+
/** Flatten all memories across all projects into one list. */
|
|
16
|
+
export declare function allMemories(projects: ProjectMemoryDir[]): Memory[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Walks `~/.claude/projects/<slug>/memory/` and returns one ProjectMemoryDir
|
|
2
|
+
// per project that has a memory directory.
|
|
3
|
+
//
|
|
4
|
+
// Projects without a `memory/` subdir are skipped silently — that's the normal
|
|
5
|
+
// state for projects Claude hasn't written memory for yet.
|
|
6
|
+
import { readdirSync, existsSync, statSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { parseMemoryFile } from "./parse.js";
|
|
10
|
+
const INDEX_FILE = "MEMORY.md";
|
|
11
|
+
export function defaultProjectsRoot() {
|
|
12
|
+
return join(homedir(), ".claude", "projects");
|
|
13
|
+
}
|
|
14
|
+
function safeIsDirectory(path) {
|
|
15
|
+
try {
|
|
16
|
+
return statSync(path).isDirectory();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function scanAll(opts = {}) {
|
|
23
|
+
const root = opts.projectsRoot ?? defaultProjectsRoot();
|
|
24
|
+
const warn = opts.onWarning ?? (() => { });
|
|
25
|
+
if (!existsSync(root))
|
|
26
|
+
return [];
|
|
27
|
+
const projects = [];
|
|
28
|
+
let slugs;
|
|
29
|
+
try {
|
|
30
|
+
slugs = readdirSync(root);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
warn(`memex: cannot read ${root}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
for (const slug of slugs) {
|
|
37
|
+
const projectDir = join(root, slug);
|
|
38
|
+
const memoryDir = join(projectDir, "memory");
|
|
39
|
+
if (!safeIsDirectory(projectDir))
|
|
40
|
+
continue;
|
|
41
|
+
if (!safeIsDirectory(memoryDir))
|
|
42
|
+
continue;
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = readdirSync(memoryDir);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
warn(`memex: cannot read ${memoryDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const memories = [];
|
|
52
|
+
for (const name of entries) {
|
|
53
|
+
if (!name.endsWith(".md"))
|
|
54
|
+
continue;
|
|
55
|
+
if (name === INDEX_FILE)
|
|
56
|
+
continue;
|
|
57
|
+
const filePath = join(memoryDir, name);
|
|
58
|
+
try {
|
|
59
|
+
memories.push(parseMemoryFile(filePath, slug));
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
warn(`memex: skip ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Deterministic order across runs — keep filename sort.
|
|
66
|
+
memories.sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
67
|
+
projects.push({
|
|
68
|
+
slug,
|
|
69
|
+
path: projectDir,
|
|
70
|
+
hasIndex: existsSync(join(memoryDir, INDEX_FILE)),
|
|
71
|
+
memories,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
projects.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
75
|
+
return projects;
|
|
76
|
+
}
|
|
77
|
+
/** Flatten all memories across all projects into one list. */
|
|
78
|
+
export function allMemories(projects) {
|
|
79
|
+
return projects.flatMap((p) => p.memories);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/core/scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare const MEMORY_TYPES: readonly ["user", "feedback", "project", "reference"];
|
|
2
|
+
export type MemoryType = (typeof MEMORY_TYPES)[number];
|
|
3
|
+
export type MemoryTypeOrUntyped = MemoryType | "untyped";
|
|
4
|
+
export type TypeSource = "frontmatter" | "filename-prefix" | "unknown";
|
|
5
|
+
export interface Memory {
|
|
6
|
+
/** Stable id: `<project-slug>/<filename>`. Survives across scans. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The directory name under `~/.claude/projects/` (slug-encoded path). */
|
|
9
|
+
projectSlug: string;
|
|
10
|
+
/** Absolute path to the file on disk. */
|
|
11
|
+
filePath: string;
|
|
12
|
+
/** Just the filename, e.g. `user_git_rules.md`. */
|
|
13
|
+
fileName: string;
|
|
14
|
+
type: MemoryTypeOrUntyped;
|
|
15
|
+
typeSource: TypeSource;
|
|
16
|
+
/** From frontmatter `name:`. Null if absent. */
|
|
17
|
+
name: string | null;
|
|
18
|
+
/** From frontmatter `description:`. Null if absent. */
|
|
19
|
+
description: string | null;
|
|
20
|
+
/** Body content with frontmatter stripped. */
|
|
21
|
+
body: string;
|
|
22
|
+
/** Full frontmatter object — we tolerate unknown fields like `originSessionId`. */
|
|
23
|
+
frontmatter: Record<string, unknown>;
|
|
24
|
+
/** File mtime in ms since epoch, for staleness checks. */
|
|
25
|
+
mtimeMs: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ProjectMemoryDir {
|
|
28
|
+
/** Directory name under `~/.claude/projects/` (slug-encoded). */
|
|
29
|
+
slug: string;
|
|
30
|
+
/** Absolute path to the project dir (`<root>/<slug>`). */
|
|
31
|
+
path: string;
|
|
32
|
+
/** Whether a `MEMORY.md` index file exists in the memory dir. */
|
|
33
|
+
hasIndex: boolean;
|
|
34
|
+
/** Parsed memories — does not include `MEMORY.md` itself. */
|
|
35
|
+
memories: Memory[];
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAE1E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,CAAU,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export interface ServerOptions {
|
|
3
|
+
/** Override projects root for testing. */
|
|
4
|
+
projectsRoot?: string;
|
|
5
|
+
/** Override CLAUDE.md path for testing. */
|
|
6
|
+
claudeMdPath?: string;
|
|
7
|
+
/** Override journal dir for testing. */
|
|
8
|
+
journalDir?: string;
|
|
9
|
+
/** Read package.json for the version string. */
|
|
10
|
+
version: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildMcpServer(opts: ServerOptions): McpServer;
|
|
13
|
+
export declare function runMcpServerStdio(opts: ServerOptions): Promise<void>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// memex MCP server — exposes the same operations as the CLI over MCP stdio.
|
|
2
|
+
//
|
|
3
|
+
// The pitch: install this once as a Claude Code plugin, and Claude itself
|
|
4
|
+
// can call list_memories / find_duplicates / lint / doctor / plan_merge /
|
|
5
|
+
// plan_promote / apply_plan / undo as tools. Periodic memory maintenance
|
|
6
|
+
// stops being a manual chore and becomes something Claude does during a
|
|
7
|
+
// session when the user asks "clean up my memory".
|
|
8
|
+
//
|
|
9
|
+
// Safety model (mirrors the CLI):
|
|
10
|
+
// - Read-only tools: list_memories, find_duplicates, lint, doctor
|
|
11
|
+
// - Plan generators: plan_merge, plan_promote_memory, plan_promote_cluster
|
|
12
|
+
// → return a Plan, do not execute
|
|
13
|
+
// - Mutating: apply_plan (executes a Plan it was just given), undo
|
|
14
|
+
// - Everything mutating goes through Phase 5's journal, so undo always works
|
|
15
|
+
//
|
|
16
|
+
// We pin to @modelcontextprotocol/sdk v1.x — v2 is pre-alpha as of 2026-05.
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { scanAll, allMemories } from "../core/scan.js";
|
|
21
|
+
import { lintAll } from "../core/lint.js";
|
|
22
|
+
import { runDoctor } from "../core/doctor.js";
|
|
23
|
+
import { findDuplicates } from "../core/dedupe.js";
|
|
24
|
+
import { buildMergePlan } from "../core/merge.js";
|
|
25
|
+
import { buildPromoteMemoryPlan, buildPromoteClusterPlan } from "../core/promote.js";
|
|
26
|
+
import { applyPlan, undoPlan } from "../core/apply.js";
|
|
27
|
+
import { latestJournalEntry, listJournal } from "../core/journal.js";
|
|
28
|
+
import { readFileSync } from "node:fs";
|
|
29
|
+
// Plan schema — used by apply_plan so callers send back exactly what a plan_*
|
|
30
|
+
// tool returned. We don't trust arbitrary disk paths here, but we don't need
|
|
31
|
+
// to: every plan went through our generator and is just JSON.
|
|
32
|
+
const PlanSchema = z.object({
|
|
33
|
+
id: z.string(),
|
|
34
|
+
label: z.string(),
|
|
35
|
+
description: z.string(),
|
|
36
|
+
createdAt: z.string(),
|
|
37
|
+
ops: z.array(z.union([
|
|
38
|
+
z.object({
|
|
39
|
+
kind: z.literal("write"),
|
|
40
|
+
path: z.string(),
|
|
41
|
+
content: z.string(),
|
|
42
|
+
}),
|
|
43
|
+
z.object({
|
|
44
|
+
kind: z.literal("delete"),
|
|
45
|
+
path: z.string(),
|
|
46
|
+
}),
|
|
47
|
+
z.object({
|
|
48
|
+
kind: z.literal("ensure-dir"),
|
|
49
|
+
path: z.string(),
|
|
50
|
+
}),
|
|
51
|
+
])),
|
|
52
|
+
});
|
|
53
|
+
const SCAN_OPTS_DESC = "Optional projectsRoot — default is ~/.claude/projects. Set CLAUDE_PROJECT_DIR for project-scoped Claude Code MCP usage.";
|
|
54
|
+
function projectsRootFromEnv() {
|
|
55
|
+
// Claude Code auto-sets CLAUDE_PROJECT_DIR for MCP servers it launches.
|
|
56
|
+
// We don't use it as the projects root (that's always ~/.claude/projects/),
|
|
57
|
+
// but we keep this hook for future "current project" defaults.
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
function jsonReply(payload) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function buildMcpServer(opts) {
|
|
66
|
+
const server = new McpServer({
|
|
67
|
+
name: "memex",
|
|
68
|
+
version: opts.version,
|
|
69
|
+
});
|
|
70
|
+
const root = opts.projectsRoot ?? projectsRootFromEnv();
|
|
71
|
+
// -------- read-only tools --------
|
|
72
|
+
server.tool("list_memories", `List memories across all projects under ~/.claude/projects (or projectsRoot). ${SCAN_OPTS_DESC}`, {
|
|
73
|
+
type: z.enum(["user", "feedback", "project", "reference", "untyped"]).optional()
|
|
74
|
+
.describe("Filter by memory type"),
|
|
75
|
+
project: z.string().optional()
|
|
76
|
+
.describe("Filter to projects whose slug contains this substring"),
|
|
77
|
+
}, async ({ type, project }) => {
|
|
78
|
+
const projects = scanAll({ projectsRoot: root });
|
|
79
|
+
const filtered = project ? projects.filter((p) => p.slug.includes(project)) : projects;
|
|
80
|
+
const memories = allMemories(filtered).filter((m) => !type || m.type === type);
|
|
81
|
+
return jsonReply(memories);
|
|
82
|
+
});
|
|
83
|
+
server.tool("find_duplicates", "Find duplicate memory clusters across projects. Cross-project user/feedback duplicates are flagged as promotion candidates for ~/.claude/CLAUDE.md.", {
|
|
84
|
+
project: z.string().optional()
|
|
85
|
+
.describe("Filter to projects whose slug contains this substring"),
|
|
86
|
+
promotionOnly: z.boolean().optional()
|
|
87
|
+
.describe("Return only clusters flagged as promote-to-global candidates"),
|
|
88
|
+
}, async ({ project, promotionOnly }) => {
|
|
89
|
+
const result = findDuplicates({ projectsRoot: root, projectFilter: project });
|
|
90
|
+
if (promotionOnly) {
|
|
91
|
+
result.clusters = result.clusters.filter((c) => c.promotionCandidate);
|
|
92
|
+
}
|
|
93
|
+
return jsonReply(result);
|
|
94
|
+
});
|
|
95
|
+
server.tool("lint", "Lint MEMORY.md indexes and memory files. Surfaces broken-yaml, dangling-ref, missing-index, orphan-file, untyped-file, and over-cap issues.", {
|
|
96
|
+
project: z.string().optional(),
|
|
97
|
+
}, async ({ project }) => {
|
|
98
|
+
const result = lintAll({ projectsRoot: root, projectFilter: project });
|
|
99
|
+
return jsonReply(result);
|
|
100
|
+
});
|
|
101
|
+
server.tool("doctor", "One-shot memory health report: per-project status, severity counts, index size warnings, and prioritized next actions.", {
|
|
102
|
+
project: z.string().optional(),
|
|
103
|
+
}, async ({ project }) => {
|
|
104
|
+
const report = runDoctor({ projectsRoot: root, projectFilter: project });
|
|
105
|
+
return jsonReply(report);
|
|
106
|
+
});
|
|
107
|
+
// -------- plan generators (return Plan, do not execute) --------
|
|
108
|
+
server.tool("plan_merge", "Build (but do not execute) a Plan to merge a duplicate cluster down to its representative. The returned Plan can be sent to apply_plan to commit.", {
|
|
109
|
+
clusterId: z.string().describe("Cluster id from find_duplicates, e.g. c1"),
|
|
110
|
+
keepId: z.string().optional()
|
|
111
|
+
.describe("Override which member to keep (default: longest body + most recent mtime)"),
|
|
112
|
+
skipIndexUpdate: z.boolean().optional()
|
|
113
|
+
.describe("Don't modify MEMORY.md indexes"),
|
|
114
|
+
}, async ({ clusterId, keepId, skipIndexUpdate }) => {
|
|
115
|
+
const dedupe = findDuplicates({ projectsRoot: root });
|
|
116
|
+
const cluster = dedupe.clusters.find((c) => c.id === clusterId);
|
|
117
|
+
if (!cluster) {
|
|
118
|
+
return jsonReply({ error: `cluster "${clusterId}" not found`, available: dedupe.clusters.map((c) => c.id) });
|
|
119
|
+
}
|
|
120
|
+
const result = buildMergePlan(cluster, { keepId, skipIndexUpdate });
|
|
121
|
+
return jsonReply(result);
|
|
122
|
+
});
|
|
123
|
+
server.tool("plan_promote_memory", "Build a Plan to promote a single memory to ~/.claude/CLAUDE.md as a managed block. apply_plan it to commit.", {
|
|
124
|
+
memoryId: z.string().describe("Memory id from list_memories, e.g. <project-slug>/<filename>"),
|
|
125
|
+
skipIndexUpdate: z.boolean().optional(),
|
|
126
|
+
}, async ({ memoryId, skipIndexUpdate }) => {
|
|
127
|
+
const projects = scanAll({ projectsRoot: root });
|
|
128
|
+
const memory = allMemories(projects).find((m) => m.id === memoryId);
|
|
129
|
+
if (!memory) {
|
|
130
|
+
return jsonReply({ error: `memory "${memoryId}" not found` });
|
|
131
|
+
}
|
|
132
|
+
const result = buildPromoteMemoryPlan(memory, {
|
|
133
|
+
claudeMdPath: opts.claudeMdPath,
|
|
134
|
+
skipIndexUpdate,
|
|
135
|
+
});
|
|
136
|
+
return jsonReply(result);
|
|
137
|
+
});
|
|
138
|
+
server.tool("plan_promote_cluster", "Build a Plan to promote a whole duplicate cluster (representative → CLAUDE.md, all members deleted).", {
|
|
139
|
+
clusterId: z.string(),
|
|
140
|
+
skipIndexUpdate: z.boolean().optional(),
|
|
141
|
+
}, async ({ clusterId, skipIndexUpdate }) => {
|
|
142
|
+
const dedupe = findDuplicates({ projectsRoot: root });
|
|
143
|
+
const cluster = dedupe.clusters.find((c) => c.id === clusterId);
|
|
144
|
+
if (!cluster) {
|
|
145
|
+
return jsonReply({ error: `cluster "${clusterId}" not found` });
|
|
146
|
+
}
|
|
147
|
+
const rep = cluster.members.find((m) => m.id === cluster.representative.id);
|
|
148
|
+
if (!rep) {
|
|
149
|
+
return jsonReply({ error: "representative missing from members" });
|
|
150
|
+
}
|
|
151
|
+
let repBody = "";
|
|
152
|
+
try {
|
|
153
|
+
const raw = readFileSync(rep.filePath, "utf8");
|
|
154
|
+
repBody = raw.replace(/^---[\s\S]*?---\n?/, "").trim();
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
return jsonReply({ error: `cannot read representative: ${err instanceof Error ? err.message : String(err)}` });
|
|
158
|
+
}
|
|
159
|
+
const result = buildPromoteClusterPlan(cluster, repBody, {
|
|
160
|
+
claudeMdPath: opts.claudeMdPath,
|
|
161
|
+
skipIndexUpdate,
|
|
162
|
+
});
|
|
163
|
+
return jsonReply(result);
|
|
164
|
+
});
|
|
165
|
+
// -------- mutating tools (always journaled, undoable) --------
|
|
166
|
+
server.tool("apply_plan", "Execute a Plan returned by plan_merge / plan_promote_*. The mutation is journaled at ~/.claude/.memex/log/ and reversible via undo.", {
|
|
167
|
+
plan: PlanSchema,
|
|
168
|
+
}, async ({ plan }) => {
|
|
169
|
+
const entry = applyPlan(plan, { journalDir: opts.journalDir });
|
|
170
|
+
return jsonReply({ status: entry.status, entryId: entry.plan.id, ops: entry.ops });
|
|
171
|
+
});
|
|
172
|
+
server.tool("undo", "Reverse the most recent applied plan, or a specific plan by id. Writes a new journal entry that itself can be undone (redo).", {
|
|
173
|
+
planId: z.string().optional().describe("Specific plan id; default is the most recent"),
|
|
174
|
+
}, async ({ planId }) => {
|
|
175
|
+
const { undone, newEntry } = undoPlan(planId ?? "latest", { journalDir: opts.journalDir });
|
|
176
|
+
if (!undone) {
|
|
177
|
+
return jsonReply({ error: planId ? `no journal entry with id "${planId}"` : "journal is empty" });
|
|
178
|
+
}
|
|
179
|
+
return jsonReply({
|
|
180
|
+
undoneId: undone.plan.id,
|
|
181
|
+
undoneLabel: undone.plan.label,
|
|
182
|
+
newEntryId: newEntry?.plan.id ?? null,
|
|
183
|
+
status: newEntry?.status ?? "no-op",
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
server.tool("journal_list", "List all entries in the mutation journal, newest first. Includes status, op counts, and undoOf links.", {}, async () => {
|
|
187
|
+
const entries = listJournal({ journalDir: opts.journalDir });
|
|
188
|
+
return jsonReply(entries.map((e) => ({
|
|
189
|
+
id: e.plan.id,
|
|
190
|
+
label: e.plan.label,
|
|
191
|
+
status: e.status,
|
|
192
|
+
startedAt: e.startedAt,
|
|
193
|
+
finishedAt: e.finishedAt,
|
|
194
|
+
opCount: e.plan.ops.length,
|
|
195
|
+
undoOf: e.undoOf ?? null,
|
|
196
|
+
})));
|
|
197
|
+
});
|
|
198
|
+
server.tool("journal_latest", "Return the most recent journal entry (for inspection before calling undo).", {}, async () => {
|
|
199
|
+
const latest = latestJournalEntry({ journalDir: opts.journalDir });
|
|
200
|
+
if (!latest)
|
|
201
|
+
return jsonReply(null);
|
|
202
|
+
return jsonReply(latest);
|
|
203
|
+
});
|
|
204
|
+
return server;
|
|
205
|
+
}
|
|
206
|
+
export async function runMcpServerStdio(opts) {
|
|
207
|
+
const server = buildMcpServer(opts);
|
|
208
|
+
const transport = new StdioServerTransport();
|
|
209
|
+
await server.connect(transport);
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|