@datacore-one/mcp 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -9
- package/dist/index.js +1040 -275
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -68,11 +68,115 @@ function initCore(basePath) {
|
|
|
68
68
|
}
|
|
69
69
|
const configPath = path.join(basePath, "config.yaml");
|
|
70
70
|
if (!fs.existsSync(configPath)) {
|
|
71
|
-
fs.writeFileSync(configPath, "# Datacore MCP configuration\nversion:
|
|
71
|
+
fs.writeFileSync(configPath, "# Datacore MCP configuration\nversion: 2\n# engrams:\n# auto_promote: false\n# packs:\n# trusted_publishers: []\n# search:\n# max_results: 20\n# snippet_length: 500\n# hints:\n# enabled: true\n");
|
|
72
72
|
}
|
|
73
|
+
generateContextFiles(basePath);
|
|
73
74
|
copyStarterPacks(basePath);
|
|
74
75
|
return { isFirstRun: isFirstRun2 };
|
|
75
76
|
}
|
|
77
|
+
function generateContextFiles(basePath) {
|
|
78
|
+
const files = [
|
|
79
|
+
{ rel: "CLAUDE.md", content: CONTEXT_CLAUDE },
|
|
80
|
+
{ rel: "AGENTS.md", content: CONTEXT_AGENTS },
|
|
81
|
+
{ rel: ".cursorrules", content: CONTEXT_CURSORRULES },
|
|
82
|
+
{ rel: ".github/copilot-instructions.md", content: CONTEXT_COPILOT }
|
|
83
|
+
];
|
|
84
|
+
for (const { rel, content } of files) {
|
|
85
|
+
const filePath = path.join(basePath, rel);
|
|
86
|
+
if (!fs.existsSync(filePath)) {
|
|
87
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
88
|
+
fs.writeFileSync(filePath, content);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
var DATACORE_GUIDE = `Datacore gives AI assistants persistent memory through **engrams** \u2014 typed knowledge units
|
|
93
|
+
that get injected into context when relevant.
|
|
94
|
+
|
|
95
|
+
## Session Workflow
|
|
96
|
+
|
|
97
|
+
1. **datacore.session.start** \u2014 Call this first. Gets relevant engrams + today's journal.
|
|
98
|
+
2. Work on the task. Use **datacore.recall** to search everything.
|
|
99
|
+
3. **datacore.feedback** \u2014 Rate which injected engrams were helpful.
|
|
100
|
+
4. **datacore.session.end** \u2014 Capture summary + suggest new engrams.
|
|
101
|
+
|
|
102
|
+
## Key Tools
|
|
103
|
+
|
|
104
|
+
| Tool | Purpose |
|
|
105
|
+
|------|---------|
|
|
106
|
+
| session.start | Start here. Begin session with context injection. |
|
|
107
|
+
| session.end | End session, capture journal + new engrams. |
|
|
108
|
+
| learn | Record a reusable insight (creates candidate engram). |
|
|
109
|
+
| promote | Activate candidate engrams. |
|
|
110
|
+
| inject | Get relevant engrams for a specific task. |
|
|
111
|
+
| recall | Search all sources (engrams + journal + knowledge). |
|
|
112
|
+
| capture | Write a journal entry or knowledge note. |
|
|
113
|
+
| search | Search journal and knowledge files. |
|
|
114
|
+
| ingest | Import text and extract engram suggestions. |
|
|
115
|
+
| feedback | Rate engrams: positive/negative/neutral. |
|
|
116
|
+
| forget | Retire an engram permanently. |
|
|
117
|
+
| status | System health + recommendations. |
|
|
118
|
+
| packs.discover | Browse available engram packs. |
|
|
119
|
+
| packs.install | Install a pack. |
|
|
120
|
+
| packs.export | Export your engrams as a pack. |
|
|
121
|
+
|
|
122
|
+
## Engram Lifecycle
|
|
123
|
+
|
|
124
|
+
learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker \u2192 forget (retire)
|
|
125
|
+
|
|
126
|
+
- **candidate**: Created but not yet active. Won't appear in inject results.
|
|
127
|
+
- **active**: Appears in inject results when relevant to the task.
|
|
128
|
+
- **retired**: Permanently removed from injection.
|
|
129
|
+
|
|
130
|
+
Positive feedback strengthens retrieval. Unused engrams naturally decay over time.
|
|
131
|
+
|
|
132
|
+
## Data Storage
|
|
133
|
+
|
|
134
|
+
All data is in this directory as plain text files:
|
|
135
|
+
- \`engrams.yaml\` \u2014 Your learned knowledge
|
|
136
|
+
- \`journal/\` \u2014 Daily session logs (YYYY-MM-DD.md)
|
|
137
|
+
- \`knowledge/\` \u2014 Ingested reference material
|
|
138
|
+
- \`packs/\` \u2014 Installed engram packs
|
|
139
|
+
- \`config.yaml\` \u2014 Configuration (all fields optional)
|
|
140
|
+
`;
|
|
141
|
+
var CONTEXT_CLAUDE = `# Datacore
|
|
142
|
+
|
|
143
|
+
This is a Datacore installation \u2014 persistent memory for AI assistants.
|
|
144
|
+
|
|
145
|
+
${DATACORE_GUIDE}
|
|
146
|
+
|
|
147
|
+
## MCP Tools
|
|
148
|
+
|
|
149
|
+
All tools are prefixed with \`datacore.\` (e.g., \`datacore.session.start\`).
|
|
150
|
+
Call \`datacore.session.start\` at the beginning of every conversation.
|
|
151
|
+
`;
|
|
152
|
+
var CONTEXT_AGENTS = `# AGENTS.md
|
|
153
|
+
|
|
154
|
+
This directory is managed by [Datacore](https://github.com/datacore-one/mcp) \u2014 persistent memory for AI assistants.
|
|
155
|
+
|
|
156
|
+
${DATACORE_GUIDE}
|
|
157
|
+
|
|
158
|
+
## For AI Agents
|
|
159
|
+
|
|
160
|
+
All tools are available via MCP under the \`datacore.\` namespace.
|
|
161
|
+
Start every session by calling \`datacore.session.start\`.
|
|
162
|
+
`;
|
|
163
|
+
var CONTEXT_CURSORRULES = `# Datacore
|
|
164
|
+
|
|
165
|
+
This directory is managed by Datacore \u2014 persistent memory for AI assistants.
|
|
166
|
+
All tools are available via MCP under the \`datacore.\` namespace.
|
|
167
|
+
|
|
168
|
+
${DATACORE_GUIDE}`;
|
|
169
|
+
var CONTEXT_COPILOT = `# Datacore
|
|
170
|
+
|
|
171
|
+
This directory is managed by [Datacore](https://github.com/datacore-one/mcp) \u2014 persistent memory for AI assistants.
|
|
172
|
+
|
|
173
|
+
${DATACORE_GUIDE}
|
|
174
|
+
|
|
175
|
+
## MCP Integration
|
|
176
|
+
|
|
177
|
+
All tools are available via MCP under the \`datacore.\` namespace.
|
|
178
|
+
Start every session by calling \`datacore.session.start\`.
|
|
179
|
+
`;
|
|
76
180
|
function copyStarterPacks(basePath) {
|
|
77
181
|
const packsDir = path.join(basePath, "packs");
|
|
78
182
|
const bundledPacksDir = path.join(
|
|
@@ -90,10 +194,50 @@ function copyStarterPacks(basePath) {
|
|
|
90
194
|
}
|
|
91
195
|
}
|
|
92
196
|
|
|
197
|
+
// src/config.ts
|
|
198
|
+
import * as fs2 from "fs";
|
|
199
|
+
import * as path2 from "path";
|
|
200
|
+
import * as yaml from "js-yaml";
|
|
201
|
+
import { z } from "zod";
|
|
202
|
+
var ConfigSchema = z.object({
|
|
203
|
+
version: z.number().default(2),
|
|
204
|
+
engrams: z.object({
|
|
205
|
+
auto_promote: z.boolean().default(false)
|
|
206
|
+
}).default({}),
|
|
207
|
+
packs: z.object({
|
|
208
|
+
trusted_publishers: z.array(z.string()).default([])
|
|
209
|
+
}).default({}),
|
|
210
|
+
search: z.object({
|
|
211
|
+
max_results: z.number().default(20),
|
|
212
|
+
snippet_length: z.number().default(500)
|
|
213
|
+
}).default({}),
|
|
214
|
+
hints: z.object({
|
|
215
|
+
enabled: z.boolean().default(true)
|
|
216
|
+
}).default({})
|
|
217
|
+
});
|
|
218
|
+
var cachedConfig = null;
|
|
219
|
+
function loadConfig(basePath, mode) {
|
|
220
|
+
const configPath = mode === "full" ? path2.join(basePath, ".datacore", "config.yaml") : path2.join(basePath, "config.yaml");
|
|
221
|
+
let raw = {};
|
|
222
|
+
if (fs2.existsSync(configPath)) {
|
|
223
|
+
try {
|
|
224
|
+
raw = yaml.load(fs2.readFileSync(configPath, "utf8")) ?? {};
|
|
225
|
+
} catch {
|
|
226
|
+
raw = {};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
cachedConfig = ConfigSchema.parse(raw);
|
|
230
|
+
return cachedConfig;
|
|
231
|
+
}
|
|
232
|
+
function getConfig() {
|
|
233
|
+
if (!cachedConfig) return ConfigSchema.parse({});
|
|
234
|
+
return cachedConfig;
|
|
235
|
+
}
|
|
236
|
+
|
|
93
237
|
// package.json
|
|
94
238
|
var package_default = {
|
|
95
239
|
name: "@datacore-one/mcp",
|
|
96
|
-
version: "1.0
|
|
240
|
+
version: "1.1.0",
|
|
97
241
|
description: "Datacore MCP server \u2014 The Software of You",
|
|
98
242
|
type: "module",
|
|
99
243
|
bin: {
|
|
@@ -159,132 +303,177 @@ async function checkForUpdate() {
|
|
|
159
303
|
}
|
|
160
304
|
|
|
161
305
|
// src/tools/index.ts
|
|
162
|
-
import { z } from "zod";
|
|
306
|
+
import { z as z2 } from "zod";
|
|
163
307
|
var TOOLS = [
|
|
164
308
|
{
|
|
165
309
|
name: "datacore.capture",
|
|
166
310
|
description: "Capture a journal entry or knowledge note",
|
|
167
|
-
inputSchema:
|
|
168
|
-
type:
|
|
169
|
-
content:
|
|
170
|
-
title:
|
|
171
|
-
tags:
|
|
311
|
+
inputSchema: z2.object({
|
|
312
|
+
type: z2.enum(["journal", "knowledge"]),
|
|
313
|
+
content: z2.string().describe("Content to capture"),
|
|
314
|
+
title: z2.string().optional().describe("Title for knowledge notes"),
|
|
315
|
+
tags: z2.array(z2.string()).optional().describe("Tags to attach")
|
|
172
316
|
})
|
|
173
317
|
},
|
|
174
318
|
{
|
|
175
319
|
name: "datacore.learn",
|
|
176
320
|
description: "Create an engram from a statement \u2014 record a reusable learning",
|
|
177
|
-
inputSchema:
|
|
178
|
-
statement:
|
|
179
|
-
type:
|
|
180
|
-
scope:
|
|
181
|
-
tags:
|
|
182
|
-
domain:
|
|
183
|
-
visibility:
|
|
321
|
+
inputSchema: z2.object({
|
|
322
|
+
statement: z2.string().describe("The knowledge assertion"),
|
|
323
|
+
type: z2.enum(["behavioral", "terminological", "procedural", "architectural"]).optional(),
|
|
324
|
+
scope: z2.string().optional().describe("Scope: global | agent:X | command:X"),
|
|
325
|
+
tags: z2.array(z2.string()).optional(),
|
|
326
|
+
domain: z2.string().optional().describe("Dot-notation domain: software.architecture"),
|
|
327
|
+
visibility: z2.enum(["private", "public", "template"]).optional()
|
|
184
328
|
})
|
|
185
329
|
},
|
|
186
330
|
{
|
|
187
331
|
name: "datacore.inject",
|
|
188
332
|
description: "Get relevant engrams for a task \u2014 returns directives and considerations",
|
|
189
|
-
inputSchema:
|
|
190
|
-
prompt:
|
|
191
|
-
scope:
|
|
192
|
-
max_tokens:
|
|
193
|
-
min_relevance:
|
|
333
|
+
inputSchema: z2.object({
|
|
334
|
+
prompt: z2.string().describe("The task or question to match against"),
|
|
335
|
+
scope: z2.string().optional().describe("Filter by scope: global | agent:X | module:X | command:X"),
|
|
336
|
+
max_tokens: z2.number().optional().describe("Token budget (default: 8000)"),
|
|
337
|
+
min_relevance: z2.number().optional().describe("Minimum score threshold (default: 0.3)")
|
|
194
338
|
})
|
|
195
339
|
},
|
|
196
340
|
{
|
|
197
341
|
name: "datacore.search",
|
|
198
342
|
description: "Search journal entries and knowledge notes by keyword",
|
|
199
|
-
inputSchema:
|
|
200
|
-
query:
|
|
201
|
-
scope:
|
|
202
|
-
method:
|
|
203
|
-
limit:
|
|
343
|
+
inputSchema: z2.object({
|
|
344
|
+
query: z2.string().describe("Search query"),
|
|
345
|
+
scope: z2.enum(["journal", "knowledge", "all"]).optional(),
|
|
346
|
+
method: z2.enum(["keyword", "semantic"]).optional().describe("Search method (default: keyword)"),
|
|
347
|
+
limit: z2.number().optional().describe("Max results (default: 20)")
|
|
204
348
|
})
|
|
205
349
|
},
|
|
206
350
|
{
|
|
207
351
|
name: "datacore.ingest",
|
|
208
352
|
description: "Ingest text content as a knowledge note, optionally extract engram suggestions",
|
|
209
|
-
inputSchema:
|
|
210
|
-
content:
|
|
211
|
-
title:
|
|
212
|
-
tags:
|
|
353
|
+
inputSchema: z2.object({
|
|
354
|
+
content: z2.string().describe("Content to ingest"),
|
|
355
|
+
title: z2.string().optional(),
|
|
356
|
+
tags: z2.array(z2.string()).optional()
|
|
213
357
|
})
|
|
214
358
|
},
|
|
215
359
|
{
|
|
216
360
|
name: "datacore.status",
|
|
217
361
|
description: "Show Datacore status: engram/pack/note counts, scaling hints, update info",
|
|
218
|
-
inputSchema:
|
|
362
|
+
inputSchema: z2.object({})
|
|
219
363
|
},
|
|
220
364
|
{
|
|
221
365
|
name: "datacore.packs.discover",
|
|
222
366
|
description: "Browse available engram packs from the registry",
|
|
223
|
-
inputSchema:
|
|
224
|
-
query:
|
|
225
|
-
tags:
|
|
367
|
+
inputSchema: z2.object({
|
|
368
|
+
query: z2.string().optional().describe("Filter by name/description"),
|
|
369
|
+
tags: z2.array(z2.string()).optional().describe("Filter by tags")
|
|
226
370
|
})
|
|
227
371
|
},
|
|
228
372
|
{
|
|
229
373
|
name: "datacore.packs.install",
|
|
230
374
|
description: "Install or upgrade an engram pack",
|
|
231
|
-
inputSchema:
|
|
232
|
-
source:
|
|
375
|
+
inputSchema: z2.object({
|
|
376
|
+
source: z2.string().describe("Pack source: local path or pack ID from registry")
|
|
233
377
|
})
|
|
234
378
|
},
|
|
235
379
|
{
|
|
236
380
|
name: "datacore.forget",
|
|
237
381
|
description: "Retire an engram by ID or search term \u2014 marks it as retired so it is no longer injected",
|
|
238
|
-
inputSchema:
|
|
239
|
-
id:
|
|
240
|
-
search:
|
|
382
|
+
inputSchema: z2.object({
|
|
383
|
+
id: z2.string().optional().describe("Exact engram ID to retire (e.g., ENG-2026-0219-001)"),
|
|
384
|
+
search: z2.string().optional().describe("Search term to find engram by statement, tag, or ID fragment")
|
|
241
385
|
})
|
|
242
386
|
},
|
|
243
387
|
{
|
|
244
388
|
name: "datacore.feedback",
|
|
245
|
-
description: "Signal whether an injected engram was helpful (positive), unhelpful (negative), or seen but not acted on (neutral)",
|
|
246
|
-
inputSchema:
|
|
247
|
-
engram_id:
|
|
248
|
-
signal:
|
|
249
|
-
|
|
389
|
+
description: "Signal whether an injected engram was helpful (positive), unhelpful (negative), or seen but not acted on (neutral). Supports single or batch mode.",
|
|
390
|
+
inputSchema: z2.object({
|
|
391
|
+
engram_id: z2.string().optional().describe("The engram ID to provide feedback on (single mode)"),
|
|
392
|
+
signal: z2.enum(["positive", "negative", "neutral"]).optional().describe("Feedback signal (single mode)"),
|
|
393
|
+
signals: z2.array(z2.object({
|
|
394
|
+
engram_id: z2.string().describe("Engram ID"),
|
|
395
|
+
signal: z2.enum(["positive", "negative", "neutral"]).describe("Feedback signal")
|
|
396
|
+
})).optional().describe("Batch feedback signals"),
|
|
397
|
+
comment: z2.string().optional().describe("Optional comment about why")
|
|
398
|
+
}).refine((data) => data.engram_id && data.signal || data.signals && data.signals.length > 0, {
|
|
399
|
+
message: "Either (engram_id + signal) or signals array required"
|
|
250
400
|
})
|
|
251
401
|
},
|
|
252
402
|
{
|
|
253
403
|
name: "datacore.packs.export",
|
|
254
404
|
description: "Export personal engrams as a shareable pack. Preview by default, set confirm=true to write.",
|
|
255
|
-
inputSchema:
|
|
256
|
-
name:
|
|
257
|
-
description:
|
|
258
|
-
engram_ids:
|
|
259
|
-
filter_tags:
|
|
260
|
-
filter_domain:
|
|
261
|
-
confirm:
|
|
405
|
+
inputSchema: z2.object({
|
|
406
|
+
name: z2.string().describe("Pack name"),
|
|
407
|
+
description: z2.string().describe("Pack description"),
|
|
408
|
+
engram_ids: z2.array(z2.string()).optional().describe("Specific engram IDs to export"),
|
|
409
|
+
filter_tags: z2.array(z2.string()).optional().describe("Filter by tags"),
|
|
410
|
+
filter_domain: z2.string().optional().describe("Filter by domain prefix"),
|
|
411
|
+
confirm: z2.boolean().optional().describe("Set true to write pack (default: preview only)")
|
|
412
|
+
})
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "datacore.session.start",
|
|
416
|
+
description: "Start here. Call this at the beginning of every session to get relevant context, today's journal, and a guide to all available tools.",
|
|
417
|
+
inputSchema: z2.object({
|
|
418
|
+
task: z2.string().optional().describe("What you are working on (triggers engram injection)"),
|
|
419
|
+
tags: z2.array(z2.string()).optional().describe("Tags to filter injected engrams")
|
|
420
|
+
})
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "datacore.session.end",
|
|
424
|
+
description: "End a session \u2014 captures journal summary and creates engrams from suggestions",
|
|
425
|
+
inputSchema: z2.object({
|
|
426
|
+
summary: z2.string().describe("Session summary for the journal"),
|
|
427
|
+
tags: z2.array(z2.string()).optional().describe("Tags for the journal entry"),
|
|
428
|
+
engram_suggestions: z2.array(z2.object({
|
|
429
|
+
statement: z2.string().describe("The knowledge assertion"),
|
|
430
|
+
type: z2.enum(["behavioral", "terminological", "procedural", "architectural"]).optional()
|
|
431
|
+
})).optional().describe("Engrams to create from this session")
|
|
432
|
+
})
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "datacore.recall",
|
|
436
|
+
description: "Search all sources (engrams, journal, knowledge) for a topic \u2014 results grouped by source",
|
|
437
|
+
inputSchema: z2.object({
|
|
438
|
+
topic: z2.string().describe("What to search for"),
|
|
439
|
+
sources: z2.array(z2.enum(["engrams", "journal", "knowledge"])).optional().describe("Which sources to search (default: all)"),
|
|
440
|
+
limit: z2.number().optional().describe("Max results per source (default: 10)")
|
|
441
|
+
})
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "datacore.promote",
|
|
445
|
+
description: "Activate candidate engrams so they appear in inject results",
|
|
446
|
+
inputSchema: z2.object({
|
|
447
|
+
id: z2.string().optional().describe("Single engram ID to promote"),
|
|
448
|
+
ids: z2.array(z2.string()).optional().describe("Multiple engram IDs to promote")
|
|
449
|
+
}).refine((data) => data.id || data.ids && data.ids.length > 0, {
|
|
450
|
+
message: "At least one engram ID required (id or ids)"
|
|
262
451
|
})
|
|
263
452
|
},
|
|
264
453
|
{
|
|
265
454
|
name: "datacore.modules.list",
|
|
266
455
|
description: "List installed modules with scope, version, and capability counts",
|
|
267
|
-
inputSchema:
|
|
456
|
+
inputSchema: z2.object({})
|
|
268
457
|
},
|
|
269
458
|
{
|
|
270
459
|
name: "datacore.modules.info",
|
|
271
460
|
description: "Get detailed info about a specific module: manifest, tools, skills, agents, engrams",
|
|
272
|
-
inputSchema:
|
|
273
|
-
module:
|
|
461
|
+
inputSchema: z2.object({
|
|
462
|
+
module: z2.string().describe('Module name (e.g., "gtd", "slides", "crm")')
|
|
274
463
|
})
|
|
275
464
|
},
|
|
276
465
|
{
|
|
277
466
|
name: "datacore.modules.health",
|
|
278
467
|
description: "Check module health: missing files, env vars, data separation issues",
|
|
279
|
-
inputSchema:
|
|
280
|
-
module:
|
|
468
|
+
inputSchema: z2.object({
|
|
469
|
+
module: z2.string().optional().describe("Module name (omit for all modules)")
|
|
281
470
|
})
|
|
282
471
|
}
|
|
283
472
|
];
|
|
284
473
|
|
|
285
474
|
// src/tools/capture.ts
|
|
286
|
-
import * as
|
|
287
|
-
import * as
|
|
475
|
+
import * as fs3 from "fs";
|
|
476
|
+
import * as path3 from "path";
|
|
288
477
|
|
|
289
478
|
// src/limits.ts
|
|
290
479
|
var MAX_CONTENT_SIZE = 1e6;
|
|
@@ -324,17 +513,17 @@ function localDate(tz) {
|
|
|
324
513
|
}
|
|
325
514
|
function captureJournal(content, journalDir) {
|
|
326
515
|
const { date: today, time } = localDate();
|
|
327
|
-
const filePath =
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
const existing =
|
|
331
|
-
|
|
516
|
+
const filePath = path3.join(journalDir, `${today}.md`);
|
|
517
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
518
|
+
if (fs3.existsSync(filePath)) {
|
|
519
|
+
const existing = fs3.readFileSync(filePath, "utf8");
|
|
520
|
+
fs3.writeFileSync(filePath, `${existing}
|
|
332
521
|
## ${time}
|
|
333
522
|
|
|
334
523
|
${content}
|
|
335
524
|
`);
|
|
336
525
|
} else {
|
|
337
|
-
|
|
526
|
+
fs3.writeFileSync(filePath, `# ${today}
|
|
338
527
|
|
|
339
528
|
## ${time}
|
|
340
529
|
|
|
@@ -347,8 +536,8 @@ function captureKnowledge(content, title, tags, knowledgeDir) {
|
|
|
347
536
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
348
537
|
const slug = (title ?? "note").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
349
538
|
const fileName = `${timestamp}-${slug}.md`;
|
|
350
|
-
const filePath =
|
|
351
|
-
|
|
539
|
+
const filePath = path3.join(knowledgeDir, fileName);
|
|
540
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
352
541
|
const frontmatter = `---
|
|
353
542
|
title: "${title ?? "Untitled"}"
|
|
354
543
|
created: "${(/* @__PURE__ */ new Date()).toISOString()}"
|
|
@@ -358,82 +547,82 @@ created: "${(/* @__PURE__ */ new Date()).toISOString()}"
|
|
|
358
547
|
const tagLine = tags?.length ? `
|
|
359
548
|
${tags.map((t) => `#${t}`).join(" ")}
|
|
360
549
|
` : "";
|
|
361
|
-
|
|
550
|
+
fs3.writeFileSync(filePath, `${frontmatter}${content}
|
|
362
551
|
${tagLine}`);
|
|
363
552
|
return { success: true, path: filePath };
|
|
364
553
|
}
|
|
365
554
|
|
|
366
555
|
// src/engrams.ts
|
|
367
|
-
import * as
|
|
368
|
-
import * as
|
|
556
|
+
import * as fs4 from "fs";
|
|
557
|
+
import * as yaml2 from "js-yaml";
|
|
369
558
|
|
|
370
559
|
// src/schemas/engram.ts
|
|
371
|
-
import { z as
|
|
372
|
-
var ActivationSchema =
|
|
373
|
-
retrieval_strength:
|
|
374
|
-
storage_strength:
|
|
375
|
-
frequency:
|
|
376
|
-
last_accessed:
|
|
560
|
+
import { z as z3 } from "zod";
|
|
561
|
+
var ActivationSchema = z3.object({
|
|
562
|
+
retrieval_strength: z3.number().min(0).max(1),
|
|
563
|
+
storage_strength: z3.number().min(0).max(1),
|
|
564
|
+
frequency: z3.number().int().min(0),
|
|
565
|
+
last_accessed: z3.string()
|
|
377
566
|
});
|
|
378
|
-
var KnowledgeTypeSchema =
|
|
379
|
-
memory_class:
|
|
380
|
-
cognitive_level:
|
|
567
|
+
var KnowledgeTypeSchema = z3.object({
|
|
568
|
+
memory_class: z3.enum(["semantic", "episodic", "procedural", "metacognitive"]),
|
|
569
|
+
cognitive_level: z3.enum(["remember", "understand", "apply", "analyze", "evaluate", "create"])
|
|
381
570
|
});
|
|
382
|
-
var RelationsSchema =
|
|
383
|
-
broader:
|
|
384
|
-
narrower:
|
|
385
|
-
related:
|
|
386
|
-
conflicts:
|
|
571
|
+
var RelationsSchema = z3.object({
|
|
572
|
+
broader: z3.array(z3.string()).default([]),
|
|
573
|
+
narrower: z3.array(z3.string()).default([]),
|
|
574
|
+
related: z3.array(z3.string()).default([]),
|
|
575
|
+
conflicts: z3.array(z3.string()).default([])
|
|
387
576
|
});
|
|
388
|
-
var ProvenanceSchema =
|
|
389
|
-
origin:
|
|
390
|
-
chain:
|
|
391
|
-
signature:
|
|
392
|
-
license:
|
|
577
|
+
var ProvenanceSchema = z3.object({
|
|
578
|
+
origin: z3.string(),
|
|
579
|
+
chain: z3.array(z3.string()).default([]),
|
|
580
|
+
signature: z3.string().nullable().default(null),
|
|
581
|
+
license: z3.string().default("cc-by-sa-4.0")
|
|
393
582
|
});
|
|
394
|
-
var FeedbackSignalsSchema =
|
|
395
|
-
positive:
|
|
396
|
-
negative:
|
|
397
|
-
neutral:
|
|
583
|
+
var FeedbackSignalsSchema = z3.object({
|
|
584
|
+
positive: z3.number().int().default(0),
|
|
585
|
+
negative: z3.number().int().default(0),
|
|
586
|
+
neutral: z3.number().int().default(0)
|
|
398
587
|
});
|
|
399
|
-
var EngramSchema =
|
|
400
|
-
id:
|
|
401
|
-
version:
|
|
402
|
-
status:
|
|
403
|
-
consolidated:
|
|
404
|
-
type:
|
|
405
|
-
scope:
|
|
406
|
-
visibility:
|
|
407
|
-
statement:
|
|
408
|
-
rationale:
|
|
409
|
-
contraindications:
|
|
410
|
-
source_patterns:
|
|
411
|
-
derivation_count:
|
|
588
|
+
var EngramSchema = z3.object({
|
|
589
|
+
id: z3.string().regex(/^ENG-[A-Za-z0-9-]+$/),
|
|
590
|
+
version: z3.number().int().min(1),
|
|
591
|
+
status: z3.enum(["active", "dormant", "retired", "candidate"]),
|
|
592
|
+
consolidated: z3.boolean().default(false),
|
|
593
|
+
type: z3.enum(["behavioral", "terminological", "procedural", "architectural"]),
|
|
594
|
+
scope: z3.string(),
|
|
595
|
+
visibility: z3.enum(["private", "public", "template"]).default("private"),
|
|
596
|
+
statement: z3.string().min(1),
|
|
597
|
+
rationale: z3.string().optional(),
|
|
598
|
+
contraindications: z3.array(z3.string()).optional(),
|
|
599
|
+
source_patterns: z3.array(z3.string()).optional(),
|
|
600
|
+
derivation_count: z3.number().int().min(0).default(1),
|
|
412
601
|
knowledge_type: KnowledgeTypeSchema.optional(),
|
|
413
|
-
domain:
|
|
602
|
+
domain: z3.string().optional(),
|
|
414
603
|
relations: RelationsSchema.optional(),
|
|
415
604
|
activation: ActivationSchema,
|
|
416
605
|
provenance: ProvenanceSchema.optional(),
|
|
417
606
|
feedback_signals: FeedbackSignalsSchema.optional(),
|
|
418
|
-
tags:
|
|
419
|
-
pack:
|
|
420
|
-
abstract:
|
|
421
|
-
derived_from:
|
|
607
|
+
tags: z3.array(z3.string()).default([]),
|
|
608
|
+
pack: z3.string().nullable().default(null),
|
|
609
|
+
abstract: z3.string().nullable().default(null),
|
|
610
|
+
derived_from: z3.string().nullable().default(null)
|
|
422
611
|
});
|
|
423
|
-
var DatacoreExtensionSchema =
|
|
424
|
-
id:
|
|
425
|
-
injection_policy:
|
|
426
|
-
match_terms:
|
|
427
|
-
domain:
|
|
428
|
-
engram_count:
|
|
612
|
+
var DatacoreExtensionSchema = z3.object({
|
|
613
|
+
id: z3.string(),
|
|
614
|
+
injection_policy: z3.enum(["on_match", "on_request"]),
|
|
615
|
+
match_terms: z3.array(z3.string()).default([]),
|
|
616
|
+
domain: z3.string().optional(),
|
|
617
|
+
engram_count: z3.number().int().min(0)
|
|
429
618
|
});
|
|
430
|
-
var PackManifestSchema =
|
|
431
|
-
name:
|
|
432
|
-
description:
|
|
433
|
-
version:
|
|
434
|
-
creator:
|
|
435
|
-
license:
|
|
436
|
-
tags:
|
|
619
|
+
var PackManifestSchema = z3.object({
|
|
620
|
+
name: z3.string(),
|
|
621
|
+
description: z3.string(),
|
|
622
|
+
version: z3.string(),
|
|
623
|
+
creator: z3.string().optional(),
|
|
624
|
+
license: z3.string().optional(),
|
|
625
|
+
tags: z3.array(z3.string()).default([]),
|
|
437
626
|
"x-datacore": DatacoreExtensionSchema
|
|
438
627
|
});
|
|
439
628
|
|
|
@@ -491,9 +680,9 @@ var logger = new Logger();
|
|
|
491
680
|
|
|
492
681
|
// src/engrams.ts
|
|
493
682
|
function loadEngrams(filePath) {
|
|
494
|
-
if (!
|
|
683
|
+
if (!fs4.existsSync(filePath)) return [];
|
|
495
684
|
try {
|
|
496
|
-
const raw =
|
|
685
|
+
const raw = yaml2.load(fs4.readFileSync(filePath, "utf8"));
|
|
497
686
|
if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
|
|
498
687
|
const valid = [];
|
|
499
688
|
let skipped = 0;
|
|
@@ -515,15 +704,15 @@ function loadEngrams(filePath) {
|
|
|
515
704
|
}
|
|
516
705
|
}
|
|
517
706
|
function saveEngrams(filePath, engrams) {
|
|
518
|
-
const content =
|
|
519
|
-
|
|
707
|
+
const content = yaml2.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
708
|
+
fs4.writeFileSync(filePath, content);
|
|
520
709
|
}
|
|
521
710
|
function parseSkillMdFrontmatter(filePath) {
|
|
522
|
-
const content =
|
|
711
|
+
const content = fs4.readFileSync(filePath, "utf8");
|
|
523
712
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
524
713
|
if (!match) throw new Error(`No frontmatter found in ${filePath}`);
|
|
525
714
|
try {
|
|
526
|
-
return
|
|
715
|
+
return yaml2.load(match[1]);
|
|
527
716
|
} catch (err) {
|
|
528
717
|
throw new Error(`Failed to parse YAML frontmatter in ${filePath}: ${err}`);
|
|
529
718
|
}
|
|
@@ -537,12 +726,12 @@ function loadPack(packDir) {
|
|
|
537
726
|
return { manifest, engrams };
|
|
538
727
|
}
|
|
539
728
|
function loadAllPacks(packsDir) {
|
|
540
|
-
if (!
|
|
729
|
+
if (!fs4.existsSync(packsDir)) return [];
|
|
541
730
|
const packs = [];
|
|
542
|
-
for (const entry of
|
|
731
|
+
for (const entry of fs4.readdirSync(packsDir)) {
|
|
543
732
|
const packDir = `${packsDir}/${entry}`;
|
|
544
|
-
if (!
|
|
545
|
-
if (!
|
|
733
|
+
if (!fs4.statSync(packDir).isDirectory()) continue;
|
|
734
|
+
if (!fs4.existsSync(`${packDir}/SKILL.md`)) continue;
|
|
546
735
|
try {
|
|
547
736
|
packs.push(loadPack(packDir));
|
|
548
737
|
} catch (err) {
|
|
@@ -552,6 +741,13 @@ function loadAllPacks(packsDir) {
|
|
|
552
741
|
return packs;
|
|
553
742
|
}
|
|
554
743
|
|
|
744
|
+
// src/hints.ts
|
|
745
|
+
function buildHints(hints) {
|
|
746
|
+
if (!getConfig().hints.enabled) return void 0;
|
|
747
|
+
if (!hints.next && !hints.related?.length && !hints.warning) return void 0;
|
|
748
|
+
return hints;
|
|
749
|
+
}
|
|
750
|
+
|
|
555
751
|
// src/tools/learn.ts
|
|
556
752
|
function generateEngramId(existingEngrams) {
|
|
557
753
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -571,10 +767,11 @@ function generateEngramId(existingEngrams) {
|
|
|
571
767
|
async function handleLearn(args2, engramsPath) {
|
|
572
768
|
const engrams = loadEngrams(engramsPath);
|
|
573
769
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
770
|
+
const autoPromote = getConfig().engrams.auto_promote;
|
|
574
771
|
const engram = {
|
|
575
772
|
id: generateEngramId(engrams),
|
|
576
773
|
version: 2,
|
|
577
|
-
status: "candidate",
|
|
774
|
+
status: autoPromote ? "active" : "candidate",
|
|
578
775
|
consolidated: false,
|
|
579
776
|
type: args2.type ?? "behavioral",
|
|
580
777
|
scope: args2.scope ?? "global",
|
|
@@ -585,8 +782,8 @@ async function handleLearn(args2, engramsPath) {
|
|
|
585
782
|
domain: args2.domain,
|
|
586
783
|
tags: args2.tags ?? [],
|
|
587
784
|
activation: {
|
|
588
|
-
retrieval_strength: 0.5,
|
|
589
|
-
storage_strength: 0.3,
|
|
785
|
+
retrieval_strength: autoPromote ? 0.7 : 0.5,
|
|
786
|
+
storage_strength: autoPromote ? 1 : 0.3,
|
|
590
787
|
frequency: 0,
|
|
591
788
|
last_accessed: today
|
|
592
789
|
},
|
|
@@ -596,12 +793,21 @@ async function handleLearn(args2, engramsPath) {
|
|
|
596
793
|
};
|
|
597
794
|
engrams.push(engram);
|
|
598
795
|
saveEngrams(engramsPath, engrams);
|
|
599
|
-
|
|
796
|
+
const statusLabel = autoPromote ? "active" : "candidate";
|
|
797
|
+
const hints = autoPromote ? buildHints({
|
|
798
|
+
next: "Created as active (auto_promote on). Use datacore.inject to retrieve.",
|
|
799
|
+
related: ["datacore.inject"],
|
|
800
|
+
warning: "Auto-promotion enabled. Engrams are immediately active without review."
|
|
801
|
+
}) : buildHints({
|
|
802
|
+
next: "Created as candidate. Use datacore.promote to activate.",
|
|
803
|
+
related: ["datacore.promote", "datacore.inject"]
|
|
804
|
+
});
|
|
805
|
+
return { success: true, engram, _hints: hints };
|
|
600
806
|
}
|
|
601
807
|
|
|
602
808
|
// src/tools/inject-tool.ts
|
|
603
|
-
import * as
|
|
604
|
-
import * as
|
|
809
|
+
import * as fs5 from "fs";
|
|
810
|
+
import * as yaml3 from "js-yaml";
|
|
605
811
|
|
|
606
812
|
// src/decay.ts
|
|
607
813
|
var DECAY_RATE = 0.05;
|
|
@@ -747,7 +953,17 @@ async function handleInject(args2, paths) {
|
|
|
747
953
|
personalEngrams,
|
|
748
954
|
[...result.directives, ...result.consider]
|
|
749
955
|
);
|
|
750
|
-
|
|
956
|
+
const injectedIds = [...result.directives, ...result.consider].filter((e) => !e.pack).map((e) => e.id);
|
|
957
|
+
const idsList = injectedIds.length > 0 ? ` Injected IDs: ${injectedIds.join(", ")}` : "";
|
|
958
|
+
return {
|
|
959
|
+
text: lines.join("\n"),
|
|
960
|
+
count: totalCount,
|
|
961
|
+
tokens_used: result.tokens_used,
|
|
962
|
+
_hints: buildHints({
|
|
963
|
+
next: `After task, call datacore.feedback on helpful/unhelpful engrams.${idsList}`,
|
|
964
|
+
related: ["datacore.feedback", "datacore.session.end"]
|
|
965
|
+
})
|
|
966
|
+
};
|
|
751
967
|
}
|
|
752
968
|
function updateUsageTracking(engramsPath, allPersonal, selected) {
|
|
753
969
|
const selectedPersonalIds = new Set(
|
|
@@ -768,10 +984,10 @@ function updateUsageTracking(engramsPath, allPersonal, selected) {
|
|
|
768
984
|
}
|
|
769
985
|
}
|
|
770
986
|
function atomicWriteYaml(filePath, data) {
|
|
771
|
-
const content =
|
|
987
|
+
const content = yaml3.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
772
988
|
const tmpPath = filePath + ".tmp." + process.pid;
|
|
773
|
-
|
|
774
|
-
|
|
989
|
+
fs5.writeFileSync(tmpPath, content);
|
|
990
|
+
fs5.renameSync(tmpPath, filePath);
|
|
775
991
|
}
|
|
776
992
|
function formatEngram(engram, totalCount) {
|
|
777
993
|
if (totalCount < 10) {
|
|
@@ -792,15 +1008,15 @@ function formatEngram(engram, totalCount) {
|
|
|
792
1008
|
}
|
|
793
1009
|
|
|
794
1010
|
// src/tools/search.ts
|
|
795
|
-
import * as
|
|
796
|
-
import * as
|
|
1011
|
+
import * as fs6 from "fs";
|
|
1012
|
+
import * as path4 from "path";
|
|
797
1013
|
var CONTENT_CACHE_MAX = 500;
|
|
798
1014
|
var contentCache = /* @__PURE__ */ new Map();
|
|
799
1015
|
function getCachedContent(filePath) {
|
|
800
1016
|
const entry = contentCache.get(filePath);
|
|
801
1017
|
if (!entry) return null;
|
|
802
1018
|
try {
|
|
803
|
-
const stat =
|
|
1019
|
+
const stat = fs6.statSync(filePath);
|
|
804
1020
|
if (stat.mtimeMs === entry.mtime) return entry.content;
|
|
805
1021
|
} catch {
|
|
806
1022
|
}
|
|
@@ -809,7 +1025,7 @@ function getCachedContent(filePath) {
|
|
|
809
1025
|
}
|
|
810
1026
|
function setCachedContent(filePath, content) {
|
|
811
1027
|
try {
|
|
812
|
-
const mtime =
|
|
1028
|
+
const mtime = fs6.statSync(filePath).mtimeMs;
|
|
813
1029
|
if (contentCache.size >= CONTENT_CACHE_MAX) {
|
|
814
1030
|
const firstKey = contentCache.keys().next().value;
|
|
815
1031
|
if (firstKey) contentCache.delete(firstKey);
|
|
@@ -846,13 +1062,13 @@ async function keywordSearch(args2, paths) {
|
|
|
846
1062
|
return { results: results.slice(0, limit), method: "keyword" };
|
|
847
1063
|
}
|
|
848
1064
|
function searchDir(dirPath, query) {
|
|
849
|
-
if (!
|
|
1065
|
+
if (!fs6.existsSync(dirPath)) return [];
|
|
850
1066
|
const results = [];
|
|
851
1067
|
const queryLower = query.toLowerCase();
|
|
852
1068
|
for (const file of walkDir(dirPath)) {
|
|
853
1069
|
if (!file.endsWith(".md")) continue;
|
|
854
1070
|
const content = getCachedContent(file) ?? (() => {
|
|
855
|
-
const c =
|
|
1071
|
+
const c = fs6.readFileSync(file, "utf8");
|
|
856
1072
|
setCachedContent(file, c);
|
|
857
1073
|
return c;
|
|
858
1074
|
})();
|
|
@@ -860,14 +1076,16 @@ function searchDir(dirPath, query) {
|
|
|
860
1076
|
const occurrences = countOccurrences(contentLower, queryLower);
|
|
861
1077
|
if (occurrences === 0) continue;
|
|
862
1078
|
const snippet = extractSnippet(content, query);
|
|
863
|
-
|
|
1079
|
+
const title = extractTitle(content, file);
|
|
1080
|
+
const date = extractDate(file);
|
|
1081
|
+
results.push({ path: file, snippet, score: occurrences, title, date });
|
|
864
1082
|
}
|
|
865
1083
|
return results;
|
|
866
1084
|
}
|
|
867
1085
|
function walkDir(dir) {
|
|
868
1086
|
const files = [];
|
|
869
|
-
for (const entry of
|
|
870
|
-
const fullPath =
|
|
1087
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
1088
|
+
const fullPath = path4.join(dir, entry.name);
|
|
871
1089
|
if (entry.isDirectory()) files.push(...walkDir(fullPath));
|
|
872
1090
|
else files.push(fullPath);
|
|
873
1091
|
}
|
|
@@ -883,16 +1101,29 @@ function countOccurrences(text, query) {
|
|
|
883
1101
|
return count;
|
|
884
1102
|
}
|
|
885
1103
|
function extractSnippet(content, query) {
|
|
1104
|
+
const snippetLength = getConfig().search.snippet_length;
|
|
1105
|
+
if (content.length < 2e3) return content;
|
|
886
1106
|
const idx = content.toLowerCase().indexOf(query.toLowerCase());
|
|
887
|
-
if (idx === -1) return content.slice(0,
|
|
888
|
-
const
|
|
889
|
-
const
|
|
1107
|
+
if (idx === -1) return content.slice(0, snippetLength);
|
|
1108
|
+
const half = Math.floor(snippetLength / 2);
|
|
1109
|
+
const start2 = Math.max(0, idx - half);
|
|
1110
|
+
const end = Math.min(content.length, idx + query.length + half);
|
|
890
1111
|
return (start2 > 0 ? "..." : "") + content.slice(start2, end) + (end < content.length ? "..." : "");
|
|
891
1112
|
}
|
|
1113
|
+
function extractTitle(content, filePath) {
|
|
1114
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
1115
|
+
if (match) return match[1].trim();
|
|
1116
|
+
return path4.basename(filePath, path4.extname(filePath));
|
|
1117
|
+
}
|
|
1118
|
+
function extractDate(filePath) {
|
|
1119
|
+
const match = path4.basename(filePath).match(/^(\d{4}-\d{2}-\d{2})/);
|
|
1120
|
+
if (match) return match[1];
|
|
1121
|
+
return void 0;
|
|
1122
|
+
}
|
|
892
1123
|
|
|
893
1124
|
// src/tools/ingest.ts
|
|
894
|
-
import * as
|
|
895
|
-
import * as
|
|
1125
|
+
import * as fs7 from "fs";
|
|
1126
|
+
import * as path5 from "path";
|
|
896
1127
|
async function handleIngest(args2, paths) {
|
|
897
1128
|
const contentError = validateContent(args2.content);
|
|
898
1129
|
if (contentError) return { success: false, error: contentError };
|
|
@@ -903,8 +1134,8 @@ async function handleIngest(args2, paths) {
|
|
|
903
1134
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
904
1135
|
const slug = (args2.title ?? "ingested").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
905
1136
|
const fileName = `${timestamp}-${slug}.md`;
|
|
906
|
-
const filePath =
|
|
907
|
-
|
|
1137
|
+
const filePath = path5.join(paths.knowledgePath, fileName);
|
|
1138
|
+
fs7.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
908
1139
|
const frontmatter = `---
|
|
909
1140
|
title: "${args2.title ?? "Ingested Note"}"
|
|
910
1141
|
created: "${(/* @__PURE__ */ new Date()).toISOString()}"
|
|
@@ -915,13 +1146,17 @@ type: ingested
|
|
|
915
1146
|
const tagLine = args2.tags?.length ? `
|
|
916
1147
|
${args2.tags.map((t) => `#${t}`).join(" ")}
|
|
917
1148
|
` : "";
|
|
918
|
-
|
|
1149
|
+
fs7.writeFileSync(filePath, `${frontmatter}${args2.content}
|
|
919
1150
|
${tagLine}`);
|
|
920
1151
|
const suggestions = extractEngramSuggestions(args2.content);
|
|
921
1152
|
return {
|
|
922
1153
|
success: true,
|
|
923
1154
|
note_path: filePath,
|
|
924
|
-
engram_suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
1155
|
+
engram_suggestions: suggestions.length > 0 ? suggestions : void 0,
|
|
1156
|
+
_hints: suggestions.length > 0 ? buildHints({
|
|
1157
|
+
next: `Call datacore.learn for each suggestion to create engrams. Example: datacore.learn({statement: '${suggestions[0]}', type: 'behavioral'})`,
|
|
1158
|
+
related: ["datacore.learn"]
|
|
1159
|
+
}) : void 0
|
|
925
1160
|
};
|
|
926
1161
|
}
|
|
927
1162
|
function extractEngramSuggestions(content) {
|
|
@@ -945,21 +1180,21 @@ function extractEngramSuggestions(content) {
|
|
|
945
1180
|
}
|
|
946
1181
|
|
|
947
1182
|
// src/tools/status.ts
|
|
948
|
-
import * as
|
|
949
|
-
import * as
|
|
1183
|
+
import * as fs9 from "fs";
|
|
1184
|
+
import * as path7 from "path";
|
|
950
1185
|
|
|
951
1186
|
// src/trust.ts
|
|
952
|
-
import * as
|
|
953
|
-
import * as
|
|
1187
|
+
import * as fs8 from "fs";
|
|
1188
|
+
import * as path6 from "path";
|
|
954
1189
|
import * as crypto from "crypto";
|
|
955
1190
|
function computePackChecksum(packDir) {
|
|
956
1191
|
const files = ["SKILL.md", "engrams.yaml"];
|
|
957
1192
|
const hash = crypto.createHash("sha256");
|
|
958
1193
|
let hasContent = false;
|
|
959
1194
|
for (const file of files) {
|
|
960
|
-
const filePath =
|
|
961
|
-
if (
|
|
962
|
-
hash.update(
|
|
1195
|
+
const filePath = path6.join(packDir, file);
|
|
1196
|
+
if (fs8.existsSync(filePath)) {
|
|
1197
|
+
hash.update(fs8.readFileSync(filePath));
|
|
963
1198
|
hasContent = true;
|
|
964
1199
|
}
|
|
965
1200
|
}
|
|
@@ -1029,12 +1264,31 @@ async function handleStatus(paths, updateAvailable2) {
|
|
|
1029
1264
|
const packIntegrity = [];
|
|
1030
1265
|
for (const regPack of packs_default.packs) {
|
|
1031
1266
|
if (!regPack.checksum) continue;
|
|
1032
|
-
const packDir =
|
|
1033
|
-
if (!
|
|
1034
|
-
const
|
|
1035
|
-
packIntegrity.push({ name: regPack.id, valid:
|
|
1267
|
+
const packDir = path7.join(paths.packsPath, regPack.id);
|
|
1268
|
+
if (!fs9.existsSync(packDir)) continue;
|
|
1269
|
+
const result = verifyPackChecksum(packDir, regPack.checksum);
|
|
1270
|
+
packIntegrity.push({ name: regPack.id, valid: result.valid });
|
|
1271
|
+
}
|
|
1272
|
+
const recommendations = [];
|
|
1273
|
+
const candidateCount = engrams.filter((e) => e.status === "candidate").length;
|
|
1274
|
+
if (healthCounts.retirement_candidate > 0) {
|
|
1275
|
+
recommendations.push(`${healthCounts.retirement_candidate} engrams are retirement candidates. Use datacore.forget to clean up.`);
|
|
1036
1276
|
}
|
|
1037
|
-
|
|
1277
|
+
if (healthCounts.fading > 0) {
|
|
1278
|
+
recommendations.push(`${healthCounts.fading} engrams are fading. Use datacore.feedback with positive signal to reinforce.`);
|
|
1279
|
+
}
|
|
1280
|
+
if (candidateCount > 0) {
|
|
1281
|
+
recommendations.push(`${candidateCount} candidate engrams awaiting review. Use datacore.promote or enable engrams.auto_promote in config.yaml.`);
|
|
1282
|
+
}
|
|
1283
|
+
const { date: today } = localDate();
|
|
1284
|
+
const todayJournal = path7.join(paths.journalPath, `${today}.md`);
|
|
1285
|
+
if (!fs9.existsSync(todayJournal)) {
|
|
1286
|
+
recommendations.push("No journal entry today. Use datacore.capture to start one.");
|
|
1287
|
+
}
|
|
1288
|
+
if (updateAvailable2) {
|
|
1289
|
+
recommendations.push(`Update available: ${updateAvailable2}. Run: npm update -g @datacore-one/mcp`);
|
|
1290
|
+
}
|
|
1291
|
+
const statusResult = {
|
|
1038
1292
|
version: currentVersion,
|
|
1039
1293
|
mode: paths.mode,
|
|
1040
1294
|
engrams: engrams.length,
|
|
@@ -1042,42 +1296,47 @@ async function handleStatus(paths, updateAvailable2) {
|
|
|
1042
1296
|
packs: packsCount,
|
|
1043
1297
|
pack_integrity: packIntegrity.length > 0 ? packIntegrity : void 0,
|
|
1044
1298
|
journal_entries: journalCount,
|
|
1045
|
-
knowledge_notes: knowledgeCount
|
|
1299
|
+
knowledge_notes: knowledgeCount,
|
|
1300
|
+
_recommendations: recommendations.length > 0 ? recommendations : void 0,
|
|
1301
|
+
_hints: buildHints({
|
|
1302
|
+
next: recommendations.length > 0 ? recommendations[0] : "System healthy. Use datacore.session.start to begin working.",
|
|
1303
|
+
related: ["datacore.promote", "datacore.forget"]
|
|
1304
|
+
})
|
|
1046
1305
|
};
|
|
1047
1306
|
if (engrams.length >= 500) {
|
|
1048
|
-
|
|
1307
|
+
statusResult.scaling_hint = `You have ${engrams.length} engrams. Consider migrating to full Datacore for SQLite-backed search.`;
|
|
1049
1308
|
}
|
|
1050
1309
|
if (updateAvailable2) {
|
|
1051
|
-
|
|
1310
|
+
statusResult.update_available = updateAvailable2;
|
|
1052
1311
|
}
|
|
1053
|
-
return
|
|
1312
|
+
return statusResult;
|
|
1054
1313
|
}
|
|
1055
1314
|
function countFiles(dir, ext) {
|
|
1056
|
-
if (!
|
|
1315
|
+
if (!fs9.existsSync(dir)) return 0;
|
|
1057
1316
|
let count = 0;
|
|
1058
|
-
for (const entry of
|
|
1059
|
-
const fullPath =
|
|
1317
|
+
for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
|
|
1318
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1060
1319
|
if (entry.isDirectory()) count += countFiles(fullPath, ext);
|
|
1061
1320
|
else if (entry.name.endsWith(ext)) count++;
|
|
1062
1321
|
}
|
|
1063
1322
|
return count;
|
|
1064
1323
|
}
|
|
1065
1324
|
function countDirs(dir) {
|
|
1066
|
-
if (!
|
|
1067
|
-
return
|
|
1325
|
+
if (!fs9.existsSync(dir)) return 0;
|
|
1326
|
+
return fs9.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
|
|
1068
1327
|
}
|
|
1069
1328
|
|
|
1070
1329
|
// src/tools/discover.ts
|
|
1071
|
-
import * as
|
|
1072
|
-
import * as
|
|
1330
|
+
import * as fs10 from "fs";
|
|
1331
|
+
import * as path8 from "path";
|
|
1073
1332
|
function handleDiscover(args2, packsDir) {
|
|
1074
1333
|
let packs = packs_default.packs.map((p) => {
|
|
1075
|
-
const localDir =
|
|
1076
|
-
const installed =
|
|
1334
|
+
const localDir = path8.join(packsDir, p.id);
|
|
1335
|
+
const installed = fs10.existsSync(path8.join(localDir, "SKILL.md"));
|
|
1077
1336
|
let installedVersion;
|
|
1078
1337
|
if (installed) {
|
|
1079
1338
|
try {
|
|
1080
|
-
const content =
|
|
1339
|
+
const content = fs10.readFileSync(path8.join(localDir, "SKILL.md"), "utf8");
|
|
1081
1340
|
const match = content.match(/version:\s*["']?([^"'\n]+)/);
|
|
1082
1341
|
installedVersion = match?.[1];
|
|
1083
1342
|
} catch {
|
|
@@ -1101,13 +1360,21 @@ function handleDiscover(args2, packsDir) {
|
|
|
1101
1360
|
const filterTags = new Set(args2.tags.map((t) => t.toLowerCase()));
|
|
1102
1361
|
packs = packs.filter((p) => p.tags.some((t) => filterTags.has(t.toLowerCase())));
|
|
1103
1362
|
}
|
|
1104
|
-
|
|
1363
|
+
const trusted = new Set(getConfig().packs.trusted_publishers);
|
|
1364
|
+
const result = { packs };
|
|
1365
|
+
if (trusted.size > 0) {
|
|
1366
|
+
const autoInstallable = packs.filter((p) => trusted.has(p.author) && !p.installed && p.can_install).map((p) => p.id);
|
|
1367
|
+
const autoUpgradeable = packs.filter((p) => trusted.has(p.author) && p.upgradeable).map((p) => p.id);
|
|
1368
|
+
if (autoInstallable.length > 0) result.auto_installable = autoInstallable;
|
|
1369
|
+
if (autoUpgradeable.length > 0) result.auto_upgradeable = autoUpgradeable;
|
|
1370
|
+
}
|
|
1371
|
+
return result;
|
|
1105
1372
|
}
|
|
1106
1373
|
|
|
1107
1374
|
// src/tools/install.ts
|
|
1108
|
-
import * as
|
|
1109
|
-
import * as
|
|
1110
|
-
import * as
|
|
1375
|
+
import * as fs11 from "fs";
|
|
1376
|
+
import * as path9 from "path";
|
|
1377
|
+
import * as yaml4 from "js-yaml";
|
|
1111
1378
|
import * as os2 from "os";
|
|
1112
1379
|
import { execSync } from "child_process";
|
|
1113
1380
|
async function handleInstall(args2, packsDir) {
|
|
@@ -1117,34 +1384,34 @@ async function handleInstall(args2, packsDir) {
|
|
|
1117
1384
|
if (downloaded.error) return { success: false, error: downloaded.error };
|
|
1118
1385
|
srcDir = downloaded.path;
|
|
1119
1386
|
}
|
|
1120
|
-
const skillPath =
|
|
1121
|
-
if (!
|
|
1387
|
+
const skillPath = path9.join(srcDir, "SKILL.md");
|
|
1388
|
+
if (!fs11.existsSync(skillPath)) {
|
|
1122
1389
|
return { success: false, error: "No SKILL.md found in source directory" };
|
|
1123
1390
|
}
|
|
1124
|
-
const skillContent =
|
|
1391
|
+
const skillContent = fs11.readFileSync(skillPath, "utf8");
|
|
1125
1392
|
const frontmatterMatch = skillContent.match(/^---\n([\s\S]*?)\n---/);
|
|
1126
1393
|
if (!frontmatterMatch) {
|
|
1127
1394
|
return { success: false, error: "No YAML frontmatter in SKILL.md" };
|
|
1128
1395
|
}
|
|
1129
|
-
const manifest =
|
|
1396
|
+
const manifest = yaml4.load(frontmatterMatch[1]);
|
|
1130
1397
|
const packId = manifest?.["x-datacore"]?.id;
|
|
1131
1398
|
const newVersion = manifest?.version;
|
|
1132
1399
|
if (!packId) {
|
|
1133
1400
|
return { success: false, error: "Missing x-datacore.id in SKILL.md frontmatter" };
|
|
1134
1401
|
}
|
|
1135
|
-
const destDir =
|
|
1136
|
-
if (
|
|
1137
|
-
const existingContent =
|
|
1402
|
+
const destDir = path9.join(packsDir, packId);
|
|
1403
|
+
if (fs11.existsSync(path9.join(destDir, "SKILL.md"))) {
|
|
1404
|
+
const existingContent = fs11.readFileSync(path9.join(destDir, "SKILL.md"), "utf8");
|
|
1138
1405
|
const existingMatch = existingContent.match(/version:\s*["']?([^"'\n]+)/);
|
|
1139
1406
|
const existingVersion = existingMatch?.[1];
|
|
1140
1407
|
if (existingVersion === newVersion) {
|
|
1141
1408
|
return { success: true, pack_id: packId, already_current: true };
|
|
1142
1409
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1410
|
+
fs11.rmSync(destDir, { recursive: true, force: true });
|
|
1411
|
+
fs11.cpSync(srcDir, destDir, { recursive: true });
|
|
1145
1412
|
return { success: true, pack_id: packId, upgraded: true };
|
|
1146
1413
|
}
|
|
1147
|
-
|
|
1414
|
+
fs11.cpSync(srcDir, destDir, { recursive: true });
|
|
1148
1415
|
const checksumVerified = verifyInstalledChecksum(packId, destDir);
|
|
1149
1416
|
return { success: true, pack_id: packId, checksum_verified: checksumVerified ?? void 0 };
|
|
1150
1417
|
}
|
|
@@ -1155,15 +1422,15 @@ function verifyInstalledChecksum(packId, destDir) {
|
|
|
1155
1422
|
return result.valid;
|
|
1156
1423
|
}
|
|
1157
1424
|
async function downloadPack(url) {
|
|
1158
|
-
const tmpDir =
|
|
1425
|
+
const tmpDir = fs11.mkdtempSync(path9.join(os2.tmpdir(), "datacore-pack-"));
|
|
1159
1426
|
try {
|
|
1160
1427
|
const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
1161
1428
|
if (!res.ok) return { error: `Download failed: HTTP ${res.status}` };
|
|
1162
1429
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
1163
|
-
const archivePath =
|
|
1164
|
-
|
|
1165
|
-
const extractDir =
|
|
1166
|
-
|
|
1430
|
+
const archivePath = path9.join(tmpDir, "pack.tar.gz");
|
|
1431
|
+
fs11.writeFileSync(archivePath, buffer);
|
|
1432
|
+
const extractDir = path9.join(tmpDir, "extracted");
|
|
1433
|
+
fs11.mkdirSync(extractDir);
|
|
1167
1434
|
execSync(`tar xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(extractDir)}`, { timeout: 1e4 });
|
|
1168
1435
|
const packRoot = findPackRoot(extractDir);
|
|
1169
1436
|
if (!packRoot) return { error: "Downloaded archive does not contain SKILL.md" };
|
|
@@ -1173,10 +1440,10 @@ async function downloadPack(url) {
|
|
|
1173
1440
|
}
|
|
1174
1441
|
}
|
|
1175
1442
|
function findPackRoot(dir) {
|
|
1176
|
-
if (
|
|
1177
|
-
for (const entry of
|
|
1443
|
+
if (fs11.existsSync(path9.join(dir, "SKILL.md"))) return dir;
|
|
1444
|
+
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
1178
1445
|
if (entry.isDirectory()) {
|
|
1179
|
-
const found = findPackRoot(
|
|
1446
|
+
const found = findPackRoot(path9.join(dir, entry.name));
|
|
1180
1447
|
if (found) return found;
|
|
1181
1448
|
}
|
|
1182
1449
|
}
|
|
@@ -1184,9 +1451,9 @@ function findPackRoot(dir) {
|
|
|
1184
1451
|
}
|
|
1185
1452
|
|
|
1186
1453
|
// src/tools/export.ts
|
|
1187
|
-
import * as
|
|
1188
|
-
import * as
|
|
1189
|
-
import * as
|
|
1454
|
+
import * as fs12 from "fs";
|
|
1455
|
+
import * as path10 from "path";
|
|
1456
|
+
import * as yaml5 from "js-yaml";
|
|
1190
1457
|
async function handleExport(args2, paths) {
|
|
1191
1458
|
const allEngrams = loadEngrams(paths.engramsPath);
|
|
1192
1459
|
let selected = allEngrams.filter((e) => e.status === "active");
|
|
@@ -1218,7 +1485,7 @@ async function handleExport(args2, paths) {
|
|
|
1218
1485
|
return { success: false, error: "No engrams match the filter criteria" };
|
|
1219
1486
|
}
|
|
1220
1487
|
const packId = args2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
1221
|
-
const packDir =
|
|
1488
|
+
const packDir = path10.join(paths.packsPath, packId);
|
|
1222
1489
|
if (!args2.confirm) {
|
|
1223
1490
|
return {
|
|
1224
1491
|
success: true,
|
|
@@ -1229,7 +1496,7 @@ async function handleExport(args2, paths) {
|
|
|
1229
1496
|
}
|
|
1230
1497
|
};
|
|
1231
1498
|
}
|
|
1232
|
-
|
|
1499
|
+
fs12.mkdirSync(packDir, { recursive: true });
|
|
1233
1500
|
const skillContent = `---
|
|
1234
1501
|
name: "${args2.name}"
|
|
1235
1502
|
description: "${args2.description}"
|
|
@@ -1248,7 +1515,7 @@ ${args2.description}
|
|
|
1248
1515
|
|
|
1249
1516
|
Exported ${selected.length} engrams.
|
|
1250
1517
|
`;
|
|
1251
|
-
|
|
1518
|
+
fs12.writeFileSync(path10.join(packDir, "SKILL.md"), skillContent);
|
|
1252
1519
|
const exportEngrams = selected.map((e) => ({
|
|
1253
1520
|
id: e.id,
|
|
1254
1521
|
version: e.version,
|
|
@@ -1268,27 +1535,27 @@ Exported ${selected.length} engrams.
|
|
|
1268
1535
|
},
|
|
1269
1536
|
feedback_signals: { positive: 0, negative: 0 }
|
|
1270
1537
|
}));
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1538
|
+
fs12.writeFileSync(
|
|
1539
|
+
path10.join(packDir, "engrams.yaml"),
|
|
1540
|
+
yaml5.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
|
|
1274
1541
|
);
|
|
1275
1542
|
return { success: true, pack_path: packDir };
|
|
1276
1543
|
}
|
|
1277
1544
|
|
|
1278
1545
|
// src/modules.ts
|
|
1279
|
-
import * as
|
|
1280
|
-
import * as
|
|
1281
|
-
import * as
|
|
1546
|
+
import * as fs13 from "fs";
|
|
1547
|
+
import * as path11 from "path";
|
|
1548
|
+
import * as yaml6 from "js-yaml";
|
|
1282
1549
|
function discoverModules(storage2) {
|
|
1283
1550
|
const modules = [];
|
|
1284
1551
|
if (storage2.mode !== "full") return modules;
|
|
1285
|
-
const globalModulesDir =
|
|
1552
|
+
const globalModulesDir = path11.join(storage2.basePath, ".datacore", "modules");
|
|
1286
1553
|
modules.push(...scanModulesDir(globalModulesDir, "global"));
|
|
1287
1554
|
try {
|
|
1288
|
-
const entries =
|
|
1555
|
+
const entries = fs13.readdirSync(storage2.basePath);
|
|
1289
1556
|
for (const entry of entries) {
|
|
1290
1557
|
if (/^\d+-/.test(entry)) {
|
|
1291
|
-
const spaceModulesDir =
|
|
1558
|
+
const spaceModulesDir = path11.join(storage2.basePath, entry, ".datacore", "modules");
|
|
1292
1559
|
modules.push(...scanModulesDir(spaceModulesDir, "space", entry));
|
|
1293
1560
|
}
|
|
1294
1561
|
}
|
|
@@ -1298,16 +1565,16 @@ function discoverModules(storage2) {
|
|
|
1298
1565
|
}
|
|
1299
1566
|
function scanModulesDir(modulesDir, scope, spaceName) {
|
|
1300
1567
|
const modules = [];
|
|
1301
|
-
if (!
|
|
1568
|
+
if (!fs13.existsSync(modulesDir)) return modules;
|
|
1302
1569
|
try {
|
|
1303
|
-
const entries =
|
|
1570
|
+
const entries = fs13.readdirSync(modulesDir);
|
|
1304
1571
|
for (const entry of entries) {
|
|
1305
|
-
const modulePath =
|
|
1306
|
-
const manifestPath =
|
|
1307
|
-
if (!
|
|
1572
|
+
const modulePath = path11.join(modulesDir, entry);
|
|
1573
|
+
const manifestPath = path11.join(modulePath, "module.yaml");
|
|
1574
|
+
if (!fs13.existsSync(manifestPath)) continue;
|
|
1308
1575
|
try {
|
|
1309
|
-
const raw =
|
|
1310
|
-
const manifest =
|
|
1576
|
+
const raw = fs13.readFileSync(manifestPath, "utf-8");
|
|
1577
|
+
const manifest = yaml6.load(raw);
|
|
1311
1578
|
if (!manifest || !manifest.name) continue;
|
|
1312
1579
|
modules.push({
|
|
1313
1580
|
name: manifest.name,
|
|
@@ -1328,12 +1595,12 @@ async function loadModuleTools(modules, storage2) {
|
|
|
1328
1595
|
for (const mod of modules) {
|
|
1329
1596
|
const declaredTools = mod.manifest.provides?.tools;
|
|
1330
1597
|
if (!declaredTools || declaredTools.length === 0) continue;
|
|
1331
|
-
const toolsIndexPath =
|
|
1332
|
-
if (!
|
|
1598
|
+
const toolsIndexPath = path11.join(mod.modulePath, "tools", "index.js");
|
|
1599
|
+
if (!fs13.existsSync(toolsIndexPath)) continue;
|
|
1333
1600
|
try {
|
|
1334
1601
|
const toolsModule = await import(toolsIndexPath);
|
|
1335
1602
|
const moduleTools2 = toolsModule.tools || toolsModule.default?.tools || [];
|
|
1336
|
-
const dataPath = mod.scope === "space" && mod.spaceName ?
|
|
1603
|
+
const dataPath = mod.scope === "space" && mod.spaceName ? path11.join(storage2.basePath, mod.spaceName, ".datacore", "modules", mod.name, "data") : path11.join(storage2.basePath, "0-personal", ".datacore", "modules", mod.name, "data");
|
|
1337
1604
|
const context = {
|
|
1338
1605
|
storage: storage2,
|
|
1339
1606
|
modulePath: mod.modulePath,
|
|
@@ -1429,8 +1696,8 @@ async function handleModulesInfo(args2, storage2, cachedModules) {
|
|
|
1429
1696
|
}
|
|
1430
1697
|
|
|
1431
1698
|
// src/tools/modules-health.ts
|
|
1432
|
-
import * as
|
|
1433
|
-
import * as
|
|
1699
|
+
import * as fs14 from "fs";
|
|
1700
|
+
import * as path12 from "path";
|
|
1434
1701
|
async function handleModulesHealth(args2, storage2, cachedModules) {
|
|
1435
1702
|
const modules = cachedModules ?? discoverModules(storage2);
|
|
1436
1703
|
if (args2.module) {
|
|
@@ -1452,10 +1719,10 @@ async function handleModulesHealth(args2, storage2, cachedModules) {
|
|
|
1452
1719
|
async function checkModule(mod, storage2) {
|
|
1453
1720
|
const issues = [];
|
|
1454
1721
|
const manifest = mod.manifest;
|
|
1455
|
-
if (!
|
|
1722
|
+
if (!fs14.existsSync(path12.join(mod.modulePath, "SKILL.md"))) {
|
|
1456
1723
|
issues.push("Missing SKILL.md (ecosystem entry point)");
|
|
1457
1724
|
}
|
|
1458
|
-
if (!
|
|
1725
|
+
if (!fs14.existsSync(path12.join(mod.modulePath, "CLAUDE.base.md"))) {
|
|
1459
1726
|
issues.push("Missing CLAUDE.base.md (AI context)");
|
|
1460
1727
|
}
|
|
1461
1728
|
if (!manifest.manifest_version || manifest.manifest_version < 2) {
|
|
@@ -1471,8 +1738,8 @@ async function checkModule(mod, storage2) {
|
|
|
1471
1738
|
const provides = manifest.provides;
|
|
1472
1739
|
const declaredTools = provides?.tools || [];
|
|
1473
1740
|
if (declaredTools.length > 0) {
|
|
1474
|
-
const toolsIndex =
|
|
1475
|
-
if (!
|
|
1741
|
+
const toolsIndex = path12.join(mod.modulePath, "tools", "index.js");
|
|
1742
|
+
if (!fs14.existsSync(toolsIndex)) {
|
|
1476
1743
|
issues.push(`Declares ${declaredTools.length} tools but tools/index.js not found`);
|
|
1477
1744
|
} else {
|
|
1478
1745
|
try {
|
|
@@ -1491,13 +1758,13 @@ async function checkModule(mod, storage2) {
|
|
|
1491
1758
|
const suspectExts = [".db", ".sqlite", ".json"];
|
|
1492
1759
|
const suspectDirs = ["output", "data", "state"];
|
|
1493
1760
|
for (const dir of suspectDirs) {
|
|
1494
|
-
const fullPath =
|
|
1495
|
-
if (
|
|
1761
|
+
const fullPath = path12.join(mod.modulePath, dir);
|
|
1762
|
+
if (fs14.existsSync(fullPath) && fs14.statSync(fullPath).isDirectory()) {
|
|
1496
1763
|
issues.push(`Data dir '${dir}/' found in module code (should be in space data path)`);
|
|
1497
1764
|
}
|
|
1498
1765
|
}
|
|
1499
1766
|
try {
|
|
1500
|
-
const entries =
|
|
1767
|
+
const entries = fs14.readdirSync(mod.modulePath);
|
|
1501
1768
|
for (const entry of entries) {
|
|
1502
1769
|
if (suspectExts.some((ext) => entry.endsWith(ext))) {
|
|
1503
1770
|
issues.push(`Data file '${entry}' found in module code dir`);
|
|
@@ -1554,27 +1821,291 @@ async function handleForget(args2, engramsPath) {
|
|
|
1554
1821
|
|
|
1555
1822
|
// src/tools/feedback.ts
|
|
1556
1823
|
async function handleFeedback(args2, engramsPath) {
|
|
1824
|
+
if (args2.signals && args2.signals.length > 0) {
|
|
1825
|
+
return handleBatchFeedback(args2.signals, engramsPath);
|
|
1826
|
+
}
|
|
1827
|
+
return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath);
|
|
1828
|
+
}
|
|
1829
|
+
async function handleSingleFeedback(engram_id, signal, comment, engramsPath) {
|
|
1557
1830
|
const engrams = loadEngrams(engramsPath);
|
|
1558
|
-
const engram = engrams.find((e) => e.id ===
|
|
1831
|
+
const engram = engrams.find((e) => e.id === engram_id);
|
|
1559
1832
|
if (!engram) {
|
|
1560
|
-
return {
|
|
1833
|
+
return {
|
|
1834
|
+
mode: "single",
|
|
1835
|
+
success: false,
|
|
1836
|
+
engram_id,
|
|
1837
|
+
signal,
|
|
1838
|
+
error: `Engram ${engram_id} not found`,
|
|
1839
|
+
_hints: buildHints({
|
|
1840
|
+
next: "Engram not found. Use datacore.search or datacore.status to find valid IDs.",
|
|
1841
|
+
related: ["datacore.search", "datacore.status"]
|
|
1842
|
+
})
|
|
1843
|
+
};
|
|
1561
1844
|
}
|
|
1562
1845
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1563
|
-
if (engram.activation.last_accessed === today) {
|
|
1564
|
-
}
|
|
1565
1846
|
if (!engram.feedback_signals) {
|
|
1566
1847
|
engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
|
|
1567
1848
|
}
|
|
1568
|
-
engram.feedback_signals[
|
|
1849
|
+
engram.feedback_signals[signal] += 1;
|
|
1569
1850
|
engram.activation.last_accessed = today;
|
|
1570
1851
|
atomicWriteYaml(engramsPath, { engrams });
|
|
1571
1852
|
return {
|
|
1853
|
+
mode: "single",
|
|
1572
1854
|
success: true,
|
|
1573
|
-
engram_id
|
|
1574
|
-
signal
|
|
1855
|
+
engram_id,
|
|
1856
|
+
signal,
|
|
1575
1857
|
feedback_signals: { ...engram.feedback_signals }
|
|
1576
1858
|
};
|
|
1577
1859
|
}
|
|
1860
|
+
async function handleBatchFeedback(signals, engramsPath) {
|
|
1861
|
+
const engrams = loadEngrams(engramsPath);
|
|
1862
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1863
|
+
const results = [];
|
|
1864
|
+
const summary = { positive: 0, negative: 0, neutral: 0 };
|
|
1865
|
+
let changed = false;
|
|
1866
|
+
for (const { engram_id, signal } of signals) {
|
|
1867
|
+
const engram = engrams.find((e) => e.id === engram_id);
|
|
1868
|
+
if (!engram) {
|
|
1869
|
+
results.push({ engram_id, signal, success: false, error: `Engram ${engram_id} not found` });
|
|
1870
|
+
continue;
|
|
1871
|
+
}
|
|
1872
|
+
if (!engram.feedback_signals) {
|
|
1873
|
+
engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
|
|
1874
|
+
}
|
|
1875
|
+
engram.feedback_signals[signal] += 1;
|
|
1876
|
+
engram.activation.last_accessed = today;
|
|
1877
|
+
summary[signal]++;
|
|
1878
|
+
changed = true;
|
|
1879
|
+
results.push({ engram_id, signal, success: true });
|
|
1880
|
+
}
|
|
1881
|
+
if (changed) {
|
|
1882
|
+
atomicWriteYaml(engramsPath, { engrams });
|
|
1883
|
+
}
|
|
1884
|
+
return {
|
|
1885
|
+
mode: "batch",
|
|
1886
|
+
results,
|
|
1887
|
+
summary,
|
|
1888
|
+
_hints: buildHints({
|
|
1889
|
+
next: `Batch feedback recorded: ${summary.positive} positive, ${summary.negative} negative, ${summary.neutral} neutral.`,
|
|
1890
|
+
related: ["datacore.session.end", "datacore.status"]
|
|
1891
|
+
})
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// src/tools/session-start.ts
|
|
1896
|
+
import * as fs15 from "fs";
|
|
1897
|
+
import * as path13 from "path";
|
|
1898
|
+
async function handleSessionStart(args2, storage2, bridge) {
|
|
1899
|
+
let engrams = null;
|
|
1900
|
+
if (args2.task) {
|
|
1901
|
+
const injectResult = await handleInject(
|
|
1902
|
+
{ prompt: args2.task, scope: args2.tags?.length ? `tags:${args2.tags.join(",")}` : void 0 },
|
|
1903
|
+
{ engramsPath: storage2.engramsPath, packsPath: storage2.packsPath }
|
|
1904
|
+
);
|
|
1905
|
+
if (injectResult.count > 0) {
|
|
1906
|
+
engrams = { text: injectResult.text, count: injectResult.count };
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
const { date: today } = localDate();
|
|
1910
|
+
const journalFile = path13.join(storage2.journalPath, `${today}.md`);
|
|
1911
|
+
const journal_today = fs15.existsSync(journalFile) ? fs15.readFileSync(journalFile, "utf8") : null;
|
|
1912
|
+
const allEngrams = loadEngrams(storage2.engramsPath);
|
|
1913
|
+
const pending_candidates = allEngrams.filter((e) => e.status === "candidate").length;
|
|
1914
|
+
const recommendations = [];
|
|
1915
|
+
if (pending_candidates > 0) {
|
|
1916
|
+
recommendations.push(`${pending_candidates} candidate engram(s) awaiting review. Use datacore.promote to activate.`);
|
|
1917
|
+
}
|
|
1918
|
+
if (!journal_today) {
|
|
1919
|
+
recommendations.push("No journal entry today. Use datacore.capture to start one.");
|
|
1920
|
+
}
|
|
1921
|
+
const hints = args2.task ? buildHints({
|
|
1922
|
+
next: "Work on your task. End with datacore.session.end.",
|
|
1923
|
+
related: ["datacore.session.end", "datacore.feedback"]
|
|
1924
|
+
}) : buildHints({
|
|
1925
|
+
next: "No task specified \u2014 showing journal and candidates only. Call datacore.inject when ready.",
|
|
1926
|
+
related: ["datacore.inject", "datacore.session.end"]
|
|
1927
|
+
});
|
|
1928
|
+
const activeCount = allEngrams.filter((e) => e.status === "active").length;
|
|
1929
|
+
const guide = activeCount === 0 ? SESSION_GUIDE_FULL : SESSION_GUIDE_SHORT;
|
|
1930
|
+
return { engrams, journal_today, pending_candidates, recommendations, guide, _hints: hints };
|
|
1931
|
+
}
|
|
1932
|
+
var SESSION_GUIDE_FULL = `## Datacore Quick Start
|
|
1933
|
+
|
|
1934
|
+
Datacore gives you persistent memory through **engrams** \u2014 knowledge that gets injected into context when relevant.
|
|
1935
|
+
|
|
1936
|
+
### Session Workflow
|
|
1937
|
+
1. **session.start** (you just called this) \u2014 get context
|
|
1938
|
+
2. Work on your task. Use **recall** to search everything, **search** for files.
|
|
1939
|
+
3. **feedback** \u2014 rate which injected engrams helped (strengthens useful ones)
|
|
1940
|
+
4. **session.end** \u2014 capture summary + suggest new engrams
|
|
1941
|
+
|
|
1942
|
+
### Key Tools
|
|
1943
|
+
- **learn** \u2014 record a reusable insight (creates candidate engram)
|
|
1944
|
+
- **promote** \u2014 activate candidate engrams so they appear in future sessions
|
|
1945
|
+
- **capture** \u2014 write a journal entry or knowledge note
|
|
1946
|
+
- **ingest** \u2014 import text and extract engram suggestions
|
|
1947
|
+
- **status** \u2014 system health and actionable recommendations
|
|
1948
|
+
- **forget** \u2014 retire an engram you no longer need
|
|
1949
|
+
|
|
1950
|
+
### How Engrams Work
|
|
1951
|
+
learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
|
|
1952
|
+
Positive feedback strengthens engrams. Unused ones naturally decay.`;
|
|
1953
|
+
var SESSION_GUIDE_SHORT = `Session started. Workflow: work \u2192 feedback \u2192 session.end.`;
|
|
1954
|
+
|
|
1955
|
+
// src/tools/session-end.ts
|
|
1956
|
+
async function handleSessionEnd(args2, storage2) {
|
|
1957
|
+
const captureResult = await handleCapture(
|
|
1958
|
+
{ type: "journal", content: args2.summary, tags: args2.tags },
|
|
1959
|
+
storage2
|
|
1960
|
+
);
|
|
1961
|
+
let engramsCreated = 0;
|
|
1962
|
+
if (args2.engram_suggestions?.length) {
|
|
1963
|
+
for (const suggestion of args2.engram_suggestions) {
|
|
1964
|
+
await handleLearn(
|
|
1965
|
+
{ statement: suggestion.statement, type: suggestion.type },
|
|
1966
|
+
storage2.engramsPath
|
|
1967
|
+
);
|
|
1968
|
+
engramsCreated++;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
const autoPromote = getConfig().engrams.auto_promote;
|
|
1972
|
+
const statusLabel = autoPromote ? "active" : "candidates";
|
|
1973
|
+
return {
|
|
1974
|
+
journal_path: captureResult.path ?? null,
|
|
1975
|
+
engrams_created: engramsCreated,
|
|
1976
|
+
_hints: buildHints({
|
|
1977
|
+
next: engramsCreated > 0 ? `Session captured. ${engramsCreated} engram(s) created as ${statusLabel}.` : "Session captured.",
|
|
1978
|
+
related: ["datacore.session.start", "datacore.status"]
|
|
1979
|
+
})
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// src/tools/recall.ts
|
|
1984
|
+
async function handleRecall(args2, storage2, bridge) {
|
|
1985
|
+
const sources = args2.sources ?? ["engrams", "journal", "knowledge"];
|
|
1986
|
+
const limit = args2.limit ?? 10;
|
|
1987
|
+
const result = {};
|
|
1988
|
+
let fallbackWarning;
|
|
1989
|
+
if (sources.includes("engrams")) {
|
|
1990
|
+
const engrams = loadEngrams(storage2.engramsPath);
|
|
1991
|
+
const topicWords = args2.topic.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
1992
|
+
const scored = [];
|
|
1993
|
+
for (const e of engrams) {
|
|
1994
|
+
if (e.status === "retired") continue;
|
|
1995
|
+
const text = `${e.statement} ${e.tags.join(" ")}`.toLowerCase();
|
|
1996
|
+
let score = 0;
|
|
1997
|
+
for (const word of topicWords) {
|
|
1998
|
+
if (text.includes(word)) score++;
|
|
1999
|
+
}
|
|
2000
|
+
if (score > 0) {
|
|
2001
|
+
scored.push({ id: e.id, statement: e.statement, score });
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
scored.sort((a, b) => b.score - a.score);
|
|
2005
|
+
const engramResults = scored.slice(0, limit);
|
|
2006
|
+
if (engramResults.length > 0) {
|
|
2007
|
+
result.engrams = engramResults;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
if (sources.includes("journal")) {
|
|
2011
|
+
const searchResult = await handleSearch(
|
|
2012
|
+
{ query: args2.topic, scope: "journal", limit },
|
|
2013
|
+
{ journalPath: storage2.journalPath, knowledgePath: storage2.knowledgePath },
|
|
2014
|
+
bridge
|
|
2015
|
+
);
|
|
2016
|
+
if (searchResult.results.length > 0) {
|
|
2017
|
+
result.journal = searchResult.results.map((r) => ({
|
|
2018
|
+
path: r.path,
|
|
2019
|
+
snippet: r.snippet,
|
|
2020
|
+
title: r.title,
|
|
2021
|
+
date: r.date,
|
|
2022
|
+
score: r.score
|
|
2023
|
+
}));
|
|
2024
|
+
}
|
|
2025
|
+
if (searchResult.fallback_warning) {
|
|
2026
|
+
fallbackWarning = searchResult.fallback_warning;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
if (sources.includes("knowledge")) {
|
|
2030
|
+
const searchResult = await handleSearch(
|
|
2031
|
+
{ query: args2.topic, scope: "knowledge", limit },
|
|
2032
|
+
{ journalPath: storage2.journalPath, knowledgePath: storage2.knowledgePath },
|
|
2033
|
+
bridge
|
|
2034
|
+
);
|
|
2035
|
+
if (searchResult.results.length > 0) {
|
|
2036
|
+
result.knowledge = searchResult.results.map((r) => ({
|
|
2037
|
+
path: r.path,
|
|
2038
|
+
snippet: r.snippet,
|
|
2039
|
+
title: r.title,
|
|
2040
|
+
date: r.date,
|
|
2041
|
+
score: r.score
|
|
2042
|
+
}));
|
|
2043
|
+
}
|
|
2044
|
+
if (searchResult.fallback_warning) {
|
|
2045
|
+
fallbackWarning = searchResult.fallback_warning;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
if (fallbackWarning) {
|
|
2049
|
+
result.fallback_warning = fallbackWarning;
|
|
2050
|
+
}
|
|
2051
|
+
result._hints = buildHints({
|
|
2052
|
+
next: "Use datacore.feedback on helpful engrams, or datacore.learn to create new ones.",
|
|
2053
|
+
related: ["datacore.feedback", "datacore.learn"]
|
|
2054
|
+
});
|
|
2055
|
+
return result;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// src/tools/promote.ts
|
|
2059
|
+
async function handlePromote(args2, engramsPath) {
|
|
2060
|
+
const targetIds = args2.ids ?? (args2.id ? [args2.id] : []);
|
|
2061
|
+
if (targetIds.length === 0) {
|
|
2062
|
+
return {
|
|
2063
|
+
success: false,
|
|
2064
|
+
promoted: [],
|
|
2065
|
+
errors: [{ id: "", error: "At least one engram ID required (id or ids)" }],
|
|
2066
|
+
_hints: buildHints({
|
|
2067
|
+
next: "Provide an engram ID. Use datacore.search or datacore.status to find valid IDs.",
|
|
2068
|
+
related: ["datacore.search", "datacore.status"]
|
|
2069
|
+
})
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
const engrams = loadEngrams(engramsPath);
|
|
2073
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2074
|
+
const promoted = [];
|
|
2075
|
+
const errors = [];
|
|
2076
|
+
for (const id of targetIds) {
|
|
2077
|
+
const engram = engrams.find((e) => e.id === id);
|
|
2078
|
+
if (!engram) {
|
|
2079
|
+
errors.push({ id, error: "Engram not found" });
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
if (engram.status === "active") {
|
|
2083
|
+
errors.push({ id, error: "Already active" });
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
if (engram.status === "retired") {
|
|
2087
|
+
errors.push({ id, error: "Cannot promote retired engram" });
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
engram.status = "active";
|
|
2091
|
+
engram.activation.retrieval_strength = 0.7;
|
|
2092
|
+
engram.activation.storage_strength = 1;
|
|
2093
|
+
engram.activation.last_accessed = today;
|
|
2094
|
+
promoted.push({ id: engram.id, statement: engram.statement });
|
|
2095
|
+
}
|
|
2096
|
+
if (promoted.length > 0) {
|
|
2097
|
+
atomicWriteYaml(engramsPath, { engrams });
|
|
2098
|
+
}
|
|
2099
|
+
return {
|
|
2100
|
+
success: errors.length === 0,
|
|
2101
|
+
promoted,
|
|
2102
|
+
errors,
|
|
2103
|
+
_hints: buildHints({
|
|
2104
|
+
next: promoted.length > 0 ? `Promoted ${promoted.length} engram(s). They will now appear in inject results.` : "No engrams were promoted. Check the errors above.",
|
|
2105
|
+
related: ["datacore.inject", "datacore.status"]
|
|
2106
|
+
})
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
1578
2109
|
|
|
1579
2110
|
// src/resources.ts
|
|
1580
2111
|
import {
|
|
@@ -1582,8 +2113,8 @@ import {
|
|
|
1582
2113
|
ReadResourceRequestSchema,
|
|
1583
2114
|
ListResourceTemplatesRequestSchema
|
|
1584
2115
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1585
|
-
import * as
|
|
1586
|
-
import * as
|
|
2116
|
+
import * as fs16 from "fs";
|
|
2117
|
+
import * as path14 from "path";
|
|
1587
2118
|
function registerResources(server, storage2) {
|
|
1588
2119
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
1589
2120
|
resources: [
|
|
@@ -1604,6 +2135,12 @@ function registerResources(server, storage2) {
|
|
|
1604
2135
|
name: "Today's Journal",
|
|
1605
2136
|
description: "Today's journal entry",
|
|
1606
2137
|
mimeType: "text/markdown"
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
uri: "datacore://guide",
|
|
2141
|
+
name: "Datacore Agent Guide",
|
|
2142
|
+
description: "Workflow guide for AI agents: session lifecycle, engram lifecycle, tool reference",
|
|
2143
|
+
mimeType: "text/markdown"
|
|
1607
2144
|
}
|
|
1608
2145
|
]
|
|
1609
2146
|
}));
|
|
@@ -1646,14 +2183,23 @@ function registerResources(server, storage2) {
|
|
|
1646
2183
|
}]
|
|
1647
2184
|
};
|
|
1648
2185
|
}
|
|
2186
|
+
if (uri === "datacore://guide") {
|
|
2187
|
+
return {
|
|
2188
|
+
contents: [{
|
|
2189
|
+
uri,
|
|
2190
|
+
mimeType: "text/markdown",
|
|
2191
|
+
text: AGENT_GUIDE
|
|
2192
|
+
}]
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
1649
2195
|
const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
|
|
1650
2196
|
if (journalMatch) {
|
|
1651
2197
|
const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
|
|
1652
|
-
const filePath =
|
|
1653
|
-
if (!
|
|
2198
|
+
const filePath = path14.join(storage2.journalPath, `${dateStr}.md`);
|
|
2199
|
+
if (!fs16.existsSync(filePath)) {
|
|
1654
2200
|
return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
|
|
1655
2201
|
}
|
|
1656
|
-
return { contents: [{ uri, mimeType: "text/markdown", text:
|
|
2202
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: fs16.readFileSync(filePath, "utf8") }] };
|
|
1657
2203
|
}
|
|
1658
2204
|
const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
|
|
1659
2205
|
if (engramMatch) {
|
|
@@ -1673,6 +2219,40 @@ function registerResources(server, storage2) {
|
|
|
1673
2219
|
throw new Error(`Unknown resource: ${uri}`);
|
|
1674
2220
|
});
|
|
1675
2221
|
}
|
|
2222
|
+
var AGENT_GUIDE = `# Datacore Agent Guide
|
|
2223
|
+
|
|
2224
|
+
## Session Lifecycle
|
|
2225
|
+
1. datacore.session.start \u2014 Get relevant engrams + today's context
|
|
2226
|
+
2. Work on task, use datacore.recall or datacore.search as needed
|
|
2227
|
+
3. datacore.feedback \u2014 Rate which injected engrams helped (batch supported)
|
|
2228
|
+
4. datacore.session.end \u2014 Capture summary + engram suggestions
|
|
2229
|
+
|
|
2230
|
+
## Engram Lifecycle
|
|
2231
|
+
- datacore.learn creates candidate engrams (or active if auto_promote enabled)
|
|
2232
|
+
- datacore.promote activates candidates so they appear in inject results
|
|
2233
|
+
- datacore.feedback with positive signals strengthens injection priority
|
|
2234
|
+
- datacore.forget retires engrams permanently
|
|
2235
|
+
- Unused engrams naturally decay over time
|
|
2236
|
+
|
|
2237
|
+
## Quick Reference
|
|
2238
|
+
| Tool | Purpose |
|
|
2239
|
+
|------|---------|
|
|
2240
|
+
| session.start | Begin session with context injection |
|
|
2241
|
+
| session.end | End session with journal + engrams |
|
|
2242
|
+
| learn | Create engram from knowledge statement |
|
|
2243
|
+
| promote | Activate candidate engrams |
|
|
2244
|
+
| inject | Get relevant engrams for specific task |
|
|
2245
|
+
| recall | Search all sources (engrams + journal + knowledge) |
|
|
2246
|
+
| capture | Write journal entry or knowledge note |
|
|
2247
|
+
| search | Keyword/semantic file search |
|
|
2248
|
+
| ingest | Ingest text + extract engram suggestions |
|
|
2249
|
+
| feedback | Rate engrams (single or batch) |
|
|
2250
|
+
| forget | Retire an engram |
|
|
2251
|
+
| status | System health + actionable recommendations |
|
|
2252
|
+
| packs.discover | Browse available engram packs |
|
|
2253
|
+
| packs.install | Install or upgrade a pack |
|
|
2254
|
+
| packs.export | Export engrams as shareable pack |
|
|
2255
|
+
`;
|
|
1676
2256
|
function notifyEngramsChanged(server) {
|
|
1677
2257
|
try {
|
|
1678
2258
|
server.sendResourceUpdated?.({ uri: "datacore://engrams/active" });
|
|
@@ -1680,10 +2260,178 @@ function notifyEngramsChanged(server) {
|
|
|
1680
2260
|
}
|
|
1681
2261
|
}
|
|
1682
2262
|
|
|
2263
|
+
// src/prompts.ts
|
|
2264
|
+
import {
|
|
2265
|
+
ListPromptsRequestSchema,
|
|
2266
|
+
GetPromptRequestSchema
|
|
2267
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
2268
|
+
var PROMPTS = [
|
|
2269
|
+
{
|
|
2270
|
+
name: "datacore-session",
|
|
2271
|
+
title: "Start a Datacore session",
|
|
2272
|
+
description: "Begin a working session with Datacore. Injects relevant context, shows today's journal, and guides you through the session lifecycle.",
|
|
2273
|
+
arguments: [
|
|
2274
|
+
{ name: "task", description: "What you are working on (optional \u2014 triggers engram injection)", required: false }
|
|
2275
|
+
],
|
|
2276
|
+
messages: (args2) => [{
|
|
2277
|
+
role: "user",
|
|
2278
|
+
content: {
|
|
2279
|
+
type: "text",
|
|
2280
|
+
text: `Start a new Datacore session.${args2.task ? ` Task: ${args2.task}` : ""}
|
|
2281
|
+
|
|
2282
|
+
Call datacore.session.start${args2.task ? ` with task: "${args2.task}"` : ""} to begin. This will:
|
|
2283
|
+
- Inject relevant engrams (learned knowledge) for the task
|
|
2284
|
+
- Show today's journal entry if one exists
|
|
2285
|
+
- List any candidate engrams awaiting review
|
|
2286
|
+
|
|
2287
|
+
When done working, call datacore.session.end with a summary and any new learnings.
|
|
2288
|
+
|
|
2289
|
+
Session lifecycle:
|
|
2290
|
+
1. datacore.session.start \u2192 get context
|
|
2291
|
+
2. Work on task (use datacore.recall or datacore.search as needed)
|
|
2292
|
+
3. datacore.feedback \u2192 rate which injected engrams were helpful
|
|
2293
|
+
4. datacore.session.end \u2192 capture summary + new engrams`
|
|
2294
|
+
}
|
|
2295
|
+
}]
|
|
2296
|
+
},
|
|
2297
|
+
{
|
|
2298
|
+
name: "datacore-learn",
|
|
2299
|
+
title: "Teach Datacore something",
|
|
2300
|
+
description: "Record a reusable learning as an engram. Covers the full engram lifecycle: create, review, activate, reinforce.",
|
|
2301
|
+
arguments: [
|
|
2302
|
+
{ name: "statement", description: 'The knowledge to record (e.g., "Always run tests before deploying")', required: true }
|
|
2303
|
+
],
|
|
2304
|
+
messages: (args2) => [{
|
|
2305
|
+
role: "user",
|
|
2306
|
+
content: {
|
|
2307
|
+
type: "text",
|
|
2308
|
+
text: `Record this learning in Datacore: "${args2.statement || "..."}"
|
|
2309
|
+
|
|
2310
|
+
Call datacore.learn with the statement. This creates a candidate engram.
|
|
2311
|
+
|
|
2312
|
+
Engram lifecycle:
|
|
2313
|
+
1. datacore.learn \u2192 creates candidate (not yet active)
|
|
2314
|
+
2. datacore.promote \u2192 activates it so it appears in future inject results
|
|
2315
|
+
3. datacore.inject \u2192 retrieves relevant engrams when working on tasks
|
|
2316
|
+
4. datacore.feedback \u2192 positive signals strengthen it, negative signals weaken it
|
|
2317
|
+
5. datacore.forget \u2192 retires it permanently if no longer useful
|
|
2318
|
+
|
|
2319
|
+
Engrams that prove useful get reinforced over time. Unused ones naturally decay.`
|
|
2320
|
+
}
|
|
2321
|
+
}]
|
|
2322
|
+
},
|
|
2323
|
+
{
|
|
2324
|
+
name: "datacore-guide",
|
|
2325
|
+
title: "How to use Datacore",
|
|
2326
|
+
description: "Complete guide to Datacore tools, workflows, and concepts. Read this to understand the system.",
|
|
2327
|
+
messages: () => [{
|
|
2328
|
+
role: "assistant",
|
|
2329
|
+
content: {
|
|
2330
|
+
type: "text",
|
|
2331
|
+
text: `# Datacore \u2014 Persistent Memory for AI
|
|
2332
|
+
|
|
2333
|
+
Datacore gives you persistent memory through **engrams** \u2014 typed knowledge units that get injected into context when relevant. You learn patterns, remember preferences, and build on previous work across sessions.
|
|
2334
|
+
|
|
2335
|
+
## Core Concepts
|
|
2336
|
+
|
|
2337
|
+
**Engrams** are reusable knowledge: "Always validate input at API boundaries", "User prefers tabs over spaces". They have activation dynamics \u2014 frequently useful ones get stronger, unused ones decay.
|
|
2338
|
+
|
|
2339
|
+
**Journal** is your session log. Each session captures what happened, what was learned.
|
|
2340
|
+
|
|
2341
|
+
**Knowledge** is ingested reference material \u2014 articles, notes, documents broken into searchable pieces.
|
|
2342
|
+
|
|
2343
|
+
## Session Workflow
|
|
2344
|
+
|
|
2345
|
+
Every session follows this pattern:
|
|
2346
|
+
|
|
2347
|
+
1. **datacore.session.start** \u2014 Injects relevant engrams + shows today's journal
|
|
2348
|
+
2. **Work** \u2014 Use datacore.recall or datacore.search to find information
|
|
2349
|
+
3. **datacore.feedback** \u2014 Rate which engrams were helpful (strengthens good ones)
|
|
2350
|
+
4. **datacore.session.end** \u2014 Capture summary + suggest new engrams
|
|
2351
|
+
|
|
2352
|
+
## Tool Reference
|
|
2353
|
+
|
|
2354
|
+
### Session
|
|
2355
|
+
| Tool | What it does |
|
|
2356
|
+
|------|-------------|
|
|
2357
|
+
| **session.start** | Begin session, inject context, show journal |
|
|
2358
|
+
| **session.end** | End session, capture journal + new engrams |
|
|
2359
|
+
|
|
2360
|
+
### Core
|
|
2361
|
+
| Tool | What it does |
|
|
2362
|
+
|------|-------------|
|
|
2363
|
+
| **capture** | Write a journal entry or knowledge note |
|
|
2364
|
+
| **learn** | Create an engram (starts as candidate) |
|
|
2365
|
+
| **promote** | Activate candidate engrams |
|
|
2366
|
+
| **inject** | Get relevant engrams for a specific task |
|
|
2367
|
+
| **recall** | Search everything (engrams + journal + knowledge) |
|
|
2368
|
+
| **search** | Search journal and knowledge files |
|
|
2369
|
+
| **ingest** | Ingest text, extract engram suggestions |
|
|
2370
|
+
| **status** | System health + actionable recommendations |
|
|
2371
|
+
|
|
2372
|
+
### Engram Management
|
|
2373
|
+
| Tool | What it does |
|
|
2374
|
+
|------|-------------|
|
|
2375
|
+
| **feedback** | Rate engrams: positive/negative/neutral (single or batch) |
|
|
2376
|
+
| **forget** | Retire an engram permanently |
|
|
2377
|
+
|
|
2378
|
+
### Packs (Shareable Knowledge)
|
|
2379
|
+
| Tool | What it does |
|
|
2380
|
+
|------|-------------|
|
|
2381
|
+
| **packs.discover** | Browse available engram packs |
|
|
2382
|
+
| **packs.install** | Install a pack |
|
|
2383
|
+
| **packs.export** | Export your engrams as a pack |
|
|
2384
|
+
|
|
2385
|
+
## Engram Lifecycle
|
|
2386
|
+
|
|
2387
|
+
\`\`\`
|
|
2388
|
+
learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
|
|
2389
|
+
\u2192 forget (retire)
|
|
2390
|
+
\`\`\`
|
|
2391
|
+
|
|
2392
|
+
- **candidate**: Created but not yet active. Won't appear in inject results.
|
|
2393
|
+
- **active**: Appears in inject results when relevant.
|
|
2394
|
+
- **retired**: Permanently removed from injection.
|
|
2395
|
+
|
|
2396
|
+
Feedback matters: positive signals increase retrieval strength, negative signals decrease it. Engrams that are never accessed naturally decay over time.
|
|
2397
|
+
|
|
2398
|
+
## Tips
|
|
2399
|
+
|
|
2400
|
+
- Start every session with **session.start** \u2014 it gives you relevant context
|
|
2401
|
+
- End every session with **session.end** \u2014 it captures what you learned
|
|
2402
|
+
- Use **feedback** after getting injected engrams \u2014 this is how Datacore learns what's useful
|
|
2403
|
+
- Use **recall** for broad searches across all sources, **search** for targeted file searches
|
|
2404
|
+
- Check **status** periodically \u2014 it shows actionable recommendations`
|
|
2405
|
+
}
|
|
2406
|
+
}]
|
|
2407
|
+
}
|
|
2408
|
+
];
|
|
2409
|
+
function registerPrompts(server) {
|
|
2410
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
2411
|
+
prompts: PROMPTS.map((p) => ({
|
|
2412
|
+
name: p.name,
|
|
2413
|
+
title: p.title,
|
|
2414
|
+
description: p.description,
|
|
2415
|
+
arguments: p.arguments
|
|
2416
|
+
}))
|
|
2417
|
+
}));
|
|
2418
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
2419
|
+
const { name, arguments: promptArgs } = request.params;
|
|
2420
|
+
const prompt = PROMPTS.find((p) => p.name === name);
|
|
2421
|
+
if (!prompt) {
|
|
2422
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
2423
|
+
}
|
|
2424
|
+
return {
|
|
2425
|
+
description: prompt.description,
|
|
2426
|
+
messages: prompt.messages(promptArgs ?? {})
|
|
2427
|
+
};
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
|
|
1683
2431
|
// src/datacortex.ts
|
|
1684
2432
|
import { execFile } from "child_process";
|
|
1685
|
-
import * as
|
|
1686
|
-
import * as
|
|
2433
|
+
import * as fs17 from "fs";
|
|
2434
|
+
import * as path15 from "path";
|
|
1687
2435
|
var DatacortexBridge = class {
|
|
1688
2436
|
pythonPath;
|
|
1689
2437
|
scriptPath;
|
|
@@ -1693,11 +2441,11 @@ var DatacortexBridge = class {
|
|
|
1693
2441
|
}
|
|
1694
2442
|
findBridgeScript(datacorePath) {
|
|
1695
2443
|
const candidates = [
|
|
1696
|
-
|
|
1697
|
-
|
|
2444
|
+
path15.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
|
|
2445
|
+
path15.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
|
|
1698
2446
|
];
|
|
1699
2447
|
for (const candidate of candidates) {
|
|
1700
|
-
if (
|
|
2448
|
+
if (fs17.existsSync(candidate)) return candidate;
|
|
1701
2449
|
}
|
|
1702
2450
|
return null;
|
|
1703
2451
|
}
|
|
@@ -1760,22 +2508,25 @@ var datacortexBridge = null;
|
|
|
1760
2508
|
function createServer() {
|
|
1761
2509
|
const server = new Server(
|
|
1762
2510
|
{ name: "datacore-mcp", version: currentVersion },
|
|
1763
|
-
{ capabilities: { tools: {}, logging: {}, resources: { subscribe: true } } }
|
|
2511
|
+
{ capabilities: { tools: {}, logging: {}, resources: { subscribe: true }, prompts: {} } }
|
|
1764
2512
|
);
|
|
1765
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2513
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2514
|
+
const coreTools = storage.mode === "core" ? TOOLS.filter((t) => !t.name.startsWith("datacore.modules.")) : TOOLS;
|
|
2515
|
+
return {
|
|
2516
|
+
tools: [
|
|
2517
|
+
...coreTools.map((t) => ({
|
|
2518
|
+
name: t.name,
|
|
2519
|
+
description: t.description,
|
|
2520
|
+
inputSchema: zodToJsonSchema(t.inputSchema)
|
|
2521
|
+
})),
|
|
2522
|
+
...moduleTools.map((t) => ({
|
|
2523
|
+
name: t.fullName,
|
|
2524
|
+
description: t.definition.description,
|
|
2525
|
+
inputSchema: zodToJsonSchema(t.definition.inputSchema)
|
|
2526
|
+
}))
|
|
2527
|
+
]
|
|
2528
|
+
};
|
|
2529
|
+
});
|
|
1779
2530
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1780
2531
|
const { name, arguments: args2 } = request.params;
|
|
1781
2532
|
try {
|
|
@@ -1795,10 +2546,11 @@ function createServer() {
|
|
|
1795
2546
|
});
|
|
1796
2547
|
logger.setServer(server);
|
|
1797
2548
|
registerResources(server, storage);
|
|
2549
|
+
registerPrompts(server);
|
|
1798
2550
|
serverRef = server;
|
|
1799
2551
|
return server;
|
|
1800
2552
|
}
|
|
1801
|
-
var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback"]);
|
|
2553
|
+
var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote"]);
|
|
1802
2554
|
async function routeTool(name, args2) {
|
|
1803
2555
|
const coreTool = TOOLS.find((t) => t.name === name);
|
|
1804
2556
|
if (coreTool) {
|
|
@@ -1829,6 +2581,18 @@ async function routeTool(name, args2) {
|
|
|
1829
2581
|
case "datacore.feedback":
|
|
1830
2582
|
result = await handleFeedback(validated, storage.engramsPath);
|
|
1831
2583
|
break;
|
|
2584
|
+
case "datacore.session.start":
|
|
2585
|
+
result = await handleSessionStart(validated, storage, datacortexBridge);
|
|
2586
|
+
break;
|
|
2587
|
+
case "datacore.session.end":
|
|
2588
|
+
result = await handleSessionEnd(validated, storage);
|
|
2589
|
+
break;
|
|
2590
|
+
case "datacore.recall":
|
|
2591
|
+
result = await handleRecall(validated, { engramsPath: storage.engramsPath, journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
|
|
2592
|
+
break;
|
|
2593
|
+
case "datacore.promote":
|
|
2594
|
+
result = await handlePromote(validated, storage.engramsPath);
|
|
2595
|
+
break;
|
|
1832
2596
|
case "datacore.packs.discover":
|
|
1833
2597
|
result = handleDiscover(validated, storage.packsPath);
|
|
1834
2598
|
break;
|
|
@@ -1890,6 +2654,7 @@ async function initStorage() {
|
|
|
1890
2654
|
const result = initCore(storage.basePath);
|
|
1891
2655
|
isFirstRun = result.isFirstRun;
|
|
1892
2656
|
}
|
|
2657
|
+
loadConfig(storage.basePath, storage.mode);
|
|
1893
2658
|
if (storage.mode === "full") {
|
|
1894
2659
|
discoveredModules = discoverModules(storage);
|
|
1895
2660
|
moduleTools = await loadModuleTools(discoveredModules, storage);
|