@datacore-one/mcp 1.0.2 → 1.1.1
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 +1142 -287
- 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.
|
|
240
|
+
version: "1.1.1",
|
|
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;
|
|
@@ -727,7 +933,15 @@ async function handleInject(args2, paths) {
|
|
|
727
933
|
const result = selectEngrams(ctx, personalEngrams, packs);
|
|
728
934
|
const totalCount = result.directives.length + result.consider.length;
|
|
729
935
|
if (totalCount === 0) {
|
|
730
|
-
return {
|
|
936
|
+
return {
|
|
937
|
+
text: "",
|
|
938
|
+
count: 0,
|
|
939
|
+
tokens_used: 0,
|
|
940
|
+
_hints: buildHints({
|
|
941
|
+
next: "No engrams matched this task. Use datacore.recall to search all sources, or datacore.learn to record new knowledge.",
|
|
942
|
+
related: ["datacore.recall", "datacore.learn"]
|
|
943
|
+
})
|
|
944
|
+
};
|
|
731
945
|
}
|
|
732
946
|
const lines = [];
|
|
733
947
|
if (result.directives.length > 0) {
|
|
@@ -747,7 +961,17 @@ async function handleInject(args2, paths) {
|
|
|
747
961
|
personalEngrams,
|
|
748
962
|
[...result.directives, ...result.consider]
|
|
749
963
|
);
|
|
750
|
-
|
|
964
|
+
const injectedIds = [...result.directives, ...result.consider].filter((e) => !e.pack).map((e) => e.id);
|
|
965
|
+
const idsList = injectedIds.length > 0 ? ` Injected IDs: ${injectedIds.join(", ")}` : "";
|
|
966
|
+
return {
|
|
967
|
+
text: lines.join("\n"),
|
|
968
|
+
count: totalCount,
|
|
969
|
+
tokens_used: result.tokens_used,
|
|
970
|
+
_hints: buildHints({
|
|
971
|
+
next: `After task, call datacore.feedback on helpful/unhelpful engrams.${idsList}`,
|
|
972
|
+
related: ["datacore.feedback", "datacore.session.end"]
|
|
973
|
+
})
|
|
974
|
+
};
|
|
751
975
|
}
|
|
752
976
|
function updateUsageTracking(engramsPath, allPersonal, selected) {
|
|
753
977
|
const selectedPersonalIds = new Set(
|
|
@@ -768,10 +992,10 @@ function updateUsageTracking(engramsPath, allPersonal, selected) {
|
|
|
768
992
|
}
|
|
769
993
|
}
|
|
770
994
|
function atomicWriteYaml(filePath, data) {
|
|
771
|
-
const content =
|
|
995
|
+
const content = yaml3.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
772
996
|
const tmpPath = filePath + ".tmp." + process.pid;
|
|
773
|
-
|
|
774
|
-
|
|
997
|
+
fs5.writeFileSync(tmpPath, content);
|
|
998
|
+
fs5.renameSync(tmpPath, filePath);
|
|
775
999
|
}
|
|
776
1000
|
function formatEngram(engram, totalCount) {
|
|
777
1001
|
if (totalCount < 10) {
|
|
@@ -792,15 +1016,15 @@ function formatEngram(engram, totalCount) {
|
|
|
792
1016
|
}
|
|
793
1017
|
|
|
794
1018
|
// src/tools/search.ts
|
|
795
|
-
import * as
|
|
796
|
-
import * as
|
|
1019
|
+
import * as fs6 from "fs";
|
|
1020
|
+
import * as path4 from "path";
|
|
797
1021
|
var CONTENT_CACHE_MAX = 500;
|
|
798
1022
|
var contentCache = /* @__PURE__ */ new Map();
|
|
799
1023
|
function getCachedContent(filePath) {
|
|
800
1024
|
const entry = contentCache.get(filePath);
|
|
801
1025
|
if (!entry) return null;
|
|
802
1026
|
try {
|
|
803
|
-
const stat =
|
|
1027
|
+
const stat = fs6.statSync(filePath);
|
|
804
1028
|
if (stat.mtimeMs === entry.mtime) return entry.content;
|
|
805
1029
|
} catch {
|
|
806
1030
|
}
|
|
@@ -809,7 +1033,7 @@ function getCachedContent(filePath) {
|
|
|
809
1033
|
}
|
|
810
1034
|
function setCachedContent(filePath, content) {
|
|
811
1035
|
try {
|
|
812
|
-
const mtime =
|
|
1036
|
+
const mtime = fs6.statSync(filePath).mtimeMs;
|
|
813
1037
|
if (contentCache.size >= CONTENT_CACHE_MAX) {
|
|
814
1038
|
const firstKey = contentCache.keys().next().value;
|
|
815
1039
|
if (firstKey) contentCache.delete(firstKey);
|
|
@@ -846,13 +1070,13 @@ async function keywordSearch(args2, paths) {
|
|
|
846
1070
|
return { results: results.slice(0, limit), method: "keyword" };
|
|
847
1071
|
}
|
|
848
1072
|
function searchDir(dirPath, query) {
|
|
849
|
-
if (!
|
|
1073
|
+
if (!fs6.existsSync(dirPath)) return [];
|
|
850
1074
|
const results = [];
|
|
851
1075
|
const queryLower = query.toLowerCase();
|
|
852
1076
|
for (const file of walkDir(dirPath)) {
|
|
853
1077
|
if (!file.endsWith(".md")) continue;
|
|
854
1078
|
const content = getCachedContent(file) ?? (() => {
|
|
855
|
-
const c =
|
|
1079
|
+
const c = fs6.readFileSync(file, "utf8");
|
|
856
1080
|
setCachedContent(file, c);
|
|
857
1081
|
return c;
|
|
858
1082
|
})();
|
|
@@ -860,14 +1084,16 @@ function searchDir(dirPath, query) {
|
|
|
860
1084
|
const occurrences = countOccurrences(contentLower, queryLower);
|
|
861
1085
|
if (occurrences === 0) continue;
|
|
862
1086
|
const snippet = extractSnippet(content, query);
|
|
863
|
-
|
|
1087
|
+
const title = extractTitle(content, file);
|
|
1088
|
+
const date = extractDate(file);
|
|
1089
|
+
results.push({ path: file, snippet, score: occurrences, title, date });
|
|
864
1090
|
}
|
|
865
1091
|
return results;
|
|
866
1092
|
}
|
|
867
1093
|
function walkDir(dir) {
|
|
868
1094
|
const files = [];
|
|
869
|
-
for (const entry of
|
|
870
|
-
const fullPath =
|
|
1095
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
1096
|
+
const fullPath = path4.join(dir, entry.name);
|
|
871
1097
|
if (entry.isDirectory()) files.push(...walkDir(fullPath));
|
|
872
1098
|
else files.push(fullPath);
|
|
873
1099
|
}
|
|
@@ -883,16 +1109,29 @@ function countOccurrences(text, query) {
|
|
|
883
1109
|
return count;
|
|
884
1110
|
}
|
|
885
1111
|
function extractSnippet(content, query) {
|
|
1112
|
+
const snippetLength = getConfig().search.snippet_length;
|
|
1113
|
+
if (content.length < 2e3) return content;
|
|
886
1114
|
const idx = content.toLowerCase().indexOf(query.toLowerCase());
|
|
887
|
-
if (idx === -1) return content.slice(0,
|
|
888
|
-
const
|
|
889
|
-
const
|
|
1115
|
+
if (idx === -1) return content.slice(0, snippetLength);
|
|
1116
|
+
const half = Math.floor(snippetLength / 2);
|
|
1117
|
+
const start2 = Math.max(0, idx - half);
|
|
1118
|
+
const end = Math.min(content.length, idx + query.length + half);
|
|
890
1119
|
return (start2 > 0 ? "..." : "") + content.slice(start2, end) + (end < content.length ? "..." : "");
|
|
891
1120
|
}
|
|
1121
|
+
function extractTitle(content, filePath) {
|
|
1122
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
1123
|
+
if (match) return match[1].trim();
|
|
1124
|
+
return path4.basename(filePath, path4.extname(filePath));
|
|
1125
|
+
}
|
|
1126
|
+
function extractDate(filePath) {
|
|
1127
|
+
const match = path4.basename(filePath).match(/^(\d{4}-\d{2}-\d{2})/);
|
|
1128
|
+
if (match) return match[1];
|
|
1129
|
+
return void 0;
|
|
1130
|
+
}
|
|
892
1131
|
|
|
893
1132
|
// src/tools/ingest.ts
|
|
894
|
-
import * as
|
|
895
|
-
import * as
|
|
1133
|
+
import * as fs7 from "fs";
|
|
1134
|
+
import * as path5 from "path";
|
|
896
1135
|
async function handleIngest(args2, paths) {
|
|
897
1136
|
const contentError = validateContent(args2.content);
|
|
898
1137
|
if (contentError) return { success: false, error: contentError };
|
|
@@ -903,8 +1142,8 @@ async function handleIngest(args2, paths) {
|
|
|
903
1142
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
904
1143
|
const slug = (args2.title ?? "ingested").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
905
1144
|
const fileName = `${timestamp}-${slug}.md`;
|
|
906
|
-
const filePath =
|
|
907
|
-
|
|
1145
|
+
const filePath = path5.join(paths.knowledgePath, fileName);
|
|
1146
|
+
fs7.mkdirSync(path5.dirname(filePath), { recursive: true });
|
|
908
1147
|
const frontmatter = `---
|
|
909
1148
|
title: "${args2.title ?? "Ingested Note"}"
|
|
910
1149
|
created: "${(/* @__PURE__ */ new Date()).toISOString()}"
|
|
@@ -915,13 +1154,17 @@ type: ingested
|
|
|
915
1154
|
const tagLine = args2.tags?.length ? `
|
|
916
1155
|
${args2.tags.map((t) => `#${t}`).join(" ")}
|
|
917
1156
|
` : "";
|
|
918
|
-
|
|
1157
|
+
fs7.writeFileSync(filePath, `${frontmatter}${args2.content}
|
|
919
1158
|
${tagLine}`);
|
|
920
1159
|
const suggestions = extractEngramSuggestions(args2.content);
|
|
921
1160
|
return {
|
|
922
1161
|
success: true,
|
|
923
1162
|
note_path: filePath,
|
|
924
|
-
engram_suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
1163
|
+
engram_suggestions: suggestions.length > 0 ? suggestions : void 0,
|
|
1164
|
+
_hints: suggestions.length > 0 ? buildHints({
|
|
1165
|
+
next: `Call datacore.learn for each suggestion to create engrams. Example: datacore.learn({statement: '${suggestions[0]}', type: 'behavioral'})`,
|
|
1166
|
+
related: ["datacore.learn"]
|
|
1167
|
+
}) : void 0
|
|
925
1168
|
};
|
|
926
1169
|
}
|
|
927
1170
|
function extractEngramSuggestions(content) {
|
|
@@ -945,21 +1188,21 @@ function extractEngramSuggestions(content) {
|
|
|
945
1188
|
}
|
|
946
1189
|
|
|
947
1190
|
// src/tools/status.ts
|
|
948
|
-
import * as
|
|
949
|
-
import * as
|
|
1191
|
+
import * as fs9 from "fs";
|
|
1192
|
+
import * as path7 from "path";
|
|
950
1193
|
|
|
951
1194
|
// src/trust.ts
|
|
952
|
-
import * as
|
|
953
|
-
import * as
|
|
1195
|
+
import * as fs8 from "fs";
|
|
1196
|
+
import * as path6 from "path";
|
|
954
1197
|
import * as crypto from "crypto";
|
|
955
1198
|
function computePackChecksum(packDir) {
|
|
956
1199
|
const files = ["SKILL.md", "engrams.yaml"];
|
|
957
1200
|
const hash = crypto.createHash("sha256");
|
|
958
1201
|
let hasContent = false;
|
|
959
1202
|
for (const file of files) {
|
|
960
|
-
const filePath =
|
|
961
|
-
if (
|
|
962
|
-
hash.update(
|
|
1203
|
+
const filePath = path6.join(packDir, file);
|
|
1204
|
+
if (fs8.existsSync(filePath)) {
|
|
1205
|
+
hash.update(fs8.readFileSync(filePath));
|
|
963
1206
|
hasContent = true;
|
|
964
1207
|
}
|
|
965
1208
|
}
|
|
@@ -1029,12 +1272,31 @@ async function handleStatus(paths, updateAvailable2) {
|
|
|
1029
1272
|
const packIntegrity = [];
|
|
1030
1273
|
for (const regPack of packs_default.packs) {
|
|
1031
1274
|
if (!regPack.checksum) continue;
|
|
1032
|
-
const packDir =
|
|
1033
|
-
if (!
|
|
1034
|
-
const
|
|
1035
|
-
packIntegrity.push({ name: regPack.id, valid:
|
|
1275
|
+
const packDir = path7.join(paths.packsPath, regPack.id);
|
|
1276
|
+
if (!fs9.existsSync(packDir)) continue;
|
|
1277
|
+
const result = verifyPackChecksum(packDir, regPack.checksum);
|
|
1278
|
+
packIntegrity.push({ name: regPack.id, valid: result.valid });
|
|
1279
|
+
}
|
|
1280
|
+
const recommendations = [];
|
|
1281
|
+
const candidateCount = engrams.filter((e) => e.status === "candidate").length;
|
|
1282
|
+
if (healthCounts.retirement_candidate > 0) {
|
|
1283
|
+
recommendations.push(`${healthCounts.retirement_candidate} engrams are retirement candidates. Use datacore.forget to clean up.`);
|
|
1284
|
+
}
|
|
1285
|
+
if (healthCounts.fading > 0) {
|
|
1286
|
+
recommendations.push(`${healthCounts.fading} engrams are fading. Use datacore.feedback with positive signal to reinforce.`);
|
|
1036
1287
|
}
|
|
1037
|
-
|
|
1288
|
+
if (candidateCount > 0) {
|
|
1289
|
+
recommendations.push(`${candidateCount} candidate engrams awaiting review. Use datacore.promote or enable engrams.auto_promote in config.yaml.`);
|
|
1290
|
+
}
|
|
1291
|
+
const { date: today } = localDate();
|
|
1292
|
+
const todayJournal = path7.join(paths.journalPath, `${today}.md`);
|
|
1293
|
+
if (!fs9.existsSync(todayJournal)) {
|
|
1294
|
+
recommendations.push("No journal entry today. Use datacore.capture to start one.");
|
|
1295
|
+
}
|
|
1296
|
+
if (updateAvailable2) {
|
|
1297
|
+
recommendations.push(`Update available: ${updateAvailable2}. Run: npm update -g @datacore-one/mcp`);
|
|
1298
|
+
}
|
|
1299
|
+
const statusResult = {
|
|
1038
1300
|
version: currentVersion,
|
|
1039
1301
|
mode: paths.mode,
|
|
1040
1302
|
engrams: engrams.length,
|
|
@@ -1042,42 +1304,47 @@ async function handleStatus(paths, updateAvailable2) {
|
|
|
1042
1304
|
packs: packsCount,
|
|
1043
1305
|
pack_integrity: packIntegrity.length > 0 ? packIntegrity : void 0,
|
|
1044
1306
|
journal_entries: journalCount,
|
|
1045
|
-
knowledge_notes: knowledgeCount
|
|
1307
|
+
knowledge_notes: knowledgeCount,
|
|
1308
|
+
_recommendations: recommendations.length > 0 ? recommendations : void 0,
|
|
1309
|
+
_hints: buildHints({
|
|
1310
|
+
next: recommendations.length > 0 ? recommendations[0] : "System healthy. Use datacore.session.start to begin working.",
|
|
1311
|
+
related: ["datacore.promote", "datacore.forget"]
|
|
1312
|
+
})
|
|
1046
1313
|
};
|
|
1047
1314
|
if (engrams.length >= 500) {
|
|
1048
|
-
|
|
1315
|
+
statusResult.scaling_hint = `You have ${engrams.length} engrams. Consider migrating to full Datacore for SQLite-backed search.`;
|
|
1049
1316
|
}
|
|
1050
1317
|
if (updateAvailable2) {
|
|
1051
|
-
|
|
1318
|
+
statusResult.update_available = updateAvailable2;
|
|
1052
1319
|
}
|
|
1053
|
-
return
|
|
1320
|
+
return statusResult;
|
|
1054
1321
|
}
|
|
1055
1322
|
function countFiles(dir, ext) {
|
|
1056
|
-
if (!
|
|
1323
|
+
if (!fs9.existsSync(dir)) return 0;
|
|
1057
1324
|
let count = 0;
|
|
1058
|
-
for (const entry of
|
|
1059
|
-
const fullPath =
|
|
1325
|
+
for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
|
|
1326
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1060
1327
|
if (entry.isDirectory()) count += countFiles(fullPath, ext);
|
|
1061
1328
|
else if (entry.name.endsWith(ext)) count++;
|
|
1062
1329
|
}
|
|
1063
1330
|
return count;
|
|
1064
1331
|
}
|
|
1065
1332
|
function countDirs(dir) {
|
|
1066
|
-
if (!
|
|
1067
|
-
return
|
|
1333
|
+
if (!fs9.existsSync(dir)) return 0;
|
|
1334
|
+
return fs9.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
|
|
1068
1335
|
}
|
|
1069
1336
|
|
|
1070
1337
|
// src/tools/discover.ts
|
|
1071
|
-
import * as
|
|
1072
|
-
import * as
|
|
1338
|
+
import * as fs10 from "fs";
|
|
1339
|
+
import * as path8 from "path";
|
|
1073
1340
|
function handleDiscover(args2, packsDir) {
|
|
1074
1341
|
let packs = packs_default.packs.map((p) => {
|
|
1075
|
-
const localDir =
|
|
1076
|
-
const installed =
|
|
1342
|
+
const localDir = path8.join(packsDir, p.id);
|
|
1343
|
+
const installed = fs10.existsSync(path8.join(localDir, "SKILL.md"));
|
|
1077
1344
|
let installedVersion;
|
|
1078
1345
|
if (installed) {
|
|
1079
1346
|
try {
|
|
1080
|
-
const content =
|
|
1347
|
+
const content = fs10.readFileSync(path8.join(localDir, "SKILL.md"), "utf8");
|
|
1081
1348
|
const match = content.match(/version:\s*["']?([^"'\n]+)/);
|
|
1082
1349
|
installedVersion = match?.[1];
|
|
1083
1350
|
} catch {
|
|
@@ -1101,13 +1368,21 @@ function handleDiscover(args2, packsDir) {
|
|
|
1101
1368
|
const filterTags = new Set(args2.tags.map((t) => t.toLowerCase()));
|
|
1102
1369
|
packs = packs.filter((p) => p.tags.some((t) => filterTags.has(t.toLowerCase())));
|
|
1103
1370
|
}
|
|
1104
|
-
|
|
1371
|
+
const trusted = new Set(getConfig().packs.trusted_publishers);
|
|
1372
|
+
const result = { packs };
|
|
1373
|
+
if (trusted.size > 0) {
|
|
1374
|
+
const autoInstallable = packs.filter((p) => trusted.has(p.author) && !p.installed && p.can_install).map((p) => p.id);
|
|
1375
|
+
const autoUpgradeable = packs.filter((p) => trusted.has(p.author) && p.upgradeable).map((p) => p.id);
|
|
1376
|
+
if (autoInstallable.length > 0) result.auto_installable = autoInstallable;
|
|
1377
|
+
if (autoUpgradeable.length > 0) result.auto_upgradeable = autoUpgradeable;
|
|
1378
|
+
}
|
|
1379
|
+
return result;
|
|
1105
1380
|
}
|
|
1106
1381
|
|
|
1107
1382
|
// src/tools/install.ts
|
|
1108
|
-
import * as
|
|
1109
|
-
import * as
|
|
1110
|
-
import * as
|
|
1383
|
+
import * as fs11 from "fs";
|
|
1384
|
+
import * as path9 from "path";
|
|
1385
|
+
import * as yaml4 from "js-yaml";
|
|
1111
1386
|
import * as os2 from "os";
|
|
1112
1387
|
import { execSync } from "child_process";
|
|
1113
1388
|
async function handleInstall(args2, packsDir) {
|
|
@@ -1116,35 +1391,39 @@ async function handleInstall(args2, packsDir) {
|
|
|
1116
1391
|
const downloaded = await downloadPack(srcDir);
|
|
1117
1392
|
if (downloaded.error) return { success: false, error: downloaded.error };
|
|
1118
1393
|
srcDir = downloaded.path;
|
|
1394
|
+
} else if (!srcDir.includes("/") && !srcDir.includes("\\")) {
|
|
1395
|
+
const resolved = resolvePackId(srcDir, packsDir);
|
|
1396
|
+
if (resolved.error) return { success: false, error: resolved.error };
|
|
1397
|
+
srcDir = resolved.path;
|
|
1119
1398
|
}
|
|
1120
|
-
const skillPath =
|
|
1121
|
-
if (!
|
|
1399
|
+
const skillPath = path9.join(srcDir, "SKILL.md");
|
|
1400
|
+
if (!fs11.existsSync(skillPath)) {
|
|
1122
1401
|
return { success: false, error: "No SKILL.md found in source directory" };
|
|
1123
1402
|
}
|
|
1124
|
-
const skillContent =
|
|
1403
|
+
const skillContent = fs11.readFileSync(skillPath, "utf8");
|
|
1125
1404
|
const frontmatterMatch = skillContent.match(/^---\n([\s\S]*?)\n---/);
|
|
1126
1405
|
if (!frontmatterMatch) {
|
|
1127
1406
|
return { success: false, error: "No YAML frontmatter in SKILL.md" };
|
|
1128
1407
|
}
|
|
1129
|
-
const manifest =
|
|
1408
|
+
const manifest = yaml4.load(frontmatterMatch[1]);
|
|
1130
1409
|
const packId = manifest?.["x-datacore"]?.id;
|
|
1131
1410
|
const newVersion = manifest?.version;
|
|
1132
1411
|
if (!packId) {
|
|
1133
1412
|
return { success: false, error: "Missing x-datacore.id in SKILL.md frontmatter" };
|
|
1134
1413
|
}
|
|
1135
|
-
const destDir =
|
|
1136
|
-
if (
|
|
1137
|
-
const existingContent =
|
|
1414
|
+
const destDir = path9.join(packsDir, packId);
|
|
1415
|
+
if (fs11.existsSync(path9.join(destDir, "SKILL.md"))) {
|
|
1416
|
+
const existingContent = fs11.readFileSync(path9.join(destDir, "SKILL.md"), "utf8");
|
|
1138
1417
|
const existingMatch = existingContent.match(/version:\s*["']?([^"'\n]+)/);
|
|
1139
1418
|
const existingVersion = existingMatch?.[1];
|
|
1140
1419
|
if (existingVersion === newVersion) {
|
|
1141
1420
|
return { success: true, pack_id: packId, already_current: true };
|
|
1142
1421
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1422
|
+
fs11.rmSync(destDir, { recursive: true, force: true });
|
|
1423
|
+
fs11.cpSync(srcDir, destDir, { recursive: true });
|
|
1145
1424
|
return { success: true, pack_id: packId, upgraded: true };
|
|
1146
1425
|
}
|
|
1147
|
-
|
|
1426
|
+
fs11.cpSync(srcDir, destDir, { recursive: true });
|
|
1148
1427
|
const checksumVerified = verifyInstalledChecksum(packId, destDir);
|
|
1149
1428
|
return { success: true, pack_id: packId, checksum_verified: checksumVerified ?? void 0 };
|
|
1150
1429
|
}
|
|
@@ -1155,15 +1434,15 @@ function verifyInstalledChecksum(packId, destDir) {
|
|
|
1155
1434
|
return result.valid;
|
|
1156
1435
|
}
|
|
1157
1436
|
async function downloadPack(url) {
|
|
1158
|
-
const tmpDir =
|
|
1437
|
+
const tmpDir = fs11.mkdtempSync(path9.join(os2.tmpdir(), "datacore-pack-"));
|
|
1159
1438
|
try {
|
|
1160
1439
|
const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
|
|
1161
1440
|
if (!res.ok) return { error: `Download failed: HTTP ${res.status}` };
|
|
1162
1441
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
1163
|
-
const archivePath =
|
|
1164
|
-
|
|
1165
|
-
const extractDir =
|
|
1166
|
-
|
|
1442
|
+
const archivePath = path9.join(tmpDir, "pack.tar.gz");
|
|
1443
|
+
fs11.writeFileSync(archivePath, buffer);
|
|
1444
|
+
const extractDir = path9.join(tmpDir, "extracted");
|
|
1445
|
+
fs11.mkdirSync(extractDir);
|
|
1167
1446
|
execSync(`tar xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(extractDir)}`, { timeout: 1e4 });
|
|
1168
1447
|
const packRoot = findPackRoot(extractDir);
|
|
1169
1448
|
if (!packRoot) return { error: "Downloaded archive does not contain SKILL.md" };
|
|
@@ -1173,20 +1452,43 @@ async function downloadPack(url) {
|
|
|
1173
1452
|
}
|
|
1174
1453
|
}
|
|
1175
1454
|
function findPackRoot(dir) {
|
|
1176
|
-
if (
|
|
1177
|
-
for (const entry of
|
|
1455
|
+
if (fs11.existsSync(path9.join(dir, "SKILL.md"))) return dir;
|
|
1456
|
+
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
1178
1457
|
if (entry.isDirectory()) {
|
|
1179
|
-
const found = findPackRoot(
|
|
1458
|
+
const found = findPackRoot(path9.join(dir, entry.name));
|
|
1180
1459
|
if (found) return found;
|
|
1181
1460
|
}
|
|
1182
1461
|
}
|
|
1183
1462
|
return null;
|
|
1184
1463
|
}
|
|
1464
|
+
function resolvePackId(packId, packsDir) {
|
|
1465
|
+
const registryPack = packs_default.packs.find((p) => p.id === packId);
|
|
1466
|
+
if (!registryPack) {
|
|
1467
|
+
return { error: `Pack "${packId}" not found in registry. Use datacore.packs.discover to browse available packs.` };
|
|
1468
|
+
}
|
|
1469
|
+
if (registryPack.download_url) {
|
|
1470
|
+
return { error: `Pack "${packId}" must be installed via URL: ${registryPack.download_url}` };
|
|
1471
|
+
}
|
|
1472
|
+
const bundledDir = path9.join(
|
|
1473
|
+
path9.dirname(new URL(import.meta.url).pathname),
|
|
1474
|
+
"..",
|
|
1475
|
+
"packs",
|
|
1476
|
+
packId
|
|
1477
|
+
);
|
|
1478
|
+
if (fs11.existsSync(path9.join(bundledDir, "SKILL.md"))) {
|
|
1479
|
+
return { path: bundledDir };
|
|
1480
|
+
}
|
|
1481
|
+
const localDir = path9.join(packsDir, packId);
|
|
1482
|
+
if (fs11.existsSync(path9.join(localDir, "SKILL.md"))) {
|
|
1483
|
+
return { path: localDir };
|
|
1484
|
+
}
|
|
1485
|
+
return { error: `Pack "${packId}" is registered but not available locally. It may need to be downloaded manually.` };
|
|
1486
|
+
}
|
|
1185
1487
|
|
|
1186
1488
|
// src/tools/export.ts
|
|
1187
|
-
import * as
|
|
1188
|
-
import * as
|
|
1189
|
-
import * as
|
|
1489
|
+
import * as fs12 from "fs";
|
|
1490
|
+
import * as path10 from "path";
|
|
1491
|
+
import * as yaml5 from "js-yaml";
|
|
1190
1492
|
async function handleExport(args2, paths) {
|
|
1191
1493
|
const allEngrams = loadEngrams(paths.engramsPath);
|
|
1192
1494
|
let selected = allEngrams.filter((e) => e.status === "active");
|
|
@@ -1218,7 +1520,7 @@ async function handleExport(args2, paths) {
|
|
|
1218
1520
|
return { success: false, error: "No engrams match the filter criteria" };
|
|
1219
1521
|
}
|
|
1220
1522
|
const packId = args2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
|
|
1221
|
-
const packDir =
|
|
1523
|
+
const packDir = path10.join(paths.packsPath, packId);
|
|
1222
1524
|
if (!args2.confirm) {
|
|
1223
1525
|
return {
|
|
1224
1526
|
success: true,
|
|
@@ -1229,7 +1531,13 @@ async function handleExport(args2, paths) {
|
|
|
1229
1531
|
}
|
|
1230
1532
|
};
|
|
1231
1533
|
}
|
|
1232
|
-
|
|
1534
|
+
if (fs12.existsSync(packDir)) {
|
|
1535
|
+
return {
|
|
1536
|
+
success: false,
|
|
1537
|
+
error: `Pack directory already exists at ${packDir}. Remove it first or use a different name.`
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
fs12.mkdirSync(packDir, { recursive: true });
|
|
1233
1541
|
const skillContent = `---
|
|
1234
1542
|
name: "${args2.name}"
|
|
1235
1543
|
description: "${args2.description}"
|
|
@@ -1248,7 +1556,7 @@ ${args2.description}
|
|
|
1248
1556
|
|
|
1249
1557
|
Exported ${selected.length} engrams.
|
|
1250
1558
|
`;
|
|
1251
|
-
|
|
1559
|
+
fs12.writeFileSync(path10.join(packDir, "SKILL.md"), skillContent);
|
|
1252
1560
|
const exportEngrams = selected.map((e) => ({
|
|
1253
1561
|
id: e.id,
|
|
1254
1562
|
version: e.version,
|
|
@@ -1257,6 +1565,7 @@ Exported ${selected.length} engrams.
|
|
|
1257
1565
|
visibility: e.visibility,
|
|
1258
1566
|
statement: e.statement,
|
|
1259
1567
|
rationale: e.rationale,
|
|
1568
|
+
contraindications: e.contraindications,
|
|
1260
1569
|
tags: e.tags,
|
|
1261
1570
|
domain: e.domain,
|
|
1262
1571
|
status: "active",
|
|
@@ -1268,27 +1577,27 @@ Exported ${selected.length} engrams.
|
|
|
1268
1577
|
},
|
|
1269
1578
|
feedback_signals: { positive: 0, negative: 0 }
|
|
1270
1579
|
}));
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1580
|
+
fs12.writeFileSync(
|
|
1581
|
+
path10.join(packDir, "engrams.yaml"),
|
|
1582
|
+
yaml5.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
|
|
1274
1583
|
);
|
|
1275
1584
|
return { success: true, pack_path: packDir };
|
|
1276
1585
|
}
|
|
1277
1586
|
|
|
1278
1587
|
// src/modules.ts
|
|
1279
|
-
import * as
|
|
1280
|
-
import * as
|
|
1281
|
-
import * as
|
|
1588
|
+
import * as fs13 from "fs";
|
|
1589
|
+
import * as path11 from "path";
|
|
1590
|
+
import * as yaml6 from "js-yaml";
|
|
1282
1591
|
function discoverModules(storage2) {
|
|
1283
1592
|
const modules = [];
|
|
1284
1593
|
if (storage2.mode !== "full") return modules;
|
|
1285
|
-
const globalModulesDir =
|
|
1594
|
+
const globalModulesDir = path11.join(storage2.basePath, ".datacore", "modules");
|
|
1286
1595
|
modules.push(...scanModulesDir(globalModulesDir, "global"));
|
|
1287
1596
|
try {
|
|
1288
|
-
const entries =
|
|
1597
|
+
const entries = fs13.readdirSync(storage2.basePath);
|
|
1289
1598
|
for (const entry of entries) {
|
|
1290
1599
|
if (/^\d+-/.test(entry)) {
|
|
1291
|
-
const spaceModulesDir =
|
|
1600
|
+
const spaceModulesDir = path11.join(storage2.basePath, entry, ".datacore", "modules");
|
|
1292
1601
|
modules.push(...scanModulesDir(spaceModulesDir, "space", entry));
|
|
1293
1602
|
}
|
|
1294
1603
|
}
|
|
@@ -1298,16 +1607,16 @@ function discoverModules(storage2) {
|
|
|
1298
1607
|
}
|
|
1299
1608
|
function scanModulesDir(modulesDir, scope, spaceName) {
|
|
1300
1609
|
const modules = [];
|
|
1301
|
-
if (!
|
|
1610
|
+
if (!fs13.existsSync(modulesDir)) return modules;
|
|
1302
1611
|
try {
|
|
1303
|
-
const entries =
|
|
1612
|
+
const entries = fs13.readdirSync(modulesDir);
|
|
1304
1613
|
for (const entry of entries) {
|
|
1305
|
-
const modulePath =
|
|
1306
|
-
const manifestPath =
|
|
1307
|
-
if (!
|
|
1614
|
+
const modulePath = path11.join(modulesDir, entry);
|
|
1615
|
+
const manifestPath = path11.join(modulePath, "module.yaml");
|
|
1616
|
+
if (!fs13.existsSync(manifestPath)) continue;
|
|
1308
1617
|
try {
|
|
1309
|
-
const raw =
|
|
1310
|
-
const manifest =
|
|
1618
|
+
const raw = fs13.readFileSync(manifestPath, "utf-8");
|
|
1619
|
+
const manifest = yaml6.load(raw);
|
|
1311
1620
|
if (!manifest || !manifest.name) continue;
|
|
1312
1621
|
modules.push({
|
|
1313
1622
|
name: manifest.name,
|
|
@@ -1328,12 +1637,12 @@ async function loadModuleTools(modules, storage2) {
|
|
|
1328
1637
|
for (const mod of modules) {
|
|
1329
1638
|
const declaredTools = mod.manifest.provides?.tools;
|
|
1330
1639
|
if (!declaredTools || declaredTools.length === 0) continue;
|
|
1331
|
-
const toolsIndexPath =
|
|
1332
|
-
if (!
|
|
1640
|
+
const toolsIndexPath = path11.join(mod.modulePath, "tools", "index.js");
|
|
1641
|
+
if (!fs13.existsSync(toolsIndexPath)) continue;
|
|
1333
1642
|
try {
|
|
1334
1643
|
const toolsModule = await import(toolsIndexPath);
|
|
1335
1644
|
const moduleTools2 = toolsModule.tools || toolsModule.default?.tools || [];
|
|
1336
|
-
const dataPath = mod.scope === "space" && mod.spaceName ?
|
|
1645
|
+
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
1646
|
const context = {
|
|
1338
1647
|
storage: storage2,
|
|
1339
1648
|
modulePath: mod.modulePath,
|
|
@@ -1429,8 +1738,8 @@ async function handleModulesInfo(args2, storage2, cachedModules) {
|
|
|
1429
1738
|
}
|
|
1430
1739
|
|
|
1431
1740
|
// src/tools/modules-health.ts
|
|
1432
|
-
import * as
|
|
1433
|
-
import * as
|
|
1741
|
+
import * as fs14 from "fs";
|
|
1742
|
+
import * as path12 from "path";
|
|
1434
1743
|
async function handleModulesHealth(args2, storage2, cachedModules) {
|
|
1435
1744
|
const modules = cachedModules ?? discoverModules(storage2);
|
|
1436
1745
|
if (args2.module) {
|
|
@@ -1452,10 +1761,10 @@ async function handleModulesHealth(args2, storage2, cachedModules) {
|
|
|
1452
1761
|
async function checkModule(mod, storage2) {
|
|
1453
1762
|
const issues = [];
|
|
1454
1763
|
const manifest = mod.manifest;
|
|
1455
|
-
if (!
|
|
1764
|
+
if (!fs14.existsSync(path12.join(mod.modulePath, "SKILL.md"))) {
|
|
1456
1765
|
issues.push("Missing SKILL.md (ecosystem entry point)");
|
|
1457
1766
|
}
|
|
1458
|
-
if (!
|
|
1767
|
+
if (!fs14.existsSync(path12.join(mod.modulePath, "CLAUDE.base.md"))) {
|
|
1459
1768
|
issues.push("Missing CLAUDE.base.md (AI context)");
|
|
1460
1769
|
}
|
|
1461
1770
|
if (!manifest.manifest_version || manifest.manifest_version < 2) {
|
|
@@ -1471,8 +1780,8 @@ async function checkModule(mod, storage2) {
|
|
|
1471
1780
|
const provides = manifest.provides;
|
|
1472
1781
|
const declaredTools = provides?.tools || [];
|
|
1473
1782
|
if (declaredTools.length > 0) {
|
|
1474
|
-
const toolsIndex =
|
|
1475
|
-
if (!
|
|
1783
|
+
const toolsIndex = path12.join(mod.modulePath, "tools", "index.js");
|
|
1784
|
+
if (!fs14.existsSync(toolsIndex)) {
|
|
1476
1785
|
issues.push(`Declares ${declaredTools.length} tools but tools/index.js not found`);
|
|
1477
1786
|
} else {
|
|
1478
1787
|
try {
|
|
@@ -1491,13 +1800,13 @@ async function checkModule(mod, storage2) {
|
|
|
1491
1800
|
const suspectExts = [".db", ".sqlite", ".json"];
|
|
1492
1801
|
const suspectDirs = ["output", "data", "state"];
|
|
1493
1802
|
for (const dir of suspectDirs) {
|
|
1494
|
-
const fullPath =
|
|
1495
|
-
if (
|
|
1803
|
+
const fullPath = path12.join(mod.modulePath, dir);
|
|
1804
|
+
if (fs14.existsSync(fullPath) && fs14.statSync(fullPath).isDirectory()) {
|
|
1496
1805
|
issues.push(`Data dir '${dir}/' found in module code (should be in space data path)`);
|
|
1497
1806
|
}
|
|
1498
1807
|
}
|
|
1499
1808
|
try {
|
|
1500
|
-
const entries =
|
|
1809
|
+
const entries = fs14.readdirSync(mod.modulePath);
|
|
1501
1810
|
for (const entry of entries) {
|
|
1502
1811
|
if (suspectExts.some((ext) => entry.endsWith(ext))) {
|
|
1503
1812
|
issues.push(`Data file '${entry}' found in module code dir`);
|
|
@@ -1530,9 +1839,10 @@ async function handleForget(args2, engramsPath) {
|
|
|
1530
1839
|
}
|
|
1531
1840
|
if (args2.search) {
|
|
1532
1841
|
const searchLower = args2.search.toLowerCase();
|
|
1533
|
-
const
|
|
1842
|
+
const allMatches = engrams.filter((e) => e.status !== "retired").filter(
|
|
1534
1843
|
(e) => e.statement.toLowerCase().includes(searchLower) || e.id.toLowerCase().includes(searchLower) || e.tags.some((t) => t.toLowerCase().includes(searchLower))
|
|
1535
|
-
)
|
|
1844
|
+
);
|
|
1845
|
+
const matches = allMatches.slice(0, 100);
|
|
1536
1846
|
if (matches.length === 0) {
|
|
1537
1847
|
return { success: false, error: `No active engrams matching "${args2.search}"` };
|
|
1538
1848
|
}
|
|
@@ -1543,36 +1853,347 @@ async function handleForget(args2, engramsPath) {
|
|
|
1543
1853
|
saveEngrams(engramsPath, engrams);
|
|
1544
1854
|
return { success: true, retired: { id: engram.id, statement: engram.statement } };
|
|
1545
1855
|
}
|
|
1856
|
+
const truncated = allMatches.length > 100;
|
|
1546
1857
|
return {
|
|
1547
1858
|
success: false,
|
|
1548
1859
|
matches: matches.map((e) => ({ id: e.id, statement: e.statement })),
|
|
1549
|
-
|
|
1860
|
+
total_matches: allMatches.length,
|
|
1861
|
+
error: `${allMatches.length} matches found${truncated ? " (showing first 100)" : ""}. Specify an exact ID to retire.`
|
|
1550
1862
|
};
|
|
1551
1863
|
}
|
|
1552
1864
|
return { success: false, error: "Provide either id or search parameter" };
|
|
1553
1865
|
}
|
|
1554
1866
|
|
|
1555
1867
|
// src/tools/feedback.ts
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
const
|
|
1559
|
-
|
|
1560
|
-
|
|
1868
|
+
import * as path13 from "path";
|
|
1869
|
+
function findEngram(engramId, engramsPath, packsPath) {
|
|
1870
|
+
const personal = loadEngrams(engramsPath);
|
|
1871
|
+
const found = personal.find((e) => e.id === engramId);
|
|
1872
|
+
if (found) return { engram: found, source: "personal", personalEngrams: personal };
|
|
1873
|
+
const packs = loadAllPacks(packsPath);
|
|
1874
|
+
for (const pack of packs) {
|
|
1875
|
+
const packEngram = pack.engrams.find((e) => e.id === engramId);
|
|
1876
|
+
if (packEngram) {
|
|
1877
|
+
const packId = pack.manifest["x-datacore"]?.id;
|
|
1878
|
+
const packEngramsPath = packId ? path13.join(packsPath, packId, "engrams.yaml") : void 0;
|
|
1879
|
+
return { engram: packEngram, source: "pack", packEngrams: pack.engrams, packEngramsPath };
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
async function handleFeedback(args2, engramsPath, packsPath) {
|
|
1885
|
+
const pPath = packsPath ?? path13.join(path13.dirname(engramsPath), "packs");
|
|
1886
|
+
if (args2.signals && args2.signals.length > 0) {
|
|
1887
|
+
return handleBatchFeedback(args2.signals, engramsPath, pPath);
|
|
1888
|
+
}
|
|
1889
|
+
return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath, pPath);
|
|
1890
|
+
}
|
|
1891
|
+
async function handleSingleFeedback(engram_id, signal, comment, engramsPath, packsPath) {
|
|
1892
|
+
const found = findEngram(engram_id, engramsPath, packsPath);
|
|
1893
|
+
if (!found) {
|
|
1894
|
+
return {
|
|
1895
|
+
mode: "single",
|
|
1896
|
+
success: false,
|
|
1897
|
+
engram_id,
|
|
1898
|
+
signal,
|
|
1899
|
+
error: `Engram ${engram_id} not found`,
|
|
1900
|
+
_hints: buildHints({
|
|
1901
|
+
next: "Engram not found. Use datacore.search or datacore.status to find valid IDs.",
|
|
1902
|
+
related: ["datacore.search", "datacore.status"]
|
|
1903
|
+
})
|
|
1904
|
+
};
|
|
1561
1905
|
}
|
|
1562
1906
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1563
|
-
if (engram.
|
|
1907
|
+
if (!found.engram.feedback_signals) {
|
|
1908
|
+
found.engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
|
|
1564
1909
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1910
|
+
found.engram.feedback_signals[signal] += 1;
|
|
1911
|
+
found.engram.activation.last_accessed = today;
|
|
1912
|
+
if (found.source === "personal" && found.personalEngrams) {
|
|
1913
|
+
atomicWriteYaml(engramsPath, { engrams: found.personalEngrams });
|
|
1914
|
+
} else if (found.source === "pack" && found.packEngrams && found.packEngramsPath) {
|
|
1915
|
+
atomicWriteYaml(found.packEngramsPath, { engrams: found.packEngrams });
|
|
1567
1916
|
}
|
|
1568
|
-
engram.feedback_signals[args2.signal] += 1;
|
|
1569
|
-
engram.activation.last_accessed = today;
|
|
1570
|
-
atomicWriteYaml(engramsPath, { engrams });
|
|
1571
1917
|
return {
|
|
1918
|
+
mode: "single",
|
|
1572
1919
|
success: true,
|
|
1573
|
-
engram_id
|
|
1574
|
-
signal
|
|
1575
|
-
|
|
1920
|
+
engram_id,
|
|
1921
|
+
signal,
|
|
1922
|
+
source: found.source,
|
|
1923
|
+
feedback_signals: { ...found.engram.feedback_signals }
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
async function handleBatchFeedback(signals, engramsPath, packsPath) {
|
|
1927
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1928
|
+
const results = [];
|
|
1929
|
+
const summary = { positive: 0, negative: 0, neutral: 0 };
|
|
1930
|
+
const personal = loadEngrams(engramsPath);
|
|
1931
|
+
const packs = loadAllPacks(packsPath);
|
|
1932
|
+
let personalDirty = false;
|
|
1933
|
+
const dirtyPackFiles = /* @__PURE__ */ new Map();
|
|
1934
|
+
for (const { engram_id, signal } of signals) {
|
|
1935
|
+
let engram = personal.find((e) => e.id === engram_id);
|
|
1936
|
+
let source;
|
|
1937
|
+
if (engram) {
|
|
1938
|
+
source = "personal";
|
|
1939
|
+
} else {
|
|
1940
|
+
for (const pack of packs) {
|
|
1941
|
+
engram = pack.engrams.find((e) => e.id === engram_id);
|
|
1942
|
+
if (engram) {
|
|
1943
|
+
source = "pack";
|
|
1944
|
+
const packId = pack.manifest["x-datacore"]?.id;
|
|
1945
|
+
if (packId) {
|
|
1946
|
+
dirtyPackFiles.set(
|
|
1947
|
+
path13.join(packsPath, packId, "engrams.yaml"),
|
|
1948
|
+
pack.engrams
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
break;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
if (!engram || !source) {
|
|
1956
|
+
results.push({ engram_id, signal, success: false, error: `Engram ${engram_id} not found` });
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
if (!engram.feedback_signals) {
|
|
1960
|
+
engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
|
|
1961
|
+
}
|
|
1962
|
+
engram.feedback_signals[signal] += 1;
|
|
1963
|
+
engram.activation.last_accessed = today;
|
|
1964
|
+
summary[signal]++;
|
|
1965
|
+
if (source === "personal") personalDirty = true;
|
|
1966
|
+
results.push({ engram_id, signal, success: true, source });
|
|
1967
|
+
}
|
|
1968
|
+
if (personalDirty) {
|
|
1969
|
+
atomicWriteYaml(engramsPath, { engrams: personal });
|
|
1970
|
+
}
|
|
1971
|
+
for (const [filePath, engrams] of dirtyPackFiles) {
|
|
1972
|
+
atomicWriteYaml(filePath, { engrams });
|
|
1973
|
+
}
|
|
1974
|
+
return {
|
|
1975
|
+
mode: "batch",
|
|
1976
|
+
results,
|
|
1977
|
+
summary,
|
|
1978
|
+
_hints: buildHints({
|
|
1979
|
+
next: `Batch feedback recorded: ${summary.positive} positive, ${summary.negative} negative, ${summary.neutral} neutral.`,
|
|
1980
|
+
related: ["datacore.session.end", "datacore.status"]
|
|
1981
|
+
})
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// src/tools/session-start.ts
|
|
1986
|
+
import * as fs15 from "fs";
|
|
1987
|
+
import * as path14 from "path";
|
|
1988
|
+
async function handleSessionStart(args2, storage2, bridge) {
|
|
1989
|
+
let engrams = null;
|
|
1990
|
+
if (args2.task) {
|
|
1991
|
+
const injectResult = await handleInject(
|
|
1992
|
+
{ prompt: args2.task, scope: args2.tags?.length ? `tags:${args2.tags.join(",")}` : void 0 },
|
|
1993
|
+
{ engramsPath: storage2.engramsPath, packsPath: storage2.packsPath }
|
|
1994
|
+
);
|
|
1995
|
+
if (injectResult.count > 0) {
|
|
1996
|
+
engrams = { text: injectResult.text, count: injectResult.count };
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
const { date: today } = localDate();
|
|
2000
|
+
const journalFile = path14.join(storage2.journalPath, `${today}.md`);
|
|
2001
|
+
const journal_today = fs15.existsSync(journalFile) ? fs15.readFileSync(journalFile, "utf8") : null;
|
|
2002
|
+
const allEngrams = loadEngrams(storage2.engramsPath);
|
|
2003
|
+
const pending_candidates = allEngrams.filter((e) => e.status === "candidate").length;
|
|
2004
|
+
const recommendations = [];
|
|
2005
|
+
if (pending_candidates > 0) {
|
|
2006
|
+
recommendations.push(`${pending_candidates} candidate engram(s) awaiting review. Use datacore.promote to activate.`);
|
|
2007
|
+
}
|
|
2008
|
+
if (!journal_today) {
|
|
2009
|
+
recommendations.push("No journal entry today. Use datacore.capture to start one.");
|
|
2010
|
+
}
|
|
2011
|
+
const hints = args2.task ? buildHints({
|
|
2012
|
+
next: "Work on your task. End with datacore.session.end.",
|
|
2013
|
+
related: ["datacore.session.end", "datacore.feedback"]
|
|
2014
|
+
}) : buildHints({
|
|
2015
|
+
next: "No task specified \u2014 showing journal and candidates only. Call datacore.inject when ready.",
|
|
2016
|
+
related: ["datacore.inject", "datacore.session.end"]
|
|
2017
|
+
});
|
|
2018
|
+
const activeCount = allEngrams.filter((e) => e.status === "active").length;
|
|
2019
|
+
const guide = activeCount === 0 ? SESSION_GUIDE_FULL : SESSION_GUIDE_SHORT;
|
|
2020
|
+
return { engrams, journal_today, pending_candidates, recommendations, guide, _hints: hints };
|
|
2021
|
+
}
|
|
2022
|
+
var SESSION_GUIDE_FULL = `## Datacore Quick Start
|
|
2023
|
+
|
|
2024
|
+
Datacore gives you persistent memory through **engrams** \u2014 knowledge that gets injected into context when relevant.
|
|
2025
|
+
|
|
2026
|
+
### Session Workflow
|
|
2027
|
+
1. **session.start** (you just called this) \u2014 get context
|
|
2028
|
+
2. Work on your task. Use **recall** to search everything, **search** for files.
|
|
2029
|
+
3. **feedback** \u2014 rate which injected engrams helped (strengthens useful ones)
|
|
2030
|
+
4. **session.end** \u2014 capture summary + suggest new engrams
|
|
2031
|
+
|
|
2032
|
+
### Key Tools
|
|
2033
|
+
- **learn** \u2014 record a reusable insight (creates candidate engram)
|
|
2034
|
+
- **promote** \u2014 activate candidate engrams so they appear in future sessions
|
|
2035
|
+
- **capture** \u2014 write a journal entry or knowledge note
|
|
2036
|
+
- **ingest** \u2014 import text and extract engram suggestions
|
|
2037
|
+
- **status** \u2014 system health and actionable recommendations
|
|
2038
|
+
- **forget** \u2014 retire an engram you no longer need
|
|
2039
|
+
|
|
2040
|
+
### How Engrams Work
|
|
2041
|
+
learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
|
|
2042
|
+
Positive feedback strengthens engrams. Unused ones naturally decay.`;
|
|
2043
|
+
var SESSION_GUIDE_SHORT = `Session started. Workflow: work \u2192 feedback \u2192 session.end.`;
|
|
2044
|
+
|
|
2045
|
+
// src/tools/session-end.ts
|
|
2046
|
+
async function handleSessionEnd(args2, storage2) {
|
|
2047
|
+
const captureResult = await handleCapture(
|
|
2048
|
+
{ type: "journal", content: args2.summary, tags: args2.tags },
|
|
2049
|
+
storage2
|
|
2050
|
+
);
|
|
2051
|
+
let engramsCreated = 0;
|
|
2052
|
+
if (args2.engram_suggestions?.length) {
|
|
2053
|
+
for (const suggestion of args2.engram_suggestions) {
|
|
2054
|
+
await handleLearn(
|
|
2055
|
+
{ statement: suggestion.statement, type: suggestion.type },
|
|
2056
|
+
storage2.engramsPath
|
|
2057
|
+
);
|
|
2058
|
+
engramsCreated++;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
const autoPromote = getConfig().engrams.auto_promote;
|
|
2062
|
+
const statusLabel = autoPromote ? "active" : "candidates";
|
|
2063
|
+
return {
|
|
2064
|
+
journal_path: captureResult.path ?? null,
|
|
2065
|
+
engrams_created: engramsCreated,
|
|
2066
|
+
_hints: buildHints({
|
|
2067
|
+
next: engramsCreated > 0 ? `Session captured. ${engramsCreated} engram(s) created as ${statusLabel}.` : "Session captured.",
|
|
2068
|
+
related: ["datacore.session.start", "datacore.status"]
|
|
2069
|
+
})
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
// src/tools/recall.ts
|
|
2074
|
+
async function handleRecall(args2, storage2, bridge) {
|
|
2075
|
+
const sources = args2.sources ?? ["engrams", "journal", "knowledge"];
|
|
2076
|
+
const limit = args2.limit ?? 10;
|
|
2077
|
+
const result = {};
|
|
2078
|
+
let fallbackWarning;
|
|
2079
|
+
if (sources.includes("engrams")) {
|
|
2080
|
+
const engrams = loadEngrams(storage2.engramsPath);
|
|
2081
|
+
const topicWords = args2.topic.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
2082
|
+
const scored = [];
|
|
2083
|
+
for (const e of engrams) {
|
|
2084
|
+
if (e.status === "retired") continue;
|
|
2085
|
+
const text = `${e.statement} ${e.tags.join(" ")}`.toLowerCase();
|
|
2086
|
+
let score = 0;
|
|
2087
|
+
for (const word of topicWords) {
|
|
2088
|
+
if (text.includes(word)) score++;
|
|
2089
|
+
}
|
|
2090
|
+
if (score > 0) {
|
|
2091
|
+
scored.push({ id: e.id, statement: e.statement, score });
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
scored.sort((a, b) => b.score - a.score);
|
|
2095
|
+
const engramResults = scored.slice(0, limit);
|
|
2096
|
+
if (engramResults.length > 0) {
|
|
2097
|
+
result.engrams = engramResults;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (sources.includes("journal")) {
|
|
2101
|
+
const searchResult = await handleSearch(
|
|
2102
|
+
{ query: args2.topic, scope: "journal", limit },
|
|
2103
|
+
{ journalPath: storage2.journalPath, knowledgePath: storage2.knowledgePath },
|
|
2104
|
+
bridge
|
|
2105
|
+
);
|
|
2106
|
+
if (searchResult.results.length > 0) {
|
|
2107
|
+
result.journal = searchResult.results.map((r) => ({
|
|
2108
|
+
path: r.path,
|
|
2109
|
+
snippet: r.snippet,
|
|
2110
|
+
title: r.title,
|
|
2111
|
+
date: r.date,
|
|
2112
|
+
score: r.score
|
|
2113
|
+
}));
|
|
2114
|
+
}
|
|
2115
|
+
if (searchResult.fallback_warning) {
|
|
2116
|
+
fallbackWarning = searchResult.fallback_warning;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
if (sources.includes("knowledge")) {
|
|
2120
|
+
const searchResult = await handleSearch(
|
|
2121
|
+
{ query: args2.topic, scope: "knowledge", limit },
|
|
2122
|
+
{ journalPath: storage2.journalPath, knowledgePath: storage2.knowledgePath },
|
|
2123
|
+
bridge
|
|
2124
|
+
);
|
|
2125
|
+
if (searchResult.results.length > 0) {
|
|
2126
|
+
result.knowledge = searchResult.results.map((r) => ({
|
|
2127
|
+
path: r.path,
|
|
2128
|
+
snippet: r.snippet,
|
|
2129
|
+
title: r.title,
|
|
2130
|
+
date: r.date,
|
|
2131
|
+
score: r.score
|
|
2132
|
+
}));
|
|
2133
|
+
}
|
|
2134
|
+
if (searchResult.fallback_warning) {
|
|
2135
|
+
fallbackWarning = searchResult.fallback_warning;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
if (fallbackWarning) {
|
|
2139
|
+
result.fallback_warning = fallbackWarning;
|
|
2140
|
+
}
|
|
2141
|
+
result._hints = buildHints({
|
|
2142
|
+
next: "Use datacore.feedback on helpful engrams, or datacore.learn to create new ones.",
|
|
2143
|
+
related: ["datacore.feedback", "datacore.learn"]
|
|
2144
|
+
});
|
|
2145
|
+
return result;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// src/tools/promote.ts
|
|
2149
|
+
async function handlePromote(args2, engramsPath) {
|
|
2150
|
+
const targetIds = args2.ids ?? (args2.id ? [args2.id] : []);
|
|
2151
|
+
if (targetIds.length === 0) {
|
|
2152
|
+
return {
|
|
2153
|
+
success: false,
|
|
2154
|
+
promoted: [],
|
|
2155
|
+
errors: [{ id: "", error: "At least one engram ID required (id or ids)" }],
|
|
2156
|
+
_hints: buildHints({
|
|
2157
|
+
next: "Provide an engram ID. Use datacore.search or datacore.status to find valid IDs.",
|
|
2158
|
+
related: ["datacore.search", "datacore.status"]
|
|
2159
|
+
})
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
const engrams = loadEngrams(engramsPath);
|
|
2163
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2164
|
+
const promoted = [];
|
|
2165
|
+
const errors = [];
|
|
2166
|
+
for (const id of targetIds) {
|
|
2167
|
+
const engram = engrams.find((e) => e.id === id);
|
|
2168
|
+
if (!engram) {
|
|
2169
|
+
errors.push({ id, error: "Engram not found" });
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
if (engram.status === "active") {
|
|
2173
|
+
errors.push({ id, error: "Already active" });
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
if (engram.status === "retired") {
|
|
2177
|
+
errors.push({ id, error: "Cannot promote retired engram" });
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
engram.status = "active";
|
|
2181
|
+
engram.activation.retrieval_strength = 0.7;
|
|
2182
|
+
engram.activation.storage_strength = 1;
|
|
2183
|
+
engram.activation.last_accessed = today;
|
|
2184
|
+
promoted.push({ id: engram.id, statement: engram.statement });
|
|
2185
|
+
}
|
|
2186
|
+
if (promoted.length > 0) {
|
|
2187
|
+
atomicWriteYaml(engramsPath, { engrams });
|
|
2188
|
+
}
|
|
2189
|
+
return {
|
|
2190
|
+
success: errors.length === 0,
|
|
2191
|
+
promoted,
|
|
2192
|
+
errors,
|
|
2193
|
+
_hints: buildHints({
|
|
2194
|
+
next: promoted.length > 0 ? `Promoted ${promoted.length} engram(s). They will now appear in inject results.` : "No engrams were promoted. Check the errors above.",
|
|
2195
|
+
related: ["datacore.inject", "datacore.status"]
|
|
2196
|
+
})
|
|
1576
2197
|
};
|
|
1577
2198
|
}
|
|
1578
2199
|
|
|
@@ -1582,8 +2203,8 @@ import {
|
|
|
1582
2203
|
ReadResourceRequestSchema,
|
|
1583
2204
|
ListResourceTemplatesRequestSchema
|
|
1584
2205
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1585
|
-
import * as
|
|
1586
|
-
import * as
|
|
2206
|
+
import * as fs16 from "fs";
|
|
2207
|
+
import * as path15 from "path";
|
|
1587
2208
|
function registerResources(server, storage2) {
|
|
1588
2209
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
1589
2210
|
resources: [
|
|
@@ -1604,6 +2225,12 @@ function registerResources(server, storage2) {
|
|
|
1604
2225
|
name: "Today's Journal",
|
|
1605
2226
|
description: "Today's journal entry",
|
|
1606
2227
|
mimeType: "text/markdown"
|
|
2228
|
+
},
|
|
2229
|
+
{
|
|
2230
|
+
uri: "datacore://guide",
|
|
2231
|
+
name: "Datacore Agent Guide",
|
|
2232
|
+
description: "Workflow guide for AI agents: session lifecycle, engram lifecycle, tool reference",
|
|
2233
|
+
mimeType: "text/markdown"
|
|
1607
2234
|
}
|
|
1608
2235
|
]
|
|
1609
2236
|
}));
|
|
@@ -1646,14 +2273,23 @@ function registerResources(server, storage2) {
|
|
|
1646
2273
|
}]
|
|
1647
2274
|
};
|
|
1648
2275
|
}
|
|
2276
|
+
if (uri === "datacore://guide") {
|
|
2277
|
+
return {
|
|
2278
|
+
contents: [{
|
|
2279
|
+
uri,
|
|
2280
|
+
mimeType: "text/markdown",
|
|
2281
|
+
text: AGENT_GUIDE
|
|
2282
|
+
}]
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
1649
2285
|
const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
|
|
1650
2286
|
if (journalMatch) {
|
|
1651
2287
|
const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
|
|
1652
|
-
const filePath =
|
|
1653
|
-
if (!
|
|
2288
|
+
const filePath = path15.join(storage2.journalPath, `${dateStr}.md`);
|
|
2289
|
+
if (!fs16.existsSync(filePath)) {
|
|
1654
2290
|
return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
|
|
1655
2291
|
}
|
|
1656
|
-
return { contents: [{ uri, mimeType: "text/markdown", text:
|
|
2292
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: fs16.readFileSync(filePath, "utf8") }] };
|
|
1657
2293
|
}
|
|
1658
2294
|
const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
|
|
1659
2295
|
if (engramMatch) {
|
|
@@ -1673,6 +2309,40 @@ function registerResources(server, storage2) {
|
|
|
1673
2309
|
throw new Error(`Unknown resource: ${uri}`);
|
|
1674
2310
|
});
|
|
1675
2311
|
}
|
|
2312
|
+
var AGENT_GUIDE = `# Datacore Agent Guide
|
|
2313
|
+
|
|
2314
|
+
## Session Lifecycle
|
|
2315
|
+
1. datacore.session.start \u2014 Get relevant engrams + today's context
|
|
2316
|
+
2. Work on task, use datacore.recall or datacore.search as needed
|
|
2317
|
+
3. datacore.feedback \u2014 Rate which injected engrams helped (batch supported)
|
|
2318
|
+
4. datacore.session.end \u2014 Capture summary + engram suggestions
|
|
2319
|
+
|
|
2320
|
+
## Engram Lifecycle
|
|
2321
|
+
- datacore.learn creates candidate engrams (or active if auto_promote enabled)
|
|
2322
|
+
- datacore.promote activates candidates so they appear in inject results
|
|
2323
|
+
- datacore.feedback with positive signals strengthens injection priority
|
|
2324
|
+
- datacore.forget retires engrams permanently
|
|
2325
|
+
- Unused engrams naturally decay over time
|
|
2326
|
+
|
|
2327
|
+
## Quick Reference
|
|
2328
|
+
| Tool | Purpose |
|
|
2329
|
+
|------|---------|
|
|
2330
|
+
| session.start | Begin session with context injection |
|
|
2331
|
+
| session.end | End session with journal + engrams |
|
|
2332
|
+
| learn | Create engram from knowledge statement |
|
|
2333
|
+
| promote | Activate candidate engrams |
|
|
2334
|
+
| inject | Get relevant engrams for specific task |
|
|
2335
|
+
| recall | Search all sources (engrams + journal + knowledge) |
|
|
2336
|
+
| capture | Write journal entry or knowledge note |
|
|
2337
|
+
| search | Keyword/semantic file search |
|
|
2338
|
+
| ingest | Ingest text + extract engram suggestions |
|
|
2339
|
+
| feedback | Rate engrams (single or batch) |
|
|
2340
|
+
| forget | Retire an engram |
|
|
2341
|
+
| status | System health + actionable recommendations |
|
|
2342
|
+
| packs.discover | Browse available engram packs |
|
|
2343
|
+
| packs.install | Install or upgrade a pack |
|
|
2344
|
+
| packs.export | Export engrams as shareable pack |
|
|
2345
|
+
`;
|
|
1676
2346
|
function notifyEngramsChanged(server) {
|
|
1677
2347
|
try {
|
|
1678
2348
|
server.sendResourceUpdated?.({ uri: "datacore://engrams/active" });
|
|
@@ -1680,10 +2350,178 @@ function notifyEngramsChanged(server) {
|
|
|
1680
2350
|
}
|
|
1681
2351
|
}
|
|
1682
2352
|
|
|
2353
|
+
// src/prompts.ts
|
|
2354
|
+
import {
|
|
2355
|
+
ListPromptsRequestSchema,
|
|
2356
|
+
GetPromptRequestSchema
|
|
2357
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
2358
|
+
var PROMPTS = [
|
|
2359
|
+
{
|
|
2360
|
+
name: "datacore-session",
|
|
2361
|
+
title: "Start a Datacore session",
|
|
2362
|
+
description: "Begin a working session with Datacore. Injects relevant context, shows today's journal, and guides you through the session lifecycle.",
|
|
2363
|
+
arguments: [
|
|
2364
|
+
{ name: "task", description: "What you are working on (optional \u2014 triggers engram injection)", required: false }
|
|
2365
|
+
],
|
|
2366
|
+
messages: (args2) => [{
|
|
2367
|
+
role: "user",
|
|
2368
|
+
content: {
|
|
2369
|
+
type: "text",
|
|
2370
|
+
text: `Start a new Datacore session.${args2.task ? ` Task: ${args2.task}` : ""}
|
|
2371
|
+
|
|
2372
|
+
Call datacore.session.start${args2.task ? ` with task: "${args2.task}"` : ""} to begin. This will:
|
|
2373
|
+
- Inject relevant engrams (learned knowledge) for the task
|
|
2374
|
+
- Show today's journal entry if one exists
|
|
2375
|
+
- List any candidate engrams awaiting review
|
|
2376
|
+
|
|
2377
|
+
When done working, call datacore.session.end with a summary and any new learnings.
|
|
2378
|
+
|
|
2379
|
+
Session lifecycle:
|
|
2380
|
+
1. datacore.session.start \u2192 get context
|
|
2381
|
+
2. Work on task (use datacore.recall or datacore.search as needed)
|
|
2382
|
+
3. datacore.feedback \u2192 rate which injected engrams were helpful
|
|
2383
|
+
4. datacore.session.end \u2192 capture summary + new engrams`
|
|
2384
|
+
}
|
|
2385
|
+
}]
|
|
2386
|
+
},
|
|
2387
|
+
{
|
|
2388
|
+
name: "datacore-learn",
|
|
2389
|
+
title: "Teach Datacore something",
|
|
2390
|
+
description: "Record a reusable learning as an engram. Covers the full engram lifecycle: create, review, activate, reinforce.",
|
|
2391
|
+
arguments: [
|
|
2392
|
+
{ name: "statement", description: 'The knowledge to record (e.g., "Always run tests before deploying")', required: true }
|
|
2393
|
+
],
|
|
2394
|
+
messages: (args2) => [{
|
|
2395
|
+
role: "user",
|
|
2396
|
+
content: {
|
|
2397
|
+
type: "text",
|
|
2398
|
+
text: `Record this learning in Datacore: "${args2.statement || "..."}"
|
|
2399
|
+
|
|
2400
|
+
Call datacore.learn with the statement. This creates a candidate engram.
|
|
2401
|
+
|
|
2402
|
+
Engram lifecycle:
|
|
2403
|
+
1. datacore.learn \u2192 creates candidate (not yet active)
|
|
2404
|
+
2. datacore.promote \u2192 activates it so it appears in future inject results
|
|
2405
|
+
3. datacore.inject \u2192 retrieves relevant engrams when working on tasks
|
|
2406
|
+
4. datacore.feedback \u2192 positive signals strengthen it, negative signals weaken it
|
|
2407
|
+
5. datacore.forget \u2192 retires it permanently if no longer useful
|
|
2408
|
+
|
|
2409
|
+
Engrams that prove useful get reinforced over time. Unused ones naturally decay.`
|
|
2410
|
+
}
|
|
2411
|
+
}]
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
name: "datacore-guide",
|
|
2415
|
+
title: "How to use Datacore",
|
|
2416
|
+
description: "Complete guide to Datacore tools, workflows, and concepts. Read this to understand the system.",
|
|
2417
|
+
messages: () => [{
|
|
2418
|
+
role: "assistant",
|
|
2419
|
+
content: {
|
|
2420
|
+
type: "text",
|
|
2421
|
+
text: `# Datacore \u2014 Persistent Memory for AI
|
|
2422
|
+
|
|
2423
|
+
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.
|
|
2424
|
+
|
|
2425
|
+
## Core Concepts
|
|
2426
|
+
|
|
2427
|
+
**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.
|
|
2428
|
+
|
|
2429
|
+
**Journal** is your session log. Each session captures what happened, what was learned.
|
|
2430
|
+
|
|
2431
|
+
**Knowledge** is ingested reference material \u2014 articles, notes, documents broken into searchable pieces.
|
|
2432
|
+
|
|
2433
|
+
## Session Workflow
|
|
2434
|
+
|
|
2435
|
+
Every session follows this pattern:
|
|
2436
|
+
|
|
2437
|
+
1. **datacore.session.start** \u2014 Injects relevant engrams + shows today's journal
|
|
2438
|
+
2. **Work** \u2014 Use datacore.recall or datacore.search to find information
|
|
2439
|
+
3. **datacore.feedback** \u2014 Rate which engrams were helpful (strengthens good ones)
|
|
2440
|
+
4. **datacore.session.end** \u2014 Capture summary + suggest new engrams
|
|
2441
|
+
|
|
2442
|
+
## Tool Reference
|
|
2443
|
+
|
|
2444
|
+
### Session
|
|
2445
|
+
| Tool | What it does |
|
|
2446
|
+
|------|-------------|
|
|
2447
|
+
| **session.start** | Begin session, inject context, show journal |
|
|
2448
|
+
| **session.end** | End session, capture journal + new engrams |
|
|
2449
|
+
|
|
2450
|
+
### Core
|
|
2451
|
+
| Tool | What it does |
|
|
2452
|
+
|------|-------------|
|
|
2453
|
+
| **capture** | Write a journal entry or knowledge note |
|
|
2454
|
+
| **learn** | Create an engram (starts as candidate) |
|
|
2455
|
+
| **promote** | Activate candidate engrams |
|
|
2456
|
+
| **inject** | Get relevant engrams for a specific task |
|
|
2457
|
+
| **recall** | Search everything (engrams + journal + knowledge) |
|
|
2458
|
+
| **search** | Search journal and knowledge files |
|
|
2459
|
+
| **ingest** | Ingest text, extract engram suggestions |
|
|
2460
|
+
| **status** | System health + actionable recommendations |
|
|
2461
|
+
|
|
2462
|
+
### Engram Management
|
|
2463
|
+
| Tool | What it does |
|
|
2464
|
+
|------|-------------|
|
|
2465
|
+
| **feedback** | Rate engrams: positive/negative/neutral (single or batch) |
|
|
2466
|
+
| **forget** | Retire an engram permanently |
|
|
2467
|
+
|
|
2468
|
+
### Packs (Shareable Knowledge)
|
|
2469
|
+
| Tool | What it does |
|
|
2470
|
+
|------|-------------|
|
|
2471
|
+
| **packs.discover** | Browse available engram packs |
|
|
2472
|
+
| **packs.install** | Install a pack |
|
|
2473
|
+
| **packs.export** | Export your engrams as a pack |
|
|
2474
|
+
|
|
2475
|
+
## Engram Lifecycle
|
|
2476
|
+
|
|
2477
|
+
\`\`\`
|
|
2478
|
+
learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
|
|
2479
|
+
\u2192 forget (retire)
|
|
2480
|
+
\`\`\`
|
|
2481
|
+
|
|
2482
|
+
- **candidate**: Created but not yet active. Won't appear in inject results.
|
|
2483
|
+
- **active**: Appears in inject results when relevant.
|
|
2484
|
+
- **retired**: Permanently removed from injection.
|
|
2485
|
+
|
|
2486
|
+
Feedback matters: positive signals increase retrieval strength, negative signals decrease it. Engrams that are never accessed naturally decay over time.
|
|
2487
|
+
|
|
2488
|
+
## Tips
|
|
2489
|
+
|
|
2490
|
+
- Start every session with **session.start** \u2014 it gives you relevant context
|
|
2491
|
+
- End every session with **session.end** \u2014 it captures what you learned
|
|
2492
|
+
- Use **feedback** after getting injected engrams \u2014 this is how Datacore learns what's useful
|
|
2493
|
+
- Use **recall** for broad searches across all sources, **search** for targeted file searches
|
|
2494
|
+
- Check **status** periodically \u2014 it shows actionable recommendations`
|
|
2495
|
+
}
|
|
2496
|
+
}]
|
|
2497
|
+
}
|
|
2498
|
+
];
|
|
2499
|
+
function registerPrompts(server) {
|
|
2500
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
2501
|
+
prompts: PROMPTS.map((p) => ({
|
|
2502
|
+
name: p.name,
|
|
2503
|
+
title: p.title,
|
|
2504
|
+
description: p.description,
|
|
2505
|
+
arguments: p.arguments
|
|
2506
|
+
}))
|
|
2507
|
+
}));
|
|
2508
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
2509
|
+
const { name, arguments: promptArgs } = request.params;
|
|
2510
|
+
const prompt = PROMPTS.find((p) => p.name === name);
|
|
2511
|
+
if (!prompt) {
|
|
2512
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
2513
|
+
}
|
|
2514
|
+
return {
|
|
2515
|
+
description: prompt.description,
|
|
2516
|
+
messages: prompt.messages(promptArgs ?? {})
|
|
2517
|
+
};
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
|
|
1683
2521
|
// src/datacortex.ts
|
|
1684
2522
|
import { execFile } from "child_process";
|
|
1685
|
-
import * as
|
|
1686
|
-
import * as
|
|
2523
|
+
import * as fs17 from "fs";
|
|
2524
|
+
import * as path16 from "path";
|
|
1687
2525
|
var DatacortexBridge = class {
|
|
1688
2526
|
pythonPath;
|
|
1689
2527
|
scriptPath;
|
|
@@ -1693,11 +2531,11 @@ var DatacortexBridge = class {
|
|
|
1693
2531
|
}
|
|
1694
2532
|
findBridgeScript(datacorePath) {
|
|
1695
2533
|
const candidates = [
|
|
1696
|
-
|
|
1697
|
-
|
|
2534
|
+
path16.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
|
|
2535
|
+
path16.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
|
|
1698
2536
|
];
|
|
1699
2537
|
for (const candidate of candidates) {
|
|
1700
|
-
if (
|
|
2538
|
+
if (fs17.existsSync(candidate)) return candidate;
|
|
1701
2539
|
}
|
|
1702
2540
|
return null;
|
|
1703
2541
|
}
|
|
@@ -1760,22 +2598,25 @@ var datacortexBridge = null;
|
|
|
1760
2598
|
function createServer() {
|
|
1761
2599
|
const server = new Server(
|
|
1762
2600
|
{ name: "datacore-mcp", version: currentVersion },
|
|
1763
|
-
{ capabilities: { tools: {}, logging: {}, resources: { subscribe: true } } }
|
|
2601
|
+
{ capabilities: { tools: {}, logging: {}, resources: { subscribe: true }, prompts: {} } }
|
|
1764
2602
|
);
|
|
1765
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2603
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2604
|
+
const coreTools = storage.mode === "core" ? TOOLS.filter((t) => !t.name.startsWith("datacore.modules.")) : TOOLS;
|
|
2605
|
+
return {
|
|
2606
|
+
tools: [
|
|
2607
|
+
...coreTools.map((t) => ({
|
|
2608
|
+
name: t.name,
|
|
2609
|
+
description: t.description,
|
|
2610
|
+
inputSchema: zodToJsonSchema(t.inputSchema)
|
|
2611
|
+
})),
|
|
2612
|
+
...moduleTools.map((t) => ({
|
|
2613
|
+
name: t.fullName,
|
|
2614
|
+
description: t.definition.description,
|
|
2615
|
+
inputSchema: zodToJsonSchema(t.definition.inputSchema)
|
|
2616
|
+
}))
|
|
2617
|
+
]
|
|
2618
|
+
};
|
|
2619
|
+
});
|
|
1779
2620
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1780
2621
|
const { name, arguments: args2 } = request.params;
|
|
1781
2622
|
try {
|
|
@@ -1795,10 +2636,11 @@ function createServer() {
|
|
|
1795
2636
|
});
|
|
1796
2637
|
logger.setServer(server);
|
|
1797
2638
|
registerResources(server, storage);
|
|
2639
|
+
registerPrompts(server);
|
|
1798
2640
|
serverRef = server;
|
|
1799
2641
|
return server;
|
|
1800
2642
|
}
|
|
1801
|
-
var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback"]);
|
|
2643
|
+
var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote"]);
|
|
1802
2644
|
async function routeTool(name, args2) {
|
|
1803
2645
|
const coreTool = TOOLS.find((t) => t.name === name);
|
|
1804
2646
|
if (coreTool) {
|
|
@@ -1827,7 +2669,19 @@ async function routeTool(name, args2) {
|
|
|
1827
2669
|
result = await handleForget(validated, storage.engramsPath);
|
|
1828
2670
|
break;
|
|
1829
2671
|
case "datacore.feedback":
|
|
1830
|
-
result = await handleFeedback(validated, storage.engramsPath);
|
|
2672
|
+
result = await handleFeedback(validated, storage.engramsPath, storage.packsPath);
|
|
2673
|
+
break;
|
|
2674
|
+
case "datacore.session.start":
|
|
2675
|
+
result = await handleSessionStart(validated, storage, datacortexBridge);
|
|
2676
|
+
break;
|
|
2677
|
+
case "datacore.session.end":
|
|
2678
|
+
result = await handleSessionEnd(validated, storage);
|
|
2679
|
+
break;
|
|
2680
|
+
case "datacore.recall":
|
|
2681
|
+
result = await handleRecall(validated, { engramsPath: storage.engramsPath, journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
|
|
2682
|
+
break;
|
|
2683
|
+
case "datacore.promote":
|
|
2684
|
+
result = await handlePromote(validated, storage.engramsPath);
|
|
1831
2685
|
break;
|
|
1832
2686
|
case "datacore.packs.discover":
|
|
1833
2687
|
result = handleDiscover(validated, storage.packsPath);
|
|
@@ -1890,6 +2744,7 @@ async function initStorage() {
|
|
|
1890
2744
|
const result = initCore(storage.basePath);
|
|
1891
2745
|
isFirstRun = result.isFirstRun;
|
|
1892
2746
|
}
|
|
2747
|
+
loadConfig(storage.basePath, storage.mode);
|
|
1893
2748
|
if (storage.mode === "full") {
|
|
1894
2749
|
discoveredModules = discoverModules(storage);
|
|
1895
2750
|
moduleTools = await loadModuleTools(discoveredModules, storage);
|