@datacore-one/mcp 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.1",
240
+ version: "1.1.0",
97
241
  description: "Datacore MCP server \u2014 The Software of You",
98
242
  type: "module",
99
243
  bin: {
@@ -159,132 +303,177 @@ async function checkForUpdate() {
159
303
  }
160
304
 
161
305
  // src/tools/index.ts
162
- import { z } from "zod";
306
+ import { z as z2 } from "zod";
163
307
  var TOOLS = [
164
308
  {
165
309
  name: "datacore.capture",
166
310
  description: "Capture a journal entry or knowledge note",
167
- inputSchema: 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;
@@ -747,7 +953,17 @@ async function handleInject(args2, paths) {
747
953
  personalEngrams,
748
954
  [...result.directives, ...result.consider]
749
955
  );
750
- return { text: lines.join("\n"), count: totalCount, tokens_used: result.tokens_used };
956
+ const injectedIds = [...result.directives, ...result.consider].filter((e) => !e.pack).map((e) => e.id);
957
+ const idsList = injectedIds.length > 0 ? ` Injected IDs: ${injectedIds.join(", ")}` : "";
958
+ return {
959
+ text: lines.join("\n"),
960
+ count: totalCount,
961
+ tokens_used: result.tokens_used,
962
+ _hints: buildHints({
963
+ next: `After task, call datacore.feedback on helpful/unhelpful engrams.${idsList}`,
964
+ related: ["datacore.feedback", "datacore.session.end"]
965
+ })
966
+ };
751
967
  }
752
968
  function updateUsageTracking(engramsPath, allPersonal, selected) {
753
969
  const selectedPersonalIds = new Set(
@@ -768,10 +984,10 @@ function updateUsageTracking(engramsPath, allPersonal, selected) {
768
984
  }
769
985
  }
770
986
  function atomicWriteYaml(filePath, data) {
771
- const content = yaml2.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
987
+ const content = yaml3.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
772
988
  const tmpPath = filePath + ".tmp." + process.pid;
773
- fs4.writeFileSync(tmpPath, content);
774
- fs4.renameSync(tmpPath, filePath);
989
+ fs5.writeFileSync(tmpPath, content);
990
+ fs5.renameSync(tmpPath, filePath);
775
991
  }
776
992
  function formatEngram(engram, totalCount) {
777
993
  if (totalCount < 10) {
@@ -792,15 +1008,15 @@ function formatEngram(engram, totalCount) {
792
1008
  }
793
1009
 
794
1010
  // src/tools/search.ts
795
- import * as fs5 from "fs";
796
- import * as path3 from "path";
1011
+ import * as fs6 from "fs";
1012
+ import * as path4 from "path";
797
1013
  var CONTENT_CACHE_MAX = 500;
798
1014
  var contentCache = /* @__PURE__ */ new Map();
799
1015
  function getCachedContent(filePath) {
800
1016
  const entry = contentCache.get(filePath);
801
1017
  if (!entry) return null;
802
1018
  try {
803
- const stat = fs5.statSync(filePath);
1019
+ const stat = fs6.statSync(filePath);
804
1020
  if (stat.mtimeMs === entry.mtime) return entry.content;
805
1021
  } catch {
806
1022
  }
@@ -809,7 +1025,7 @@ function getCachedContent(filePath) {
809
1025
  }
810
1026
  function setCachedContent(filePath, content) {
811
1027
  try {
812
- const mtime = fs5.statSync(filePath).mtimeMs;
1028
+ const mtime = fs6.statSync(filePath).mtimeMs;
813
1029
  if (contentCache.size >= CONTENT_CACHE_MAX) {
814
1030
  const firstKey = contentCache.keys().next().value;
815
1031
  if (firstKey) contentCache.delete(firstKey);
@@ -846,13 +1062,13 @@ async function keywordSearch(args2, paths) {
846
1062
  return { results: results.slice(0, limit), method: "keyword" };
847
1063
  }
848
1064
  function searchDir(dirPath, query) {
849
- if (!fs5.existsSync(dirPath)) return [];
1065
+ if (!fs6.existsSync(dirPath)) return [];
850
1066
  const results = [];
851
1067
  const queryLower = query.toLowerCase();
852
1068
  for (const file of walkDir(dirPath)) {
853
1069
  if (!file.endsWith(".md")) continue;
854
1070
  const content = getCachedContent(file) ?? (() => {
855
- const c = fs5.readFileSync(file, "utf8");
1071
+ const c = fs6.readFileSync(file, "utf8");
856
1072
  setCachedContent(file, c);
857
1073
  return c;
858
1074
  })();
@@ -860,14 +1076,16 @@ function searchDir(dirPath, query) {
860
1076
  const occurrences = countOccurrences(contentLower, queryLower);
861
1077
  if (occurrences === 0) continue;
862
1078
  const snippet = extractSnippet(content, query);
863
- results.push({ path: file, snippet, score: occurrences });
1079
+ const title = extractTitle(content, file);
1080
+ const date = extractDate(file);
1081
+ results.push({ path: file, snippet, score: occurrences, title, date });
864
1082
  }
865
1083
  return results;
866
1084
  }
867
1085
  function walkDir(dir) {
868
1086
  const files = [];
869
- for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
870
- const fullPath = path3.join(dir, entry.name);
1087
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1088
+ const fullPath = path4.join(dir, entry.name);
871
1089
  if (entry.isDirectory()) files.push(...walkDir(fullPath));
872
1090
  else files.push(fullPath);
873
1091
  }
@@ -883,16 +1101,29 @@ function countOccurrences(text, query) {
883
1101
  return count;
884
1102
  }
885
1103
  function extractSnippet(content, query) {
1104
+ const snippetLength = getConfig().search.snippet_length;
1105
+ if (content.length < 2e3) return content;
886
1106
  const idx = content.toLowerCase().indexOf(query.toLowerCase());
887
- if (idx === -1) return content.slice(0, 100);
888
- const start2 = Math.max(0, idx - 50);
889
- const end = Math.min(content.length, idx + query.length + 50);
1107
+ if (idx === -1) return content.slice(0, snippetLength);
1108
+ const half = Math.floor(snippetLength / 2);
1109
+ const start2 = Math.max(0, idx - half);
1110
+ const end = Math.min(content.length, idx + query.length + half);
890
1111
  return (start2 > 0 ? "..." : "") + content.slice(start2, end) + (end < content.length ? "..." : "");
891
1112
  }
1113
+ function extractTitle(content, filePath) {
1114
+ const match = content.match(/^#\s+(.+)$/m);
1115
+ if (match) return match[1].trim();
1116
+ return path4.basename(filePath, path4.extname(filePath));
1117
+ }
1118
+ function extractDate(filePath) {
1119
+ const match = path4.basename(filePath).match(/^(\d{4}-\d{2}-\d{2})/);
1120
+ if (match) return match[1];
1121
+ return void 0;
1122
+ }
892
1123
 
893
1124
  // src/tools/ingest.ts
894
- import * as fs6 from "fs";
895
- import * as path4 from "path";
1125
+ import * as fs7 from "fs";
1126
+ import * as path5 from "path";
896
1127
  async function handleIngest(args2, paths) {
897
1128
  const contentError = validateContent(args2.content);
898
1129
  if (contentError) return { success: false, error: contentError };
@@ -903,8 +1134,8 @@ async function handleIngest(args2, paths) {
903
1134
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
904
1135
  const slug = (args2.title ?? "ingested").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
905
1136
  const fileName = `${timestamp}-${slug}.md`;
906
- const filePath = path4.join(paths.knowledgePath, fileName);
907
- fs6.mkdirSync(path4.dirname(filePath), { recursive: true });
1137
+ const filePath = path5.join(paths.knowledgePath, fileName);
1138
+ fs7.mkdirSync(path5.dirname(filePath), { recursive: true });
908
1139
  const frontmatter = `---
909
1140
  title: "${args2.title ?? "Ingested Note"}"
910
1141
  created: "${(/* @__PURE__ */ new Date()).toISOString()}"
@@ -915,13 +1146,17 @@ type: ingested
915
1146
  const tagLine = args2.tags?.length ? `
916
1147
  ${args2.tags.map((t) => `#${t}`).join(" ")}
917
1148
  ` : "";
918
- fs6.writeFileSync(filePath, `${frontmatter}${args2.content}
1149
+ fs7.writeFileSync(filePath, `${frontmatter}${args2.content}
919
1150
  ${tagLine}`);
920
1151
  const suggestions = extractEngramSuggestions(args2.content);
921
1152
  return {
922
1153
  success: true,
923
1154
  note_path: filePath,
924
- engram_suggestions: suggestions.length > 0 ? suggestions : void 0
1155
+ engram_suggestions: suggestions.length > 0 ? suggestions : void 0,
1156
+ _hints: suggestions.length > 0 ? buildHints({
1157
+ next: `Call datacore.learn for each suggestion to create engrams. Example: datacore.learn({statement: '${suggestions[0]}', type: 'behavioral'})`,
1158
+ related: ["datacore.learn"]
1159
+ }) : void 0
925
1160
  };
926
1161
  }
927
1162
  function extractEngramSuggestions(content) {
@@ -945,21 +1180,21 @@ function extractEngramSuggestions(content) {
945
1180
  }
946
1181
 
947
1182
  // src/tools/status.ts
948
- import * as fs8 from "fs";
949
- import * as path6 from "path";
1183
+ import * as fs9 from "fs";
1184
+ import * as path7 from "path";
950
1185
 
951
1186
  // src/trust.ts
952
- import * as fs7 from "fs";
953
- import * as path5 from "path";
1187
+ import * as fs8 from "fs";
1188
+ import * as path6 from "path";
954
1189
  import * as crypto from "crypto";
955
1190
  function computePackChecksum(packDir) {
956
1191
  const files = ["SKILL.md", "engrams.yaml"];
957
1192
  const hash = crypto.createHash("sha256");
958
1193
  let hasContent = false;
959
1194
  for (const file of files) {
960
- const filePath = path5.join(packDir, file);
961
- if (fs7.existsSync(filePath)) {
962
- hash.update(fs7.readFileSync(filePath));
1195
+ const filePath = path6.join(packDir, file);
1196
+ if (fs8.existsSync(filePath)) {
1197
+ hash.update(fs8.readFileSync(filePath));
963
1198
  hasContent = true;
964
1199
  }
965
1200
  }
@@ -1029,12 +1264,31 @@ async function handleStatus(paths, updateAvailable2) {
1029
1264
  const packIntegrity = [];
1030
1265
  for (const regPack of packs_default.packs) {
1031
1266
  if (!regPack.checksum) continue;
1032
- const packDir = 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 });
1267
+ const packDir = path7.join(paths.packsPath, regPack.id);
1268
+ if (!fs9.existsSync(packDir)) continue;
1269
+ const result = verifyPackChecksum(packDir, regPack.checksum);
1270
+ packIntegrity.push({ name: regPack.id, valid: result.valid });
1271
+ }
1272
+ const recommendations = [];
1273
+ const candidateCount = engrams.filter((e) => e.status === "candidate").length;
1274
+ if (healthCounts.retirement_candidate > 0) {
1275
+ recommendations.push(`${healthCounts.retirement_candidate} engrams are retirement candidates. Use datacore.forget to clean up.`);
1036
1276
  }
1037
- const result = {
1277
+ if (healthCounts.fading > 0) {
1278
+ recommendations.push(`${healthCounts.fading} engrams are fading. Use datacore.feedback with positive signal to reinforce.`);
1279
+ }
1280
+ if (candidateCount > 0) {
1281
+ recommendations.push(`${candidateCount} candidate engrams awaiting review. Use datacore.promote or enable engrams.auto_promote in config.yaml.`);
1282
+ }
1283
+ const { date: today } = localDate();
1284
+ const todayJournal = path7.join(paths.journalPath, `${today}.md`);
1285
+ if (!fs9.existsSync(todayJournal)) {
1286
+ recommendations.push("No journal entry today. Use datacore.capture to start one.");
1287
+ }
1288
+ if (updateAvailable2) {
1289
+ recommendations.push(`Update available: ${updateAvailable2}. Run: npm update -g @datacore-one/mcp`);
1290
+ }
1291
+ const statusResult = {
1038
1292
  version: currentVersion,
1039
1293
  mode: paths.mode,
1040
1294
  engrams: engrams.length,
@@ -1042,42 +1296,47 @@ async function handleStatus(paths, updateAvailable2) {
1042
1296
  packs: packsCount,
1043
1297
  pack_integrity: packIntegrity.length > 0 ? packIntegrity : void 0,
1044
1298
  journal_entries: journalCount,
1045
- knowledge_notes: knowledgeCount
1299
+ knowledge_notes: knowledgeCount,
1300
+ _recommendations: recommendations.length > 0 ? recommendations : void 0,
1301
+ _hints: buildHints({
1302
+ next: recommendations.length > 0 ? recommendations[0] : "System healthy. Use datacore.session.start to begin working.",
1303
+ related: ["datacore.promote", "datacore.forget"]
1304
+ })
1046
1305
  };
1047
1306
  if (engrams.length >= 500) {
1048
- result.scaling_hint = `You have ${engrams.length} engrams. Consider migrating to full Datacore for SQLite-backed search.`;
1307
+ statusResult.scaling_hint = `You have ${engrams.length} engrams. Consider migrating to full Datacore for SQLite-backed search.`;
1049
1308
  }
1050
1309
  if (updateAvailable2) {
1051
- result.update_available = updateAvailable2;
1310
+ statusResult.update_available = updateAvailable2;
1052
1311
  }
1053
- return result;
1312
+ return statusResult;
1054
1313
  }
1055
1314
  function countFiles(dir, ext) {
1056
- if (!fs8.existsSync(dir)) return 0;
1315
+ if (!fs9.existsSync(dir)) return 0;
1057
1316
  let count = 0;
1058
- for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
1059
- const fullPath = path6.join(dir, entry.name);
1317
+ for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
1318
+ const fullPath = path7.join(dir, entry.name);
1060
1319
  if (entry.isDirectory()) count += countFiles(fullPath, ext);
1061
1320
  else if (entry.name.endsWith(ext)) count++;
1062
1321
  }
1063
1322
  return count;
1064
1323
  }
1065
1324
  function countDirs(dir) {
1066
- if (!fs8.existsSync(dir)) return 0;
1067
- return fs8.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
1325
+ if (!fs9.existsSync(dir)) return 0;
1326
+ return fs9.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
1068
1327
  }
1069
1328
 
1070
1329
  // src/tools/discover.ts
1071
- import * as fs9 from "fs";
1072
- import * as path7 from "path";
1330
+ import * as fs10 from "fs";
1331
+ import * as path8 from "path";
1073
1332
  function handleDiscover(args2, packsDir) {
1074
1333
  let packs = packs_default.packs.map((p) => {
1075
- const localDir = path7.join(packsDir, p.id);
1076
- const installed = fs9.existsSync(path7.join(localDir, "SKILL.md"));
1334
+ const localDir = path8.join(packsDir, p.id);
1335
+ const installed = fs10.existsSync(path8.join(localDir, "SKILL.md"));
1077
1336
  let installedVersion;
1078
1337
  if (installed) {
1079
1338
  try {
1080
- const content = fs9.readFileSync(path7.join(localDir, "SKILL.md"), "utf8");
1339
+ const content = fs10.readFileSync(path8.join(localDir, "SKILL.md"), "utf8");
1081
1340
  const match = content.match(/version:\s*["']?([^"'\n]+)/);
1082
1341
  installedVersion = match?.[1];
1083
1342
  } catch {
@@ -1101,13 +1360,21 @@ function handleDiscover(args2, packsDir) {
1101
1360
  const filterTags = new Set(args2.tags.map((t) => t.toLowerCase()));
1102
1361
  packs = packs.filter((p) => p.tags.some((t) => filterTags.has(t.toLowerCase())));
1103
1362
  }
1104
- return { packs };
1363
+ const trusted = new Set(getConfig().packs.trusted_publishers);
1364
+ const result = { packs };
1365
+ if (trusted.size > 0) {
1366
+ const autoInstallable = packs.filter((p) => trusted.has(p.author) && !p.installed && p.can_install).map((p) => p.id);
1367
+ const autoUpgradeable = packs.filter((p) => trusted.has(p.author) && p.upgradeable).map((p) => p.id);
1368
+ if (autoInstallable.length > 0) result.auto_installable = autoInstallable;
1369
+ if (autoUpgradeable.length > 0) result.auto_upgradeable = autoUpgradeable;
1370
+ }
1371
+ return result;
1105
1372
  }
1106
1373
 
1107
1374
  // src/tools/install.ts
1108
- import * as fs10 from "fs";
1109
- import * as path8 from "path";
1110
- import * as yaml3 from "js-yaml";
1375
+ import * as fs11 from "fs";
1376
+ import * as path9 from "path";
1377
+ import * as yaml4 from "js-yaml";
1111
1378
  import * as os2 from "os";
1112
1379
  import { execSync } from "child_process";
1113
1380
  async function handleInstall(args2, packsDir) {
@@ -1117,34 +1384,34 @@ async function handleInstall(args2, packsDir) {
1117
1384
  if (downloaded.error) return { success: false, error: downloaded.error };
1118
1385
  srcDir = downloaded.path;
1119
1386
  }
1120
- const skillPath = path8.join(srcDir, "SKILL.md");
1121
- if (!fs10.existsSync(skillPath)) {
1387
+ const skillPath = path9.join(srcDir, "SKILL.md");
1388
+ if (!fs11.existsSync(skillPath)) {
1122
1389
  return { success: false, error: "No SKILL.md found in source directory" };
1123
1390
  }
1124
- const skillContent = fs10.readFileSync(skillPath, "utf8");
1391
+ const skillContent = fs11.readFileSync(skillPath, "utf8");
1125
1392
  const frontmatterMatch = skillContent.match(/^---\n([\s\S]*?)\n---/);
1126
1393
  if (!frontmatterMatch) {
1127
1394
  return { success: false, error: "No YAML frontmatter in SKILL.md" };
1128
1395
  }
1129
- const manifest = yaml3.load(frontmatterMatch[1]);
1396
+ const manifest = yaml4.load(frontmatterMatch[1]);
1130
1397
  const packId = manifest?.["x-datacore"]?.id;
1131
1398
  const newVersion = manifest?.version;
1132
1399
  if (!packId) {
1133
1400
  return { success: false, error: "Missing x-datacore.id in SKILL.md frontmatter" };
1134
1401
  }
1135
- const destDir = path8.join(packsDir, packId);
1136
- if (fs10.existsSync(path8.join(destDir, "SKILL.md"))) {
1137
- const existingContent = fs10.readFileSync(path8.join(destDir, "SKILL.md"), "utf8");
1402
+ const destDir = path9.join(packsDir, packId);
1403
+ if (fs11.existsSync(path9.join(destDir, "SKILL.md"))) {
1404
+ const existingContent = fs11.readFileSync(path9.join(destDir, "SKILL.md"), "utf8");
1138
1405
  const existingMatch = existingContent.match(/version:\s*["']?([^"'\n]+)/);
1139
1406
  const existingVersion = existingMatch?.[1];
1140
1407
  if (existingVersion === newVersion) {
1141
1408
  return { success: true, pack_id: packId, already_current: true };
1142
1409
  }
1143
- fs10.rmSync(destDir, { recursive: true, force: true });
1144
- fs10.cpSync(srcDir, destDir, { recursive: true });
1410
+ fs11.rmSync(destDir, { recursive: true, force: true });
1411
+ fs11.cpSync(srcDir, destDir, { recursive: true });
1145
1412
  return { success: true, pack_id: packId, upgraded: true };
1146
1413
  }
1147
- fs10.cpSync(srcDir, destDir, { recursive: true });
1414
+ fs11.cpSync(srcDir, destDir, { recursive: true });
1148
1415
  const checksumVerified = verifyInstalledChecksum(packId, destDir);
1149
1416
  return { success: true, pack_id: packId, checksum_verified: checksumVerified ?? void 0 };
1150
1417
  }
@@ -1155,15 +1422,15 @@ function verifyInstalledChecksum(packId, destDir) {
1155
1422
  return result.valid;
1156
1423
  }
1157
1424
  async function downloadPack(url) {
1158
- const tmpDir = fs10.mkdtempSync(path8.join(os2.tmpdir(), "datacore-pack-"));
1425
+ const tmpDir = fs11.mkdtempSync(path9.join(os2.tmpdir(), "datacore-pack-"));
1159
1426
  try {
1160
1427
  const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
1161
1428
  if (!res.ok) return { error: `Download failed: HTTP ${res.status}` };
1162
1429
  const buffer = Buffer.from(await res.arrayBuffer());
1163
- const archivePath = path8.join(tmpDir, "pack.tar.gz");
1164
- fs10.writeFileSync(archivePath, buffer);
1165
- const extractDir = path8.join(tmpDir, "extracted");
1166
- fs10.mkdirSync(extractDir);
1430
+ const archivePath = path9.join(tmpDir, "pack.tar.gz");
1431
+ fs11.writeFileSync(archivePath, buffer);
1432
+ const extractDir = path9.join(tmpDir, "extracted");
1433
+ fs11.mkdirSync(extractDir);
1167
1434
  execSync(`tar xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(extractDir)}`, { timeout: 1e4 });
1168
1435
  const packRoot = findPackRoot(extractDir);
1169
1436
  if (!packRoot) return { error: "Downloaded archive does not contain SKILL.md" };
@@ -1173,10 +1440,10 @@ async function downloadPack(url) {
1173
1440
  }
1174
1441
  }
1175
1442
  function findPackRoot(dir) {
1176
- if (fs10.existsSync(path8.join(dir, "SKILL.md"))) return dir;
1177
- for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
1443
+ if (fs11.existsSync(path9.join(dir, "SKILL.md"))) return dir;
1444
+ for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
1178
1445
  if (entry.isDirectory()) {
1179
- const found = findPackRoot(path8.join(dir, entry.name));
1446
+ const found = findPackRoot(path9.join(dir, entry.name));
1180
1447
  if (found) return found;
1181
1448
  }
1182
1449
  }
@@ -1184,9 +1451,9 @@ function findPackRoot(dir) {
1184
1451
  }
1185
1452
 
1186
1453
  // src/tools/export.ts
1187
- import * as fs11 from "fs";
1188
- import * as path9 from "path";
1189
- import * as yaml4 from "js-yaml";
1454
+ import * as fs12 from "fs";
1455
+ import * as path10 from "path";
1456
+ import * as yaml5 from "js-yaml";
1190
1457
  async function handleExport(args2, paths) {
1191
1458
  const allEngrams = loadEngrams(paths.engramsPath);
1192
1459
  let selected = allEngrams.filter((e) => e.status === "active");
@@ -1218,7 +1485,7 @@ async function handleExport(args2, paths) {
1218
1485
  return { success: false, error: "No engrams match the filter criteria" };
1219
1486
  }
1220
1487
  const packId = args2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
1221
- const packDir = path9.join(paths.packsPath, packId);
1488
+ const packDir = path10.join(paths.packsPath, packId);
1222
1489
  if (!args2.confirm) {
1223
1490
  return {
1224
1491
  success: true,
@@ -1229,7 +1496,7 @@ async function handleExport(args2, paths) {
1229
1496
  }
1230
1497
  };
1231
1498
  }
1232
- fs11.mkdirSync(packDir, { recursive: true });
1499
+ fs12.mkdirSync(packDir, { recursive: true });
1233
1500
  const skillContent = `---
1234
1501
  name: "${args2.name}"
1235
1502
  description: "${args2.description}"
@@ -1248,7 +1515,7 @@ ${args2.description}
1248
1515
 
1249
1516
  Exported ${selected.length} engrams.
1250
1517
  `;
1251
- fs11.writeFileSync(path9.join(packDir, "SKILL.md"), skillContent);
1518
+ fs12.writeFileSync(path10.join(packDir, "SKILL.md"), skillContent);
1252
1519
  const exportEngrams = selected.map((e) => ({
1253
1520
  id: e.id,
1254
1521
  version: e.version,
@@ -1268,27 +1535,27 @@ Exported ${selected.length} engrams.
1268
1535
  },
1269
1536
  feedback_signals: { positive: 0, negative: 0 }
1270
1537
  }));
1271
- fs11.writeFileSync(
1272
- path9.join(packDir, "engrams.yaml"),
1273
- yaml4.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
1538
+ fs12.writeFileSync(
1539
+ path10.join(packDir, "engrams.yaml"),
1540
+ yaml5.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
1274
1541
  );
1275
1542
  return { success: true, pack_path: packDir };
1276
1543
  }
1277
1544
 
1278
1545
  // src/modules.ts
1279
- import * as fs12 from "fs";
1280
- import * as path10 from "path";
1281
- import * as yaml5 from "js-yaml";
1546
+ import * as fs13 from "fs";
1547
+ import * as path11 from "path";
1548
+ import * as yaml6 from "js-yaml";
1282
1549
  function discoverModules(storage2) {
1283
1550
  const modules = [];
1284
1551
  if (storage2.mode !== "full") return modules;
1285
- const globalModulesDir = path10.join(storage2.basePath, ".datacore", "modules");
1552
+ const globalModulesDir = path11.join(storage2.basePath, ".datacore", "modules");
1286
1553
  modules.push(...scanModulesDir(globalModulesDir, "global"));
1287
1554
  try {
1288
- const entries = fs12.readdirSync(storage2.basePath);
1555
+ const entries = fs13.readdirSync(storage2.basePath);
1289
1556
  for (const entry of entries) {
1290
1557
  if (/^\d+-/.test(entry)) {
1291
- const spaceModulesDir = path10.join(storage2.basePath, entry, ".datacore", "modules");
1558
+ const spaceModulesDir = path11.join(storage2.basePath, entry, ".datacore", "modules");
1292
1559
  modules.push(...scanModulesDir(spaceModulesDir, "space", entry));
1293
1560
  }
1294
1561
  }
@@ -1298,16 +1565,16 @@ function discoverModules(storage2) {
1298
1565
  }
1299
1566
  function scanModulesDir(modulesDir, scope, spaceName) {
1300
1567
  const modules = [];
1301
- if (!fs12.existsSync(modulesDir)) return modules;
1568
+ if (!fs13.existsSync(modulesDir)) return modules;
1302
1569
  try {
1303
- const entries = fs12.readdirSync(modulesDir);
1570
+ const entries = fs13.readdirSync(modulesDir);
1304
1571
  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;
1572
+ const modulePath = path11.join(modulesDir, entry);
1573
+ const manifestPath = path11.join(modulePath, "module.yaml");
1574
+ if (!fs13.existsSync(manifestPath)) continue;
1308
1575
  try {
1309
- const raw = fs12.readFileSync(manifestPath, "utf-8");
1310
- const manifest = yaml5.load(raw);
1576
+ const raw = fs13.readFileSync(manifestPath, "utf-8");
1577
+ const manifest = yaml6.load(raw);
1311
1578
  if (!manifest || !manifest.name) continue;
1312
1579
  modules.push({
1313
1580
  name: manifest.name,
@@ -1328,12 +1595,12 @@ async function loadModuleTools(modules, storage2) {
1328
1595
  for (const mod of modules) {
1329
1596
  const declaredTools = mod.manifest.provides?.tools;
1330
1597
  if (!declaredTools || declaredTools.length === 0) continue;
1331
- const toolsIndexPath = path10.join(mod.modulePath, "tools", "index.js");
1332
- if (!fs12.existsSync(toolsIndexPath)) continue;
1598
+ const toolsIndexPath = path11.join(mod.modulePath, "tools", "index.js");
1599
+ if (!fs13.existsSync(toolsIndexPath)) continue;
1333
1600
  try {
1334
1601
  const toolsModule = await import(toolsIndexPath);
1335
1602
  const moduleTools2 = toolsModule.tools || toolsModule.default?.tools || [];
1336
- const dataPath = mod.scope === "space" && mod.spaceName ? path10.join(storage2.basePath, mod.spaceName, ".datacore", "modules", mod.name, "data") : path10.join(storage2.basePath, "0-personal", ".datacore", "modules", mod.name, "data");
1603
+ const dataPath = mod.scope === "space" && mod.spaceName ? path11.join(storage2.basePath, mod.spaceName, ".datacore", "modules", mod.name, "data") : path11.join(storage2.basePath, "0-personal", ".datacore", "modules", mod.name, "data");
1337
1604
  const context = {
1338
1605
  storage: storage2,
1339
1606
  modulePath: mod.modulePath,
@@ -1429,8 +1696,8 @@ async function handleModulesInfo(args2, storage2, cachedModules) {
1429
1696
  }
1430
1697
 
1431
1698
  // src/tools/modules-health.ts
1432
- import * as fs13 from "fs";
1433
- import * as path11 from "path";
1699
+ import * as fs14 from "fs";
1700
+ import * as path12 from "path";
1434
1701
  async function handleModulesHealth(args2, storage2, cachedModules) {
1435
1702
  const modules = cachedModules ?? discoverModules(storage2);
1436
1703
  if (args2.module) {
@@ -1452,10 +1719,10 @@ async function handleModulesHealth(args2, storage2, cachedModules) {
1452
1719
  async function checkModule(mod, storage2) {
1453
1720
  const issues = [];
1454
1721
  const manifest = mod.manifest;
1455
- if (!fs13.existsSync(path11.join(mod.modulePath, "SKILL.md"))) {
1722
+ if (!fs14.existsSync(path12.join(mod.modulePath, "SKILL.md"))) {
1456
1723
  issues.push("Missing SKILL.md (ecosystem entry point)");
1457
1724
  }
1458
- if (!fs13.existsSync(path11.join(mod.modulePath, "CLAUDE.base.md"))) {
1725
+ if (!fs14.existsSync(path12.join(mod.modulePath, "CLAUDE.base.md"))) {
1459
1726
  issues.push("Missing CLAUDE.base.md (AI context)");
1460
1727
  }
1461
1728
  if (!manifest.manifest_version || manifest.manifest_version < 2) {
@@ -1471,8 +1738,8 @@ async function checkModule(mod, storage2) {
1471
1738
  const provides = manifest.provides;
1472
1739
  const declaredTools = provides?.tools || [];
1473
1740
  if (declaredTools.length > 0) {
1474
- const toolsIndex = path11.join(mod.modulePath, "tools", "index.js");
1475
- if (!fs13.existsSync(toolsIndex)) {
1741
+ const toolsIndex = path12.join(mod.modulePath, "tools", "index.js");
1742
+ if (!fs14.existsSync(toolsIndex)) {
1476
1743
  issues.push(`Declares ${declaredTools.length} tools but tools/index.js not found`);
1477
1744
  } else {
1478
1745
  try {
@@ -1491,13 +1758,13 @@ async function checkModule(mod, storage2) {
1491
1758
  const suspectExts = [".db", ".sqlite", ".json"];
1492
1759
  const suspectDirs = ["output", "data", "state"];
1493
1760
  for (const dir of suspectDirs) {
1494
- const fullPath = path11.join(mod.modulePath, dir);
1495
- if (fs13.existsSync(fullPath) && fs13.statSync(fullPath).isDirectory()) {
1761
+ const fullPath = path12.join(mod.modulePath, dir);
1762
+ if (fs14.existsSync(fullPath) && fs14.statSync(fullPath).isDirectory()) {
1496
1763
  issues.push(`Data dir '${dir}/' found in module code (should be in space data path)`);
1497
1764
  }
1498
1765
  }
1499
1766
  try {
1500
- const entries = fs13.readdirSync(mod.modulePath);
1767
+ const entries = fs14.readdirSync(mod.modulePath);
1501
1768
  for (const entry of entries) {
1502
1769
  if (suspectExts.some((ext) => entry.endsWith(ext))) {
1503
1770
  issues.push(`Data file '${entry}' found in module code dir`);
@@ -1554,27 +1821,291 @@ async function handleForget(args2, engramsPath) {
1554
1821
 
1555
1822
  // src/tools/feedback.ts
1556
1823
  async function handleFeedback(args2, engramsPath) {
1824
+ if (args2.signals && args2.signals.length > 0) {
1825
+ return handleBatchFeedback(args2.signals, engramsPath);
1826
+ }
1827
+ return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath);
1828
+ }
1829
+ async function handleSingleFeedback(engram_id, signal, comment, engramsPath) {
1557
1830
  const engrams = loadEngrams(engramsPath);
1558
- const engram = engrams.find((e) => e.id === args2.engram_id);
1831
+ const engram = engrams.find((e) => e.id === engram_id);
1559
1832
  if (!engram) {
1560
- return { success: false, engram_id: args2.engram_id, signal: args2.signal, error: `Engram ${args2.engram_id} not found` };
1833
+ return {
1834
+ mode: "single",
1835
+ success: false,
1836
+ engram_id,
1837
+ signal,
1838
+ error: `Engram ${engram_id} not found`,
1839
+ _hints: buildHints({
1840
+ next: "Engram not found. Use datacore.search or datacore.status to find valid IDs.",
1841
+ related: ["datacore.search", "datacore.status"]
1842
+ })
1843
+ };
1561
1844
  }
1562
1845
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1563
- if (engram.activation.last_accessed === today) {
1564
- }
1565
1846
  if (!engram.feedback_signals) {
1566
1847
  engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
1567
1848
  }
1568
- engram.feedback_signals[args2.signal] += 1;
1849
+ engram.feedback_signals[signal] += 1;
1569
1850
  engram.activation.last_accessed = today;
1570
1851
  atomicWriteYaml(engramsPath, { engrams });
1571
1852
  return {
1853
+ mode: "single",
1572
1854
  success: true,
1573
- engram_id: args2.engram_id,
1574
- signal: args2.signal,
1855
+ engram_id,
1856
+ signal,
1575
1857
  feedback_signals: { ...engram.feedback_signals }
1576
1858
  };
1577
1859
  }
1860
+ async function handleBatchFeedback(signals, engramsPath) {
1861
+ const engrams = loadEngrams(engramsPath);
1862
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1863
+ const results = [];
1864
+ const summary = { positive: 0, negative: 0, neutral: 0 };
1865
+ let changed = false;
1866
+ for (const { engram_id, signal } of signals) {
1867
+ const engram = engrams.find((e) => e.id === engram_id);
1868
+ if (!engram) {
1869
+ results.push({ engram_id, signal, success: false, error: `Engram ${engram_id} not found` });
1870
+ continue;
1871
+ }
1872
+ if (!engram.feedback_signals) {
1873
+ engram.feedback_signals = { positive: 0, negative: 0, neutral: 0 };
1874
+ }
1875
+ engram.feedback_signals[signal] += 1;
1876
+ engram.activation.last_accessed = today;
1877
+ summary[signal]++;
1878
+ changed = true;
1879
+ results.push({ engram_id, signal, success: true });
1880
+ }
1881
+ if (changed) {
1882
+ atomicWriteYaml(engramsPath, { engrams });
1883
+ }
1884
+ return {
1885
+ mode: "batch",
1886
+ results,
1887
+ summary,
1888
+ _hints: buildHints({
1889
+ next: `Batch feedback recorded: ${summary.positive} positive, ${summary.negative} negative, ${summary.neutral} neutral.`,
1890
+ related: ["datacore.session.end", "datacore.status"]
1891
+ })
1892
+ };
1893
+ }
1894
+
1895
+ // src/tools/session-start.ts
1896
+ import * as fs15 from "fs";
1897
+ import * as path13 from "path";
1898
+ async function handleSessionStart(args2, storage2, bridge) {
1899
+ let engrams = null;
1900
+ if (args2.task) {
1901
+ const injectResult = await handleInject(
1902
+ { prompt: args2.task, scope: args2.tags?.length ? `tags:${args2.tags.join(",")}` : void 0 },
1903
+ { engramsPath: storage2.engramsPath, packsPath: storage2.packsPath }
1904
+ );
1905
+ if (injectResult.count > 0) {
1906
+ engrams = { text: injectResult.text, count: injectResult.count };
1907
+ }
1908
+ }
1909
+ const { date: today } = localDate();
1910
+ const journalFile = path13.join(storage2.journalPath, `${today}.md`);
1911
+ const journal_today = fs15.existsSync(journalFile) ? fs15.readFileSync(journalFile, "utf8") : null;
1912
+ const allEngrams = loadEngrams(storage2.engramsPath);
1913
+ const pending_candidates = allEngrams.filter((e) => e.status === "candidate").length;
1914
+ const recommendations = [];
1915
+ if (pending_candidates > 0) {
1916
+ recommendations.push(`${pending_candidates} candidate engram(s) awaiting review. Use datacore.promote to activate.`);
1917
+ }
1918
+ if (!journal_today) {
1919
+ recommendations.push("No journal entry today. Use datacore.capture to start one.");
1920
+ }
1921
+ const hints = args2.task ? buildHints({
1922
+ next: "Work on your task. End with datacore.session.end.",
1923
+ related: ["datacore.session.end", "datacore.feedback"]
1924
+ }) : buildHints({
1925
+ next: "No task specified \u2014 showing journal and candidates only. Call datacore.inject when ready.",
1926
+ related: ["datacore.inject", "datacore.session.end"]
1927
+ });
1928
+ const activeCount = allEngrams.filter((e) => e.status === "active").length;
1929
+ const guide = activeCount === 0 ? SESSION_GUIDE_FULL : SESSION_GUIDE_SHORT;
1930
+ return { engrams, journal_today, pending_candidates, recommendations, guide, _hints: hints };
1931
+ }
1932
+ var SESSION_GUIDE_FULL = `## Datacore Quick Start
1933
+
1934
+ Datacore gives you persistent memory through **engrams** \u2014 knowledge that gets injected into context when relevant.
1935
+
1936
+ ### Session Workflow
1937
+ 1. **session.start** (you just called this) \u2014 get context
1938
+ 2. Work on your task. Use **recall** to search everything, **search** for files.
1939
+ 3. **feedback** \u2014 rate which injected engrams helped (strengthens useful ones)
1940
+ 4. **session.end** \u2014 capture summary + suggest new engrams
1941
+
1942
+ ### Key Tools
1943
+ - **learn** \u2014 record a reusable insight (creates candidate engram)
1944
+ - **promote** \u2014 activate candidate engrams so they appear in future sessions
1945
+ - **capture** \u2014 write a journal entry or knowledge note
1946
+ - **ingest** \u2014 import text and extract engram suggestions
1947
+ - **status** \u2014 system health and actionable recommendations
1948
+ - **forget** \u2014 retire an engram you no longer need
1949
+
1950
+ ### How Engrams Work
1951
+ learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
1952
+ Positive feedback strengthens engrams. Unused ones naturally decay.`;
1953
+ var SESSION_GUIDE_SHORT = `Session started. Workflow: work \u2192 feedback \u2192 session.end.`;
1954
+
1955
+ // src/tools/session-end.ts
1956
+ async function handleSessionEnd(args2, storage2) {
1957
+ const captureResult = await handleCapture(
1958
+ { type: "journal", content: args2.summary, tags: args2.tags },
1959
+ storage2
1960
+ );
1961
+ let engramsCreated = 0;
1962
+ if (args2.engram_suggestions?.length) {
1963
+ for (const suggestion of args2.engram_suggestions) {
1964
+ await handleLearn(
1965
+ { statement: suggestion.statement, type: suggestion.type },
1966
+ storage2.engramsPath
1967
+ );
1968
+ engramsCreated++;
1969
+ }
1970
+ }
1971
+ const autoPromote = getConfig().engrams.auto_promote;
1972
+ const statusLabel = autoPromote ? "active" : "candidates";
1973
+ return {
1974
+ journal_path: captureResult.path ?? null,
1975
+ engrams_created: engramsCreated,
1976
+ _hints: buildHints({
1977
+ next: engramsCreated > 0 ? `Session captured. ${engramsCreated} engram(s) created as ${statusLabel}.` : "Session captured.",
1978
+ related: ["datacore.session.start", "datacore.status"]
1979
+ })
1980
+ };
1981
+ }
1982
+
1983
+ // src/tools/recall.ts
1984
+ async function handleRecall(args2, storage2, bridge) {
1985
+ const sources = args2.sources ?? ["engrams", "journal", "knowledge"];
1986
+ const limit = args2.limit ?? 10;
1987
+ const result = {};
1988
+ let fallbackWarning;
1989
+ if (sources.includes("engrams")) {
1990
+ const engrams = loadEngrams(storage2.engramsPath);
1991
+ const topicWords = args2.topic.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
1992
+ const scored = [];
1993
+ for (const e of engrams) {
1994
+ if (e.status === "retired") continue;
1995
+ const text = `${e.statement} ${e.tags.join(" ")}`.toLowerCase();
1996
+ let score = 0;
1997
+ for (const word of topicWords) {
1998
+ if (text.includes(word)) score++;
1999
+ }
2000
+ if (score > 0) {
2001
+ scored.push({ id: e.id, statement: e.statement, score });
2002
+ }
2003
+ }
2004
+ scored.sort((a, b) => b.score - a.score);
2005
+ const engramResults = scored.slice(0, limit);
2006
+ if (engramResults.length > 0) {
2007
+ result.engrams = engramResults;
2008
+ }
2009
+ }
2010
+ if (sources.includes("journal")) {
2011
+ const searchResult = await handleSearch(
2012
+ { query: args2.topic, scope: "journal", limit },
2013
+ { journalPath: storage2.journalPath, knowledgePath: storage2.knowledgePath },
2014
+ bridge
2015
+ );
2016
+ if (searchResult.results.length > 0) {
2017
+ result.journal = searchResult.results.map((r) => ({
2018
+ path: r.path,
2019
+ snippet: r.snippet,
2020
+ title: r.title,
2021
+ date: r.date,
2022
+ score: r.score
2023
+ }));
2024
+ }
2025
+ if (searchResult.fallback_warning) {
2026
+ fallbackWarning = searchResult.fallback_warning;
2027
+ }
2028
+ }
2029
+ if (sources.includes("knowledge")) {
2030
+ const searchResult = await handleSearch(
2031
+ { query: args2.topic, scope: "knowledge", limit },
2032
+ { journalPath: storage2.journalPath, knowledgePath: storage2.knowledgePath },
2033
+ bridge
2034
+ );
2035
+ if (searchResult.results.length > 0) {
2036
+ result.knowledge = searchResult.results.map((r) => ({
2037
+ path: r.path,
2038
+ snippet: r.snippet,
2039
+ title: r.title,
2040
+ date: r.date,
2041
+ score: r.score
2042
+ }));
2043
+ }
2044
+ if (searchResult.fallback_warning) {
2045
+ fallbackWarning = searchResult.fallback_warning;
2046
+ }
2047
+ }
2048
+ if (fallbackWarning) {
2049
+ result.fallback_warning = fallbackWarning;
2050
+ }
2051
+ result._hints = buildHints({
2052
+ next: "Use datacore.feedback on helpful engrams, or datacore.learn to create new ones.",
2053
+ related: ["datacore.feedback", "datacore.learn"]
2054
+ });
2055
+ return result;
2056
+ }
2057
+
2058
+ // src/tools/promote.ts
2059
+ async function handlePromote(args2, engramsPath) {
2060
+ const targetIds = args2.ids ?? (args2.id ? [args2.id] : []);
2061
+ if (targetIds.length === 0) {
2062
+ return {
2063
+ success: false,
2064
+ promoted: [],
2065
+ errors: [{ id: "", error: "At least one engram ID required (id or ids)" }],
2066
+ _hints: buildHints({
2067
+ next: "Provide an engram ID. Use datacore.search or datacore.status to find valid IDs.",
2068
+ related: ["datacore.search", "datacore.status"]
2069
+ })
2070
+ };
2071
+ }
2072
+ const engrams = loadEngrams(engramsPath);
2073
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2074
+ const promoted = [];
2075
+ const errors = [];
2076
+ for (const id of targetIds) {
2077
+ const engram = engrams.find((e) => e.id === id);
2078
+ if (!engram) {
2079
+ errors.push({ id, error: "Engram not found" });
2080
+ continue;
2081
+ }
2082
+ if (engram.status === "active") {
2083
+ errors.push({ id, error: "Already active" });
2084
+ continue;
2085
+ }
2086
+ if (engram.status === "retired") {
2087
+ errors.push({ id, error: "Cannot promote retired engram" });
2088
+ continue;
2089
+ }
2090
+ engram.status = "active";
2091
+ engram.activation.retrieval_strength = 0.7;
2092
+ engram.activation.storage_strength = 1;
2093
+ engram.activation.last_accessed = today;
2094
+ promoted.push({ id: engram.id, statement: engram.statement });
2095
+ }
2096
+ if (promoted.length > 0) {
2097
+ atomicWriteYaml(engramsPath, { engrams });
2098
+ }
2099
+ return {
2100
+ success: errors.length === 0,
2101
+ promoted,
2102
+ errors,
2103
+ _hints: buildHints({
2104
+ next: promoted.length > 0 ? `Promoted ${promoted.length} engram(s). They will now appear in inject results.` : "No engrams were promoted. Check the errors above.",
2105
+ related: ["datacore.inject", "datacore.status"]
2106
+ })
2107
+ };
2108
+ }
1578
2109
 
1579
2110
  // src/resources.ts
1580
2111
  import {
@@ -1582,8 +2113,8 @@ import {
1582
2113
  ReadResourceRequestSchema,
1583
2114
  ListResourceTemplatesRequestSchema
1584
2115
  } from "@modelcontextprotocol/sdk/types.js";
1585
- import * as fs14 from "fs";
1586
- import * as path12 from "path";
2116
+ import * as fs16 from "fs";
2117
+ import * as path14 from "path";
1587
2118
  function registerResources(server, storage2) {
1588
2119
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
1589
2120
  resources: [
@@ -1604,6 +2135,12 @@ function registerResources(server, storage2) {
1604
2135
  name: "Today's Journal",
1605
2136
  description: "Today's journal entry",
1606
2137
  mimeType: "text/markdown"
2138
+ },
2139
+ {
2140
+ uri: "datacore://guide",
2141
+ name: "Datacore Agent Guide",
2142
+ description: "Workflow guide for AI agents: session lifecycle, engram lifecycle, tool reference",
2143
+ mimeType: "text/markdown"
1607
2144
  }
1608
2145
  ]
1609
2146
  }));
@@ -1646,14 +2183,23 @@ function registerResources(server, storage2) {
1646
2183
  }]
1647
2184
  };
1648
2185
  }
2186
+ if (uri === "datacore://guide") {
2187
+ return {
2188
+ contents: [{
2189
+ uri,
2190
+ mimeType: "text/markdown",
2191
+ text: AGENT_GUIDE
2192
+ }]
2193
+ };
2194
+ }
1649
2195
  const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
1650
2196
  if (journalMatch) {
1651
2197
  const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
1652
- const filePath = path12.join(storage2.journalPath, `${dateStr}.md`);
1653
- if (!fs14.existsSync(filePath)) {
2198
+ const filePath = path14.join(storage2.journalPath, `${dateStr}.md`);
2199
+ if (!fs16.existsSync(filePath)) {
1654
2200
  return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
1655
2201
  }
1656
- return { contents: [{ uri, mimeType: "text/markdown", text: fs14.readFileSync(filePath, "utf8") }] };
2202
+ return { contents: [{ uri, mimeType: "text/markdown", text: fs16.readFileSync(filePath, "utf8") }] };
1657
2203
  }
1658
2204
  const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
1659
2205
  if (engramMatch) {
@@ -1673,6 +2219,40 @@ function registerResources(server, storage2) {
1673
2219
  throw new Error(`Unknown resource: ${uri}`);
1674
2220
  });
1675
2221
  }
2222
+ var AGENT_GUIDE = `# Datacore Agent Guide
2223
+
2224
+ ## Session Lifecycle
2225
+ 1. datacore.session.start \u2014 Get relevant engrams + today's context
2226
+ 2. Work on task, use datacore.recall or datacore.search as needed
2227
+ 3. datacore.feedback \u2014 Rate which injected engrams helped (batch supported)
2228
+ 4. datacore.session.end \u2014 Capture summary + engram suggestions
2229
+
2230
+ ## Engram Lifecycle
2231
+ - datacore.learn creates candidate engrams (or active if auto_promote enabled)
2232
+ - datacore.promote activates candidates so they appear in inject results
2233
+ - datacore.feedback with positive signals strengthens injection priority
2234
+ - datacore.forget retires engrams permanently
2235
+ - Unused engrams naturally decay over time
2236
+
2237
+ ## Quick Reference
2238
+ | Tool | Purpose |
2239
+ |------|---------|
2240
+ | session.start | Begin session with context injection |
2241
+ | session.end | End session with journal + engrams |
2242
+ | learn | Create engram from knowledge statement |
2243
+ | promote | Activate candidate engrams |
2244
+ | inject | Get relevant engrams for specific task |
2245
+ | recall | Search all sources (engrams + journal + knowledge) |
2246
+ | capture | Write journal entry or knowledge note |
2247
+ | search | Keyword/semantic file search |
2248
+ | ingest | Ingest text + extract engram suggestions |
2249
+ | feedback | Rate engrams (single or batch) |
2250
+ | forget | Retire an engram |
2251
+ | status | System health + actionable recommendations |
2252
+ | packs.discover | Browse available engram packs |
2253
+ | packs.install | Install or upgrade a pack |
2254
+ | packs.export | Export engrams as shareable pack |
2255
+ `;
1676
2256
  function notifyEngramsChanged(server) {
1677
2257
  try {
1678
2258
  server.sendResourceUpdated?.({ uri: "datacore://engrams/active" });
@@ -1680,10 +2260,178 @@ function notifyEngramsChanged(server) {
1680
2260
  }
1681
2261
  }
1682
2262
 
2263
+ // src/prompts.ts
2264
+ import {
2265
+ ListPromptsRequestSchema,
2266
+ GetPromptRequestSchema
2267
+ } from "@modelcontextprotocol/sdk/types.js";
2268
+ var PROMPTS = [
2269
+ {
2270
+ name: "datacore-session",
2271
+ title: "Start a Datacore session",
2272
+ description: "Begin a working session with Datacore. Injects relevant context, shows today's journal, and guides you through the session lifecycle.",
2273
+ arguments: [
2274
+ { name: "task", description: "What you are working on (optional \u2014 triggers engram injection)", required: false }
2275
+ ],
2276
+ messages: (args2) => [{
2277
+ role: "user",
2278
+ content: {
2279
+ type: "text",
2280
+ text: `Start a new Datacore session.${args2.task ? ` Task: ${args2.task}` : ""}
2281
+
2282
+ Call datacore.session.start${args2.task ? ` with task: "${args2.task}"` : ""} to begin. This will:
2283
+ - Inject relevant engrams (learned knowledge) for the task
2284
+ - Show today's journal entry if one exists
2285
+ - List any candidate engrams awaiting review
2286
+
2287
+ When done working, call datacore.session.end with a summary and any new learnings.
2288
+
2289
+ Session lifecycle:
2290
+ 1. datacore.session.start \u2192 get context
2291
+ 2. Work on task (use datacore.recall or datacore.search as needed)
2292
+ 3. datacore.feedback \u2192 rate which injected engrams were helpful
2293
+ 4. datacore.session.end \u2192 capture summary + new engrams`
2294
+ }
2295
+ }]
2296
+ },
2297
+ {
2298
+ name: "datacore-learn",
2299
+ title: "Teach Datacore something",
2300
+ description: "Record a reusable learning as an engram. Covers the full engram lifecycle: create, review, activate, reinforce.",
2301
+ arguments: [
2302
+ { name: "statement", description: 'The knowledge to record (e.g., "Always run tests before deploying")', required: true }
2303
+ ],
2304
+ messages: (args2) => [{
2305
+ role: "user",
2306
+ content: {
2307
+ type: "text",
2308
+ text: `Record this learning in Datacore: "${args2.statement || "..."}"
2309
+
2310
+ Call datacore.learn with the statement. This creates a candidate engram.
2311
+
2312
+ Engram lifecycle:
2313
+ 1. datacore.learn \u2192 creates candidate (not yet active)
2314
+ 2. datacore.promote \u2192 activates it so it appears in future inject results
2315
+ 3. datacore.inject \u2192 retrieves relevant engrams when working on tasks
2316
+ 4. datacore.feedback \u2192 positive signals strengthen it, negative signals weaken it
2317
+ 5. datacore.forget \u2192 retires it permanently if no longer useful
2318
+
2319
+ Engrams that prove useful get reinforced over time. Unused ones naturally decay.`
2320
+ }
2321
+ }]
2322
+ },
2323
+ {
2324
+ name: "datacore-guide",
2325
+ title: "How to use Datacore",
2326
+ description: "Complete guide to Datacore tools, workflows, and concepts. Read this to understand the system.",
2327
+ messages: () => [{
2328
+ role: "assistant",
2329
+ content: {
2330
+ type: "text",
2331
+ text: `# Datacore \u2014 Persistent Memory for AI
2332
+
2333
+ Datacore gives you persistent memory through **engrams** \u2014 typed knowledge units that get injected into context when relevant. You learn patterns, remember preferences, and build on previous work across sessions.
2334
+
2335
+ ## Core Concepts
2336
+
2337
+ **Engrams** are reusable knowledge: "Always validate input at API boundaries", "User prefers tabs over spaces". They have activation dynamics \u2014 frequently useful ones get stronger, unused ones decay.
2338
+
2339
+ **Journal** is your session log. Each session captures what happened, what was learned.
2340
+
2341
+ **Knowledge** is ingested reference material \u2014 articles, notes, documents broken into searchable pieces.
2342
+
2343
+ ## Session Workflow
2344
+
2345
+ Every session follows this pattern:
2346
+
2347
+ 1. **datacore.session.start** \u2014 Injects relevant engrams + shows today's journal
2348
+ 2. **Work** \u2014 Use datacore.recall or datacore.search to find information
2349
+ 3. **datacore.feedback** \u2014 Rate which engrams were helpful (strengthens good ones)
2350
+ 4. **datacore.session.end** \u2014 Capture summary + suggest new engrams
2351
+
2352
+ ## Tool Reference
2353
+
2354
+ ### Session
2355
+ | Tool | What it does |
2356
+ |------|-------------|
2357
+ | **session.start** | Begin session, inject context, show journal |
2358
+ | **session.end** | End session, capture journal + new engrams |
2359
+
2360
+ ### Core
2361
+ | Tool | What it does |
2362
+ |------|-------------|
2363
+ | **capture** | Write a journal entry or knowledge note |
2364
+ | **learn** | Create an engram (starts as candidate) |
2365
+ | **promote** | Activate candidate engrams |
2366
+ | **inject** | Get relevant engrams for a specific task |
2367
+ | **recall** | Search everything (engrams + journal + knowledge) |
2368
+ | **search** | Search journal and knowledge files |
2369
+ | **ingest** | Ingest text, extract engram suggestions |
2370
+ | **status** | System health + actionable recommendations |
2371
+
2372
+ ### Engram Management
2373
+ | Tool | What it does |
2374
+ |------|-------------|
2375
+ | **feedback** | Rate engrams: positive/negative/neutral (single or batch) |
2376
+ | **forget** | Retire an engram permanently |
2377
+
2378
+ ### Packs (Shareable Knowledge)
2379
+ | Tool | What it does |
2380
+ |------|-------------|
2381
+ | **packs.discover** | Browse available engram packs |
2382
+ | **packs.install** | Install a pack |
2383
+ | **packs.export** | Export your engrams as a pack |
2384
+
2385
+ ## Engram Lifecycle
2386
+
2387
+ \`\`\`
2388
+ learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
2389
+ \u2192 forget (retire)
2390
+ \`\`\`
2391
+
2392
+ - **candidate**: Created but not yet active. Won't appear in inject results.
2393
+ - **active**: Appears in inject results when relevant.
2394
+ - **retired**: Permanently removed from injection.
2395
+
2396
+ Feedback matters: positive signals increase retrieval strength, negative signals decrease it. Engrams that are never accessed naturally decay over time.
2397
+
2398
+ ## Tips
2399
+
2400
+ - Start every session with **session.start** \u2014 it gives you relevant context
2401
+ - End every session with **session.end** \u2014 it captures what you learned
2402
+ - Use **feedback** after getting injected engrams \u2014 this is how Datacore learns what's useful
2403
+ - Use **recall** for broad searches across all sources, **search** for targeted file searches
2404
+ - Check **status** periodically \u2014 it shows actionable recommendations`
2405
+ }
2406
+ }]
2407
+ }
2408
+ ];
2409
+ function registerPrompts(server) {
2410
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
2411
+ prompts: PROMPTS.map((p) => ({
2412
+ name: p.name,
2413
+ title: p.title,
2414
+ description: p.description,
2415
+ arguments: p.arguments
2416
+ }))
2417
+ }));
2418
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2419
+ const { name, arguments: promptArgs } = request.params;
2420
+ const prompt = PROMPTS.find((p) => p.name === name);
2421
+ if (!prompt) {
2422
+ throw new Error(`Unknown prompt: ${name}`);
2423
+ }
2424
+ return {
2425
+ description: prompt.description,
2426
+ messages: prompt.messages(promptArgs ?? {})
2427
+ };
2428
+ });
2429
+ }
2430
+
1683
2431
  // src/datacortex.ts
1684
2432
  import { execFile } from "child_process";
1685
- import * as fs15 from "fs";
1686
- import * as path13 from "path";
2433
+ import * as fs17 from "fs";
2434
+ import * as path15 from "path";
1687
2435
  var DatacortexBridge = class {
1688
2436
  pythonPath;
1689
2437
  scriptPath;
@@ -1693,11 +2441,11 @@ var DatacortexBridge = class {
1693
2441
  }
1694
2442
  findBridgeScript(datacorePath) {
1695
2443
  const candidates = [
1696
- path13.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
1697
- path13.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
2444
+ path15.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
2445
+ path15.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
1698
2446
  ];
1699
2447
  for (const candidate of candidates) {
1700
- if (fs15.existsSync(candidate)) return candidate;
2448
+ if (fs17.existsSync(candidate)) return candidate;
1701
2449
  }
1702
2450
  return null;
1703
2451
  }
@@ -1760,22 +2508,25 @@ var datacortexBridge = null;
1760
2508
  function createServer() {
1761
2509
  const server = new Server(
1762
2510
  { name: "datacore-mcp", version: currentVersion },
1763
- { capabilities: { tools: {}, logging: {}, resources: { subscribe: true } } }
2511
+ { capabilities: { tools: {}, logging: {}, resources: { subscribe: true }, prompts: {} } }
1764
2512
  );
1765
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
1766
- 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
- }));
2513
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
2514
+ const coreTools = storage.mode === "core" ? TOOLS.filter((t) => !t.name.startsWith("datacore.modules.")) : TOOLS;
2515
+ return {
2516
+ tools: [
2517
+ ...coreTools.map((t) => ({
2518
+ name: t.name,
2519
+ description: t.description,
2520
+ inputSchema: zodToJsonSchema(t.inputSchema)
2521
+ })),
2522
+ ...moduleTools.map((t) => ({
2523
+ name: t.fullName,
2524
+ description: t.definition.description,
2525
+ inputSchema: zodToJsonSchema(t.definition.inputSchema)
2526
+ }))
2527
+ ]
2528
+ };
2529
+ });
1779
2530
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
1780
2531
  const { name, arguments: args2 } = request.params;
1781
2532
  try {
@@ -1795,10 +2546,11 @@ function createServer() {
1795
2546
  });
1796
2547
  logger.setServer(server);
1797
2548
  registerResources(server, storage);
2549
+ registerPrompts(server);
1798
2550
  serverRef = server;
1799
2551
  return server;
1800
2552
  }
1801
- var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback"]);
2553
+ var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote"]);
1802
2554
  async function routeTool(name, args2) {
1803
2555
  const coreTool = TOOLS.find((t) => t.name === name);
1804
2556
  if (coreTool) {
@@ -1829,6 +2581,18 @@ async function routeTool(name, args2) {
1829
2581
  case "datacore.feedback":
1830
2582
  result = await handleFeedback(validated, storage.engramsPath);
1831
2583
  break;
2584
+ case "datacore.session.start":
2585
+ result = await handleSessionStart(validated, storage, datacortexBridge);
2586
+ break;
2587
+ case "datacore.session.end":
2588
+ result = await handleSessionEnd(validated, storage);
2589
+ break;
2590
+ case "datacore.recall":
2591
+ result = await handleRecall(validated, { engramsPath: storage.engramsPath, journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
2592
+ break;
2593
+ case "datacore.promote":
2594
+ result = await handlePromote(validated, storage.engramsPath);
2595
+ break;
1832
2596
  case "datacore.packs.discover":
1833
2597
  result = handleDiscover(validated, storage.packsPath);
1834
2598
  break;
@@ -1890,6 +2654,7 @@ async function initStorage() {
1890
2654
  const result = initCore(storage.basePath);
1891
2655
  isFirstRun = result.isFirstRun;
1892
2656
  }
2657
+ loadConfig(storage.basePath, storage.mode);
1893
2658
  if (storage.mode === "full") {
1894
2659
  discoveredModules = discoverModules(storage);
1895
2660
  moduleTools = await loadModuleTools(discoveredModules, storage);