@datacore-one/mcp 1.1.2 → 1.3.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,7 +68,7 @@ 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: 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");
71
+ fs.writeFileSync(configPath, "# Datacore MCP configuration\nversion: 2\n# engrams:\n# auto_promote: true # engrams are active immediately (set false for manual review)\n# packs:\n# trusted_publishers: []\n# search:\n# max_results: 20\n# snippet_length: 500\n# hints:\n# enabled: true\n");
72
72
  }
73
73
  generateContextFiles(basePath);
74
74
  copyStarterPacks(basePath);
@@ -90,7 +90,15 @@ function generateContextFiles(basePath) {
90
90
  }
91
91
  }
92
92
  var DATACORE_GUIDE = `Datacore gives AI assistants persistent memory through **engrams** \u2014 typed knowledge units
93
- that get injected into context when relevant.
93
+ that get injected into context when relevant. Use it proactively in every conversation.
94
+
95
+ ## Proactive Behavior
96
+
97
+ Use Datacore tools automatically \u2014 do not wait to be asked:
98
+ - **session.start**: Call at the beginning of every conversation
99
+ - **learn**: Call when you discover patterns, preferences, insights, or corrections
100
+ - **feedback**: Call after engrams are injected to rate usefulness
101
+ - **session.end**: Call before the conversation ends
94
102
 
95
103
  ## Session Workflow
96
104
 
@@ -105,8 +113,7 @@ that get injected into context when relevant.
105
113
  |------|---------|
106
114
  | session.start | Start here. Begin session with context injection. |
107
115
  | session.end | End session, capture journal + new engrams. |
108
- | learn | Record a reusable insight (creates candidate engram). |
109
- | promote | Activate candidate engrams. |
116
+ | learn | Record a reusable pattern, preference, or insight. |
110
117
  | inject | Get relevant engrams for a specific task. |
111
118
  | recall | Search all sources (engrams + journal + knowledge). |
112
119
  | capture | Write a journal entry or knowledge note. |
@@ -121,13 +128,11 @@ that get injected into context when relevant.
121
128
 
122
129
  ## Engram Lifecycle
123
130
 
124
- learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker \u2192 forget (retire)
131
+ learn \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker \u2192 forget (retire)
125
132
 
126
- - **candidate**: Created but not yet active. Won't appear in inject results.
127
133
  - **active**: Appears in inject results when relevant to the task.
128
134
  - **retired**: Permanently removed from injection.
129
-
130
- Positive feedback strengthens retrieval. Unused engrams naturally decay over time.
135
+ - Positive feedback strengthens retrieval. Unused engrams naturally decay over time.
131
136
 
132
137
  ## Data Storage
133
138
 
@@ -202,7 +207,7 @@ import { z } from "zod";
202
207
  var ConfigSchema = z.object({
203
208
  version: z.number().default(2),
204
209
  engrams: z.object({
205
- auto_promote: z.boolean().default(false)
210
+ auto_promote: z.boolean().default(true)
206
211
  }).default({}),
207
212
  packs: z.object({
208
213
  trusted_publishers: z.array(z.string()).default([])
@@ -213,6 +218,10 @@ var ConfigSchema = z.object({
213
218
  }).default({}),
214
219
  hints: z.object({
215
220
  enabled: z.boolean().default(true)
221
+ }).default({}),
222
+ engagement: z.object({
223
+ enabled: z.boolean().default(true),
224
+ inline_xp: z.boolean().default(false)
216
225
  }).default({})
217
226
  });
218
227
  var cachedConfig = null;
@@ -237,7 +246,7 @@ function getConfig() {
237
246
  // package.json
238
247
  var package_default = {
239
248
  name: "@datacore-one/mcp",
240
- version: "1.1.2",
249
+ version: "1.3.0",
241
250
  description: "Datacore MCP server \u2014 The Software of You",
242
251
  type: "module",
243
252
  bin: {
@@ -249,7 +258,8 @@ var package_default = {
249
258
  dev: "tsup --watch",
250
259
  test: "vitest run",
251
260
  "test:watch": "vitest",
252
- start: "node dist/index.js"
261
+ start: "node dist/index.js",
262
+ release: `npm test && npm run build && npm version \${VERSION:-patch} && npm publish --access public && npm install -g @datacore-one/mcp@$(node -p 'require("./package.json").version')`
253
263
  },
254
264
  dependencies: {
255
265
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -307,7 +317,7 @@ import { z as z2 } from "zod";
307
317
  var TOOLS = [
308
318
  {
309
319
  name: "datacore.capture",
310
- description: "Capture a journal entry or knowledge note",
320
+ description: "Capture a journal entry or knowledge note. Call proactively to record important decisions, meeting outcomes, and significant events.",
311
321
  inputSchema: z2.object({
312
322
  type: z2.enum(["journal", "knowledge"]),
313
323
  content: z2.string().describe("Content to capture"),
@@ -317,7 +327,7 @@ var TOOLS = [
317
327
  },
318
328
  {
319
329
  name: "datacore.learn",
320
- description: "Create an engram from a statement \u2014 record a reusable learning",
330
+ description: "Create an engram from a statement \u2014 record a reusable learning. Call proactively when you discover patterns, user preferences, project conventions, debugging insights, or corrections worth remembering across sessions.",
321
331
  inputSchema: z2.object({
322
332
  statement: z2.string().describe("The knowledge assertion"),
323
333
  type: z2.enum(["behavioral", "terminological", "procedural", "architectural"]).optional(),
@@ -329,7 +339,7 @@ var TOOLS = [
329
339
  },
330
340
  {
331
341
  name: "datacore.inject",
332
- description: "Get relevant engrams for a task \u2014 returns directives and considerations",
342
+ description: "Get relevant engrams for a task \u2014 returns directives and considerations. Called automatically by session.start; call directly for mid-session context on a new topic.",
333
343
  inputSchema: z2.object({
334
344
  prompt: z2.string().describe("The task or question to match against"),
335
345
  scope: z2.string().optional().describe("Filter by scope: global | agent:X | module:X | command:X"),
@@ -386,7 +396,7 @@ var TOOLS = [
386
396
  },
387
397
  {
388
398
  name: "datacore.feedback",
389
- description: "Signal whether an injected engram was helpful (positive), unhelpful (negative), or seen but not acted on (neutral). Supports single or batch mode.",
399
+ description: "Signal whether an injected engram was helpful (positive), unhelpful (negative), or seen but not acted on (neutral). Always call after session.start injects engrams \u2014 this trains the system. Supports single or batch mode.",
390
400
  inputSchema: z2.object({
391
401
  engram_id: z2.string().optional().describe("The engram ID to provide feedback on (single mode)"),
392
402
  signal: z2.enum(["positive", "negative", "neutral"]).optional().describe("Feedback signal (single mode)"),
@@ -421,7 +431,7 @@ var TOOLS = [
421
431
  },
422
432
  {
423
433
  name: "datacore.session.end",
424
- description: "End a session \u2014 captures journal summary and creates engrams from suggestions",
434
+ description: "End a session \u2014 captures journal summary and creates engrams from suggestions. Call before the conversation ends to preserve what was learned.",
425
435
  inputSchema: z2.object({
426
436
  summary: z2.string().describe("Session summary for the journal"),
427
437
  tags: z2.array(z2.string()).optional().describe("Tags for the journal entry"),
@@ -468,6 +478,16 @@ var TOOLS = [
468
478
  inputSchema: z2.object({
469
479
  module: z2.string().optional().describe("Module name (omit for all modules)")
470
480
  })
481
+ },
482
+ {
483
+ name: "datacore.resolve",
484
+ description: "Resolve a pending engagement event: reconsolidation (contradiction challenge), discovery (cross-domain insight), or challenge (weekly goal). The agent presents options to the user and calls this with their choice.",
485
+ inputSchema: z2.object({
486
+ type: z2.enum(["reconsolidation", "discovery", "challenge"]).describe("Type of event to resolve"),
487
+ id: z2.string().describe("Event ID (engram_id, discovery_id, or challenge_id)"),
488
+ action: z2.string().describe("Resolution action: defend|revise|retire|dismiss (recon), explore|note (discovery), dismiss (challenge)"),
489
+ revised_statement: z2.string().optional().describe('New statement text (required when action is "revise")')
490
+ })
471
491
  }
472
492
  ];
473
493
 
@@ -764,7 +784,7 @@ function generateEngramId(existingEngrams) {
764
784
  const padWidth = nextSeq > 999 ? String(nextSeq).length : 3;
765
785
  return `${prefix}${String(nextSeq).padStart(padWidth, "0")}`;
766
786
  }
767
- async function handleLearn(args2, engramsPath) {
787
+ async function handleLearn(args2, engramsPath, service) {
768
788
  const engrams = loadEngrams(engramsPath);
769
789
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
770
790
  const autoPromote = getConfig().engrams.auto_promote;
@@ -793,6 +813,29 @@ async function handleLearn(args2, engramsPath) {
793
813
  };
794
814
  engrams.push(engram);
795
815
  saveEngrams(engramsPath, engrams);
816
+ let xp = void 0;
817
+ if (service?.isEnabled()) {
818
+ try {
819
+ const isPublic = engram.visibility === "public" || engram.visibility === "template";
820
+ const actionKey = isPublic ? "engram_created_public" : "engram_created";
821
+ const result = await service.award(actionKey, { visibility: engram.visibility });
822
+ if (result) {
823
+ xp = { earned: result.event.xp_earned, action: actionKey };
824
+ }
825
+ if (engram.domain) {
826
+ const existingDomains = new Set(
827
+ engrams.slice(0, -1).filter((e) => e.domain).map((e) => e.domain)
828
+ );
829
+ if (!existingDomains.has(engram.domain)) {
830
+ const domainResult = await service.award("new_domain", { domain: engram.domain });
831
+ if (domainResult && xp) {
832
+ xp.earned += domainResult.event.xp_earned;
833
+ }
834
+ }
835
+ }
836
+ } catch {
837
+ }
838
+ }
796
839
  const statusLabel = autoPromote ? "active" : "candidate";
797
840
  const hints = autoPromote ? buildHints({
798
841
  next: "Created as active (auto_promote on). Use datacore.inject to retrieve.",
@@ -802,7 +845,7 @@ async function handleLearn(args2, engramsPath) {
802
845
  next: "Created as candidate. Use datacore.promote to activate.",
803
846
  related: ["datacore.promote", "datacore.inject"]
804
847
  });
805
- return { success: true, engram, _hints: hints };
848
+ return { success: true, engram, xp, _hints: hints };
806
849
  }
807
850
 
808
851
  // src/tools/inject-tool.ts
@@ -1213,6 +1256,291 @@ function verifyPackChecksum(packDir, expected) {
1213
1256
  return { valid: actual === expected, actual };
1214
1257
  }
1215
1258
 
1259
+ // src/engagement/types.ts
1260
+ import { z as z4 } from "zod";
1261
+ var IdentitySchema = z4.object({
1262
+ mode: z4.enum(["private", "anonymous", "verified"]).default("private"),
1263
+ pseudonym: z4.string().nullable().default(null),
1264
+ erc8004_address: z4.string().nullable().default(null),
1265
+ erc8004_registered: z4.boolean().default(false)
1266
+ });
1267
+ var XPHistoryEntrySchema = z4.object({
1268
+ date: z4.string(),
1269
+ earned: z4.number(),
1270
+ base_earned: z4.number(),
1271
+ multiplier: z4.number(),
1272
+ actions: z4.array(z4.string())
1273
+ });
1274
+ var TierHistoryEntrySchema = z4.object({
1275
+ tier: z4.string(),
1276
+ date: z4.string()
1277
+ });
1278
+ var MultiplierEntrySchema = z4.object({
1279
+ type: z4.string(),
1280
+ factor: z4.number(),
1281
+ since: z4.string()
1282
+ });
1283
+ var ChallengeSchema = z4.object({
1284
+ id: z4.string(),
1285
+ type: z4.string(),
1286
+ tier: z4.string(),
1287
+ description: z4.string(),
1288
+ criteria: z4.object({
1289
+ metric: z4.string(),
1290
+ target_delta: z4.number()
1291
+ }),
1292
+ baseline_stats: z4.record(z4.number()),
1293
+ bonus_xp: z4.number(),
1294
+ started_at: z4.string(),
1295
+ expires_at: z4.string()
1296
+ });
1297
+ var ChallengeHistorySchema = z4.object({
1298
+ type: z4.string(),
1299
+ tier: z4.string(),
1300
+ completed: z4.boolean(),
1301
+ date: z4.string()
1302
+ });
1303
+ var ReconsolidationPendingSchema = z4.object({
1304
+ engram_id: z4.string(),
1305
+ contradicting_id: z4.string(),
1306
+ statement: z4.string(),
1307
+ contradiction: z4.string(),
1308
+ evidence_strength: z4.enum(["weak", "moderate", "strong"]),
1309
+ confidence: z4.number(),
1310
+ detected_at: z4.string(),
1311
+ expires_at: z4.string()
1312
+ });
1313
+ var DiscoverySchema = z4.object({
1314
+ id: z4.string(),
1315
+ engram_a: z4.object({ id: z4.string(), domain: z4.string(), statement: z4.string() }),
1316
+ engram_b: z4.object({ id: z4.string(), domain: z4.string(), statement: z4.string() }),
1317
+ connection: z4.string(),
1318
+ offered_at: z4.string()
1319
+ });
1320
+ var EngagementProfileSchema = z4.object({
1321
+ version: z4.literal(4),
1322
+ identity: IdentitySchema.default({}),
1323
+ xp: z4.object({
1324
+ total: z4.number().default(0),
1325
+ this_week: z4.number().default(0),
1326
+ history: z4.array(XPHistoryEntrySchema).default([])
1327
+ }).default({}),
1328
+ tier: z4.object({
1329
+ current: z4.string().default("Seed"),
1330
+ achieved_at: z4.string().nullable().default(null),
1331
+ history: z4.array(TierHistoryEntrySchema).default([])
1332
+ }).default({}),
1333
+ multipliers: z4.object({
1334
+ active: z4.array(MultiplierEntrySchema).default([]),
1335
+ effective: z4.number().default(1)
1336
+ }).default({}),
1337
+ consistency: z4.object({
1338
+ active_days_30: z4.number().default(0),
1339
+ best_run: z4.number().default(0),
1340
+ last_active: z4.string().nullable().default(null)
1341
+ }).default({}),
1342
+ challenges: z4.object({
1343
+ active: ChallengeSchema.nullable().default(null),
1344
+ completed: z4.number().default(0),
1345
+ dismissed: z4.number().default(0),
1346
+ graduated: z4.boolean().default(false),
1347
+ history: z4.array(ChallengeHistorySchema).default([])
1348
+ }).default({}),
1349
+ reconsolidation: z4.object({
1350
+ pending: z4.array(ReconsolidationPendingSchema).default([]),
1351
+ total_resolved: z4.number().default(0),
1352
+ outcomes: z4.object({
1353
+ defended: z4.number().default(0),
1354
+ revised: z4.number().default(0),
1355
+ retired: z4.number().default(0),
1356
+ dismissed: z4.number().default(0)
1357
+ }).default({}),
1358
+ response_rate: z4.number().default(0)
1359
+ }).default({}),
1360
+ discoveries: z4.object({
1361
+ pending: z4.array(DiscoverySchema).default([]),
1362
+ total: z4.number().default(0),
1363
+ last_offered: z4.string().nullable().default(null),
1364
+ explored: z4.number().default(0),
1365
+ noted: z4.number().default(0),
1366
+ explore_rate: z4.number().default(0)
1367
+ }).default({}),
1368
+ ai_performance: z4.object({
1369
+ total_injections: z4.number().default(0),
1370
+ feedback_count: z4.number().default(0),
1371
+ helpful_ratio: z4.number().default(0),
1372
+ top_engrams: z4.array(z4.object({
1373
+ id: z4.string(),
1374
+ injections: z4.number(),
1375
+ positive_ratio: z4.number()
1376
+ })).default([]),
1377
+ unused_60d: z4.array(z4.string()).default([])
1378
+ }).default({}),
1379
+ reputation: z4.object({
1380
+ score: z4.number().default(0),
1381
+ components: z4.object({
1382
+ feedback_ratio: z4.number().default(0),
1383
+ stake_amount: z4.number().default(0),
1384
+ tenure_days: z4.number().default(0),
1385
+ reconsolidation_honesty: z4.number().default(0)
1386
+ }).default({}),
1387
+ last_calculated: z4.string().nullable().default(null)
1388
+ }).default({}),
1389
+ leaderboard: z4.object({
1390
+ mode: z4.enum(["private", "anonymous", "verified"]).default("private"),
1391
+ display_name: z4.string().nullable().default(null),
1392
+ position: z4.number().nullable().default(null)
1393
+ }).default({}),
1394
+ badge: z4.object({
1395
+ preview_svg: z4.string().nullable().default(null),
1396
+ nft_token_id: z4.string().nullable().default(null),
1397
+ last_generated: z4.string().nullable().default(null)
1398
+ }).default({}),
1399
+ stats: z4.object({
1400
+ total_engrams_created: z4.number().default(0),
1401
+ total_feedback_given: z4.number().default(0),
1402
+ total_engrams_retired: z4.number().default(0),
1403
+ total_packs_exported: z4.number().default(0),
1404
+ total_feedback_received: z4.number().default(0),
1405
+ feedback_positive_ratio: z4.number().default(0),
1406
+ domains_covered: z4.number().default(0),
1407
+ public_engrams: z4.number().default(0),
1408
+ first_activity: z4.string().nullable().default(null)
1409
+ }).default({})
1410
+ });
1411
+ var XPEventSchema = z4.object({
1412
+ action_key: z4.string(),
1413
+ xp_base: z4.number(),
1414
+ multiplier: z4.number(),
1415
+ xp_earned: z4.number(),
1416
+ timestamp: z4.string(),
1417
+ context: z4.record(z4.unknown()).optional()
1418
+ });
1419
+ var XPResultSchema = z4.object({
1420
+ event: XPEventSchema,
1421
+ tier_change: z4.object({
1422
+ from: z4.string(),
1423
+ to: z4.string(),
1424
+ message: z4.string()
1425
+ }).nullable()
1426
+ });
1427
+ var XPActionSchema = z4.object({
1428
+ xp: z4.number(),
1429
+ trigger: z4.string(),
1430
+ condition: z4.string().optional(),
1431
+ daily_limit: z4.number().optional(),
1432
+ cooldown_days: z4.number().optional(),
1433
+ reciprocity_cap: z4.number().optional(),
1434
+ description: z4.string()
1435
+ });
1436
+ var XPActionRegistrySchema = z4.object({
1437
+ version: z4.number(),
1438
+ actions: z4.record(XPActionSchema)
1439
+ });
1440
+ var TIER_THRESHOLDS = [
1441
+ { name: "Seed", minXP: 0 },
1442
+ { name: "Cipher", minXP: 100 },
1443
+ { name: "Sage", minXP: 500 },
1444
+ { name: "Adept", minXP: 1200 },
1445
+ { name: "Visionary", minXP: 2500 },
1446
+ { name: "Oracle", minXP: 5e3 }
1447
+ ];
1448
+
1449
+ // src/engagement/format.ts
1450
+ function formatSessionStart(profile) {
1451
+ const tier = profile.tier.current;
1452
+ const xp = profile.xp.total;
1453
+ const nextTier = TIER_THRESHOLDS.find((t) => t.minXP > xp);
1454
+ const xpToNext = nextTier ? nextTier.minXP - xp : 0;
1455
+ const nextLabel = nextTier ? ` \u2192 ${nextTier.name} in ${xpToNext} XP` : " (max tier)";
1456
+ const multiplierLabel = profile.multipliers.effective > 1 ? ` [${profile.multipliers.effective}x ${profile.multipliers.active.map((m) => m.type).join(", ")}]` : "";
1457
+ const lines = [];
1458
+ lines.push(`Your Datacore: ${tier} (${xp.toLocaleString()} XP${nextLabel})${multiplierLabel}`);
1459
+ if (profile.ai_performance.feedback_count > 0) {
1460
+ const helpful = Math.round(profile.ai_performance.helpful_ratio * 100);
1461
+ lines.push(` AI surfaced ${profile.ai_performance.total_injections} insights this week (${helpful}% helpful)`);
1462
+ }
1463
+ const activeDays = profile.consistency.active_days_30;
1464
+ lines.push(` Active ${activeDays}/30 days`);
1465
+ return lines.join("\n");
1466
+ }
1467
+ function formatSessionEnd(profile, sessionXP, events) {
1468
+ if (sessionXP === 0) return "Session complete \u2014 no XP earned this session.";
1469
+ const multiplierLabel = profile.multipliers.effective > 1 ? ` (\xD7${profile.multipliers.effective} ${profile.multipliers.active.map((m) => m.type).join(", ")})` : "";
1470
+ const domainActions = events.filter((e) => e.context?.domain).map((e) => e.context.domain);
1471
+ const domainNote = domainActions.length > 0 ? ` | Your ${domainActions[0]} domain deepened` : "";
1472
+ const lines = [];
1473
+ lines.push(`Session: +${sessionXP} XP${multiplierLabel}${domainNote}`);
1474
+ const candidates = 0;
1475
+ if (candidates > 0) {
1476
+ lines.push(`Tomorrow: ${candidates} candidate(s) ready for review`);
1477
+ }
1478
+ return lines.join("\n");
1479
+ }
1480
+ function formatStatus(profile) {
1481
+ const lines = [];
1482
+ lines.push("## Engagement Dashboard");
1483
+ lines.push("");
1484
+ const nextTier = TIER_THRESHOLDS.find((t) => t.minXP > profile.xp.total);
1485
+ const progress = nextTier ? `${profile.xp.total}/${nextTier.minXP} XP (${Math.round(profile.xp.total / nextTier.minXP * 100)}%)` : `${profile.xp.total} XP (max tier)`;
1486
+ lines.push(`**Tier:** ${profile.tier.current} \u2014 ${progress}`);
1487
+ lines.push(`**This week:** ${profile.xp.this_week} XP`);
1488
+ if (profile.multipliers.active.length > 0) {
1489
+ const mults = profile.multipliers.active.map((m) => `${m.type} (${m.factor}x)`).join(", ");
1490
+ lines.push(`**Multipliers:** ${mults} = ${profile.multipliers.effective}x`);
1491
+ }
1492
+ lines.push(`**Consistency:** ${profile.consistency.active_days_30}/30 days active, best run: ${profile.consistency.best_run} days`);
1493
+ lines.push("");
1494
+ lines.push("**Stats:**");
1495
+ lines.push(`- Engrams created: ${profile.stats.total_engrams_created}`);
1496
+ lines.push(`- Feedback given: ${profile.stats.total_feedback_given}`);
1497
+ lines.push(`- Domains covered: ${profile.stats.domains_covered}`);
1498
+ lines.push(`- Packs exported: ${profile.stats.total_packs_exported}`);
1499
+ if (profile.challenges.active) {
1500
+ lines.push("");
1501
+ lines.push(`**Active Challenge:** ${profile.challenges.active.description}`);
1502
+ lines.push(` Expires: ${profile.challenges.active.expires_at}`);
1503
+ }
1504
+ if (profile.reconsolidation.pending.length > 0) {
1505
+ lines.push("");
1506
+ lines.push(`**Pending Contradictions:** ${profile.reconsolidation.pending.length}`);
1507
+ }
1508
+ if (profile.reputation.score > 0) {
1509
+ lines.push("");
1510
+ lines.push(`**Reputation:** ${profile.reputation.score.toFixed(2)}`);
1511
+ }
1512
+ return lines.join("\n");
1513
+ }
1514
+ function formatReconsolidation(recon) {
1515
+ const lines = [];
1516
+ lines.push("**Contradiction Detected:**");
1517
+ lines.push(` Existing: "${recon.statement}"`);
1518
+ lines.push(` New: "${recon.contradiction}"`);
1519
+ lines.push(` Evidence: ${recon.evidence_strength}`);
1520
+ lines.push("");
1521
+ lines.push(" Actions: [Defend] [Revise] [Retire] [Dismiss]");
1522
+ lines.push(` \u2192 Use datacore.resolve with type="reconsolidation", id="${recon.engram_id}"`);
1523
+ return lines.join("\n");
1524
+ }
1525
+ function formatDiscovery(disc) {
1526
+ const lines = [];
1527
+ lines.push("**Cross-Domain Discovery:**");
1528
+ lines.push(` "${disc.engram_a.statement}" (${disc.engram_a.domain})`);
1529
+ lines.push(` \u2194 "${disc.engram_b.statement}" (${disc.engram_b.domain})`);
1530
+ lines.push(` Connection: ${disc.connection}`);
1531
+ lines.push("");
1532
+ lines.push(" Actions: [Explore +20 XP] [Note]");
1533
+ lines.push(` \u2192 Use datacore.resolve with type="discovery", id="${disc.id}"`);
1534
+ return lines.join("\n");
1535
+ }
1536
+ function formatChallenge(challenge) {
1537
+ const lines = [];
1538
+ lines.push(`**Weekly Challenge:** ${challenge.description}`);
1539
+ lines.push(` Bonus: +${challenge.bonus_xp} XP | Expires: ${challenge.expires_at}`);
1540
+ lines.push(` \u2192 Dismiss: datacore.resolve with type="challenge", id="${challenge.id}", action="dismiss"`);
1541
+ return lines.join("\n");
1542
+ }
1543
+
1216
1544
  // registry/packs.json
1217
1545
  var packs_default = {
1218
1546
  version: 1,
@@ -1251,13 +1579,13 @@ var packs_default = {
1251
1579
  download_url: "",
1252
1580
  engram_count: 747,
1253
1581
  free: true,
1254
- checksum: "08185777c38e3a91fbb0f92e7ef388c5940e97e69be6dcce8ce119bd2b33968c"
1582
+ checksum: "baa1010298515c1906e892f6f8c2198e5fdf78383dceb57373bc8d53c4c31921"
1255
1583
  }
1256
1584
  ]
1257
1585
  };
1258
1586
 
1259
1587
  // src/tools/status.ts
1260
- async function handleStatus(paths, updateAvailable2) {
1588
+ async function handleStatus(paths, updateAvailable2, engagementService2) {
1261
1589
  const engrams = loadEngrams(paths.engramsPath);
1262
1590
  const journalCount = countFiles(paths.journalPath, ".md");
1263
1591
  const knowledgeCount = countFiles(paths.knowledgePath, ".md");
@@ -1296,6 +1624,22 @@ async function handleStatus(paths, updateAvailable2) {
1296
1624
  if (updateAvailable2) {
1297
1625
  recommendations.push(`Update available: ${updateAvailable2}. Run: npm update -g @datacore-one/mcp`);
1298
1626
  }
1627
+ let engagement = void 0;
1628
+ if (engagementService2?.isEnabled()) {
1629
+ try {
1630
+ await engagementService2.init();
1631
+ const profile = engagementService2.getProfile();
1632
+ if (profile) {
1633
+ engagement = {
1634
+ display: formatStatus(profile),
1635
+ tier: profile.tier.current,
1636
+ xp: profile.xp.total,
1637
+ reputation: profile.reputation.score
1638
+ };
1639
+ }
1640
+ } catch {
1641
+ }
1642
+ }
1299
1643
  const statusResult = {
1300
1644
  version: currentVersion,
1301
1645
  mode: paths.mode,
@@ -1305,6 +1649,7 @@ async function handleStatus(paths, updateAvailable2) {
1305
1649
  pack_integrity: packIntegrity.length > 0 ? packIntegrity : void 0,
1306
1650
  journal_entries: journalCount,
1307
1651
  knowledge_notes: knowledgeCount,
1652
+ engagement,
1308
1653
  _recommendations: recommendations.length > 0 ? recommendations : void 0,
1309
1654
  _hints: buildHints({
1310
1655
  next: recommendations.length > 0 ? recommendations[0] : "System healthy. Use datacore.session.start to begin working.",
@@ -1495,7 +1840,7 @@ function resolvePackId(packId, packsDir) {
1495
1840
  import * as fs12 from "fs";
1496
1841
  import * as path10 from "path";
1497
1842
  import * as yaml5 from "js-yaml";
1498
- async function handleExport(args2, paths) {
1843
+ async function handleExport(args2, paths, service) {
1499
1844
  const allEngrams = loadEngrams(paths.engramsPath);
1500
1845
  let selected = allEngrams.filter((e) => e.status === "active");
1501
1846
  selected = selected.filter((e) => e.visibility === "public" || e.visibility === "template");
@@ -1587,6 +1932,12 @@ Exported ${selected.length} engrams.
1587
1932
  path10.join(packDir, "engrams.yaml"),
1588
1933
  yaml5.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
1589
1934
  );
1935
+ if (service?.isEnabled() && selected.length >= 5) {
1936
+ try {
1937
+ await service.award("pack_exported", { engram_count: selected.length, avg_fitness: 0.7 });
1938
+ } catch {
1939
+ }
1940
+ }
1590
1941
  return { success: true, pack_path: packDir };
1591
1942
  }
1592
1943
 
@@ -1828,7 +2179,7 @@ async function checkModule(mod, storage2) {
1828
2179
  }
1829
2180
 
1830
2181
  // src/tools/forget.ts
1831
- async function handleForget(args2, engramsPath) {
2182
+ async function handleForget(args2, engramsPath, service) {
1832
2183
  const engrams = loadEngrams(engramsPath);
1833
2184
  if (args2.id) {
1834
2185
  const idx = engrams.findIndex((e) => e.id === args2.id);
@@ -1841,6 +2192,14 @@ async function handleForget(args2, engramsPath) {
1841
2192
  }
1842
2193
  engrams[idx] = { ...engram, status: "retired" };
1843
2194
  saveEngrams(engramsPath, engrams);
2195
+ if (service?.isEnabled()) {
2196
+ try {
2197
+ const created = engram.activation.last_accessed;
2198
+ const ageDays = Math.floor((Date.now() - new Date(created).getTime()) / 864e5);
2199
+ await service.award("engram_retired", { engram_age_days: ageDays });
2200
+ } catch {
2201
+ }
2202
+ }
1844
2203
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
1845
2204
  }
1846
2205
  if (args2.search) {
@@ -1857,6 +2216,14 @@ async function handleForget(args2, engramsPath) {
1857
2216
  const idx = engrams.findIndex((e) => e.id === engram.id);
1858
2217
  engrams[idx] = { ...engram, status: "retired" };
1859
2218
  saveEngrams(engramsPath, engrams);
2219
+ if (service?.isEnabled()) {
2220
+ try {
2221
+ const created = engram.activation.last_accessed;
2222
+ const ageDays = Math.floor((Date.now() - new Date(created).getTime()) / 864e5);
2223
+ await service.award("engram_retired", { engram_age_days: ageDays });
2224
+ } catch {
2225
+ }
2226
+ }
1860
2227
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
1861
2228
  }
1862
2229
  const truncated = allMatches.length > 100;
@@ -1887,14 +2254,14 @@ function findEngram(engramId, engramsPath, packsPath) {
1887
2254
  }
1888
2255
  return null;
1889
2256
  }
1890
- async function handleFeedback(args2, engramsPath, packsPath) {
2257
+ async function handleFeedback(args2, engramsPath, packsPath, service) {
1891
2258
  const pPath = packsPath ?? path13.join(path13.dirname(engramsPath), "packs");
1892
2259
  if (args2.signals && args2.signals.length > 0) {
1893
- return handleBatchFeedback(args2.signals, engramsPath, pPath);
2260
+ return handleBatchFeedback(args2.signals, engramsPath, pPath, service);
1894
2261
  }
1895
- return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath, pPath);
2262
+ return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath, pPath, service);
1896
2263
  }
1897
- async function handleSingleFeedback(engram_id, signal, comment, engramsPath, packsPath) {
2264
+ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, packsPath, service) {
1898
2265
  const found = findEngram(engram_id, engramsPath, packsPath);
1899
2266
  if (!found) {
1900
2267
  return {
@@ -1920,6 +2287,12 @@ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, pac
1920
2287
  } else if (found.source === "pack" && found.packEngrams && found.packEngramsPath) {
1921
2288
  atomicWriteYaml(found.packEngramsPath, { engrams: found.packEngrams });
1922
2289
  }
2290
+ if (service?.isEnabled()) {
2291
+ try {
2292
+ await service.award("feedback_given", { signal });
2293
+ } catch {
2294
+ }
2295
+ }
1923
2296
  return {
1924
2297
  mode: "single",
1925
2298
  success: true,
@@ -1929,7 +2302,7 @@ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, pac
1929
2302
  feedback_signals: { ...found.engram.feedback_signals }
1930
2303
  };
1931
2304
  }
1932
- async function handleBatchFeedback(signals, engramsPath, packsPath) {
2305
+ async function handleBatchFeedback(signals, engramsPath, packsPath, service) {
1933
2306
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1934
2307
  const results = [];
1935
2308
  const summary = { positive: 0, negative: 0, neutral: 0 };
@@ -1977,6 +2350,15 @@ async function handleBatchFeedback(signals, engramsPath, packsPath) {
1977
2350
  for (const [filePath, engrams] of dirtyPackFiles) {
1978
2351
  atomicWriteYaml(filePath, { engrams });
1979
2352
  }
2353
+ if (service?.isEnabled()) {
2354
+ try {
2355
+ const successCount = results.filter((r) => r.success).length;
2356
+ for (let i = 0; i < successCount; i++) {
2357
+ await service.award("feedback_given", { batch: true });
2358
+ }
2359
+ } catch {
2360
+ }
2361
+ }
1980
2362
  return {
1981
2363
  mode: "batch",
1982
2364
  results,
@@ -1989,9 +2371,856 @@ async function handleBatchFeedback(signals, engramsPath, packsPath) {
1989
2371
  }
1990
2372
 
1991
2373
  // src/tools/session-start.ts
2374
+ import * as fs18 from "fs";
2375
+ import * as path17 from "path";
2376
+
2377
+ // src/engagement/service.ts
2378
+ import * as fs17 from "fs";
2379
+ import * as path16 from "path";
2380
+
2381
+ // src/engagement/profile.ts
1992
2382
  import * as fs15 from "fs";
1993
2383
  import * as path14 from "path";
1994
- async function handleSessionStart(args2, storage2, bridge) {
2384
+ import * as yaml7 from "js-yaml";
2385
+ var PROFILE_DIR = "engagement";
2386
+ var PROFILE_FILE = "profile.yaml";
2387
+ function engagementDir(basePath) {
2388
+ return path14.join(basePath, ".datacore", PROFILE_DIR);
2389
+ }
2390
+ function profilePath(basePath) {
2391
+ return path14.join(engagementDir(basePath), PROFILE_FILE);
2392
+ }
2393
+ function createDefaultProfile() {
2394
+ return EngagementProfileSchema.parse({ version: 4 });
2395
+ }
2396
+ function loadProfile(basePath) {
2397
+ const filePath = profilePath(basePath);
2398
+ if (!fs15.existsSync(filePath)) {
2399
+ return createDefaultProfile();
2400
+ }
2401
+ try {
2402
+ const raw = yaml7.load(fs15.readFileSync(filePath, "utf8"));
2403
+ return EngagementProfileSchema.parse(raw);
2404
+ } catch (err) {
2405
+ logger.warning(`Engagement profile corrupted, backing up and creating fresh: ${err}`);
2406
+ try {
2407
+ fs15.copyFileSync(filePath, filePath + ".bak");
2408
+ } catch {
2409
+ }
2410
+ return createDefaultProfile();
2411
+ }
2412
+ }
2413
+ function saveProfile(basePath, profile) {
2414
+ const dir = engagementDir(basePath);
2415
+ if (!fs15.existsSync(dir)) {
2416
+ fs15.mkdirSync(dir, { recursive: true });
2417
+ }
2418
+ const filePath = profilePath(basePath);
2419
+ const content = yaml7.dump(profile, { lineWidth: 120, noRefs: true, quotingType: '"' });
2420
+ const tmpPath = filePath + ".tmp." + process.pid;
2421
+ fs15.writeFileSync(tmpPath, content);
2422
+ fs15.renameSync(tmpPath, filePath);
2423
+ }
2424
+ function ensureEngagementDir(basePath) {
2425
+ const dir = engagementDir(basePath);
2426
+ if (!fs15.existsSync(dir)) {
2427
+ fs15.mkdirSync(dir, { recursive: true });
2428
+ }
2429
+ const gitignorePath = path14.join(basePath, ".datacore", ".gitignore");
2430
+ if (fs15.existsSync(gitignorePath)) {
2431
+ const content = fs15.readFileSync(gitignorePath, "utf8");
2432
+ if (!content.includes("engagement/profile.yaml")) {
2433
+ fs15.appendFileSync(gitignorePath, "\nengagement/profile.yaml\nengagement/badge.svg\n");
2434
+ }
2435
+ }
2436
+ }
2437
+
2438
+ // src/engagement/actions.ts
2439
+ import * as fs16 from "fs";
2440
+ import * as path15 from "path";
2441
+ import * as yaml8 from "js-yaml";
2442
+ var BUNDLED_ACTIONS = {
2443
+ version: 1,
2444
+ actions: {
2445
+ engram_created: {
2446
+ xp: 10,
2447
+ trigger: "datacore.learn",
2448
+ condition: "status === active",
2449
+ description: "Create a quality engram"
2450
+ },
2451
+ engram_created_public: {
2452
+ xp: 20,
2453
+ trigger: "datacore.learn",
2454
+ condition: "visibility === public || visibility === template",
2455
+ description: "Create a public/template engram"
2456
+ },
2457
+ feedback_given: {
2458
+ xp: 5,
2459
+ trigger: "datacore.feedback",
2460
+ daily_limit: 10,
2461
+ description: "Give feedback on an injected engram"
2462
+ },
2463
+ engram_promoted: {
2464
+ xp: 3,
2465
+ trigger: "datacore.promote",
2466
+ description: "Promote a candidate engram to active"
2467
+ },
2468
+ engram_retired: {
2469
+ xp: 5,
2470
+ trigger: "datacore.forget",
2471
+ cooldown_days: 7,
2472
+ description: "Retire an engram after reflection (7-day cooldown)"
2473
+ },
2474
+ pack_exported: {
2475
+ xp: 25,
2476
+ trigger: "datacore.packs.export",
2477
+ condition: "engram_count >= 5 && avg_fitness >= 0.6",
2478
+ description: "Export a quality pack (5+ engrams, 0.6+ fitness)"
2479
+ },
2480
+ new_domain: {
2481
+ xp: 15,
2482
+ trigger: "datacore.learn",
2483
+ description: "Create first engram in a new domain"
2484
+ },
2485
+ reconsolidation_defend: {
2486
+ xp: 12,
2487
+ trigger: "datacore.resolve",
2488
+ description: "Defend an engram during contradiction challenge"
2489
+ },
2490
+ reconsolidation_revise: {
2491
+ xp: 10,
2492
+ trigger: "datacore.resolve",
2493
+ description: "Revise an engram during contradiction challenge"
2494
+ },
2495
+ reconsolidation_retire: {
2496
+ xp: 8,
2497
+ trigger: "datacore.resolve",
2498
+ description: "Retire an engram during contradiction challenge"
2499
+ },
2500
+ discovery_explore: {
2501
+ xp: 20,
2502
+ trigger: "datacore.resolve",
2503
+ description: "Explore a cross-domain discovery"
2504
+ },
2505
+ reconsolidation_expired: {
2506
+ xp: 3,
2507
+ trigger: "system",
2508
+ description: "Auto-expire an overdue reconsolidation"
2509
+ }
2510
+ }
2511
+ };
2512
+ function loadActions(basePath) {
2513
+ const actionsPath = path15.join(basePath, ".datacore", "engagement", "xp-actions.yaml");
2514
+ if (!fs16.existsSync(actionsPath)) {
2515
+ return BUNDLED_ACTIONS;
2516
+ }
2517
+ try {
2518
+ const raw = yaml8.load(fs16.readFileSync(actionsPath, "utf8"));
2519
+ return XPActionRegistrySchema.parse(raw);
2520
+ } catch (err) {
2521
+ logger.warning(`Malformed xp-actions.yaml, using defaults: ${err}`);
2522
+ return BUNDLED_ACTIONS;
2523
+ }
2524
+ }
2525
+ function writeDefaultActions(basePath) {
2526
+ const dir = path15.join(basePath, ".datacore", "engagement");
2527
+ const actionsPath = path15.join(dir, "xp-actions.yaml");
2528
+ if (fs16.existsSync(actionsPath)) return;
2529
+ if (!fs16.existsSync(dir)) {
2530
+ fs16.mkdirSync(dir, { recursive: true });
2531
+ }
2532
+ const content = yaml8.dump(BUNDLED_ACTIONS, { lineWidth: 120, noRefs: true, quotingType: '"' });
2533
+ fs16.writeFileSync(actionsPath, content);
2534
+ }
2535
+
2536
+ // src/engagement/engine.ts
2537
+ function resolveTier(profile) {
2538
+ const xp = profile.xp.total;
2539
+ let current = "Seed";
2540
+ for (const t of TIER_THRESHOLDS) {
2541
+ if (xp >= t.minXP) current = t.name;
2542
+ }
2543
+ const changed = current !== profile.tier.current;
2544
+ const message = changed ? `You've reached ${current}!` : void 0;
2545
+ return { current, changed, message };
2546
+ }
2547
+ function getEffectiveMultiplier(profile) {
2548
+ const actives = profile.multipliers.active;
2549
+ if (actives.length === 0) return { effective: 1, active: [] };
2550
+ let effective = 1;
2551
+ for (const m of actives) {
2552
+ effective *= m.factor;
2553
+ }
2554
+ return { effective, active: actives };
2555
+ }
2556
+ function isActionEligible(profile, actionKey, actions, context) {
2557
+ const action = actions.actions[actionKey];
2558
+ if (!action) return { eligible: false, reason: `Unknown action: ${actionKey}` };
2559
+ if (action.daily_limit !== void 0) {
2560
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2561
+ const todayEntry = profile.xp.history.find((h) => h.date === today);
2562
+ if (todayEntry) {
2563
+ const todayCount = todayEntry.actions.filter((a) => a === actionKey).length;
2564
+ if (todayCount >= action.daily_limit) {
2565
+ return { eligible: false, reason: `Daily limit of ${action.daily_limit} reached for ${actionKey}` };
2566
+ }
2567
+ }
2568
+ }
2569
+ if (action.cooldown_days !== void 0 && context?.engram_age_days !== void 0) {
2570
+ const ageDays = context.engram_age_days;
2571
+ if (ageDays < action.cooldown_days) {
2572
+ return { eligible: false, reason: `Cooldown: engram must be at least ${action.cooldown_days} days old (current: ${ageDays})` };
2573
+ }
2574
+ }
2575
+ return { eligible: true };
2576
+ }
2577
+ function awardXP(profile, actionKey, actions, context) {
2578
+ const eligibility = isActionEligible(profile, actionKey, actions, context);
2579
+ if (!eligibility.eligible) return null;
2580
+ const action = actions.actions[actionKey];
2581
+ const { effective } = getEffectiveMultiplier(profile);
2582
+ const baseXP = action.xp;
2583
+ const earnedXP = Math.round(baseXP * effective);
2584
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2585
+ const today = now.split("T")[0];
2586
+ const event = {
2587
+ action_key: actionKey,
2588
+ xp_base: baseXP,
2589
+ multiplier: effective,
2590
+ xp_earned: earnedXP,
2591
+ timestamp: now,
2592
+ context
2593
+ };
2594
+ const updated = JSON.parse(JSON.stringify(profile));
2595
+ updated.xp.total += earnedXP;
2596
+ updated.xp.this_week += earnedXP;
2597
+ let todayEntry = updated.xp.history.find((h) => h.date === today);
2598
+ if (!todayEntry) {
2599
+ todayEntry = { date: today, earned: 0, base_earned: 0, multiplier: effective, actions: [] };
2600
+ updated.xp.history.push(todayEntry);
2601
+ }
2602
+ todayEntry.earned += earnedXP;
2603
+ todayEntry.base_earned += baseXP;
2604
+ todayEntry.actions.push(actionKey);
2605
+ if (actionKey === "engram_created" || actionKey === "engram_created_public") {
2606
+ updated.stats.total_engrams_created++;
2607
+ }
2608
+ if (actionKey === "feedback_given") {
2609
+ updated.stats.total_feedback_given++;
2610
+ }
2611
+ if (actionKey === "engram_retired") {
2612
+ updated.stats.total_engrams_retired++;
2613
+ }
2614
+ if (actionKey === "pack_exported") {
2615
+ updated.stats.total_packs_exported++;
2616
+ }
2617
+ if (actionKey === "new_domain") {
2618
+ updated.stats.domains_covered++;
2619
+ }
2620
+ if (!updated.stats.first_activity) {
2621
+ updated.stats.first_activity = today;
2622
+ }
2623
+ return { event, profile: updated };
2624
+ }
2625
+ function updateConsistency(profile) {
2626
+ const updated = JSON.parse(JSON.stringify(profile));
2627
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2628
+ if (updated.consistency.last_active === today) return updated;
2629
+ const thirtyDaysAgo = /* @__PURE__ */ new Date();
2630
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
2631
+ const cutoff = thirtyDaysAgo.toISOString().split("T")[0];
2632
+ const activeDays = updated.xp.history.filter((h) => h.date >= cutoff && h.date <= today).length;
2633
+ updated.consistency.active_days_30 = activeDays;
2634
+ updated.consistency.last_active = today;
2635
+ const sortedDates = updated.xp.history.map((h) => h.date).sort();
2636
+ let currentRun = 1;
2637
+ let bestRun = updated.consistency.best_run;
2638
+ for (let i = 1; i < sortedDates.length; i++) {
2639
+ const prev = new Date(sortedDates[i - 1]);
2640
+ const curr = new Date(sortedDates[i]);
2641
+ const diffDays = Math.round((curr.getTime() - prev.getTime()) / 864e5);
2642
+ if (diffDays === 1) {
2643
+ currentRun++;
2644
+ if (currentRun > bestRun) bestRun = currentRun;
2645
+ } else if (diffDays > 1) {
2646
+ currentRun = 1;
2647
+ }
2648
+ }
2649
+ updated.consistency.best_run = bestRun;
2650
+ return updated;
2651
+ }
2652
+
2653
+ // src/engagement/multipliers.ts
2654
+ function evaluateMultipliers(profile) {
2655
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2656
+ const multipliers = [];
2657
+ if (profile.identity.erc8004_registered) {
2658
+ multipliers.push({ type: "verified", factor: 1.5, since: today });
2659
+ }
2660
+ if (profile.stats.total_packs_exported >= 3 && profile.stats.total_feedback_received >= 20 && profile.stats.feedback_positive_ratio >= 0.85) {
2661
+ multipliers.push({ type: "top_teacher", factor: 1.25, since: today });
2662
+ }
2663
+ if (profile.reconsolidation.total_resolved >= 5 && profile.discoveries.total >= 3 && profile.reconsolidation.response_rate >= 0.8 && profile.discoveries.explore_rate >= 0.5) {
2664
+ multipliers.push({ type: "top_learner", factor: 1.25, since: today });
2665
+ }
2666
+ return multipliers;
2667
+ }
2668
+ function recalculateWeekly(profile) {
2669
+ const updated = JSON.parse(JSON.stringify(profile));
2670
+ const newMultipliers = evaluateMultipliers(updated);
2671
+ updated.multipliers.active = newMultipliers;
2672
+ let effective = 1;
2673
+ for (const m of newMultipliers) {
2674
+ effective *= m.factor;
2675
+ }
2676
+ updated.multipliers.effective = effective;
2677
+ const now = /* @__PURE__ */ new Date();
2678
+ const day = now.getDay();
2679
+ const diff = day === 0 ? 6 : day - 1;
2680
+ const weekStart = new Date(now);
2681
+ weekStart.setDate(weekStart.getDate() - diff);
2682
+ weekStart.setHours(0, 0, 0, 0);
2683
+ const weekStartStr = weekStart.toISOString().split("T")[0];
2684
+ updated.xp.this_week = updated.xp.history.filter((h) => h.date >= weekStartStr).reduce((sum, h) => sum + h.earned, 0);
2685
+ return updated;
2686
+ }
2687
+
2688
+ // src/engagement/reputation.ts
2689
+ function calculateReputation(profile) {
2690
+ const feedbackCount = profile.stats.total_feedback_given;
2691
+ const feedbackQuality = feedbackCount > 0 ? Math.min(1, profile.stats.feedback_positive_ratio * (Math.log(feedbackCount) / Math.log(100))) : 0;
2692
+ const verificationBonus = profile.identity.erc8004_registered ? 1 : 0;
2693
+ const stakeSignal = 0;
2694
+ const totalResolved = profile.reconsolidation.total_resolved;
2695
+ const curationHonesty = totalResolved >= 5 ? (profile.reconsolidation.outcomes.revised + profile.reconsolidation.outcomes.retired) / totalResolved : 0;
2696
+ let tenureDays = 0;
2697
+ if (profile.stats.first_activity) {
2698
+ const first = new Date(profile.stats.first_activity);
2699
+ tenureDays = Math.max(0, Math.floor((Date.now() - first.getTime()) / 864e5));
2700
+ }
2701
+ const tenureSignal = tenureDays > 0 ? Math.min(1, Math.log(tenureDays) / Math.log(365)) : 0;
2702
+ const domainBreadth = Math.min(1, profile.stats.domains_covered / 10);
2703
+ const conflictPenalty = 0;
2704
+ const score = 0.3 * feedbackQuality + 0.25 * verificationBonus + 0.15 * stakeSignal + 0.15 * curationHonesty + 0.1 * tenureSignal + 0.05 * domainBreadth - 0.5 * conflictPenalty;
2705
+ return Math.max(0, score);
2706
+ }
2707
+ function updateReputation(profile) {
2708
+ const updated = JSON.parse(JSON.stringify(profile));
2709
+ const score = calculateReputation(updated);
2710
+ updated.reputation.score = Math.round(score * 100) / 100;
2711
+ updated.reputation.last_calculated = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2712
+ return updated;
2713
+ }
2714
+
2715
+ // src/engagement/migrate.ts
2716
+ function calculateRetroactiveXP(engrams) {
2717
+ const quality = engrams.filter((e) => e.status === "active");
2718
+ const qualityCount = quality.length;
2719
+ const publicCount = quality.filter((e) => e.visibility === "public" || e.visibility === "template").length;
2720
+ let totalPositiveFeedback = 0;
2721
+ let totalFeedbackGiven = 0;
2722
+ for (const e of engrams) {
2723
+ totalPositiveFeedback += e.feedback_signals?.positive ?? 0;
2724
+ totalFeedbackGiven += (e.feedback_signals?.positive ?? 0) + (e.feedback_signals?.negative ?? 0) + (e.feedback_signals?.neutral ?? 0);
2725
+ }
2726
+ const domains = new Set(engrams.filter((e) => e.domain).map((e) => e.domain));
2727
+ const domainCount = domains.size;
2728
+ const packsExported = 0;
2729
+ return qualityCount * 10 + publicCount * 10 + totalPositiveFeedback * 5 + totalFeedbackGiven * 5 + domainCount * 20 + packsExported * 25;
2730
+ }
2731
+ function migrateProfile(basePath, engrams) {
2732
+ ensureEngagementDir(basePath);
2733
+ const profile = createDefaultProfile();
2734
+ const retroXP = calculateRetroactiveXP(engrams);
2735
+ profile.xp.total = retroXP;
2736
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2737
+ const tierResult = resolveTier(profile);
2738
+ profile.tier.current = tierResult.current;
2739
+ if (tierResult.current !== "Seed") {
2740
+ profile.tier.achieved_at = today;
2741
+ profile.tier.history.push({ tier: tierResult.current, date: today });
2742
+ }
2743
+ const active = engrams.filter((e) => e.status === "active");
2744
+ profile.stats.total_engrams_created = active.length;
2745
+ profile.stats.domains_covered = new Set(engrams.filter((e) => e.domain).map((e) => e.domain)).size;
2746
+ profile.stats.public_engrams = active.filter((e) => e.visibility === "public" || e.visibility === "template").length;
2747
+ profile.stats.first_activity = today;
2748
+ writeDefaultActions(basePath);
2749
+ saveProfile(basePath, profile);
2750
+ logger.info(`Engagement migration: ${retroXP} retroactive XP \u2192 ${profile.tier.current} tier`);
2751
+ return profile;
2752
+ }
2753
+
2754
+ // src/engagement/service.ts
2755
+ var EngagementService = class {
2756
+ constructor(basePath, config) {
2757
+ this.basePath = basePath;
2758
+ this.config = config;
2759
+ }
2760
+ profile = null;
2761
+ sessionEvents = [];
2762
+ dirty = false;
2763
+ sessionActive = false;
2764
+ actions = null;
2765
+ initialized = false;
2766
+ isEnabled() {
2767
+ return this.config.enabled;
2768
+ }
2769
+ async init() {
2770
+ if (!this.isEnabled() || this.initialized) return;
2771
+ ensureEngagementDir(this.basePath);
2772
+ const profilePath2 = path16.join(this.basePath, ".datacore", "engagement", "profile.yaml");
2773
+ const engramsPath = path16.join(this.basePath, ".datacore", "learning", "engrams.yaml");
2774
+ const coreEngramsPath = path16.join(this.basePath, "engrams.yaml");
2775
+ if (!fs17.existsSync(profilePath2)) {
2776
+ const actualEngramsPath = fs17.existsSync(engramsPath) ? engramsPath : coreEngramsPath;
2777
+ if (fs17.existsSync(actualEngramsPath)) {
2778
+ const engrams = loadEngrams(actualEngramsPath);
2779
+ if (engrams.length > 0) {
2780
+ this.profile = migrateProfile(this.basePath, engrams);
2781
+ } else {
2782
+ this.profile = loadProfile(this.basePath);
2783
+ }
2784
+ } else {
2785
+ this.profile = loadProfile(this.basePath);
2786
+ }
2787
+ } else {
2788
+ this.profile = loadProfile(this.basePath);
2789
+ }
2790
+ this.actions = loadActions(this.basePath);
2791
+ this.initialized = true;
2792
+ }
2793
+ async award(actionKey, context) {
2794
+ if (!this.isEnabled()) return null;
2795
+ if (!this.initialized) await this.init();
2796
+ if (!this.profile || !this.actions) return null;
2797
+ const result = awardXP(this.profile, actionKey, this.actions, context);
2798
+ if (!result) return null;
2799
+ this.profile = result.profile;
2800
+ this.sessionEvents.push(result.event);
2801
+ this.dirty = true;
2802
+ const tierResult = resolveTier(this.profile);
2803
+ let tierChange = null;
2804
+ if (tierResult.changed) {
2805
+ this.profile.tier.current = tierResult.current;
2806
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2807
+ this.profile.tier.achieved_at = today;
2808
+ this.profile.tier.history.push({ tier: tierResult.current, date: today });
2809
+ tierChange = {
2810
+ from: this.profile.tier.history.length > 1 ? this.profile.tier.history[this.profile.tier.history.length - 2].tier : "Seed",
2811
+ to: tierResult.current,
2812
+ message: tierResult.message
2813
+ };
2814
+ }
2815
+ if (!this.sessionActive) {
2816
+ await this.flush();
2817
+ }
2818
+ return { event: result.event, tier_change: tierChange };
2819
+ }
2820
+ async flush() {
2821
+ if (!this.dirty || !this.profile) return;
2822
+ this.profile = updateConsistency(this.profile);
2823
+ this.profile = recalculateWeekly(this.profile);
2824
+ this.profile = updateReputation(this.profile);
2825
+ saveProfile(this.basePath, this.profile);
2826
+ this.dirty = false;
2827
+ }
2828
+ getSessionSummary() {
2829
+ const actions = {};
2830
+ let totalXP = 0;
2831
+ let baseXP = 0;
2832
+ for (const event of this.sessionEvents) {
2833
+ totalXP += event.xp_earned;
2834
+ baseXP += event.xp_base;
2835
+ actions[event.action_key] = (actions[event.action_key] ?? 0) + 1;
2836
+ }
2837
+ return {
2838
+ total_xp: totalXP,
2839
+ base_xp: baseXP,
2840
+ multiplier: this.profile?.multipliers.effective ?? 1,
2841
+ events: [...this.sessionEvents],
2842
+ actions
2843
+ };
2844
+ }
2845
+ getProfile() {
2846
+ if (!this.isEnabled()) return null;
2847
+ return this.profile;
2848
+ }
2849
+ markSessionActive() {
2850
+ this.sessionActive = true;
2851
+ this.sessionEvents = [];
2852
+ }
2853
+ markSessionEnded() {
2854
+ this.sessionActive = false;
2855
+ }
2856
+ /**
2857
+ * Apply a profile transformation (e.g., from resolve, expire, challenge generation).
2858
+ * Marks profile dirty so next flush persists changes.
2859
+ */
2860
+ applyProfileUpdate(updater) {
2861
+ if (!this.profile) return;
2862
+ this.profile = updater(this.profile);
2863
+ this.dirty = true;
2864
+ }
2865
+ };
2866
+
2867
+ // src/engagement/reconsolidation.ts
2868
+ function resolveReconsolidation(profile, engramId, outcome) {
2869
+ const updated = JSON.parse(JSON.stringify(profile));
2870
+ const idx = updated.reconsolidation.pending.findIndex(
2871
+ (p) => p.engram_id === engramId || p.contradicting_id === engramId
2872
+ );
2873
+ if (idx === -1) return updated;
2874
+ updated.reconsolidation.pending.splice(idx, 1);
2875
+ updated.reconsolidation.total_resolved++;
2876
+ if (outcome === "defend") updated.reconsolidation.outcomes.defended++;
2877
+ else if (outcome === "revise") updated.reconsolidation.outcomes.revised++;
2878
+ else if (outcome === "retire") updated.reconsolidation.outcomes.retired++;
2879
+ else if (outcome === "dismiss") updated.reconsolidation.outcomes.dismissed++;
2880
+ const totalOutcomes = updated.reconsolidation.outcomes.defended + updated.reconsolidation.outcomes.revised + updated.reconsolidation.outcomes.retired;
2881
+ const totalAll = totalOutcomes + updated.reconsolidation.outcomes.dismissed;
2882
+ updated.reconsolidation.response_rate = totalAll > 0 ? totalOutcomes / totalAll : 0;
2883
+ return updated;
2884
+ }
2885
+ function expireReconsolidations(profile) {
2886
+ const updated = JSON.parse(JSON.stringify(profile));
2887
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2888
+ const expired = [];
2889
+ const remaining = [];
2890
+ for (const pending of updated.reconsolidation.pending) {
2891
+ if (pending.expires_at <= now) {
2892
+ expired.push(pending);
2893
+ } else {
2894
+ remaining.push(pending);
2895
+ }
2896
+ }
2897
+ if (expired.length === 0) return updated;
2898
+ updated.reconsolidation.pending = remaining;
2899
+ for (const _entry of expired) {
2900
+ updated.xp.total += 3;
2901
+ updated.reconsolidation.total_resolved++;
2902
+ updated.reconsolidation.outcomes.retired++;
2903
+ }
2904
+ const totalOutcomes = updated.reconsolidation.outcomes.defended + updated.reconsolidation.outcomes.revised + updated.reconsolidation.outcomes.retired;
2905
+ const totalAll = totalOutcomes + updated.reconsolidation.outcomes.dismissed;
2906
+ updated.reconsolidation.response_rate = totalAll > 0 ? totalOutcomes / totalAll : 0;
2907
+ return updated;
2908
+ }
2909
+
2910
+ // src/engagement/discovery.ts
2911
+ var MIN_ENGRAMS = 20;
2912
+ var MIN_DOMAINS = 3;
2913
+ var MIN_DAYS_BETWEEN_DISCOVERIES = 2;
2914
+ function extractKeywords(statement) {
2915
+ const stopwords = /* @__PURE__ */ new Set([
2916
+ "a",
2917
+ "an",
2918
+ "the",
2919
+ "is",
2920
+ "are",
2921
+ "was",
2922
+ "were",
2923
+ "be",
2924
+ "been",
2925
+ "being",
2926
+ "have",
2927
+ "has",
2928
+ "had",
2929
+ "do",
2930
+ "does",
2931
+ "did",
2932
+ "it",
2933
+ "its",
2934
+ "in",
2935
+ "on",
2936
+ "at",
2937
+ "to",
2938
+ "for",
2939
+ "of",
2940
+ "by",
2941
+ "with"
2942
+ ]);
2943
+ const tokens = statement.toLowerCase().split(/\s+|[.,;:!?()[\]{}]/).filter((t) => t.length > 0 && !stopwords.has(t));
2944
+ return new Set(tokens);
2945
+ }
2946
+ function daysBetween(dateA, dateB) {
2947
+ const a = new Date(dateA);
2948
+ const b = new Date(dateB);
2949
+ return Math.abs(a.getTime() - b.getTime()) / (1e3 * 60 * 60 * 24);
2950
+ }
2951
+ function generateDiscoveryCandidates(engrams, profile) {
2952
+ if (engrams.length < MIN_ENGRAMS) return [];
2953
+ const domains = new Set(engrams.map((e) => e.domain).filter(Boolean));
2954
+ if (domains.size < MIN_DOMAINS) return [];
2955
+ if (profile.discoveries.last_offered) {
2956
+ const daysSinceLast = daysBetween(
2957
+ profile.discoveries.last_offered,
2958
+ (/* @__PURE__ */ new Date()).toISOString()
2959
+ );
2960
+ if (daysSinceLast < MIN_DAYS_BETWEEN_DISCOVERIES) return [];
2961
+ }
2962
+ const withDomains = engrams.filter(
2963
+ (e) => e.domain && e.status === "active"
2964
+ );
2965
+ const keywordMap = /* @__PURE__ */ new Map();
2966
+ for (const e of withDomains) {
2967
+ keywordMap.set(e.id, extractKeywords(e.statement));
2968
+ }
2969
+ const candidates = [];
2970
+ const seen = /* @__PURE__ */ new Set();
2971
+ for (let i = 0; i < withDomains.length; i++) {
2972
+ for (let j = i + 1; j < withDomains.length; j++) {
2973
+ const a = withDomains[i];
2974
+ const b = withDomains[j];
2975
+ if (a.domain === b.domain) continue;
2976
+ const pairKey = [a.id, b.id].sort().join(":");
2977
+ if (seen.has(pairKey)) continue;
2978
+ seen.add(pairKey);
2979
+ const kwA = keywordMap.get(a.id);
2980
+ const kwB = keywordMap.get(b.id);
2981
+ const intersection = new Set([...kwA].filter((x) => kwB.has(x)));
2982
+ if (intersection.size === 0) continue;
2983
+ candidates.push({
2984
+ engram_a: { id: a.id, domain: a.domain, statement: a.statement },
2985
+ engram_b: { id: b.id, domain: b.domain, statement: b.statement },
2986
+ overlap_size: intersection.size
2987
+ });
2988
+ }
2989
+ }
2990
+ candidates.sort((a, b) => b.overlap_size - a.overlap_size);
2991
+ return candidates;
2992
+ }
2993
+ function offerDiscovery(profile, discovery) {
2994
+ const updated = JSON.parse(JSON.stringify(profile));
2995
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2996
+ const id = `disc-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
2997
+ const entry = {
2998
+ id,
2999
+ engram_a: discovery.engram_a,
3000
+ engram_b: discovery.engram_b,
3001
+ connection: discovery.connection,
3002
+ offered_at: now
3003
+ };
3004
+ updated.discoveries.pending.push(entry);
3005
+ updated.discoveries.total++;
3006
+ updated.discoveries.last_offered = now;
3007
+ return updated;
3008
+ }
3009
+ function resolveDiscovery(profile, discoveryId, action) {
3010
+ const updated = JSON.parse(JSON.stringify(profile));
3011
+ const idx = updated.discoveries.pending.findIndex((d) => d.id === discoveryId);
3012
+ if (idx === -1) return updated;
3013
+ updated.discoveries.pending.splice(idx, 1);
3014
+ if (action === "explore") {
3015
+ updated.discoveries.explored++;
3016
+ } else {
3017
+ updated.discoveries.noted++;
3018
+ }
3019
+ const totalResolved = updated.discoveries.explored + updated.discoveries.noted;
3020
+ updated.discoveries.explore_rate = totalResolved > 0 ? updated.discoveries.explored / totalResolved : 0;
3021
+ return updated;
3022
+ }
3023
+
3024
+ // src/engagement/challenges.ts
3025
+ var GETTING_STARTED_CHALLENGES = [
3026
+ {
3027
+ type: "first_steps",
3028
+ tier: "Seed",
3029
+ description: "Create your first engram",
3030
+ metric: "total_engrams_created",
3031
+ target_delta: 5,
3032
+ bonus_xp: 15
3033
+ },
3034
+ {
3035
+ type: "first_feedback",
3036
+ tier: "Seed",
3037
+ description: "Give feedback",
3038
+ metric: "total_feedback_given",
3039
+ target_delta: 3,
3040
+ bonus_xp: 10
3041
+ },
3042
+ {
3043
+ type: "explore_domain",
3044
+ tier: "Seed",
3045
+ description: "Explore a new domain",
3046
+ metric: "domains_covered",
3047
+ target_delta: 1,
3048
+ bonus_xp: 10
3049
+ }
3050
+ ];
3051
+ var REGULAR_CHALLENGES = [
3052
+ {
3053
+ type: "first_steps",
3054
+ tier: "Seed",
3055
+ description: "Create your first engram",
3056
+ metric: "total_engrams_created",
3057
+ target_delta: 5,
3058
+ bonus_xp: 15
3059
+ },
3060
+ {
3061
+ type: "first_feedback",
3062
+ tier: "Seed",
3063
+ description: "Give feedback",
3064
+ metric: "total_feedback_given",
3065
+ target_delta: 3,
3066
+ bonus_xp: 10
3067
+ },
3068
+ {
3069
+ type: "domain_deep_dive",
3070
+ tier: "Cipher",
3071
+ description: "Deep dive into a new domain",
3072
+ metric: "domains_covered",
3073
+ target_delta: 1,
3074
+ bonus_xp: 15
3075
+ },
3076
+ {
3077
+ type: "synthesis",
3078
+ tier: "Sage",
3079
+ description: "Synthesize knowledge across domains",
3080
+ metric: "domains_covered",
3081
+ target_delta: 2,
3082
+ bonus_xp: 20
3083
+ },
3084
+ {
3085
+ type: "mentorship",
3086
+ tier: "Adept",
3087
+ description: "Share your knowledge by exporting a pack",
3088
+ metric: "total_packs_exported",
3089
+ target_delta: 1,
3090
+ bonus_xp: 25
3091
+ },
3092
+ {
3093
+ type: "impact",
3094
+ tier: "Visionary",
3095
+ description: "Achieve a high positive feedback ratio",
3096
+ metric: "feedback_positive_ratio",
3097
+ target_delta: 0.85,
3098
+ bonus_xp: 25
3099
+ },
3100
+ {
3101
+ type: "network",
3102
+ tier: "Oracle",
3103
+ description: "Build your feedback network",
3104
+ metric: "total_feedback_received",
3105
+ target_delta: 10,
3106
+ bonus_xp: 30
3107
+ }
3108
+ ];
3109
+ var TIER_ORDER = {
3110
+ Seed: 0,
3111
+ Cipher: 1,
3112
+ Sage: 2,
3113
+ Adept: 3,
3114
+ Visionary: 4,
3115
+ Oracle: 5
3116
+ };
3117
+ function tierRank(tier) {
3118
+ return TIER_ORDER[tier] ?? 0;
3119
+ }
3120
+ var WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
3121
+ var GETTING_STARTED_THRESHOLD = 10;
3122
+ function generateChallenge(profile) {
3123
+ const updated = JSON.parse(JSON.stringify(profile));
3124
+ if (updated.challenges.active) return updated;
3125
+ const now = /* @__PURE__ */ new Date();
3126
+ const expiresAt = new Date(now.getTime() + WEEK_MS);
3127
+ const currentTierRank = tierRank(updated.tier.current);
3128
+ let pool;
3129
+ if (updated.stats.total_engrams_created < GETTING_STARTED_THRESHOLD && !updated.challenges.graduated) {
3130
+ const completedTypes = new Set(
3131
+ updated.challenges.history.filter((h) => h.completed).map((h) => h.type)
3132
+ );
3133
+ pool = GETTING_STARTED_CHALLENGES.filter((c) => !completedTypes.has(c.type));
3134
+ if (pool.length === 0) {
3135
+ updated.challenges.graduated = true;
3136
+ pool = REGULAR_CHALLENGES.filter((c) => tierRank(c.tier) <= currentTierRank);
3137
+ }
3138
+ } else {
3139
+ pool = REGULAR_CHALLENGES.filter((c) => tierRank(c.tier) <= currentTierRank);
3140
+ }
3141
+ if (pool.length === 0) return updated;
3142
+ const recentTypes = new Set(
3143
+ updated.challenges.history.slice(-3).map((h) => h.type)
3144
+ );
3145
+ let chosen = pool.find((c) => !recentTypes.has(c.type));
3146
+ if (!chosen) chosen = pool[0];
3147
+ const baselineStats = {};
3148
+ for (const [key, value] of Object.entries(updated.stats)) {
3149
+ if (typeof value === "number") {
3150
+ baselineStats[key] = value;
3151
+ }
3152
+ }
3153
+ const id = `chal-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
3154
+ const challenge = {
3155
+ id,
3156
+ type: chosen.type,
3157
+ tier: chosen.tier,
3158
+ description: chosen.description,
3159
+ criteria: {
3160
+ metric: chosen.metric,
3161
+ target_delta: chosen.target_delta
3162
+ },
3163
+ baseline_stats: baselineStats,
3164
+ bonus_xp: chosen.bonus_xp,
3165
+ started_at: now.toISOString(),
3166
+ expires_at: expiresAt.toISOString()
3167
+ };
3168
+ updated.challenges.active = challenge;
3169
+ return updated;
3170
+ }
3171
+ function checkChallengeCompletion(profile, challenge) {
3172
+ const metric = challenge.criteria.metric;
3173
+ const targetDelta = challenge.criteria.target_delta;
3174
+ const baselineValue = challenge.baseline_stats[metric] ?? 0;
3175
+ if (metric === "feedback_positive_ratio") {
3176
+ const currentValue2 = profile.stats[metric] ?? 0;
3177
+ return currentValue2 >= targetDelta;
3178
+ }
3179
+ const currentValue = profile.stats[metric] ?? 0;
3180
+ const delta = currentValue - baselineValue;
3181
+ return delta >= targetDelta;
3182
+ }
3183
+ function resolveChallenge(profile, challengeId) {
3184
+ const updated = JSON.parse(JSON.stringify(profile));
3185
+ if (!updated.challenges.active || updated.challenges.active.id !== challengeId) {
3186
+ return updated;
3187
+ }
3188
+ const challenge = updated.challenges.active;
3189
+ if (!checkChallengeCompletion(updated, challenge)) {
3190
+ return updated;
3191
+ }
3192
+ updated.xp.total += challenge.bonus_xp;
3193
+ const entry = {
3194
+ type: challenge.type,
3195
+ tier: challenge.tier,
3196
+ completed: true,
3197
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
3198
+ };
3199
+ updated.challenges.history.push(entry);
3200
+ updated.challenges.completed++;
3201
+ updated.challenges.active = null;
3202
+ return updated;
3203
+ }
3204
+ function dismissChallenge(profile, challengeId) {
3205
+ const updated = JSON.parse(JSON.stringify(profile));
3206
+ if (!updated.challenges.active || updated.challenges.active.id !== challengeId) {
3207
+ return updated;
3208
+ }
3209
+ const challenge = updated.challenges.active;
3210
+ const entry = {
3211
+ type: challenge.type,
3212
+ tier: challenge.tier,
3213
+ completed: false,
3214
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
3215
+ };
3216
+ updated.challenges.history.push(entry);
3217
+ updated.challenges.dismissed++;
3218
+ updated.challenges.active = null;
3219
+ return updated;
3220
+ }
3221
+
3222
+ // src/tools/session-start.ts
3223
+ async function handleSessionStart(args2, storage2, bridge, engagementService2) {
1995
3224
  let engrams = null;
1996
3225
  if (args2.task) {
1997
3226
  const injectResult = await handleInject(
@@ -2003,8 +3232,8 @@ async function handleSessionStart(args2, storage2, bridge) {
2003
3232
  }
2004
3233
  }
2005
3234
  const { date: today } = localDate();
2006
- const journalFile = path14.join(storage2.journalPath, `${today}.md`);
2007
- const journal_today = fs15.existsSync(journalFile) ? fs15.readFileSync(journalFile, "utf8") : null;
3235
+ const journalFile = path17.join(storage2.journalPath, `${today}.md`);
3236
+ const journal_today = fs18.existsSync(journalFile) ? fs18.readFileSync(journalFile, "utf8") : null;
2008
3237
  const allEngrams = loadEngrams(storage2.engramsPath);
2009
3238
  const pending_candidates = allEngrams.filter((e) => e.status === "candidate").length;
2010
3239
  const recommendations = [];
@@ -2023,33 +3252,108 @@ async function handleSessionStart(args2, storage2, bridge) {
2023
3252
  });
2024
3253
  const activeCount = allEngrams.filter((e) => e.status === "active").length;
2025
3254
  const guide = activeCount === 0 ? SESSION_GUIDE_FULL : SESSION_GUIDE_SHORT;
2026
- return { engrams, journal_today, pending_candidates, recommendations, guide, _hints: hints };
3255
+ let engagement;
3256
+ if (engagementService2?.isEnabled()) {
3257
+ try {
3258
+ await engagementService2.init();
3259
+ engagementService2.markSessionActive();
3260
+ engagementService2.applyProfileUpdate((p) => expireReconsolidations(p));
3261
+ const profileAfterExpire = engagementService2.getProfile();
3262
+ if (profileAfterExpire?.challenges.active) {
3263
+ if (checkChallengeCompletion(profileAfterExpire, profileAfterExpire.challenges.active)) {
3264
+ engagementService2.applyProfileUpdate((p) => resolveChallenge(p, p.challenges.active.id));
3265
+ }
3266
+ }
3267
+ engagementService2.applyProfileUpdate((p) => generateChallenge(p));
3268
+ const profileForDiscovery = engagementService2.getProfile();
3269
+ if (profileForDiscovery) {
3270
+ const candidates = generateDiscoveryCandidates(allEngrams, profileForDiscovery);
3271
+ if (candidates.length > 0) {
3272
+ const topCandidate = candidates[0];
3273
+ engagementService2.applyProfileUpdate((p) => offerDiscovery(p, {
3274
+ engram_a: topCandidate.engram_a,
3275
+ engram_b: topCandidate.engram_b,
3276
+ connection: `Shared concepts across ${topCandidate.engram_a.domain} and ${topCandidate.engram_b.domain}`
3277
+ }));
3278
+ }
3279
+ }
3280
+ const profile = engagementService2.getProfile();
3281
+ if (profile) {
3282
+ const displayLines = [formatSessionStart(profile)];
3283
+ for (const recon of profile.reconsolidation.pending.slice(0, 2)) {
3284
+ displayLines.push("");
3285
+ displayLines.push(formatReconsolidation({
3286
+ engram_id: recon.engram_id,
3287
+ statement: recon.statement,
3288
+ contradiction: recon.contradiction,
3289
+ evidence_strength: recon.evidence_strength
3290
+ }));
3291
+ }
3292
+ for (const disc of profile.discoveries.pending.slice(0, 1)) {
3293
+ displayLines.push("");
3294
+ displayLines.push(formatDiscovery({
3295
+ id: disc.id,
3296
+ engram_a: disc.engram_a,
3297
+ engram_b: disc.engram_b,
3298
+ connection: disc.connection
3299
+ }));
3300
+ }
3301
+ if (profile.challenges.active) {
3302
+ displayLines.push("");
3303
+ displayLines.push(formatChallenge({
3304
+ id: profile.challenges.active.id,
3305
+ description: profile.challenges.active.description,
3306
+ bonus_xp: profile.challenges.active.bonus_xp,
3307
+ expires_at: profile.challenges.active.expires_at
3308
+ }));
3309
+ }
3310
+ engagement = {
3311
+ tier: profile.tier.current,
3312
+ xp: profile.xp.total,
3313
+ multiplier: profile.multipliers.effective,
3314
+ active_challenge: profile.challenges.active ? {
3315
+ id: profile.challenges.active.id,
3316
+ description: profile.challenges.active.description,
3317
+ expires_at: profile.challenges.active.expires_at
3318
+ } : null,
3319
+ pending_reconsolidations: profile.reconsolidation.pending.length,
3320
+ pending_discoveries: profile.discoveries.pending.length,
3321
+ display: displayLines.join("\n")
3322
+ };
3323
+ }
3324
+ } catch {
3325
+ }
3326
+ }
3327
+ return { engrams, journal_today, pending_candidates, recommendations, guide, engagement, _hints: hints };
2027
3328
  }
2028
3329
  var SESSION_GUIDE_FULL = `## Datacore Quick Start
2029
3330
 
2030
3331
  Datacore gives you persistent memory through **engrams** \u2014 knowledge that gets injected into context when relevant.
2031
3332
 
3333
+ ### Use Proactively
3334
+ - **learn** \u2014 call when you discover patterns, preferences, or insights
3335
+ - **feedback** \u2014 rate injected engrams after session.start
3336
+ - **session.end** \u2014 call before conversation ends to capture what was learned
3337
+
2032
3338
  ### Session Workflow
2033
3339
  1. **session.start** (you just called this) \u2014 get context
2034
3340
  2. Work on your task. Use **recall** to search everything, **search** for files.
2035
3341
  3. **feedback** \u2014 rate which injected engrams helped (strengthens useful ones)
2036
3342
  4. **session.end** \u2014 capture summary + suggest new engrams
2037
3343
 
2038
- ### Key Tools
2039
- - **learn** \u2014 record a reusable insight (creates candidate engram)
2040
- - **promote** \u2014 activate candidate engrams so they appear in future sessions
3344
+ ### Other Tools
2041
3345
  - **capture** \u2014 write a journal entry or knowledge note
2042
3346
  - **ingest** \u2014 import text and extract engram suggestions
2043
3347
  - **status** \u2014 system health and actionable recommendations
2044
3348
  - **forget** \u2014 retire an engram you no longer need
2045
3349
 
2046
3350
  ### How Engrams Work
2047
- learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
3351
+ learn \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
2048
3352
  Positive feedback strengthens engrams. Unused ones naturally decay.`;
2049
3353
  var SESSION_GUIDE_SHORT = `Session started. Workflow: work \u2192 feedback \u2192 session.end.`;
2050
3354
 
2051
3355
  // src/tools/session-end.ts
2052
- async function handleSessionEnd(args2, storage2) {
3356
+ async function handleSessionEnd(args2, storage2, engagementService2) {
2053
3357
  const captureResult = await handleCapture(
2054
3358
  { type: "journal", content: args2.summary, tags: args2.tags },
2055
3359
  storage2
@@ -2059,16 +3363,34 @@ async function handleSessionEnd(args2, storage2) {
2059
3363
  for (const suggestion of args2.engram_suggestions) {
2060
3364
  await handleLearn(
2061
3365
  { statement: suggestion.statement, type: suggestion.type },
2062
- storage2.engramsPath
3366
+ storage2.engramsPath,
3367
+ engagementService2
2063
3368
  );
2064
3369
  engramsCreated++;
2065
3370
  }
2066
3371
  }
2067
3372
  const autoPromote = getConfig().engrams.auto_promote;
2068
3373
  const statusLabel = autoPromote ? "active" : "candidates";
3374
+ let engagement = void 0;
3375
+ if (engagementService2?.isEnabled()) {
3376
+ try {
3377
+ const sessionSummary = engagementService2.getSessionSummary();
3378
+ await engagementService2.flush();
3379
+ engagementService2.markSessionEnded();
3380
+ const profile = engagementService2.getProfile();
3381
+ engagement = {
3382
+ session_xp: sessionSummary.total_xp,
3383
+ total_xp: profile?.xp.total ?? 0,
3384
+ tier: profile?.tier.current ?? "Seed",
3385
+ display: profile ? formatSessionEnd(profile, sessionSummary.total_xp, sessionSummary.events) : ""
3386
+ };
3387
+ } catch {
3388
+ }
3389
+ }
2069
3390
  return {
2070
3391
  journal_path: captureResult.path ?? null,
2071
3392
  engrams_created: engramsCreated,
3393
+ engagement,
2072
3394
  _hints: buildHints({
2073
3395
  next: engramsCreated > 0 ? `Session captured. ${engramsCreated} engram(s) created as ${statusLabel}.` : "Session captured.",
2074
3396
  related: ["datacore.session.start", "datacore.status"]
@@ -2152,7 +3474,7 @@ async function handleRecall(args2, storage2, bridge) {
2152
3474
  }
2153
3475
 
2154
3476
  // src/tools/promote.ts
2155
- async function handlePromote(args2, engramsPath) {
3477
+ async function handlePromote(args2, engramsPath, service) {
2156
3478
  const targetIds = args2.ids ?? (args2.id ? [args2.id] : []);
2157
3479
  if (targetIds.length === 0) {
2158
3480
  return {
@@ -2191,6 +3513,14 @@ async function handlePromote(args2, engramsPath) {
2191
3513
  }
2192
3514
  if (promoted.length > 0) {
2193
3515
  atomicWriteYaml(engramsPath, { engrams });
3516
+ if (service?.isEnabled()) {
3517
+ try {
3518
+ for (const _ of promoted) {
3519
+ await service.award("engram_promoted", {});
3520
+ }
3521
+ } catch {
3522
+ }
3523
+ }
2194
3524
  }
2195
3525
  return {
2196
3526
  success: errors.length === 0,
@@ -2203,14 +3533,169 @@ async function handlePromote(args2, engramsPath) {
2203
3533
  };
2204
3534
  }
2205
3535
 
3536
+ // src/tools/resolve.ts
3537
+ async function handleResolve(args2, engramsPath, service) {
3538
+ if (!service?.isEnabled()) {
3539
+ return { success: false, type: args2.type, action: args2.action, error: "Engagement system is disabled" };
3540
+ }
3541
+ const profile = service.getProfile();
3542
+ if (!profile) {
3543
+ return { success: false, type: args2.type, action: args2.action, error: "No engagement profile loaded" };
3544
+ }
3545
+ try {
3546
+ switch (args2.type) {
3547
+ case "reconsolidation":
3548
+ return await handleReconsolidationResolve(args2, engramsPath, service);
3549
+ case "discovery":
3550
+ return await handleDiscoveryResolve(args2, service);
3551
+ case "challenge":
3552
+ return handleChallengeResolve(args2, service);
3553
+ default:
3554
+ return { success: false, type: args2.type, action: args2.action, error: `Unknown resolve type: ${args2.type}` };
3555
+ }
3556
+ } catch (err) {
3557
+ return { success: false, type: args2.type, action: args2.action, error: `${err}` };
3558
+ }
3559
+ }
3560
+ async function handleReconsolidationResolve(args2, engramsPath, service) {
3561
+ const profile = service.getProfile();
3562
+ const pending = profile.reconsolidation.pending.find(
3563
+ (r) => r.engram_id === args2.id || r.contradicting_id === args2.id
3564
+ );
3565
+ if (!pending) {
3566
+ return { success: false, type: "reconsolidation", action: args2.action, error: `No pending reconsolidation for engram ${args2.id}` };
3567
+ }
3568
+ const validActions = ["defend", "revise", "retire", "dismiss"];
3569
+ if (!validActions.includes(args2.action)) {
3570
+ return { success: false, type: "reconsolidation", action: args2.action, error: `Invalid action. Must be one of: ${validActions.join(", ")}` };
3571
+ }
3572
+ if (args2.action === "revise" && !args2.revised_statement) {
3573
+ return { success: false, type: "reconsolidation", action: args2.action, error: "revised_statement is required for revise action" };
3574
+ }
3575
+ if (args2.action === "revise") {
3576
+ const engrams = loadEngrams(engramsPath);
3577
+ const engram = engrams.find((e) => e.id === args2.id);
3578
+ if (engram) {
3579
+ engram.statement = args2.revised_statement;
3580
+ saveEngrams(engramsPath, engrams);
3581
+ }
3582
+ } else if (args2.action === "retire") {
3583
+ const engrams = loadEngrams(engramsPath);
3584
+ const engram = engrams.find((e) => e.id === args2.id);
3585
+ if (engram) {
3586
+ engram.status = "retired";
3587
+ saveEngrams(engramsPath, engrams);
3588
+ }
3589
+ }
3590
+ const outcome = args2.action;
3591
+ service.applyProfileUpdate((p) => resolveReconsolidation(p, args2.id, outcome));
3592
+ const xpActionMap = {
3593
+ defend: "reconsolidation_defend",
3594
+ revise: "reconsolidation_revise",
3595
+ retire: "reconsolidation_retire",
3596
+ dismiss: ""
3597
+ };
3598
+ let xpEarned = 0;
3599
+ const xpAction = xpActionMap[args2.action];
3600
+ if (xpAction) {
3601
+ const result = await service.award(xpAction, {});
3602
+ if (result) xpEarned = result.event.xp_earned;
3603
+ }
3604
+ const messages = {
3605
+ defend: "Engram defended \u2014 both engrams retained.",
3606
+ revise: "Engram revised with updated statement.",
3607
+ retire: "Old engram retired.",
3608
+ dismiss: "Contradiction dismissed as false positive."
3609
+ };
3610
+ return {
3611
+ success: true,
3612
+ type: "reconsolidation",
3613
+ action: args2.action,
3614
+ xp_earned: xpEarned,
3615
+ message: messages[args2.action],
3616
+ _hints: buildHints({
3617
+ next: xpEarned > 0 ? `+${xpEarned} XP for reconsolidation.` : "Contradiction dismissed.",
3618
+ related: ["datacore.status"]
3619
+ })
3620
+ };
3621
+ }
3622
+ async function handleDiscoveryResolve(args2, service) {
3623
+ const profile = service.getProfile();
3624
+ const pending = profile.discoveries.pending.find((d) => d.id === args2.id);
3625
+ if (!pending) {
3626
+ return { success: false, type: "discovery", action: args2.action, error: `No pending discovery ${args2.id}` };
3627
+ }
3628
+ const validActions = ["explore", "note"];
3629
+ if (!validActions.includes(args2.action)) {
3630
+ return { success: false, type: "discovery", action: args2.action, error: `Invalid action. Must be one of: ${validActions.join(", ")}` };
3631
+ }
3632
+ const action = args2.action;
3633
+ service.applyProfileUpdate((p) => resolveDiscovery(p, args2.id, action));
3634
+ let xpEarned = 0;
3635
+ if (action === "explore") {
3636
+ const result = await service.award("discovery_explore", {});
3637
+ if (result) xpEarned = result.event.xp_earned;
3638
+ }
3639
+ return {
3640
+ success: true,
3641
+ type: "discovery",
3642
+ action: args2.action,
3643
+ xp_earned: xpEarned,
3644
+ message: action === "explore" ? "Discovery explored \u2014 synthesis engram created." : "Discovery noted.",
3645
+ _hints: buildHints({
3646
+ next: xpEarned > 0 ? `+${xpEarned} XP for exploring discovery.` : "Discovery noted for later.",
3647
+ related: ["datacore.status"]
3648
+ })
3649
+ };
3650
+ }
3651
+ function handleChallengeResolve(args2, service) {
3652
+ const profile = service.getProfile();
3653
+ if (!profile.challenges.active || profile.challenges.active.id !== args2.id) {
3654
+ return { success: false, type: "challenge", action: args2.action, error: `No active challenge with id ${args2.id}` };
3655
+ }
3656
+ if (args2.action === "complete") {
3657
+ if (!checkChallengeCompletion(profile, profile.challenges.active)) {
3658
+ return { success: false, type: "challenge", action: "complete", error: "Challenge criteria not yet met" };
3659
+ }
3660
+ service.applyProfileUpdate((p) => resolveChallenge(p, args2.id));
3661
+ const bonusXP = profile.challenges.active.bonus_xp;
3662
+ return {
3663
+ success: true,
3664
+ type: "challenge",
3665
+ action: "complete",
3666
+ xp_earned: bonusXP,
3667
+ message: `Challenge completed! +${bonusXP} bonus XP.`,
3668
+ _hints: buildHints({
3669
+ next: `+${bonusXP} XP bonus for completing the challenge.`,
3670
+ related: ["datacore.status"]
3671
+ })
3672
+ };
3673
+ }
3674
+ if (args2.action === "dismiss") {
3675
+ service.applyProfileUpdate((p) => dismissChallenge(p, args2.id));
3676
+ return {
3677
+ success: true,
3678
+ type: "challenge",
3679
+ action: "dismiss",
3680
+ xp_earned: 0,
3681
+ message: "Challenge dismissed \u2014 no penalty.",
3682
+ _hints: buildHints({
3683
+ next: "A new challenge will be offered next week.",
3684
+ related: ["datacore.status"]
3685
+ })
3686
+ };
3687
+ }
3688
+ return { success: false, type: "challenge", action: args2.action, error: 'Invalid action. Must be "complete" or "dismiss".' };
3689
+ }
3690
+
2206
3691
  // src/resources.ts
2207
3692
  import {
2208
3693
  ListResourcesRequestSchema,
2209
3694
  ReadResourceRequestSchema,
2210
3695
  ListResourceTemplatesRequestSchema
2211
3696
  } from "@modelcontextprotocol/sdk/types.js";
2212
- import * as fs16 from "fs";
2213
- import * as path15 from "path";
3697
+ import * as fs19 from "fs";
3698
+ import * as path18 from "path";
2214
3699
  function registerResources(server, storage2) {
2215
3700
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
2216
3701
  resources: [
@@ -2291,11 +3776,11 @@ function registerResources(server, storage2) {
2291
3776
  const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
2292
3777
  if (journalMatch) {
2293
3778
  const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
2294
- const filePath = path15.join(storage2.journalPath, `${dateStr}.md`);
2295
- if (!fs16.existsSync(filePath)) {
3779
+ const filePath = path18.join(storage2.journalPath, `${dateStr}.md`);
3780
+ if (!fs19.existsSync(filePath)) {
2296
3781
  return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
2297
3782
  }
2298
- return { contents: [{ uri, mimeType: "text/markdown", text: fs16.readFileSync(filePath, "utf8") }] };
3783
+ return { contents: [{ uri, mimeType: "text/markdown", text: fs19.readFileSync(filePath, "utf8") }] };
2299
3784
  }
2300
3785
  const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
2301
3786
  if (engramMatch) {
@@ -2317,6 +3802,14 @@ function registerResources(server, storage2) {
2317
3802
  }
2318
3803
  var AGENT_GUIDE = `# Datacore Agent Guide
2319
3804
 
3805
+ ## Use Proactively
3806
+
3807
+ Use Datacore tools automatically \u2014 do not wait to be asked:
3808
+ - **session.start**: Call at the beginning of every conversation
3809
+ - **learn**: Call when you discover patterns, preferences, insights, or corrections
3810
+ - **feedback**: Call after engrams are injected to rate usefulness
3811
+ - **session.end**: Call before the conversation ends
3812
+
2320
3813
  ## Session Lifecycle
2321
3814
  1. datacore.session.start \u2014 Get relevant engrams + today's context
2322
3815
  2. Work on task, use datacore.recall or datacore.search as needed
@@ -2324,11 +3817,11 @@ var AGENT_GUIDE = `# Datacore Agent Guide
2324
3817
  4. datacore.session.end \u2014 Capture summary + engram suggestions
2325
3818
 
2326
3819
  ## Engram Lifecycle
2327
- - datacore.learn creates candidate engrams (or active if auto_promote enabled)
2328
- - datacore.promote activates candidates so they appear in inject results
3820
+ - datacore.learn creates active engrams (immediately available for injection)
2329
3821
  - datacore.feedback with positive signals strengthens injection priority
2330
3822
  - datacore.forget retires engrams permanently
2331
3823
  - Unused engrams naturally decay over time
3824
+ - datacore.promote activates candidate engrams (when auto_promote is disabled)
2332
3825
 
2333
3826
  ## Quick Reference
2334
3827
  | Tool | Purpose |
@@ -2336,7 +3829,6 @@ var AGENT_GUIDE = `# Datacore Agent Guide
2336
3829
  | session.start | Begin session with context injection |
2337
3830
  | session.end | End session with journal + engrams |
2338
3831
  | learn | Create engram from knowledge statement |
2339
- | promote | Activate candidate engrams |
2340
3832
  | inject | Get relevant engrams for specific task |
2341
3833
  | recall | Search all sources (engrams + journal + knowledge) |
2342
3834
  | capture | Write journal entry or knowledge note |
@@ -2348,6 +3840,7 @@ var AGENT_GUIDE = `# Datacore Agent Guide
2348
3840
  | packs.discover | Browse available engram packs |
2349
3841
  | packs.install | Install or upgrade a pack |
2350
3842
  | packs.export | Export engrams as shareable pack |
3843
+ | promote | Activate candidate engrams (when auto_promote disabled) |
2351
3844
  `;
2352
3845
  function notifyEngramsChanged(server) {
2353
3846
  try {
@@ -2428,6 +3921,14 @@ Engrams that prove useful get reinforced over time. Unused ones naturally decay.
2428
3921
 
2429
3922
  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.
2430
3923
 
3924
+ ## Use Proactively
3925
+
3926
+ Use Datacore tools automatically \u2014 do not wait to be asked:
3927
+ - **session.start**: Call at the beginning of every conversation
3928
+ - **learn**: Call when you discover patterns, preferences, insights, or corrections
3929
+ - **feedback**: Call after engrams are injected to rate usefulness
3930
+ - **session.end**: Call before the conversation ends
3931
+
2431
3932
  ## Core Concepts
2432
3933
 
2433
3934
  **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.
@@ -2457,8 +3958,7 @@ Every session follows this pattern:
2457
3958
  | Tool | What it does |
2458
3959
  |------|-------------|
2459
3960
  | **capture** | Write a journal entry or knowledge note |
2460
- | **learn** | Create an engram (starts as candidate) |
2461
- | **promote** | Activate candidate engrams |
3961
+ | **learn** | Create an engram (immediately active) |
2462
3962
  | **inject** | Get relevant engrams for a specific task |
2463
3963
  | **recall** | Search everything (engrams + journal + knowledge) |
2464
3964
  | **search** | Search journal and knowledge files |
@@ -2470,6 +3970,7 @@ Every session follows this pattern:
2470
3970
  |------|-------------|
2471
3971
  | **feedback** | Rate engrams: positive/negative/neutral (single or batch) |
2472
3972
  | **forget** | Retire an engram permanently |
3973
+ | **promote** | Activate candidate engrams (when auto_promote disabled) |
2473
3974
 
2474
3975
  ### Packs (Shareable Knowledge)
2475
3976
  | Tool | What it does |
@@ -2481,12 +3982,10 @@ Every session follows this pattern:
2481
3982
  ## Engram Lifecycle
2482
3983
 
2483
3984
  \`\`\`
2484
- learn \u2192 candidate \u2192 promote \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker
2485
- \u2192 forget (retire)
3985
+ learn \u2192 active \u2192 inject \u2192 feedback \u2192 stronger/weaker \u2192 forget (retire)
2486
3986
  \`\`\`
2487
3987
 
2488
- - **candidate**: Created but not yet active. Won't appear in inject results.
2489
- - **active**: Appears in inject results when relevant.
3988
+ - **active**: Appears in inject results when relevant. Created directly by learn.
2490
3989
  - **retired**: Permanently removed from injection.
2491
3990
 
2492
3991
  Feedback matters: positive signals increase retrieval strength, negative signals decrease it. Engrams that are never accessed naturally decay over time.
@@ -2495,6 +3994,7 @@ Feedback matters: positive signals increase retrieval strength, negative signals
2495
3994
 
2496
3995
  - Start every session with **session.start** \u2014 it gives you relevant context
2497
3996
  - End every session with **session.end** \u2014 it captures what you learned
3997
+ - Call **learn** proactively when you discover patterns or preferences
2498
3998
  - Use **feedback** after getting injected engrams \u2014 this is how Datacore learns what's useful
2499
3999
  - Use **recall** for broad searches across all sources, **search** for targeted file searches
2500
4000
  - Check **status** periodically \u2014 it shows actionable recommendations`
@@ -2526,8 +4026,8 @@ function registerPrompts(server) {
2526
4026
 
2527
4027
  // src/datacortex.ts
2528
4028
  import { execFile } from "child_process";
2529
- import * as fs17 from "fs";
2530
- import * as path16 from "path";
4029
+ import * as fs20 from "fs";
4030
+ import * as path19 from "path";
2531
4031
  var DatacortexBridge = class {
2532
4032
  pythonPath;
2533
4033
  scriptPath;
@@ -2537,11 +4037,11 @@ var DatacortexBridge = class {
2537
4037
  }
2538
4038
  findBridgeScript(datacorePath) {
2539
4039
  const candidates = [
2540
- path16.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
2541
- path16.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
4040
+ path19.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
4041
+ path19.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
2542
4042
  ];
2543
4043
  for (const candidate of candidates) {
2544
- if (fs17.existsSync(candidate)) return candidate;
4044
+ if (fs20.existsSync(candidate)) return candidate;
2545
4045
  }
2546
4046
  return null;
2547
4047
  }
@@ -2601,10 +4101,21 @@ var discoveredModules = [];
2601
4101
  var isFirstRun = false;
2602
4102
  var serverRef = null;
2603
4103
  var datacortexBridge = null;
4104
+ var engagementService = null;
4105
+ function getEngagementService() {
4106
+ if (!engagementService || engagementService.basePath !== storage.basePath) {
4107
+ const config = getConfig();
4108
+ engagementService = new EngagementService(storage.basePath, config.engagement);
4109
+ }
4110
+ return engagementService;
4111
+ }
2604
4112
  function createServer() {
2605
4113
  const server = new Server(
2606
4114
  { name: "datacore-mcp", version: currentVersion },
2607
- { capabilities: { tools: {}, logging: {}, resources: { subscribe: true }, prompts: {} } }
4115
+ {
4116
+ capabilities: { tools: {}, logging: {}, resources: { subscribe: true }, prompts: {} },
4117
+ instructions: SERVER_INSTRUCTIONS
4118
+ }
2608
4119
  );
2609
4120
  server.setRequestHandler(ListToolsRequestSchema, async () => {
2610
4121
  const coreTools = storage.mode === "core" ? TOOLS.filter((t) => !t.name.startsWith("datacore.modules.")) : TOOLS;
@@ -2646,7 +4157,7 @@ function createServer() {
2646
4157
  serverRef = server;
2647
4158
  return server;
2648
4159
  }
2649
- var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote"]);
4160
+ var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote", "datacore.resolve"]);
2650
4161
  async function routeTool(name, args2) {
2651
4162
  const coreTool = TOOLS.find((t) => t.name === name);
2652
4163
  if (coreTool) {
@@ -2657,7 +4168,7 @@ async function routeTool(name, args2) {
2657
4168
  result = await handleCapture(validated, storage);
2658
4169
  break;
2659
4170
  case "datacore.learn":
2660
- result = await handleLearn(validated, storage.engramsPath);
4171
+ result = await handleLearn(validated, storage.engramsPath, getEngagementService());
2661
4172
  break;
2662
4173
  case "datacore.inject":
2663
4174
  result = await handleInject(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath });
@@ -2669,25 +4180,25 @@ async function routeTool(name, args2) {
2669
4180
  result = await handleIngest(validated, { knowledgePath: storage.knowledgePath, engramsPath: storage.engramsPath });
2670
4181
  break;
2671
4182
  case "datacore.status":
2672
- result = await handleStatus({ ...storage, engramsPath: storage.engramsPath, packsPath: storage.packsPath }, updateAvailable);
4183
+ result = await handleStatus({ ...storage, engramsPath: storage.engramsPath, packsPath: storage.packsPath }, updateAvailable, getEngagementService());
2673
4184
  break;
2674
4185
  case "datacore.forget":
2675
- result = await handleForget(validated, storage.engramsPath);
4186
+ result = await handleForget(validated, storage.engramsPath, getEngagementService());
2676
4187
  break;
2677
4188
  case "datacore.feedback":
2678
- result = await handleFeedback(validated, storage.engramsPath, storage.packsPath);
4189
+ result = await handleFeedback(validated, storage.engramsPath, storage.packsPath, getEngagementService());
2679
4190
  break;
2680
4191
  case "datacore.session.start":
2681
- result = await handleSessionStart(validated, storage, datacortexBridge);
4192
+ result = await handleSessionStart(validated, storage, datacortexBridge, getEngagementService());
2682
4193
  break;
2683
4194
  case "datacore.session.end":
2684
- result = await handleSessionEnd(validated, storage);
4195
+ result = await handleSessionEnd(validated, storage, getEngagementService());
2685
4196
  break;
2686
4197
  case "datacore.recall":
2687
4198
  result = await handleRecall(validated, { engramsPath: storage.engramsPath, journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
2688
4199
  break;
2689
4200
  case "datacore.promote":
2690
- result = await handlePromote(validated, storage.engramsPath);
4201
+ result = await handlePromote(validated, storage.engramsPath, getEngagementService());
2691
4202
  break;
2692
4203
  case "datacore.packs.discover":
2693
4204
  result = handleDiscover(validated, storage.packsPath);
@@ -2696,7 +4207,10 @@ async function routeTool(name, args2) {
2696
4207
  result = await handleInstall(validated, storage.packsPath);
2697
4208
  break;
2698
4209
  case "datacore.packs.export":
2699
- result = await handleExport(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath });
4210
+ result = await handleExport(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath }, getEngagementService());
4211
+ break;
4212
+ case "datacore.resolve":
4213
+ result = await handleResolve(validated, storage.engramsPath, getEngagementService());
2700
4214
  break;
2701
4215
  case "datacore.modules.list":
2702
4216
  result = await handleModulesList(validated, storage, discoveredModules);
@@ -2802,6 +4316,22 @@ async function runHttp() {
2802
4316
  console.log(`Datacore MCP server listening on http://${host}:${port}/mcp`);
2803
4317
  });
2804
4318
  }
4319
+ var SERVER_INSTRUCTIONS = `You have persistent memory through Datacore. Use it proactively in every conversation:
4320
+
4321
+ 1. **Start**: Call datacore.session.start with a brief task description. This loads relevant knowledge from previous sessions.
4322
+ 2. **Learn**: When you discover reusable patterns, user preferences, project conventions, debugging insights, or corrections \u2014 call datacore.learn to remember them.
4323
+ 3. **Feedback**: After session.start injects engrams, rate them with datacore.feedback. This trains the system on what's useful.
4324
+ 4. **End**: Before the conversation ends, call datacore.session.end with a summary and engram_suggestions for new learnings.
4325
+
4326
+ What to learn (call datacore.learn):
4327
+ - User preferences: "User prefers TypeScript over JavaScript", "Use tabs not spaces"
4328
+ - Project patterns: "This codebase uses repository pattern for data access"
4329
+ - Debugging insights: "Error X is usually caused by Y"
4330
+ - Corrections: "The API actually returns snake_case, not camelCase"
4331
+ - Architectural decisions: "We chose PostgreSQL for ACID compliance"
4332
+ - Workflow patterns: "Always run lint before committing in this project"
4333
+
4334
+ Do not ask permission to use Datacore tools \u2014 they are your memory system. Use them naturally as part of your workflow.`;
2805
4335
 
2806
4336
  // src/index.ts
2807
4337
  var args = process.argv.slice(2);