r2mcp 0.2.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/CHANGELOG.md +66 -0
- package/LICENSE +21 -0
- package/README.md +532 -0
- package/dist/breadcrumbs.d.ts +123 -0
- package/dist/breadcrumbs.js +135 -0
- package/dist/cli/classify-edges.d.ts +2 -0
- package/dist/cli/classify-edges.js +130 -0
- package/dist/cli/compile-wiki.d.ts +2 -0
- package/dist/cli/compile-wiki.js +173 -0
- package/dist/cli/dump-edges-json.d.ts +2 -0
- package/dist/cli/dump-edges-json.js +21 -0
- package/dist/cli/extract-entities.d.ts +17 -0
- package/dist/cli/extract-entities.js +166 -0
- package/dist/cli/lint-memory.d.ts +16 -0
- package/dist/cli/lint-memory.js +94 -0
- package/dist/cli/migrate.d.ts +17 -0
- package/dist/cli/migrate.js +146 -0
- package/dist/cli/setup-helpers.d.ts +7 -0
- package/dist/cli/setup-helpers.js +72 -0
- package/dist/cli/setup.d.ts +15 -0
- package/dist/cli/setup.js +95 -0
- package/dist/compiler/clustering.d.ts +29 -0
- package/dist/compiler/clustering.js +66 -0
- package/dist/compiler/frontmatter.d.ts +35 -0
- package/dist/compiler/frontmatter.js +168 -0
- package/dist/compiler/manifest.d.ts +32 -0
- package/dist/compiler/manifest.js +82 -0
- package/dist/compiler/prompts.d.ts +17 -0
- package/dist/compiler/prompts.js +82 -0
- package/dist/compiler/run.d.ts +52 -0
- package/dist/compiler/run.js +186 -0
- package/dist/compiler/tier.d.ts +10 -0
- package/dist/compiler/tier.js +85 -0
- package/dist/compiler/topic.d.ts +16 -0
- package/dist/compiler/topic.js +105 -0
- package/dist/compiler/types.d.ts +101 -0
- package/dist/compiler/types.js +4 -0
- package/dist/db.d.ts +10 -0
- package/dist/db.js +46 -0
- package/dist/edges/candidate-pairs.d.ts +24 -0
- package/dist/edges/candidate-pairs.js +35 -0
- package/dist/edges/classifier.d.ts +45 -0
- package/dist/edges/classifier.js +172 -0
- package/dist/edges/signals.d.ts +13 -0
- package/dist/edges/signals.js +45 -0
- package/dist/edges/stage1-haiku.d.ts +21 -0
- package/dist/edges/stage1-haiku.js +33 -0
- package/dist/edges/stage2-opus.d.ts +41 -0
- package/dist/edges/stage2-opus.js +101 -0
- package/dist/edges/state.d.ts +44 -0
- package/dist/edges/state.js +79 -0
- package/dist/edges/types.d.ts +20 -0
- package/dist/edges/types.js +1 -0
- package/dist/embeddings.d.ts +13 -0
- package/dist/embeddings.js +54 -0
- package/dist/entities/db.d.ts +49 -0
- package/dist/entities/db.js +109 -0
- package/dist/entities/extractor.d.ts +14 -0
- package/dist/entities/extractor.js +154 -0
- package/dist/entities/normalize.d.ts +5 -0
- package/dist/entities/normalize.js +7 -0
- package/dist/entities/prompt.d.ts +19 -0
- package/dist/entities/prompt.js +100 -0
- package/dist/entities/state.d.ts +44 -0
- package/dist/entities/state.js +99 -0
- package/dist/entities/types.d.ts +62 -0
- package/dist/entities/types.js +6 -0
- package/dist/env.d.ts +13 -0
- package/dist/env.js +32 -0
- package/dist/fingerprint.d.ts +2 -0
- package/dist/fingerprint.js +12 -0
- package/dist/graph-rebuild.d.ts +6 -0
- package/dist/graph-rebuild.js +20 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +403 -0
- package/dist/instrumentation.d.ts +10 -0
- package/dist/instrumentation.js +37 -0
- package/dist/lint/checks/contradictions.d.ts +30 -0
- package/dist/lint/checks/contradictions.js +52 -0
- package/dist/lint/checks/drift.d.ts +5 -0
- package/dist/lint/checks/drift.js +34 -0
- package/dist/lint/checks/orphans.d.ts +5 -0
- package/dist/lint/checks/orphans.js +25 -0
- package/dist/lint/checks/stale.d.ts +6 -0
- package/dist/lint/checks/stale.js +29 -0
- package/dist/lint/checks/superseded-unflagged.d.ts +5 -0
- package/dist/lint/checks/superseded-unflagged.js +47 -0
- package/dist/lint/run.d.ts +11 -0
- package/dist/lint/run.js +95 -0
- package/dist/lint/types.d.ts +60 -0
- package/dist/lint/types.js +13 -0
- package/dist/mcp-response.d.ts +7 -0
- package/dist/mcp-response.js +13 -0
- package/dist/providers/anthropic.d.ts +13 -0
- package/dist/providers/anthropic.js +56 -0
- package/dist/providers/claude-code.d.ts +35 -0
- package/dist/providers/claude-code.js +175 -0
- package/dist/providers/errors.d.ts +12 -0
- package/dist/providers/errors.js +19 -0
- package/dist/providers/index.d.ts +30 -0
- package/dist/providers/index.js +71 -0
- package/dist/providers/openrouter.d.ts +19 -0
- package/dist/providers/openrouter.js +76 -0
- package/dist/providers/semaphore.d.ts +19 -0
- package/dist/providers/semaphore.js +51 -0
- package/dist/providers/types.d.ts +27 -0
- package/dist/providers/types.js +7 -0
- package/dist/schema.sql +116 -0
- package/dist/server-instructions.d.ts +9 -0
- package/dist/server-instructions.js +20 -0
- package/dist/telemetry.d.ts +39 -0
- package/dist/telemetry.js +130 -0
- package/dist/tools/classify.d.ts +44 -0
- package/dist/tools/classify.js +121 -0
- package/dist/tools/compile.d.ts +31 -0
- package/dist/tools/compile.js +132 -0
- package/dist/tools/dump-edges-sidecar.d.ts +37 -0
- package/dist/tools/dump-edges-sidecar.js +80 -0
- package/dist/tools/extract-entities.d.ts +53 -0
- package/dist/tools/extract-entities.js +169 -0
- package/dist/tools/lint.d.ts +10 -0
- package/dist/tools/lint.js +13 -0
- package/dist/tools/meditate.d.ts +25 -0
- package/dist/tools/meditate.js +128 -0
- package/dist/tools/recall.d.ts +66 -0
- package/dist/tools/recall.js +409 -0
- package/dist/tools/reject.d.ts +10 -0
- package/dist/tools/reject.js +24 -0
- package/dist/tools/remember.d.ts +26 -0
- package/dist/tools/remember.js +140 -0
- package/dist/tools/search.d.ts +30 -0
- package/dist/tools/search.js +69 -0
- package/dist/tools/spawn-cli.d.ts +14 -0
- package/dist/tools/spawn-cli.js +41 -0
- package/dist/tools/stats.d.ts +31 -0
- package/dist/tools/stats.js +88 -0
- package/package.json +86 -0
- package/skills/remember/SKILL.md +357 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type Operation = 'ADD' | 'UPDATE' | 'ARCHIVE' | 'REJECTION' | 'NOOP';
|
|
2
|
+
export type Tier = 'preferences' | 'project-context' | 'conversations';
|
|
3
|
+
export type MemoryType = 'preference' | 'decision' | 'context' | 'relationship' | 'observation' | 'rejection';
|
|
4
|
+
export interface MemoryMetadata {
|
|
5
|
+
type: MemoryType;
|
|
6
|
+
topics?: string[];
|
|
7
|
+
people?: string[];
|
|
8
|
+
section?: string;
|
|
9
|
+
date?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RememberInput {
|
|
12
|
+
operation: Operation;
|
|
13
|
+
tier: Tier;
|
|
14
|
+
content: string;
|
|
15
|
+
metadata: MemoryMetadata;
|
|
16
|
+
target_id?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface RememberResult {
|
|
19
|
+
operation: Operation;
|
|
20
|
+
id?: string;
|
|
21
|
+
dedup?: boolean;
|
|
22
|
+
message: string;
|
|
23
|
+
/** Present only when the operation completed degraded (claw-8cjf.2). */
|
|
24
|
+
warnings?: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function remember(input: RememberInput, projectRoot?: string): Promise<RememberResult>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { getPool } from '../db.js';
|
|
2
|
+
import { fingerprint } from '../fingerprint.js';
|
|
3
|
+
import { embedText, embeddingWarning } from '../embeddings.js';
|
|
4
|
+
import { triggerGraphRebuild } from '../graph-rebuild.js';
|
|
5
|
+
import pgvector from 'pgvector';
|
|
6
|
+
const { toSql } = pgvector;
|
|
7
|
+
export async function remember(input, projectRoot) {
|
|
8
|
+
const { operation, tier, content, metadata, target_id } = input;
|
|
9
|
+
if (operation === 'NOOP') {
|
|
10
|
+
return { operation: 'NOOP', message: 'No action taken.' };
|
|
11
|
+
}
|
|
12
|
+
const pool = getPool();
|
|
13
|
+
if (operation === 'ADD' || operation === 'REJECTION') {
|
|
14
|
+
const fp = fingerprint(content);
|
|
15
|
+
const type = operation === 'REJECTION' ? 'rejection' : metadata.type;
|
|
16
|
+
// Check for dedup via fingerprint
|
|
17
|
+
const existing = await pool.query('SELECT id FROM memories WHERE fingerprint = $1', [fp]);
|
|
18
|
+
if (existing.rows.length > 0) {
|
|
19
|
+
// Dedup: just update timestamp
|
|
20
|
+
await pool.query('UPDATE memories SET updated_at = NOW() WHERE id = $1', [
|
|
21
|
+
existing.rows[0].id,
|
|
22
|
+
]);
|
|
23
|
+
return {
|
|
24
|
+
operation,
|
|
25
|
+
id: existing.rows[0].id,
|
|
26
|
+
dedup: true,
|
|
27
|
+
message: `Duplicate detected. Updated timestamp for existing memory ${existing.rows[0].id}.`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Generate embedding (null-safe)
|
|
31
|
+
const embedding = await embedText(content);
|
|
32
|
+
const result = await pool.query(`INSERT INTO memories (content, tier, type, section, topics, people, date, fingerprint, embedding)
|
|
33
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
34
|
+
RETURNING id`, [
|
|
35
|
+
content,
|
|
36
|
+
tier,
|
|
37
|
+
type,
|
|
38
|
+
metadata.section || null,
|
|
39
|
+
metadata.topics || [],
|
|
40
|
+
metadata.people || [],
|
|
41
|
+
metadata.date || null,
|
|
42
|
+
fp,
|
|
43
|
+
embedding ? toSql(embedding) : null,
|
|
44
|
+
]);
|
|
45
|
+
if (projectRoot) {
|
|
46
|
+
triggerGraphRebuild(projectRoot);
|
|
47
|
+
}
|
|
48
|
+
const warning = embeddingWarning(embedding);
|
|
49
|
+
return {
|
|
50
|
+
operation,
|
|
51
|
+
id: result.rows[0].id,
|
|
52
|
+
dedup: false,
|
|
53
|
+
message: `Memory stored with id ${result.rows[0].id}.`,
|
|
54
|
+
...(warning ? { warnings: [warning] } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (operation === 'UPDATE') {
|
|
58
|
+
if (!target_id) {
|
|
59
|
+
return { operation: 'UPDATE', message: 'UPDATE requires target_id.' };
|
|
60
|
+
}
|
|
61
|
+
const fp = fingerprint(content);
|
|
62
|
+
const embedding = await embedText(content);
|
|
63
|
+
const result = await pool.query(`UPDATE memories SET
|
|
64
|
+
content = $1,
|
|
65
|
+
tier = $2,
|
|
66
|
+
type = $3,
|
|
67
|
+
section = $4,
|
|
68
|
+
topics = $5,
|
|
69
|
+
people = $6,
|
|
70
|
+
date = $7,
|
|
71
|
+
fingerprint = $8,
|
|
72
|
+
embedding = $9,
|
|
73
|
+
updated_at = NOW()
|
|
74
|
+
WHERE id = $10
|
|
75
|
+
RETURNING id`, [
|
|
76
|
+
content,
|
|
77
|
+
tier,
|
|
78
|
+
metadata.type,
|
|
79
|
+
metadata.section || null,
|
|
80
|
+
metadata.topics || [],
|
|
81
|
+
metadata.people || [],
|
|
82
|
+
metadata.date || null,
|
|
83
|
+
fp,
|
|
84
|
+
embedding ? toSql(embedding) : null,
|
|
85
|
+
target_id,
|
|
86
|
+
]);
|
|
87
|
+
if (result.rows.length === 0) {
|
|
88
|
+
return { operation: 'UPDATE', message: `No memory found with id ${target_id}.` };
|
|
89
|
+
}
|
|
90
|
+
if (projectRoot) {
|
|
91
|
+
triggerGraphRebuild(projectRoot);
|
|
92
|
+
}
|
|
93
|
+
const warning = embeddingWarning(embedding);
|
|
94
|
+
return {
|
|
95
|
+
operation: 'UPDATE',
|
|
96
|
+
id: result.rows[0].id,
|
|
97
|
+
message: `Memory ${target_id} updated.`,
|
|
98
|
+
...(warning ? { warnings: [warning] } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (operation === 'ARCHIVE') {
|
|
102
|
+
if (!target_id) {
|
|
103
|
+
return { operation: 'ARCHIVE', message: 'ARCHIVE requires target_id.' };
|
|
104
|
+
}
|
|
105
|
+
// Soft-archive: set type to 'archived' (preserves data for reversibility)
|
|
106
|
+
const archived = await pool.query("UPDATE memories SET type = 'archived', updated_at = NOW() WHERE id = $1 RETURNING id", [target_id]);
|
|
107
|
+
if (archived.rows.length === 0) {
|
|
108
|
+
return { operation: 'ARCHIVE', message: `No memory found with id ${target_id}.` };
|
|
109
|
+
}
|
|
110
|
+
// If replacement content is provided, insert it as a new memory
|
|
111
|
+
let replacementWarning = null;
|
|
112
|
+
if (content && content.trim().length > 0) {
|
|
113
|
+
const fp = fingerprint(content);
|
|
114
|
+
const embedding = await embedText(content);
|
|
115
|
+
replacementWarning = embeddingWarning(embedding);
|
|
116
|
+
await pool.query(`INSERT INTO memories (content, tier, type, section, topics, people, date, fingerprint, embedding)
|
|
117
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
|
|
118
|
+
content,
|
|
119
|
+
tier,
|
|
120
|
+
metadata.type,
|
|
121
|
+
metadata.section || null,
|
|
122
|
+
metadata.topics || [],
|
|
123
|
+
metadata.people || [],
|
|
124
|
+
metadata.date || null,
|
|
125
|
+
fp,
|
|
126
|
+
embedding ? toSql(embedding) : null,
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
if (projectRoot) {
|
|
130
|
+
triggerGraphRebuild(projectRoot);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
operation: 'ARCHIVE',
|
|
134
|
+
id: target_id,
|
|
135
|
+
message: `Memory ${target_id} archived.${content ? ' Replacement stored.' : ''}`,
|
|
136
|
+
...(replacementWarning ? { warnings: [replacementWarning] } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return { operation, message: `Unknown operation: ${operation}` };
|
|
140
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface SearchFilter {
|
|
2
|
+
type?: string;
|
|
3
|
+
tier?: string;
|
|
4
|
+
topics?: string[];
|
|
5
|
+
persons?: string[];
|
|
6
|
+
created_after?: string;
|
|
7
|
+
created_before?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchInput {
|
|
10
|
+
filter?: SearchFilter;
|
|
11
|
+
query?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SearchResultEntry {
|
|
15
|
+
id: string;
|
|
16
|
+
content: string;
|
|
17
|
+
tier: string;
|
|
18
|
+
type: string;
|
|
19
|
+
topics: string[];
|
|
20
|
+
people: string[];
|
|
21
|
+
section: string | null;
|
|
22
|
+
date: string | null;
|
|
23
|
+
created_at: string;
|
|
24
|
+
updated_at: string;
|
|
25
|
+
}
|
|
26
|
+
export interface SearchResult {
|
|
27
|
+
results: SearchResultEntry[];
|
|
28
|
+
count: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function search(input: SearchInput): Promise<SearchResult>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getPool } from '../db.js';
|
|
2
|
+
export async function search(input) {
|
|
3
|
+
const pool = getPool();
|
|
4
|
+
const { filter, query, limit = 20 } = input;
|
|
5
|
+
const conditions = ["type != 'rejection'"];
|
|
6
|
+
const params = [];
|
|
7
|
+
let paramIndex = 1;
|
|
8
|
+
if (filter) {
|
|
9
|
+
if (filter.type) {
|
|
10
|
+
conditions.push(`type = $${paramIndex}`);
|
|
11
|
+
params.push(filter.type);
|
|
12
|
+
paramIndex++;
|
|
13
|
+
}
|
|
14
|
+
if (filter.tier) {
|
|
15
|
+
conditions.push(`tier = $${paramIndex}`);
|
|
16
|
+
params.push(filter.tier);
|
|
17
|
+
paramIndex++;
|
|
18
|
+
}
|
|
19
|
+
if (filter.topics && filter.topics.length > 0) {
|
|
20
|
+
conditions.push(`topics && $${paramIndex}::text[]`);
|
|
21
|
+
params.push(filter.topics);
|
|
22
|
+
paramIndex++;
|
|
23
|
+
}
|
|
24
|
+
if (filter.persons && filter.persons.length > 0) {
|
|
25
|
+
conditions.push(`people && $${paramIndex}::text[]`);
|
|
26
|
+
params.push(filter.persons);
|
|
27
|
+
paramIndex++;
|
|
28
|
+
}
|
|
29
|
+
if (filter.created_after) {
|
|
30
|
+
conditions.push(`created_at >= $${paramIndex}::timestamptz`);
|
|
31
|
+
params.push(filter.created_after);
|
|
32
|
+
paramIndex++;
|
|
33
|
+
}
|
|
34
|
+
if (filter.created_before) {
|
|
35
|
+
conditions.push(`created_at <= $${paramIndex}::timestamptz`);
|
|
36
|
+
params.push(filter.created_before);
|
|
37
|
+
paramIndex++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (query) {
|
|
41
|
+
conditions.push(`tsv @@ plainto_tsquery('english', $${paramIndex})`);
|
|
42
|
+
params.push(query);
|
|
43
|
+
paramIndex++;
|
|
44
|
+
}
|
|
45
|
+
params.push(limit);
|
|
46
|
+
const sql = `
|
|
47
|
+
SELECT id, content, tier, type, topics, people, section, date, created_at, updated_at
|
|
48
|
+
FROM memories
|
|
49
|
+
WHERE ${conditions.join(' AND ')}
|
|
50
|
+
ORDER BY created_at DESC
|
|
51
|
+
LIMIT $${paramIndex}
|
|
52
|
+
`;
|
|
53
|
+
const result = await pool.query(sql, params);
|
|
54
|
+
return {
|
|
55
|
+
results: result.rows.map((row) => ({
|
|
56
|
+
id: row.id,
|
|
57
|
+
content: row.content,
|
|
58
|
+
tier: row.tier,
|
|
59
|
+
type: row.type,
|
|
60
|
+
topics: row.topics || [],
|
|
61
|
+
people: row.people || [],
|
|
62
|
+
section: row.section,
|
|
63
|
+
date: row.date ? row.date.toISOString().split('T')[0] : null,
|
|
64
|
+
created_at: row.created_at.toISOString(),
|
|
65
|
+
updated_at: row.updated_at.toISOString(),
|
|
66
|
+
})),
|
|
67
|
+
count: result.rows.length,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type CliScriptName = 'classify-edges' | 'compile-wiki' | 'dump-edges-json' | 'extract-entities';
|
|
2
|
+
export interface ResolvedCli {
|
|
3
|
+
bin: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Convenience wrapper that uses the caller's import.meta.url. Most callers
|
|
8
|
+
* use this; resolveCliCommandForUrl is the testable seam.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveCliCommand(scriptName: CliScriptName): ResolvedCli;
|
|
11
|
+
/**
|
|
12
|
+
* Pure function — testable in isolation by passing a fake import.meta.url.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveCliCommandForUrl(scriptName: CliScriptName, importMetaUrl: string): ResolvedCli;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPEC-045: shared subprocess command resolver for r2mcp's CLI tools.
|
|
3
|
+
*
|
|
4
|
+
* Returns {bin, args} for spawning a script under both:
|
|
5
|
+
* - dev mode: this file is .ts (via tsx) → spawn tsx <project>/src/cli/<name>.ts
|
|
6
|
+
* - prod mode: this file is .js (compiled to dist/) → spawn node <project>/dist/cli/<name>.js
|
|
7
|
+
*
|
|
8
|
+
* Detection uses the file extension at import.meta.url. This is automatic,
|
|
9
|
+
* zero-config, and authoritative for tsx + tsc workflows. If r2mcp ever
|
|
10
|
+
* adopts esbuild single-file bundling (.ts and .js both look like .js),
|
|
11
|
+
* promote a different mitigation here (NODE_ENV check, or dist/ substring).
|
|
12
|
+
*/
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { dirname, resolve } from 'node:path';
|
|
15
|
+
/**
|
|
16
|
+
* Convenience wrapper that uses the caller's import.meta.url. Most callers
|
|
17
|
+
* use this; resolveCliCommandForUrl is the testable seam.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveCliCommand(scriptName) {
|
|
20
|
+
return resolveCliCommandForUrl(scriptName, import.meta.url);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Pure function — testable in isolation by passing a fake import.meta.url.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveCliCommandForUrl(scriptName, importMetaUrl) {
|
|
26
|
+
const thisFile = fileURLToPath(importMetaUrl);
|
|
27
|
+
const isDev = thisFile.endsWith('.ts');
|
|
28
|
+
const dir = dirname(thisFile);
|
|
29
|
+
if (isDev) {
|
|
30
|
+
// dev: <root>/src/tools/spawn-cli.ts → <root>/src/cli/<name>.ts
|
|
31
|
+
return {
|
|
32
|
+
bin: 'tsx',
|
|
33
|
+
args: [resolve(dir, '..', 'cli', `${scriptName}.ts`)],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// prod: <root>/dist/tools/spawn-cli.js → <root>/dist/cli/<name>.js
|
|
37
|
+
return {
|
|
38
|
+
bin: 'node',
|
|
39
|
+
args: [resolve(dir, '..', 'cli', `${scriptName}.js`)],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface StatsResult {
|
|
2
|
+
total: number;
|
|
3
|
+
by_tier: {
|
|
4
|
+
preferences: number;
|
|
5
|
+
'project-context': number;
|
|
6
|
+
conversations: number;
|
|
7
|
+
};
|
|
8
|
+
by_type: {
|
|
9
|
+
preference: number;
|
|
10
|
+
decision: number;
|
|
11
|
+
context: number;
|
|
12
|
+
relationship: number;
|
|
13
|
+
observation: number;
|
|
14
|
+
rejection: number;
|
|
15
|
+
};
|
|
16
|
+
staleness: {
|
|
17
|
+
oldest_entry: string | null;
|
|
18
|
+
avg_age_days: number;
|
|
19
|
+
};
|
|
20
|
+
top_topics: Array<{
|
|
21
|
+
topic: string;
|
|
22
|
+
count: number;
|
|
23
|
+
}>;
|
|
24
|
+
index: {
|
|
25
|
+
entries_with_embeddings: number;
|
|
26
|
+
entries_without_embeddings: number;
|
|
27
|
+
model: 'openai/text-embedding-3-small';
|
|
28
|
+
last_write: string | null;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function stats(): Promise<StatsResult>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { getPool } from '../db.js';
|
|
2
|
+
export async function stats() {
|
|
3
|
+
const pool = getPool();
|
|
4
|
+
// Run all queries in parallel
|
|
5
|
+
const [totalResult, tierResult, typeResult, stalenessResult, topicsResult, embeddingResult, lastWriteResult,] = await Promise.all([
|
|
6
|
+
// Total count
|
|
7
|
+
pool.query('SELECT COUNT(*)::int AS total FROM memories'),
|
|
8
|
+
// By tier
|
|
9
|
+
pool.query(`
|
|
10
|
+
SELECT tier, COUNT(*)::int AS count
|
|
11
|
+
FROM memories
|
|
12
|
+
GROUP BY tier
|
|
13
|
+
`),
|
|
14
|
+
// By type
|
|
15
|
+
pool.query(`
|
|
16
|
+
SELECT type, COUNT(*)::int AS count
|
|
17
|
+
FROM memories
|
|
18
|
+
GROUP BY type
|
|
19
|
+
`),
|
|
20
|
+
// Staleness
|
|
21
|
+
pool.query(`
|
|
22
|
+
SELECT
|
|
23
|
+
MIN(created_at) AS oldest_entry,
|
|
24
|
+
COALESCE(EXTRACT(EPOCH FROM AVG(NOW() - created_at)) / 86400, 0) AS avg_age_days
|
|
25
|
+
FROM memories
|
|
26
|
+
`),
|
|
27
|
+
// Top topics (unnest topics array and count)
|
|
28
|
+
pool.query(`
|
|
29
|
+
SELECT topic, COUNT(*)::int AS count
|
|
30
|
+
FROM memories, unnest(topics) AS topic
|
|
31
|
+
GROUP BY topic
|
|
32
|
+
ORDER BY count DESC
|
|
33
|
+
LIMIT 10
|
|
34
|
+
`),
|
|
35
|
+
// Embedding counts
|
|
36
|
+
pool.query(`
|
|
37
|
+
SELECT
|
|
38
|
+
COUNT(*) FILTER (WHERE embedding IS NOT NULL)::int AS with_embeddings,
|
|
39
|
+
COUNT(*) FILTER (WHERE embedding IS NULL)::int AS without_embeddings
|
|
40
|
+
FROM memories
|
|
41
|
+
`),
|
|
42
|
+
// Last write
|
|
43
|
+
pool.query('SELECT MAX(updated_at) AS last_write FROM memories'),
|
|
44
|
+
]);
|
|
45
|
+
// Build tier map
|
|
46
|
+
const tierMap = {};
|
|
47
|
+
for (const row of tierResult.rows) {
|
|
48
|
+
tierMap[row.tier] = row.count;
|
|
49
|
+
}
|
|
50
|
+
// Build type map
|
|
51
|
+
const typeMap = {};
|
|
52
|
+
for (const row of typeResult.rows) {
|
|
53
|
+
typeMap[row.type] = row.count;
|
|
54
|
+
}
|
|
55
|
+
const stalenessRow = stalenessResult.rows[0];
|
|
56
|
+
const embeddingRow = embeddingResult.rows[0];
|
|
57
|
+
const lastWriteRow = lastWriteResult.rows[0];
|
|
58
|
+
return {
|
|
59
|
+
total: totalResult.rows[0].total,
|
|
60
|
+
by_tier: {
|
|
61
|
+
preferences: tierMap['preferences'] || 0,
|
|
62
|
+
'project-context': tierMap['project-context'] || 0,
|
|
63
|
+
conversations: tierMap['conversations'] || 0,
|
|
64
|
+
},
|
|
65
|
+
by_type: {
|
|
66
|
+
preference: typeMap['preference'] || 0,
|
|
67
|
+
decision: typeMap['decision'] || 0,
|
|
68
|
+
context: typeMap['context'] || 0,
|
|
69
|
+
relationship: typeMap['relationship'] || 0,
|
|
70
|
+
observation: typeMap['observation'] || 0,
|
|
71
|
+
rejection: typeMap['rejection'] || 0,
|
|
72
|
+
},
|
|
73
|
+
staleness: {
|
|
74
|
+
oldest_entry: stalenessRow.oldest_entry ? stalenessRow.oldest_entry.toISOString() : null,
|
|
75
|
+
avg_age_days: Math.round(parseFloat(stalenessRow.avg_age_days) * 100) / 100,
|
|
76
|
+
},
|
|
77
|
+
top_topics: topicsResult.rows.map((row) => ({
|
|
78
|
+
topic: row.topic,
|
|
79
|
+
count: row.count,
|
|
80
|
+
})),
|
|
81
|
+
index: {
|
|
82
|
+
entries_with_embeddings: embeddingRow.with_embeddings,
|
|
83
|
+
entries_without_embeddings: embeddingRow.without_embeddings,
|
|
84
|
+
model: 'openai/text-embedding-3-small',
|
|
85
|
+
last_write: lastWriteRow.last_write ? lastWriteRow.last_write.toISOString() : null,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "r2mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Persistent semantic memory layer for Claude Code — PostgreSQL + pgvector + OpenRouter embeddings",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/",
|
|
10
|
+
"skills/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"CHANGELOG.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && cp src/schema.sql dist/",
|
|
17
|
+
"dev": "tsx src/index.ts",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"setup": "tsx src/cli/setup.ts",
|
|
21
|
+
"edges:classify": "tsx src/cli/classify-edges.ts",
|
|
22
|
+
"entities:extract": "tsx src/cli/extract-entities.ts",
|
|
23
|
+
"compile-wiki": "tsx src/cli/compile-wiki.ts",
|
|
24
|
+
"lint:memory": "tsx src/cli/lint-memory.ts",
|
|
25
|
+
"migrate": "tsx src/cli/migrate.ts",
|
|
26
|
+
"gauntlet": "vitest run tests/gauntlet.test.ts",
|
|
27
|
+
"lint": "eslint .",
|
|
28
|
+
"format": "prettier --write .",
|
|
29
|
+
"format:check": "prettier --check .",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@anthropic-ai/sdk": "^0.92.0",
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
35
|
+
"@opentelemetry/api": "^1.9.0",
|
|
36
|
+
"@opentelemetry/exporter-metrics-otlp-proto": "^0.213.0",
|
|
37
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.213.0",
|
|
38
|
+
"@opentelemetry/instrumentation-http": "^0.213.0",
|
|
39
|
+
"@opentelemetry/instrumentation-pg": "^0.65.0",
|
|
40
|
+
"@opentelemetry/resources": "^2.6.0",
|
|
41
|
+
"@opentelemetry/sdk-metrics": "^2.6.0",
|
|
42
|
+
"@opentelemetry/sdk-node": "^0.213.0",
|
|
43
|
+
"@opentelemetry/semantic-conventions": "^1.40.0",
|
|
44
|
+
"pg": "^8.20.0",
|
|
45
|
+
"pgvector": "^0.2.1",
|
|
46
|
+
"zod": "^3.24.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^10.0.1",
|
|
50
|
+
"@types/pg": "^8.11.0",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
|
52
|
+
"@typescript-eslint/parser": "^8.59.3",
|
|
53
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
54
|
+
"eslint": "^10.4.0",
|
|
55
|
+
"eslint-config-prettier": "^10.1.8",
|
|
56
|
+
"prettier": "^3.8.3",
|
|
57
|
+
"tsx": "^4.19.0",
|
|
58
|
+
"typescript": "^5.7.0",
|
|
59
|
+
"vitest": "^3.0.0"
|
|
60
|
+
},
|
|
61
|
+
"types": "dist/index.d.ts",
|
|
62
|
+
"bin": {
|
|
63
|
+
"r2mcp": "dist/index.js",
|
|
64
|
+
"r2mcp-setup": "dist/cli/setup.js"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=20"
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/DMokong/r2mcp.git"
|
|
72
|
+
},
|
|
73
|
+
"homepage": "https://github.com/DMokong/r2mcp#readme",
|
|
74
|
+
"bugs": {
|
|
75
|
+
"url": "https://github.com/DMokong/r2mcp/issues"
|
|
76
|
+
},
|
|
77
|
+
"author": "Dustin Cheng",
|
|
78
|
+
"keywords": [
|
|
79
|
+
"mcp",
|
|
80
|
+
"claude-code",
|
|
81
|
+
"memory",
|
|
82
|
+
"pgvector",
|
|
83
|
+
"semantic-search",
|
|
84
|
+
"model-context-protocol"
|
|
85
|
+
]
|
|
86
|
+
}
|