@datacore-one/mcp 1.0.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/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2009 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
- package/packs/datacore-starter-v1/SKILL.md +18 -0
- package/packs/datacore-starter-v1/engrams.yaml +60 -0
- package/packs/dips-v1/SKILL.md +20 -0
- package/packs/dips-v1/engrams.yaml +25146 -0
- package/packs/fds-principles-v1/SKILL.md +19 -0
- package/packs/fds-principles-v1/engrams.yaml +120 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2009 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/server.ts
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import {
|
|
13
|
+
ListToolsRequestSchema,
|
|
14
|
+
CallToolRequestSchema
|
|
15
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
17
|
+
|
|
18
|
+
// src/storage.ts
|
|
19
|
+
import * as fs from "fs";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
import * as os from "os";
|
|
22
|
+
function detectStorage() {
|
|
23
|
+
const dcPath = process.env.DATACORE_PATH;
|
|
24
|
+
if (dcPath && fs.existsSync(path.join(dcPath, ".datacore"))) {
|
|
25
|
+
return fullConfig(dcPath);
|
|
26
|
+
}
|
|
27
|
+
const corePath = process.env.DATACORE_CORE_PATH;
|
|
28
|
+
if (corePath && fs.existsSync(corePath)) {
|
|
29
|
+
return coreConfig(corePath);
|
|
30
|
+
}
|
|
31
|
+
const defaultFull = path.join(os.homedir(), "Data");
|
|
32
|
+
if (fs.existsSync(path.join(defaultFull, ".datacore"))) {
|
|
33
|
+
return fullConfig(defaultFull);
|
|
34
|
+
}
|
|
35
|
+
return coreConfig(path.join(os.homedir(), "Datacore"));
|
|
36
|
+
}
|
|
37
|
+
function fullConfig(basePath) {
|
|
38
|
+
return {
|
|
39
|
+
mode: "full",
|
|
40
|
+
basePath,
|
|
41
|
+
engramsPath: path.join(basePath, ".datacore", "learning", "engrams.yaml"),
|
|
42
|
+
journalPath: path.join(basePath, "0-personal", "journal"),
|
|
43
|
+
knowledgePath: path.join(basePath, "0-personal", "3-knowledge"),
|
|
44
|
+
packsPath: path.join(basePath, ".datacore", "learning", "packs")
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function coreConfig(basePath) {
|
|
48
|
+
return {
|
|
49
|
+
mode: "core",
|
|
50
|
+
basePath,
|
|
51
|
+
engramsPath: path.join(basePath, "engrams.yaml"),
|
|
52
|
+
journalPath: path.join(basePath, "journal"),
|
|
53
|
+
knowledgePath: path.join(basePath, "knowledge"),
|
|
54
|
+
packsPath: path.join(basePath, "packs")
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function initCore(basePath) {
|
|
58
|
+
const isFirstRun2 = !fs.existsSync(path.join(basePath, "engrams.yaml"));
|
|
59
|
+
for (const dir of ["journal", "knowledge", "packs"]) {
|
|
60
|
+
const dirPath = path.join(basePath, dir);
|
|
61
|
+
if (!fs.existsSync(dirPath)) {
|
|
62
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const engramsPath = path.join(basePath, "engrams.yaml");
|
|
66
|
+
if (!fs.existsSync(engramsPath)) {
|
|
67
|
+
fs.writeFileSync(engramsPath, "engrams: []\n");
|
|
68
|
+
}
|
|
69
|
+
const configPath = path.join(basePath, "config.yaml");
|
|
70
|
+
if (!fs.existsSync(configPath)) {
|
|
71
|
+
fs.writeFileSync(configPath, "# Datacore MCP configuration\nversion: 1\n");
|
|
72
|
+
}
|
|
73
|
+
copyStarterPacks(basePath);
|
|
74
|
+
return { isFirstRun: isFirstRun2 };
|
|
75
|
+
}
|
|
76
|
+
function copyStarterPacks(basePath) {
|
|
77
|
+
const packsDir = path.join(basePath, "packs");
|
|
78
|
+
const bundledPacksDir = path.join(
|
|
79
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
80
|
+
"..",
|
|
81
|
+
"packs"
|
|
82
|
+
);
|
|
83
|
+
if (!fs.existsSync(bundledPacksDir)) return;
|
|
84
|
+
for (const entry of fs.readdirSync(bundledPacksDir)) {
|
|
85
|
+
const src = path.join(bundledPacksDir, entry);
|
|
86
|
+
const dest = path.join(packsDir, entry);
|
|
87
|
+
if (!fs.existsSync(dest) && fs.statSync(src).isDirectory()) {
|
|
88
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// package.json
|
|
94
|
+
var package_default = {
|
|
95
|
+
name: "@datacore-one/mcp",
|
|
96
|
+
version: "1.0.0",
|
|
97
|
+
description: "Datacore MCP server \u2014 The Software of You",
|
|
98
|
+
type: "module",
|
|
99
|
+
bin: {
|
|
100
|
+
"datacore-mcp": "./dist/index.js"
|
|
101
|
+
},
|
|
102
|
+
main: "./dist/index.js",
|
|
103
|
+
scripts: {
|
|
104
|
+
build: "tsup",
|
|
105
|
+
dev: "tsup --watch",
|
|
106
|
+
test: "vitest run",
|
|
107
|
+
"test:watch": "vitest",
|
|
108
|
+
start: "node dist/index.js"
|
|
109
|
+
},
|
|
110
|
+
dependencies: {
|
|
111
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
112
|
+
"js-yaml": "^4.1.0",
|
|
113
|
+
zod: "^3.23.0",
|
|
114
|
+
"zod-to-json-schema": "^3.25.1"
|
|
115
|
+
},
|
|
116
|
+
devDependencies: {
|
|
117
|
+
"@types/js-yaml": "^4.0.9",
|
|
118
|
+
"@types/node": "^22.0.0",
|
|
119
|
+
tsup: "^8.0.0",
|
|
120
|
+
typescript: "^5.5.0",
|
|
121
|
+
vitest: "^2.0.0"
|
|
122
|
+
},
|
|
123
|
+
files: [
|
|
124
|
+
"dist",
|
|
125
|
+
"packs"
|
|
126
|
+
],
|
|
127
|
+
keywords: [
|
|
128
|
+
"mcp",
|
|
129
|
+
"datacore",
|
|
130
|
+
"knowledge",
|
|
131
|
+
"learning",
|
|
132
|
+
"engrams"
|
|
133
|
+
],
|
|
134
|
+
license: "MIT",
|
|
135
|
+
author: "Fair Data Society",
|
|
136
|
+
repository: {
|
|
137
|
+
type: "git",
|
|
138
|
+
url: "https://github.com/datacore-one/mcp"
|
|
139
|
+
},
|
|
140
|
+
engines: {
|
|
141
|
+
node: ">=22"
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/version.ts
|
|
146
|
+
var currentVersion = package_default.version;
|
|
147
|
+
async function checkForUpdate() {
|
|
148
|
+
try {
|
|
149
|
+
const res = await fetch("https://registry.npmjs.org/@datacore-one/mcp/latest", {
|
|
150
|
+
signal: AbortSignal.timeout(3e3)
|
|
151
|
+
});
|
|
152
|
+
if (!res.ok) return null;
|
|
153
|
+
const data = await res.json();
|
|
154
|
+
if (data.version !== currentVersion) return data.version;
|
|
155
|
+
return null;
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/tools/index.ts
|
|
162
|
+
import { z } from "zod";
|
|
163
|
+
var TOOLS = [
|
|
164
|
+
{
|
|
165
|
+
name: "datacore.capture",
|
|
166
|
+
description: "Capture a journal entry or knowledge note",
|
|
167
|
+
inputSchema: z.object({
|
|
168
|
+
type: z.enum(["journal", "knowledge"]),
|
|
169
|
+
content: z.string().describe("Content to capture"),
|
|
170
|
+
title: z.string().optional().describe("Title for knowledge notes"),
|
|
171
|
+
tags: z.array(z.string()).optional().describe("Tags to attach")
|
|
172
|
+
})
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "datacore.learn",
|
|
176
|
+
description: "Create an engram from a statement \u2014 record a reusable learning",
|
|
177
|
+
inputSchema: z.object({
|
|
178
|
+
statement: z.string().describe("The knowledge assertion"),
|
|
179
|
+
type: z.enum(["behavioral", "terminological", "procedural", "architectural"]).optional(),
|
|
180
|
+
scope: z.string().optional().describe("Scope: global | agent:X | command:X"),
|
|
181
|
+
tags: z.array(z.string()).optional(),
|
|
182
|
+
domain: z.string().optional().describe("Dot-notation domain: software.architecture"),
|
|
183
|
+
visibility: z.enum(["private", "public", "template"]).optional()
|
|
184
|
+
})
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "datacore.inject",
|
|
188
|
+
description: "Get relevant engrams for a task \u2014 returns directives and considerations",
|
|
189
|
+
inputSchema: z.object({
|
|
190
|
+
prompt: z.string().describe("The task or question to match against"),
|
|
191
|
+
scope: z.string().optional().describe("Filter by scope: global | agent:X | module:X | command:X"),
|
|
192
|
+
max_tokens: z.number().optional().describe("Token budget (default: 8000)"),
|
|
193
|
+
min_relevance: z.number().optional().describe("Minimum score threshold (default: 0.3)")
|
|
194
|
+
})
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "datacore.search",
|
|
198
|
+
description: "Search journal entries and knowledge notes by keyword",
|
|
199
|
+
inputSchema: z.object({
|
|
200
|
+
query: z.string().describe("Search query"),
|
|
201
|
+
scope: z.enum(["journal", "knowledge", "all"]).optional(),
|
|
202
|
+
method: z.enum(["keyword", "semantic"]).optional().describe("Search method (default: keyword)"),
|
|
203
|
+
limit: z.number().optional().describe("Max results (default: 20)")
|
|
204
|
+
})
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "datacore.ingest",
|
|
208
|
+
description: "Ingest text content as a knowledge note, optionally extract engram suggestions",
|
|
209
|
+
inputSchema: z.object({
|
|
210
|
+
content: z.string().describe("Content to ingest"),
|
|
211
|
+
title: z.string().optional(),
|
|
212
|
+
tags: z.array(z.string()).optional()
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "datacore.status",
|
|
217
|
+
description: "Show Datacore status: engram/pack/note counts, scaling hints, update info",
|
|
218
|
+
inputSchema: z.object({})
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "datacore.packs.discover",
|
|
222
|
+
description: "Browse available engram packs from the registry",
|
|
223
|
+
inputSchema: z.object({
|
|
224
|
+
query: z.string().optional().describe("Filter by name/description"),
|
|
225
|
+
tags: z.array(z.string()).optional().describe("Filter by tags")
|
|
226
|
+
})
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "datacore.packs.install",
|
|
230
|
+
description: "Install or upgrade an engram pack",
|
|
231
|
+
inputSchema: z.object({
|
|
232
|
+
source: z.string().describe("Pack source: local path or pack ID from registry")
|
|
233
|
+
})
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: "datacore.forget",
|
|
237
|
+
description: "Retire an engram by ID or search term \u2014 marks it as retired so it is no longer injected",
|
|
238
|
+
inputSchema: z.object({
|
|
239
|
+
id: z.string().optional().describe("Exact engram ID to retire (e.g., ENG-2026-0219-001)"),
|
|
240
|
+
search: z.string().optional().describe("Search term to find engram by statement, tag, or ID fragment")
|
|
241
|
+
})
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "datacore.feedback",
|
|
245
|
+
description: "Signal whether an injected engram was helpful (positive), unhelpful (negative), or seen but not acted on (neutral)",
|
|
246
|
+
inputSchema: z.object({
|
|
247
|
+
engram_id: z.string().describe("The engram ID to provide feedback on"),
|
|
248
|
+
signal: z.enum(["positive", "negative", "neutral"]).describe("Feedback signal"),
|
|
249
|
+
comment: z.string().optional().describe("Optional comment about why")
|
|
250
|
+
})
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "datacore.packs.export",
|
|
254
|
+
description: "Export personal engrams as a shareable pack. Preview by default, set confirm=true to write.",
|
|
255
|
+
inputSchema: z.object({
|
|
256
|
+
name: z.string().describe("Pack name"),
|
|
257
|
+
description: z.string().describe("Pack description"),
|
|
258
|
+
engram_ids: z.array(z.string()).optional().describe("Specific engram IDs to export"),
|
|
259
|
+
filter_tags: z.array(z.string()).optional().describe("Filter by tags"),
|
|
260
|
+
filter_domain: z.string().optional().describe("Filter by domain prefix"),
|
|
261
|
+
confirm: z.boolean().optional().describe("Set true to write pack (default: preview only)")
|
|
262
|
+
})
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "datacore.modules.list",
|
|
266
|
+
description: "List installed modules with scope, version, and capability counts",
|
|
267
|
+
inputSchema: z.object({})
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "datacore.modules.info",
|
|
271
|
+
description: "Get detailed info about a specific module: manifest, tools, skills, agents, engrams",
|
|
272
|
+
inputSchema: z.object({
|
|
273
|
+
module: z.string().describe('Module name (e.g., "gtd", "slides", "crm")')
|
|
274
|
+
})
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "datacore.modules.health",
|
|
278
|
+
description: "Check module health: missing files, env vars, data separation issues",
|
|
279
|
+
inputSchema: z.object({
|
|
280
|
+
module: z.string().optional().describe("Module name (omit for all modules)")
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
// src/tools/capture.ts
|
|
286
|
+
import * as fs2 from "fs";
|
|
287
|
+
import * as path2 from "path";
|
|
288
|
+
|
|
289
|
+
// src/limits.ts
|
|
290
|
+
var MAX_CONTENT_SIZE = 1e6;
|
|
291
|
+
var MAX_TITLE_LENGTH = 200;
|
|
292
|
+
function validateContent(content) {
|
|
293
|
+
if (content.length > MAX_CONTENT_SIZE) {
|
|
294
|
+
return `Content too large: ${content.length} characters (max: ${MAX_CONTENT_SIZE})`;
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
function validateTitle(title) {
|
|
299
|
+
if (title.length > MAX_TITLE_LENGTH) {
|
|
300
|
+
return `Title too long: ${title.length} characters (max: ${MAX_TITLE_LENGTH})`;
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/tools/capture.ts
|
|
306
|
+
async function handleCapture(args2, storage2) {
|
|
307
|
+
const contentError = validateContent(args2.content);
|
|
308
|
+
if (contentError) return { success: false, error: contentError };
|
|
309
|
+
if (args2.title) {
|
|
310
|
+
const titleError = validateTitle(args2.title);
|
|
311
|
+
if (titleError) return { success: false, error: titleError };
|
|
312
|
+
}
|
|
313
|
+
if (args2.type === "journal") {
|
|
314
|
+
return captureJournal(args2.content, storage2.journalPath);
|
|
315
|
+
}
|
|
316
|
+
return captureKnowledge(args2.content, args2.title, args2.tags, storage2.knowledgePath);
|
|
317
|
+
}
|
|
318
|
+
function localDate(tz) {
|
|
319
|
+
const timezone = tz || process.env.DATACORE_TIMEZONE || void 0;
|
|
320
|
+
const now = /* @__PURE__ */ new Date();
|
|
321
|
+
const dateStr = now.toLocaleDateString("en-CA", { timeZone: timezone });
|
|
322
|
+
const timeStr = now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: timezone });
|
|
323
|
+
return { date: dateStr, time: timeStr };
|
|
324
|
+
}
|
|
325
|
+
function captureJournal(content, journalDir) {
|
|
326
|
+
const { date: today, time } = localDate();
|
|
327
|
+
const filePath = path2.join(journalDir, `${today}.md`);
|
|
328
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
329
|
+
if (fs2.existsSync(filePath)) {
|
|
330
|
+
const existing = fs2.readFileSync(filePath, "utf8");
|
|
331
|
+
fs2.writeFileSync(filePath, `${existing}
|
|
332
|
+
## ${time}
|
|
333
|
+
|
|
334
|
+
${content}
|
|
335
|
+
`);
|
|
336
|
+
} else {
|
|
337
|
+
fs2.writeFileSync(filePath, `# ${today}
|
|
338
|
+
|
|
339
|
+
## ${time}
|
|
340
|
+
|
|
341
|
+
${content}
|
|
342
|
+
`);
|
|
343
|
+
}
|
|
344
|
+
return { success: true, path: filePath };
|
|
345
|
+
}
|
|
346
|
+
function captureKnowledge(content, title, tags, knowledgeDir) {
|
|
347
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
348
|
+
const slug = (title ?? "note").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
349
|
+
const fileName = `${timestamp}-${slug}.md`;
|
|
350
|
+
const filePath = path2.join(knowledgeDir, fileName);
|
|
351
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
352
|
+
const frontmatter = `---
|
|
353
|
+
title: "${title ?? "Untitled"}"
|
|
354
|
+
created: "${(/* @__PURE__ */ new Date()).toISOString()}"
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
`;
|
|
358
|
+
const tagLine = tags?.length ? `
|
|
359
|
+
${tags.map((t) => `#${t}`).join(" ")}
|
|
360
|
+
` : "";
|
|
361
|
+
fs2.writeFileSync(filePath, `${frontmatter}${content}
|
|
362
|
+
${tagLine}`);
|
|
363
|
+
return { success: true, path: filePath };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/engrams.ts
|
|
367
|
+
import * as fs3 from "fs";
|
|
368
|
+
import * as yaml from "js-yaml";
|
|
369
|
+
|
|
370
|
+
// src/schemas/engram.ts
|
|
371
|
+
import { z as z2 } from "zod";
|
|
372
|
+
var ActivationSchema = z2.object({
|
|
373
|
+
retrieval_strength: z2.number().min(0).max(1),
|
|
374
|
+
storage_strength: z2.number().min(0).max(1),
|
|
375
|
+
frequency: z2.number().int().min(0),
|
|
376
|
+
last_accessed: z2.string()
|
|
377
|
+
});
|
|
378
|
+
var KnowledgeTypeSchema = z2.object({
|
|
379
|
+
memory_class: z2.enum(["semantic", "episodic", "procedural", "metacognitive"]),
|
|
380
|
+
cognitive_level: z2.enum(["remember", "understand", "apply", "analyze", "evaluate", "create"])
|
|
381
|
+
});
|
|
382
|
+
var RelationsSchema = z2.object({
|
|
383
|
+
broader: z2.array(z2.string()).default([]),
|
|
384
|
+
narrower: z2.array(z2.string()).default([]),
|
|
385
|
+
related: z2.array(z2.string()).default([]),
|
|
386
|
+
conflicts: z2.array(z2.string()).default([])
|
|
387
|
+
});
|
|
388
|
+
var ProvenanceSchema = z2.object({
|
|
389
|
+
origin: z2.string(),
|
|
390
|
+
chain: z2.array(z2.string()).default([]),
|
|
391
|
+
signature: z2.string().nullable().default(null),
|
|
392
|
+
license: z2.string().default("cc-by-sa-4.0")
|
|
393
|
+
});
|
|
394
|
+
var FeedbackSignalsSchema = z2.object({
|
|
395
|
+
positive: z2.number().int().default(0),
|
|
396
|
+
negative: z2.number().int().default(0),
|
|
397
|
+
neutral: z2.number().int().default(0)
|
|
398
|
+
});
|
|
399
|
+
var EngramSchema = z2.object({
|
|
400
|
+
id: z2.string().regex(/^ENG-[A-Za-z0-9-]+$/),
|
|
401
|
+
version: z2.number().int().min(1),
|
|
402
|
+
status: z2.enum(["active", "dormant", "retired", "candidate"]),
|
|
403
|
+
consolidated: z2.boolean().default(false),
|
|
404
|
+
type: z2.enum(["behavioral", "terminological", "procedural", "architectural"]),
|
|
405
|
+
scope: z2.string(),
|
|
406
|
+
visibility: z2.enum(["private", "public", "template"]).default("private"),
|
|
407
|
+
statement: z2.string().min(1),
|
|
408
|
+
rationale: z2.string().optional(),
|
|
409
|
+
contraindications: z2.array(z2.string()).optional(),
|
|
410
|
+
source_patterns: z2.array(z2.string()).optional(),
|
|
411
|
+
derivation_count: z2.number().int().min(0).default(1),
|
|
412
|
+
knowledge_type: KnowledgeTypeSchema.optional(),
|
|
413
|
+
domain: z2.string().optional(),
|
|
414
|
+
relations: RelationsSchema.optional(),
|
|
415
|
+
activation: ActivationSchema,
|
|
416
|
+
provenance: ProvenanceSchema.optional(),
|
|
417
|
+
feedback_signals: FeedbackSignalsSchema.optional(),
|
|
418
|
+
tags: z2.array(z2.string()).default([]),
|
|
419
|
+
pack: z2.string().nullable().default(null),
|
|
420
|
+
abstract: z2.string().nullable().default(null),
|
|
421
|
+
derived_from: z2.string().nullable().default(null)
|
|
422
|
+
});
|
|
423
|
+
var DatacoreExtensionSchema = z2.object({
|
|
424
|
+
id: z2.string(),
|
|
425
|
+
injection_policy: z2.enum(["on_match", "on_request"]),
|
|
426
|
+
match_terms: z2.array(z2.string()).default([]),
|
|
427
|
+
domain: z2.string().optional(),
|
|
428
|
+
engram_count: z2.number().int().min(0)
|
|
429
|
+
});
|
|
430
|
+
var PackManifestSchema = z2.object({
|
|
431
|
+
name: z2.string(),
|
|
432
|
+
description: z2.string(),
|
|
433
|
+
version: z2.string(),
|
|
434
|
+
creator: z2.string().optional(),
|
|
435
|
+
license: z2.string().optional(),
|
|
436
|
+
tags: z2.array(z2.string()).default([]),
|
|
437
|
+
"x-datacore": DatacoreExtensionSchema
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// src/logger.ts
|
|
441
|
+
var LEVEL_ORDER = {
|
|
442
|
+
debug: 0,
|
|
443
|
+
info: 1,
|
|
444
|
+
warning: 2,
|
|
445
|
+
error: 3
|
|
446
|
+
};
|
|
447
|
+
var MAX_MESSAGE_LENGTH = 4096;
|
|
448
|
+
var Logger = class {
|
|
449
|
+
server = null;
|
|
450
|
+
minLevel;
|
|
451
|
+
constructor() {
|
|
452
|
+
const envLevel = process.env.DATACORE_LOG_LEVEL?.toLowerCase();
|
|
453
|
+
this.minLevel = envLevel && envLevel in LEVEL_ORDER ? envLevel : "warning";
|
|
454
|
+
}
|
|
455
|
+
setServer(server) {
|
|
456
|
+
this.server = server;
|
|
457
|
+
}
|
|
458
|
+
shouldLog(level) {
|
|
459
|
+
return LEVEL_ORDER[level] >= LEVEL_ORDER[this.minLevel];
|
|
460
|
+
}
|
|
461
|
+
truncate(msg) {
|
|
462
|
+
if (msg.length <= MAX_MESSAGE_LENGTH) return msg;
|
|
463
|
+
return msg.slice(0, MAX_MESSAGE_LENGTH - 3) + "...";
|
|
464
|
+
}
|
|
465
|
+
emit(level, message) {
|
|
466
|
+
if (!this.shouldLog(level)) return;
|
|
467
|
+
const truncated = this.truncate(message);
|
|
468
|
+
process.stderr.write(`[datacore:${level}] ${truncated}
|
|
469
|
+
`);
|
|
470
|
+
if (this.server && LEVEL_ORDER[level] >= LEVEL_ORDER["warning"]) {
|
|
471
|
+
try {
|
|
472
|
+
this.server.sendLoggingMessage({ level, data: truncated });
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
debug(message) {
|
|
478
|
+
this.emit("debug", message);
|
|
479
|
+
}
|
|
480
|
+
info(message) {
|
|
481
|
+
this.emit("info", message);
|
|
482
|
+
}
|
|
483
|
+
warning(message) {
|
|
484
|
+
this.emit("warning", message);
|
|
485
|
+
}
|
|
486
|
+
error(message) {
|
|
487
|
+
this.emit("error", message);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
var logger = new Logger();
|
|
491
|
+
|
|
492
|
+
// src/engrams.ts
|
|
493
|
+
function loadEngrams(filePath) {
|
|
494
|
+
if (!fs3.existsSync(filePath)) return [];
|
|
495
|
+
try {
|
|
496
|
+
const raw = yaml.load(fs3.readFileSync(filePath, "utf8"));
|
|
497
|
+
if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
|
|
498
|
+
const valid = [];
|
|
499
|
+
let skipped = 0;
|
|
500
|
+
for (const entry of raw.engrams) {
|
|
501
|
+
const result = EngramSchema.safeParse(entry);
|
|
502
|
+
if (result.success) {
|
|
503
|
+
valid.push(result.data);
|
|
504
|
+
} else {
|
|
505
|
+
skipped++;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (skipped > 0) {
|
|
509
|
+
logger.warning(`Skipped ${skipped} invalid engram(s) in ${filePath}`);
|
|
510
|
+
}
|
|
511
|
+
return valid;
|
|
512
|
+
} catch (err) {
|
|
513
|
+
logger.error(`Failed to parse engrams file ${filePath}: ${err}`);
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function saveEngrams(filePath, engrams) {
|
|
518
|
+
const content = yaml.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
519
|
+
fs3.writeFileSync(filePath, content);
|
|
520
|
+
}
|
|
521
|
+
function parseSkillMdFrontmatter(filePath) {
|
|
522
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
523
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
524
|
+
if (!match) throw new Error(`No frontmatter found in ${filePath}`);
|
|
525
|
+
try {
|
|
526
|
+
return yaml.load(match[1]);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
throw new Error(`Failed to parse YAML frontmatter in ${filePath}: ${err}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function loadPack(packDir) {
|
|
532
|
+
const skillMdPath = `${packDir}/SKILL.md`;
|
|
533
|
+
const engramsPath = `${packDir}/engrams.yaml`;
|
|
534
|
+
const rawManifest = parseSkillMdFrontmatter(skillMdPath);
|
|
535
|
+
const manifest = PackManifestSchema.parse(rawManifest);
|
|
536
|
+
const engrams = loadEngrams(engramsPath);
|
|
537
|
+
return { manifest, engrams };
|
|
538
|
+
}
|
|
539
|
+
function loadAllPacks(packsDir) {
|
|
540
|
+
if (!fs3.existsSync(packsDir)) return [];
|
|
541
|
+
const packs = [];
|
|
542
|
+
for (const entry of fs3.readdirSync(packsDir)) {
|
|
543
|
+
const packDir = `${packsDir}/${entry}`;
|
|
544
|
+
if (!fs3.statSync(packDir).isDirectory()) continue;
|
|
545
|
+
if (!fs3.existsSync(`${packDir}/SKILL.md`)) continue;
|
|
546
|
+
try {
|
|
547
|
+
packs.push(loadPack(packDir));
|
|
548
|
+
} catch (err) {
|
|
549
|
+
logger.warning(`Failed to load pack ${entry}: ${err}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return packs;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/tools/learn.ts
|
|
556
|
+
function generateEngramId(existingEngrams) {
|
|
557
|
+
const now = /* @__PURE__ */ new Date();
|
|
558
|
+
const date = now.toISOString().split("T")[0].replace(/-/g, "").slice(0, 8);
|
|
559
|
+
const prefix = `ENG-${date.slice(0, 4)}-${date.slice(4)}-`;
|
|
560
|
+
let maxSeq = 0;
|
|
561
|
+
for (const e of existingEngrams) {
|
|
562
|
+
if (e.id.startsWith(prefix)) {
|
|
563
|
+
const seq = parseInt(e.id.slice(prefix.length), 10);
|
|
564
|
+
if (seq > maxSeq) maxSeq = seq;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const nextSeq = maxSeq + 1;
|
|
568
|
+
const padWidth = nextSeq > 999 ? String(nextSeq).length : 3;
|
|
569
|
+
return `${prefix}${String(nextSeq).padStart(padWidth, "0")}`;
|
|
570
|
+
}
|
|
571
|
+
async function handleLearn(args2, engramsPath) {
|
|
572
|
+
const engrams = loadEngrams(engramsPath);
|
|
573
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
574
|
+
const engram = {
|
|
575
|
+
id: generateEngramId(engrams),
|
|
576
|
+
version: 2,
|
|
577
|
+
status: "candidate",
|
|
578
|
+
consolidated: false,
|
|
579
|
+
type: args2.type ?? "behavioral",
|
|
580
|
+
scope: args2.scope ?? "global",
|
|
581
|
+
visibility: args2.visibility ?? "private",
|
|
582
|
+
statement: args2.statement,
|
|
583
|
+
rationale: args2.rationale,
|
|
584
|
+
derivation_count: 1,
|
|
585
|
+
domain: args2.domain,
|
|
586
|
+
tags: args2.tags ?? [],
|
|
587
|
+
activation: {
|
|
588
|
+
retrieval_strength: 0.5,
|
|
589
|
+
storage_strength: 0.3,
|
|
590
|
+
frequency: 0,
|
|
591
|
+
last_accessed: today
|
|
592
|
+
},
|
|
593
|
+
pack: null,
|
|
594
|
+
abstract: null,
|
|
595
|
+
derived_from: null
|
|
596
|
+
};
|
|
597
|
+
engrams.push(engram);
|
|
598
|
+
saveEngrams(engramsPath, engrams);
|
|
599
|
+
return { success: true, engram };
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/tools/inject-tool.ts
|
|
603
|
+
import * as fs4 from "fs";
|
|
604
|
+
import * as yaml2 from "js-yaml";
|
|
605
|
+
|
|
606
|
+
// src/decay.ts
|
|
607
|
+
var DECAY_RATE = 0.05;
|
|
608
|
+
var FLOOR = 0.05;
|
|
609
|
+
var MS_PER_DAY = 864e5;
|
|
610
|
+
function decayedStrength(retrievalStrength, lastAccessed, now) {
|
|
611
|
+
const last = new Date(lastAccessed);
|
|
612
|
+
const current = now ?? /* @__PURE__ */ new Date();
|
|
613
|
+
const days = Math.max(0, (current.getTime() - last.getTime()) / MS_PER_DAY);
|
|
614
|
+
return Math.max(retrievalStrength * Math.exp(-DECAY_RATE * days), FLOOR);
|
|
615
|
+
}
|
|
616
|
+
function engramState(retrievalStrength) {
|
|
617
|
+
if (retrievalStrength >= 0.5) return "active";
|
|
618
|
+
if (retrievalStrength >= 0.3) return "fading";
|
|
619
|
+
if (retrievalStrength >= 0.1) return "dormant";
|
|
620
|
+
return "retirement_candidate";
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/inject.ts
|
|
624
|
+
var DEFAULT_MAX_TOKENS = 8e3;
|
|
625
|
+
var DEFAULT_MIN_RELEVANCE = 0.3;
|
|
626
|
+
var TOKENS_PER_ENGRAM = 40;
|
|
627
|
+
var MAX_PER_PACK = 5;
|
|
628
|
+
var MAX_PER_DOMAIN = 10;
|
|
629
|
+
function selectEngrams(ctx, personalEngrams, packs) {
|
|
630
|
+
const promptLower = ctx.prompt.toLowerCase();
|
|
631
|
+
const promptWords = new Set(promptLower.split(/\W+/).filter((w) => w.length > 2));
|
|
632
|
+
const scored = [];
|
|
633
|
+
for (const engram of personalEngrams) {
|
|
634
|
+
if (engram.status !== "active") continue;
|
|
635
|
+
const score = scoreEngram(engram, promptLower, promptWords, [], ctx.scope, false);
|
|
636
|
+
if (score > 0) scored.push({ engram, score });
|
|
637
|
+
}
|
|
638
|
+
for (const pack of packs) {
|
|
639
|
+
if (pack.manifest["x-datacore"].injection_policy === "on_request") continue;
|
|
640
|
+
const matchTerms = pack.manifest["x-datacore"].match_terms;
|
|
641
|
+
for (const engram of pack.engrams) {
|
|
642
|
+
if (engram.status !== "active") continue;
|
|
643
|
+
const score = scoreEngram(engram, promptLower, promptWords, matchTerms, ctx.scope, true);
|
|
644
|
+
if (score > 0) scored.push({ engram, score });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const maxTokens = ctx.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
648
|
+
const minRelevance = ctx.minRelevance ?? DEFAULT_MIN_RELEVANCE;
|
|
649
|
+
const passing = scored.filter((s) => s.score >= minRelevance);
|
|
650
|
+
passing.sort((a, b) => b.score - a.score);
|
|
651
|
+
const selected = fillTokenBudget(passing, maxTokens);
|
|
652
|
+
const splitPoint = Math.ceil(selected.length * 2 / 3);
|
|
653
|
+
return {
|
|
654
|
+
directives: selected.slice(0, splitPoint),
|
|
655
|
+
consider: selected.slice(splitPoint),
|
|
656
|
+
tokens_used: selected.length * TOKENS_PER_ENGRAM
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function scoreEngram(engram, promptLower, promptWords, packMatchTerms, scopeFilter, isPack) {
|
|
660
|
+
if (scopeFilter) {
|
|
661
|
+
if (scopeFilter === "global") {
|
|
662
|
+
if (engram.scope !== "global") return 0;
|
|
663
|
+
} else if (!engram.scope.startsWith(scopeFilter) && engram.scope !== "global") {
|
|
664
|
+
return 0;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
let termHits = 0;
|
|
668
|
+
for (const term of packMatchTerms) {
|
|
669
|
+
if (promptLower.includes(term.toLowerCase())) termHits++;
|
|
670
|
+
}
|
|
671
|
+
for (const tag of engram.tags) {
|
|
672
|
+
if (promptWords.has(tag.toLowerCase())) termHits++;
|
|
673
|
+
}
|
|
674
|
+
if (engram.domain) {
|
|
675
|
+
for (const part of engram.domain.split(/[./]/)) {
|
|
676
|
+
if (promptWords.has(part.toLowerCase())) termHits++;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const statementWords = new Set(engram.statement.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
|
|
680
|
+
for (const word of promptWords) {
|
|
681
|
+
if (statementWords.has(word)) termHits += 0.5;
|
|
682
|
+
}
|
|
683
|
+
if (termHits === 0) return 0;
|
|
684
|
+
const rs = isPack ? engram.activation.retrieval_strength : decayedStrength(engram.activation.retrieval_strength, engram.activation.last_accessed);
|
|
685
|
+
let score = termHits * rs;
|
|
686
|
+
const feedback = engram.feedback_signals;
|
|
687
|
+
if (feedback) {
|
|
688
|
+
const netFeedback = feedback.positive - feedback.negative;
|
|
689
|
+
if (netFeedback > 0) score *= 1 + Math.min(netFeedback * 0.05, 0.3);
|
|
690
|
+
else if (netFeedback < 0) score *= Math.max(1 + netFeedback * 0.1, 0.5);
|
|
691
|
+
}
|
|
692
|
+
if (engram.consolidated) score *= 1.1;
|
|
693
|
+
return score;
|
|
694
|
+
}
|
|
695
|
+
function fillTokenBudget(scored, maxTokens) {
|
|
696
|
+
const result = [];
|
|
697
|
+
const packCounts = /* @__PURE__ */ new Map();
|
|
698
|
+
const domainCounts = /* @__PURE__ */ new Map();
|
|
699
|
+
let tokensUsed = 0;
|
|
700
|
+
for (const { engram } of scored) {
|
|
701
|
+
if (tokensUsed + TOKENS_PER_ENGRAM > maxTokens) break;
|
|
702
|
+
const pack = engram.pack ?? "__personal__";
|
|
703
|
+
const packCount = packCounts.get(pack) ?? 0;
|
|
704
|
+
if (packCount >= MAX_PER_PACK && pack !== "__personal__") continue;
|
|
705
|
+
const domain = engram.domain ?? "__none__";
|
|
706
|
+
const topDomain = domain.split(".")[0];
|
|
707
|
+
const domainCount = domainCounts.get(topDomain) ?? 0;
|
|
708
|
+
if (domainCount >= MAX_PER_DOMAIN) continue;
|
|
709
|
+
result.push(engram);
|
|
710
|
+
tokensUsed += TOKENS_PER_ENGRAM;
|
|
711
|
+
packCounts.set(pack, packCount + 1);
|
|
712
|
+
domainCounts.set(topDomain, domainCount + 1);
|
|
713
|
+
}
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/tools/inject-tool.ts
|
|
718
|
+
async function handleInject(args2, paths) {
|
|
719
|
+
const personalEngrams = loadEngrams(paths.engramsPath);
|
|
720
|
+
const packs = loadAllPacks(paths.packsPath);
|
|
721
|
+
const ctx = {
|
|
722
|
+
prompt: args2.prompt,
|
|
723
|
+
scope: args2.scope,
|
|
724
|
+
maxTokens: args2.max_tokens,
|
|
725
|
+
minRelevance: args2.min_relevance
|
|
726
|
+
};
|
|
727
|
+
const result = selectEngrams(ctx, personalEngrams, packs);
|
|
728
|
+
const totalCount = result.directives.length + result.consider.length;
|
|
729
|
+
if (totalCount === 0) {
|
|
730
|
+
return { text: "", count: 0, tokens_used: 0 };
|
|
731
|
+
}
|
|
732
|
+
const lines = [];
|
|
733
|
+
if (result.directives.length > 0) {
|
|
734
|
+
lines.push("## DIRECTIVES\n");
|
|
735
|
+
for (const e of result.directives) {
|
|
736
|
+
lines.push(formatEngram(e, totalCount));
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (result.consider.length > 0) {
|
|
740
|
+
lines.push("\n## ALSO CONSIDER\n");
|
|
741
|
+
for (const e of result.consider) {
|
|
742
|
+
lines.push(formatEngram(e, totalCount));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
updateUsageTracking(
|
|
746
|
+
paths.engramsPath,
|
|
747
|
+
personalEngrams,
|
|
748
|
+
[...result.directives, ...result.consider]
|
|
749
|
+
);
|
|
750
|
+
return { text: lines.join("\n"), count: totalCount, tokens_used: result.tokens_used };
|
|
751
|
+
}
|
|
752
|
+
function updateUsageTracking(engramsPath, allPersonal, selected) {
|
|
753
|
+
const selectedPersonalIds = new Set(
|
|
754
|
+
selected.filter((e) => !e.pack).map((e) => e.id)
|
|
755
|
+
);
|
|
756
|
+
if (selectedPersonalIds.size === 0) return;
|
|
757
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
758
|
+
let changed = false;
|
|
759
|
+
for (const engram of allPersonal) {
|
|
760
|
+
if (selectedPersonalIds.has(engram.id)) {
|
|
761
|
+
engram.activation.last_accessed = today;
|
|
762
|
+
engram.activation.frequency += 1;
|
|
763
|
+
changed = true;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (changed) {
|
|
767
|
+
atomicWriteYaml(engramsPath, { engrams: allPersonal });
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function atomicWriteYaml(filePath, data) {
|
|
771
|
+
const content = yaml2.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
772
|
+
const tmpPath = filePath + ".tmp." + process.pid;
|
|
773
|
+
fs4.writeFileSync(tmpPath, content);
|
|
774
|
+
fs4.renameSync(tmpPath, filePath);
|
|
775
|
+
}
|
|
776
|
+
function formatEngram(engram, totalCount) {
|
|
777
|
+
if (totalCount < 10) {
|
|
778
|
+
let text = `- **${engram.statement}**`;
|
|
779
|
+
if (engram.rationale) text += `
|
|
780
|
+
_${engram.rationale}_`;
|
|
781
|
+
if (engram.contraindications?.length) {
|
|
782
|
+
text += `
|
|
783
|
+
Except: ${engram.contraindications.join(", ")}`;
|
|
784
|
+
}
|
|
785
|
+
return text;
|
|
786
|
+
}
|
|
787
|
+
if (totalCount < 30) {
|
|
788
|
+
const source = engram.pack ? ` [${engram.pack}]` : "";
|
|
789
|
+
return `- ${engram.statement}${source}`;
|
|
790
|
+
}
|
|
791
|
+
return `- ${engram.statement}`;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/tools/search.ts
|
|
795
|
+
import * as fs5 from "fs";
|
|
796
|
+
import * as path3 from "path";
|
|
797
|
+
var CONTENT_CACHE_MAX = 500;
|
|
798
|
+
var contentCache = /* @__PURE__ */ new Map();
|
|
799
|
+
function getCachedContent(filePath) {
|
|
800
|
+
const entry = contentCache.get(filePath);
|
|
801
|
+
if (!entry) return null;
|
|
802
|
+
try {
|
|
803
|
+
const stat = fs5.statSync(filePath);
|
|
804
|
+
if (stat.mtimeMs === entry.mtime) return entry.content;
|
|
805
|
+
} catch {
|
|
806
|
+
}
|
|
807
|
+
contentCache.delete(filePath);
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
function setCachedContent(filePath, content) {
|
|
811
|
+
try {
|
|
812
|
+
const mtime = fs5.statSync(filePath).mtimeMs;
|
|
813
|
+
if (contentCache.size >= CONTENT_CACHE_MAX) {
|
|
814
|
+
const firstKey = contentCache.keys().next().value;
|
|
815
|
+
if (firstKey) contentCache.delete(firstKey);
|
|
816
|
+
}
|
|
817
|
+
contentCache.set(filePath, { mtime, content });
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async function handleSearch(args2, paths, bridge) {
|
|
822
|
+
if (args2.method === "semantic" && bridge) {
|
|
823
|
+
const availability = bridge.isAvailable();
|
|
824
|
+
if (availability.available) {
|
|
825
|
+
const result = await bridge.search(args2.query, args2.limit ?? 20);
|
|
826
|
+
if (!result.fallback) {
|
|
827
|
+
return { results: result.results, method: "semantic" };
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const keywordResults = await keywordSearch(args2, paths);
|
|
831
|
+
return { ...keywordResults, method: "keyword", fallback_warning: "Semantic search unavailable, using keyword fallback" };
|
|
832
|
+
}
|
|
833
|
+
return keywordSearch(args2, paths);
|
|
834
|
+
}
|
|
835
|
+
async function keywordSearch(args2, paths) {
|
|
836
|
+
const scope = args2.scope ?? "all";
|
|
837
|
+
const limit = args2.limit ?? 20;
|
|
838
|
+
const results = [];
|
|
839
|
+
if (scope === "journal" || scope === "all") {
|
|
840
|
+
results.push(...searchDir(paths.journalPath, args2.query));
|
|
841
|
+
}
|
|
842
|
+
if (scope === "knowledge" || scope === "all") {
|
|
843
|
+
results.push(...searchDir(paths.knowledgePath, args2.query));
|
|
844
|
+
}
|
|
845
|
+
results.sort((a, b) => b.score - a.score);
|
|
846
|
+
return { results: results.slice(0, limit), method: "keyword" };
|
|
847
|
+
}
|
|
848
|
+
function searchDir(dirPath, query) {
|
|
849
|
+
if (!fs5.existsSync(dirPath)) return [];
|
|
850
|
+
const results = [];
|
|
851
|
+
const queryLower = query.toLowerCase();
|
|
852
|
+
for (const file of walkDir(dirPath)) {
|
|
853
|
+
if (!file.endsWith(".md")) continue;
|
|
854
|
+
const content = getCachedContent(file) ?? (() => {
|
|
855
|
+
const c = fs5.readFileSync(file, "utf8");
|
|
856
|
+
setCachedContent(file, c);
|
|
857
|
+
return c;
|
|
858
|
+
})();
|
|
859
|
+
const contentLower = content.toLowerCase();
|
|
860
|
+
const occurrences = countOccurrences(contentLower, queryLower);
|
|
861
|
+
if (occurrences === 0) continue;
|
|
862
|
+
const snippet = extractSnippet(content, query);
|
|
863
|
+
results.push({ path: file, snippet, score: occurrences });
|
|
864
|
+
}
|
|
865
|
+
return results;
|
|
866
|
+
}
|
|
867
|
+
function walkDir(dir) {
|
|
868
|
+
const files = [];
|
|
869
|
+
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
870
|
+
const fullPath = path3.join(dir, entry.name);
|
|
871
|
+
if (entry.isDirectory()) files.push(...walkDir(fullPath));
|
|
872
|
+
else files.push(fullPath);
|
|
873
|
+
}
|
|
874
|
+
return files;
|
|
875
|
+
}
|
|
876
|
+
function countOccurrences(text, query) {
|
|
877
|
+
let count = 0;
|
|
878
|
+
let pos = 0;
|
|
879
|
+
while ((pos = text.indexOf(query, pos)) !== -1) {
|
|
880
|
+
count++;
|
|
881
|
+
pos += query.length;
|
|
882
|
+
}
|
|
883
|
+
return count;
|
|
884
|
+
}
|
|
885
|
+
function extractSnippet(content, query) {
|
|
886
|
+
const idx = content.toLowerCase().indexOf(query.toLowerCase());
|
|
887
|
+
if (idx === -1) return content.slice(0, 100);
|
|
888
|
+
const start2 = Math.max(0, idx - 50);
|
|
889
|
+
const end = Math.min(content.length, idx + query.length + 50);
|
|
890
|
+
return (start2 > 0 ? "..." : "") + content.slice(start2, end) + (end < content.length ? "..." : "");
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/tools/ingest.ts
|
|
894
|
+
import * as fs6 from "fs";
|
|
895
|
+
import * as path4 from "path";
|
|
896
|
+
async function handleIngest(args2, paths) {
|
|
897
|
+
const contentError = validateContent(args2.content);
|
|
898
|
+
if (contentError) return { success: false, error: contentError };
|
|
899
|
+
if (args2.title) {
|
|
900
|
+
const titleError = validateTitle(args2.title);
|
|
901
|
+
if (titleError) return { success: false, error: titleError };
|
|
902
|
+
}
|
|
903
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
904
|
+
const slug = (args2.title ?? "ingested").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
905
|
+
const fileName = `${timestamp}-${slug}.md`;
|
|
906
|
+
const filePath = path4.join(paths.knowledgePath, fileName);
|
|
907
|
+
fs6.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
908
|
+
const frontmatter = `---
|
|
909
|
+
title: "${args2.title ?? "Ingested Note"}"
|
|
910
|
+
created: "${(/* @__PURE__ */ new Date()).toISOString()}"
|
|
911
|
+
type: ingested
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
`;
|
|
915
|
+
const tagLine = args2.tags?.length ? `
|
|
916
|
+
${args2.tags.map((t) => `#${t}`).join(" ")}
|
|
917
|
+
` : "";
|
|
918
|
+
fs6.writeFileSync(filePath, `${frontmatter}${args2.content}
|
|
919
|
+
${tagLine}`);
|
|
920
|
+
const suggestions = extractEngramSuggestions(args2.content);
|
|
921
|
+
return {
|
|
922
|
+
success: true,
|
|
923
|
+
note_path: filePath,
|
|
924
|
+
engram_suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function extractEngramSuggestions(content) {
|
|
928
|
+
const patterns = [
|
|
929
|
+
/(?:^|[.!?]\s+)(always\s+\w[\w\s]*?)(?:\.|$)/gim,
|
|
930
|
+
/(?:^|[.!?]\s+)(never\s+\w[\w\s]*?)(?:\.|$)/gim,
|
|
931
|
+
/(?:^|[.!?]\s+)(prefer\s+\w[\w\s]*?)(?:\.|$)/gim,
|
|
932
|
+
/(?:^|[.!?]\s+)(avoid\s+\w[\w\s]*?)(?:\.|$)/gim,
|
|
933
|
+
/(?:^|[.!?]\s+)(ensure\s+\w[\w\s]*?)(?:\.|$)/gim
|
|
934
|
+
];
|
|
935
|
+
const suggestions = [];
|
|
936
|
+
for (const pattern of patterns) {
|
|
937
|
+
for (const match of content.matchAll(pattern)) {
|
|
938
|
+
const suggestion = match[1].trim();
|
|
939
|
+
if (suggestion.length >= 8 && suggestion.length <= 180) {
|
|
940
|
+
suggestions.push(suggestion);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return suggestions;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/tools/status.ts
|
|
948
|
+
import * as fs8 from "fs";
|
|
949
|
+
import * as path6 from "path";
|
|
950
|
+
|
|
951
|
+
// src/trust.ts
|
|
952
|
+
import * as fs7 from "fs";
|
|
953
|
+
import * as path5 from "path";
|
|
954
|
+
import * as crypto from "crypto";
|
|
955
|
+
function computePackChecksum(packDir) {
|
|
956
|
+
const files = ["SKILL.md", "engrams.yaml"];
|
|
957
|
+
const hash = crypto.createHash("sha256");
|
|
958
|
+
let hasContent = false;
|
|
959
|
+
for (const file of files) {
|
|
960
|
+
const filePath = path5.join(packDir, file);
|
|
961
|
+
if (fs7.existsSync(filePath)) {
|
|
962
|
+
hash.update(fs7.readFileSync(filePath));
|
|
963
|
+
hasContent = true;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return hasContent ? hash.digest("hex") : null;
|
|
967
|
+
}
|
|
968
|
+
function verifyPackChecksum(packDir, expected) {
|
|
969
|
+
const actual = computePackChecksum(packDir);
|
|
970
|
+
return { valid: actual === expected, actual };
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// registry/packs.json
|
|
974
|
+
var packs_default = {
|
|
975
|
+
version: 1,
|
|
976
|
+
packs: [
|
|
977
|
+
{
|
|
978
|
+
id: "datacore-starter-v1",
|
|
979
|
+
name: "Datacore Methodology",
|
|
980
|
+
description: "Core knowledge management principles \u2014 capture, journal, learn, compound",
|
|
981
|
+
version: "1.0.0",
|
|
982
|
+
author: "Datacore",
|
|
983
|
+
tags: ["methodology", "gtd", "knowledge-management"],
|
|
984
|
+
download_url: "",
|
|
985
|
+
engram_count: 5,
|
|
986
|
+
free: true,
|
|
987
|
+
checksum: "4fb30b260c6ba5fd36e7cf9240f0a651ca484c777ab4a7be0b3438627138f1a3"
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
id: "fds-principles-v1",
|
|
991
|
+
name: "FDS Principles",
|
|
992
|
+
description: "Fair Data Society's 10 principles for ethical data handling",
|
|
993
|
+
version: "1.0.0",
|
|
994
|
+
author: "Fair Data Society",
|
|
995
|
+
tags: ["ethics", "data-sovereignty", "privacy", "consent"],
|
|
996
|
+
download_url: "",
|
|
997
|
+
engram_count: 10,
|
|
998
|
+
free: true,
|
|
999
|
+
checksum: "8cc7bbb7e259486b16370511fefe937532ee7dcba60ce87c2cd6b8647b5b9847"
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
id: "dips-v1",
|
|
1003
|
+
name: "Datacore DIPs",
|
|
1004
|
+
description: "Comprehensive knowledge from all 20 Datacore Improvement Proposals",
|
|
1005
|
+
version: "1.0.0",
|
|
1006
|
+
author: "Datacore",
|
|
1007
|
+
tags: ["architecture", "patterns", "gtd", "modules", "knowledge-management"],
|
|
1008
|
+
download_url: "",
|
|
1009
|
+
engram_count: 747,
|
|
1010
|
+
free: true,
|
|
1011
|
+
checksum: "08185777c38e3a91fbb0f92e7ef388c5940e97e69be6dcce8ce119bd2b33968c"
|
|
1012
|
+
}
|
|
1013
|
+
]
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
// src/tools/status.ts
|
|
1017
|
+
async function handleStatus(paths, updateAvailable2) {
|
|
1018
|
+
const engrams = loadEngrams(paths.engramsPath);
|
|
1019
|
+
const journalCount = countFiles(paths.journalPath, ".md");
|
|
1020
|
+
const knowledgeCount = countFiles(paths.knowledgePath, ".md");
|
|
1021
|
+
const packsCount = countDirs(paths.packsPath);
|
|
1022
|
+
const healthCounts = { active: 0, fading: 0, dormant: 0, retirement_candidate: 0 };
|
|
1023
|
+
for (const e of engrams) {
|
|
1024
|
+
if (e.status !== "active") continue;
|
|
1025
|
+
const rs = decayedStrength(e.activation.retrieval_strength, e.activation.last_accessed);
|
|
1026
|
+
const state = engramState(rs);
|
|
1027
|
+
healthCounts[state]++;
|
|
1028
|
+
}
|
|
1029
|
+
const packIntegrity = [];
|
|
1030
|
+
for (const regPack of packs_default.packs) {
|
|
1031
|
+
if (!regPack.checksum) continue;
|
|
1032
|
+
const packDir = path6.join(paths.packsPath, regPack.id);
|
|
1033
|
+
if (!fs8.existsSync(packDir)) continue;
|
|
1034
|
+
const result2 = verifyPackChecksum(packDir, regPack.checksum);
|
|
1035
|
+
packIntegrity.push({ name: regPack.id, valid: result2.valid });
|
|
1036
|
+
}
|
|
1037
|
+
const result = {
|
|
1038
|
+
version: currentVersion,
|
|
1039
|
+
mode: paths.mode,
|
|
1040
|
+
engrams: engrams.length,
|
|
1041
|
+
engram_health: healthCounts,
|
|
1042
|
+
packs: packsCount,
|
|
1043
|
+
pack_integrity: packIntegrity.length > 0 ? packIntegrity : void 0,
|
|
1044
|
+
journal_entries: journalCount,
|
|
1045
|
+
knowledge_notes: knowledgeCount
|
|
1046
|
+
};
|
|
1047
|
+
if (engrams.length >= 500) {
|
|
1048
|
+
result.scaling_hint = `You have ${engrams.length} engrams. Consider migrating to full Datacore for SQLite-backed search.`;
|
|
1049
|
+
}
|
|
1050
|
+
if (updateAvailable2) {
|
|
1051
|
+
result.update_available = updateAvailable2;
|
|
1052
|
+
}
|
|
1053
|
+
return result;
|
|
1054
|
+
}
|
|
1055
|
+
function countFiles(dir, ext) {
|
|
1056
|
+
if (!fs8.existsSync(dir)) return 0;
|
|
1057
|
+
let count = 0;
|
|
1058
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
1059
|
+
const fullPath = path6.join(dir, entry.name);
|
|
1060
|
+
if (entry.isDirectory()) count += countFiles(fullPath, ext);
|
|
1061
|
+
else if (entry.name.endsWith(ext)) count++;
|
|
1062
|
+
}
|
|
1063
|
+
return count;
|
|
1064
|
+
}
|
|
1065
|
+
function countDirs(dir) {
|
|
1066
|
+
if (!fs8.existsSync(dir)) return 0;
|
|
1067
|
+
return fs8.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/tools/discover.ts
|
|
1071
|
+
import * as fs9 from "fs";
|
|
1072
|
+
import * as path7 from "path";
|
|
1073
|
+
function handleDiscover(args2, packsDir) {
|
|
1074
|
+
let packs = packs_default.packs.map((p) => {
|
|
1075
|
+
const localDir = path7.join(packsDir, p.id);
|
|
1076
|
+
const installed = fs9.existsSync(path7.join(localDir, "SKILL.md"));
|
|
1077
|
+
let installedVersion;
|
|
1078
|
+
if (installed) {
|
|
1079
|
+
try {
|
|
1080
|
+
const content = fs9.readFileSync(path7.join(localDir, "SKILL.md"), "utf8");
|
|
1081
|
+
const match = content.match(/version:\s*["']?([^"'\n]+)/);
|
|
1082
|
+
installedVersion = match?.[1];
|
|
1083
|
+
} catch {
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
...p,
|
|
1088
|
+
installed,
|
|
1089
|
+
installed_version: installedVersion,
|
|
1090
|
+
upgradeable: installed && installedVersion !== p.version,
|
|
1091
|
+
can_install: !!p.download_url
|
|
1092
|
+
};
|
|
1093
|
+
});
|
|
1094
|
+
if (args2.query) {
|
|
1095
|
+
const q = args2.query.toLowerCase();
|
|
1096
|
+
packs = packs.filter(
|
|
1097
|
+
(p) => p.name.toLowerCase().includes(q) || p.description.toLowerCase().includes(q) || p.tags.some((t) => t.toLowerCase().includes(q))
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
if (args2.tags?.length) {
|
|
1101
|
+
const filterTags = new Set(args2.tags.map((t) => t.toLowerCase()));
|
|
1102
|
+
packs = packs.filter((p) => p.tags.some((t) => filterTags.has(t.toLowerCase())));
|
|
1103
|
+
}
|
|
1104
|
+
return { packs };
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// src/tools/install.ts
|
|
1108
|
+
import * as fs10 from "fs";
|
|
1109
|
+
import * as path8 from "path";
|
|
1110
|
+
import * as yaml3 from "js-yaml";
|
|
1111
|
+
import * as os2 from "os";
|
|
1112
|
+
import { execSync } from "child_process";
|
|
1113
|
+
async function handleInstall(args2, packsDir) {
|
|
1114
|
+
let srcDir = args2.source;
|
|
1115
|
+
if (srcDir.startsWith("http://") || srcDir.startsWith("https://")) {
|
|
1116
|
+
const downloaded = await downloadPack(srcDir);
|
|
1117
|
+
if (downloaded.error) return { success: false, error: downloaded.error };
|
|
1118
|
+
srcDir = downloaded.path;
|
|
1119
|
+
}
|
|
1120
|
+
const skillPath = path8.join(srcDir, "SKILL.md");
|
|
1121
|
+
if (!fs10.existsSync(skillPath)) {
|
|
1122
|
+
return { success: false, error: "No SKILL.md found in source directory" };
|
|
1123
|
+
}
|
|
1124
|
+
const skillContent = fs10.readFileSync(skillPath, "utf8");
|
|
1125
|
+
const frontmatterMatch = skillContent.match(/^---\n([\s\S]*?)\n---/);
|
|
1126
|
+
if (!frontmatterMatch) {
|
|
1127
|
+
return { success: false, error: "No YAML frontmatter in SKILL.md" };
|
|
1128
|
+
}
|
|
1129
|
+
const manifest = yaml3.load(frontmatterMatch[1]);
|
|
1130
|
+
const packId = manifest?.["x-datacore"]?.id;
|
|
1131
|
+
const newVersion = manifest?.version;
|
|
1132
|
+
if (!packId) {
|
|
1133
|
+
return { success: false, error: "Missing x-datacore.id in SKILL.md frontmatter" };
|
|
1134
|
+
}
|
|
1135
|
+
const destDir = path8.join(packsDir, packId);
|
|
1136
|
+
if (fs10.existsSync(path8.join(destDir, "SKILL.md"))) {
|
|
1137
|
+
const existingContent = fs10.readFileSync(path8.join(destDir, "SKILL.md"), "utf8");
|
|
1138
|
+
const existingMatch = existingContent.match(/version:\s*["']?([^"'\n]+)/);
|
|
1139
|
+
const existingVersion = existingMatch?.[1];
|
|
1140
|
+
if (existingVersion === newVersion) {
|
|
1141
|
+
return { success: true, pack_id: packId, already_current: true };
|
|
1142
|
+
}
|
|
1143
|
+
fs10.rmSync(destDir, { recursive: true, force: true });
|
|
1144
|
+
fs10.cpSync(srcDir, destDir, { recursive: true });
|
|
1145
|
+
return { success: true, pack_id: packId, upgraded: true };
|
|
1146
|
+
}
|
|
1147
|
+
fs10.cpSync(srcDir, destDir, { recursive: true });
|
|
1148
|
+
const checksumVerified = verifyInstalledChecksum(packId, destDir);
|
|
1149
|
+
return { success: true, pack_id: packId, checksum_verified: checksumVerified ?? void 0 };
|
|
1150
|
+
}
|
|
1151
|
+
function verifyInstalledChecksum(packId, destDir) {
|
|
1152
|
+
const registryPack = packs_default.packs.find((p) => p.id === packId);
|
|
1153
|
+
if (!registryPack?.checksum) return null;
|
|
1154
|
+
const result = verifyPackChecksum(destDir, registryPack.checksum);
|
|
1155
|
+
return result.valid;
|
|
1156
|
+
}
|
|
1157
|
+
async function downloadPack(url) {
|
|
1158
|
+
const tmpDir = fs10.mkdtempSync(path8.join(os2.tmpdir(), "datacore-pack-"));
|
|
1159
|
+
try {
|
|
1160
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
1161
|
+
if (!res.ok) return { error: `Download failed: HTTP ${res.status}` };
|
|
1162
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
1163
|
+
const archivePath = path8.join(tmpDir, "pack.tar.gz");
|
|
1164
|
+
fs10.writeFileSync(archivePath, buffer);
|
|
1165
|
+
const extractDir = path8.join(tmpDir, "extracted");
|
|
1166
|
+
fs10.mkdirSync(extractDir);
|
|
1167
|
+
execSync(`tar xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(extractDir)}`, { timeout: 1e4 });
|
|
1168
|
+
const packRoot = findPackRoot(extractDir);
|
|
1169
|
+
if (!packRoot) return { error: "Downloaded archive does not contain SKILL.md" };
|
|
1170
|
+
return { path: packRoot };
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
return { error: `Download failed: ${err instanceof Error ? err.message : err}` };
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
function findPackRoot(dir) {
|
|
1176
|
+
if (fs10.existsSync(path8.join(dir, "SKILL.md"))) return dir;
|
|
1177
|
+
for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
|
|
1178
|
+
if (entry.isDirectory()) {
|
|
1179
|
+
const found = findPackRoot(path8.join(dir, entry.name));
|
|
1180
|
+
if (found) return found;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// src/tools/export.ts
|
|
1187
|
+
import * as fs11 from "fs";
|
|
1188
|
+
import * as path9 from "path";
|
|
1189
|
+
import * as yaml4 from "js-yaml";
|
|
1190
|
+
async function handleExport(args2, paths) {
|
|
1191
|
+
const allEngrams = loadEngrams(paths.engramsPath);
|
|
1192
|
+
let selected = allEngrams.filter((e) => e.status === "active");
|
|
1193
|
+
selected = selected.filter((e) => e.visibility === "public" || e.visibility === "template");
|
|
1194
|
+
if (selected.length === 0 && !args2.engram_ids?.length) {
|
|
1195
|
+
return { success: false, error: "No exportable engrams found (only public/template engrams can be exported)" };
|
|
1196
|
+
}
|
|
1197
|
+
if (args2.engram_ids?.length) {
|
|
1198
|
+
const idSet = new Set(args2.engram_ids);
|
|
1199
|
+
selected = allEngrams.filter(
|
|
1200
|
+
(e) => idSet.has(e.id) && e.status === "active" && (e.visibility === "public" || e.visibility === "template")
|
|
1201
|
+
);
|
|
1202
|
+
const privateSkipped = args2.engram_ids.filter((id) => {
|
|
1203
|
+
const e = allEngrams.find((eng) => eng.id === id);
|
|
1204
|
+
return e && e.visibility === "private";
|
|
1205
|
+
});
|
|
1206
|
+
if (privateSkipped.length > 0) {
|
|
1207
|
+
return { success: false, error: `Cannot export private engrams: ${privateSkipped.join(", ")}. Set visibility to public or template first.` };
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (args2.filter_tags?.length) {
|
|
1211
|
+
const tagSet = new Set(args2.filter_tags.map((t) => t.toLowerCase()));
|
|
1212
|
+
selected = selected.filter((e) => e.tags.some((t) => tagSet.has(t.toLowerCase())));
|
|
1213
|
+
}
|
|
1214
|
+
if (args2.filter_domain) {
|
|
1215
|
+
selected = selected.filter((e) => e.domain?.startsWith(args2.filter_domain));
|
|
1216
|
+
}
|
|
1217
|
+
if (selected.length === 0) {
|
|
1218
|
+
return { success: false, error: "No engrams match the filter criteria" };
|
|
1219
|
+
}
|
|
1220
|
+
const packId = args2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
1221
|
+
const packDir = path9.join(paths.packsPath, packId);
|
|
1222
|
+
if (!args2.confirm) {
|
|
1223
|
+
return {
|
|
1224
|
+
success: true,
|
|
1225
|
+
preview: {
|
|
1226
|
+
count: selected.length,
|
|
1227
|
+
statements: selected.map((e) => e.statement).slice(0, 10),
|
|
1228
|
+
pack_path: packDir
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
fs11.mkdirSync(packDir, { recursive: true });
|
|
1233
|
+
const skillContent = `---
|
|
1234
|
+
name: "${args2.name}"
|
|
1235
|
+
description: "${args2.description}"
|
|
1236
|
+
version: "1.0.0"
|
|
1237
|
+
schema_version: 2
|
|
1238
|
+
x-datacore:
|
|
1239
|
+
id: "${packId}"
|
|
1240
|
+
injection_policy: on_match
|
|
1241
|
+
match_terms: []
|
|
1242
|
+
engram_count: ${selected.length}
|
|
1243
|
+
---
|
|
1244
|
+
|
|
1245
|
+
# ${args2.name}
|
|
1246
|
+
|
|
1247
|
+
${args2.description}
|
|
1248
|
+
|
|
1249
|
+
Exported ${selected.length} engrams.
|
|
1250
|
+
`;
|
|
1251
|
+
fs11.writeFileSync(path9.join(packDir, "SKILL.md"), skillContent);
|
|
1252
|
+
const exportEngrams = selected.map((e) => ({
|
|
1253
|
+
id: e.id,
|
|
1254
|
+
version: e.version,
|
|
1255
|
+
type: e.type,
|
|
1256
|
+
scope: e.scope,
|
|
1257
|
+
visibility: e.visibility,
|
|
1258
|
+
statement: e.statement,
|
|
1259
|
+
rationale: e.rationale,
|
|
1260
|
+
tags: e.tags,
|
|
1261
|
+
domain: e.domain,
|
|
1262
|
+
status: "active",
|
|
1263
|
+
activation: {
|
|
1264
|
+
retrieval_strength: 0.7,
|
|
1265
|
+
storage_strength: 1,
|
|
1266
|
+
frequency: 0,
|
|
1267
|
+
last_accessed: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1268
|
+
},
|
|
1269
|
+
feedback_signals: { positive: 0, negative: 0 }
|
|
1270
|
+
}));
|
|
1271
|
+
fs11.writeFileSync(
|
|
1272
|
+
path9.join(packDir, "engrams.yaml"),
|
|
1273
|
+
yaml4.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
|
|
1274
|
+
);
|
|
1275
|
+
return { success: true, pack_path: packDir };
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/modules.ts
|
|
1279
|
+
import * as fs12 from "fs";
|
|
1280
|
+
import * as path10 from "path";
|
|
1281
|
+
import * as yaml5 from "js-yaml";
|
|
1282
|
+
function discoverModules(storage2) {
|
|
1283
|
+
const modules = [];
|
|
1284
|
+
if (storage2.mode !== "full") return modules;
|
|
1285
|
+
const globalModulesDir = path10.join(storage2.basePath, ".datacore", "modules");
|
|
1286
|
+
modules.push(...scanModulesDir(globalModulesDir, "global"));
|
|
1287
|
+
try {
|
|
1288
|
+
const entries = fs12.readdirSync(storage2.basePath);
|
|
1289
|
+
for (const entry of entries) {
|
|
1290
|
+
if (/^\d+-/.test(entry)) {
|
|
1291
|
+
const spaceModulesDir = path10.join(storage2.basePath, entry, ".datacore", "modules");
|
|
1292
|
+
modules.push(...scanModulesDir(spaceModulesDir, "space", entry));
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
} catch {
|
|
1296
|
+
}
|
|
1297
|
+
return modules;
|
|
1298
|
+
}
|
|
1299
|
+
function scanModulesDir(modulesDir, scope, spaceName) {
|
|
1300
|
+
const modules = [];
|
|
1301
|
+
if (!fs12.existsSync(modulesDir)) return modules;
|
|
1302
|
+
try {
|
|
1303
|
+
const entries = fs12.readdirSync(modulesDir);
|
|
1304
|
+
for (const entry of entries) {
|
|
1305
|
+
const modulePath = path10.join(modulesDir, entry);
|
|
1306
|
+
const manifestPath = path10.join(modulePath, "module.yaml");
|
|
1307
|
+
if (!fs12.existsSync(manifestPath)) continue;
|
|
1308
|
+
try {
|
|
1309
|
+
const raw = fs12.readFileSync(manifestPath, "utf-8");
|
|
1310
|
+
const manifest = yaml5.load(raw);
|
|
1311
|
+
if (!manifest || !manifest.name) continue;
|
|
1312
|
+
modules.push({
|
|
1313
|
+
name: manifest.name,
|
|
1314
|
+
manifest,
|
|
1315
|
+
modulePath,
|
|
1316
|
+
scope,
|
|
1317
|
+
spaceName
|
|
1318
|
+
});
|
|
1319
|
+
} catch {
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
} catch {
|
|
1323
|
+
}
|
|
1324
|
+
return modules;
|
|
1325
|
+
}
|
|
1326
|
+
async function loadModuleTools(modules, storage2) {
|
|
1327
|
+
const tools = [];
|
|
1328
|
+
for (const mod of modules) {
|
|
1329
|
+
const declaredTools = mod.manifest.provides?.tools;
|
|
1330
|
+
if (!declaredTools || declaredTools.length === 0) continue;
|
|
1331
|
+
const toolsIndexPath = path10.join(mod.modulePath, "tools", "index.js");
|
|
1332
|
+
if (!fs12.existsSync(toolsIndexPath)) continue;
|
|
1333
|
+
try {
|
|
1334
|
+
const toolsModule = await import(toolsIndexPath);
|
|
1335
|
+
const moduleTools2 = toolsModule.tools || toolsModule.default?.tools || [];
|
|
1336
|
+
const dataPath = mod.scope === "space" && mod.spaceName ? path10.join(storage2.basePath, mod.spaceName, ".datacore", "modules", mod.name, "data") : path10.join(storage2.basePath, "0-personal", ".datacore", "modules", mod.name, "data");
|
|
1337
|
+
const context = {
|
|
1338
|
+
storage: storage2,
|
|
1339
|
+
modulePath: mod.modulePath,
|
|
1340
|
+
dataPath,
|
|
1341
|
+
spaceName: mod.spaceName
|
|
1342
|
+
};
|
|
1343
|
+
for (const toolDef of moduleTools2) {
|
|
1344
|
+
const declared = declaredTools.find((d) => d.name === toolDef.name);
|
|
1345
|
+
if (!declared) continue;
|
|
1346
|
+
tools.push({
|
|
1347
|
+
fullName: `datacore.${mod.name}.${toolDef.name}`,
|
|
1348
|
+
moduleName: mod.name,
|
|
1349
|
+
definition: toolDef,
|
|
1350
|
+
context
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return tools;
|
|
1357
|
+
}
|
|
1358
|
+
function getModuleInfo(mod) {
|
|
1359
|
+
const m = mod.manifest;
|
|
1360
|
+
return {
|
|
1361
|
+
name: m.name,
|
|
1362
|
+
version: m.version || "0.0.0",
|
|
1363
|
+
description: m.description || "",
|
|
1364
|
+
scope: mod.scope,
|
|
1365
|
+
space: mod.spaceName,
|
|
1366
|
+
builtin: m.builtin || false,
|
|
1367
|
+
manifest_version: m.manifest_version || 1,
|
|
1368
|
+
provides: {
|
|
1369
|
+
tools: m.provides?.tools?.length || 0,
|
|
1370
|
+
skills: m.provides?.skills?.length || 0,
|
|
1371
|
+
agents: m.provides?.agents?.length || 0,
|
|
1372
|
+
commands: m.provides?.commands?.length || 0,
|
|
1373
|
+
workflows: m.provides?.workflows?.length || 0
|
|
1374
|
+
},
|
|
1375
|
+
context_priority: m.context?.priority || "minimal",
|
|
1376
|
+
engrams: m.engrams ? {
|
|
1377
|
+
namespace: m.engrams.namespace,
|
|
1378
|
+
injection_policy: m.engrams.injection_policy,
|
|
1379
|
+
has_starter_pack: !!m.engrams.starter_pack
|
|
1380
|
+
} : null,
|
|
1381
|
+
requires: m.requires?.env_vars ? {
|
|
1382
|
+
env_required: m.requires.env_vars.required || [],
|
|
1383
|
+
env_optional: m.requires.env_vars.optional || []
|
|
1384
|
+
} : null,
|
|
1385
|
+
path: mod.modulePath
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// src/tools/modules-list.ts
|
|
1390
|
+
async function handleModulesList(_args, storage2, cachedModules) {
|
|
1391
|
+
const modules = cachedModules ?? discoverModules(storage2);
|
|
1392
|
+
if (modules.length === 0) {
|
|
1393
|
+
return {
|
|
1394
|
+
count: 0,
|
|
1395
|
+
modules: [],
|
|
1396
|
+
message: storage2.mode === "core" ? "Module discovery requires a full Datacore installation" : "No modules found"
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
return {
|
|
1400
|
+
count: modules.length,
|
|
1401
|
+
modules: modules.map((m) => ({
|
|
1402
|
+
name: m.manifest.name,
|
|
1403
|
+
version: m.manifest.version || "0.0.0",
|
|
1404
|
+
description: m.manifest.description || "",
|
|
1405
|
+
scope: m.scope,
|
|
1406
|
+
space: m.spaceName || null,
|
|
1407
|
+
builtin: m.manifest.builtin || false,
|
|
1408
|
+
manifest_version: m.manifest.manifest_version || 1,
|
|
1409
|
+
provides: {
|
|
1410
|
+
tools: m.manifest.provides?.tools?.length || 0,
|
|
1411
|
+
skills: m.manifest.provides?.skills?.length || 0,
|
|
1412
|
+
agents: m.manifest.provides?.agents?.length || 0,
|
|
1413
|
+
commands: m.manifest.provides?.commands?.length || 0,
|
|
1414
|
+
workflows: m.manifest.provides?.workflows?.length || 0
|
|
1415
|
+
},
|
|
1416
|
+
context_priority: m.manifest.context?.priority || "minimal"
|
|
1417
|
+
}))
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/tools/modules-info.ts
|
|
1422
|
+
async function handleModulesInfo(args2, storage2, cachedModules) {
|
|
1423
|
+
const modules = cachedModules ?? discoverModules(storage2);
|
|
1424
|
+
const found = modules.find((m) => m.manifest.name === args2.module);
|
|
1425
|
+
if (!found) {
|
|
1426
|
+
return { error: `Module '${args2.module}' not found`, installed_modules: modules.map((m) => m.name) };
|
|
1427
|
+
}
|
|
1428
|
+
return getModuleInfo(found);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/tools/modules-health.ts
|
|
1432
|
+
import * as fs13 from "fs";
|
|
1433
|
+
import * as path11 from "path";
|
|
1434
|
+
async function handleModulesHealth(args2, storage2, cachedModules) {
|
|
1435
|
+
const modules = cachedModules ?? discoverModules(storage2);
|
|
1436
|
+
if (args2.module) {
|
|
1437
|
+
const found = modules.find((m) => m.manifest.name === args2.module);
|
|
1438
|
+
if (!found) {
|
|
1439
|
+
return { error: `Module '${args2.module}' not found` };
|
|
1440
|
+
}
|
|
1441
|
+
return await checkModule(found, storage2);
|
|
1442
|
+
}
|
|
1443
|
+
const checks = await Promise.all(modules.map((m) => checkModule(m, storage2)));
|
|
1444
|
+
const ok = checks.filter((c) => c.status === "ok").length;
|
|
1445
|
+
const warnings = checks.filter((c) => c.status === "warning").length;
|
|
1446
|
+
const errors = checks.filter((c) => c.status === "error").length;
|
|
1447
|
+
return {
|
|
1448
|
+
summary: { total: checks.length, ok, warnings, errors },
|
|
1449
|
+
modules: checks
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
async function checkModule(mod, storage2) {
|
|
1453
|
+
const issues = [];
|
|
1454
|
+
const manifest = mod.manifest;
|
|
1455
|
+
if (!fs13.existsSync(path11.join(mod.modulePath, "SKILL.md"))) {
|
|
1456
|
+
issues.push("Missing SKILL.md (ecosystem entry point)");
|
|
1457
|
+
}
|
|
1458
|
+
if (!fs13.existsSync(path11.join(mod.modulePath, "CLAUDE.base.md"))) {
|
|
1459
|
+
issues.push("Missing CLAUDE.base.md (AI context)");
|
|
1460
|
+
}
|
|
1461
|
+
if (!manifest.manifest_version || manifest.manifest_version < 2) {
|
|
1462
|
+
issues.push("module.yaml uses v1 format (missing manifest_version: 2)");
|
|
1463
|
+
}
|
|
1464
|
+
const requires = manifest.requires;
|
|
1465
|
+
const requiredEnv = requires?.env_vars?.required || [];
|
|
1466
|
+
for (const envVar of requiredEnv) {
|
|
1467
|
+
if (!process.env[envVar]) {
|
|
1468
|
+
issues.push(`Missing required env var: ${envVar}`);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
const provides = manifest.provides;
|
|
1472
|
+
const declaredTools = provides?.tools || [];
|
|
1473
|
+
if (declaredTools.length > 0) {
|
|
1474
|
+
const toolsIndex = path11.join(mod.modulePath, "tools", "index.js");
|
|
1475
|
+
if (!fs13.existsSync(toolsIndex)) {
|
|
1476
|
+
issues.push(`Declares ${declaredTools.length} tools but tools/index.js not found`);
|
|
1477
|
+
} else {
|
|
1478
|
+
try {
|
|
1479
|
+
const toolModule = await import(toolsIndex);
|
|
1480
|
+
for (const tool of declaredTools) {
|
|
1481
|
+
const handlerName = tool.handler || tool.name;
|
|
1482
|
+
if (typeof toolModule[handlerName] !== "function") {
|
|
1483
|
+
issues.push(`Tool '${tool.name}' declares handler '${handlerName}' but export not found`);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
issues.push(`tools/index.js failed to load: ${err instanceof Error ? err.message : err}`);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
const suspectExts = [".db", ".sqlite", ".json"];
|
|
1492
|
+
const suspectDirs = ["output", "data", "state"];
|
|
1493
|
+
for (const dir of suspectDirs) {
|
|
1494
|
+
const fullPath = path11.join(mod.modulePath, dir);
|
|
1495
|
+
if (fs13.existsSync(fullPath) && fs13.statSync(fullPath).isDirectory()) {
|
|
1496
|
+
issues.push(`Data dir '${dir}/' found in module code (should be in space data path)`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
try {
|
|
1500
|
+
const entries = fs13.readdirSync(mod.modulePath);
|
|
1501
|
+
for (const entry of entries) {
|
|
1502
|
+
if (suspectExts.some((ext) => entry.endsWith(ext))) {
|
|
1503
|
+
issues.push(`Data file '${entry}' found in module code dir`);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
} catch {
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
name: mod.name,
|
|
1510
|
+
status: issues.length === 0 ? "ok" : issues.some((i) => i.startsWith("Missing required")) ? "error" : "warning",
|
|
1511
|
+
issues
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// src/tools/forget.ts
|
|
1516
|
+
async function handleForget(args2, engramsPath) {
|
|
1517
|
+
const engrams = loadEngrams(engramsPath);
|
|
1518
|
+
if (args2.id) {
|
|
1519
|
+
const idx = engrams.findIndex((e) => e.id === args2.id);
|
|
1520
|
+
if (idx === -1) {
|
|
1521
|
+
return { success: false, error: `Engram ${args2.id} not found` };
|
|
1522
|
+
}
|
|
1523
|
+
const engram = engrams[idx];
|
|
1524
|
+
if (engram.status === "retired") {
|
|
1525
|
+
return { success: false, error: `Engram ${args2.id} is already retired` };
|
|
1526
|
+
}
|
|
1527
|
+
engrams[idx] = { ...engram, status: "retired" };
|
|
1528
|
+
saveEngrams(engramsPath, engrams);
|
|
1529
|
+
return { success: true, retired: { id: engram.id, statement: engram.statement } };
|
|
1530
|
+
}
|
|
1531
|
+
if (args2.search) {
|
|
1532
|
+
const searchLower = args2.search.toLowerCase();
|
|
1533
|
+
const matches = engrams.filter((e) => e.status !== "retired").filter(
|
|
1534
|
+
(e) => e.statement.toLowerCase().includes(searchLower) || e.id.toLowerCase().includes(searchLower) || e.tags.some((t) => t.toLowerCase().includes(searchLower))
|
|
1535
|
+
).slice(0, 10);
|
|
1536
|
+
if (matches.length === 0) {
|
|
1537
|
+
return { success: false, error: `No active engrams matching "${args2.search}"` };
|
|
1538
|
+
}
|
|
1539
|
+
if (matches.length === 1) {
|
|
1540
|
+
const engram = matches[0];
|
|
1541
|
+
const idx = engrams.findIndex((e) => e.id === engram.id);
|
|
1542
|
+
engrams[idx] = { ...engram, status: "retired" };
|
|
1543
|
+
saveEngrams(engramsPath, engrams);
|
|
1544
|
+
return { success: true, retired: { id: engram.id, statement: engram.statement } };
|
|
1545
|
+
}
|
|
1546
|
+
return {
|
|
1547
|
+
success: false,
|
|
1548
|
+
matches: matches.map((e) => ({ id: e.id, statement: e.statement })),
|
|
1549
|
+
error: `Multiple matches found. Specify an exact ID to retire.`
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
return { success: false, error: "Provide either id or search parameter" };
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/tools/feedback.ts
|
|
1556
|
+
async function handleFeedback(args2, engramsPath) {
|
|
1557
|
+
const engrams = loadEngrams(engramsPath);
|
|
1558
|
+
const engram = engrams.find((e) => e.id === args2.engram_id);
|
|
1559
|
+
if (!engram) {
|
|
1560
|
+
return { success: false, engram_id: args2.engram_id, signal: args2.signal, error: `Engram ${args2.engram_id} not found` };
|
|
1561
|
+
}
|
|
1562
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1563
|
+
if (engram.activation.last_accessed === today) {
|
|
1564
|
+
}
|
|
1565
|
+
if (!engram.feedback_signals) {
|
|
1566
|
+
engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
|
|
1567
|
+
}
|
|
1568
|
+
engram.feedback_signals[args2.signal] += 1;
|
|
1569
|
+
engram.activation.last_accessed = today;
|
|
1570
|
+
atomicWriteYaml(engramsPath, { engrams });
|
|
1571
|
+
return {
|
|
1572
|
+
success: true,
|
|
1573
|
+
engram_id: args2.engram_id,
|
|
1574
|
+
signal: args2.signal,
|
|
1575
|
+
feedback_signals: { ...engram.feedback_signals }
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// src/resources.ts
|
|
1580
|
+
import {
|
|
1581
|
+
ListResourcesRequestSchema,
|
|
1582
|
+
ReadResourceRequestSchema,
|
|
1583
|
+
ListResourceTemplatesRequestSchema
|
|
1584
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1585
|
+
import * as fs14 from "fs";
|
|
1586
|
+
import * as path12 from "path";
|
|
1587
|
+
function registerResources(server, storage2) {
|
|
1588
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
1589
|
+
resources: [
|
|
1590
|
+
{
|
|
1591
|
+
uri: "datacore://status",
|
|
1592
|
+
name: "Datacore Status",
|
|
1593
|
+
description: "Current system status summary",
|
|
1594
|
+
mimeType: "application/json"
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
uri: "datacore://engrams/active",
|
|
1598
|
+
name: "Active Engrams",
|
|
1599
|
+
description: "All active engrams with their metadata",
|
|
1600
|
+
mimeType: "application/json"
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
uri: "datacore://journal/today",
|
|
1604
|
+
name: "Today's Journal",
|
|
1605
|
+
description: "Today's journal entry",
|
|
1606
|
+
mimeType: "text/markdown"
|
|
1607
|
+
}
|
|
1608
|
+
]
|
|
1609
|
+
}));
|
|
1610
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
1611
|
+
resourceTemplates: [
|
|
1612
|
+
{
|
|
1613
|
+
uriTemplate: "datacore://journal/{date}",
|
|
1614
|
+
name: "Journal Entry",
|
|
1615
|
+
description: "Journal entry for a specific date (YYYY-MM-DD)",
|
|
1616
|
+
mimeType: "text/markdown"
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
uriTemplate: "datacore://engrams/{id}",
|
|
1620
|
+
name: "Engram",
|
|
1621
|
+
description: "A specific engram by ID",
|
|
1622
|
+
mimeType: "application/json"
|
|
1623
|
+
}
|
|
1624
|
+
]
|
|
1625
|
+
}));
|
|
1626
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1627
|
+
const uri = request.params.uri;
|
|
1628
|
+
if (uri === "datacore://status") {
|
|
1629
|
+
const engrams = loadEngrams(storage2.engramsPath);
|
|
1630
|
+
const active = engrams.filter((e) => e.status === "active").length;
|
|
1631
|
+
return {
|
|
1632
|
+
contents: [{
|
|
1633
|
+
uri,
|
|
1634
|
+
mimeType: "application/json",
|
|
1635
|
+
text: JSON.stringify({ version: currentVersion, mode: storage2.mode, engrams: engrams.length, active })
|
|
1636
|
+
}]
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
if (uri === "datacore://engrams/active") {
|
|
1640
|
+
const engrams = loadEngrams(storage2.engramsPath).filter((e) => e.status === "active");
|
|
1641
|
+
return {
|
|
1642
|
+
contents: [{
|
|
1643
|
+
uri,
|
|
1644
|
+
mimeType: "application/json",
|
|
1645
|
+
text: JSON.stringify(engrams, null, 2)
|
|
1646
|
+
}]
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
|
|
1650
|
+
if (journalMatch) {
|
|
1651
|
+
const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
|
|
1652
|
+
const filePath = path12.join(storage2.journalPath, `${dateStr}.md`);
|
|
1653
|
+
if (!fs14.existsSync(filePath)) {
|
|
1654
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
|
|
1655
|
+
}
|
|
1656
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: fs14.readFileSync(filePath, "utf8") }] };
|
|
1657
|
+
}
|
|
1658
|
+
const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
|
|
1659
|
+
if (engramMatch) {
|
|
1660
|
+
const engrams = loadEngrams(storage2.engramsPath);
|
|
1661
|
+
const engram = engrams.find((e) => e.id === engramMatch[1]);
|
|
1662
|
+
if (!engram) {
|
|
1663
|
+
throw new Error(`Engram not found: ${engramMatch[1]}`);
|
|
1664
|
+
}
|
|
1665
|
+
return {
|
|
1666
|
+
contents: [{
|
|
1667
|
+
uri,
|
|
1668
|
+
mimeType: "application/json",
|
|
1669
|
+
text: JSON.stringify(engram, null, 2)
|
|
1670
|
+
}]
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
function notifyEngramsChanged(server) {
|
|
1677
|
+
try {
|
|
1678
|
+
server.sendResourceUpdated?.({ uri: "datacore://engrams/active" });
|
|
1679
|
+
} catch {
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// src/datacortex.ts
|
|
1684
|
+
import { execFile } from "child_process";
|
|
1685
|
+
import * as fs15 from "fs";
|
|
1686
|
+
import * as path13 from "path";
|
|
1687
|
+
var DatacortexBridge = class {
|
|
1688
|
+
pythonPath;
|
|
1689
|
+
scriptPath;
|
|
1690
|
+
constructor(datacorePath) {
|
|
1691
|
+
this.pythonPath = process.env.DATACORE_PYTHON ?? "python3";
|
|
1692
|
+
this.scriptPath = this.findBridgeScript(datacorePath);
|
|
1693
|
+
}
|
|
1694
|
+
findBridgeScript(datacorePath) {
|
|
1695
|
+
const candidates = [
|
|
1696
|
+
path13.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
|
|
1697
|
+
path13.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
|
|
1698
|
+
];
|
|
1699
|
+
for (const candidate of candidates) {
|
|
1700
|
+
if (fs15.existsSync(candidate)) return candidate;
|
|
1701
|
+
}
|
|
1702
|
+
return null;
|
|
1703
|
+
}
|
|
1704
|
+
isAvailable() {
|
|
1705
|
+
if (!this.scriptPath) {
|
|
1706
|
+
return { available: false, reason: "Datacortex bridge script not found" };
|
|
1707
|
+
}
|
|
1708
|
+
try {
|
|
1709
|
+
const { execSync: execSync2 } = __require("child_process");
|
|
1710
|
+
execSync2(`${this.pythonPath} --version`, { timeout: 5e3, stdio: "pipe" });
|
|
1711
|
+
return { available: true };
|
|
1712
|
+
} catch {
|
|
1713
|
+
return { available: false, reason: `Python not found at ${this.pythonPath}` };
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
async search(query, limit = 20) {
|
|
1717
|
+
if (!this.scriptPath) {
|
|
1718
|
+
return { results: [], fallback: true };
|
|
1719
|
+
}
|
|
1720
|
+
const request = JSON.stringify({ action: "search", query, limit });
|
|
1721
|
+
return new Promise((resolve) => {
|
|
1722
|
+
const proc = execFile(
|
|
1723
|
+
this.pythonPath,
|
|
1724
|
+
[this.scriptPath],
|
|
1725
|
+
{ timeout: 3e4 },
|
|
1726
|
+
(error, stdout, stderr) => {
|
|
1727
|
+
if (error) {
|
|
1728
|
+
logger.warning(`Datacortex bridge error: ${error.message}`);
|
|
1729
|
+
resolve({ results: [], fallback: true });
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
try {
|
|
1733
|
+
const response = JSON.parse(stdout.trim());
|
|
1734
|
+
if (response.error) {
|
|
1735
|
+
logger.warning(`Datacortex bridge: ${response.error}`);
|
|
1736
|
+
resolve({ results: [], fallback: true });
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
resolve({ results: response.results ?? [] });
|
|
1740
|
+
} catch {
|
|
1741
|
+
logger.warning(`Datacortex bridge: invalid response`);
|
|
1742
|
+
resolve({ results: [], fallback: true });
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
);
|
|
1746
|
+
proc.stdin?.write(request + "\n");
|
|
1747
|
+
proc.stdin?.end();
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1752
|
+
// src/server.ts
|
|
1753
|
+
var storage;
|
|
1754
|
+
var updateAvailable = null;
|
|
1755
|
+
var moduleTools = [];
|
|
1756
|
+
var discoveredModules = [];
|
|
1757
|
+
var isFirstRun = false;
|
|
1758
|
+
var serverRef = null;
|
|
1759
|
+
var datacortexBridge = null;
|
|
1760
|
+
function createServer() {
|
|
1761
|
+
const server = new Server(
|
|
1762
|
+
{ name: "datacore-mcp", version: currentVersion },
|
|
1763
|
+
{ capabilities: { tools: {}, logging: {}, resources: { subscribe: true } } }
|
|
1764
|
+
);
|
|
1765
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1766
|
+
tools: [
|
|
1767
|
+
...TOOLS.map((t) => ({
|
|
1768
|
+
name: t.name,
|
|
1769
|
+
description: t.description,
|
|
1770
|
+
inputSchema: zodToJsonSchema(t.inputSchema)
|
|
1771
|
+
})),
|
|
1772
|
+
...moduleTools.map((t) => ({
|
|
1773
|
+
name: t.fullName,
|
|
1774
|
+
description: t.definition.description,
|
|
1775
|
+
inputSchema: zodToJsonSchema(t.definition.inputSchema)
|
|
1776
|
+
}))
|
|
1777
|
+
]
|
|
1778
|
+
}));
|
|
1779
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1780
|
+
const { name, arguments: args2 } = request.params;
|
|
1781
|
+
try {
|
|
1782
|
+
const result = await routeTool(name, args2 ?? {});
|
|
1783
|
+
const response = [];
|
|
1784
|
+
if (isFirstRun) {
|
|
1785
|
+
isFirstRun = false;
|
|
1786
|
+
response.push({ type: "text", text: JSON.stringify({
|
|
1787
|
+
_welcome: `Welcome to Datacore MCP! Your data is stored at ${storage.basePath}. Try: datacore.learn to create your first engram, datacore.capture to write a journal entry, or datacore.status to see system info.`
|
|
1788
|
+
}) });
|
|
1789
|
+
}
|
|
1790
|
+
response.push({ type: "text", text: JSON.stringify(result, null, 2) });
|
|
1791
|
+
return { content: response };
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
return { content: [{ type: "text", text: `Error: ${error}` }], isError: true };
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
logger.setServer(server);
|
|
1797
|
+
registerResources(server, storage);
|
|
1798
|
+
serverRef = server;
|
|
1799
|
+
return server;
|
|
1800
|
+
}
|
|
1801
|
+
var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback"]);
|
|
1802
|
+
async function routeTool(name, args2) {
|
|
1803
|
+
const coreTool = TOOLS.find((t) => t.name === name);
|
|
1804
|
+
if (coreTool) {
|
|
1805
|
+
const validated = coreTool.inputSchema.parse(args2);
|
|
1806
|
+
let result;
|
|
1807
|
+
switch (name) {
|
|
1808
|
+
case "datacore.capture":
|
|
1809
|
+
result = await handleCapture(validated, storage);
|
|
1810
|
+
break;
|
|
1811
|
+
case "datacore.learn":
|
|
1812
|
+
result = await handleLearn(validated, storage.engramsPath);
|
|
1813
|
+
break;
|
|
1814
|
+
case "datacore.inject":
|
|
1815
|
+
result = await handleInject(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath });
|
|
1816
|
+
break;
|
|
1817
|
+
case "datacore.search":
|
|
1818
|
+
result = await handleSearch(validated, { journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
|
|
1819
|
+
break;
|
|
1820
|
+
case "datacore.ingest":
|
|
1821
|
+
result = await handleIngest(validated, { knowledgePath: storage.knowledgePath, engramsPath: storage.engramsPath });
|
|
1822
|
+
break;
|
|
1823
|
+
case "datacore.status":
|
|
1824
|
+
result = await handleStatus({ ...storage, engramsPath: storage.engramsPath, packsPath: storage.packsPath }, updateAvailable);
|
|
1825
|
+
break;
|
|
1826
|
+
case "datacore.forget":
|
|
1827
|
+
result = await handleForget(validated, storage.engramsPath);
|
|
1828
|
+
break;
|
|
1829
|
+
case "datacore.feedback":
|
|
1830
|
+
result = await handleFeedback(validated, storage.engramsPath);
|
|
1831
|
+
break;
|
|
1832
|
+
case "datacore.packs.discover":
|
|
1833
|
+
result = handleDiscover(validated, storage.packsPath);
|
|
1834
|
+
break;
|
|
1835
|
+
case "datacore.packs.install":
|
|
1836
|
+
result = await handleInstall(validated, storage.packsPath);
|
|
1837
|
+
break;
|
|
1838
|
+
case "datacore.packs.export":
|
|
1839
|
+
result = await handleExport(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath });
|
|
1840
|
+
break;
|
|
1841
|
+
case "datacore.modules.list":
|
|
1842
|
+
result = await handleModulesList(validated, storage, discoveredModules);
|
|
1843
|
+
break;
|
|
1844
|
+
case "datacore.modules.info":
|
|
1845
|
+
result = await handleModulesInfo(validated, storage, discoveredModules);
|
|
1846
|
+
break;
|
|
1847
|
+
case "datacore.modules.health":
|
|
1848
|
+
result = await handleModulesHealth(validated, storage, discoveredModules);
|
|
1849
|
+
break;
|
|
1850
|
+
default:
|
|
1851
|
+
throw new Error(`Unknown core tool: ${name}`);
|
|
1852
|
+
}
|
|
1853
|
+
if (ENGRAM_MUTATING_TOOLS.has(name) && serverRef) {
|
|
1854
|
+
notifyEngramsChanged(serverRef);
|
|
1855
|
+
}
|
|
1856
|
+
return result;
|
|
1857
|
+
}
|
|
1858
|
+
const modTool = moduleTools.find((t) => t.fullName === name);
|
|
1859
|
+
if (modTool) {
|
|
1860
|
+
const validated = modTool.definition.inputSchema.parse(args2);
|
|
1861
|
+
return modTool.definition.handler(validated, modTool.context);
|
|
1862
|
+
}
|
|
1863
|
+
const allNames = [...TOOLS.map((t) => t.name), ...moduleTools.map((t) => t.fullName)];
|
|
1864
|
+
const suggestions = findClosestTools(name, allNames);
|
|
1865
|
+
const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
1866
|
+
throw new Error(`Unknown tool: ${name}.${hint}`);
|
|
1867
|
+
}
|
|
1868
|
+
function levenshtein(a, b) {
|
|
1869
|
+
const m = a.length, n = b.length;
|
|
1870
|
+
const dp = Array.from({ length: n + 1 }, (_, i) => i);
|
|
1871
|
+
for (let i = 1; i <= m; i++) {
|
|
1872
|
+
let prev = dp[0];
|
|
1873
|
+
dp[0] = i;
|
|
1874
|
+
for (let j = 1; j <= n; j++) {
|
|
1875
|
+
const tmp = dp[j];
|
|
1876
|
+
dp[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
|
|
1877
|
+
prev = tmp;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return dp[n];
|
|
1881
|
+
}
|
|
1882
|
+
function findClosestTools(name, allNames) {
|
|
1883
|
+
const threshold = Math.max(3, Math.floor(name.length * 0.35));
|
|
1884
|
+
const scored = allNames.map((t) => ({ name: t, dist: levenshtein(name.toLowerCase(), t.toLowerCase()) })).filter((s) => s.dist <= threshold).sort((a, b) => a.dist - b.dist);
|
|
1885
|
+
return scored.slice(0, 2).map((s) => s.name);
|
|
1886
|
+
}
|
|
1887
|
+
async function initStorage() {
|
|
1888
|
+
storage = detectStorage();
|
|
1889
|
+
if (storage.mode === "core") {
|
|
1890
|
+
const result = initCore(storage.basePath);
|
|
1891
|
+
isFirstRun = result.isFirstRun;
|
|
1892
|
+
}
|
|
1893
|
+
if (storage.mode === "full") {
|
|
1894
|
+
discoveredModules = discoverModules(storage);
|
|
1895
|
+
moduleTools = await loadModuleTools(discoveredModules, storage);
|
|
1896
|
+
datacortexBridge = new DatacortexBridge(storage.basePath);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
async function runStdio() {
|
|
1900
|
+
await initStorage();
|
|
1901
|
+
checkForUpdate().then((v) => {
|
|
1902
|
+
updateAvailable = v;
|
|
1903
|
+
});
|
|
1904
|
+
const updateInterval = setInterval(() => {
|
|
1905
|
+
checkForUpdate().then((v) => {
|
|
1906
|
+
updateAvailable = v;
|
|
1907
|
+
});
|
|
1908
|
+
}, 36e5);
|
|
1909
|
+
updateInterval.unref();
|
|
1910
|
+
const server = createServer();
|
|
1911
|
+
const transport = new StdioServerTransport();
|
|
1912
|
+
await server.connect(transport);
|
|
1913
|
+
server.onclose = () => {
|
|
1914
|
+
clearInterval(updateInterval);
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
async function runHttp() {
|
|
1918
|
+
const { createServer: createHttpServer } = await import("http");
|
|
1919
|
+
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
1920
|
+
await initStorage();
|
|
1921
|
+
checkForUpdate().then((v) => {
|
|
1922
|
+
updateAvailable = v;
|
|
1923
|
+
});
|
|
1924
|
+
const port = parseInt(process.env.DATACORE_HTTP_PORT ?? "3100", 10);
|
|
1925
|
+
const host = process.env.DATACORE_HTTP_HOST ?? "127.0.0.1";
|
|
1926
|
+
const server = createServer();
|
|
1927
|
+
const httpServer = createHttpServer(async (req, res) => {
|
|
1928
|
+
if (req.method === "POST" && req.url === "/mcp") {
|
|
1929
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
1930
|
+
await server.connect(transport);
|
|
1931
|
+
await transport.handleRequest(req, res);
|
|
1932
|
+
} else if (req.method === "GET" && req.url === "/health") {
|
|
1933
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1934
|
+
res.end(JSON.stringify({ status: "ok", version: currentVersion }));
|
|
1935
|
+
} else {
|
|
1936
|
+
res.writeHead(404);
|
|
1937
|
+
res.end("Not Found");
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
httpServer.listen(port, host, () => {
|
|
1941
|
+
console.log(`Datacore MCP server listening on http://${host}:${port}/mcp`);
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// src/index.ts
|
|
1946
|
+
var args = process.argv.slice(2);
|
|
1947
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
1948
|
+
console.log(currentVersion);
|
|
1949
|
+
process.exit(0);
|
|
1950
|
+
}
|
|
1951
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1952
|
+
console.log(`Datacore MCP Server v${currentVersion}
|
|
1953
|
+
An MCP server that gives AI assistants persistent memory through engrams.
|
|
1954
|
+
|
|
1955
|
+
Usage:
|
|
1956
|
+
npx @datacore-one/mcp Start MCP server (stdio transport)
|
|
1957
|
+
npx @datacore-one/mcp --http Start MCP server (HTTP transport)
|
|
1958
|
+
npx @datacore-one/mcp --help Show this help
|
|
1959
|
+
npx @datacore-one/mcp --version Show version
|
|
1960
|
+
|
|
1961
|
+
Tools:
|
|
1962
|
+
Core
|
|
1963
|
+
datacore.capture Capture a journal entry or knowledge note
|
|
1964
|
+
datacore.learn Create an engram from a statement
|
|
1965
|
+
datacore.inject Get relevant engrams for a task
|
|
1966
|
+
datacore.search Search journal and knowledge by keyword
|
|
1967
|
+
datacore.ingest Ingest text as knowledge note with engram extraction
|
|
1968
|
+
datacore.status System status, counts, update info
|
|
1969
|
+
|
|
1970
|
+
Lifecycle
|
|
1971
|
+
datacore.feedback Signal whether an injected engram was helpful
|
|
1972
|
+
datacore.forget Retire an engram by ID or search
|
|
1973
|
+
|
|
1974
|
+
Packs
|
|
1975
|
+
datacore.packs.discover Browse available engram packs
|
|
1976
|
+
datacore.packs.install Install or upgrade an engram pack
|
|
1977
|
+
datacore.packs.export Export personal engrams as a shareable pack
|
|
1978
|
+
|
|
1979
|
+
Modules (full mode)
|
|
1980
|
+
datacore.modules.list List installed modules
|
|
1981
|
+
datacore.modules.info Detailed info about a module
|
|
1982
|
+
datacore.modules.health Health check for modules
|
|
1983
|
+
|
|
1984
|
+
Configuration:
|
|
1985
|
+
DATACORE_PATH Full installation path (default: ~/Data)
|
|
1986
|
+
DATACORE_CORE_PATH Core mode storage path (default: ~/Datacore)
|
|
1987
|
+
DATACORE_TIMEZONE IANA timezone (e.g., Europe/Ljubljana)
|
|
1988
|
+
DATACORE_LOG_LEVEL Log level: debug|info|warning|error (default: warning)
|
|
1989
|
+
DATACORE_CACHE_TTL File cache TTL in seconds (default: 60)
|
|
1990
|
+
DATACORE_TRANSPORT Transport: stdio or http (default: stdio)
|
|
1991
|
+
DATACORE_HTTP_PORT HTTP transport port (default: 3100)
|
|
1992
|
+
DATACORE_HTTP_HOST HTTP transport bind address (default: 127.0.0.1)
|
|
1993
|
+
|
|
1994
|
+
Examples:
|
|
1995
|
+
# Add to Claude Desktop config
|
|
1996
|
+
{ "mcpServers": { "datacore": { "command": "npx", "args": ["@datacore-one/mcp"] } } }
|
|
1997
|
+
|
|
1998
|
+
# Run with HTTP transport on custom port
|
|
1999
|
+
DATACORE_HTTP_PORT=8080 npx @datacore-one/mcp --http
|
|
2000
|
+
`);
|
|
2001
|
+
process.exit(0);
|
|
2002
|
+
}
|
|
2003
|
+
var useHttp = args.includes("--http") || process.env.DATACORE_TRANSPORT === "http";
|
|
2004
|
+
var start = useHttp ? runHttp : runStdio;
|
|
2005
|
+
start().catch((error) => {
|
|
2006
|
+
console.error("Failed to start Datacore MCP server:", error);
|
|
2007
|
+
process.exit(1);
|
|
2008
|
+
});
|
|
2009
|
+
//# sourceMappingURL=index.js.map
|