@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/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: 1\n");
71
+ fs.writeFileSync(configPath, "# Datacore MCP configuration\nversion: 2\n# engrams:\n# auto_promote: false\n# packs:\n# trusted_publishers: []\n# search:\n# max_results: 20\n# snippet_length: 500\n# hints:\n# enabled: true\n");
72
72
  }
73
+ generateContextFiles(basePath);
73
74
  copyStarterPacks(basePath);
74
75
  return { isFirstRun: isFirstRun2 };
75
76
  }
77
+ function generateContextFiles(basePath) {
78
+ const files = [
79
+ { rel: "CLAUDE.md", content: CONTEXT_CLAUDE },
80
+ { rel: "AGENTS.md", content: CONTEXT_AGENTS },
81
+ { rel: ".cursorrules", content: CONTEXT_CURSORRULES },
82
+ { rel: ".github/copilot-instructions.md", content: CONTEXT_COPILOT }
83
+ ];
84
+ for (const { rel, content } of files) {
85
+ const filePath = path.join(basePath, rel);
86
+ if (!fs.existsSync(filePath)) {
87
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
88
+ fs.writeFileSync(filePath, content);
89
+ }
90
+ }
91
+ }
92
+ var DATACORE_GUIDE = `Datacore gives AI assistants persistent memory through **engrams** \u2014 typed knowledge units
93
+ that get injected into context when relevant.
94
+
95
+ ## Session Workflow
96
+
97
+ 1. **datacore.session.start** \u2014 Call this first. Gets relevant engrams + today's journal.
98
+ 2. Work on the task. Use **datacore.recall** to search everything.
99
+ 3. **datacore.feedback** \u2014 Rate which injected engrams were helpful.
100
+ 4. **datacore.session.end** \u2014 Capture summary + suggest new engrams.
101
+
102
+ ## Key Tools
103
+
104
+ | Tool | Purpose |
105
+ |------|---------|
106
+ | session.start | Start here. Begin session with context injection. |
107
+ | session.end | End session, capture journal + new engrams. |
108
+ | learn | Record a reusable insight (creates candidate engram). |
109
+ | promote | Activate candidate engrams. |
110
+ | inject | Get relevant engrams for a specific task. |
111
+ | recall | Search all sources (engrams + journal + knowledge). |
112
+ | capture | Write a journal entry or knowledge note. |
113
+ | search | Search journal and knowledge files. |
114
+ | ingest | Import text and extract engram suggestions. |
115
+ | feedback | Rate engrams: positive/negative/neutral. |
116
+ | forget | Retire an engram permanently. |
117
+ | status | System health + recommendations. |
118
+ | packs.discover | Browse available engram packs. |
119
+ | packs.install | Install a pack. |
120
+ | packs.export | Export your engrams as a pack. |
121
+
122
+ ## Engram Lifecycle
123
+
124
+ learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker \u2192 forget (retire)
125
+
126
+ - **candidate**: Created but not yet active. Won't appear in inject results.
127
+ - **active**: Appears in inject results when relevant to the task.
128
+ - **retired**: Permanently removed from injection.
129
+
130
+ Positive feedback strengthens retrieval. Unused engrams naturally decay over time.
131
+
132
+ ## Data Storage
133
+
134
+ All data is in this directory as plain text files:
135
+ - \`engrams.yaml\` \u2014 Your learned knowledge
136
+ - \`journal/\` \u2014 Daily session logs (YYYY-MM-DD.md)
137
+ - \`knowledge/\` \u2014 Ingested reference material
138
+ - \`packs/\` \u2014 Installed engram packs
139
+ - \`config.yaml\` \u2014 Configuration (all fields optional)
140
+ `;
141
+ var CONTEXT_CLAUDE = `# Datacore
142
+
143
+ This is a Datacore installation \u2014 persistent memory for AI assistants.
144
+
145
+ ${DATACORE_GUIDE}
146
+
147
+ ## MCP Tools
148
+
149
+ All tools are prefixed with \`datacore.\` (e.g., \`datacore.session.start\`).
150
+ Call \`datacore.session.start\` at the beginning of every conversation.
151
+ `;
152
+ var CONTEXT_AGENTS = `# AGENTS.md
153
+
154
+ This directory is managed by [Datacore](https://github.com/datacore-one/mcp) \u2014 persistent memory for AI assistants.
155
+
156
+ ${DATACORE_GUIDE}
157
+
158
+ ## For AI Agents
159
+
160
+ All tools are available via MCP under the \`datacore.\` namespace.
161
+ Start every session by calling \`datacore.session.start\`.
162
+ `;
163
+ var CONTEXT_CURSORRULES = `# Datacore
164
+
165
+ This directory is managed by Datacore \u2014 persistent memory for AI assistants.
166
+ All tools are available via MCP under the \`datacore.\` namespace.
167
+
168
+ ${DATACORE_GUIDE}`;
169
+ var CONTEXT_COPILOT = `# Datacore
170
+
171
+ This directory is managed by [Datacore](https://github.com/datacore-one/mcp) \u2014 persistent memory for AI assistants.
172
+
173
+ ${DATACORE_GUIDE}
174
+
175
+ ## MCP Integration
176
+
177
+ All tools are available via MCP under the \`datacore.\` namespace.
178
+ Start every session by calling \`datacore.session.start\`.
179
+ `;
76
180
  function copyStarterPacks(basePath) {
77
181
  const packsDir = path.join(basePath, "packs");
78
182
  const bundledPacksDir = path.join(
@@ -90,10 +194,50 @@ function copyStarterPacks(basePath) {
90
194
  }
91
195
  }
92
196
 
197
+ // src/config.ts
198
+ import * as fs2 from "fs";
199
+ import * as path2 from "path";
200
+ import * as yaml from "js-yaml";
201
+ import { z } from "zod";
202
+ var ConfigSchema = z.object({
203
+ version: z.number().default(2),
204
+ engrams: z.object({
205
+ auto_promote: z.boolean().default(false)
206
+ }).default({}),
207
+ packs: z.object({
208
+ trusted_publishers: z.array(z.string()).default([])
209
+ }).default({}),
210
+ search: z.object({
211
+ max_results: z.number().default(20),
212
+ snippet_length: z.number().default(500)
213
+ }).default({}),
214
+ hints: z.object({
215
+ enabled: z.boolean().default(true)
216
+ }).default({})
217
+ });
218
+ var cachedConfig = null;
219
+ function loadConfig(basePath, mode) {
220
+ const configPath = mode === "full" ? path2.join(basePath, ".datacore", "config.yaml") : path2.join(basePath, "config.yaml");
221
+ let raw = {};
222
+ if (fs2.existsSync(configPath)) {
223
+ try {
224
+ raw = yaml.load(fs2.readFileSync(configPath, "utf8")) ?? {};
225
+ } catch {
226
+ raw = {};
227
+ }
228
+ }
229
+ cachedConfig = ConfigSchema.parse(raw);
230
+ return cachedConfig;
231
+ }
232
+ function getConfig() {
233
+ if (!cachedConfig) return ConfigSchema.parse({});
234
+ return cachedConfig;
235
+ }
236
+
93
237
  // package.json
94
238
  var package_default = {
95
239
  name: "@datacore-one/mcp",
96
- version: "1.0.2",
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: z.object({
168
- type: z.enum(["journal", "knowledge"]),
169
- content: z.string().describe("Content to capture"),
170
- title: z.string().optional().describe("Title for knowledge notes"),
171
- tags: z.array(z.string()).optional().describe("Tags to attach")
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: z.object({
178
- statement: z.string().describe("The knowledge assertion"),
179
- type: z.enum(["behavioral", "terminological", "procedural", "architectural"]).optional(),
180
- scope: z.string().optional().describe("Scope: global | agent:X | command:X"),
181
- tags: z.array(z.string()).optional(),
182
- domain: z.string().optional().describe("Dot-notation domain: software.architecture"),
183
- visibility: z.enum(["private", "public", "template"]).optional()
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: z.object({
190
- prompt: z.string().describe("The task or question to match against"),
191
- scope: z.string().optional().describe("Filter by scope: global | agent:X | module:X | command:X"),
192
- max_tokens: z.number().optional().describe("Token budget (default: 8000)"),
193
- min_relevance: z.number().optional().describe("Minimum score threshold (default: 0.3)")
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: z.object({
200
- query: z.string().describe("Search query"),
201
- scope: z.enum(["journal", "knowledge", "all"]).optional(),
202
- method: z.enum(["keyword", "semantic"]).optional().describe("Search method (default: keyword)"),
203
- limit: z.number().optional().describe("Max results (default: 20)")
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: z.object({
210
- content: z.string().describe("Content to ingest"),
211
- title: z.string().optional(),
212
- tags: z.array(z.string()).optional()
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: z.object({})
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: z.object({
224
- query: z.string().optional().describe("Filter by name/description"),
225
- tags: z.array(z.string()).optional().describe("Filter by 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: z.object({
232
- source: z.string().describe("Pack source: local path or pack ID from registry")
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: z.object({
239
- id: z.string().optional().describe("Exact engram ID to retire (e.g., ENG-2026-0219-001)"),
240
- search: z.string().optional().describe("Search term to find engram by statement, tag, or ID fragment")
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: z.object({
247
- engram_id: z.string().describe("The engram ID to provide feedback on"),
248
- signal: z.enum(["positive", "negative", "neutral"]).describe("Feedback signal"),
249
- comment: z.string().optional().describe("Optional comment about why")
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: z.object({
256
- name: z.string().describe("Pack name"),
257
- description: z.string().describe("Pack description"),
258
- engram_ids: z.array(z.string()).optional().describe("Specific engram IDs to export"),
259
- filter_tags: z.array(z.string()).optional().describe("Filter by tags"),
260
- filter_domain: z.string().optional().describe("Filter by domain prefix"),
261
- confirm: z.boolean().optional().describe("Set true to write pack (default: preview only)")
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: z.object({})
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: z.object({
273
- module: z.string().describe('Module name (e.g., "gtd", "slides", "crm")')
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: z.object({
280
- module: z.string().optional().describe("Module name (omit for all modules)")
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 fs2 from "fs";
287
- import * as path2 from "path";
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 = path2.join(journalDir, `${today}.md`);
328
- fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
329
- if (fs2.existsSync(filePath)) {
330
- const existing = fs2.readFileSync(filePath, "utf8");
331
- fs2.writeFileSync(filePath, `${existing}
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
- fs2.writeFileSync(filePath, `# ${today}
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 = path2.join(knowledgeDir, fileName);
351
- fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
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
- fs2.writeFileSync(filePath, `${frontmatter}${content}
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 fs3 from "fs";
368
- import * as yaml from "js-yaml";
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 z2 } from "zod";
372
- var ActivationSchema = z2.object({
373
- retrieval_strength: z2.number().min(0).max(1),
374
- storage_strength: z2.number().min(0).max(1),
375
- frequency: z2.number().int().min(0),
376
- last_accessed: z2.string()
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 = z2.object({
379
- memory_class: z2.enum(["semantic", "episodic", "procedural", "metacognitive"]),
380
- cognitive_level: z2.enum(["remember", "understand", "apply", "analyze", "evaluate", "create"])
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 = z2.object({
383
- broader: z2.array(z2.string()).default([]),
384
- narrower: z2.array(z2.string()).default([]),
385
- related: z2.array(z2.string()).default([]),
386
- conflicts: z2.array(z2.string()).default([])
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 = z2.object({
389
- origin: z2.string(),
390
- chain: z2.array(z2.string()).default([]),
391
- signature: z2.string().nullable().default(null),
392
- license: z2.string().default("cc-by-sa-4.0")
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 = z2.object({
395
- positive: z2.number().int().default(0),
396
- negative: z2.number().int().default(0),
397
- neutral: z2.number().int().default(0)
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 = z2.object({
400
- id: z2.string().regex(/^ENG-[A-Za-z0-9-]+$/),
401
- version: z2.number().int().min(1),
402
- status: z2.enum(["active", "dormant", "retired", "candidate"]),
403
- consolidated: z2.boolean().default(false),
404
- type: z2.enum(["behavioral", "terminological", "procedural", "architectural"]),
405
- scope: z2.string(),
406
- visibility: z2.enum(["private", "public", "template"]).default("private"),
407
- statement: z2.string().min(1),
408
- rationale: z2.string().optional(),
409
- contraindications: z2.array(z2.string()).optional(),
410
- source_patterns: z2.array(z2.string()).optional(),
411
- derivation_count: z2.number().int().min(0).default(1),
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: z2.string().optional(),
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: z2.array(z2.string()).default([]),
419
- pack: z2.string().nullable().default(null),
420
- abstract: z2.string().nullable().default(null),
421
- derived_from: z2.string().nullable().default(null)
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 = z2.object({
424
- id: z2.string(),
425
- injection_policy: z2.enum(["on_match", "on_request"]),
426
- match_terms: z2.array(z2.string()).default([]),
427
- domain: z2.string().optional(),
428
- engram_count: z2.number().int().min(0)
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 = z2.object({
431
- name: z2.string(),
432
- description: z2.string(),
433
- version: z2.string(),
434
- creator: z2.string().optional(),
435
- license: z2.string().optional(),
436
- tags: z2.array(z2.string()).default([]),
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 (!fs3.existsSync(filePath)) return [];
683
+ if (!fs4.existsSync(filePath)) return [];
495
684
  try {
496
- const raw = yaml.load(fs3.readFileSync(filePath, "utf8"));
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 = yaml.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
519
- fs3.writeFileSync(filePath, content);
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 = fs3.readFileSync(filePath, "utf8");
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 yaml.load(match[1]);
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 (!fs3.existsSync(packsDir)) return [];
729
+ if (!fs4.existsSync(packsDir)) return [];
541
730
  const packs = [];
542
- for (const entry of fs3.readdirSync(packsDir)) {
731
+ for (const entry of fs4.readdirSync(packsDir)) {
543
732
  const packDir = `${packsDir}/${entry}`;
544
- if (!fs3.statSync(packDir).isDirectory()) continue;
545
- if (!fs3.existsSync(`${packDir}/SKILL.md`)) continue;
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
- return { success: true, engram };
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 fs4 from "fs";
604
- import * as yaml2 from "js-yaml";
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 { text: "", count: 0, tokens_used: 0 };
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
- return { text: lines.join("\n"), count: totalCount, tokens_used: result.tokens_used };
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 = yaml2.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
995
+ const content = yaml3.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
772
996
  const tmpPath = filePath + ".tmp." + process.pid;
773
- fs4.writeFileSync(tmpPath, content);
774
- fs4.renameSync(tmpPath, filePath);
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 fs5 from "fs";
796
- import * as path3 from "path";
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 = fs5.statSync(filePath);
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 = fs5.statSync(filePath).mtimeMs;
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 (!fs5.existsSync(dirPath)) return [];
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 = fs5.readFileSync(file, "utf8");
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
- results.push({ path: file, snippet, score: occurrences });
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 fs5.readdirSync(dir, { withFileTypes: true })) {
870
- const fullPath = path3.join(dir, entry.name);
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, 100);
888
- const start2 = Math.max(0, idx - 50);
889
- const end = Math.min(content.length, idx + query.length + 50);
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 fs6 from "fs";
895
- import * as path4 from "path";
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 = path4.join(paths.knowledgePath, fileName);
907
- fs6.mkdirSync(path4.dirname(filePath), { recursive: true });
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
- fs6.writeFileSync(filePath, `${frontmatter}${args2.content}
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 fs8 from "fs";
949
- import * as path6 from "path";
1191
+ import * as fs9 from "fs";
1192
+ import * as path7 from "path";
950
1193
 
951
1194
  // src/trust.ts
952
- import * as fs7 from "fs";
953
- import * as path5 from "path";
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 = path5.join(packDir, file);
961
- if (fs7.existsSync(filePath)) {
962
- hash.update(fs7.readFileSync(filePath));
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 = path6.join(paths.packsPath, regPack.id);
1033
- if (!fs8.existsSync(packDir)) continue;
1034
- const result2 = verifyPackChecksum(packDir, regPack.checksum);
1035
- packIntegrity.push({ name: regPack.id, valid: result2.valid });
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
- const result = {
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
- result.scaling_hint = `You have ${engrams.length} engrams. Consider migrating to full Datacore for SQLite-backed search.`;
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
- result.update_available = updateAvailable2;
1318
+ statusResult.update_available = updateAvailable2;
1052
1319
  }
1053
- return result;
1320
+ return statusResult;
1054
1321
  }
1055
1322
  function countFiles(dir, ext) {
1056
- if (!fs8.existsSync(dir)) return 0;
1323
+ if (!fs9.existsSync(dir)) return 0;
1057
1324
  let count = 0;
1058
- for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
1059
- const fullPath = path6.join(dir, entry.name);
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 (!fs8.existsSync(dir)) return 0;
1067
- return fs8.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
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 fs9 from "fs";
1072
- import * as path7 from "path";
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 = path7.join(packsDir, p.id);
1076
- const installed = fs9.existsSync(path7.join(localDir, "SKILL.md"));
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 = fs9.readFileSync(path7.join(localDir, "SKILL.md"), "utf8");
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
- return { packs };
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 fs10 from "fs";
1109
- import * as path8 from "path";
1110
- import * as yaml3 from "js-yaml";
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 = path8.join(srcDir, "SKILL.md");
1121
- if (!fs10.existsSync(skillPath)) {
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 = fs10.readFileSync(skillPath, "utf8");
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 = yaml3.load(frontmatterMatch[1]);
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 = path8.join(packsDir, packId);
1136
- if (fs10.existsSync(path8.join(destDir, "SKILL.md"))) {
1137
- const existingContent = fs10.readFileSync(path8.join(destDir, "SKILL.md"), "utf8");
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
- fs10.rmSync(destDir, { recursive: true, force: true });
1144
- fs10.cpSync(srcDir, destDir, { recursive: true });
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
- fs10.cpSync(srcDir, destDir, { recursive: true });
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 = fs10.mkdtempSync(path8.join(os2.tmpdir(), "datacore-pack-"));
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 = path8.join(tmpDir, "pack.tar.gz");
1164
- fs10.writeFileSync(archivePath, buffer);
1165
- const extractDir = path8.join(tmpDir, "extracted");
1166
- fs10.mkdirSync(extractDir);
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 (fs10.existsSync(path8.join(dir, "SKILL.md"))) return dir;
1177
- for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
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(path8.join(dir, entry.name));
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 fs11 from "fs";
1188
- import * as path9 from "path";
1189
- import * as yaml4 from "js-yaml";
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 = path9.join(paths.packsPath, packId);
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
- fs11.mkdirSync(packDir, { recursive: true });
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
- fs11.writeFileSync(path9.join(packDir, "SKILL.md"), skillContent);
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
- fs11.writeFileSync(
1272
- path9.join(packDir, "engrams.yaml"),
1273
- yaml4.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
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 fs12 from "fs";
1280
- import * as path10 from "path";
1281
- import * as yaml5 from "js-yaml";
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 = path10.join(storage2.basePath, ".datacore", "modules");
1594
+ const globalModulesDir = path11.join(storage2.basePath, ".datacore", "modules");
1286
1595
  modules.push(...scanModulesDir(globalModulesDir, "global"));
1287
1596
  try {
1288
- const entries = fs12.readdirSync(storage2.basePath);
1597
+ const entries = fs13.readdirSync(storage2.basePath);
1289
1598
  for (const entry of entries) {
1290
1599
  if (/^\d+-/.test(entry)) {
1291
- const spaceModulesDir = path10.join(storage2.basePath, entry, ".datacore", "modules");
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 (!fs12.existsSync(modulesDir)) return modules;
1610
+ if (!fs13.existsSync(modulesDir)) return modules;
1302
1611
  try {
1303
- const entries = fs12.readdirSync(modulesDir);
1612
+ const entries = fs13.readdirSync(modulesDir);
1304
1613
  for (const entry of entries) {
1305
- const modulePath = path10.join(modulesDir, entry);
1306
- const manifestPath = path10.join(modulePath, "module.yaml");
1307
- if (!fs12.existsSync(manifestPath)) continue;
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 = fs12.readFileSync(manifestPath, "utf-8");
1310
- const manifest = yaml5.load(raw);
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 = path10.join(mod.modulePath, "tools", "index.js");
1332
- if (!fs12.existsSync(toolsIndexPath)) continue;
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 ? path10.join(storage2.basePath, mod.spaceName, ".datacore", "modules", mod.name, "data") : path10.join(storage2.basePath, "0-personal", ".datacore", "modules", mod.name, "data");
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 fs13 from "fs";
1433
- import * as path11 from "path";
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 (!fs13.existsSync(path11.join(mod.modulePath, "SKILL.md"))) {
1764
+ if (!fs14.existsSync(path12.join(mod.modulePath, "SKILL.md"))) {
1456
1765
  issues.push("Missing SKILL.md (ecosystem entry point)");
1457
1766
  }
1458
- if (!fs13.existsSync(path11.join(mod.modulePath, "CLAUDE.base.md"))) {
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 = path11.join(mod.modulePath, "tools", "index.js");
1475
- if (!fs13.existsSync(toolsIndex)) {
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 = path11.join(mod.modulePath, dir);
1495
- if (fs13.existsSync(fullPath) && fs13.statSync(fullPath).isDirectory()) {
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 = fs13.readdirSync(mod.modulePath);
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 matches = engrams.filter((e) => e.status !== "retired").filter(
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
- ).slice(0, 10);
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
- error: `Multiple matches found. Specify an exact ID to retire.`
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
- async function handleFeedback(args2, engramsPath) {
1557
- const engrams = loadEngrams(engramsPath);
1558
- const engram = engrams.find((e) => e.id === args2.engram_id);
1559
- if (!engram) {
1560
- return { success: false, engram_id: args2.engram_id, signal: args2.signal, error: `Engram ${args2.engram_id} not found` };
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.activation.last_accessed === today) {
1907
+ if (!found.engram.feedback_signals) {
1908
+ found.engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
1564
1909
  }
1565
- if (!engram.feedback_signals) {
1566
- engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
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: args2.engram_id,
1574
- signal: args2.signal,
1575
- feedback_signals: { ...engram.feedback_signals }
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 fs14 from "fs";
1586
- import * as path12 from "path";
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 = path12.join(storage2.journalPath, `${dateStr}.md`);
1653
- if (!fs14.existsSync(filePath)) {
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: fs14.readFileSync(filePath, "utf8") }] };
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 fs15 from "fs";
1686
- import * as path13 from "path";
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
- path13.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
1697
- path13.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
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 (fs15.existsSync(candidate)) return candidate;
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
- tools: [
1767
- ...TOOLS.map((t) => ({
1768
- name: t.name,
1769
- description: t.description,
1770
- inputSchema: zodToJsonSchema(t.inputSchema)
1771
- })),
1772
- ...moduleTools.map((t) => ({
1773
- name: t.fullName,
1774
- description: t.definition.description,
1775
- inputSchema: zodToJsonSchema(t.definition.inputSchema)
1776
- }))
1777
- ]
1778
- }));
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);