@datacore-one/mcp 1.2.0 → 1.4.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,16 @@ 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)
225
+ }).default({}),
226
+ injection: z.object({
227
+ directive_cap: z.number().default(10),
228
+ consider_cap: z.number().default(5),
229
+ spread_cap: z.number().default(3),
230
+ spread_budget: z.number().default(480)
221
231
  }).default({})
222
232
  });
223
233
  var cachedConfig = null;
@@ -242,7 +252,7 @@ function getConfig() {
242
252
  // package.json
243
253
  var package_default = {
244
254
  name: "@datacore-one/mcp",
245
- version: "1.2.0",
255
+ version: "1.3.0",
246
256
  description: "Datacore MCP server \u2014 The Software of You",
247
257
  type: "module",
248
258
  bin: {
@@ -254,7 +264,8 @@ var package_default = {
254
264
  dev: "tsup --watch",
255
265
  test: "vitest run",
256
266
  "test:watch": "vitest",
257
- start: "node dist/index.js"
267
+ start: "node dist/index.js",
268
+ 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
269
  },
259
270
  dependencies: {
260
271
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -329,7 +340,17 @@ var TOOLS = [
329
340
  scope: z2.string().optional().describe("Scope: global | agent:X | command:X"),
330
341
  tags: z2.array(z2.string()).optional(),
331
342
  domain: z2.string().optional().describe("Dot-notation domain: software.architecture"),
332
- visibility: z2.enum(["private", "public", "template"]).optional()
343
+ visibility: z2.enum(["private", "public", "template"]).optional(),
344
+ knowledge_anchors: z2.array(z2.object({
345
+ path: z2.string().describe("Path to related document (e.g., zettel/Data-Pricing.md)"),
346
+ relevance: z2.enum(["primary", "supporting", "example"]).optional().describe("Anchor relevance level"),
347
+ snippet: z2.string().optional().describe("Short snippet from the document (max 200 chars)"),
348
+ snippet_extracted_at: z2.string().optional().describe("ISO date when snippet was extracted")
349
+ })).optional().describe("Links to related knowledge documents"),
350
+ dual_coding: z2.object({
351
+ example: z2.string().optional().describe("Concrete example illustrating the engram"),
352
+ analogy: z2.string().optional().describe("Analogy to aid understanding")
353
+ }).optional().describe("Dual coding: example and/or analogy for richer encoding")
333
354
  })
334
355
  },
335
356
  {
@@ -338,6 +359,7 @@ var TOOLS = [
338
359
  inputSchema: z2.object({
339
360
  prompt: z2.string().describe("The task or question to match against"),
340
361
  scope: z2.string().optional().describe("Filter by scope: global | agent:X | module:X | command:X"),
362
+ session_id: z2.string().optional().describe("Session ID for co-access tracking (from session.start)"),
341
363
  max_tokens: z2.number().optional().describe("Token budget (default: 8000)"),
342
364
  min_relevance: z2.number().optional().describe("Minimum score threshold (default: 0.3)")
343
365
  })
@@ -429,6 +451,7 @@ var TOOLS = [
429
451
  description: "End a session \u2014 captures journal summary and creates engrams from suggestions. Call before the conversation ends to preserve what was learned.",
430
452
  inputSchema: z2.object({
431
453
  summary: z2.string().describe("Session summary for the journal"),
454
+ session_id: z2.string().optional().describe("Session ID from session.start (for co-access tracking)"),
432
455
  tags: z2.array(z2.string()).optional().describe("Tags for the journal entry"),
433
456
  engram_suggestions: z2.array(z2.object({
434
457
  statement: z2.string().describe("The knowledge assertion"),
@@ -473,6 +496,16 @@ var TOOLS = [
473
496
  inputSchema: z2.object({
474
497
  module: z2.string().optional().describe("Module name (omit for all modules)")
475
498
  })
499
+ },
500
+ {
501
+ name: "datacore.resolve",
502
+ 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.",
503
+ inputSchema: z2.object({
504
+ type: z2.enum(["reconsolidation", "discovery", "challenge"]).describe("Type of event to resolve"),
505
+ id: z2.string().describe("Event ID (engram_id, discovery_id, or challenge_id)"),
506
+ action: z2.string().describe("Resolution action: defend|revise|retire|dismiss (recon), explore|note (discovery), dismiss (challenge)"),
507
+ revised_statement: z2.string().optional().describe('New statement text (required when action is "revise")')
508
+ })
476
509
  }
477
510
  ];
478
511
 
@@ -573,6 +606,25 @@ var KnowledgeTypeSchema = z3.object({
573
606
  memory_class: z3.enum(["semantic", "episodic", "procedural", "metacognitive"]),
574
607
  cognitive_level: z3.enum(["remember", "understand", "apply", "analyze", "evaluate", "create"])
575
608
  });
609
+ var KnowledgeAnchorSchema = z3.object({
610
+ path: z3.string(),
611
+ relevance: z3.enum(["primary", "supporting", "example"]).default("supporting"),
612
+ snippet: z3.string().max(200).optional(),
613
+ snippet_extracted_at: z3.string().optional()
614
+ });
615
+ var AssociationSchema = z3.object({
616
+ target_type: z3.enum(["engram", "document"]),
617
+ target: z3.string(),
618
+ strength: z3.number().min(0).max(0.95),
619
+ type: z3.enum(["semantic", "temporal", "causal", "co_accessed"])
620
+ });
621
+ var DualCodingSchema = z3.object({
622
+ example: z3.string().optional(),
623
+ analogy: z3.string().optional()
624
+ }).refine(
625
+ (d) => d.example || d.analogy,
626
+ "At least one of example or analogy must be provided"
627
+ );
576
628
  var RelationsSchema = z3.object({
577
629
  broader: z3.array(z3.string()).default([]),
578
630
  narrower: z3.array(z3.string()).default([]),
@@ -609,6 +661,9 @@ var EngramSchema = z3.object({
609
661
  activation: ActivationSchema,
610
662
  provenance: ProvenanceSchema.optional(),
611
663
  feedback_signals: FeedbackSignalsSchema.optional(),
664
+ knowledge_anchors: z3.array(KnowledgeAnchorSchema).default([]),
665
+ associations: z3.array(AssociationSchema).default([]),
666
+ dual_coding: DualCodingSchema.optional(),
612
667
  tags: z3.array(z3.string()).default([]),
613
668
  pack: z3.string().nullable().default(null),
614
669
  abstract: z3.string().nullable().default(null),
@@ -769,7 +824,7 @@ function generateEngramId(existingEngrams) {
769
824
  const padWidth = nextSeq > 999 ? String(nextSeq).length : 3;
770
825
  return `${prefix}${String(nextSeq).padStart(padWidth, "0")}`;
771
826
  }
772
- async function handleLearn(args2, engramsPath) {
827
+ async function handleLearn(args2, engramsPath, service) {
773
828
  const engrams = loadEngrams(engramsPath);
774
829
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
775
830
  const autoPromote = getConfig().engrams.auto_promote;
@@ -785,6 +840,14 @@ async function handleLearn(args2, engramsPath) {
785
840
  rationale: args2.rationale,
786
841
  derivation_count: 1,
787
842
  domain: args2.domain,
843
+ knowledge_anchors: args2.knowledge_anchors?.map((a) => ({
844
+ path: a.path,
845
+ relevance: a.relevance ?? "supporting",
846
+ snippet: a.snippet,
847
+ snippet_extracted_at: a.snippet_extracted_at
848
+ })) ?? [],
849
+ associations: [],
850
+ dual_coding: args2.dual_coding?.example || args2.dual_coding?.analogy ? args2.dual_coding : void 0,
788
851
  tags: args2.tags ?? [],
789
852
  activation: {
790
853
  retrieval_strength: autoPromote ? 0.7 : 0.5,
@@ -798,6 +861,29 @@ async function handleLearn(args2, engramsPath) {
798
861
  };
799
862
  engrams.push(engram);
800
863
  saveEngrams(engramsPath, engrams);
864
+ let xp = void 0;
865
+ if (service?.isEnabled()) {
866
+ try {
867
+ const isPublic = engram.visibility === "public" || engram.visibility === "template";
868
+ const actionKey = isPublic ? "engram_created_public" : "engram_created";
869
+ const result = await service.award(actionKey, { visibility: engram.visibility });
870
+ if (result) {
871
+ xp = { earned: result.event.xp_earned, action: actionKey };
872
+ }
873
+ if (engram.domain) {
874
+ const existingDomains = new Set(
875
+ engrams.slice(0, -1).filter((e) => e.domain).map((e) => e.domain)
876
+ );
877
+ if (!existingDomains.has(engram.domain)) {
878
+ const domainResult = await service.award("new_domain", { domain: engram.domain });
879
+ if (domainResult && xp) {
880
+ xp.earned += domainResult.event.xp_earned;
881
+ }
882
+ }
883
+ }
884
+ } catch {
885
+ }
886
+ }
801
887
  const statusLabel = autoPromote ? "active" : "candidate";
802
888
  const hints = autoPromote ? buildHints({
803
889
  next: "Created as active (auto_promote on). Use datacore.inject to retrieve.",
@@ -807,11 +893,12 @@ async function handleLearn(args2, engramsPath) {
807
893
  next: "Created as candidate. Use datacore.promote to activate.",
808
894
  related: ["datacore.promote", "datacore.inject"]
809
895
  });
810
- return { success: true, engram, _hints: hints };
896
+ return { success: true, engram, xp, _hints: hints };
811
897
  }
812
898
 
813
899
  // src/tools/inject-tool.ts
814
900
  import * as fs5 from "fs";
901
+ import * as path4 from "path";
815
902
  import * as yaml3 from "js-yaml";
816
903
 
817
904
  // src/decay.ts
@@ -834,38 +921,81 @@ function engramState(retrievalStrength) {
834
921
  // src/inject.ts
835
922
  var DEFAULT_MAX_TOKENS = 8e3;
836
923
  var DEFAULT_MIN_RELEVANCE = 0.3;
837
- var TOKENS_PER_ENGRAM = 40;
838
924
  var MAX_PER_PACK = 5;
839
925
  var MAX_PER_DOMAIN = 10;
840
- function selectEngrams(ctx, personalEngrams, packs) {
841
- const promptLower = ctx.prompt.toLowerCase();
842
- const promptWords = new Set(promptLower.split(/\W+/).filter((w) => w.length > 2));
843
- const scored = [];
844
- for (const engram of personalEngrams) {
845
- if (engram.status !== "active") continue;
846
- const score = scoreEngram(engram, promptLower, promptWords, [], ctx.scope, false);
847
- if (score > 0) scored.push({ engram, score });
848
- }
849
- for (const pack of packs) {
850
- if (pack.manifest["x-datacore"].injection_policy === "on_request") continue;
851
- const matchTerms = pack.manifest["x-datacore"].match_terms;
852
- for (const engram of pack.engrams) {
853
- if (engram.status !== "active") continue;
854
- const score = scoreEngram(engram, promptLower, promptWords, matchTerms, ctx.scope, true);
855
- if (score > 0) scored.push({ engram, score });
926
+ var DIP19_CONSIDER_MAX = 5;
927
+ var DIP19_CONSIDER_BUDGET = 200;
928
+ var RELEVANCE_RANK = { primary: 0, supporting: 1, example: 2 };
929
+ function estimateTokens(engram) {
930
+ const { keyword_match: _km, raw_score: _rs, score: _s, associations: _a, ...wire } = engram;
931
+ const serialized = JSON.stringify(wire);
932
+ return Math.ceil(serialized.length / 4);
933
+ }
934
+ function tokenize(text) {
935
+ return new Set(text.toLowerCase().split(/\W+/).filter((w) => w.length > 2));
936
+ }
937
+ function anchorBoost(engram, taskWords) {
938
+ if (!engram.knowledge_anchors?.length) return 0;
939
+ const threshold = taskWords.size <= 1 ? 1 : 2;
940
+ let boost = 0;
941
+ for (const anchor of engram.knowledge_anchors) {
942
+ if (!anchor.snippet) continue;
943
+ const snippetWords = tokenize(anchor.snippet);
944
+ let overlap = 0;
945
+ for (const word of taskWords) {
946
+ if (snippetWords.has(word)) overlap++;
856
947
  }
948
+ if (overlap >= threshold) boost += 0.5;
857
949
  }
858
- const maxTokens = ctx.maxTokens ?? DEFAULT_MAX_TOKENS;
859
- const minRelevance = ctx.minRelevance ?? DEFAULT_MIN_RELEVANCE;
860
- const passing = scored.filter((s) => s.score >= minRelevance);
861
- passing.sort((a, b) => b.score - a.score);
862
- const selected = fillTokenBudget(passing, maxTokens);
863
- const splitPoint = Math.ceil(selected.length * 2 / 3);
864
- return {
865
- directives: selected.slice(0, splitPoint),
866
- consider: selected.slice(splitPoint),
867
- tokens_used: selected.length * TOKENS_PER_ENGRAM
950
+ return Math.min(boost, 2);
951
+ }
952
+ function flattenRelations(engram) {
953
+ if (!engram.relations) return [];
954
+ const associations = [];
955
+ for (const id of engram.relations.broader) {
956
+ associations.push({ target_type: "engram", target: id, type: "semantic", strength: 0.5 });
957
+ }
958
+ for (const id of engram.relations.narrower) {
959
+ associations.push({ target_type: "engram", target: id, type: "semantic", strength: 0.5 });
960
+ }
961
+ for (const id of engram.relations.related) {
962
+ associations.push({ target_type: "engram", target: id, type: "semantic", strength: 0.5 });
963
+ }
964
+ return associations;
965
+ }
966
+ function stripAssociations(engram) {
967
+ const { associations: _, ...rest } = engram;
968
+ return rest;
969
+ }
970
+ function stripScoring(engram) {
971
+ const { keyword_match: _, raw_score: _r, score: _s, ...rest } = engram;
972
+ return rest;
973
+ }
974
+ function aggregateAnchors(directives, consider) {
975
+ const seen = /* @__PURE__ */ new Map();
976
+ const processPool = (pool) => {
977
+ for (const engram of pool) {
978
+ if (!engram.knowledge_anchors?.length) continue;
979
+ for (const anchor of engram.knowledge_anchors) {
980
+ const existing = seen.get(anchor.path);
981
+ const rank = RELEVANCE_RANK[anchor.relevance] ?? 2;
982
+ const existingRank = existing ? RELEVANCE_RANK[existing.anchor.relevance] ?? 2 : Infinity;
983
+ if (!existing || rank < existingRank || rank === existingRank && engram.score > existing.engramScore) {
984
+ seen.set(anchor.path, { anchor, engramScore: engram.score });
985
+ }
986
+ }
987
+ }
868
988
  };
989
+ processPool(directives);
990
+ processPool(consider);
991
+ const entries = Array.from(seen.values());
992
+ entries.sort((a, b) => {
993
+ const rankA = RELEVANCE_RANK[a.anchor.relevance] ?? 2;
994
+ const rankB = RELEVANCE_RANK[b.anchor.relevance] ?? 2;
995
+ if (rankA !== rankB) return rankA - rankB;
996
+ return b.engramScore - a.engramScore;
997
+ });
998
+ return entries.slice(0, 10).map((e) => e.anchor);
869
999
  }
870
1000
  function scoreEngram(engram, promptLower, promptWords, packMatchTerms, scopeFilter, isPack) {
871
1001
  if (scopeFilter) {
@@ -908,8 +1038,9 @@ function fillTokenBudget(scored, maxTokens) {
908
1038
  const packCounts = /* @__PURE__ */ new Map();
909
1039
  const domainCounts = /* @__PURE__ */ new Map();
910
1040
  let tokensUsed = 0;
911
- for (const { engram } of scored) {
912
- if (tokensUsed + TOKENS_PER_ENGRAM > maxTokens) break;
1041
+ for (const engram of scored) {
1042
+ const cost = estimateTokens(engram);
1043
+ if (tokensUsed + cost > maxTokens) continue;
913
1044
  const pack = engram.pack ?? "__personal__";
914
1045
  const packCount = packCounts.get(pack) ?? 0;
915
1046
  if (packCount >= MAX_PER_PACK && pack !== "__personal__") continue;
@@ -918,11 +1049,120 @@ function fillTokenBudget(scored, maxTokens) {
918
1049
  const domainCount = domainCounts.get(topDomain) ?? 0;
919
1050
  if (domainCount >= MAX_PER_DOMAIN) continue;
920
1051
  result.push(engram);
921
- tokensUsed += TOKENS_PER_ENGRAM;
1052
+ tokensUsed += cost;
922
1053
  packCounts.set(pack, packCount + 1);
923
1054
  domainCounts.set(topDomain, domainCount + 1);
924
1055
  }
925
- return result;
1056
+ return { selected: result, tokens_used: tokensUsed };
1057
+ }
1058
+ function selectAndSpread(ctx, personalEngrams, packs) {
1059
+ const config = getConfig();
1060
+ const spreadCap = config.injection?.spread_cap ?? 3;
1061
+ const spreadBudget = config.injection?.spread_budget ?? 480;
1062
+ const promptLower = ctx.prompt.toLowerCase();
1063
+ const promptWords = new Set(promptLower.split(/\W+/).filter((w) => w.length > 2));
1064
+ const maxTokens = ctx.maxTokens ?? DEFAULT_MAX_TOKENS;
1065
+ const minRelevance = ctx.minRelevance ?? DEFAULT_MIN_RELEVANCE;
1066
+ const engramMap = /* @__PURE__ */ new Map();
1067
+ const scored = [];
1068
+ for (const engram of personalEngrams) {
1069
+ if (engram.status !== "active") continue;
1070
+ engramMap.set(engram.id, engram);
1071
+ const raw = scoreEngram(engram, promptLower, promptWords, [], ctx.scope, false);
1072
+ if (raw > 0) {
1073
+ scored.push({ ...engram, keyword_match: raw, raw_score: raw, score: raw });
1074
+ }
1075
+ }
1076
+ for (const pack of packs) {
1077
+ if (pack.manifest["x-datacore"].injection_policy === "on_request") continue;
1078
+ const matchTerms = pack.manifest["x-datacore"].match_terms;
1079
+ for (const engram of pack.engrams) {
1080
+ if (engram.status !== "active") continue;
1081
+ engramMap.set(engram.id, engram);
1082
+ const raw = scoreEngram(engram, promptLower, promptWords, matchTerms, ctx.scope, true);
1083
+ if (raw > 0) {
1084
+ scored.push({ ...engram, keyword_match: raw, raw_score: raw, score: raw });
1085
+ }
1086
+ }
1087
+ }
1088
+ const filtered = scored.filter((s) => s.score >= minRelevance);
1089
+ const maxKm = Math.max(...filtered.map((e) => e.keyword_match), 1);
1090
+ for (const e of filtered) {
1091
+ e.keyword_match = e.keyword_match / maxKm * 10;
1092
+ }
1093
+ for (const e of filtered) {
1094
+ const aBoost = anchorBoost(e, promptWords);
1095
+ e.score = e.keyword_match + aBoost;
1096
+ }
1097
+ filtered.sort((a, b) => b.score - a.score);
1098
+ const { selected: directives, tokens_used: directiveTokens } = fillTokenBudget(filtered, maxTokens);
1099
+ const directiveIds = new Set(directives.map((e) => e.id));
1100
+ const directivePackCounts = /* @__PURE__ */ new Map();
1101
+ for (const e of directives) {
1102
+ const pack = e.pack ?? "__personal__";
1103
+ directivePackCounts.set(pack, (directivePackCounts.get(pack) ?? 0) + 1);
1104
+ }
1105
+ const dip19Remainder = filtered.filter((e) => {
1106
+ if (directiveIds.has(e.id)) return false;
1107
+ const pack = e.pack ?? "__personal__";
1108
+ if (pack !== "__personal__" && (directivePackCounts.get(pack) ?? 0) >= MAX_PER_PACK) return false;
1109
+ return true;
1110
+ });
1111
+ const { selected: dip19Consider } = fillTokenBudget(
1112
+ dip19Remainder,
1113
+ DIP19_CONSIDER_BUDGET
1114
+ );
1115
+ const dip19Pool = dip19Consider.slice(0, DIP19_CONSIDER_MAX);
1116
+ const dip19PoolTokens = dip19Pool.reduce((acc, e) => acc + estimateTokens(e), 0);
1117
+ if (directives.length === 0 && dip19Pool.length === 0) {
1118
+ return {
1119
+ directives: [],
1120
+ consider: [],
1121
+ related_documents: [],
1122
+ tokens_used: { directives: 0, consider: 0 }
1123
+ };
1124
+ }
1125
+ const maxFirstPass = Math.max(...directives.map((e) => e.score), 1);
1126
+ const visited = new Set(directives.map((e) => e.id));
1127
+ for (const e of dip19Pool) visited.add(e.id);
1128
+ const spreadCandidates = [];
1129
+ let spreadTokens = 0;
1130
+ for (const directive of directives) {
1131
+ const assocs = directive.associations?.length ? directive.associations : flattenRelations(directive);
1132
+ for (const assoc of assocs) {
1133
+ if (assoc.target_type !== "engram") continue;
1134
+ if (visited.has(assoc.target)) continue;
1135
+ const target = engramMap.get(assoc.target);
1136
+ if (!target || target.status !== "active") continue;
1137
+ const spreadScore = directive.score / maxFirstPass * assoc.strength;
1138
+ if (spreadScore < minRelevance * 0.5) continue;
1139
+ const spreadEngram = {
1140
+ ...target,
1141
+ keyword_match: 0,
1142
+ raw_score: 0,
1143
+ score: spreadScore
1144
+ };
1145
+ const cost = estimateTokens(spreadEngram);
1146
+ if (spreadTokens + cost > spreadBudget) continue;
1147
+ if (spreadCandidates.length >= spreadCap) break;
1148
+ spreadCandidates.push(spreadEngram);
1149
+ spreadTokens += cost;
1150
+ visited.add(assoc.target);
1151
+ }
1152
+ }
1153
+ const allConsider = [...dip19Pool, ...spreadCandidates];
1154
+ const agentDirectives = directives.map(stripAssociations);
1155
+ const agentConsider = allConsider.map(stripAssociations);
1156
+ const relatedDocs = aggregateAnchors(agentDirectives, agentConsider);
1157
+ const wireDirectives = agentDirectives.map(stripScoring);
1158
+ const wireConsider = agentConsider.map(stripScoring);
1159
+ const considerTokens = dip19PoolTokens + spreadTokens;
1160
+ return {
1161
+ directives: wireDirectives,
1162
+ consider: wireConsider,
1163
+ related_documents: relatedDocs,
1164
+ tokens_used: { directives: directiveTokens, consider: considerTokens }
1165
+ };
926
1166
  }
927
1167
 
928
1168
  // src/tools/inject-tool.ts
@@ -932,16 +1172,17 @@ async function handleInject(args2, paths) {
932
1172
  const ctx = {
933
1173
  prompt: args2.prompt,
934
1174
  scope: args2.scope,
1175
+ session_id: args2.session_id,
935
1176
  maxTokens: args2.max_tokens,
936
1177
  minRelevance: args2.min_relevance
937
1178
  };
938
- const result = selectEngrams(ctx, personalEngrams, packs);
1179
+ const result = selectAndSpread(ctx, personalEngrams, packs);
939
1180
  const totalCount = result.directives.length + result.consider.length;
940
1181
  if (totalCount === 0) {
941
1182
  return {
942
1183
  text: "",
943
1184
  count: 0,
944
- tokens_used: 0,
1185
+ tokens_used: { directives: 0, consider: 0 },
945
1186
  _hints: buildHints({
946
1187
  next: "No engrams matched this task. Use datacore.recall to search all sources, or datacore.learn to record new knowledge.",
947
1188
  related: ["datacore.recall", "datacore.learn"]
@@ -961,6 +1202,10 @@ async function handleInject(args2, paths) {
961
1202
  lines.push(formatEngram(e, totalCount));
962
1203
  }
963
1204
  }
1205
+ const relatedDocs = paths.basePath ? result.related_documents.filter((doc) => fs5.existsSync(path4.join(paths.basePath, doc.path))) : result.related_documents;
1206
+ if (relatedDocs.length > 0) {
1207
+ lines.push("\n" + formatRelatedDocs(relatedDocs));
1208
+ }
964
1209
  updateUsageTracking(
965
1210
  paths.engramsPath,
966
1211
  personalEngrams,
@@ -972,6 +1217,7 @@ async function handleInject(args2, paths) {
972
1217
  text: lines.join("\n"),
973
1218
  count: totalCount,
974
1219
  tokens_used: result.tokens_used,
1220
+ related_documents: relatedDocs.length > 0 ? relatedDocs.length : void 0,
975
1221
  _hints: buildHints({
976
1222
  next: `After task, call datacore.feedback on helpful/unhelpful engrams.${idsList}`,
977
1223
  related: ["datacore.feedback", "datacore.session.end"]
@@ -1010,6 +1256,12 @@ function formatEngram(engram, totalCount) {
1010
1256
  if (engram.contraindications?.length) {
1011
1257
  text += `
1012
1258
  Except: ${engram.contraindications.join(", ")}`;
1259
+ }
1260
+ if (engram.dual_coding) {
1261
+ if (engram.dual_coding.example) text += `
1262
+ Example: ${engram.dual_coding.example}`;
1263
+ if (engram.dual_coding.analogy) text += `
1264
+ Analogy: ${engram.dual_coding.analogy}`;
1013
1265
  }
1014
1266
  return text;
1015
1267
  }
@@ -1019,10 +1271,19 @@ function formatEngram(engram, totalCount) {
1019
1271
  }
1020
1272
  return `- ${engram.statement}`;
1021
1273
  }
1274
+ function formatRelatedDocs(docs) {
1275
+ const lines = ["## RELATED DOCUMENTS\n"];
1276
+ for (const doc of docs) {
1277
+ let line = `- [${doc.relevance}] ${doc.path}`;
1278
+ if (doc.snippet) line += ` \u2014 "${doc.snippet}"`;
1279
+ lines.push(line);
1280
+ }
1281
+ return lines.join("\n");
1282
+ }
1022
1283
 
1023
1284
  // src/tools/search.ts
1024
1285
  import * as fs6 from "fs";
1025
- import * as path4 from "path";
1286
+ import * as path5 from "path";
1026
1287
  var CONTENT_CACHE_MAX = 500;
1027
1288
  var contentCache = /* @__PURE__ */ new Map();
1028
1289
  function getCachedContent(filePath) {
@@ -1098,7 +1359,7 @@ function searchDir(dirPath, query) {
1098
1359
  function walkDir(dir) {
1099
1360
  const files = [];
1100
1361
  for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1101
- const fullPath = path4.join(dir, entry.name);
1362
+ const fullPath = path5.join(dir, entry.name);
1102
1363
  if (entry.isDirectory()) files.push(...walkDir(fullPath));
1103
1364
  else files.push(fullPath);
1104
1365
  }
@@ -1126,17 +1387,17 @@ function extractSnippet(content, query) {
1126
1387
  function extractTitle(content, filePath) {
1127
1388
  const match = content.match(/^#\s+(.+)$/m);
1128
1389
  if (match) return match[1].trim();
1129
- return path4.basename(filePath, path4.extname(filePath));
1390
+ return path5.basename(filePath, path5.extname(filePath));
1130
1391
  }
1131
1392
  function extractDate(filePath) {
1132
- const match = path4.basename(filePath).match(/^(\d{4}-\d{2}-\d{2})/);
1393
+ const match = path5.basename(filePath).match(/^(\d{4}-\d{2}-\d{2})/);
1133
1394
  if (match) return match[1];
1134
1395
  return void 0;
1135
1396
  }
1136
1397
 
1137
1398
  // src/tools/ingest.ts
1138
1399
  import * as fs7 from "fs";
1139
- import * as path5 from "path";
1400
+ import * as path6 from "path";
1140
1401
  async function handleIngest(args2, paths) {
1141
1402
  const contentError = validateContent(args2.content);
1142
1403
  if (contentError) return { success: false, error: contentError };
@@ -1147,8 +1408,8 @@ async function handleIngest(args2, paths) {
1147
1408
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
1148
1409
  const slug = (args2.title ?? "ingested").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
1149
1410
  const fileName = `${timestamp}-${slug}.md`;
1150
- const filePath = path5.join(paths.knowledgePath, fileName);
1151
- fs7.mkdirSync(path5.dirname(filePath), { recursive: true });
1411
+ const filePath = path6.join(paths.knowledgePath, fileName);
1412
+ fs7.mkdirSync(path6.dirname(filePath), { recursive: true });
1152
1413
  const frontmatter = `---
1153
1414
  title: "${args2.title ?? "Ingested Note"}"
1154
1415
  created: "${(/* @__PURE__ */ new Date()).toISOString()}"
@@ -1194,18 +1455,18 @@ function extractEngramSuggestions(content) {
1194
1455
 
1195
1456
  // src/tools/status.ts
1196
1457
  import * as fs9 from "fs";
1197
- import * as path7 from "path";
1458
+ import * as path8 from "path";
1198
1459
 
1199
1460
  // src/trust.ts
1200
1461
  import * as fs8 from "fs";
1201
- import * as path6 from "path";
1462
+ import * as path7 from "path";
1202
1463
  import * as crypto from "crypto";
1203
1464
  function computePackChecksum(packDir) {
1204
1465
  const files = ["SKILL.md", "engrams.yaml"];
1205
1466
  const hash = crypto.createHash("sha256");
1206
1467
  let hasContent = false;
1207
1468
  for (const file of files) {
1208
- const filePath = path6.join(packDir, file);
1469
+ const filePath = path7.join(packDir, file);
1209
1470
  if (fs8.existsSync(filePath)) {
1210
1471
  hash.update(fs8.readFileSync(filePath));
1211
1472
  hasContent = true;
@@ -1218,6 +1479,291 @@ function verifyPackChecksum(packDir, expected) {
1218
1479
  return { valid: actual === expected, actual };
1219
1480
  }
1220
1481
 
1482
+ // src/engagement/types.ts
1483
+ import { z as z4 } from "zod";
1484
+ var IdentitySchema = z4.object({
1485
+ mode: z4.enum(["private", "anonymous", "verified"]).default("private"),
1486
+ pseudonym: z4.string().nullable().default(null),
1487
+ erc8004_address: z4.string().nullable().default(null),
1488
+ erc8004_registered: z4.boolean().default(false)
1489
+ });
1490
+ var XPHistoryEntrySchema = z4.object({
1491
+ date: z4.string(),
1492
+ earned: z4.number(),
1493
+ base_earned: z4.number(),
1494
+ multiplier: z4.number(),
1495
+ actions: z4.array(z4.string())
1496
+ });
1497
+ var TierHistoryEntrySchema = z4.object({
1498
+ tier: z4.string(),
1499
+ date: z4.string()
1500
+ });
1501
+ var MultiplierEntrySchema = z4.object({
1502
+ type: z4.string(),
1503
+ factor: z4.number(),
1504
+ since: z4.string()
1505
+ });
1506
+ var ChallengeSchema = z4.object({
1507
+ id: z4.string(),
1508
+ type: z4.string(),
1509
+ tier: z4.string(),
1510
+ description: z4.string(),
1511
+ criteria: z4.object({
1512
+ metric: z4.string(),
1513
+ target_delta: z4.number()
1514
+ }),
1515
+ baseline_stats: z4.record(z4.number()),
1516
+ bonus_xp: z4.number(),
1517
+ started_at: z4.string(),
1518
+ expires_at: z4.string()
1519
+ });
1520
+ var ChallengeHistorySchema = z4.object({
1521
+ type: z4.string(),
1522
+ tier: z4.string(),
1523
+ completed: z4.boolean(),
1524
+ date: z4.string()
1525
+ });
1526
+ var ReconsolidationPendingSchema = z4.object({
1527
+ engram_id: z4.string(),
1528
+ contradicting_id: z4.string(),
1529
+ statement: z4.string(),
1530
+ contradiction: z4.string(),
1531
+ evidence_strength: z4.enum(["weak", "moderate", "strong"]),
1532
+ confidence: z4.number(),
1533
+ detected_at: z4.string(),
1534
+ expires_at: z4.string()
1535
+ });
1536
+ var DiscoverySchema = z4.object({
1537
+ id: z4.string(),
1538
+ engram_a: z4.object({ id: z4.string(), domain: z4.string(), statement: z4.string() }),
1539
+ engram_b: z4.object({ id: z4.string(), domain: z4.string(), statement: z4.string() }),
1540
+ connection: z4.string(),
1541
+ offered_at: z4.string()
1542
+ });
1543
+ var EngagementProfileSchema = z4.object({
1544
+ version: z4.literal(4),
1545
+ identity: IdentitySchema.default({}),
1546
+ xp: z4.object({
1547
+ total: z4.number().default(0),
1548
+ this_week: z4.number().default(0),
1549
+ history: z4.array(XPHistoryEntrySchema).default([])
1550
+ }).default({}),
1551
+ tier: z4.object({
1552
+ current: z4.string().default("Seed"),
1553
+ achieved_at: z4.string().nullable().default(null),
1554
+ history: z4.array(TierHistoryEntrySchema).default([])
1555
+ }).default({}),
1556
+ multipliers: z4.object({
1557
+ active: z4.array(MultiplierEntrySchema).default([]),
1558
+ effective: z4.number().default(1)
1559
+ }).default({}),
1560
+ consistency: z4.object({
1561
+ active_days_30: z4.number().default(0),
1562
+ best_run: z4.number().default(0),
1563
+ last_active: z4.string().nullable().default(null)
1564
+ }).default({}),
1565
+ challenges: z4.object({
1566
+ active: ChallengeSchema.nullable().default(null),
1567
+ completed: z4.number().default(0),
1568
+ dismissed: z4.number().default(0),
1569
+ graduated: z4.boolean().default(false),
1570
+ history: z4.array(ChallengeHistorySchema).default([])
1571
+ }).default({}),
1572
+ reconsolidation: z4.object({
1573
+ pending: z4.array(ReconsolidationPendingSchema).default([]),
1574
+ total_resolved: z4.number().default(0),
1575
+ outcomes: z4.object({
1576
+ defended: z4.number().default(0),
1577
+ revised: z4.number().default(0),
1578
+ retired: z4.number().default(0),
1579
+ dismissed: z4.number().default(0)
1580
+ }).default({}),
1581
+ response_rate: z4.number().default(0)
1582
+ }).default({}),
1583
+ discoveries: z4.object({
1584
+ pending: z4.array(DiscoverySchema).default([]),
1585
+ total: z4.number().default(0),
1586
+ last_offered: z4.string().nullable().default(null),
1587
+ explored: z4.number().default(0),
1588
+ noted: z4.number().default(0),
1589
+ explore_rate: z4.number().default(0)
1590
+ }).default({}),
1591
+ ai_performance: z4.object({
1592
+ total_injections: z4.number().default(0),
1593
+ feedback_count: z4.number().default(0),
1594
+ helpful_ratio: z4.number().default(0),
1595
+ top_engrams: z4.array(z4.object({
1596
+ id: z4.string(),
1597
+ injections: z4.number(),
1598
+ positive_ratio: z4.number()
1599
+ })).default([]),
1600
+ unused_60d: z4.array(z4.string()).default([])
1601
+ }).default({}),
1602
+ reputation: z4.object({
1603
+ score: z4.number().default(0),
1604
+ components: z4.object({
1605
+ feedback_ratio: z4.number().default(0),
1606
+ stake_amount: z4.number().default(0),
1607
+ tenure_days: z4.number().default(0),
1608
+ reconsolidation_honesty: z4.number().default(0)
1609
+ }).default({}),
1610
+ last_calculated: z4.string().nullable().default(null)
1611
+ }).default({}),
1612
+ leaderboard: z4.object({
1613
+ mode: z4.enum(["private", "anonymous", "verified"]).default("private"),
1614
+ display_name: z4.string().nullable().default(null),
1615
+ position: z4.number().nullable().default(null)
1616
+ }).default({}),
1617
+ badge: z4.object({
1618
+ preview_svg: z4.string().nullable().default(null),
1619
+ nft_token_id: z4.string().nullable().default(null),
1620
+ last_generated: z4.string().nullable().default(null)
1621
+ }).default({}),
1622
+ stats: z4.object({
1623
+ total_engrams_created: z4.number().default(0),
1624
+ total_feedback_given: z4.number().default(0),
1625
+ total_engrams_retired: z4.number().default(0),
1626
+ total_packs_exported: z4.number().default(0),
1627
+ total_feedback_received: z4.number().default(0),
1628
+ feedback_positive_ratio: z4.number().default(0),
1629
+ domains_covered: z4.number().default(0),
1630
+ public_engrams: z4.number().default(0),
1631
+ first_activity: z4.string().nullable().default(null)
1632
+ }).default({})
1633
+ });
1634
+ var XPEventSchema = z4.object({
1635
+ action_key: z4.string(),
1636
+ xp_base: z4.number(),
1637
+ multiplier: z4.number(),
1638
+ xp_earned: z4.number(),
1639
+ timestamp: z4.string(),
1640
+ context: z4.record(z4.unknown()).optional()
1641
+ });
1642
+ var XPResultSchema = z4.object({
1643
+ event: XPEventSchema,
1644
+ tier_change: z4.object({
1645
+ from: z4.string(),
1646
+ to: z4.string(),
1647
+ message: z4.string()
1648
+ }).nullable()
1649
+ });
1650
+ var XPActionSchema = z4.object({
1651
+ xp: z4.number(),
1652
+ trigger: z4.string(),
1653
+ condition: z4.string().optional(),
1654
+ daily_limit: z4.number().optional(),
1655
+ cooldown_days: z4.number().optional(),
1656
+ reciprocity_cap: z4.number().optional(),
1657
+ description: z4.string()
1658
+ });
1659
+ var XPActionRegistrySchema = z4.object({
1660
+ version: z4.number(),
1661
+ actions: z4.record(XPActionSchema)
1662
+ });
1663
+ var TIER_THRESHOLDS = [
1664
+ { name: "Seed", minXP: 0 },
1665
+ { name: "Cipher", minXP: 100 },
1666
+ { name: "Sage", minXP: 500 },
1667
+ { name: "Adept", minXP: 1200 },
1668
+ { name: "Visionary", minXP: 2500 },
1669
+ { name: "Oracle", minXP: 5e3 }
1670
+ ];
1671
+
1672
+ // src/engagement/format.ts
1673
+ function formatSessionStart(profile) {
1674
+ const tier = profile.tier.current;
1675
+ const xp = profile.xp.total;
1676
+ const nextTier = TIER_THRESHOLDS.find((t) => t.minXP > xp);
1677
+ const xpToNext = nextTier ? nextTier.minXP - xp : 0;
1678
+ const nextLabel = nextTier ? ` \u2192 ${nextTier.name} in ${xpToNext} XP` : " (max tier)";
1679
+ const multiplierLabel = profile.multipliers.effective > 1 ? ` [${profile.multipliers.effective}x ${profile.multipliers.active.map((m) => m.type).join(", ")}]` : "";
1680
+ const lines = [];
1681
+ lines.push(`Your Datacore: ${tier} (${xp.toLocaleString()} XP${nextLabel})${multiplierLabel}`);
1682
+ if (profile.ai_performance.feedback_count > 0) {
1683
+ const helpful = Math.round(profile.ai_performance.helpful_ratio * 100);
1684
+ lines.push(` AI surfaced ${profile.ai_performance.total_injections} insights this week (${helpful}% helpful)`);
1685
+ }
1686
+ const activeDays = profile.consistency.active_days_30;
1687
+ lines.push(` Active ${activeDays}/30 days`);
1688
+ return lines.join("\n");
1689
+ }
1690
+ function formatSessionEnd(profile, sessionXP, events) {
1691
+ if (sessionXP === 0) return "Session complete \u2014 no XP earned this session.";
1692
+ const multiplierLabel = profile.multipliers.effective > 1 ? ` (\xD7${profile.multipliers.effective} ${profile.multipliers.active.map((m) => m.type).join(", ")})` : "";
1693
+ const domainActions = events.filter((e) => e.context?.domain).map((e) => e.context.domain);
1694
+ const domainNote = domainActions.length > 0 ? ` | Your ${domainActions[0]} domain deepened` : "";
1695
+ const lines = [];
1696
+ lines.push(`Session: +${sessionXP} XP${multiplierLabel}${domainNote}`);
1697
+ const candidates = 0;
1698
+ if (candidates > 0) {
1699
+ lines.push(`Tomorrow: ${candidates} candidate(s) ready for review`);
1700
+ }
1701
+ return lines.join("\n");
1702
+ }
1703
+ function formatStatus(profile) {
1704
+ const lines = [];
1705
+ lines.push("## Engagement Dashboard");
1706
+ lines.push("");
1707
+ const nextTier = TIER_THRESHOLDS.find((t) => t.minXP > profile.xp.total);
1708
+ const progress = nextTier ? `${profile.xp.total}/${nextTier.minXP} XP (${Math.round(profile.xp.total / nextTier.minXP * 100)}%)` : `${profile.xp.total} XP (max tier)`;
1709
+ lines.push(`**Tier:** ${profile.tier.current} \u2014 ${progress}`);
1710
+ lines.push(`**This week:** ${profile.xp.this_week} XP`);
1711
+ if (profile.multipliers.active.length > 0) {
1712
+ const mults = profile.multipliers.active.map((m) => `${m.type} (${m.factor}x)`).join(", ");
1713
+ lines.push(`**Multipliers:** ${mults} = ${profile.multipliers.effective}x`);
1714
+ }
1715
+ lines.push(`**Consistency:** ${profile.consistency.active_days_30}/30 days active, best run: ${profile.consistency.best_run} days`);
1716
+ lines.push("");
1717
+ lines.push("**Stats:**");
1718
+ lines.push(`- Engrams created: ${profile.stats.total_engrams_created}`);
1719
+ lines.push(`- Feedback given: ${profile.stats.total_feedback_given}`);
1720
+ lines.push(`- Domains covered: ${profile.stats.domains_covered}`);
1721
+ lines.push(`- Packs exported: ${profile.stats.total_packs_exported}`);
1722
+ if (profile.challenges.active) {
1723
+ lines.push("");
1724
+ lines.push(`**Active Challenge:** ${profile.challenges.active.description}`);
1725
+ lines.push(` Expires: ${profile.challenges.active.expires_at}`);
1726
+ }
1727
+ if (profile.reconsolidation.pending.length > 0) {
1728
+ lines.push("");
1729
+ lines.push(`**Pending Contradictions:** ${profile.reconsolidation.pending.length}`);
1730
+ }
1731
+ if (profile.reputation.score > 0) {
1732
+ lines.push("");
1733
+ lines.push(`**Reputation:** ${profile.reputation.score.toFixed(2)}`);
1734
+ }
1735
+ return lines.join("\n");
1736
+ }
1737
+ function formatReconsolidation(recon) {
1738
+ const lines = [];
1739
+ lines.push("**Contradiction Detected:**");
1740
+ lines.push(` Existing: "${recon.statement}"`);
1741
+ lines.push(` New: "${recon.contradiction}"`);
1742
+ lines.push(` Evidence: ${recon.evidence_strength}`);
1743
+ lines.push("");
1744
+ lines.push(" Actions: [Defend] [Revise] [Retire] [Dismiss]");
1745
+ lines.push(` \u2192 Use datacore.resolve with type="reconsolidation", id="${recon.engram_id}"`);
1746
+ return lines.join("\n");
1747
+ }
1748
+ function formatDiscovery(disc) {
1749
+ const lines = [];
1750
+ lines.push("**Cross-Domain Discovery:**");
1751
+ lines.push(` "${disc.engram_a.statement}" (${disc.engram_a.domain})`);
1752
+ lines.push(` \u2194 "${disc.engram_b.statement}" (${disc.engram_b.domain})`);
1753
+ lines.push(` Connection: ${disc.connection}`);
1754
+ lines.push("");
1755
+ lines.push(" Actions: [Explore +20 XP] [Note]");
1756
+ lines.push(` \u2192 Use datacore.resolve with type="discovery", id="${disc.id}"`);
1757
+ return lines.join("\n");
1758
+ }
1759
+ function formatChallenge(challenge) {
1760
+ const lines = [];
1761
+ lines.push(`**Weekly Challenge:** ${challenge.description}`);
1762
+ lines.push(` Bonus: +${challenge.bonus_xp} XP | Expires: ${challenge.expires_at}`);
1763
+ lines.push(` \u2192 Dismiss: datacore.resolve with type="challenge", id="${challenge.id}", action="dismiss"`);
1764
+ return lines.join("\n");
1765
+ }
1766
+
1221
1767
  // registry/packs.json
1222
1768
  var packs_default = {
1223
1769
  version: 1,
@@ -1256,13 +1802,13 @@ var packs_default = {
1256
1802
  download_url: "",
1257
1803
  engram_count: 747,
1258
1804
  free: true,
1259
- checksum: "08185777c38e3a91fbb0f92e7ef388c5940e97e69be6dcce8ce119bd2b33968c"
1805
+ checksum: "baa1010298515c1906e892f6f8c2198e5fdf78383dceb57373bc8d53c4c31921"
1260
1806
  }
1261
1807
  ]
1262
1808
  };
1263
1809
 
1264
1810
  // src/tools/status.ts
1265
- async function handleStatus(paths, updateAvailable2) {
1811
+ async function handleStatus(paths, updateAvailable2, engagementService2) {
1266
1812
  const engrams = loadEngrams(paths.engramsPath);
1267
1813
  const journalCount = countFiles(paths.journalPath, ".md");
1268
1814
  const knowledgeCount = countFiles(paths.knowledgePath, ".md");
@@ -1277,7 +1823,7 @@ async function handleStatus(paths, updateAvailable2) {
1277
1823
  const packIntegrity = [];
1278
1824
  for (const regPack of packs_default.packs) {
1279
1825
  if (!regPack.checksum) continue;
1280
- const packDir = path7.join(paths.packsPath, regPack.id);
1826
+ const packDir = path8.join(paths.packsPath, regPack.id);
1281
1827
  if (!fs9.existsSync(packDir)) continue;
1282
1828
  const result = verifyPackChecksum(packDir, regPack.checksum);
1283
1829
  packIntegrity.push({ name: regPack.id, valid: result.valid });
@@ -1294,13 +1840,29 @@ async function handleStatus(paths, updateAvailable2) {
1294
1840
  recommendations.push(`${candidateCount} candidate engrams awaiting review. Use datacore.promote or enable engrams.auto_promote in config.yaml.`);
1295
1841
  }
1296
1842
  const { date: today } = localDate();
1297
- const todayJournal = path7.join(paths.journalPath, `${today}.md`);
1843
+ const todayJournal = path8.join(paths.journalPath, `${today}.md`);
1298
1844
  if (!fs9.existsSync(todayJournal)) {
1299
1845
  recommendations.push("No journal entry today. Use datacore.capture to start one.");
1300
1846
  }
1301
1847
  if (updateAvailable2) {
1302
1848
  recommendations.push(`Update available: ${updateAvailable2}. Run: npm update -g @datacore-one/mcp`);
1303
1849
  }
1850
+ let engagement = void 0;
1851
+ if (engagementService2?.isEnabled()) {
1852
+ try {
1853
+ await engagementService2.init();
1854
+ const profile = engagementService2.getProfile();
1855
+ if (profile) {
1856
+ engagement = {
1857
+ display: formatStatus(profile),
1858
+ tier: profile.tier.current,
1859
+ xp: profile.xp.total,
1860
+ reputation: profile.reputation.score
1861
+ };
1862
+ }
1863
+ } catch {
1864
+ }
1865
+ }
1304
1866
  const statusResult = {
1305
1867
  version: currentVersion,
1306
1868
  mode: paths.mode,
@@ -1310,6 +1872,7 @@ async function handleStatus(paths, updateAvailable2) {
1310
1872
  pack_integrity: packIntegrity.length > 0 ? packIntegrity : void 0,
1311
1873
  journal_entries: journalCount,
1312
1874
  knowledge_notes: knowledgeCount,
1875
+ engagement,
1313
1876
  _recommendations: recommendations.length > 0 ? recommendations : void 0,
1314
1877
  _hints: buildHints({
1315
1878
  next: recommendations.length > 0 ? recommendations[0] : "System healthy. Use datacore.session.start to begin working.",
@@ -1328,7 +1891,7 @@ function countFiles(dir, ext) {
1328
1891
  if (!fs9.existsSync(dir)) return 0;
1329
1892
  let count = 0;
1330
1893
  for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
1331
- const fullPath = path7.join(dir, entry.name);
1894
+ const fullPath = path8.join(dir, entry.name);
1332
1895
  if (entry.isDirectory()) count += countFiles(fullPath, ext);
1333
1896
  else if (entry.name.endsWith(ext)) count++;
1334
1897
  }
@@ -1341,26 +1904,26 @@ function countDirs(dir) {
1341
1904
 
1342
1905
  // src/tools/discover.ts
1343
1906
  import * as fs10 from "fs";
1344
- import * as path8 from "path";
1907
+ import * as path9 from "path";
1345
1908
  function handleDiscover(args2, packsDir) {
1346
- const bundledDir = path8.join(
1347
- path8.dirname(new URL(import.meta.url).pathname),
1909
+ const bundledDir = path9.join(
1910
+ path9.dirname(new URL(import.meta.url).pathname),
1348
1911
  "..",
1349
1912
  "packs"
1350
1913
  );
1351
1914
  let packs = packs_default.packs.map((p) => {
1352
- const localDir = path8.join(packsDir, p.id);
1353
- const installed = fs10.existsSync(path8.join(localDir, "SKILL.md"));
1915
+ const localDir = path9.join(packsDir, p.id);
1916
+ const installed = fs10.existsSync(path9.join(localDir, "SKILL.md"));
1354
1917
  let installedVersion;
1355
1918
  if (installed) {
1356
1919
  try {
1357
- const content = fs10.readFileSync(path8.join(localDir, "SKILL.md"), "utf8");
1920
+ const content = fs10.readFileSync(path9.join(localDir, "SKILL.md"), "utf8");
1358
1921
  const match = content.match(/version:\s*["']?([^"'\n]+)/);
1359
1922
  installedVersion = match?.[1];
1360
1923
  } catch {
1361
1924
  }
1362
1925
  }
1363
- const bundled = fs10.existsSync(path8.join(bundledDir, p.id, "SKILL.md"));
1926
+ const bundled = fs10.existsSync(path9.join(bundledDir, p.id, "SKILL.md"));
1364
1927
  return {
1365
1928
  ...p,
1366
1929
  installed,
@@ -1392,7 +1955,7 @@ function handleDiscover(args2, packsDir) {
1392
1955
 
1393
1956
  // src/tools/install.ts
1394
1957
  import * as fs11 from "fs";
1395
- import * as path9 from "path";
1958
+ import * as path10 from "path";
1396
1959
  import * as yaml4 from "js-yaml";
1397
1960
  import * as os2 from "os";
1398
1961
  import { execSync } from "child_process";
@@ -1407,7 +1970,7 @@ async function handleInstall(args2, packsDir) {
1407
1970
  if (resolved.error) return { success: false, error: resolved.error };
1408
1971
  srcDir = resolved.path;
1409
1972
  }
1410
- const skillPath = path9.join(srcDir, "SKILL.md");
1973
+ const skillPath = path10.join(srcDir, "SKILL.md");
1411
1974
  if (!fs11.existsSync(skillPath)) {
1412
1975
  return { success: false, error: "No SKILL.md found in source directory" };
1413
1976
  }
@@ -1422,9 +1985,9 @@ async function handleInstall(args2, packsDir) {
1422
1985
  if (!packId) {
1423
1986
  return { success: false, error: "Missing x-datacore.id in SKILL.md frontmatter" };
1424
1987
  }
1425
- const destDir = path9.join(packsDir, packId);
1426
- if (fs11.existsSync(path9.join(destDir, "SKILL.md"))) {
1427
- const existingContent = fs11.readFileSync(path9.join(destDir, "SKILL.md"), "utf8");
1988
+ const destDir = path10.join(packsDir, packId);
1989
+ if (fs11.existsSync(path10.join(destDir, "SKILL.md"))) {
1990
+ const existingContent = fs11.readFileSync(path10.join(destDir, "SKILL.md"), "utf8");
1428
1991
  const existingMatch = existingContent.match(/version:\s*["']?([^"'\n]+)/);
1429
1992
  const existingVersion = existingMatch?.[1];
1430
1993
  if (existingVersion === newVersion) {
@@ -1445,14 +2008,14 @@ function verifyInstalledChecksum(packId, destDir) {
1445
2008
  return result.valid;
1446
2009
  }
1447
2010
  async function downloadPack(url) {
1448
- const tmpDir = fs11.mkdtempSync(path9.join(os2.tmpdir(), "datacore-pack-"));
2011
+ const tmpDir = fs11.mkdtempSync(path10.join(os2.tmpdir(), "datacore-pack-"));
1449
2012
  try {
1450
2013
  const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
1451
2014
  if (!res.ok) return { error: `Download failed: HTTP ${res.status}` };
1452
2015
  const buffer = Buffer.from(await res.arrayBuffer());
1453
- const archivePath = path9.join(tmpDir, "pack.tar.gz");
2016
+ const archivePath = path10.join(tmpDir, "pack.tar.gz");
1454
2017
  fs11.writeFileSync(archivePath, buffer);
1455
- const extractDir = path9.join(tmpDir, "extracted");
2018
+ const extractDir = path10.join(tmpDir, "extracted");
1456
2019
  fs11.mkdirSync(extractDir);
1457
2020
  execSync(`tar xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(extractDir)}`, { timeout: 1e4 });
1458
2021
  const packRoot = findPackRoot(extractDir);
@@ -1463,10 +2026,10 @@ async function downloadPack(url) {
1463
2026
  }
1464
2027
  }
1465
2028
  function findPackRoot(dir) {
1466
- if (fs11.existsSync(path9.join(dir, "SKILL.md"))) return dir;
2029
+ if (fs11.existsSync(path10.join(dir, "SKILL.md"))) return dir;
1467
2030
  for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
1468
2031
  if (entry.isDirectory()) {
1469
- const found = findPackRoot(path9.join(dir, entry.name));
2032
+ const found = findPackRoot(path10.join(dir, entry.name));
1470
2033
  if (found) return found;
1471
2034
  }
1472
2035
  }
@@ -1480,17 +2043,17 @@ function resolvePackId(packId, packsDir) {
1480
2043
  if (registryPack.download_url) {
1481
2044
  return { error: `Pack "${packId}" must be installed via URL: ${registryPack.download_url}` };
1482
2045
  }
1483
- const bundledDir = path9.join(
1484
- path9.dirname(new URL(import.meta.url).pathname),
2046
+ const bundledDir = path10.join(
2047
+ path10.dirname(new URL(import.meta.url).pathname),
1485
2048
  "..",
1486
2049
  "packs",
1487
2050
  packId
1488
2051
  );
1489
- if (fs11.existsSync(path9.join(bundledDir, "SKILL.md"))) {
2052
+ if (fs11.existsSync(path10.join(bundledDir, "SKILL.md"))) {
1490
2053
  return { path: bundledDir };
1491
2054
  }
1492
- const localDir = path9.join(packsDir, packId);
1493
- if (fs11.existsSync(path9.join(localDir, "SKILL.md"))) {
2055
+ const localDir = path10.join(packsDir, packId);
2056
+ if (fs11.existsSync(path10.join(localDir, "SKILL.md"))) {
1494
2057
  return { path: localDir };
1495
2058
  }
1496
2059
  return { error: `Pack "${packId}" is registered but not available locally. It may need to be downloaded manually.` };
@@ -1498,9 +2061,9 @@ function resolvePackId(packId, packsDir) {
1498
2061
 
1499
2062
  // src/tools/export.ts
1500
2063
  import * as fs12 from "fs";
1501
- import * as path10 from "path";
2064
+ import * as path11 from "path";
1502
2065
  import * as yaml5 from "js-yaml";
1503
- async function handleExport(args2, paths) {
2066
+ async function handleExport(args2, paths, service) {
1504
2067
  const allEngrams = loadEngrams(paths.engramsPath);
1505
2068
  let selected = allEngrams.filter((e) => e.status === "active");
1506
2069
  selected = selected.filter((e) => e.visibility === "public" || e.visibility === "template");
@@ -1531,7 +2094,7 @@ async function handleExport(args2, paths) {
1531
2094
  return { success: false, error: "No engrams match the filter criteria" };
1532
2095
  }
1533
2096
  const packId = args2.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
1534
- const packDir = path10.join(paths.packsPath, packId);
2097
+ const packDir = path11.join(paths.packsPath, packId);
1535
2098
  if (!args2.confirm) {
1536
2099
  return {
1537
2100
  success: true,
@@ -1567,7 +2130,7 @@ ${args2.description}
1567
2130
 
1568
2131
  Exported ${selected.length} engrams.
1569
2132
  `;
1570
- fs12.writeFileSync(path10.join(packDir, "SKILL.md"), skillContent);
2133
+ fs12.writeFileSync(path11.join(packDir, "SKILL.md"), skillContent);
1571
2134
  const exportEngrams = selected.map((e) => ({
1572
2135
  id: e.id,
1573
2136
  version: e.version,
@@ -1589,26 +2152,32 @@ Exported ${selected.length} engrams.
1589
2152
  feedback_signals: { positive: 0, negative: 0 }
1590
2153
  }));
1591
2154
  fs12.writeFileSync(
1592
- path10.join(packDir, "engrams.yaml"),
2155
+ path11.join(packDir, "engrams.yaml"),
1593
2156
  yaml5.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
1594
2157
  );
2158
+ if (service?.isEnabled() && selected.length >= 5) {
2159
+ try {
2160
+ await service.award("pack_exported", { engram_count: selected.length, avg_fitness: 0.7 });
2161
+ } catch {
2162
+ }
2163
+ }
1595
2164
  return { success: true, pack_path: packDir };
1596
2165
  }
1597
2166
 
1598
2167
  // src/modules.ts
1599
2168
  import * as fs13 from "fs";
1600
- import * as path11 from "path";
2169
+ import * as path12 from "path";
1601
2170
  import * as yaml6 from "js-yaml";
1602
2171
  function discoverModules(storage2) {
1603
2172
  const modules = [];
1604
2173
  if (storage2.mode !== "full") return modules;
1605
- const globalModulesDir = path11.join(storage2.basePath, ".datacore", "modules");
2174
+ const globalModulesDir = path12.join(storage2.basePath, ".datacore", "modules");
1606
2175
  modules.push(...scanModulesDir(globalModulesDir, "global"));
1607
2176
  try {
1608
2177
  const entries = fs13.readdirSync(storage2.basePath);
1609
2178
  for (const entry of entries) {
1610
2179
  if (/^\d+-/.test(entry)) {
1611
- const spaceModulesDir = path11.join(storage2.basePath, entry, ".datacore", "modules");
2180
+ const spaceModulesDir = path12.join(storage2.basePath, entry, ".datacore", "modules");
1612
2181
  modules.push(...scanModulesDir(spaceModulesDir, "space", entry));
1613
2182
  }
1614
2183
  }
@@ -1622,8 +2191,8 @@ function scanModulesDir(modulesDir, scope, spaceName) {
1622
2191
  try {
1623
2192
  const entries = fs13.readdirSync(modulesDir);
1624
2193
  for (const entry of entries) {
1625
- const modulePath = path11.join(modulesDir, entry);
1626
- const manifestPath = path11.join(modulePath, "module.yaml");
2194
+ const modulePath = path12.join(modulesDir, entry);
2195
+ const manifestPath = path12.join(modulePath, "module.yaml");
1627
2196
  if (!fs13.existsSync(manifestPath)) continue;
1628
2197
  try {
1629
2198
  const raw = fs13.readFileSync(manifestPath, "utf-8");
@@ -1648,12 +2217,12 @@ async function loadModuleTools(modules, storage2) {
1648
2217
  for (const mod of modules) {
1649
2218
  const declaredTools = mod.manifest.provides?.tools;
1650
2219
  if (!declaredTools || declaredTools.length === 0) continue;
1651
- const toolsIndexPath = path11.join(mod.modulePath, "tools", "index.js");
2220
+ const toolsIndexPath = path12.join(mod.modulePath, "tools", "index.js");
1652
2221
  if (!fs13.existsSync(toolsIndexPath)) continue;
1653
2222
  try {
1654
2223
  const toolsModule = await import(toolsIndexPath);
1655
2224
  const moduleTools2 = toolsModule.tools || toolsModule.default?.tools || [];
1656
- const dataPath = mod.scope === "space" && mod.spaceName ? path11.join(storage2.basePath, mod.spaceName, ".datacore", "modules", mod.name, "data") : path11.join(storage2.basePath, "0-personal", ".datacore", "modules", mod.name, "data");
2225
+ const dataPath = mod.scope === "space" && mod.spaceName ? path12.join(storage2.basePath, mod.spaceName, ".datacore", "modules", mod.name, "data") : path12.join(storage2.basePath, "0-personal", ".datacore", "modules", mod.name, "data");
1657
2226
  const context = {
1658
2227
  storage: storage2,
1659
2228
  modulePath: mod.modulePath,
@@ -1750,7 +2319,7 @@ async function handleModulesInfo(args2, storage2, cachedModules) {
1750
2319
 
1751
2320
  // src/tools/modules-health.ts
1752
2321
  import * as fs14 from "fs";
1753
- import * as path12 from "path";
2322
+ import * as path13 from "path";
1754
2323
  async function handleModulesHealth(args2, storage2, cachedModules) {
1755
2324
  const modules = cachedModules ?? discoverModules(storage2);
1756
2325
  if (args2.module) {
@@ -1772,10 +2341,10 @@ async function handleModulesHealth(args2, storage2, cachedModules) {
1772
2341
  async function checkModule(mod, storage2) {
1773
2342
  const issues = [];
1774
2343
  const manifest = mod.manifest;
1775
- if (!fs14.existsSync(path12.join(mod.modulePath, "SKILL.md"))) {
2344
+ if (!fs14.existsSync(path13.join(mod.modulePath, "SKILL.md"))) {
1776
2345
  issues.push("Missing SKILL.md (ecosystem entry point)");
1777
2346
  }
1778
- if (!fs14.existsSync(path12.join(mod.modulePath, "CLAUDE.base.md"))) {
2347
+ if (!fs14.existsSync(path13.join(mod.modulePath, "CLAUDE.base.md"))) {
1779
2348
  issues.push("Missing CLAUDE.base.md (AI context)");
1780
2349
  }
1781
2350
  if (!manifest.manifest_version || manifest.manifest_version < 2) {
@@ -1791,7 +2360,7 @@ async function checkModule(mod, storage2) {
1791
2360
  const provides = manifest.provides;
1792
2361
  const declaredTools = provides?.tools || [];
1793
2362
  if (declaredTools.length > 0) {
1794
- const toolsIndex = path12.join(mod.modulePath, "tools", "index.js");
2363
+ const toolsIndex = path13.join(mod.modulePath, "tools", "index.js");
1795
2364
  if (!fs14.existsSync(toolsIndex)) {
1796
2365
  issues.push(`Declares ${declaredTools.length} tools but tools/index.js not found`);
1797
2366
  } else {
@@ -1811,7 +2380,7 @@ async function checkModule(mod, storage2) {
1811
2380
  const suspectExts = [".db", ".sqlite", ".json"];
1812
2381
  const suspectDirs = ["output", "data", "state"];
1813
2382
  for (const dir of suspectDirs) {
1814
- const fullPath = path12.join(mod.modulePath, dir);
2383
+ const fullPath = path13.join(mod.modulePath, dir);
1815
2384
  if (fs14.existsSync(fullPath) && fs14.statSync(fullPath).isDirectory()) {
1816
2385
  issues.push(`Data dir '${dir}/' found in module code (should be in space data path)`);
1817
2386
  }
@@ -1833,7 +2402,7 @@ async function checkModule(mod, storage2) {
1833
2402
  }
1834
2403
 
1835
2404
  // src/tools/forget.ts
1836
- async function handleForget(args2, engramsPath) {
2405
+ async function handleForget(args2, engramsPath, service) {
1837
2406
  const engrams = loadEngrams(engramsPath);
1838
2407
  if (args2.id) {
1839
2408
  const idx = engrams.findIndex((e) => e.id === args2.id);
@@ -1846,6 +2415,14 @@ async function handleForget(args2, engramsPath) {
1846
2415
  }
1847
2416
  engrams[idx] = { ...engram, status: "retired" };
1848
2417
  saveEngrams(engramsPath, engrams);
2418
+ if (service?.isEnabled()) {
2419
+ try {
2420
+ const created = engram.activation.last_accessed;
2421
+ const ageDays = Math.floor((Date.now() - new Date(created).getTime()) / 864e5);
2422
+ await service.award("engram_retired", { engram_age_days: ageDays });
2423
+ } catch {
2424
+ }
2425
+ }
1849
2426
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
1850
2427
  }
1851
2428
  if (args2.search) {
@@ -1862,6 +2439,14 @@ async function handleForget(args2, engramsPath) {
1862
2439
  const idx = engrams.findIndex((e) => e.id === engram.id);
1863
2440
  engrams[idx] = { ...engram, status: "retired" };
1864
2441
  saveEngrams(engramsPath, engrams);
2442
+ if (service?.isEnabled()) {
2443
+ try {
2444
+ const created = engram.activation.last_accessed;
2445
+ const ageDays = Math.floor((Date.now() - new Date(created).getTime()) / 864e5);
2446
+ await service.award("engram_retired", { engram_age_days: ageDays });
2447
+ } catch {
2448
+ }
2449
+ }
1865
2450
  return { success: true, retired: { id: engram.id, statement: engram.statement } };
1866
2451
  }
1867
2452
  const truncated = allMatches.length > 100;
@@ -1876,7 +2461,7 @@ async function handleForget(args2, engramsPath) {
1876
2461
  }
1877
2462
 
1878
2463
  // src/tools/feedback.ts
1879
- import * as path13 from "path";
2464
+ import * as path14 from "path";
1880
2465
  function findEngram(engramId, engramsPath, packsPath) {
1881
2466
  const personal = loadEngrams(engramsPath);
1882
2467
  const found = personal.find((e) => e.id === engramId);
@@ -1886,20 +2471,20 @@ function findEngram(engramId, engramsPath, packsPath) {
1886
2471
  const packEngram = pack.engrams.find((e) => e.id === engramId);
1887
2472
  if (packEngram) {
1888
2473
  const packId = pack.manifest["x-datacore"]?.id;
1889
- const packEngramsPath = packId ? path13.join(packsPath, packId, "engrams.yaml") : void 0;
2474
+ const packEngramsPath = packId ? path14.join(packsPath, packId, "engrams.yaml") : void 0;
1890
2475
  return { engram: packEngram, source: "pack", packEngrams: pack.engrams, packEngramsPath };
1891
2476
  }
1892
2477
  }
1893
2478
  return null;
1894
2479
  }
1895
- async function handleFeedback(args2, engramsPath, packsPath) {
1896
- const pPath = packsPath ?? path13.join(path13.dirname(engramsPath), "packs");
2480
+ async function handleFeedback(args2, engramsPath, packsPath, service) {
2481
+ const pPath = packsPath ?? path14.join(path14.dirname(engramsPath), "packs");
1897
2482
  if (args2.signals && args2.signals.length > 0) {
1898
- return handleBatchFeedback(args2.signals, engramsPath, pPath);
2483
+ return handleBatchFeedback(args2.signals, engramsPath, pPath, service);
1899
2484
  }
1900
- return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath, pPath);
2485
+ return handleSingleFeedback(args2.engram_id, args2.signal, args2.comment, engramsPath, pPath, service);
1901
2486
  }
1902
- async function handleSingleFeedback(engram_id, signal, comment, engramsPath, packsPath) {
2487
+ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, packsPath, service) {
1903
2488
  const found = findEngram(engram_id, engramsPath, packsPath);
1904
2489
  if (!found) {
1905
2490
  return {
@@ -1925,6 +2510,12 @@ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, pac
1925
2510
  } else if (found.source === "pack" && found.packEngrams && found.packEngramsPath) {
1926
2511
  atomicWriteYaml(found.packEngramsPath, { engrams: found.packEngrams });
1927
2512
  }
2513
+ if (service?.isEnabled()) {
2514
+ try {
2515
+ await service.award("feedback_given", { signal });
2516
+ } catch {
2517
+ }
2518
+ }
1928
2519
  return {
1929
2520
  mode: "single",
1930
2521
  success: true,
@@ -1934,7 +2525,7 @@ async function handleSingleFeedback(engram_id, signal, comment, engramsPath, pac
1934
2525
  feedback_signals: { ...found.engram.feedback_signals }
1935
2526
  };
1936
2527
  }
1937
- async function handleBatchFeedback(signals, engramsPath, packsPath) {
2528
+ async function handleBatchFeedback(signals, engramsPath, packsPath, service) {
1938
2529
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1939
2530
  const results = [];
1940
2531
  const summary = { positive: 0, negative: 0, neutral: 0 };
@@ -1955,7 +2546,7 @@ async function handleBatchFeedback(signals, engramsPath, packsPath) {
1955
2546
  const packId = pack.manifest["x-datacore"]?.id;
1956
2547
  if (packId) {
1957
2548
  dirtyPackFiles.set(
1958
- path13.join(packsPath, packId, "engrams.yaml"),
2549
+ path14.join(packsPath, packId, "engrams.yaml"),
1959
2550
  pack.engrams
1960
2551
  );
1961
2552
  }
@@ -1982,6 +2573,15 @@ async function handleBatchFeedback(signals, engramsPath, packsPath) {
1982
2573
  for (const [filePath, engrams] of dirtyPackFiles) {
1983
2574
  atomicWriteYaml(filePath, { engrams });
1984
2575
  }
2576
+ if (service?.isEnabled()) {
2577
+ try {
2578
+ const successCount = results.filter((r) => r.success).length;
2579
+ for (let i = 0; i < successCount; i++) {
2580
+ await service.award("feedback_given", { batch: true });
2581
+ }
2582
+ } catch {
2583
+ }
2584
+ }
1985
2585
  return {
1986
2586
  mode: "batch",
1987
2587
  results,
@@ -1994,22 +2594,871 @@ async function handleBatchFeedback(signals, engramsPath, packsPath) {
1994
2594
  }
1995
2595
 
1996
2596
  // src/tools/session-start.ts
2597
+ import * as fs18 from "fs";
2598
+ import * as path18 from "path";
2599
+ import * as crypto2 from "crypto";
2600
+
2601
+ // src/engagement/service.ts
2602
+ import * as fs17 from "fs";
2603
+ import * as path17 from "path";
2604
+
2605
+ // src/engagement/profile.ts
1997
2606
  import * as fs15 from "fs";
1998
- import * as path14 from "path";
1999
- async function handleSessionStart(args2, storage2, bridge) {
2607
+ import * as path15 from "path";
2608
+ import * as yaml7 from "js-yaml";
2609
+ var PROFILE_DIR = "engagement";
2610
+ var PROFILE_FILE = "profile.yaml";
2611
+ function engagementDir(basePath) {
2612
+ return path15.join(basePath, ".datacore", PROFILE_DIR);
2613
+ }
2614
+ function profilePath(basePath) {
2615
+ return path15.join(engagementDir(basePath), PROFILE_FILE);
2616
+ }
2617
+ function createDefaultProfile() {
2618
+ return EngagementProfileSchema.parse({ version: 4 });
2619
+ }
2620
+ function loadProfile(basePath) {
2621
+ const filePath = profilePath(basePath);
2622
+ if (!fs15.existsSync(filePath)) {
2623
+ return createDefaultProfile();
2624
+ }
2625
+ try {
2626
+ const raw = yaml7.load(fs15.readFileSync(filePath, "utf8"));
2627
+ return EngagementProfileSchema.parse(raw);
2628
+ } catch (err) {
2629
+ logger.warning(`Engagement profile corrupted, backing up and creating fresh: ${err}`);
2630
+ try {
2631
+ fs15.copyFileSync(filePath, filePath + ".bak");
2632
+ } catch {
2633
+ }
2634
+ return createDefaultProfile();
2635
+ }
2636
+ }
2637
+ function saveProfile(basePath, profile) {
2638
+ const dir = engagementDir(basePath);
2639
+ if (!fs15.existsSync(dir)) {
2640
+ fs15.mkdirSync(dir, { recursive: true });
2641
+ }
2642
+ const filePath = profilePath(basePath);
2643
+ const content = yaml7.dump(profile, { lineWidth: 120, noRefs: true, quotingType: '"' });
2644
+ const tmpPath = filePath + ".tmp." + process.pid;
2645
+ fs15.writeFileSync(tmpPath, content);
2646
+ fs15.renameSync(tmpPath, filePath);
2647
+ }
2648
+ function ensureEngagementDir(basePath) {
2649
+ const dir = engagementDir(basePath);
2650
+ if (!fs15.existsSync(dir)) {
2651
+ fs15.mkdirSync(dir, { recursive: true });
2652
+ }
2653
+ const gitignorePath = path15.join(basePath, ".datacore", ".gitignore");
2654
+ if (fs15.existsSync(gitignorePath)) {
2655
+ const content = fs15.readFileSync(gitignorePath, "utf8");
2656
+ if (!content.includes("engagement/profile.yaml")) {
2657
+ fs15.appendFileSync(gitignorePath, "\nengagement/profile.yaml\nengagement/badge.svg\n");
2658
+ }
2659
+ }
2660
+ }
2661
+
2662
+ // src/engagement/actions.ts
2663
+ import * as fs16 from "fs";
2664
+ import * as path16 from "path";
2665
+ import * as yaml8 from "js-yaml";
2666
+ var BUNDLED_ACTIONS = {
2667
+ version: 1,
2668
+ actions: {
2669
+ engram_created: {
2670
+ xp: 10,
2671
+ trigger: "datacore.learn",
2672
+ condition: "status === active",
2673
+ description: "Create a quality engram"
2674
+ },
2675
+ engram_created_public: {
2676
+ xp: 20,
2677
+ trigger: "datacore.learn",
2678
+ condition: "visibility === public || visibility === template",
2679
+ description: "Create a public/template engram"
2680
+ },
2681
+ feedback_given: {
2682
+ xp: 5,
2683
+ trigger: "datacore.feedback",
2684
+ daily_limit: 10,
2685
+ description: "Give feedback on an injected engram"
2686
+ },
2687
+ engram_promoted: {
2688
+ xp: 3,
2689
+ trigger: "datacore.promote",
2690
+ description: "Promote a candidate engram to active"
2691
+ },
2692
+ engram_retired: {
2693
+ xp: 5,
2694
+ trigger: "datacore.forget",
2695
+ cooldown_days: 7,
2696
+ description: "Retire an engram after reflection (7-day cooldown)"
2697
+ },
2698
+ pack_exported: {
2699
+ xp: 25,
2700
+ trigger: "datacore.packs.export",
2701
+ condition: "engram_count >= 5 && avg_fitness >= 0.6",
2702
+ description: "Export a quality pack (5+ engrams, 0.6+ fitness)"
2703
+ },
2704
+ new_domain: {
2705
+ xp: 15,
2706
+ trigger: "datacore.learn",
2707
+ description: "Create first engram in a new domain"
2708
+ },
2709
+ reconsolidation_defend: {
2710
+ xp: 12,
2711
+ trigger: "datacore.resolve",
2712
+ description: "Defend an engram during contradiction challenge"
2713
+ },
2714
+ reconsolidation_revise: {
2715
+ xp: 10,
2716
+ trigger: "datacore.resolve",
2717
+ description: "Revise an engram during contradiction challenge"
2718
+ },
2719
+ reconsolidation_retire: {
2720
+ xp: 8,
2721
+ trigger: "datacore.resolve",
2722
+ description: "Retire an engram during contradiction challenge"
2723
+ },
2724
+ discovery_explore: {
2725
+ xp: 20,
2726
+ trigger: "datacore.resolve",
2727
+ description: "Explore a cross-domain discovery"
2728
+ },
2729
+ reconsolidation_expired: {
2730
+ xp: 3,
2731
+ trigger: "system",
2732
+ description: "Auto-expire an overdue reconsolidation"
2733
+ }
2734
+ }
2735
+ };
2736
+ function loadActions(basePath) {
2737
+ const actionsPath = path16.join(basePath, ".datacore", "engagement", "xp-actions.yaml");
2738
+ if (!fs16.existsSync(actionsPath)) {
2739
+ return BUNDLED_ACTIONS;
2740
+ }
2741
+ try {
2742
+ const raw = yaml8.load(fs16.readFileSync(actionsPath, "utf8"));
2743
+ return XPActionRegistrySchema.parse(raw);
2744
+ } catch (err) {
2745
+ logger.warning(`Malformed xp-actions.yaml, using defaults: ${err}`);
2746
+ return BUNDLED_ACTIONS;
2747
+ }
2748
+ }
2749
+ function writeDefaultActions(basePath) {
2750
+ const dir = path16.join(basePath, ".datacore", "engagement");
2751
+ const actionsPath = path16.join(dir, "xp-actions.yaml");
2752
+ if (fs16.existsSync(actionsPath)) return;
2753
+ if (!fs16.existsSync(dir)) {
2754
+ fs16.mkdirSync(dir, { recursive: true });
2755
+ }
2756
+ const content = yaml8.dump(BUNDLED_ACTIONS, { lineWidth: 120, noRefs: true, quotingType: '"' });
2757
+ fs16.writeFileSync(actionsPath, content);
2758
+ }
2759
+
2760
+ // src/engagement/engine.ts
2761
+ function resolveTier(profile) {
2762
+ const xp = profile.xp.total;
2763
+ let current = "Seed";
2764
+ for (const t of TIER_THRESHOLDS) {
2765
+ if (xp >= t.minXP) current = t.name;
2766
+ }
2767
+ const changed = current !== profile.tier.current;
2768
+ const message = changed ? `You've reached ${current}!` : void 0;
2769
+ return { current, changed, message };
2770
+ }
2771
+ function getEffectiveMultiplier(profile) {
2772
+ const actives = profile.multipliers.active;
2773
+ if (actives.length === 0) return { effective: 1, active: [] };
2774
+ let effective = 1;
2775
+ for (const m of actives) {
2776
+ effective *= m.factor;
2777
+ }
2778
+ return { effective, active: actives };
2779
+ }
2780
+ function isActionEligible(profile, actionKey, actions, context) {
2781
+ const action = actions.actions[actionKey];
2782
+ if (!action) return { eligible: false, reason: `Unknown action: ${actionKey}` };
2783
+ if (action.daily_limit !== void 0) {
2784
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2785
+ const todayEntry = profile.xp.history.find((h) => h.date === today);
2786
+ if (todayEntry) {
2787
+ const todayCount = todayEntry.actions.filter((a) => a === actionKey).length;
2788
+ if (todayCount >= action.daily_limit) {
2789
+ return { eligible: false, reason: `Daily limit of ${action.daily_limit} reached for ${actionKey}` };
2790
+ }
2791
+ }
2792
+ }
2793
+ if (action.cooldown_days !== void 0 && context?.engram_age_days !== void 0) {
2794
+ const ageDays = context.engram_age_days;
2795
+ if (ageDays < action.cooldown_days) {
2796
+ return { eligible: false, reason: `Cooldown: engram must be at least ${action.cooldown_days} days old (current: ${ageDays})` };
2797
+ }
2798
+ }
2799
+ return { eligible: true };
2800
+ }
2801
+ function awardXP(profile, actionKey, actions, context) {
2802
+ const eligibility = isActionEligible(profile, actionKey, actions, context);
2803
+ if (!eligibility.eligible) return null;
2804
+ const action = actions.actions[actionKey];
2805
+ const { effective } = getEffectiveMultiplier(profile);
2806
+ const baseXP = action.xp;
2807
+ const earnedXP = Math.round(baseXP * effective);
2808
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2809
+ const today = now.split("T")[0];
2810
+ const event = {
2811
+ action_key: actionKey,
2812
+ xp_base: baseXP,
2813
+ multiplier: effective,
2814
+ xp_earned: earnedXP,
2815
+ timestamp: now,
2816
+ context
2817
+ };
2818
+ const updated = JSON.parse(JSON.stringify(profile));
2819
+ updated.xp.total += earnedXP;
2820
+ updated.xp.this_week += earnedXP;
2821
+ let todayEntry = updated.xp.history.find((h) => h.date === today);
2822
+ if (!todayEntry) {
2823
+ todayEntry = { date: today, earned: 0, base_earned: 0, multiplier: effective, actions: [] };
2824
+ updated.xp.history.push(todayEntry);
2825
+ }
2826
+ todayEntry.earned += earnedXP;
2827
+ todayEntry.base_earned += baseXP;
2828
+ todayEntry.actions.push(actionKey);
2829
+ if (actionKey === "engram_created" || actionKey === "engram_created_public") {
2830
+ updated.stats.total_engrams_created++;
2831
+ }
2832
+ if (actionKey === "feedback_given") {
2833
+ updated.stats.total_feedback_given++;
2834
+ }
2835
+ if (actionKey === "engram_retired") {
2836
+ updated.stats.total_engrams_retired++;
2837
+ }
2838
+ if (actionKey === "pack_exported") {
2839
+ updated.stats.total_packs_exported++;
2840
+ }
2841
+ if (actionKey === "new_domain") {
2842
+ updated.stats.domains_covered++;
2843
+ }
2844
+ if (!updated.stats.first_activity) {
2845
+ updated.stats.first_activity = today;
2846
+ }
2847
+ return { event, profile: updated };
2848
+ }
2849
+ function updateConsistency(profile) {
2850
+ const updated = JSON.parse(JSON.stringify(profile));
2851
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2852
+ if (updated.consistency.last_active === today) return updated;
2853
+ const thirtyDaysAgo = /* @__PURE__ */ new Date();
2854
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
2855
+ const cutoff = thirtyDaysAgo.toISOString().split("T")[0];
2856
+ const activeDays = updated.xp.history.filter((h) => h.date >= cutoff && h.date <= today).length;
2857
+ updated.consistency.active_days_30 = activeDays;
2858
+ updated.consistency.last_active = today;
2859
+ const sortedDates = updated.xp.history.map((h) => h.date).sort();
2860
+ let currentRun = 1;
2861
+ let bestRun = updated.consistency.best_run;
2862
+ for (let i = 1; i < sortedDates.length; i++) {
2863
+ const prev = new Date(sortedDates[i - 1]);
2864
+ const curr = new Date(sortedDates[i]);
2865
+ const diffDays = Math.round((curr.getTime() - prev.getTime()) / 864e5);
2866
+ if (diffDays === 1) {
2867
+ currentRun++;
2868
+ if (currentRun > bestRun) bestRun = currentRun;
2869
+ } else if (diffDays > 1) {
2870
+ currentRun = 1;
2871
+ }
2872
+ }
2873
+ updated.consistency.best_run = bestRun;
2874
+ return updated;
2875
+ }
2876
+
2877
+ // src/engagement/multipliers.ts
2878
+ function evaluateMultipliers(profile) {
2879
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2880
+ const multipliers = [];
2881
+ if (profile.identity.erc8004_registered) {
2882
+ multipliers.push({ type: "verified", factor: 1.5, since: today });
2883
+ }
2884
+ if (profile.stats.total_packs_exported >= 3 && profile.stats.total_feedback_received >= 20 && profile.stats.feedback_positive_ratio >= 0.85) {
2885
+ multipliers.push({ type: "top_teacher", factor: 1.25, since: today });
2886
+ }
2887
+ if (profile.reconsolidation.total_resolved >= 5 && profile.discoveries.total >= 3 && profile.reconsolidation.response_rate >= 0.8 && profile.discoveries.explore_rate >= 0.5) {
2888
+ multipliers.push({ type: "top_learner", factor: 1.25, since: today });
2889
+ }
2890
+ return multipliers;
2891
+ }
2892
+ function recalculateWeekly(profile) {
2893
+ const updated = JSON.parse(JSON.stringify(profile));
2894
+ const newMultipliers = evaluateMultipliers(updated);
2895
+ updated.multipliers.active = newMultipliers;
2896
+ let effective = 1;
2897
+ for (const m of newMultipliers) {
2898
+ effective *= m.factor;
2899
+ }
2900
+ updated.multipliers.effective = effective;
2901
+ const now = /* @__PURE__ */ new Date();
2902
+ const day = now.getDay();
2903
+ const diff = day === 0 ? 6 : day - 1;
2904
+ const weekStart = new Date(now);
2905
+ weekStart.setDate(weekStart.getDate() - diff);
2906
+ weekStart.setHours(0, 0, 0, 0);
2907
+ const weekStartStr = weekStart.toISOString().split("T")[0];
2908
+ updated.xp.this_week = updated.xp.history.filter((h) => h.date >= weekStartStr).reduce((sum, h) => sum + h.earned, 0);
2909
+ return updated;
2910
+ }
2911
+
2912
+ // src/engagement/reputation.ts
2913
+ function calculateReputation(profile) {
2914
+ const feedbackCount = profile.stats.total_feedback_given;
2915
+ const feedbackQuality = feedbackCount > 0 ? Math.min(1, profile.stats.feedback_positive_ratio * (Math.log(feedbackCount) / Math.log(100))) : 0;
2916
+ const verificationBonus = profile.identity.erc8004_registered ? 1 : 0;
2917
+ const stakeSignal = 0;
2918
+ const totalResolved = profile.reconsolidation.total_resolved;
2919
+ const curationHonesty = totalResolved >= 5 ? (profile.reconsolidation.outcomes.revised + profile.reconsolidation.outcomes.retired) / totalResolved : 0;
2920
+ let tenureDays = 0;
2921
+ if (profile.stats.first_activity) {
2922
+ const first = new Date(profile.stats.first_activity);
2923
+ tenureDays = Math.max(0, Math.floor((Date.now() - first.getTime()) / 864e5));
2924
+ }
2925
+ const tenureSignal = tenureDays > 0 ? Math.min(1, Math.log(tenureDays) / Math.log(365)) : 0;
2926
+ const domainBreadth = Math.min(1, profile.stats.domains_covered / 10);
2927
+ const conflictPenalty = 0;
2928
+ const score = 0.3 * feedbackQuality + 0.25 * verificationBonus + 0.15 * stakeSignal + 0.15 * curationHonesty + 0.1 * tenureSignal + 0.05 * domainBreadth - 0.5 * conflictPenalty;
2929
+ return Math.max(0, score);
2930
+ }
2931
+ function updateReputation(profile) {
2932
+ const updated = JSON.parse(JSON.stringify(profile));
2933
+ const score = calculateReputation(updated);
2934
+ updated.reputation.score = Math.round(score * 100) / 100;
2935
+ updated.reputation.last_calculated = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2936
+ return updated;
2937
+ }
2938
+
2939
+ // src/engagement/migrate.ts
2940
+ function calculateRetroactiveXP(engrams) {
2941
+ const quality = engrams.filter((e) => e.status === "active");
2942
+ const qualityCount = quality.length;
2943
+ const publicCount = quality.filter((e) => e.visibility === "public" || e.visibility === "template").length;
2944
+ let totalPositiveFeedback = 0;
2945
+ let totalFeedbackGiven = 0;
2946
+ for (const e of engrams) {
2947
+ totalPositiveFeedback += e.feedback_signals?.positive ?? 0;
2948
+ totalFeedbackGiven += (e.feedback_signals?.positive ?? 0) + (e.feedback_signals?.negative ?? 0) + (e.feedback_signals?.neutral ?? 0);
2949
+ }
2950
+ const domains = new Set(engrams.filter((e) => e.domain).map((e) => e.domain));
2951
+ const domainCount = domains.size;
2952
+ const packsExported = 0;
2953
+ return qualityCount * 10 + publicCount * 10 + totalPositiveFeedback * 5 + totalFeedbackGiven * 5 + domainCount * 20 + packsExported * 25;
2954
+ }
2955
+ function migrateProfile(basePath, engrams) {
2956
+ ensureEngagementDir(basePath);
2957
+ const profile = createDefaultProfile();
2958
+ const retroXP = calculateRetroactiveXP(engrams);
2959
+ profile.xp.total = retroXP;
2960
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2961
+ const tierResult = resolveTier(profile);
2962
+ profile.tier.current = tierResult.current;
2963
+ if (tierResult.current !== "Seed") {
2964
+ profile.tier.achieved_at = today;
2965
+ profile.tier.history.push({ tier: tierResult.current, date: today });
2966
+ }
2967
+ const active = engrams.filter((e) => e.status === "active");
2968
+ profile.stats.total_engrams_created = active.length;
2969
+ profile.stats.domains_covered = new Set(engrams.filter((e) => e.domain).map((e) => e.domain)).size;
2970
+ profile.stats.public_engrams = active.filter((e) => e.visibility === "public" || e.visibility === "template").length;
2971
+ profile.stats.first_activity = today;
2972
+ writeDefaultActions(basePath);
2973
+ saveProfile(basePath, profile);
2974
+ logger.info(`Engagement migration: ${retroXP} retroactive XP \u2192 ${profile.tier.current} tier`);
2975
+ return profile;
2976
+ }
2977
+
2978
+ // src/engagement/service.ts
2979
+ var EngagementService = class {
2980
+ constructor(basePath, config) {
2981
+ this.basePath = basePath;
2982
+ this.config = config;
2983
+ }
2984
+ profile = null;
2985
+ sessionEvents = [];
2986
+ dirty = false;
2987
+ sessionActive = false;
2988
+ actions = null;
2989
+ initialized = false;
2990
+ isEnabled() {
2991
+ return this.config.enabled;
2992
+ }
2993
+ async init() {
2994
+ if (!this.isEnabled() || this.initialized) return;
2995
+ ensureEngagementDir(this.basePath);
2996
+ const profilePath2 = path17.join(this.basePath, ".datacore", "engagement", "profile.yaml");
2997
+ const engramsPath = path17.join(this.basePath, ".datacore", "learning", "engrams.yaml");
2998
+ const coreEngramsPath = path17.join(this.basePath, "engrams.yaml");
2999
+ if (!fs17.existsSync(profilePath2)) {
3000
+ const actualEngramsPath = fs17.existsSync(engramsPath) ? engramsPath : coreEngramsPath;
3001
+ if (fs17.existsSync(actualEngramsPath)) {
3002
+ const engrams = loadEngrams(actualEngramsPath);
3003
+ if (engrams.length > 0) {
3004
+ this.profile = migrateProfile(this.basePath, engrams);
3005
+ } else {
3006
+ this.profile = loadProfile(this.basePath);
3007
+ }
3008
+ } else {
3009
+ this.profile = loadProfile(this.basePath);
3010
+ }
3011
+ } else {
3012
+ this.profile = loadProfile(this.basePath);
3013
+ }
3014
+ this.actions = loadActions(this.basePath);
3015
+ this.initialized = true;
3016
+ }
3017
+ async award(actionKey, context) {
3018
+ if (!this.isEnabled()) return null;
3019
+ if (!this.initialized) await this.init();
3020
+ if (!this.profile || !this.actions) return null;
3021
+ const result = awardXP(this.profile, actionKey, this.actions, context);
3022
+ if (!result) return null;
3023
+ this.profile = result.profile;
3024
+ this.sessionEvents.push(result.event);
3025
+ this.dirty = true;
3026
+ const tierResult = resolveTier(this.profile);
3027
+ let tierChange = null;
3028
+ if (tierResult.changed) {
3029
+ this.profile.tier.current = tierResult.current;
3030
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3031
+ this.profile.tier.achieved_at = today;
3032
+ this.profile.tier.history.push({ tier: tierResult.current, date: today });
3033
+ tierChange = {
3034
+ from: this.profile.tier.history.length > 1 ? this.profile.tier.history[this.profile.tier.history.length - 2].tier : "Seed",
3035
+ to: tierResult.current,
3036
+ message: tierResult.message
3037
+ };
3038
+ }
3039
+ if (!this.sessionActive) {
3040
+ await this.flush();
3041
+ }
3042
+ return { event: result.event, tier_change: tierChange };
3043
+ }
3044
+ async flush() {
3045
+ if (!this.dirty || !this.profile) return;
3046
+ this.profile = updateConsistency(this.profile);
3047
+ this.profile = recalculateWeekly(this.profile);
3048
+ this.profile = updateReputation(this.profile);
3049
+ saveProfile(this.basePath, this.profile);
3050
+ this.dirty = false;
3051
+ }
3052
+ getSessionSummary() {
3053
+ const actions = {};
3054
+ let totalXP = 0;
3055
+ let baseXP = 0;
3056
+ for (const event of this.sessionEvents) {
3057
+ totalXP += event.xp_earned;
3058
+ baseXP += event.xp_base;
3059
+ actions[event.action_key] = (actions[event.action_key] ?? 0) + 1;
3060
+ }
3061
+ return {
3062
+ total_xp: totalXP,
3063
+ base_xp: baseXP,
3064
+ multiplier: this.profile?.multipliers.effective ?? 1,
3065
+ events: [...this.sessionEvents],
3066
+ actions
3067
+ };
3068
+ }
3069
+ getProfile() {
3070
+ if (!this.isEnabled()) return null;
3071
+ return this.profile;
3072
+ }
3073
+ markSessionActive() {
3074
+ this.sessionActive = true;
3075
+ this.sessionEvents = [];
3076
+ }
3077
+ markSessionEnded() {
3078
+ this.sessionActive = false;
3079
+ }
3080
+ /**
3081
+ * Apply a profile transformation (e.g., from resolve, expire, challenge generation).
3082
+ * Marks profile dirty so next flush persists changes.
3083
+ */
3084
+ applyProfileUpdate(updater) {
3085
+ if (!this.profile) return;
3086
+ this.profile = updater(this.profile);
3087
+ this.dirty = true;
3088
+ }
3089
+ };
3090
+
3091
+ // src/engagement/reconsolidation.ts
3092
+ function resolveReconsolidation(profile, engramId, outcome) {
3093
+ const updated = JSON.parse(JSON.stringify(profile));
3094
+ const idx = updated.reconsolidation.pending.findIndex(
3095
+ (p) => p.engram_id === engramId || p.contradicting_id === engramId
3096
+ );
3097
+ if (idx === -1) return updated;
3098
+ updated.reconsolidation.pending.splice(idx, 1);
3099
+ updated.reconsolidation.total_resolved++;
3100
+ if (outcome === "defend") updated.reconsolidation.outcomes.defended++;
3101
+ else if (outcome === "revise") updated.reconsolidation.outcomes.revised++;
3102
+ else if (outcome === "retire") updated.reconsolidation.outcomes.retired++;
3103
+ else if (outcome === "dismiss") updated.reconsolidation.outcomes.dismissed++;
3104
+ const totalOutcomes = updated.reconsolidation.outcomes.defended + updated.reconsolidation.outcomes.revised + updated.reconsolidation.outcomes.retired;
3105
+ const totalAll = totalOutcomes + updated.reconsolidation.outcomes.dismissed;
3106
+ updated.reconsolidation.response_rate = totalAll > 0 ? totalOutcomes / totalAll : 0;
3107
+ return updated;
3108
+ }
3109
+ function expireReconsolidations(profile) {
3110
+ const updated = JSON.parse(JSON.stringify(profile));
3111
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3112
+ const expired = [];
3113
+ const remaining = [];
3114
+ for (const pending of updated.reconsolidation.pending) {
3115
+ if (pending.expires_at <= now) {
3116
+ expired.push(pending);
3117
+ } else {
3118
+ remaining.push(pending);
3119
+ }
3120
+ }
3121
+ if (expired.length === 0) return updated;
3122
+ updated.reconsolidation.pending = remaining;
3123
+ for (const _entry of expired) {
3124
+ updated.xp.total += 3;
3125
+ updated.reconsolidation.total_resolved++;
3126
+ updated.reconsolidation.outcomes.retired++;
3127
+ }
3128
+ const totalOutcomes = updated.reconsolidation.outcomes.defended + updated.reconsolidation.outcomes.revised + updated.reconsolidation.outcomes.retired;
3129
+ const totalAll = totalOutcomes + updated.reconsolidation.outcomes.dismissed;
3130
+ updated.reconsolidation.response_rate = totalAll > 0 ? totalOutcomes / totalAll : 0;
3131
+ return updated;
3132
+ }
3133
+
3134
+ // src/engagement/discovery.ts
3135
+ var MIN_ENGRAMS = 20;
3136
+ var MIN_DOMAINS = 3;
3137
+ var MIN_DAYS_BETWEEN_DISCOVERIES = 2;
3138
+ function extractKeywords(statement) {
3139
+ const stopwords = /* @__PURE__ */ new Set([
3140
+ "a",
3141
+ "an",
3142
+ "the",
3143
+ "is",
3144
+ "are",
3145
+ "was",
3146
+ "were",
3147
+ "be",
3148
+ "been",
3149
+ "being",
3150
+ "have",
3151
+ "has",
3152
+ "had",
3153
+ "do",
3154
+ "does",
3155
+ "did",
3156
+ "it",
3157
+ "its",
3158
+ "in",
3159
+ "on",
3160
+ "at",
3161
+ "to",
3162
+ "for",
3163
+ "of",
3164
+ "by",
3165
+ "with"
3166
+ ]);
3167
+ const tokens = statement.toLowerCase().split(/\s+|[.,;:!?()[\]{}]/).filter((t) => t.length > 0 && !stopwords.has(t));
3168
+ return new Set(tokens);
3169
+ }
3170
+ function daysBetween(dateA, dateB) {
3171
+ const a = new Date(dateA);
3172
+ const b = new Date(dateB);
3173
+ return Math.abs(a.getTime() - b.getTime()) / (1e3 * 60 * 60 * 24);
3174
+ }
3175
+ function generateDiscoveryCandidates(engrams, profile) {
3176
+ if (engrams.length < MIN_ENGRAMS) return [];
3177
+ const domains = new Set(engrams.map((e) => e.domain).filter(Boolean));
3178
+ if (domains.size < MIN_DOMAINS) return [];
3179
+ if (profile.discoveries.last_offered) {
3180
+ const daysSinceLast = daysBetween(
3181
+ profile.discoveries.last_offered,
3182
+ (/* @__PURE__ */ new Date()).toISOString()
3183
+ );
3184
+ if (daysSinceLast < MIN_DAYS_BETWEEN_DISCOVERIES) return [];
3185
+ }
3186
+ const withDomains = engrams.filter(
3187
+ (e) => e.domain && e.status === "active"
3188
+ );
3189
+ const keywordMap = /* @__PURE__ */ new Map();
3190
+ for (const e of withDomains) {
3191
+ keywordMap.set(e.id, extractKeywords(e.statement));
3192
+ }
3193
+ const candidates = [];
3194
+ const seen = /* @__PURE__ */ new Set();
3195
+ for (let i = 0; i < withDomains.length; i++) {
3196
+ for (let j = i + 1; j < withDomains.length; j++) {
3197
+ const a = withDomains[i];
3198
+ const b = withDomains[j];
3199
+ if (a.domain === b.domain) continue;
3200
+ const pairKey = [a.id, b.id].sort().join(":");
3201
+ if (seen.has(pairKey)) continue;
3202
+ seen.add(pairKey);
3203
+ const kwA = keywordMap.get(a.id);
3204
+ const kwB = keywordMap.get(b.id);
3205
+ const intersection = new Set([...kwA].filter((x) => kwB.has(x)));
3206
+ if (intersection.size === 0) continue;
3207
+ candidates.push({
3208
+ engram_a: { id: a.id, domain: a.domain, statement: a.statement },
3209
+ engram_b: { id: b.id, domain: b.domain, statement: b.statement },
3210
+ overlap_size: intersection.size
3211
+ });
3212
+ }
3213
+ }
3214
+ candidates.sort((a, b) => b.overlap_size - a.overlap_size);
3215
+ return candidates;
3216
+ }
3217
+ function offerDiscovery(profile, discovery) {
3218
+ const updated = JSON.parse(JSON.stringify(profile));
3219
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3220
+ const id = `disc-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
3221
+ const entry = {
3222
+ id,
3223
+ engram_a: discovery.engram_a,
3224
+ engram_b: discovery.engram_b,
3225
+ connection: discovery.connection,
3226
+ offered_at: now
3227
+ };
3228
+ updated.discoveries.pending.push(entry);
3229
+ updated.discoveries.total++;
3230
+ updated.discoveries.last_offered = now;
3231
+ return updated;
3232
+ }
3233
+ function resolveDiscovery(profile, discoveryId, action) {
3234
+ const updated = JSON.parse(JSON.stringify(profile));
3235
+ const idx = updated.discoveries.pending.findIndex((d) => d.id === discoveryId);
3236
+ if (idx === -1) return updated;
3237
+ updated.discoveries.pending.splice(idx, 1);
3238
+ if (action === "explore") {
3239
+ updated.discoveries.explored++;
3240
+ } else {
3241
+ updated.discoveries.noted++;
3242
+ }
3243
+ const totalResolved = updated.discoveries.explored + updated.discoveries.noted;
3244
+ updated.discoveries.explore_rate = totalResolved > 0 ? updated.discoveries.explored / totalResolved : 0;
3245
+ return updated;
3246
+ }
3247
+
3248
+ // src/engagement/challenges.ts
3249
+ var GETTING_STARTED_CHALLENGES = [
3250
+ {
3251
+ type: "first_steps",
3252
+ tier: "Seed",
3253
+ description: "Create your first engram",
3254
+ metric: "total_engrams_created",
3255
+ target_delta: 5,
3256
+ bonus_xp: 15
3257
+ },
3258
+ {
3259
+ type: "first_feedback",
3260
+ tier: "Seed",
3261
+ description: "Give feedback",
3262
+ metric: "total_feedback_given",
3263
+ target_delta: 3,
3264
+ bonus_xp: 10
3265
+ },
3266
+ {
3267
+ type: "explore_domain",
3268
+ tier: "Seed",
3269
+ description: "Explore a new domain",
3270
+ metric: "domains_covered",
3271
+ target_delta: 1,
3272
+ bonus_xp: 10
3273
+ }
3274
+ ];
3275
+ var REGULAR_CHALLENGES = [
3276
+ {
3277
+ type: "first_steps",
3278
+ tier: "Seed",
3279
+ description: "Create your first engram",
3280
+ metric: "total_engrams_created",
3281
+ target_delta: 5,
3282
+ bonus_xp: 15
3283
+ },
3284
+ {
3285
+ type: "first_feedback",
3286
+ tier: "Seed",
3287
+ description: "Give feedback",
3288
+ metric: "total_feedback_given",
3289
+ target_delta: 3,
3290
+ bonus_xp: 10
3291
+ },
3292
+ {
3293
+ type: "domain_deep_dive",
3294
+ tier: "Cipher",
3295
+ description: "Deep dive into a new domain",
3296
+ metric: "domains_covered",
3297
+ target_delta: 1,
3298
+ bonus_xp: 15
3299
+ },
3300
+ {
3301
+ type: "synthesis",
3302
+ tier: "Sage",
3303
+ description: "Synthesize knowledge across domains",
3304
+ metric: "domains_covered",
3305
+ target_delta: 2,
3306
+ bonus_xp: 20
3307
+ },
3308
+ {
3309
+ type: "mentorship",
3310
+ tier: "Adept",
3311
+ description: "Share your knowledge by exporting a pack",
3312
+ metric: "total_packs_exported",
3313
+ target_delta: 1,
3314
+ bonus_xp: 25
3315
+ },
3316
+ {
3317
+ type: "impact",
3318
+ tier: "Visionary",
3319
+ description: "Achieve a high positive feedback ratio",
3320
+ metric: "feedback_positive_ratio",
3321
+ target_delta: 0.85,
3322
+ bonus_xp: 25
3323
+ },
3324
+ {
3325
+ type: "network",
3326
+ tier: "Oracle",
3327
+ description: "Build your feedback network",
3328
+ metric: "total_feedback_received",
3329
+ target_delta: 10,
3330
+ bonus_xp: 30
3331
+ }
3332
+ ];
3333
+ var TIER_ORDER = {
3334
+ Seed: 0,
3335
+ Cipher: 1,
3336
+ Sage: 2,
3337
+ Adept: 3,
3338
+ Visionary: 4,
3339
+ Oracle: 5
3340
+ };
3341
+ function tierRank(tier) {
3342
+ return TIER_ORDER[tier] ?? 0;
3343
+ }
3344
+ var WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
3345
+ var GETTING_STARTED_THRESHOLD = 10;
3346
+ function generateChallenge(profile) {
3347
+ const updated = JSON.parse(JSON.stringify(profile));
3348
+ if (updated.challenges.active) return updated;
3349
+ const now = /* @__PURE__ */ new Date();
3350
+ const expiresAt = new Date(now.getTime() + WEEK_MS);
3351
+ const currentTierRank = tierRank(updated.tier.current);
3352
+ let pool;
3353
+ if (updated.stats.total_engrams_created < GETTING_STARTED_THRESHOLD && !updated.challenges.graduated) {
3354
+ const completedTypes = new Set(
3355
+ updated.challenges.history.filter((h) => h.completed).map((h) => h.type)
3356
+ );
3357
+ pool = GETTING_STARTED_CHALLENGES.filter((c) => !completedTypes.has(c.type));
3358
+ if (pool.length === 0) {
3359
+ updated.challenges.graduated = true;
3360
+ pool = REGULAR_CHALLENGES.filter((c) => tierRank(c.tier) <= currentTierRank);
3361
+ }
3362
+ } else {
3363
+ pool = REGULAR_CHALLENGES.filter((c) => tierRank(c.tier) <= currentTierRank);
3364
+ }
3365
+ if (pool.length === 0) return updated;
3366
+ const recentTypes = new Set(
3367
+ updated.challenges.history.slice(-3).map((h) => h.type)
3368
+ );
3369
+ let chosen = pool.find((c) => !recentTypes.has(c.type));
3370
+ if (!chosen) chosen = pool[0];
3371
+ const baselineStats = {};
3372
+ for (const [key, value] of Object.entries(updated.stats)) {
3373
+ if (typeof value === "number") {
3374
+ baselineStats[key] = value;
3375
+ }
3376
+ }
3377
+ const id = `chal-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
3378
+ const challenge = {
3379
+ id,
3380
+ type: chosen.type,
3381
+ tier: chosen.tier,
3382
+ description: chosen.description,
3383
+ criteria: {
3384
+ metric: chosen.metric,
3385
+ target_delta: chosen.target_delta
3386
+ },
3387
+ baseline_stats: baselineStats,
3388
+ bonus_xp: chosen.bonus_xp,
3389
+ started_at: now.toISOString(),
3390
+ expires_at: expiresAt.toISOString()
3391
+ };
3392
+ updated.challenges.active = challenge;
3393
+ return updated;
3394
+ }
3395
+ function checkChallengeCompletion(profile, challenge) {
3396
+ const metric = challenge.criteria.metric;
3397
+ const targetDelta = challenge.criteria.target_delta;
3398
+ const baselineValue = challenge.baseline_stats[metric] ?? 0;
3399
+ if (metric === "feedback_positive_ratio") {
3400
+ const currentValue2 = profile.stats[metric] ?? 0;
3401
+ return currentValue2 >= targetDelta;
3402
+ }
3403
+ const currentValue = profile.stats[metric] ?? 0;
3404
+ const delta = currentValue - baselineValue;
3405
+ return delta >= targetDelta;
3406
+ }
3407
+ function resolveChallenge(profile, challengeId) {
3408
+ const updated = JSON.parse(JSON.stringify(profile));
3409
+ if (!updated.challenges.active || updated.challenges.active.id !== challengeId) {
3410
+ return updated;
3411
+ }
3412
+ const challenge = updated.challenges.active;
3413
+ if (!checkChallengeCompletion(updated, challenge)) {
3414
+ return updated;
3415
+ }
3416
+ updated.xp.total += challenge.bonus_xp;
3417
+ const entry = {
3418
+ type: challenge.type,
3419
+ tier: challenge.tier,
3420
+ completed: true,
3421
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
3422
+ };
3423
+ updated.challenges.history.push(entry);
3424
+ updated.challenges.completed++;
3425
+ updated.challenges.active = null;
3426
+ return updated;
3427
+ }
3428
+ function dismissChallenge(profile, challengeId) {
3429
+ const updated = JSON.parse(JSON.stringify(profile));
3430
+ if (!updated.challenges.active || updated.challenges.active.id !== challengeId) {
3431
+ return updated;
3432
+ }
3433
+ const challenge = updated.challenges.active;
3434
+ const entry = {
3435
+ type: challenge.type,
3436
+ tier: challenge.tier,
3437
+ completed: false,
3438
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
3439
+ };
3440
+ updated.challenges.history.push(entry);
3441
+ updated.challenges.dismissed++;
3442
+ updated.challenges.active = null;
3443
+ return updated;
3444
+ }
3445
+
3446
+ // src/tools/session-start.ts
3447
+ async function handleSessionStart(args2, storage2, bridge, engagementService2) {
3448
+ const session_id = crypto2.randomUUID();
2000
3449
  let engrams = null;
2001
3450
  if (args2.task) {
2002
3451
  const injectResult = await handleInject(
2003
- { prompt: args2.task, scope: args2.tags?.length ? `tags:${args2.tags.join(",")}` : void 0 },
2004
- { engramsPath: storage2.engramsPath, packsPath: storage2.packsPath }
3452
+ { prompt: args2.task, session_id, scope: args2.tags?.length ? `tags:${args2.tags.join(",")}` : void 0 },
3453
+ { engramsPath: storage2.engramsPath, packsPath: storage2.packsPath, basePath: storage2.basePath }
2005
3454
  );
2006
3455
  if (injectResult.count > 0) {
2007
3456
  engrams = { text: injectResult.text, count: injectResult.count };
2008
3457
  }
2009
3458
  }
2010
3459
  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;
3460
+ const journalFile = path18.join(storage2.journalPath, `${today}.md`);
3461
+ const journal_today = fs18.existsSync(journalFile) ? fs18.readFileSync(journalFile, "utf8") : null;
2013
3462
  const allEngrams = loadEngrams(storage2.engramsPath);
2014
3463
  const pending_candidates = allEngrams.filter((e) => e.status === "candidate").length;
2015
3464
  const recommendations = [];
@@ -2028,7 +3477,79 @@ async function handleSessionStart(args2, storage2, bridge) {
2028
3477
  });
2029
3478
  const activeCount = allEngrams.filter((e) => e.status === "active").length;
2030
3479
  const guide = activeCount === 0 ? SESSION_GUIDE_FULL : SESSION_GUIDE_SHORT;
2031
- return { engrams, journal_today, pending_candidates, recommendations, guide, _hints: hints };
3480
+ let engagement;
3481
+ if (engagementService2?.isEnabled()) {
3482
+ try {
3483
+ await engagementService2.init();
3484
+ engagementService2.markSessionActive();
3485
+ engagementService2.applyProfileUpdate((p) => expireReconsolidations(p));
3486
+ const profileAfterExpire = engagementService2.getProfile();
3487
+ if (profileAfterExpire?.challenges.active) {
3488
+ if (checkChallengeCompletion(profileAfterExpire, profileAfterExpire.challenges.active)) {
3489
+ engagementService2.applyProfileUpdate((p) => resolveChallenge(p, p.challenges.active.id));
3490
+ }
3491
+ }
3492
+ engagementService2.applyProfileUpdate((p) => generateChallenge(p));
3493
+ const profileForDiscovery = engagementService2.getProfile();
3494
+ if (profileForDiscovery) {
3495
+ const candidates = generateDiscoveryCandidates(allEngrams, profileForDiscovery);
3496
+ if (candidates.length > 0) {
3497
+ const topCandidate = candidates[0];
3498
+ engagementService2.applyProfileUpdate((p) => offerDiscovery(p, {
3499
+ engram_a: topCandidate.engram_a,
3500
+ engram_b: topCandidate.engram_b,
3501
+ connection: `Shared concepts across ${topCandidate.engram_a.domain} and ${topCandidate.engram_b.domain}`
3502
+ }));
3503
+ }
3504
+ }
3505
+ const profile = engagementService2.getProfile();
3506
+ if (profile) {
3507
+ const displayLines = [formatSessionStart(profile)];
3508
+ for (const recon of profile.reconsolidation.pending.slice(0, 2)) {
3509
+ displayLines.push("");
3510
+ displayLines.push(formatReconsolidation({
3511
+ engram_id: recon.engram_id,
3512
+ statement: recon.statement,
3513
+ contradiction: recon.contradiction,
3514
+ evidence_strength: recon.evidence_strength
3515
+ }));
3516
+ }
3517
+ for (const disc of profile.discoveries.pending.slice(0, 1)) {
3518
+ displayLines.push("");
3519
+ displayLines.push(formatDiscovery({
3520
+ id: disc.id,
3521
+ engram_a: disc.engram_a,
3522
+ engram_b: disc.engram_b,
3523
+ connection: disc.connection
3524
+ }));
3525
+ }
3526
+ if (profile.challenges.active) {
3527
+ displayLines.push("");
3528
+ displayLines.push(formatChallenge({
3529
+ id: profile.challenges.active.id,
3530
+ description: profile.challenges.active.description,
3531
+ bonus_xp: profile.challenges.active.bonus_xp,
3532
+ expires_at: profile.challenges.active.expires_at
3533
+ }));
3534
+ }
3535
+ engagement = {
3536
+ tier: profile.tier.current,
3537
+ xp: profile.xp.total,
3538
+ multiplier: profile.multipliers.effective,
3539
+ active_challenge: profile.challenges.active ? {
3540
+ id: profile.challenges.active.id,
3541
+ description: profile.challenges.active.description,
3542
+ expires_at: profile.challenges.active.expires_at
3543
+ } : null,
3544
+ pending_reconsolidations: profile.reconsolidation.pending.length,
3545
+ pending_discoveries: profile.discoveries.pending.length,
3546
+ display: displayLines.join("\n")
3547
+ };
3548
+ }
3549
+ } catch {
3550
+ }
3551
+ }
3552
+ return { session_id, engrams, journal_today, pending_candidates, recommendations, guide, engagement, _hints: hints };
2032
3553
  }
2033
3554
  var SESSION_GUIDE_FULL = `## Datacore Quick Start
2034
3555
 
@@ -2057,7 +3578,7 @@ Positive feedback strengthens engrams. Unused ones naturally decay.`;
2057
3578
  var SESSION_GUIDE_SHORT = `Session started. Workflow: work \u2192 feedback \u2192 session.end.`;
2058
3579
 
2059
3580
  // src/tools/session-end.ts
2060
- async function handleSessionEnd(args2, storage2) {
3581
+ async function handleSessionEnd(args2, storage2, engagementService2) {
2061
3582
  const captureResult = await handleCapture(
2062
3583
  { type: "journal", content: args2.summary, tags: args2.tags },
2063
3584
  storage2
@@ -2067,16 +3588,34 @@ async function handleSessionEnd(args2, storage2) {
2067
3588
  for (const suggestion of args2.engram_suggestions) {
2068
3589
  await handleLearn(
2069
3590
  { statement: suggestion.statement, type: suggestion.type },
2070
- storage2.engramsPath
3591
+ storage2.engramsPath,
3592
+ engagementService2
2071
3593
  );
2072
3594
  engramsCreated++;
2073
3595
  }
2074
3596
  }
2075
3597
  const autoPromote = getConfig().engrams.auto_promote;
2076
3598
  const statusLabel = autoPromote ? "active" : "candidates";
3599
+ let engagement = void 0;
3600
+ if (engagementService2?.isEnabled()) {
3601
+ try {
3602
+ const sessionSummary = engagementService2.getSessionSummary();
3603
+ await engagementService2.flush();
3604
+ engagementService2.markSessionEnded();
3605
+ const profile = engagementService2.getProfile();
3606
+ engagement = {
3607
+ session_xp: sessionSummary.total_xp,
3608
+ total_xp: profile?.xp.total ?? 0,
3609
+ tier: profile?.tier.current ?? "Seed",
3610
+ display: profile ? formatSessionEnd(profile, sessionSummary.total_xp, sessionSummary.events) : ""
3611
+ };
3612
+ } catch {
3613
+ }
3614
+ }
2077
3615
  return {
2078
3616
  journal_path: captureResult.path ?? null,
2079
3617
  engrams_created: engramsCreated,
3618
+ engagement,
2080
3619
  _hints: buildHints({
2081
3620
  next: engramsCreated > 0 ? `Session captured. ${engramsCreated} engram(s) created as ${statusLabel}.` : "Session captured.",
2082
3621
  related: ["datacore.session.start", "datacore.status"]
@@ -2160,7 +3699,7 @@ async function handleRecall(args2, storage2, bridge) {
2160
3699
  }
2161
3700
 
2162
3701
  // src/tools/promote.ts
2163
- async function handlePromote(args2, engramsPath) {
3702
+ async function handlePromote(args2, engramsPath, service) {
2164
3703
  const targetIds = args2.ids ?? (args2.id ? [args2.id] : []);
2165
3704
  if (targetIds.length === 0) {
2166
3705
  return {
@@ -2199,6 +3738,14 @@ async function handlePromote(args2, engramsPath) {
2199
3738
  }
2200
3739
  if (promoted.length > 0) {
2201
3740
  atomicWriteYaml(engramsPath, { engrams });
3741
+ if (service?.isEnabled()) {
3742
+ try {
3743
+ for (const _ of promoted) {
3744
+ await service.award("engram_promoted", {});
3745
+ }
3746
+ } catch {
3747
+ }
3748
+ }
2202
3749
  }
2203
3750
  return {
2204
3751
  success: errors.length === 0,
@@ -2211,14 +3758,169 @@ async function handlePromote(args2, engramsPath) {
2211
3758
  };
2212
3759
  }
2213
3760
 
3761
+ // src/tools/resolve.ts
3762
+ async function handleResolve(args2, engramsPath, service) {
3763
+ if (!service?.isEnabled()) {
3764
+ return { success: false, type: args2.type, action: args2.action, error: "Engagement system is disabled" };
3765
+ }
3766
+ const profile = service.getProfile();
3767
+ if (!profile) {
3768
+ return { success: false, type: args2.type, action: args2.action, error: "No engagement profile loaded" };
3769
+ }
3770
+ try {
3771
+ switch (args2.type) {
3772
+ case "reconsolidation":
3773
+ return await handleReconsolidationResolve(args2, engramsPath, service);
3774
+ case "discovery":
3775
+ return await handleDiscoveryResolve(args2, service);
3776
+ case "challenge":
3777
+ return handleChallengeResolve(args2, service);
3778
+ default:
3779
+ return { success: false, type: args2.type, action: args2.action, error: `Unknown resolve type: ${args2.type}` };
3780
+ }
3781
+ } catch (err) {
3782
+ return { success: false, type: args2.type, action: args2.action, error: `${err}` };
3783
+ }
3784
+ }
3785
+ async function handleReconsolidationResolve(args2, engramsPath, service) {
3786
+ const profile = service.getProfile();
3787
+ const pending = profile.reconsolidation.pending.find(
3788
+ (r) => r.engram_id === args2.id || r.contradicting_id === args2.id
3789
+ );
3790
+ if (!pending) {
3791
+ return { success: false, type: "reconsolidation", action: args2.action, error: `No pending reconsolidation for engram ${args2.id}` };
3792
+ }
3793
+ const validActions = ["defend", "revise", "retire", "dismiss"];
3794
+ if (!validActions.includes(args2.action)) {
3795
+ return { success: false, type: "reconsolidation", action: args2.action, error: `Invalid action. Must be one of: ${validActions.join(", ")}` };
3796
+ }
3797
+ if (args2.action === "revise" && !args2.revised_statement) {
3798
+ return { success: false, type: "reconsolidation", action: args2.action, error: "revised_statement is required for revise action" };
3799
+ }
3800
+ if (args2.action === "revise") {
3801
+ const engrams = loadEngrams(engramsPath);
3802
+ const engram = engrams.find((e) => e.id === args2.id);
3803
+ if (engram) {
3804
+ engram.statement = args2.revised_statement;
3805
+ saveEngrams(engramsPath, engrams);
3806
+ }
3807
+ } else if (args2.action === "retire") {
3808
+ const engrams = loadEngrams(engramsPath);
3809
+ const engram = engrams.find((e) => e.id === args2.id);
3810
+ if (engram) {
3811
+ engram.status = "retired";
3812
+ saveEngrams(engramsPath, engrams);
3813
+ }
3814
+ }
3815
+ const outcome = args2.action;
3816
+ service.applyProfileUpdate((p) => resolveReconsolidation(p, args2.id, outcome));
3817
+ const xpActionMap = {
3818
+ defend: "reconsolidation_defend",
3819
+ revise: "reconsolidation_revise",
3820
+ retire: "reconsolidation_retire",
3821
+ dismiss: ""
3822
+ };
3823
+ let xpEarned = 0;
3824
+ const xpAction = xpActionMap[args2.action];
3825
+ if (xpAction) {
3826
+ const result = await service.award(xpAction, {});
3827
+ if (result) xpEarned = result.event.xp_earned;
3828
+ }
3829
+ const messages = {
3830
+ defend: "Engram defended \u2014 both engrams retained.",
3831
+ revise: "Engram revised with updated statement.",
3832
+ retire: "Old engram retired.",
3833
+ dismiss: "Contradiction dismissed as false positive."
3834
+ };
3835
+ return {
3836
+ success: true,
3837
+ type: "reconsolidation",
3838
+ action: args2.action,
3839
+ xp_earned: xpEarned,
3840
+ message: messages[args2.action],
3841
+ _hints: buildHints({
3842
+ next: xpEarned > 0 ? `+${xpEarned} XP for reconsolidation.` : "Contradiction dismissed.",
3843
+ related: ["datacore.status"]
3844
+ })
3845
+ };
3846
+ }
3847
+ async function handleDiscoveryResolve(args2, service) {
3848
+ const profile = service.getProfile();
3849
+ const pending = profile.discoveries.pending.find((d) => d.id === args2.id);
3850
+ if (!pending) {
3851
+ return { success: false, type: "discovery", action: args2.action, error: `No pending discovery ${args2.id}` };
3852
+ }
3853
+ const validActions = ["explore", "note"];
3854
+ if (!validActions.includes(args2.action)) {
3855
+ return { success: false, type: "discovery", action: args2.action, error: `Invalid action. Must be one of: ${validActions.join(", ")}` };
3856
+ }
3857
+ const action = args2.action;
3858
+ service.applyProfileUpdate((p) => resolveDiscovery(p, args2.id, action));
3859
+ let xpEarned = 0;
3860
+ if (action === "explore") {
3861
+ const result = await service.award("discovery_explore", {});
3862
+ if (result) xpEarned = result.event.xp_earned;
3863
+ }
3864
+ return {
3865
+ success: true,
3866
+ type: "discovery",
3867
+ action: args2.action,
3868
+ xp_earned: xpEarned,
3869
+ message: action === "explore" ? "Discovery explored \u2014 synthesis engram created." : "Discovery noted.",
3870
+ _hints: buildHints({
3871
+ next: xpEarned > 0 ? `+${xpEarned} XP for exploring discovery.` : "Discovery noted for later.",
3872
+ related: ["datacore.status"]
3873
+ })
3874
+ };
3875
+ }
3876
+ function handleChallengeResolve(args2, service) {
3877
+ const profile = service.getProfile();
3878
+ if (!profile.challenges.active || profile.challenges.active.id !== args2.id) {
3879
+ return { success: false, type: "challenge", action: args2.action, error: `No active challenge with id ${args2.id}` };
3880
+ }
3881
+ if (args2.action === "complete") {
3882
+ if (!checkChallengeCompletion(profile, profile.challenges.active)) {
3883
+ return { success: false, type: "challenge", action: "complete", error: "Challenge criteria not yet met" };
3884
+ }
3885
+ service.applyProfileUpdate((p) => resolveChallenge(p, args2.id));
3886
+ const bonusXP = profile.challenges.active.bonus_xp;
3887
+ return {
3888
+ success: true,
3889
+ type: "challenge",
3890
+ action: "complete",
3891
+ xp_earned: bonusXP,
3892
+ message: `Challenge completed! +${bonusXP} bonus XP.`,
3893
+ _hints: buildHints({
3894
+ next: `+${bonusXP} XP bonus for completing the challenge.`,
3895
+ related: ["datacore.status"]
3896
+ })
3897
+ };
3898
+ }
3899
+ if (args2.action === "dismiss") {
3900
+ service.applyProfileUpdate((p) => dismissChallenge(p, args2.id));
3901
+ return {
3902
+ success: true,
3903
+ type: "challenge",
3904
+ action: "dismiss",
3905
+ xp_earned: 0,
3906
+ message: "Challenge dismissed \u2014 no penalty.",
3907
+ _hints: buildHints({
3908
+ next: "A new challenge will be offered next week.",
3909
+ related: ["datacore.status"]
3910
+ })
3911
+ };
3912
+ }
3913
+ return { success: false, type: "challenge", action: args2.action, error: 'Invalid action. Must be "complete" or "dismiss".' };
3914
+ }
3915
+
2214
3916
  // src/resources.ts
2215
3917
  import {
2216
3918
  ListResourcesRequestSchema,
2217
3919
  ReadResourceRequestSchema,
2218
3920
  ListResourceTemplatesRequestSchema
2219
3921
  } from "@modelcontextprotocol/sdk/types.js";
2220
- import * as fs16 from "fs";
2221
- import * as path15 from "path";
3922
+ import * as fs19 from "fs";
3923
+ import * as path19 from "path";
2222
3924
  function registerResources(server, storage2) {
2223
3925
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
2224
3926
  resources: [
@@ -2299,11 +4001,11 @@ function registerResources(server, storage2) {
2299
4001
  const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
2300
4002
  if (journalMatch) {
2301
4003
  const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
2302
- const filePath = path15.join(storage2.journalPath, `${dateStr}.md`);
2303
- if (!fs16.existsSync(filePath)) {
4004
+ const filePath = path19.join(storage2.journalPath, `${dateStr}.md`);
4005
+ if (!fs19.existsSync(filePath)) {
2304
4006
  return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
2305
4007
  }
2306
- return { contents: [{ uri, mimeType: "text/markdown", text: fs16.readFileSync(filePath, "utf8") }] };
4008
+ return { contents: [{ uri, mimeType: "text/markdown", text: fs19.readFileSync(filePath, "utf8") }] };
2307
4009
  }
2308
4010
  const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
2309
4011
  if (engramMatch) {
@@ -2549,8 +4251,8 @@ function registerPrompts(server) {
2549
4251
 
2550
4252
  // src/datacortex.ts
2551
4253
  import { execFile } from "child_process";
2552
- import * as fs17 from "fs";
2553
- import * as path16 from "path";
4254
+ import * as fs20 from "fs";
4255
+ import * as path20 from "path";
2554
4256
  var DatacortexBridge = class {
2555
4257
  pythonPath;
2556
4258
  scriptPath;
@@ -2560,11 +4262,11 @@ var DatacortexBridge = class {
2560
4262
  }
2561
4263
  findBridgeScript(datacorePath) {
2562
4264
  const candidates = [
2563
- path16.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
2564
- path16.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
4265
+ path20.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
4266
+ path20.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
2565
4267
  ];
2566
4268
  for (const candidate of candidates) {
2567
- if (fs17.existsSync(candidate)) return candidate;
4269
+ if (fs20.existsSync(candidate)) return candidate;
2568
4270
  }
2569
4271
  return null;
2570
4272
  }
@@ -2624,6 +4326,14 @@ var discoveredModules = [];
2624
4326
  var isFirstRun = false;
2625
4327
  var serverRef = null;
2626
4328
  var datacortexBridge = null;
4329
+ var engagementService = null;
4330
+ function getEngagementService() {
4331
+ if (!engagementService || engagementService.basePath !== storage.basePath) {
4332
+ const config = getConfig();
4333
+ engagementService = new EngagementService(storage.basePath, config.engagement);
4334
+ }
4335
+ return engagementService;
4336
+ }
2627
4337
  function createServer() {
2628
4338
  const server = new Server(
2629
4339
  { name: "datacore-mcp", version: currentVersion },
@@ -2672,7 +4382,7 @@ function createServer() {
2672
4382
  serverRef = server;
2673
4383
  return server;
2674
4384
  }
2675
- var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote"]);
4385
+ var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote", "datacore.resolve"]);
2676
4386
  async function routeTool(name, args2) {
2677
4387
  const coreTool = TOOLS.find((t) => t.name === name);
2678
4388
  if (coreTool) {
@@ -2683,10 +4393,10 @@ async function routeTool(name, args2) {
2683
4393
  result = await handleCapture(validated, storage);
2684
4394
  break;
2685
4395
  case "datacore.learn":
2686
- result = await handleLearn(validated, storage.engramsPath);
4396
+ result = await handleLearn(validated, storage.engramsPath, getEngagementService());
2687
4397
  break;
2688
4398
  case "datacore.inject":
2689
- result = await handleInject(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath });
4399
+ result = await handleInject(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath, basePath: storage.basePath });
2690
4400
  break;
2691
4401
  case "datacore.search":
2692
4402
  result = await handleSearch(validated, { journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
@@ -2695,25 +4405,25 @@ async function routeTool(name, args2) {
2695
4405
  result = await handleIngest(validated, { knowledgePath: storage.knowledgePath, engramsPath: storage.engramsPath });
2696
4406
  break;
2697
4407
  case "datacore.status":
2698
- result = await handleStatus({ ...storage, engramsPath: storage.engramsPath, packsPath: storage.packsPath }, updateAvailable);
4408
+ result = await handleStatus({ ...storage, engramsPath: storage.engramsPath, packsPath: storage.packsPath }, updateAvailable, getEngagementService());
2699
4409
  break;
2700
4410
  case "datacore.forget":
2701
- result = await handleForget(validated, storage.engramsPath);
4411
+ result = await handleForget(validated, storage.engramsPath, getEngagementService());
2702
4412
  break;
2703
4413
  case "datacore.feedback":
2704
- result = await handleFeedback(validated, storage.engramsPath, storage.packsPath);
4414
+ result = await handleFeedback(validated, storage.engramsPath, storage.packsPath, getEngagementService());
2705
4415
  break;
2706
4416
  case "datacore.session.start":
2707
- result = await handleSessionStart(validated, storage, datacortexBridge);
4417
+ result = await handleSessionStart(validated, storage, datacortexBridge, getEngagementService());
2708
4418
  break;
2709
4419
  case "datacore.session.end":
2710
- result = await handleSessionEnd(validated, storage);
4420
+ result = await handleSessionEnd(validated, storage, getEngagementService());
2711
4421
  break;
2712
4422
  case "datacore.recall":
2713
4423
  result = await handleRecall(validated, { engramsPath: storage.engramsPath, journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
2714
4424
  break;
2715
4425
  case "datacore.promote":
2716
- result = await handlePromote(validated, storage.engramsPath);
4426
+ result = await handlePromote(validated, storage.engramsPath, getEngagementService());
2717
4427
  break;
2718
4428
  case "datacore.packs.discover":
2719
4429
  result = handleDiscover(validated, storage.packsPath);
@@ -2722,7 +4432,10 @@ async function routeTool(name, args2) {
2722
4432
  result = await handleInstall(validated, storage.packsPath);
2723
4433
  break;
2724
4434
  case "datacore.packs.export":
2725
- result = await handleExport(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath });
4435
+ result = await handleExport(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath }, getEngagementService());
4436
+ break;
4437
+ case "datacore.resolve":
4438
+ result = await handleResolve(validated, storage.engramsPath, getEngagementService());
2726
4439
  break;
2727
4440
  case "datacore.modules.list":
2728
4441
  result = await handleModulesList(validated, storage, discoveredModules);