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.
Files changed (138) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/LICENSE +21 -0
  3. package/README.md +532 -0
  4. package/dist/breadcrumbs.d.ts +123 -0
  5. package/dist/breadcrumbs.js +135 -0
  6. package/dist/cli/classify-edges.d.ts +2 -0
  7. package/dist/cli/classify-edges.js +130 -0
  8. package/dist/cli/compile-wiki.d.ts +2 -0
  9. package/dist/cli/compile-wiki.js +173 -0
  10. package/dist/cli/dump-edges-json.d.ts +2 -0
  11. package/dist/cli/dump-edges-json.js +21 -0
  12. package/dist/cli/extract-entities.d.ts +17 -0
  13. package/dist/cli/extract-entities.js +166 -0
  14. package/dist/cli/lint-memory.d.ts +16 -0
  15. package/dist/cli/lint-memory.js +94 -0
  16. package/dist/cli/migrate.d.ts +17 -0
  17. package/dist/cli/migrate.js +146 -0
  18. package/dist/cli/setup-helpers.d.ts +7 -0
  19. package/dist/cli/setup-helpers.js +72 -0
  20. package/dist/cli/setup.d.ts +15 -0
  21. package/dist/cli/setup.js +95 -0
  22. package/dist/compiler/clustering.d.ts +29 -0
  23. package/dist/compiler/clustering.js +66 -0
  24. package/dist/compiler/frontmatter.d.ts +35 -0
  25. package/dist/compiler/frontmatter.js +168 -0
  26. package/dist/compiler/manifest.d.ts +32 -0
  27. package/dist/compiler/manifest.js +82 -0
  28. package/dist/compiler/prompts.d.ts +17 -0
  29. package/dist/compiler/prompts.js +82 -0
  30. package/dist/compiler/run.d.ts +52 -0
  31. package/dist/compiler/run.js +186 -0
  32. package/dist/compiler/tier.d.ts +10 -0
  33. package/dist/compiler/tier.js +85 -0
  34. package/dist/compiler/topic.d.ts +16 -0
  35. package/dist/compiler/topic.js +105 -0
  36. package/dist/compiler/types.d.ts +101 -0
  37. package/dist/compiler/types.js +4 -0
  38. package/dist/db.d.ts +10 -0
  39. package/dist/db.js +46 -0
  40. package/dist/edges/candidate-pairs.d.ts +24 -0
  41. package/dist/edges/candidate-pairs.js +35 -0
  42. package/dist/edges/classifier.d.ts +45 -0
  43. package/dist/edges/classifier.js +172 -0
  44. package/dist/edges/signals.d.ts +13 -0
  45. package/dist/edges/signals.js +45 -0
  46. package/dist/edges/stage1-haiku.d.ts +21 -0
  47. package/dist/edges/stage1-haiku.js +33 -0
  48. package/dist/edges/stage2-opus.d.ts +41 -0
  49. package/dist/edges/stage2-opus.js +101 -0
  50. package/dist/edges/state.d.ts +44 -0
  51. package/dist/edges/state.js +79 -0
  52. package/dist/edges/types.d.ts +20 -0
  53. package/dist/edges/types.js +1 -0
  54. package/dist/embeddings.d.ts +13 -0
  55. package/dist/embeddings.js +54 -0
  56. package/dist/entities/db.d.ts +49 -0
  57. package/dist/entities/db.js +109 -0
  58. package/dist/entities/extractor.d.ts +14 -0
  59. package/dist/entities/extractor.js +154 -0
  60. package/dist/entities/normalize.d.ts +5 -0
  61. package/dist/entities/normalize.js +7 -0
  62. package/dist/entities/prompt.d.ts +19 -0
  63. package/dist/entities/prompt.js +100 -0
  64. package/dist/entities/state.d.ts +44 -0
  65. package/dist/entities/state.js +99 -0
  66. package/dist/entities/types.d.ts +62 -0
  67. package/dist/entities/types.js +6 -0
  68. package/dist/env.d.ts +13 -0
  69. package/dist/env.js +32 -0
  70. package/dist/fingerprint.d.ts +2 -0
  71. package/dist/fingerprint.js +12 -0
  72. package/dist/graph-rebuild.d.ts +6 -0
  73. package/dist/graph-rebuild.js +20 -0
  74. package/dist/index.d.ts +4 -0
  75. package/dist/index.js +403 -0
  76. package/dist/instrumentation.d.ts +10 -0
  77. package/dist/instrumentation.js +37 -0
  78. package/dist/lint/checks/contradictions.d.ts +30 -0
  79. package/dist/lint/checks/contradictions.js +52 -0
  80. package/dist/lint/checks/drift.d.ts +5 -0
  81. package/dist/lint/checks/drift.js +34 -0
  82. package/dist/lint/checks/orphans.d.ts +5 -0
  83. package/dist/lint/checks/orphans.js +25 -0
  84. package/dist/lint/checks/stale.d.ts +6 -0
  85. package/dist/lint/checks/stale.js +29 -0
  86. package/dist/lint/checks/superseded-unflagged.d.ts +5 -0
  87. package/dist/lint/checks/superseded-unflagged.js +47 -0
  88. package/dist/lint/run.d.ts +11 -0
  89. package/dist/lint/run.js +95 -0
  90. package/dist/lint/types.d.ts +60 -0
  91. package/dist/lint/types.js +13 -0
  92. package/dist/mcp-response.d.ts +7 -0
  93. package/dist/mcp-response.js +13 -0
  94. package/dist/providers/anthropic.d.ts +13 -0
  95. package/dist/providers/anthropic.js +56 -0
  96. package/dist/providers/claude-code.d.ts +35 -0
  97. package/dist/providers/claude-code.js +175 -0
  98. package/dist/providers/errors.d.ts +12 -0
  99. package/dist/providers/errors.js +19 -0
  100. package/dist/providers/index.d.ts +30 -0
  101. package/dist/providers/index.js +71 -0
  102. package/dist/providers/openrouter.d.ts +19 -0
  103. package/dist/providers/openrouter.js +76 -0
  104. package/dist/providers/semaphore.d.ts +19 -0
  105. package/dist/providers/semaphore.js +51 -0
  106. package/dist/providers/types.d.ts +27 -0
  107. package/dist/providers/types.js +7 -0
  108. package/dist/schema.sql +116 -0
  109. package/dist/server-instructions.d.ts +9 -0
  110. package/dist/server-instructions.js +20 -0
  111. package/dist/telemetry.d.ts +39 -0
  112. package/dist/telemetry.js +130 -0
  113. package/dist/tools/classify.d.ts +44 -0
  114. package/dist/tools/classify.js +121 -0
  115. package/dist/tools/compile.d.ts +31 -0
  116. package/dist/tools/compile.js +132 -0
  117. package/dist/tools/dump-edges-sidecar.d.ts +37 -0
  118. package/dist/tools/dump-edges-sidecar.js +80 -0
  119. package/dist/tools/extract-entities.d.ts +53 -0
  120. package/dist/tools/extract-entities.js +169 -0
  121. package/dist/tools/lint.d.ts +10 -0
  122. package/dist/tools/lint.js +13 -0
  123. package/dist/tools/meditate.d.ts +25 -0
  124. package/dist/tools/meditate.js +128 -0
  125. package/dist/tools/recall.d.ts +66 -0
  126. package/dist/tools/recall.js +409 -0
  127. package/dist/tools/reject.d.ts +10 -0
  128. package/dist/tools/reject.js +24 -0
  129. package/dist/tools/remember.d.ts +26 -0
  130. package/dist/tools/remember.js +140 -0
  131. package/dist/tools/search.d.ts +30 -0
  132. package/dist/tools/search.js +69 -0
  133. package/dist/tools/spawn-cli.d.ts +14 -0
  134. package/dist/tools/spawn-cli.js +41 -0
  135. package/dist/tools/stats.d.ts +31 -0
  136. package/dist/tools/stats.js +88 -0
  137. package/package.json +86 -0
  138. 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
+ }