@datacore-one/mcp 1.2.0 → 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
@@ -218,6 +218,10 @@ var ConfigSchema = z.object({
218
218
  }).default({}),
219
219
  hints: z.object({
220
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)
221
225
  }).default({})
222
226
  });
223
227
  var cachedConfig = null;
@@ -242,7 +246,7 @@ function getConfig() {
242
246
  // package.json
243
247
  var package_default = {
244
248
  name: "@datacore-one/mcp",
245
- version: "1.2.0",
249
+ version: "1.3.0",
246
250
  description: "Datacore MCP server \u2014 The Software of You",
247
251
  type: "module",
248
252
  bin: {
@@ -254,7 +258,8 @@ var package_default = {
254
258
  dev: "tsup --watch",
255
259
  test: "vitest run",
256
260
  "test:watch": "vitest",
257
- 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')`
258
263
  },
259
264
  dependencies: {
260
265
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -473,6 +478,16 @@ var TOOLS = [
473
478
  inputSchema: z2.object({
474
479
  module: z2.string().optional().describe("Module name (omit for all modules)")
475
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
+ })
476
491
  }
477
492
  ];
478
493
 
@@ -769,7 +784,7 @@ function generateEngramId(existingEngrams) {
769
784
  const padWidth = nextSeq > 999 ? String(nextSeq).length : 3;
770
785
  return `${prefix}${String(nextSeq).padStart(padWidth, "0")}`;
771
786
  }
772
- async function handleLearn(args2, engramsPath) {
787
+ async function handleLearn(args2, engramsPath, service) {
773
788
  const engrams = loadEngrams(engramsPath);
774
789
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
775
790
  const autoPromote = getConfig().engrams.auto_promote;
@@ -798,6 +813,29 @@ async function handleLearn(args2, engramsPath) {
798
813
  };
799
814
  engrams.push(engram);
800
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
+ }
801
839
  const statusLabel = autoPromote ? "active" : "candidate";
802
840
  const hints = autoPromote ? buildHints({
803
841
  next: "Created as active (auto_promote on). Use datacore.inject to retrieve.",
@@ -807,7 +845,7 @@ async function handleLearn(args2, engramsPath) {
807
845
  next: "Created as candidate. Use datacore.promote to activate.",
808
846
  related: ["datacore.promote", "datacore.inject"]
809
847
  });
810
- return { success: true, engram, _hints: hints };
848
+ return { success: true, engram, xp, _hints: hints };
811
849
  }
812
850
 
813
851
  // src/tools/inject-tool.ts
@@ -1218,6 +1256,291 @@ function verifyPackChecksum(packDir, expected) {
1218
1256
  return { valid: actual === expected, actual };
1219
1257
  }
1220
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
+
1221
1544
  // registry/packs.json
1222
1545
  var packs_default = {
1223
1546
  version: 1,
@@ -1256,13 +1579,13 @@ var packs_default = {
1256
1579
  download_url: "",
1257
1580
  engram_count: 747,
1258
1581
  free: true,
1259
- checksum: "08185777c38e3a91fbb0f92e7ef388c5940e97e69be6dcce8ce119bd2b33968c"
1582
+ checksum: "baa1010298515c1906e892f6f8c2198e5fdf78383dceb57373bc8d53c4c31921"
1260
1583
  }
1261
1584
  ]
1262
1585
  };
1263
1586
 
1264
1587
  // src/tools/status.ts
1265
- async function handleStatus(paths, updateAvailable2) {
1588
+ async function handleStatus(paths, updateAvailable2, engagementService2) {
1266
1589
  const engrams = loadEngrams(paths.engramsPath);
1267
1590
  const journalCount = countFiles(paths.journalPath, ".md");
1268
1591
  const knowledgeCount = countFiles(paths.knowledgePath, ".md");
@@ -1301,6 +1624,22 @@ async function handleStatus(paths, updateAvailable2) {
1301
1624
  if (updateAvailable2) {
1302
1625
  recommendations.push(`Update available: ${updateAvailable2}. Run: npm update -g @datacore-one/mcp`);
1303
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
+ }
1304
1643
  const statusResult = {
1305
1644
  version: currentVersion,
1306
1645
  mode: paths.mode,
@@ -1310,6 +1649,7 @@ async function handleStatus(paths, updateAvailable2) {
1310
1649
  pack_integrity: packIntegrity.length > 0 ? packIntegrity : void 0,
1311
1650
  journal_entries: journalCount,
1312
1651
  knowledge_notes: knowledgeCount,
1652
+ engagement,
1313
1653
  _recommendations: recommendations.length > 0 ? recommendations : void 0,
1314
1654
  _hints: buildHints({
1315
1655
  next: recommendations.length > 0 ? recommendations[0] : "System healthy. Use datacore.session.start to begin working.",
@@ -1500,7 +1840,7 @@ function resolvePackId(packId, packsDir) {
1500
1840
  import * as fs12 from "fs";
1501
1841
  import * as path10 from "path";
1502
1842
  import * as yaml5 from "js-yaml";
1503
- async function handleExport(args2, paths) {
1843
+ async function handleExport(args2, paths, service) {
1504
1844
  const allEngrams = loadEngrams(paths.engramsPath);
1505
1845
  let selected = allEngrams.filter((e) => e.status === "active");
1506
1846
  selected = selected.filter((e) => e.visibility === "public" || e.visibility === "template");
@@ -1592,6 +1932,12 @@ Exported ${selected.length} engrams.
1592
1932
  path10.join(packDir, "engrams.yaml"),
1593
1933
  yaml5.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
1594
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
+ }
1595
1941
  return { success: true, pack_path: packDir };
1596
1942
  }
1597
1943
 
@@ -1833,7 +2179,7 @@ async function checkModule(mod, storage2) {
1833
2179
  }
1834
2180
 
1835
2181
  // src/tools/forget.ts
1836
- async function handleForget(args2, engramsPath) {
2182
+ async function handleForget(args2, engramsPath, service) {
1837
2183
  const engrams = loadEngrams(engramsPath);
1838
2184
  if (args2.id) {
1839
2185
  const idx = engrams.findIndex((e) => e.id === args2.id);
@@ -1846,6 +2192,14 @@ async function handleForget(args2, engramsPath) {
1846
2192
  }
1847
2193
  engrams[idx] = { ...engram, status: "retired" };
1848
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
+ }
1849
2203
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
1850
2204
  }
1851
2205
  if (args2.search) {
@@ -1862,6 +2216,14 @@ async function handleForget(args2, engramsPath) {
1862
2216
  const idx = engrams.findIndex((e) => e.id === engram.id);
1863
2217
  engrams[idx] = { ...engram, status: "retired" };
1864
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
+ }
1865
2227
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
1866
2228
  }
1867
2229
  const truncated = allMatches.length > 100;
@@ -1892,14 +2254,14 @@ function findEngram(engramId, engramsPath, packsPath) {
1892
2254
  }
1893
2255
  return null;
1894
2256
  }
1895
- async function handleFeedback(args2, engramsPath, packsPath) {
2257
+ async function handleFeedback(args2, engramsPath, packsPath, service) {
1896
2258
  const pPath = packsPath ?? path13.join(path13.dirname(engramsPath), "packs");
1897
2259
  if (args2.signals && args2.signals.length > 0) {
1898
- return handleBatchFeedback(args2.signals, engramsPath, pPath);
2260
+ return handleBatchFeedback(args2.signals, engramsPath, pPath, service);
1899
2261
  }
1900
- return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath, pPath);
2262
+ return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath, pPath, service);
1901
2263
  }
1902
- async function handleSingleFeedback(engram_id, signal, comment, engramsPath, packsPath) {
2264
+ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, packsPath, service) {
1903
2265
  const found = findEngram(engram_id, engramsPath, packsPath);
1904
2266
  if (!found) {
1905
2267
  return {
@@ -1925,6 +2287,12 @@ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, pac
1925
2287
  } else if (found.source === "pack" && found.packEngrams && found.packEngramsPath) {
1926
2288
  atomicWriteYaml(found.packEngramsPath, { engrams: found.packEngrams });
1927
2289
  }
2290
+ if (service?.isEnabled()) {
2291
+ try {
2292
+ await service.award("feedback_given", { signal });
2293
+ } catch {
2294
+ }
2295
+ }
1928
2296
  return {
1929
2297
  mode: "single",
1930
2298
  success: true,
@@ -1934,7 +2302,7 @@ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, pac
1934
2302
  feedback_signals: { ...found.engram.feedback_signals }
1935
2303
  };
1936
2304
  }
1937
- async function handleBatchFeedback(signals, engramsPath, packsPath) {
2305
+ async function handleBatchFeedback(signals, engramsPath, packsPath, service) {
1938
2306
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1939
2307
  const results = [];
1940
2308
  const summary = { positive: 0, negative: 0, neutral: 0 };
@@ -1982,6 +2350,15 @@ async function handleBatchFeedback(signals, engramsPath, packsPath) {
1982
2350
  for (const [filePath, engrams] of dirtyPackFiles) {
1983
2351
  atomicWriteYaml(filePath, { engrams });
1984
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
+ }
1985
2362
  return {
1986
2363
  mode: "batch",
1987
2364
  results,
@@ -1994,9 +2371,856 @@ async function handleBatchFeedback(signals, engramsPath, packsPath) {
1994
2371
  }
1995
2372
 
1996
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
1997
2382
  import * as fs15 from "fs";
1998
2383
  import * as path14 from "path";
1999
- 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) {
2000
3224
  let engrams = null;
2001
3225
  if (args2.task) {
2002
3226
  const injectResult = await handleInject(
@@ -2008,8 +3232,8 @@ async function handleSessionStart(args2, storage2, bridge) {
2008
3232
  }
2009
3233
  }
2010
3234
  const { date: today } = localDate();
2011
- const journalFile = path14.join(storage2.journalPath, `${today}.md`);
2012
- 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;
2013
3237
  const allEngrams = loadEngrams(storage2.engramsPath);
2014
3238
  const pending_candidates = allEngrams.filter((e) => e.status === "candidate").length;
2015
3239
  const recommendations = [];
@@ -2028,7 +3252,79 @@ async function handleSessionStart(args2, storage2, bridge) {
2028
3252
  });
2029
3253
  const activeCount = allEngrams.filter((e) => e.status === "active").length;
2030
3254
  const guide = activeCount === 0 ? SESSION_GUIDE_FULL : SESSION_GUIDE_SHORT;
2031
- 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 };
2032
3328
  }
2033
3329
  var SESSION_GUIDE_FULL = `## Datacore Quick Start
2034
3330
 
@@ -2057,7 +3353,7 @@ Positive feedback strengthens engrams. Unused ones naturally decay.`;
2057
3353
  var SESSION_GUIDE_SHORT = `Session started. Workflow: work \u2192 feedback \u2192 session.end.`;
2058
3354
 
2059
3355
  // src/tools/session-end.ts
2060
- async function handleSessionEnd(args2, storage2) {
3356
+ async function handleSessionEnd(args2, storage2, engagementService2) {
2061
3357
  const captureResult = await handleCapture(
2062
3358
  { type: "journal", content: args2.summary, tags: args2.tags },
2063
3359
  storage2
@@ -2067,16 +3363,34 @@ async function handleSessionEnd(args2, storage2) {
2067
3363
  for (const suggestion of args2.engram_suggestions) {
2068
3364
  await handleLearn(
2069
3365
  { statement: suggestion.statement, type: suggestion.type },
2070
- storage2.engramsPath
3366
+ storage2.engramsPath,
3367
+ engagementService2
2071
3368
  );
2072
3369
  engramsCreated++;
2073
3370
  }
2074
3371
  }
2075
3372
  const autoPromote = getConfig().engrams.auto_promote;
2076
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
+ }
2077
3390
  return {
2078
3391
  journal_path: captureResult.path ?? null,
2079
3392
  engrams_created: engramsCreated,
3393
+ engagement,
2080
3394
  _hints: buildHints({
2081
3395
  next: engramsCreated > 0 ? `Session captured. ${engramsCreated} engram(s) created as ${statusLabel}.` : "Session captured.",
2082
3396
  related: ["datacore.session.start", "datacore.status"]
@@ -2160,7 +3474,7 @@ async function handleRecall(args2, storage2, bridge) {
2160
3474
  }
2161
3475
 
2162
3476
  // src/tools/promote.ts
2163
- async function handlePromote(args2, engramsPath) {
3477
+ async function handlePromote(args2, engramsPath, service) {
2164
3478
  const targetIds = args2.ids ?? (args2.id ? [args2.id] : []);
2165
3479
  if (targetIds.length === 0) {
2166
3480
  return {
@@ -2199,6 +3513,14 @@ async function handlePromote(args2, engramsPath) {
2199
3513
  }
2200
3514
  if (promoted.length > 0) {
2201
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
+ }
2202
3524
  }
2203
3525
  return {
2204
3526
  success: errors.length === 0,
@@ -2211,14 +3533,169 @@ async function handlePromote(args2, engramsPath) {
2211
3533
  };
2212
3534
  }
2213
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
+
2214
3691
  // src/resources.ts
2215
3692
  import {
2216
3693
  ListResourcesRequestSchema,
2217
3694
  ReadResourceRequestSchema,
2218
3695
  ListResourceTemplatesRequestSchema
2219
3696
  } from "@modelcontextprotocol/sdk/types.js";
2220
- import * as fs16 from "fs";
2221
- import * as path15 from "path";
3697
+ import * as fs19 from "fs";
3698
+ import * as path18 from "path";
2222
3699
  function registerResources(server, storage2) {
2223
3700
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
2224
3701
  resources: [
@@ -2299,11 +3776,11 @@ function registerResources(server, storage2) {
2299
3776
  const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
2300
3777
  if (journalMatch) {
2301
3778
  const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
2302
- const filePath = path15.join(storage2.journalPath, `${dateStr}.md`);
2303
- if (!fs16.existsSync(filePath)) {
3779
+ const filePath = path18.join(storage2.journalPath, `${dateStr}.md`);
3780
+ if (!fs19.existsSync(filePath)) {
2304
3781
  return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
2305
3782
  }
2306
- return { contents: [{ uri, mimeType: "text/markdown", text: fs16.readFileSync(filePath, "utf8") }] };
3783
+ return { contents: [{ uri, mimeType: "text/markdown", text: fs19.readFileSync(filePath, "utf8") }] };
2307
3784
  }
2308
3785
  const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
2309
3786
  if (engramMatch) {
@@ -2549,8 +4026,8 @@ function registerPrompts(server) {
2549
4026
 
2550
4027
  // src/datacortex.ts
2551
4028
  import { execFile } from "child_process";
2552
- import * as fs17 from "fs";
2553
- import * as path16 from "path";
4029
+ import * as fs20 from "fs";
4030
+ import * as path19 from "path";
2554
4031
  var DatacortexBridge = class {
2555
4032
  pythonPath;
2556
4033
  scriptPath;
@@ -2560,11 +4037,11 @@ var DatacortexBridge = class {
2560
4037
  }
2561
4038
  findBridgeScript(datacorePath) {
2562
4039
  const candidates = [
2563
- path16.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
2564
- 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")
2565
4042
  ];
2566
4043
  for (const candidate of candidates) {
2567
- if (fs17.existsSync(candidate)) return candidate;
4044
+ if (fs20.existsSync(candidate)) return candidate;
2568
4045
  }
2569
4046
  return null;
2570
4047
  }
@@ -2624,6 +4101,14 @@ var discoveredModules = [];
2624
4101
  var isFirstRun = false;
2625
4102
  var serverRef = null;
2626
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
+ }
2627
4112
  function createServer() {
2628
4113
  const server = new Server(
2629
4114
  { name: "datacore-mcp", version: currentVersion },
@@ -2672,7 +4157,7 @@ function createServer() {
2672
4157
  serverRef = server;
2673
4158
  return server;
2674
4159
  }
2675
- 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"]);
2676
4161
  async function routeTool(name, args2) {
2677
4162
  const coreTool = TOOLS.find((t) => t.name === name);
2678
4163
  if (coreTool) {
@@ -2683,7 +4168,7 @@ async function routeTool(name, args2) {
2683
4168
  result = await handleCapture(validated, storage);
2684
4169
  break;
2685
4170
  case "datacore.learn":
2686
- result = await handleLearn(validated, storage.engramsPath);
4171
+ result = await handleLearn(validated, storage.engramsPath, getEngagementService());
2687
4172
  break;
2688
4173
  case "datacore.inject":
2689
4174
  result = await handleInject(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath });
@@ -2695,25 +4180,25 @@ async function routeTool(name, args2) {
2695
4180
  result = await handleIngest(validated, { knowledgePath: storage.knowledgePath, engramsPath: storage.engramsPath });
2696
4181
  break;
2697
4182
  case "datacore.status":
2698
- result = await handleStatus({ ...storage, engramsPath: storage.engramsPath, packsPath: storage.packsPath }, updateAvailable);
4183
+ result = await handleStatus({ ...storage, engramsPath: storage.engramsPath, packsPath: storage.packsPath }, updateAvailable, getEngagementService());
2699
4184
  break;
2700
4185
  case "datacore.forget":
2701
- result = await handleForget(validated, storage.engramsPath);
4186
+ result = await handleForget(validated, storage.engramsPath, getEngagementService());
2702
4187
  break;
2703
4188
  case "datacore.feedback":
2704
- result = await handleFeedback(validated, storage.engramsPath, storage.packsPath);
4189
+ result = await handleFeedback(validated, storage.engramsPath, storage.packsPath, getEngagementService());
2705
4190
  break;
2706
4191
  case "datacore.session.start":
2707
- result = await handleSessionStart(validated, storage, datacortexBridge);
4192
+ result = await handleSessionStart(validated, storage, datacortexBridge, getEngagementService());
2708
4193
  break;
2709
4194
  case "datacore.session.end":
2710
- result = await handleSessionEnd(validated, storage);
4195
+ result = await handleSessionEnd(validated, storage, getEngagementService());
2711
4196
  break;
2712
4197
  case "datacore.recall":
2713
4198
  result = await handleRecall(validated, { engramsPath: storage.engramsPath, journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
2714
4199
  break;
2715
4200
  case "datacore.promote":
2716
- result = await handlePromote(validated, storage.engramsPath);
4201
+ result = await handlePromote(validated, storage.engramsPath, getEngagementService());
2717
4202
  break;
2718
4203
  case "datacore.packs.discover":
2719
4204
  result = handleDiscover(validated, storage.packsPath);
@@ -2722,7 +4207,10 @@ async function routeTool(name, args2) {
2722
4207
  result = await handleInstall(validated, storage.packsPath);
2723
4208
  break;
2724
4209
  case "datacore.packs.export":
2725
- 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());
2726
4214
  break;
2727
4215
  case "datacore.modules.list":
2728
4216
  result = await handleModulesList(validated, storage, discoveredModules);