@datacore-one/mcp 1.4.1 → 1.5.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
@@ -41,7 +41,13 @@ function fullConfig(basePath) {
41
41
  engramsPath: path.join(basePath, ".datacore", "learning", "engrams.yaml"),
42
42
  journalPath: path.join(basePath, "0-personal", "journal"),
43
43
  knowledgePath: path.join(basePath, "0-personal", "3-knowledge"),
44
- packsPath: path.join(basePath, ".datacore", "learning", "packs")
44
+ packsPath: path.join(basePath, ".datacore", "learning", "packs"),
45
+ schemasPath: path.join(basePath, ".datacore", "learning", "schemas.yaml"),
46
+ exchangeInboxPath: path.join(basePath, ".datacore", "learning", "exchange", "inbox"),
47
+ exchangeOutboxPath: path.join(basePath, ".datacore", "learning", "exchange", "outbox"),
48
+ knowledgeSurfacingPath: path.join(basePath, ".datacore", "state", "knowledge-surfacing.yaml"),
49
+ archivePath: path.join(basePath, ".datacore", "learning", "archive"),
50
+ statePath: path.join(basePath, ".datacore", "state")
45
51
  };
46
52
  }
47
53
  function coreConfig(basePath) {
@@ -51,12 +57,18 @@ function coreConfig(basePath) {
51
57
  engramsPath: path.join(basePath, "engrams.yaml"),
52
58
  journalPath: path.join(basePath, "journal"),
53
59
  knowledgePath: path.join(basePath, "knowledge"),
54
- packsPath: path.join(basePath, "packs")
60
+ packsPath: path.join(basePath, "packs"),
61
+ schemasPath: path.join(basePath, "schemas.yaml"),
62
+ exchangeInboxPath: path.join(basePath, "exchange", "inbox"),
63
+ exchangeOutboxPath: path.join(basePath, "exchange", "outbox"),
64
+ knowledgeSurfacingPath: path.join(basePath, "state", "knowledge-surfacing.yaml"),
65
+ archivePath: path.join(basePath, "archive"),
66
+ statePath: path.join(basePath, "state")
55
67
  };
56
68
  }
57
69
  function initCore(basePath) {
58
70
  const isFirstRun2 = !fs.existsSync(path.join(basePath, "engrams.yaml"));
59
- for (const dir of ["journal", "knowledge", "packs"]) {
71
+ for (const dir of ["journal", "knowledge", "packs", "exchange/inbox", "exchange/outbox", "archive", "state"]) {
60
72
  const dirPath = path.join(basePath, dir);
61
73
  if (!fs.existsSync(dirPath)) {
62
74
  fs.mkdirSync(dirPath, { recursive: true });
@@ -228,6 +240,21 @@ var ConfigSchema = z.object({
228
240
  consider_cap: z.number().default(5),
229
241
  spread_cap: z.number().default(3),
230
242
  spread_budget: z.number().default(480)
243
+ }).default({}),
244
+ co_access: z.object({
245
+ new_strength: z.number().default(0.1),
246
+ increment: z.number().default(0.05),
247
+ max_strength: z.number().default(0.95),
248
+ decay_rate: z.number().default(0.05),
249
+ prune_threshold: z.number().default(0.05)
250
+ }).default({}),
251
+ learning: z.object({
252
+ decay_rate: z.number().default(0.05),
253
+ abstraction_threshold: z.number().default(2),
254
+ legacy_audit_rate: z.number().default(3),
255
+ auto_defer_learning_review: z.boolean().default(false),
256
+ daily_review_max_items: z.number().default(5),
257
+ onboarding_max_items: z.number().default(15)
231
258
  }).default({})
232
259
  });
233
260
  var cachedConfig = null;
@@ -252,7 +279,7 @@ function getConfig() {
252
279
  // package.json
253
280
  var package_default = {
254
281
  name: "@datacore-one/mcp",
255
- version: "1.4.1",
282
+ version: "1.5.0",
256
283
  description: "Datacore MCP server \u2014 The Software of You",
257
284
  type: "module",
258
285
  bin: {
@@ -350,7 +377,9 @@ var TOOLS = [
350
377
  dual_coding: z2.object({
351
378
  example: z2.string().optional().describe("Concrete example illustrating the engram"),
352
379
  analogy: z2.string().optional().describe("Analogy to aid understanding")
353
- }).optional().describe("Dual coding: example and/or analogy for richer encoding")
380
+ }).optional().describe("Dual coding: example and/or analogy for richer encoding"),
381
+ abstract: z2.string().nullable().optional().describe("Abstract engram ID this was derived from"),
382
+ derived_from: z2.string().nullable().optional().describe("Source engram ID this was derived from")
354
383
  })
355
384
  },
356
385
  {
@@ -497,6 +526,40 @@ var TOOLS = [
497
526
  module: z2.string().optional().describe("Module name (omit for all modules)")
498
527
  })
499
528
  },
529
+ {
530
+ name: "datacore.knowledge.scan",
531
+ description: "Scan zettel knowledge base for engram candidates, or run consolidation pass to find low-RS and duplicate engrams.",
532
+ inputSchema: z2.object({
533
+ action: z2.enum(["scan_zettels", "scan_status", "consolidation_pass"]).describe("Action: scan_zettels | scan_status | consolidation_pass"),
534
+ confirm: z2.boolean().optional().describe("Confirm consolidation execution (default: preview only)")
535
+ })
536
+ },
537
+ {
538
+ name: "datacore.exchange",
539
+ description: "Exchange engrams via Learning Exchange Packets (LEP). Export your public engrams, import from others, or check inbox/outbox status.",
540
+ inputSchema: z2.object({
541
+ action: z2.enum(["export", "import", "status"]).describe("Action: export | import | status"),
542
+ engram_ids: z2.array(z2.string()).optional().describe("Specific engram IDs to export"),
543
+ filter_domain: z2.string().optional().describe("Filter export by domain prefix"),
544
+ path: z2.string().optional().describe("Path to LEP packet file (for import)"),
545
+ sender: z2.string().optional().describe("Sender identity (for export)"),
546
+ confirm: z2.boolean().optional().describe("Confirm import (default: preview only)"),
547
+ fitness_threshold: z2.number().optional().describe("Minimum fitness score for import (default: 0.3)"),
548
+ source_cap_percent: z2.number().optional().describe("Max percentage of engrams from one source (default: 0.20)")
549
+ })
550
+ },
551
+ {
552
+ name: "datacore.schemas",
553
+ description: "Manage knowledge schemas \u2014 detect clusters, activate, archive, merge, split, or migrate legacy relations.",
554
+ inputSchema: z2.object({
555
+ action: z2.enum(["list", "detect", "activate", "archive", "merge", "split", "migrate"]).describe("Action: list | detect | activate | archive | merge | split | migrate"),
556
+ id: z2.string().optional().describe("Schema ID (for activate/archive/merge/split)"),
557
+ target_id: z2.string().optional().describe("Target schema ID (for merge)"),
558
+ member_ids: z2.array(z2.string()).optional().describe("Member engram IDs to extract (for split)"),
559
+ name: z2.string().optional().describe("Name for new schema (for split)"),
560
+ confirm: z2.boolean().optional().describe("Confirm destructive action (for migrate)")
561
+ })
562
+ },
500
563
  {
501
564
  name: "datacore.resolve",
502
565
  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.",
@@ -616,7 +679,8 @@ var AssociationSchema = z3.object({
616
679
  target_type: z3.enum(["engram", "document"]),
617
680
  target: z3.string(),
618
681
  strength: z3.number().min(0).max(0.95),
619
- type: z3.enum(["semantic", "temporal", "causal", "co_accessed"])
682
+ type: z3.enum(["semantic", "temporal", "causal", "co_accessed"]),
683
+ updated_at: z3.string().optional()
620
684
  });
621
685
  var DualCodingSchema = z3.object({
622
686
  example: z3.string().optional(),
@@ -643,7 +707,7 @@ var FeedbackSignalsSchema = z3.object({
643
707
  neutral: z3.number().int().default(0)
644
708
  });
645
709
  var EngramSchema = z3.object({
646
- id: z3.string().regex(/^ENG-[A-Za-z0-9-]+$/),
710
+ id: z3.string().regex(/^(ENG|ABS)-[A-Za-z0-9-]+$/),
647
711
  version: z3.number().int().min(1),
648
712
  status: z3.enum(["active", "dormant", "retired", "candidate"]),
649
713
  consolidated: z3.boolean().default(false),
@@ -824,12 +888,29 @@ function generateEngramId(existingEngrams) {
824
888
  const padWidth = nextSeq > 999 ? String(nextSeq).length : 3;
825
889
  return `${prefix}${String(nextSeq).padStart(padWidth, "0")}`;
826
890
  }
891
+ function generateAbstractId(existingEngrams) {
892
+ const now = /* @__PURE__ */ new Date();
893
+ const date = now.toISOString().split("T")[0].replace(/-/g, "").slice(0, 8);
894
+ const prefix = `ABS-${date.slice(0, 4)}-${date.slice(4)}-`;
895
+ let maxSeq = 0;
896
+ for (const e of existingEngrams) {
897
+ if (e.id.startsWith(prefix)) {
898
+ const seq = parseInt(e.id.slice(prefix.length), 10);
899
+ if (seq > maxSeq) maxSeq = seq;
900
+ }
901
+ }
902
+ const nextSeq = maxSeq + 1;
903
+ const padWidth = nextSeq > 999 ? String(nextSeq).length : 3;
904
+ return `${prefix}${String(nextSeq).padStart(padWidth, "0")}`;
905
+ }
827
906
  async function handleLearn(args2, engramsPath, service) {
828
907
  const engrams = loadEngrams(engramsPath);
829
908
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
830
909
  const autoPromote = getConfig().engrams.auto_promote;
910
+ const isAbstract = args2.abstract !== void 0 && args2.abstract !== null;
911
+ const id = isAbstract ? generateAbstractId(engrams) : generateEngramId(engrams);
831
912
  const engram = {
832
- id: generateEngramId(engrams),
913
+ id,
833
914
  version: 2,
834
915
  status: autoPromote ? "active" : "candidate",
835
916
  consolidated: false,
@@ -856,8 +937,8 @@ async function handleLearn(args2, engramsPath, service) {
856
937
  last_accessed: today
857
938
  },
858
939
  pack: null,
859
- abstract: null,
860
- derived_from: null
940
+ abstract: args2.abstract ?? null,
941
+ derived_from: args2.derived_from ?? null
861
942
  };
862
943
  engrams.push(engram);
863
944
  saveEngrams(engramsPath, engrams);
@@ -897,9 +978,9 @@ async function handleLearn(args2, engramsPath, service) {
897
978
  }
898
979
 
899
980
  // src/tools/inject-tool.ts
900
- import * as fs5 from "fs";
981
+ import * as fs6 from "fs";
901
982
  import * as path4 from "path";
902
- import * as yaml3 from "js-yaml";
983
+ import * as yaml4 from "js-yaml";
903
984
 
904
985
  // src/decay.ts
905
986
  var DECAY_RATE = 0.05;
@@ -911,6 +992,15 @@ function decayedStrength(retrievalStrength, lastAccessed, now) {
911
992
  const days = Math.max(0, (current.getTime() - last.getTime()) / MS_PER_DAY);
912
993
  return Math.max(retrievalStrength * Math.exp(-DECAY_RATE * days), FLOOR);
913
994
  }
995
+ function decayedCoAccessStrength(strength, updatedAt, now, decayRate, pruneThreshold) {
996
+ const rate = decayRate ?? DECAY_RATE;
997
+ const threshold = pruneThreshold ?? FLOOR;
998
+ const last = new Date(updatedAt);
999
+ const current = now ?? /* @__PURE__ */ new Date();
1000
+ const days = Math.max(0, (current.getTime() - last.getTime()) / MS_PER_DAY);
1001
+ const decayed = strength * Math.exp(-rate * days);
1002
+ return decayed < threshold ? 0 : decayed;
1003
+ }
914
1004
  function engramState(retrievalStrength) {
915
1005
  if (retrievalStrength >= 0.5) return "active";
916
1006
  if (retrievalStrength >= 0.3) return "fading";
@@ -949,6 +1039,19 @@ function anchorBoost(engram, taskWords) {
949
1039
  }
950
1040
  return Math.min(boost, 2);
951
1041
  }
1042
+ var SCHEMA_BOOST = 2;
1043
+ function schemaBoost(engramId, schemaMap, scoredAboveMin) {
1044
+ const schemas = schemaMap.get(engramId);
1045
+ if (!schemas) return 0;
1046
+ for (const schema of schemas) {
1047
+ for (const memberId of schema.members) {
1048
+ if (memberId !== engramId && scoredAboveMin.has(memberId)) {
1049
+ return SCHEMA_BOOST;
1050
+ }
1051
+ }
1052
+ }
1053
+ return 0;
1054
+ }
952
1055
  function flattenRelations(engram) {
953
1056
  if (!engram.relations) return [];
954
1057
  const associations = [];
@@ -1055,7 +1158,7 @@ function fillTokenBudget(scored, maxTokens) {
1055
1158
  }
1056
1159
  return { selected: result, tokens_used: tokensUsed };
1057
1160
  }
1058
- function selectAndSpread(ctx, personalEngrams, packs) {
1161
+ function selectAndSpread(ctx, personalEngrams, packs, schemas = []) {
1059
1162
  const config = getConfig();
1060
1163
  const spreadCap = config.injection?.spread_cap ?? 3;
1061
1164
  const spreadBudget = config.injection?.spread_budget ?? 480;
@@ -1085,15 +1188,33 @@ function selectAndSpread(ctx, personalEngrams, packs) {
1085
1188
  }
1086
1189
  }
1087
1190
  }
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) {
1191
+ const maxKm = Math.max(...scored.map((e) => e.keyword_match), 1);
1192
+ for (const e of scored) {
1091
1193
  e.keyword_match = e.keyword_match / maxKm * 10;
1092
1194
  }
1093
- for (const e of filtered) {
1195
+ const schemaMap = /* @__PURE__ */ new Map();
1196
+ for (const schema of schemas) {
1197
+ if (schema.status !== "active" && schema.status !== "consolidated") continue;
1198
+ for (const memberId of schema.members) {
1199
+ const existing = schemaMap.get(memberId);
1200
+ if (existing) existing.push(schema);
1201
+ else schemaMap.set(memberId, [schema]);
1202
+ }
1203
+ }
1204
+ const aBoosts = /* @__PURE__ */ new Map();
1205
+ for (const e of scored) {
1094
1206
  const aBoost = anchorBoost(e, promptWords);
1207
+ aBoosts.set(e.id, aBoost);
1095
1208
  e.score = e.keyword_match + aBoost;
1096
1209
  }
1210
+ const scoredAboveMin = new Set(
1211
+ scored.filter((e) => e.score >= minRelevance).map((e) => e.id)
1212
+ );
1213
+ for (const e of scored) {
1214
+ const sBoost = schemaBoost(e.id, schemaMap, scoredAboveMin);
1215
+ e.score = e.keyword_match + (aBoosts.get(e.id) ?? 0) + sBoost;
1216
+ }
1217
+ const filtered = scored.filter((s) => s.score >= minRelevance);
1097
1218
  filtered.sort((a, b) => b.score - a.score);
1098
1219
  const { selected: directives, tokens_used: directiveTokens } = fillTokenBudget(filtered, maxTokens);
1099
1220
  const directiveIds = new Set(directives.map((e) => e.id));
@@ -1134,7 +1255,9 @@ function selectAndSpread(ctx, personalEngrams, packs) {
1134
1255
  if (visited.has(assoc.target)) continue;
1135
1256
  const target = engramMap.get(assoc.target);
1136
1257
  if (!target || target.status !== "active") continue;
1137
- const spreadScore = directive.score / maxFirstPass * assoc.strength;
1258
+ const effectiveStrength = assoc.type === "co_accessed" && assoc.updated_at ? decayedCoAccessStrength(assoc.strength, assoc.updated_at) : assoc.strength;
1259
+ if (effectiveStrength <= 0) continue;
1260
+ const spreadScore = directive.score / maxFirstPass * effectiveStrength;
1138
1261
  if (spreadScore < minRelevance * 0.5) continue;
1139
1262
  const spreadEngram = {
1140
1263
  ...target,
@@ -1165,10 +1288,62 @@ function selectAndSpread(ctx, personalEngrams, packs) {
1165
1288
  };
1166
1289
  }
1167
1290
 
1291
+ // src/schemas/schema-definition.ts
1292
+ import * as fs5 from "fs";
1293
+ import * as yaml3 from "js-yaml";
1294
+ import { z as z4 } from "zod";
1295
+ var SchemaDefinitionSchema = z4.object({
1296
+ id: z4.string(),
1297
+ name: z4.string(),
1298
+ members: z4.array(z4.string()),
1299
+ confidence: z4.number().min(0).max(1),
1300
+ status: z4.enum(["candidate", "active", "consolidated", "archived"]),
1301
+ shared_anchors: z4.array(z4.string()),
1302
+ created: z4.string(),
1303
+ updated: z4.string()
1304
+ });
1305
+ function loadSchemas(filePath) {
1306
+ if (!fs5.existsSync(filePath)) return [];
1307
+ try {
1308
+ const raw = yaml3.load(fs5.readFileSync(filePath, "utf8"));
1309
+ if (!raw?.schemas || !Array.isArray(raw.schemas)) return [];
1310
+ const valid = [];
1311
+ for (const entry of raw.schemas) {
1312
+ const result = SchemaDefinitionSchema.safeParse(entry);
1313
+ if (result.success) valid.push(result.data);
1314
+ }
1315
+ return valid;
1316
+ } catch {
1317
+ return [];
1318
+ }
1319
+ }
1320
+ function saveSchemas(filePath, schemas) {
1321
+ const content = yaml3.dump({ schemas }, { lineWidth: 120, noRefs: true, quotingType: '"' });
1322
+ const tmpPath = filePath + ".tmp." + process.pid;
1323
+ fs5.writeFileSync(tmpPath, content);
1324
+ fs5.renameSync(tmpPath, filePath);
1325
+ }
1326
+ function generateSchemaId(existing) {
1327
+ const now = /* @__PURE__ */ new Date();
1328
+ const date = now.toISOString().split("T")[0].replace(/-/g, "").slice(0, 8);
1329
+ const prefix = `SCH-${date.slice(0, 4)}-${date.slice(4)}-`;
1330
+ let maxSeq = 0;
1331
+ for (const s of existing) {
1332
+ if (s.id.startsWith(prefix)) {
1333
+ const seq = parseInt(s.id.slice(prefix.length), 10);
1334
+ if (seq > maxSeq) maxSeq = seq;
1335
+ }
1336
+ }
1337
+ const nextSeq = maxSeq + 1;
1338
+ const padWidth = nextSeq > 999 ? String(nextSeq).length : 3;
1339
+ return `${prefix}${String(nextSeq).padStart(padWidth, "0")}`;
1340
+ }
1341
+
1168
1342
  // src/tools/inject-tool.ts
1169
1343
  async function handleInject(args2, paths) {
1170
1344
  const personalEngrams = loadEngrams(paths.engramsPath);
1171
1345
  const packs = loadAllPacks(paths.packsPath);
1346
+ const schemas = paths.schemasPath ? loadSchemas(paths.schemasPath) : [];
1172
1347
  const ctx = {
1173
1348
  prompt: args2.prompt,
1174
1349
  scope: args2.scope,
@@ -1176,13 +1351,14 @@ async function handleInject(args2, paths) {
1176
1351
  maxTokens: args2.max_tokens,
1177
1352
  minRelevance: args2.min_relevance
1178
1353
  };
1179
- const result = selectAndSpread(ctx, personalEngrams, packs);
1354
+ const result = selectAndSpread(ctx, personalEngrams, packs, schemas);
1180
1355
  const totalCount = result.directives.length + result.consider.length;
1181
1356
  if (totalCount === 0) {
1182
1357
  return {
1183
1358
  text: "",
1184
1359
  count: 0,
1185
1360
  tokens_used: { directives: 0, consider: 0 },
1361
+ injected_personal_ids: [],
1186
1362
  _hints: buildHints({
1187
1363
  next: "No engrams matched this task. Use datacore.recall to search all sources, or datacore.learn to record new knowledge.",
1188
1364
  related: ["datacore.recall", "datacore.learn"]
@@ -1202,7 +1378,7 @@ async function handleInject(args2, paths) {
1202
1378
  lines.push(formatEngram(e, totalCount));
1203
1379
  }
1204
1380
  }
1205
- const relatedDocs = paths.basePath ? result.related_documents.filter((doc) => fs5.existsSync(path4.join(paths.basePath, doc.path))) : result.related_documents;
1381
+ const relatedDocs = paths.basePath ? result.related_documents.filter((doc) => fs6.existsSync(path4.join(paths.basePath, doc.path))) : result.related_documents;
1206
1382
  if (relatedDocs.length > 0) {
1207
1383
  lines.push("\n" + formatRelatedDocs(relatedDocs));
1208
1384
  }
@@ -1217,6 +1393,7 @@ async function handleInject(args2, paths) {
1217
1393
  text: lines.join("\n"),
1218
1394
  count: totalCount,
1219
1395
  tokens_used: result.tokens_used,
1396
+ injected_personal_ids: injectedIds,
1220
1397
  related_documents: relatedDocs.length > 0 ? relatedDocs.length : void 0,
1221
1398
  _hints: buildHints({
1222
1399
  next: `After task, call datacore.feedback on helpful/unhelpful engrams.${idsList}`,
@@ -1243,10 +1420,10 @@ function updateUsageTracking(engramsPath, allPersonal, selected) {
1243
1420
  }
1244
1421
  }
1245
1422
  function atomicWriteYaml(filePath, data) {
1246
- const content = yaml3.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
1423
+ const content = yaml4.dump(data, { lineWidth: 120, noRefs: true, quotingType: '"' });
1247
1424
  const tmpPath = filePath + ".tmp." + process.pid;
1248
- fs5.writeFileSync(tmpPath, content);
1249
- fs5.renameSync(tmpPath, filePath);
1425
+ fs6.writeFileSync(tmpPath, content);
1426
+ fs6.renameSync(tmpPath, filePath);
1250
1427
  }
1251
1428
  function formatEngram(engram, totalCount) {
1252
1429
  if (totalCount < 10) {
@@ -1282,7 +1459,7 @@ function formatRelatedDocs(docs) {
1282
1459
  }
1283
1460
 
1284
1461
  // src/tools/search.ts
1285
- import * as fs6 from "fs";
1462
+ import * as fs7 from "fs";
1286
1463
  import * as path5 from "path";
1287
1464
  var CONTENT_CACHE_MAX = 500;
1288
1465
  var contentCache = /* @__PURE__ */ new Map();
@@ -1290,7 +1467,7 @@ function getCachedContent(filePath) {
1290
1467
  const entry = contentCache.get(filePath);
1291
1468
  if (!entry) return null;
1292
1469
  try {
1293
- const stat = fs6.statSync(filePath);
1470
+ const stat = fs7.statSync(filePath);
1294
1471
  if (stat.mtimeMs === entry.mtime) return entry.content;
1295
1472
  } catch {
1296
1473
  }
@@ -1299,7 +1476,7 @@ function getCachedContent(filePath) {
1299
1476
  }
1300
1477
  function setCachedContent(filePath, content) {
1301
1478
  try {
1302
- const mtime = fs6.statSync(filePath).mtimeMs;
1479
+ const mtime = fs7.statSync(filePath).mtimeMs;
1303
1480
  if (contentCache.size >= CONTENT_CACHE_MAX) {
1304
1481
  const firstKey = contentCache.keys().next().value;
1305
1482
  if (firstKey) contentCache.delete(firstKey);
@@ -1336,13 +1513,13 @@ async function keywordSearch(args2, paths) {
1336
1513
  return { results: results.slice(0, limit), method: "keyword" };
1337
1514
  }
1338
1515
  function searchDir(dirPath, query) {
1339
- if (!fs6.existsSync(dirPath)) return [];
1516
+ if (!fs7.existsSync(dirPath)) return [];
1340
1517
  const results = [];
1341
1518
  const queryLower = query.toLowerCase();
1342
1519
  for (const file of walkDir(dirPath)) {
1343
1520
  if (!file.endsWith(".md")) continue;
1344
1521
  const content = getCachedContent(file) ?? (() => {
1345
- const c = fs6.readFileSync(file, "utf8");
1522
+ const c = fs7.readFileSync(file, "utf8");
1346
1523
  setCachedContent(file, c);
1347
1524
  return c;
1348
1525
  })();
@@ -1358,7 +1535,7 @@ function searchDir(dirPath, query) {
1358
1535
  }
1359
1536
  function walkDir(dir) {
1360
1537
  const files = [];
1361
- for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1538
+ for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
1362
1539
  const fullPath = path5.join(dir, entry.name);
1363
1540
  if (entry.isDirectory()) files.push(...walkDir(fullPath));
1364
1541
  else files.push(fullPath);
@@ -1396,7 +1573,7 @@ function extractDate(filePath) {
1396
1573
  }
1397
1574
 
1398
1575
  // src/tools/ingest.ts
1399
- import * as fs7 from "fs";
1576
+ import * as fs8 from "fs";
1400
1577
  import * as path6 from "path";
1401
1578
  async function handleIngest(args2, paths) {
1402
1579
  const contentError = validateContent(args2.content);
@@ -1409,7 +1586,7 @@ async function handleIngest(args2, paths) {
1409
1586
  const slug = (args2.title ?? "ingested").toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50);
1410
1587
  const fileName = `${timestamp}-${slug}.md`;
1411
1588
  const filePath = path6.join(paths.knowledgePath, fileName);
1412
- fs7.mkdirSync(path6.dirname(filePath), { recursive: true });
1589
+ fs8.mkdirSync(path6.dirname(filePath), { recursive: true });
1413
1590
  const frontmatter = `---
1414
1591
  title: "${args2.title ?? "Ingested Note"}"
1415
1592
  created: "${(/* @__PURE__ */ new Date()).toISOString()}"
@@ -1420,7 +1597,7 @@ type: ingested
1420
1597
  const tagLine = args2.tags?.length ? `
1421
1598
  ${args2.tags.map((t) => `#${t}`).join(" ")}
1422
1599
  ` : "";
1423
- fs7.writeFileSync(filePath, `${frontmatter}${args2.content}
1600
+ fs8.writeFileSync(filePath, `${frontmatter}${args2.content}
1424
1601
  ${tagLine}`);
1425
1602
  const suggestions = extractEngramSuggestions(args2.content);
1426
1603
  return {
@@ -1454,11 +1631,11 @@ function extractEngramSuggestions(content) {
1454
1631
  }
1455
1632
 
1456
1633
  // src/tools/status.ts
1457
- import * as fs9 from "fs";
1634
+ import * as fs10 from "fs";
1458
1635
  import * as path8 from "path";
1459
1636
 
1460
1637
  // src/trust.ts
1461
- import * as fs8 from "fs";
1638
+ import * as fs9 from "fs";
1462
1639
  import * as path7 from "path";
1463
1640
  import * as crypto from "crypto";
1464
1641
  function computePackChecksum(packDir) {
@@ -1467,8 +1644,8 @@ function computePackChecksum(packDir) {
1467
1644
  let hasContent = false;
1468
1645
  for (const file of files) {
1469
1646
  const filePath = path7.join(packDir, file);
1470
- if (fs8.existsSync(filePath)) {
1471
- hash.update(fs8.readFileSync(filePath));
1647
+ if (fs9.existsSync(filePath)) {
1648
+ hash.update(fs9.readFileSync(filePath));
1472
1649
  hasContent = true;
1473
1650
  }
1474
1651
  }
@@ -1480,185 +1657,185 @@ function verifyPackChecksum(packDir, expected) {
1480
1657
  }
1481
1658
 
1482
1659
  // 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)
1660
+ import { z as z5 } from "zod";
1661
+ var IdentitySchema = z5.object({
1662
+ mode: z5.enum(["private", "anonymous", "verified"]).default("private"),
1663
+ pseudonym: z5.string().nullable().default(null),
1664
+ erc8004_address: z5.string().nullable().default(null),
1665
+ erc8004_registered: z5.boolean().default(false)
1489
1666
  });
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())
1667
+ var XPHistoryEntrySchema = z5.object({
1668
+ date: z5.string(),
1669
+ earned: z5.number(),
1670
+ base_earned: z5.number(),
1671
+ multiplier: z5.number(),
1672
+ actions: z5.array(z5.string())
1496
1673
  });
1497
- var TierHistoryEntrySchema = z4.object({
1498
- tier: z4.string(),
1499
- date: z4.string()
1674
+ var TierHistoryEntrySchema = z5.object({
1675
+ tier: z5.string(),
1676
+ date: z5.string()
1500
1677
  });
1501
- var MultiplierEntrySchema = z4.object({
1502
- type: z4.string(),
1503
- factor: z4.number(),
1504
- since: z4.string()
1678
+ var MultiplierEntrySchema = z5.object({
1679
+ type: z5.string(),
1680
+ factor: z5.number(),
1681
+ since: z5.string()
1505
1682
  });
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()
1683
+ var ChallengeSchema = z5.object({
1684
+ id: z5.string(),
1685
+ type: z5.string(),
1686
+ tier: z5.string(),
1687
+ description: z5.string(),
1688
+ criteria: z5.object({
1689
+ metric: z5.string(),
1690
+ target_delta: z5.number()
1514
1691
  }),
1515
- baseline_stats: z4.record(z4.number()),
1516
- bonus_xp: z4.number(),
1517
- started_at: z4.string(),
1518
- expires_at: z4.string()
1692
+ baseline_stats: z5.record(z5.number()),
1693
+ bonus_xp: z5.number(),
1694
+ started_at: z5.string(),
1695
+ expires_at: z5.string()
1519
1696
  });
1520
- var ChallengeHistorySchema = z4.object({
1521
- type: z4.string(),
1522
- tier: z4.string(),
1523
- completed: z4.boolean(),
1524
- date: z4.string()
1697
+ var ChallengeHistorySchema = z5.object({
1698
+ type: z5.string(),
1699
+ tier: z5.string(),
1700
+ completed: z5.boolean(),
1701
+ date: z5.string()
1525
1702
  });
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()
1703
+ var ReconsolidationPendingSchema = z5.object({
1704
+ engram_id: z5.string(),
1705
+ contradicting_id: z5.string(),
1706
+ statement: z5.string(),
1707
+ contradiction: z5.string(),
1708
+ evidence_strength: z5.enum(["weak", "moderate", "strong"]),
1709
+ confidence: z5.number(),
1710
+ detected_at: z5.string(),
1711
+ expires_at: z5.string()
1535
1712
  });
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()
1713
+ var DiscoverySchema = z5.object({
1714
+ id: z5.string(),
1715
+ engram_a: z5.object({ id: z5.string(), domain: z5.string(), statement: z5.string() }),
1716
+ engram_b: z5.object({ id: z5.string(), domain: z5.string(), statement: z5.string() }),
1717
+ connection: z5.string(),
1718
+ offered_at: z5.string()
1542
1719
  });
1543
- var EngagementProfileSchema = z4.object({
1544
- version: z4.literal(4),
1720
+ var EngagementProfileSchema = z5.object({
1721
+ version: z5.literal(4),
1545
1722
  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([])
1723
+ xp: z5.object({
1724
+ total: z5.number().default(0),
1725
+ this_week: z5.number().default(0),
1726
+ history: z5.array(XPHistoryEntrySchema).default([])
1550
1727
  }).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([])
1728
+ tier: z5.object({
1729
+ current: z5.string().default("Seed"),
1730
+ achieved_at: z5.string().nullable().default(null),
1731
+ history: z5.array(TierHistoryEntrySchema).default([])
1555
1732
  }).default({}),
1556
- multipliers: z4.object({
1557
- active: z4.array(MultiplierEntrySchema).default([]),
1558
- effective: z4.number().default(1)
1733
+ multipliers: z5.object({
1734
+ active: z5.array(MultiplierEntrySchema).default([]),
1735
+ effective: z5.number().default(1)
1559
1736
  }).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)
1737
+ consistency: z5.object({
1738
+ active_days_30: z5.number().default(0),
1739
+ best_run: z5.number().default(0),
1740
+ last_active: z5.string().nullable().default(null)
1564
1741
  }).default({}),
1565
- challenges: z4.object({
1742
+ challenges: z5.object({
1566
1743
  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([])
1744
+ completed: z5.number().default(0),
1745
+ dismissed: z5.number().default(0),
1746
+ graduated: z5.boolean().default(false),
1747
+ history: z5.array(ChallengeHistorySchema).default([])
1571
1748
  }).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)
1749
+ reconsolidation: z5.object({
1750
+ pending: z5.array(ReconsolidationPendingSchema).default([]),
1751
+ total_resolved: z5.number().default(0),
1752
+ outcomes: z5.object({
1753
+ defended: z5.number().default(0),
1754
+ revised: z5.number().default(0),
1755
+ retired: z5.number().default(0),
1756
+ dismissed: z5.number().default(0)
1580
1757
  }).default({}),
1581
- response_rate: z4.number().default(0)
1758
+ response_rate: z5.number().default(0)
1582
1759
  }).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)
1760
+ discoveries: z5.object({
1761
+ pending: z5.array(DiscoverySchema).default([]),
1762
+ total: z5.number().default(0),
1763
+ last_offered: z5.string().nullable().default(null),
1764
+ explored: z5.number().default(0),
1765
+ noted: z5.number().default(0),
1766
+ explore_rate: z5.number().default(0)
1590
1767
  }).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()
1768
+ ai_performance: z5.object({
1769
+ total_injections: z5.number().default(0),
1770
+ feedback_count: z5.number().default(0),
1771
+ helpful_ratio: z5.number().default(0),
1772
+ top_engrams: z5.array(z5.object({
1773
+ id: z5.string(),
1774
+ injections: z5.number(),
1775
+ positive_ratio: z5.number()
1599
1776
  })).default([]),
1600
- unused_60d: z4.array(z4.string()).default([])
1777
+ unused_60d: z5.array(z5.string()).default([])
1601
1778
  }).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)
1779
+ reputation: z5.object({
1780
+ score: z5.number().default(0),
1781
+ components: z5.object({
1782
+ feedback_ratio: z5.number().default(0),
1783
+ stake_amount: z5.number().default(0),
1784
+ tenure_days: z5.number().default(0),
1785
+ reconsolidation_honesty: z5.number().default(0)
1609
1786
  }).default({}),
1610
- last_calculated: z4.string().nullable().default(null)
1787
+ last_calculated: z5.string().nullable().default(null)
1611
1788
  }).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)
1789
+ leaderboard: z5.object({
1790
+ mode: z5.enum(["private", "anonymous", "verified"]).default("private"),
1791
+ display_name: z5.string().nullable().default(null),
1792
+ position: z5.number().nullable().default(null)
1616
1793
  }).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)
1794
+ badge: z5.object({
1795
+ preview_svg: z5.string().nullable().default(null),
1796
+ nft_token_id: z5.string().nullable().default(null),
1797
+ last_generated: z5.string().nullable().default(null)
1621
1798
  }).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)
1799
+ stats: z5.object({
1800
+ total_engrams_created: z5.number().default(0),
1801
+ total_feedback_given: z5.number().default(0),
1802
+ total_engrams_retired: z5.number().default(0),
1803
+ total_packs_exported: z5.number().default(0),
1804
+ total_feedback_received: z5.number().default(0),
1805
+ feedback_positive_ratio: z5.number().default(0),
1806
+ domains_covered: z5.number().default(0),
1807
+ public_engrams: z5.number().default(0),
1808
+ first_activity: z5.string().nullable().default(null)
1632
1809
  }).default({})
1633
1810
  });
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()
1811
+ var XPEventSchema = z5.object({
1812
+ action_key: z5.string(),
1813
+ xp_base: z5.number(),
1814
+ multiplier: z5.number(),
1815
+ xp_earned: z5.number(),
1816
+ timestamp: z5.string(),
1817
+ context: z5.record(z5.unknown()).optional()
1641
1818
  });
1642
- var XPResultSchema = z4.object({
1819
+ var XPResultSchema = z5.object({
1643
1820
  event: XPEventSchema,
1644
- tier_change: z4.object({
1645
- from: z4.string(),
1646
- to: z4.string(),
1647
- message: z4.string()
1821
+ tier_change: z5.object({
1822
+ from: z5.string(),
1823
+ to: z5.string(),
1824
+ message: z5.string()
1648
1825
  }).nullable()
1649
1826
  });
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()
1827
+ var XPActionSchema = z5.object({
1828
+ xp: z5.number(),
1829
+ trigger: z5.string(),
1830
+ condition: z5.string().optional(),
1831
+ daily_limit: z5.number().optional(),
1832
+ cooldown_days: z5.number().optional(),
1833
+ reciprocity_cap: z5.number().optional(),
1834
+ description: z5.string()
1658
1835
  });
1659
- var XPActionRegistrySchema = z4.object({
1660
- version: z4.number(),
1661
- actions: z4.record(XPActionSchema)
1836
+ var XPActionRegistrySchema = z5.object({
1837
+ version: z5.number(),
1838
+ actions: z5.record(XPActionSchema)
1662
1839
  });
1663
1840
  var TIER_THRESHOLDS = [
1664
1841
  { name: "Seed", minXP: 0 },
@@ -1824,7 +2001,7 @@ async function handleStatus(paths, updateAvailable2, engagementService2) {
1824
2001
  for (const regPack of packs_default.packs) {
1825
2002
  if (!regPack.checksum) continue;
1826
2003
  const packDir = path8.join(paths.packsPath, regPack.id);
1827
- if (!fs9.existsSync(packDir)) continue;
2004
+ if (!fs10.existsSync(packDir)) continue;
1828
2005
  const result = verifyPackChecksum(packDir, regPack.checksum);
1829
2006
  packIntegrity.push({ name: regPack.id, valid: result.valid });
1830
2007
  }
@@ -1841,7 +2018,7 @@ async function handleStatus(paths, updateAvailable2, engagementService2) {
1841
2018
  }
1842
2019
  const { date: today } = localDate();
1843
2020
  const todayJournal = path8.join(paths.journalPath, `${today}.md`);
1844
- if (!fs9.existsSync(todayJournal)) {
2021
+ if (!fs10.existsSync(todayJournal)) {
1845
2022
  recommendations.push("No journal entry today. Use datacore.capture to start one.");
1846
2023
  }
1847
2024
  if (updateAvailable2) {
@@ -1888,9 +2065,9 @@ async function handleStatus(paths, updateAvailable2, engagementService2) {
1888
2065
  return statusResult;
1889
2066
  }
1890
2067
  function countFiles(dir, ext) {
1891
- if (!fs9.existsSync(dir)) return 0;
2068
+ if (!fs10.existsSync(dir)) return 0;
1892
2069
  let count = 0;
1893
- for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
2070
+ for (const entry of fs10.readdirSync(dir, { withFileTypes: true })) {
1894
2071
  const fullPath = path8.join(dir, entry.name);
1895
2072
  if (entry.isDirectory()) count += countFiles(fullPath, ext);
1896
2073
  else if (entry.name.endsWith(ext)) count++;
@@ -1898,12 +2075,12 @@ function countFiles(dir, ext) {
1898
2075
  return count;
1899
2076
  }
1900
2077
  function countDirs(dir) {
1901
- if (!fs9.existsSync(dir)) return 0;
1902
- return fs9.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
2078
+ if (!fs10.existsSync(dir)) return 0;
2079
+ return fs10.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).length;
1903
2080
  }
1904
2081
 
1905
2082
  // src/tools/discover.ts
1906
- import * as fs10 from "fs";
2083
+ import * as fs11 from "fs";
1907
2084
  import * as path9 from "path";
1908
2085
  function handleDiscover(args2, packsDir) {
1909
2086
  const bundledDir = path9.join(
@@ -1913,17 +2090,17 @@ function handleDiscover(args2, packsDir) {
1913
2090
  );
1914
2091
  let packs = packs_default.packs.map((p) => {
1915
2092
  const localDir = path9.join(packsDir, p.id);
1916
- const installed = fs10.existsSync(path9.join(localDir, "SKILL.md"));
2093
+ const installed = fs11.existsSync(path9.join(localDir, "SKILL.md"));
1917
2094
  let installedVersion;
1918
2095
  if (installed) {
1919
2096
  try {
1920
- const content = fs10.readFileSync(path9.join(localDir, "SKILL.md"), "utf8");
2097
+ const content = fs11.readFileSync(path9.join(localDir, "SKILL.md"), "utf8");
1921
2098
  const match = content.match(/version:\s*["']?([^"'\n]+)/);
1922
2099
  installedVersion = match?.[1];
1923
2100
  } catch {
1924
2101
  }
1925
2102
  }
1926
- const bundled = fs10.existsSync(path9.join(bundledDir, p.id, "SKILL.md"));
2103
+ const bundled = fs11.existsSync(path9.join(bundledDir, p.id, "SKILL.md"));
1927
2104
  return {
1928
2105
  ...p,
1929
2106
  installed,
@@ -1954,9 +2131,9 @@ function handleDiscover(args2, packsDir) {
1954
2131
  }
1955
2132
 
1956
2133
  // src/tools/install.ts
1957
- import * as fs11 from "fs";
2134
+ import * as fs12 from "fs";
1958
2135
  import * as path10 from "path";
1959
- import * as yaml4 from "js-yaml";
2136
+ import * as yaml5 from "js-yaml";
1960
2137
  import * as os2 from "os";
1961
2138
  import { execSync } from "child_process";
1962
2139
  async function handleInstall(args2, packsDir) {
@@ -1971,33 +2148,33 @@ async function handleInstall(args2, packsDir) {
1971
2148
  srcDir = resolved.path;
1972
2149
  }
1973
2150
  const skillPath = path10.join(srcDir, "SKILL.md");
1974
- if (!fs11.existsSync(skillPath)) {
2151
+ if (!fs12.existsSync(skillPath)) {
1975
2152
  return { success: false, error: "No SKILL.md found in source directory" };
1976
2153
  }
1977
- const skillContent = fs11.readFileSync(skillPath, "utf8");
2154
+ const skillContent = fs12.readFileSync(skillPath, "utf8");
1978
2155
  const frontmatterMatch = skillContent.match(/^---\n([\s\S]*?)\n---/);
1979
2156
  if (!frontmatterMatch) {
1980
2157
  return { success: false, error: "No YAML frontmatter in SKILL.md" };
1981
2158
  }
1982
- const manifest = yaml4.load(frontmatterMatch[1]);
2159
+ const manifest = yaml5.load(frontmatterMatch[1]);
1983
2160
  const packId = manifest?.["x-datacore"]?.id;
1984
2161
  const newVersion = manifest?.version;
1985
2162
  if (!packId) {
1986
2163
  return { success: false, error: "Missing x-datacore.id in SKILL.md frontmatter" };
1987
2164
  }
1988
2165
  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");
2166
+ if (fs12.existsSync(path10.join(destDir, "SKILL.md"))) {
2167
+ const existingContent = fs12.readFileSync(path10.join(destDir, "SKILL.md"), "utf8");
1991
2168
  const existingMatch = existingContent.match(/version:\s*["']?([^"'\n]+)/);
1992
2169
  const existingVersion = existingMatch?.[1];
1993
2170
  if (existingVersion === newVersion) {
1994
2171
  return { success: true, pack_id: packId, already_current: true };
1995
2172
  }
1996
- fs11.rmSync(destDir, { recursive: true, force: true });
1997
- fs11.cpSync(srcDir, destDir, { recursive: true });
2173
+ fs12.rmSync(destDir, { recursive: true, force: true });
2174
+ fs12.cpSync(srcDir, destDir, { recursive: true });
1998
2175
  return { success: true, pack_id: packId, upgraded: true };
1999
2176
  }
2000
- fs11.cpSync(srcDir, destDir, { recursive: true });
2177
+ fs12.cpSync(srcDir, destDir, { recursive: true });
2001
2178
  const checksumVerified = verifyInstalledChecksum(packId, destDir);
2002
2179
  return { success: true, pack_id: packId, checksum_verified: checksumVerified ?? void 0 };
2003
2180
  }
@@ -2008,15 +2185,15 @@ function verifyInstalledChecksum(packId, destDir) {
2008
2185
  return result.valid;
2009
2186
  }
2010
2187
  async function downloadPack(url) {
2011
- const tmpDir = fs11.mkdtempSync(path10.join(os2.tmpdir(), "datacore-pack-"));
2188
+ const tmpDir = fs12.mkdtempSync(path10.join(os2.tmpdir(), "datacore-pack-"));
2012
2189
  try {
2013
2190
  const res = await fetch(url, { signal: AbortSignal.timeout(3e4) });
2014
2191
  if (!res.ok) return { error: `Download failed: HTTP ${res.status}` };
2015
2192
  const buffer = Buffer.from(await res.arrayBuffer());
2016
2193
  const archivePath = path10.join(tmpDir, "pack.tar.gz");
2017
- fs11.writeFileSync(archivePath, buffer);
2194
+ fs12.writeFileSync(archivePath, buffer);
2018
2195
  const extractDir = path10.join(tmpDir, "extracted");
2019
- fs11.mkdirSync(extractDir);
2196
+ fs12.mkdirSync(extractDir);
2020
2197
  execSync(`tar xzf ${JSON.stringify(archivePath)} -C ${JSON.stringify(extractDir)}`, { timeout: 1e4 });
2021
2198
  const packRoot = findPackRoot(extractDir);
2022
2199
  if (!packRoot) return { error: "Downloaded archive does not contain SKILL.md" };
@@ -2026,8 +2203,8 @@ async function downloadPack(url) {
2026
2203
  }
2027
2204
  }
2028
2205
  function findPackRoot(dir) {
2029
- if (fs11.existsSync(path10.join(dir, "SKILL.md"))) return dir;
2030
- for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
2206
+ if (fs12.existsSync(path10.join(dir, "SKILL.md"))) return dir;
2207
+ for (const entry of fs12.readdirSync(dir, { withFileTypes: true })) {
2031
2208
  if (entry.isDirectory()) {
2032
2209
  const found = findPackRoot(path10.join(dir, entry.name));
2033
2210
  if (found) return found;
@@ -2049,20 +2226,20 @@ function resolvePackId(packId, packsDir) {
2049
2226
  "packs",
2050
2227
  packId
2051
2228
  );
2052
- if (fs11.existsSync(path10.join(bundledDir, "SKILL.md"))) {
2229
+ if (fs12.existsSync(path10.join(bundledDir, "SKILL.md"))) {
2053
2230
  return { path: bundledDir };
2054
2231
  }
2055
2232
  const localDir = path10.join(packsDir, packId);
2056
- if (fs11.existsSync(path10.join(localDir, "SKILL.md"))) {
2233
+ if (fs12.existsSync(path10.join(localDir, "SKILL.md"))) {
2057
2234
  return { path: localDir };
2058
2235
  }
2059
2236
  return { error: `Pack "${packId}" is registered but not available locally. It may need to be downloaded manually.` };
2060
2237
  }
2061
2238
 
2062
2239
  // src/tools/export.ts
2063
- import * as fs12 from "fs";
2240
+ import * as fs13 from "fs";
2064
2241
  import * as path11 from "path";
2065
- import * as yaml5 from "js-yaml";
2242
+ import * as yaml6 from "js-yaml";
2066
2243
  async function handleExport(args2, paths, service) {
2067
2244
  const allEngrams = loadEngrams(paths.engramsPath);
2068
2245
  let selected = allEngrams.filter((e) => e.status === "active");
@@ -2105,13 +2282,13 @@ async function handleExport(args2, paths, service) {
2105
2282
  }
2106
2283
  };
2107
2284
  }
2108
- if (fs12.existsSync(packDir)) {
2285
+ if (fs13.existsSync(packDir)) {
2109
2286
  return {
2110
2287
  success: false,
2111
2288
  error: `Pack directory already exists at ${packDir}. Remove it first or use a different name.`
2112
2289
  };
2113
2290
  }
2114
- fs12.mkdirSync(packDir, { recursive: true });
2291
+ fs13.mkdirSync(packDir, { recursive: true });
2115
2292
  const skillContent = `---
2116
2293
  name: "${args2.name}"
2117
2294
  description: "${args2.description}"
@@ -2130,7 +2307,7 @@ ${args2.description}
2130
2307
 
2131
2308
  Exported ${selected.length} engrams.
2132
2309
  `;
2133
- fs12.writeFileSync(path11.join(packDir, "SKILL.md"), skillContent);
2310
+ fs13.writeFileSync(path11.join(packDir, "SKILL.md"), skillContent);
2134
2311
  const exportEngrams = selected.map((e) => ({
2135
2312
  id: e.id,
2136
2313
  version: e.version,
@@ -2151,9 +2328,9 @@ Exported ${selected.length} engrams.
2151
2328
  },
2152
2329
  feedback_signals: { positive: 0, negative: 0 }
2153
2330
  }));
2154
- fs12.writeFileSync(
2331
+ fs13.writeFileSync(
2155
2332
  path11.join(packDir, "engrams.yaml"),
2156
- yaml5.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
2333
+ yaml6.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' })
2157
2334
  );
2158
2335
  if (service?.isEnabled() && selected.length >= 5) {
2159
2336
  try {
@@ -2165,16 +2342,16 @@ Exported ${selected.length} engrams.
2165
2342
  }
2166
2343
 
2167
2344
  // src/modules.ts
2168
- import * as fs13 from "fs";
2345
+ import * as fs14 from "fs";
2169
2346
  import * as path12 from "path";
2170
- import * as yaml6 from "js-yaml";
2347
+ import * as yaml7 from "js-yaml";
2171
2348
  function discoverModules(storage2) {
2172
2349
  const modules = [];
2173
2350
  if (storage2.mode !== "full") return modules;
2174
2351
  const globalModulesDir = path12.join(storage2.basePath, ".datacore", "modules");
2175
2352
  modules.push(...scanModulesDir(globalModulesDir, "global"));
2176
2353
  try {
2177
- const entries = fs13.readdirSync(storage2.basePath);
2354
+ const entries = fs14.readdirSync(storage2.basePath);
2178
2355
  for (const entry of entries) {
2179
2356
  if (/^\d+-/.test(entry)) {
2180
2357
  const spaceModulesDir = path12.join(storage2.basePath, entry, ".datacore", "modules");
@@ -2187,16 +2364,16 @@ function discoverModules(storage2) {
2187
2364
  }
2188
2365
  function scanModulesDir(modulesDir, scope, spaceName) {
2189
2366
  const modules = [];
2190
- if (!fs13.existsSync(modulesDir)) return modules;
2367
+ if (!fs14.existsSync(modulesDir)) return modules;
2191
2368
  try {
2192
- const entries = fs13.readdirSync(modulesDir);
2369
+ const entries = fs14.readdirSync(modulesDir);
2193
2370
  for (const entry of entries) {
2194
2371
  const modulePath = path12.join(modulesDir, entry);
2195
2372
  const manifestPath = path12.join(modulePath, "module.yaml");
2196
- if (!fs13.existsSync(manifestPath)) continue;
2373
+ if (!fs14.existsSync(manifestPath)) continue;
2197
2374
  try {
2198
- const raw = fs13.readFileSync(manifestPath, "utf-8");
2199
- const manifest = yaml6.load(raw);
2375
+ const raw = fs14.readFileSync(manifestPath, "utf-8");
2376
+ const manifest = yaml7.load(raw);
2200
2377
  if (!manifest || !manifest.name) continue;
2201
2378
  modules.push({
2202
2379
  name: manifest.name,
@@ -2218,7 +2395,7 @@ async function loadModuleTools(modules, storage2) {
2218
2395
  const declaredTools = mod.manifest.provides?.tools;
2219
2396
  if (!declaredTools || declaredTools.length === 0) continue;
2220
2397
  const toolsIndexPath = path12.join(mod.modulePath, "tools", "index.js");
2221
- if (!fs13.existsSync(toolsIndexPath)) continue;
2398
+ if (!fs14.existsSync(toolsIndexPath)) continue;
2222
2399
  try {
2223
2400
  const toolsModule = await import(toolsIndexPath);
2224
2401
  const moduleTools2 = toolsModule.tools || toolsModule.default?.tools || [];
@@ -2318,7 +2495,7 @@ async function handleModulesInfo(args2, storage2, cachedModules) {
2318
2495
  }
2319
2496
 
2320
2497
  // src/tools/modules-health.ts
2321
- import * as fs14 from "fs";
2498
+ import * as fs15 from "fs";
2322
2499
  import * as path13 from "path";
2323
2500
  async function handleModulesHealth(args2, storage2, cachedModules) {
2324
2501
  const modules = cachedModules ?? discoverModules(storage2);
@@ -2341,10 +2518,10 @@ async function handleModulesHealth(args2, storage2, cachedModules) {
2341
2518
  async function checkModule(mod, storage2) {
2342
2519
  const issues = [];
2343
2520
  const manifest = mod.manifest;
2344
- if (!fs14.existsSync(path13.join(mod.modulePath, "SKILL.md"))) {
2521
+ if (!fs15.existsSync(path13.join(mod.modulePath, "SKILL.md"))) {
2345
2522
  issues.push("Missing SKILL.md (ecosystem entry point)");
2346
2523
  }
2347
- if (!fs14.existsSync(path13.join(mod.modulePath, "CLAUDE.base.md"))) {
2524
+ if (!fs15.existsSync(path13.join(mod.modulePath, "CLAUDE.base.md"))) {
2348
2525
  issues.push("Missing CLAUDE.base.md (AI context)");
2349
2526
  }
2350
2527
  if (!manifest.manifest_version || manifest.manifest_version < 2) {
@@ -2361,7 +2538,7 @@ async function checkModule(mod, storage2) {
2361
2538
  const declaredTools = provides?.tools || [];
2362
2539
  if (declaredTools.length > 0) {
2363
2540
  const toolsIndex = path13.join(mod.modulePath, "tools", "index.js");
2364
- if (!fs14.existsSync(toolsIndex)) {
2541
+ if (!fs15.existsSync(toolsIndex)) {
2365
2542
  issues.push(`Declares ${declaredTools.length} tools but tools/index.js not found`);
2366
2543
  } else {
2367
2544
  try {
@@ -2381,12 +2558,12 @@ async function checkModule(mod, storage2) {
2381
2558
  const suspectDirs = ["output", "data", "state"];
2382
2559
  for (const dir of suspectDirs) {
2383
2560
  const fullPath = path13.join(mod.modulePath, dir);
2384
- if (fs14.existsSync(fullPath) && fs14.statSync(fullPath).isDirectory()) {
2561
+ if (fs15.existsSync(fullPath) && fs15.statSync(fullPath).isDirectory()) {
2385
2562
  issues.push(`Data dir '${dir}/' found in module code (should be in space data path)`);
2386
2563
  }
2387
2564
  }
2388
2565
  try {
2389
- const entries = fs14.readdirSync(mod.modulePath);
2566
+ const entries = fs15.readdirSync(mod.modulePath);
2390
2567
  for (const entry of entries) {
2391
2568
  if (suspectExts.some((ext) => entry.endsWith(ext))) {
2392
2569
  issues.push(`Data file '${entry}' found in module code dir`);
@@ -2594,18 +2771,18 @@ async function handleBatchFeedback(signals, engramsPath, packsPath, service) {
2594
2771
  }
2595
2772
 
2596
2773
  // src/tools/session-start.ts
2597
- import * as fs18 from "fs";
2774
+ import * as fs19 from "fs";
2598
2775
  import * as path18 from "path";
2599
2776
  import * as crypto2 from "crypto";
2600
2777
 
2601
2778
  // src/engagement/service.ts
2602
- import * as fs17 from "fs";
2779
+ import * as fs18 from "fs";
2603
2780
  import * as path17 from "path";
2604
2781
 
2605
2782
  // src/engagement/profile.ts
2606
- import * as fs15 from "fs";
2783
+ import * as fs16 from "fs";
2607
2784
  import * as path15 from "path";
2608
- import * as yaml7 from "js-yaml";
2785
+ import * as yaml8 from "js-yaml";
2609
2786
  var PROFILE_DIR = "engagement";
2610
2787
  var PROFILE_FILE = "profile.yaml";
2611
2788
  function engagementDir(basePath) {
@@ -2619,16 +2796,16 @@ function createDefaultProfile() {
2619
2796
  }
2620
2797
  function loadProfile(basePath) {
2621
2798
  const filePath = profilePath(basePath);
2622
- if (!fs15.existsSync(filePath)) {
2799
+ if (!fs16.existsSync(filePath)) {
2623
2800
  return createDefaultProfile();
2624
2801
  }
2625
2802
  try {
2626
- const raw = yaml7.load(fs15.readFileSync(filePath, "utf8"));
2803
+ const raw = yaml8.load(fs16.readFileSync(filePath, "utf8"));
2627
2804
  return EngagementProfileSchema.parse(raw);
2628
2805
  } catch (err) {
2629
2806
  logger.warning(`Engagement profile corrupted, backing up and creating fresh: ${err}`);
2630
2807
  try {
2631
- fs15.copyFileSync(filePath, filePath + ".bak");
2808
+ fs16.copyFileSync(filePath, filePath + ".bak");
2632
2809
  } catch {
2633
2810
  }
2634
2811
  return createDefaultProfile();
@@ -2636,33 +2813,33 @@ function loadProfile(basePath) {
2636
2813
  }
2637
2814
  function saveProfile(basePath, profile) {
2638
2815
  const dir = engagementDir(basePath);
2639
- if (!fs15.existsSync(dir)) {
2640
- fs15.mkdirSync(dir, { recursive: true });
2816
+ if (!fs16.existsSync(dir)) {
2817
+ fs16.mkdirSync(dir, { recursive: true });
2641
2818
  }
2642
2819
  const filePath = profilePath(basePath);
2643
- const content = yaml7.dump(profile, { lineWidth: 120, noRefs: true, quotingType: '"' });
2820
+ const content = yaml8.dump(profile, { lineWidth: 120, noRefs: true, quotingType: '"' });
2644
2821
  const tmpPath = filePath + ".tmp." + process.pid;
2645
- fs15.writeFileSync(tmpPath, content);
2646
- fs15.renameSync(tmpPath, filePath);
2822
+ fs16.writeFileSync(tmpPath, content);
2823
+ fs16.renameSync(tmpPath, filePath);
2647
2824
  }
2648
2825
  function ensureEngagementDir(basePath) {
2649
2826
  const dir = engagementDir(basePath);
2650
- if (!fs15.existsSync(dir)) {
2651
- fs15.mkdirSync(dir, { recursive: true });
2827
+ if (!fs16.existsSync(dir)) {
2828
+ fs16.mkdirSync(dir, { recursive: true });
2652
2829
  }
2653
2830
  const gitignorePath = path15.join(basePath, ".datacore", ".gitignore");
2654
- if (fs15.existsSync(gitignorePath)) {
2655
- const content = fs15.readFileSync(gitignorePath, "utf8");
2831
+ if (fs16.existsSync(gitignorePath)) {
2832
+ const content = fs16.readFileSync(gitignorePath, "utf8");
2656
2833
  if (!content.includes("engagement/profile.yaml")) {
2657
- fs15.appendFileSync(gitignorePath, "\nengagement/profile.yaml\nengagement/badge.svg\n");
2834
+ fs16.appendFileSync(gitignorePath, "\nengagement/profile.yaml\nengagement/badge.svg\n");
2658
2835
  }
2659
2836
  }
2660
2837
  }
2661
2838
 
2662
2839
  // src/engagement/actions.ts
2663
- import * as fs16 from "fs";
2840
+ import * as fs17 from "fs";
2664
2841
  import * as path16 from "path";
2665
- import * as yaml8 from "js-yaml";
2842
+ import * as yaml9 from "js-yaml";
2666
2843
  var BUNDLED_ACTIONS = {
2667
2844
  version: 1,
2668
2845
  actions: {
@@ -2735,11 +2912,11 @@ var BUNDLED_ACTIONS = {
2735
2912
  };
2736
2913
  function loadActions(basePath) {
2737
2914
  const actionsPath = path16.join(basePath, ".datacore", "engagement", "xp-actions.yaml");
2738
- if (!fs16.existsSync(actionsPath)) {
2915
+ if (!fs17.existsSync(actionsPath)) {
2739
2916
  return BUNDLED_ACTIONS;
2740
2917
  }
2741
2918
  try {
2742
- const raw = yaml8.load(fs16.readFileSync(actionsPath, "utf8"));
2919
+ const raw = yaml9.load(fs17.readFileSync(actionsPath, "utf8"));
2743
2920
  return XPActionRegistrySchema.parse(raw);
2744
2921
  } catch (err) {
2745
2922
  logger.warning(`Malformed xp-actions.yaml, using defaults: ${err}`);
@@ -2749,12 +2926,12 @@ function loadActions(basePath) {
2749
2926
  function writeDefaultActions(basePath) {
2750
2927
  const dir = path16.join(basePath, ".datacore", "engagement");
2751
2928
  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 });
2929
+ if (fs17.existsSync(actionsPath)) return;
2930
+ if (!fs17.existsSync(dir)) {
2931
+ fs17.mkdirSync(dir, { recursive: true });
2755
2932
  }
2756
- const content = yaml8.dump(BUNDLED_ACTIONS, { lineWidth: 120, noRefs: true, quotingType: '"' });
2757
- fs16.writeFileSync(actionsPath, content);
2933
+ const content = yaml9.dump(BUNDLED_ACTIONS, { lineWidth: 120, noRefs: true, quotingType: '"' });
2934
+ fs17.writeFileSync(actionsPath, content);
2758
2935
  }
2759
2936
 
2760
2937
  // src/engagement/engine.ts
@@ -2996,9 +3173,9 @@ var EngagementService = class {
2996
3173
  const profilePath2 = path17.join(this.basePath, ".datacore", "engagement", "profile.yaml");
2997
3174
  const engramsPath = path17.join(this.basePath, ".datacore", "learning", "engrams.yaml");
2998
3175
  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)) {
3176
+ if (!fs18.existsSync(profilePath2)) {
3177
+ const actualEngramsPath = fs18.existsSync(engramsPath) ? engramsPath : coreEngramsPath;
3178
+ if (fs18.existsSync(actualEngramsPath)) {
3002
3179
  const engrams = loadEngrams(actualEngramsPath);
3003
3180
  if (engrams.length > 0) {
3004
3181
  this.profile = migrateProfile(this.basePath, engrams);
@@ -3444,21 +3621,29 @@ function dismissChallenge(profile, challengeId) {
3444
3621
  }
3445
3622
 
3446
3623
  // src/tools/session-start.ts
3447
- async function handleSessionStart(args2, storage2, bridge, engagementService2) {
3624
+ async function handleSessionStart(args2, storage2, bridge, engagementService2, tracker) {
3625
+ if (storage2.mode === "full") {
3626
+ for (const dir of [storage2.archivePath, storage2.exchangeInboxPath, storage2.exchangeOutboxPath, storage2.statePath]) {
3627
+ if (!fs19.existsSync(dir)) fs19.mkdirSync(dir, { recursive: true });
3628
+ }
3629
+ }
3448
3630
  const session_id = crypto2.randomUUID();
3449
3631
  let engrams = null;
3450
3632
  if (args2.task) {
3451
3633
  const injectResult = await handleInject(
3452
3634
  { 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 }
3635
+ { engramsPath: storage2.engramsPath, packsPath: storage2.packsPath, basePath: storage2.basePath, schemasPath: storage2.schemasPath }
3454
3636
  );
3455
3637
  if (injectResult.count > 0) {
3456
3638
  engrams = { text: injectResult.text, count: injectResult.count };
3457
3639
  }
3640
+ if (tracker && injectResult.injected_personal_ids.length > 0) {
3641
+ tracker.trackInjected(session_id, injectResult.injected_personal_ids);
3642
+ }
3458
3643
  }
3459
3644
  const { date: today } = localDate();
3460
3645
  const journalFile = path18.join(storage2.journalPath, `${today}.md`);
3461
- const journal_today = fs18.existsSync(journalFile) ? fs18.readFileSync(journalFile, "utf8") : null;
3646
+ const journal_today = fs19.existsSync(journalFile) ? fs19.readFileSync(journalFile, "utf8") : null;
3462
3647
  const allEngrams = loadEngrams(storage2.engramsPath);
3463
3648
  const pending_candidates = allEngrams.filter((e) => e.status === "candidate").length;
3464
3649
  const recommendations = [];
@@ -3578,7 +3763,14 @@ Positive feedback strengthens engrams. Unused ones naturally decay.`;
3578
3763
  var SESSION_GUIDE_SHORT = `Session started. Workflow: work \u2192 feedback \u2192 session.end.`;
3579
3764
 
3580
3765
  // src/tools/session-end.ts
3581
- async function handleSessionEnd(args2, storage2, engagementService2) {
3766
+ async function handleSessionEnd(args2, storage2, engagementService2, tracker) {
3767
+ if (args2.session_id && tracker) {
3768
+ const pairs = tracker.getCoAccessPairs(args2.session_id);
3769
+ if (pairs.length > 0) {
3770
+ writeCoAccessAssociations(storage2.engramsPath, pairs);
3771
+ }
3772
+ tracker.clear(args2.session_id);
3773
+ }
3582
3774
  const captureResult = await handleCapture(
3583
3775
  { type: "journal", content: args2.summary, tags: args2.tags },
3584
3776
  storage2
@@ -3622,6 +3814,43 @@ async function handleSessionEnd(args2, storage2, engagementService2) {
3622
3814
  })
3623
3815
  };
3624
3816
  }
3817
+ function writeCoAccessAssociations(engramsPath, pairs) {
3818
+ const engrams = loadEngrams(engramsPath);
3819
+ const map = new Map(engrams.map((e) => [e.id, e]));
3820
+ const config = getConfig().co_access;
3821
+ let changed = false;
3822
+ for (const [idA, idB] of pairs) {
3823
+ const a = map.get(idA);
3824
+ const b = map.get(idB);
3825
+ if (!a || !b || a.pack || b.pack) continue;
3826
+ changed = strengthenCoAccess(a, idB, config) || changed;
3827
+ changed = strengthenCoAccess(b, idA, config) || changed;
3828
+ }
3829
+ if (changed) {
3830
+ atomicWriteYaml(engramsPath, { engrams });
3831
+ }
3832
+ }
3833
+ function strengthenCoAccess(engram, targetId, config) {
3834
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3835
+ const existing = engram.associations.find(
3836
+ (a) => a.target === targetId && a.type === "co_accessed"
3837
+ );
3838
+ if (existing) {
3839
+ const newStrength = Math.min(existing.strength + config.increment, config.max_strength);
3840
+ if (newStrength === existing.strength && existing.updated_at === today) return false;
3841
+ existing.strength = newStrength;
3842
+ existing.updated_at = today;
3843
+ return true;
3844
+ }
3845
+ engram.associations.push({
3846
+ target_type: "engram",
3847
+ target: targetId,
3848
+ strength: config.new_strength,
3849
+ type: "co_accessed",
3850
+ updated_at: today
3851
+ });
3852
+ return true;
3853
+ }
3625
3854
 
3626
3855
  // src/tools/recall.ts
3627
3856
  async function handleRecall(args2, storage2, bridge) {
@@ -3913,14 +4142,961 @@ function handleChallengeResolve(args2, service) {
3913
4142
  return { success: false, type: "challenge", action: args2.action, error: 'Invalid action. Must be "complete" or "dismiss".' };
3914
4143
  }
3915
4144
 
4145
+ // src/tools/schemas.ts
4146
+ import * as fs20 from "fs";
4147
+
4148
+ // src/schema-detection.ts
4149
+ var MIN_STRENGTH = 0.4;
4150
+ var MIN_MEMBERS = 3;
4151
+ var MIN_SHARED_ANCHORS = 2;
4152
+ var K_CORE = 2;
4153
+ var MAX_EDGES = 1e4;
4154
+ var STALE_DAYS = 90;
4155
+ function detectSchemas(engrams, existing) {
4156
+ const adjacency = /* @__PURE__ */ new Map();
4157
+ const engramMap = new Map(engrams.map((e) => [e.id, e]));
4158
+ const seenEdges = /* @__PURE__ */ new Set();
4159
+ for (const engram of engrams) {
4160
+ if (engram.status !== "active") continue;
4161
+ for (const assoc of engram.associations) {
4162
+ if (assoc.target_type !== "engram") continue;
4163
+ const target = engramMap.get(assoc.target);
4164
+ if (!target || target.status !== "active") continue;
4165
+ const effectiveStrength = assoc.type === "co_accessed" && assoc.updated_at ? decayedCoAccessStrength(assoc.strength, assoc.updated_at) : assoc.strength;
4166
+ if (effectiveStrength < MIN_STRENGTH) continue;
4167
+ if (!adjacency.has(engram.id)) adjacency.set(engram.id, /* @__PURE__ */ new Set());
4168
+ if (!adjacency.has(assoc.target)) adjacency.set(assoc.target, /* @__PURE__ */ new Set());
4169
+ adjacency.get(engram.id).add(assoc.target);
4170
+ adjacency.get(assoc.target).add(engram.id);
4171
+ const edgeKey = engram.id < assoc.target ? `${engram.id}:${assoc.target}` : `${assoc.target}:${engram.id}`;
4172
+ seenEdges.add(edgeKey);
4173
+ }
4174
+ }
4175
+ if (seenEdges.size > MAX_EDGES) {
4176
+ return {
4177
+ created: [],
4178
+ updated: [],
4179
+ flagged: [],
4180
+ warning: `Association graph has ${seenEdges.size} edges (limit: ${MAX_EDGES}). Skipping detection to prevent performance issues.`
4181
+ };
4182
+ }
4183
+ let changed = true;
4184
+ while (changed) {
4185
+ changed = false;
4186
+ for (const [node, neighbors] of adjacency) {
4187
+ if (neighbors.size < K_CORE) {
4188
+ for (const neighbor of neighbors) {
4189
+ adjacency.get(neighbor)?.delete(node);
4190
+ }
4191
+ adjacency.delete(node);
4192
+ changed = true;
4193
+ }
4194
+ }
4195
+ }
4196
+ const visited = /* @__PURE__ */ new Set();
4197
+ const components = [];
4198
+ for (const node of adjacency.keys()) {
4199
+ if (visited.has(node)) continue;
4200
+ const component = [];
4201
+ const queue = [node];
4202
+ visited.add(node);
4203
+ while (queue.length > 0) {
4204
+ const current = queue.shift();
4205
+ component.push(current);
4206
+ for (const neighbor of adjacency.get(current) ?? []) {
4207
+ if (!visited.has(neighbor)) {
4208
+ visited.add(neighbor);
4209
+ queue.push(neighbor);
4210
+ }
4211
+ }
4212
+ }
4213
+ components.push(component);
4214
+ }
4215
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4216
+ const created = [];
4217
+ const updated = [];
4218
+ for (const component of components) {
4219
+ if (component.length < MIN_MEMBERS) continue;
4220
+ const anchorCounts = /* @__PURE__ */ new Map();
4221
+ for (const id of component) {
4222
+ const engram = engramMap.get(id);
4223
+ if (!engram) continue;
4224
+ for (const anchor of engram.knowledge_anchors) {
4225
+ anchorCounts.set(anchor.path, (anchorCounts.get(anchor.path) ?? 0) + 1);
4226
+ }
4227
+ }
4228
+ const sharedAnchors = Array.from(anchorCounts.entries()).filter(([, count]) => count >= 2).map(([path23]) => path23);
4229
+ if (sharedAnchors.length < MIN_SHARED_ANCHORS) continue;
4230
+ const memberScore = Math.min(component.length / 10, 1);
4231
+ const anchorScore = Math.min(sharedAnchors.length / 5, 1);
4232
+ let totalStrength = 0;
4233
+ let edgePairs = 0;
4234
+ for (const id of component) {
4235
+ const neighbors = adjacency.get(id) ?? /* @__PURE__ */ new Set();
4236
+ for (const neighbor of neighbors) {
4237
+ if (component.includes(neighbor)) {
4238
+ totalStrength += getMaxStrength(engramMap.get(id), neighbor, engramMap);
4239
+ edgePairs++;
4240
+ }
4241
+ }
4242
+ }
4243
+ const avgStrength = edgePairs > 0 ? totalStrength / edgePairs : 0;
4244
+ const confidence = Math.round(memberScore * anchorScore * avgStrength * 1e3) / 1e3;
4245
+ const componentSet = new Set(component);
4246
+ let matched = false;
4247
+ for (const schema of existing) {
4248
+ if (schema.status === "archived") continue;
4249
+ const schemaSet = new Set(schema.members);
4250
+ const intersection = component.filter((id) => schemaSet.has(id)).length;
4251
+ const union = (/* @__PURE__ */ new Set([...component, ...schema.members])).size;
4252
+ const jaccard = intersection / union;
4253
+ if (jaccard >= 0.5) {
4254
+ updated.push({
4255
+ ...schema,
4256
+ members: component.sort(),
4257
+ shared_anchors: sharedAnchors,
4258
+ confidence,
4259
+ updated: today
4260
+ });
4261
+ matched = true;
4262
+ break;
4263
+ }
4264
+ }
4265
+ if (!matched) {
4266
+ const allExisting = [...existing, ...created];
4267
+ created.push({
4268
+ id: generateSchemaId(allExisting),
4269
+ name: `Schema from ${component.length} engrams`,
4270
+ members: component.sort(),
4271
+ confidence,
4272
+ status: "candidate",
4273
+ shared_anchors: sharedAnchors,
4274
+ created: today,
4275
+ updated: today
4276
+ });
4277
+ }
4278
+ }
4279
+ const flagged = [];
4280
+ const staleDate = /* @__PURE__ */ new Date();
4281
+ staleDate.setDate(staleDate.getDate() - STALE_DAYS);
4282
+ const staleDateStr = staleDate.toISOString().split("T")[0];
4283
+ for (const schema of existing) {
4284
+ if (schema.status === "archived") continue;
4285
+ if (schema.updated < staleDateStr) {
4286
+ flagged.push(schema);
4287
+ }
4288
+ }
4289
+ return { created, updated, flagged };
4290
+ }
4291
+ function getMaxStrength(engram, targetId, engramMap) {
4292
+ let max = 0;
4293
+ for (const assoc of engram.associations) {
4294
+ if (assoc.target === targetId) {
4295
+ const effective = assoc.type === "co_accessed" && assoc.updated_at ? decayedCoAccessStrength(assoc.strength, assoc.updated_at) : assoc.strength;
4296
+ if (effective > max) max = effective;
4297
+ }
4298
+ }
4299
+ const target = engramMap.get(targetId);
4300
+ if (target) {
4301
+ for (const assoc of target.associations) {
4302
+ if (assoc.target === engram.id) {
4303
+ const effective = assoc.type === "co_accessed" && assoc.updated_at ? decayedCoAccessStrength(assoc.strength, assoc.updated_at) : assoc.strength;
4304
+ if (effective > max) max = effective;
4305
+ }
4306
+ }
4307
+ }
4308
+ return max;
4309
+ }
4310
+
4311
+ // src/tools/schemas.ts
4312
+ async function handleSchemas(args2, paths) {
4313
+ switch (args2.action) {
4314
+ case "list":
4315
+ return listSchemas(paths.schemasPath);
4316
+ case "detect":
4317
+ return detectAction(paths);
4318
+ case "activate":
4319
+ return setStatus(paths.schemasPath, args2.id, "active");
4320
+ case "archive":
4321
+ return setStatus(paths.schemasPath, args2.id, "archived");
4322
+ case "merge":
4323
+ return mergeSchemas(paths.schemasPath, args2.id, args2.target_id);
4324
+ case "split":
4325
+ return splitSchema(paths.schemasPath, args2.id, args2.member_ids, args2.name);
4326
+ case "migrate":
4327
+ return migrateRelations(paths.engramsPath, args2.confirm);
4328
+ default:
4329
+ throw new Error(`Unknown schemas action: ${args2.action}`);
4330
+ }
4331
+ }
4332
+ function listSchemas(schemasPath) {
4333
+ const schemas = loadSchemas(schemasPath);
4334
+ return {
4335
+ schemas: schemas.map((s) => ({
4336
+ id: s.id,
4337
+ name: s.name,
4338
+ status: s.status,
4339
+ members: s.members.length,
4340
+ confidence: s.confidence,
4341
+ shared_anchors: s.shared_anchors.length,
4342
+ updated: s.updated
4343
+ })),
4344
+ total: schemas.length,
4345
+ _hints: buildHints({
4346
+ next: schemas.length === 0 ? 'No schemas yet. Use action="detect" to discover schemas from association graph.' : 'Use action="detect" to update schemas from current associations.',
4347
+ related: ["datacore.schemas"]
4348
+ })
4349
+ };
4350
+ }
4351
+ function detectAction(paths) {
4352
+ const engrams = loadEngrams(paths.engramsPath);
4353
+ const existing = loadSchemas(paths.schemasPath);
4354
+ const result = detectSchemas(engrams, existing);
4355
+ if (result.warning) {
4356
+ return { warning: result.warning, created: 0, updated: 0, flagged: 0 };
4357
+ }
4358
+ const updatedIds = new Set(result.updated.map((s) => s.id));
4359
+ const final = [
4360
+ ...existing.filter((s) => !updatedIds.has(s.id)),
4361
+ ...result.updated,
4362
+ ...result.created
4363
+ ];
4364
+ const dir = paths.schemasPath.substring(0, paths.schemasPath.lastIndexOf("/"));
4365
+ if (dir && !fs20.existsSync(dir)) fs20.mkdirSync(dir, { recursive: true });
4366
+ saveSchemas(paths.schemasPath, final);
4367
+ return {
4368
+ created: result.created.length,
4369
+ updated: result.updated.length,
4370
+ flagged: result.flagged.length,
4371
+ flagged_ids: result.flagged.map((s) => s.id),
4372
+ total: final.length,
4373
+ _hints: buildHints({
4374
+ next: result.created.length > 0 ? `${result.created.length} new candidate schema(s) found. Use action="activate" to promote.` : "Schema detection complete.",
4375
+ related: ["datacore.schemas"]
4376
+ })
4377
+ };
4378
+ }
4379
+ function setStatus(schemasPath, id, newStatus) {
4380
+ const schemas = loadSchemas(schemasPath);
4381
+ const schema = schemas.find((s) => s.id === id);
4382
+ if (!schema) throw new Error(`Schema not found: ${id}`);
4383
+ schema.status = newStatus;
4384
+ schema.updated = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4385
+ saveSchemas(schemasPath, schemas);
4386
+ return {
4387
+ id: schema.id,
4388
+ status: newStatus,
4389
+ _hints: buildHints({ next: `Schema ${id} is now ${newStatus}.`, related: ["datacore.schemas"] })
4390
+ };
4391
+ }
4392
+ function mergeSchemas(schemasPath, sourceId, targetId) {
4393
+ const schemas = loadSchemas(schemasPath);
4394
+ const source = schemas.find((s) => s.id === sourceId);
4395
+ const target = schemas.find((s) => s.id === targetId);
4396
+ if (!source) throw new Error(`Source schema not found: ${sourceId}`);
4397
+ if (!target) throw new Error(`Target schema not found: ${targetId}`);
4398
+ const memberSet = /* @__PURE__ */ new Set([...target.members, ...source.members]);
4399
+ target.members = Array.from(memberSet).sort();
4400
+ const anchorSet = /* @__PURE__ */ new Set([...target.shared_anchors, ...source.shared_anchors]);
4401
+ target.shared_anchors = Array.from(anchorSet);
4402
+ target.confidence = Math.max(target.confidence, source.confidence);
4403
+ const statusOrder = { archived: 0, candidate: 1, active: 2, consolidated: 3 };
4404
+ if (statusOrder[source.status] > statusOrder[target.status]) {
4405
+ target.status = source.status;
4406
+ }
4407
+ target.updated = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4408
+ source.status = "archived";
4409
+ source.updated = target.updated;
4410
+ saveSchemas(schemasPath, schemas);
4411
+ return {
4412
+ merged_into: target.id,
4413
+ archived: source.id,
4414
+ members: target.members.length,
4415
+ _hints: buildHints({ next: `Merged ${sourceId} into ${targetId}.`, related: ["datacore.schemas"] })
4416
+ };
4417
+ }
4418
+ function splitSchema(schemasPath, id, memberIds, name) {
4419
+ const schemas = loadSchemas(schemasPath);
4420
+ const schema = schemas.find((s) => s.id === id);
4421
+ if (!schema) throw new Error(`Schema not found: ${id}`);
4422
+ const schemaMembers = new Set(schema.members);
4423
+ for (const memberId of memberIds) {
4424
+ if (!schemaMembers.has(memberId)) throw new Error(`${memberId} is not a member of ${id}`);
4425
+ }
4426
+ schema.members = schema.members.filter((m) => !memberIds.includes(m));
4427
+ schema.updated = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4428
+ const newSchema = {
4429
+ id: generateSchemaId(schemas),
4430
+ name: name ?? `Split from ${schema.name}`,
4431
+ members: memberIds.sort(),
4432
+ confidence: schema.confidence * 0.8,
4433
+ status: "candidate",
4434
+ shared_anchors: [],
4435
+ created: schema.updated,
4436
+ updated: schema.updated
4437
+ };
4438
+ schemas.push(newSchema);
4439
+ saveSchemas(schemasPath, schemas);
4440
+ return {
4441
+ original: { id: schema.id, remaining_members: schema.members.length },
4442
+ new_schema: { id: newSchema.id, members: newSchema.members.length },
4443
+ _hints: buildHints({ next: `Split complete. New schema: ${newSchema.id}`, related: ["datacore.schemas"] })
4444
+ };
4445
+ }
4446
+ function migrateRelations(engramsPath, confirm) {
4447
+ const engrams = loadEngrams(engramsPath);
4448
+ let migratedCount = 0;
4449
+ let totalAssociations = 0;
4450
+ for (const engram of engrams) {
4451
+ if (engram.pack) continue;
4452
+ if (!engram.relations) continue;
4453
+ const converted = flattenRelations(engram);
4454
+ if (converted.length === 0) continue;
4455
+ if (confirm) {
4456
+ const existingTargets = new Set(engram.associations.map((a) => `${a.target}:${a.type}`));
4457
+ for (const assoc of converted) {
4458
+ const key = `${assoc.target}:${assoc.type}`;
4459
+ if (!existingTargets.has(key)) {
4460
+ engram.associations.push(assoc);
4461
+ totalAssociations++;
4462
+ }
4463
+ }
4464
+ delete engram.relations;
4465
+ migratedCount++;
4466
+ } else {
4467
+ migratedCount++;
4468
+ totalAssociations += converted.length;
4469
+ }
4470
+ }
4471
+ if (confirm && migratedCount > 0) {
4472
+ atomicWriteYaml(engramsPath, { engrams });
4473
+ }
4474
+ return {
4475
+ action: confirm ? "executed" : "preview",
4476
+ engrams_with_relations: migratedCount,
4477
+ associations_created: totalAssociations,
4478
+ _hints: buildHints({
4479
+ next: confirm ? `Migrated ${migratedCount} engram(s) from relations to associations.` : `Preview: ${migratedCount} engram(s) to migrate. Set confirm=true to execute.`,
4480
+ related: ["datacore.schemas"]
4481
+ })
4482
+ };
4483
+ }
4484
+
4485
+ // src/tools/exchange.ts
4486
+ import * as fs21 from "fs";
4487
+ import * as path19 from "path";
4488
+ import * as yaml10 from "js-yaml";
4489
+
4490
+ // src/exchange.ts
4491
+ import { z as z6 } from "zod";
4492
+ var LEPEngramSchema = z6.object({
4493
+ id: z6.string(),
4494
+ type: z6.enum(["behavioral", "terminological", "procedural", "architectural"]),
4495
+ scope: z6.string(),
4496
+ statement: z6.string(),
4497
+ rationale: z6.string().optional(),
4498
+ domain: z6.string().optional(),
4499
+ tags: z6.array(z6.string()).default([]),
4500
+ fitness: z6.number().min(0).max(1),
4501
+ provenance: z6.object({
4502
+ origin: z6.string(),
4503
+ chain: z6.array(z6.string()).default([])
4504
+ }).optional()
4505
+ });
4506
+ var LEPPacketSchema = z6.object({
4507
+ id: z6.string(),
4508
+ sender: z6.string(),
4509
+ signature: z6.string().nullable().default(null),
4510
+ created: z6.string(),
4511
+ engrams: z6.array(LEPEngramSchema)
4512
+ });
4513
+ function calculateFitness(engram, allEngrams) {
4514
+ const adoptionCount = engram.derivation_count;
4515
+ const adoptionBase = Math.min(Math.log2(adoptionCount + 1) / 6, 1);
4516
+ const associatedScopes = /* @__PURE__ */ new Set();
4517
+ for (const assoc of engram.associations) {
4518
+ if (assoc.target_type !== "engram") continue;
4519
+ const target = allEngrams.find((e) => e.id === assoc.target);
4520
+ if (target) {
4521
+ const prefix = target.scope.split(":")[0];
4522
+ associatedScopes.add(prefix);
4523
+ }
4524
+ }
4525
+ const envDiversity = Math.max(associatedScopes.size, 1);
4526
+ const envDiversityNorm = Math.min(envDiversity / 5, 1);
4527
+ const adoptionScore = adoptionBase * envDiversityNorm * 0.4;
4528
+ const rsScore = engram.activation.retrieval_strength * 0.3;
4529
+ const createdDate = parseEngramDate(engram.id);
4530
+ const ageDays = createdDate ? Math.max(0, (Date.now() - createdDate.getTime()) / 864e5) : 0;
4531
+ const ageScore = Math.min(Math.log(ageDays + 1) / 7, 1) * 0.2;
4532
+ const feedback = engram.feedback_signals;
4533
+ const totalFeedback = feedback ? feedback.positive + feedback.negative + feedback.neutral : 0;
4534
+ const contradictionRate = totalFeedback > 0 && feedback ? feedback.negative / totalFeedback : 0;
4535
+ const contradictionScore = (1 - contradictionRate) * 0.1;
4536
+ return Math.round((adoptionScore + rsScore + ageScore + contradictionScore) * 1e3) / 1e3;
4537
+ }
4538
+ function parseEngramDate(id) {
4539
+ const match = id.match(/^ENG-(\d{4})-(\d{4})-/);
4540
+ if (!match) return null;
4541
+ const year = match[1];
4542
+ const mmdd = match[2];
4543
+ const month = mmdd.slice(0, 2);
4544
+ const day = mmdd.slice(2, 4);
4545
+ return /* @__PURE__ */ new Date(`${year}-${month}-${day}`);
4546
+ }
4547
+ function createLEPPacket(engrams, allEngrams, sender) {
4548
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4549
+ const id = `LEP-${today.replace(/-/g, "").slice(0, 4)}-${today.replace(/-/g, "").slice(4)}-${String(Date.now()).slice(-3)}`;
4550
+ const lepEngrams = engrams.filter((e) => e.visibility === "public" || e.visibility === "template").map((e) => ({
4551
+ id: e.id,
4552
+ type: e.type,
4553
+ scope: e.scope,
4554
+ statement: e.statement,
4555
+ rationale: e.rationale,
4556
+ domain: e.domain,
4557
+ tags: e.tags,
4558
+ fitness: calculateFitness(e, allEngrams),
4559
+ provenance: {
4560
+ origin: sender,
4561
+ chain: [id]
4562
+ }
4563
+ }));
4564
+ return { id, sender, signature: null, created: today, engrams: lepEngrams };
4565
+ }
4566
+ function validateLEPPacket(raw) {
4567
+ return LEPPacketSchema.parse(raw);
4568
+ }
4569
+ function levenshteinDistance(a, b) {
4570
+ const m = a.length;
4571
+ const n = b.length;
4572
+ const dp = Array.from({ length: n + 1 }, (_, i) => i);
4573
+ for (let i = 1; i <= m; i++) {
4574
+ let prev = dp[0];
4575
+ dp[0] = i;
4576
+ for (let j = 1; j <= n; j++) {
4577
+ const tmp = dp[j];
4578
+ dp[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
4579
+ prev = tmp;
4580
+ }
4581
+ }
4582
+ return dp[n];
4583
+ }
4584
+ function normalizeStatement(s) {
4585
+ return s.toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, " ").trim();
4586
+ }
4587
+ function importLEPEngrams(packet, existing, config = {}) {
4588
+ const sourceCapPercent = config.sourceCapPercent ?? 0.2;
4589
+ const fitnessThreshold = config.fitnessThreshold ?? 0.3;
4590
+ const personalCount = existing.filter((e) => !e.pack).length;
4591
+ if (personalCount > 0) {
4592
+ const fromSender = existing.filter((e) => e.provenance?.origin === packet.sender).length;
4593
+ if (fromSender / personalCount > sourceCapPercent) {
4594
+ return {
4595
+ imported: 0,
4596
+ skipped_fitness: 0,
4597
+ skipped_duplicate: 0,
4598
+ skipped_source_cap: true,
4599
+ candidates: []
4600
+ };
4601
+ }
4602
+ }
4603
+ const existingNormalized = existing.map((e) => normalizeStatement(e.statement));
4604
+ let skippedFitness = 0;
4605
+ let skippedDuplicate = 0;
4606
+ const candidates = [];
4607
+ const now = /* @__PURE__ */ new Date();
4608
+ const today = now.toISOString().split("T")[0];
4609
+ const trialExpiry = new Date(now);
4610
+ trialExpiry.setDate(trialExpiry.getDate() + 30);
4611
+ const trialExpiryTag = `_trial_expires:${trialExpiry.toISOString().split("T")[0]}`;
4612
+ for (const lepEngram of packet.engrams) {
4613
+ if (lepEngram.fitness < fitnessThreshold) {
4614
+ skippedFitness++;
4615
+ continue;
4616
+ }
4617
+ const normalized = normalizeStatement(lepEngram.statement);
4618
+ let isDuplicate = false;
4619
+ for (const existingNorm of existingNormalized) {
4620
+ const dist = levenshteinDistance(normalized, existingNorm);
4621
+ const maxLen = Math.max(normalized.length, existingNorm.length);
4622
+ if (maxLen > 0 && dist / maxLen < 0.15) {
4623
+ isDuplicate = true;
4624
+ break;
4625
+ }
4626
+ }
4627
+ if (isDuplicate) {
4628
+ skippedDuplicate++;
4629
+ continue;
4630
+ }
4631
+ const newId = generateEngramId(existing);
4632
+ candidates.push({
4633
+ id: newId,
4634
+ statement: lepEngram.statement,
4635
+ fitness: lepEngram.fitness
4636
+ });
4637
+ const newEngram = {
4638
+ id: newId,
4639
+ version: 2,
4640
+ status: "candidate",
4641
+ consolidated: false,
4642
+ type: lepEngram.type,
4643
+ scope: lepEngram.scope,
4644
+ visibility: "private",
4645
+ statement: lepEngram.statement,
4646
+ rationale: lepEngram.rationale,
4647
+ derivation_count: 1,
4648
+ domain: lepEngram.domain,
4649
+ knowledge_anchors: [],
4650
+ associations: [],
4651
+ tags: [...lepEngram.tags, "_trial", trialExpiryTag],
4652
+ activation: {
4653
+ retrieval_strength: 0.5,
4654
+ storage_strength: 0.3,
4655
+ frequency: 0,
4656
+ last_accessed: today
4657
+ },
4658
+ pack: packet.id,
4659
+ abstract: null,
4660
+ derived_from: lepEngram.id,
4661
+ provenance: {
4662
+ origin: lepEngram.provenance?.origin ?? packet.sender,
4663
+ chain: [...lepEngram.provenance?.chain ?? [], packet.id],
4664
+ signature: null,
4665
+ license: "cc-by-sa-4.0"
4666
+ }
4667
+ };
4668
+ existing.push(newEngram);
4669
+ existingNormalized.push(normalized);
4670
+ }
4671
+ return {
4672
+ imported: candidates.length,
4673
+ skipped_fitness: skippedFitness,
4674
+ skipped_duplicate: skippedDuplicate,
4675
+ skipped_source_cap: false,
4676
+ candidates
4677
+ };
4678
+ }
4679
+
4680
+ // src/tools/exchange.ts
4681
+ async function handleExchange(args2, paths, engagementService2) {
4682
+ switch (args2.action) {
4683
+ case "export":
4684
+ return exportAction(args2, paths, engagementService2);
4685
+ case "import":
4686
+ return importAction(args2, paths);
4687
+ case "status":
4688
+ return statusAction(paths);
4689
+ default:
4690
+ throw new Error(`Unknown exchange action: ${args2.action}`);
4691
+ }
4692
+ }
4693
+ async function exportAction(args2, paths, engagementService2) {
4694
+ const allEngrams = loadEngrams(paths.engramsPath);
4695
+ const sender = args2.sender ?? "anonymous";
4696
+ let candidates = allEngrams.filter(
4697
+ (e) => !e.pack && e.status === "active" && (e.visibility === "public" || e.visibility === "template")
4698
+ );
4699
+ if (args2.engram_ids?.length) {
4700
+ const idSet = new Set(args2.engram_ids);
4701
+ candidates = candidates.filter((e) => idSet.has(e.id));
4702
+ }
4703
+ if (args2.filter_domain) {
4704
+ candidates = candidates.filter((e) => e.domain?.startsWith(args2.filter_domain));
4705
+ }
4706
+ if (candidates.length === 0) {
4707
+ return {
4708
+ exported: 0,
4709
+ message: "No eligible engrams found. Only public/template visibility engrams can be exported.",
4710
+ _hints: buildHints({ next: "Set visibility to public or template on engrams you want to share.", related: ["datacore.learn"] })
4711
+ };
4712
+ }
4713
+ const packet = createLEPPacket(candidates, allEngrams, sender);
4714
+ fs21.mkdirSync(paths.exchangeOutboxPath, { recursive: true });
4715
+ const outPath = path19.join(paths.exchangeOutboxPath, `${packet.id}.yaml`);
4716
+ fs21.writeFileSync(outPath, yaml10.dump(packet, { lineWidth: 120, noRefs: true, quotingType: '"' }));
4717
+ if (engagementService2?.isEnabled()) {
4718
+ try {
4719
+ await engagementService2.award("pack_exported", { count: packet.engrams.length });
4720
+ } catch {
4721
+ }
4722
+ }
4723
+ return {
4724
+ exported: packet.engrams.length,
4725
+ packet_id: packet.id,
4726
+ path: outPath,
4727
+ _hints: buildHints({ next: `Exported ${packet.engrams.length} engram(s) to ${outPath}`, related: ["datacore.exchange"] })
4728
+ };
4729
+ }
4730
+ async function importAction(args2, paths) {
4731
+ if (!args2.path) throw new Error("path is required for import action");
4732
+ if (!fs21.existsSync(args2.path)) throw new Error(`File not found: ${args2.path}`);
4733
+ const raw = yaml10.load(fs21.readFileSync(args2.path, "utf8"));
4734
+ const packet = validateLEPPacket(raw);
4735
+ const existing = loadEngrams(paths.engramsPath);
4736
+ const result = importLEPEngrams(packet, existing, {
4737
+ fitnessThreshold: args2.fitness_threshold,
4738
+ sourceCapPercent: args2.source_cap_percent
4739
+ });
4740
+ if (result.skipped_source_cap) {
4741
+ return {
4742
+ imported: 0,
4743
+ message: `Source cap exceeded: too many engrams already from ${packet.sender}.`,
4744
+ _hints: buildHints({ next: "Remove some imported engrams from this source first.", related: ["datacore.forget"] })
4745
+ };
4746
+ }
4747
+ if (args2.confirm && result.imported > 0) {
4748
+ saveEngrams(paths.engramsPath, existing);
4749
+ return {
4750
+ imported: result.imported,
4751
+ skipped_fitness: result.skipped_fitness,
4752
+ skipped_duplicate: result.skipped_duplicate,
4753
+ candidates: result.candidates,
4754
+ _hints: buildHints({
4755
+ next: `Imported ${result.imported} engram(s) as candidates. Use datacore.promote to activate.`,
4756
+ related: ["datacore.promote"]
4757
+ })
4758
+ };
4759
+ }
4760
+ return {
4761
+ action: "preview",
4762
+ would_import: result.imported,
4763
+ skipped_fitness: result.skipped_fitness,
4764
+ skipped_duplicate: result.skipped_duplicate,
4765
+ candidates: result.candidates,
4766
+ _hints: buildHints({
4767
+ next: result.imported > 0 ? `Preview: ${result.imported} engram(s) would be imported. Set confirm=true to execute.` : "No engrams passed fitness and duplicate filters.",
4768
+ related: ["datacore.exchange"]
4769
+ })
4770
+ };
4771
+ }
4772
+ function statusAction(paths) {
4773
+ const inboxCount = countYamlFiles(paths.exchangeInboxPath);
4774
+ const outboxCount = countYamlFiles(paths.exchangeOutboxPath);
4775
+ return {
4776
+ inbox: inboxCount,
4777
+ outbox: outboxCount,
4778
+ _hints: buildHints({
4779
+ next: inboxCount > 0 ? `${inboxCount} packet(s) in inbox. Use action="import" with path to process.` : "No packets in inbox.",
4780
+ related: ["datacore.exchange"]
4781
+ })
4782
+ };
4783
+ }
4784
+ function countYamlFiles(dir) {
4785
+ if (!fs21.existsSync(dir)) return 0;
4786
+ return fs21.readdirSync(dir).filter((f) => f.endsWith(".yaml")).length;
4787
+ }
4788
+
4789
+ // src/knowledge-surfacing.ts
4790
+ import * as fs22 from "fs";
4791
+ import * as path20 from "path";
4792
+ import * as yaml11 from "js-yaml";
4793
+ import { z as z7 } from "zod";
4794
+ var ZettelCandidateSchema = z7.object({
4795
+ path: z7.string(),
4796
+ title: z7.string(),
4797
+ suggested_statement: z7.string(),
4798
+ suggested_type: z7.enum(["behavioral", "terminological", "procedural", "architectural"]),
4799
+ suggested_scope: z7.string(),
4800
+ suggested_anchors: z7.array(z7.string()),
4801
+ confidence: z7.number().min(0).max(1),
4802
+ status: z7.enum(["pending", "accepted", "rejected"]),
4803
+ created: z7.string()
4804
+ });
4805
+ var defaultState = {
4806
+ last_zettel_scan: null,
4807
+ scanned_paths: [],
4808
+ zettel_candidates: [],
4809
+ last_consolidation: null,
4810
+ consolidation_results: null
4811
+ };
4812
+ function loadKnowledgeSurfacing(filePath) {
4813
+ if (!fs22.existsSync(filePath)) return { ...defaultState };
4814
+ try {
4815
+ const raw = yaml11.load(fs22.readFileSync(filePath, "utf8"));
4816
+ return { ...defaultState, ...raw };
4817
+ } catch {
4818
+ return { ...defaultState };
4819
+ }
4820
+ }
4821
+ function saveKnowledgeSurfacing(filePath, state) {
4822
+ const dir = path20.dirname(filePath);
4823
+ if (!fs22.existsSync(dir)) fs22.mkdirSync(dir, { recursive: true });
4824
+ const content = yaml11.dump(state, { lineWidth: 120, noRefs: true, quotingType: '"' });
4825
+ const tmpPath = filePath + ".tmp." + process.pid;
4826
+ fs22.writeFileSync(tmpPath, content);
4827
+ fs22.renameSync(tmpPath, filePath);
4828
+ }
4829
+ var ACTIONABLE_WORDS = /\b(should|always|never|when|before|after|must|avoid|prefer|ensure|make sure)\b/i;
4830
+ var PROCEDURAL_PATTERNS = /(\d+\.\s|\bhow to\b|step \d+)/i;
4831
+ var DECISION_PATTERNS = /\b(because|due to|trade-?off|we chose|decided|rationale)\b/i;
4832
+ function scanZettels(knowledgePath, state) {
4833
+ const zettelDir = path20.join(knowledgePath, "zettel");
4834
+ if (!fs22.existsSync(zettelDir)) return [];
4835
+ const scannedSet = new Set(state.scanned_paths);
4836
+ const candidates = [];
4837
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4838
+ const files = globMd(zettelDir);
4839
+ for (const filePath of files) {
4840
+ const relativePath = path20.relative(path20.dirname(knowledgePath), filePath);
4841
+ if (scannedSet.has(relativePath)) continue;
4842
+ const content = fs22.readFileSync(filePath, "utf8");
4843
+ const candidate = analyzeZettel(content, relativePath, today);
4844
+ if (candidate) candidates.push(candidate);
4845
+ state.scanned_paths.push(relativePath);
4846
+ }
4847
+ state.last_zettel_scan = today;
4848
+ return candidates;
4849
+ }
4850
+ function globMd(dir) {
4851
+ const results = [];
4852
+ if (!fs22.existsSync(dir)) return results;
4853
+ for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
4854
+ const full = path20.join(dir, entry.name);
4855
+ if (entry.isDirectory()) {
4856
+ results.push(...globMd(full));
4857
+ } else if (entry.name.endsWith(".md")) {
4858
+ results.push(full);
4859
+ }
4860
+ }
4861
+ return results;
4862
+ }
4863
+ function analyzeZettel(content, relativePath, today) {
4864
+ const lines = content.split("\n");
4865
+ const title = extractTitle2(lines) ?? path20.basename(relativePath, ".md");
4866
+ const hasActionable = ACTIONABLE_WORDS.test(content);
4867
+ const hasProcedural = PROCEDURAL_PATTERNS.test(content);
4868
+ const hasDecision = DECISION_PATTERNS.test(content);
4869
+ if (!hasActionable && !hasProcedural && !hasDecision) return null;
4870
+ let type = "behavioral";
4871
+ if (hasProcedural) type = "procedural";
4872
+ else if (hasDecision) type = "architectural";
4873
+ let confidence = 0.4;
4874
+ if (hasActionable) confidence += 0.3;
4875
+ if (hasProcedural || hasDecision) confidence += 0.1;
4876
+ confidence = Math.min(confidence, 1);
4877
+ if (confidence < 0.4) return null;
4878
+ const statement = extractActionableSentence(content) ?? `${title}: ${firstParagraph(content)}`;
4879
+ return {
4880
+ path: relativePath,
4881
+ title,
4882
+ suggested_statement: statement.slice(0, 300),
4883
+ suggested_type: type,
4884
+ suggested_scope: "global",
4885
+ suggested_anchors: [relativePath],
4886
+ confidence: Math.round(confidence * 100) / 100,
4887
+ status: "pending",
4888
+ created: today
4889
+ };
4890
+ }
4891
+ function extractTitle2(lines) {
4892
+ for (const line of lines) {
4893
+ if (line.startsWith("# ")) return line.slice(2).trim();
4894
+ }
4895
+ return null;
4896
+ }
4897
+ function extractActionableSentence(content) {
4898
+ const sentences = content.split(/(?<=[.!?])\s+/);
4899
+ for (const sentence of sentences) {
4900
+ if (ACTIONABLE_WORDS.test(sentence) && sentence.length > 20) {
4901
+ return sentence.replace(/^[-*#>\s]+/, "").trim();
4902
+ }
4903
+ }
4904
+ return null;
4905
+ }
4906
+ function firstParagraph(content) {
4907
+ const lines = content.split("\n");
4908
+ const para = [];
4909
+ let started = false;
4910
+ for (const line of lines) {
4911
+ if (line.startsWith("#")) continue;
4912
+ if (line.trim() === "") {
4913
+ if (started) break;
4914
+ continue;
4915
+ }
4916
+ started = true;
4917
+ para.push(line.trim());
4918
+ }
4919
+ return para.join(" ").slice(0, 200);
4920
+ }
4921
+ function normalizeStatement2(s) {
4922
+ return s.toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, " ").trim();
4923
+ }
4924
+ function consolidationPass(engramsPath, confirm = false) {
4925
+ const engrams = loadEngrams(engramsPath);
4926
+ const active = engrams.filter((e) => e.status === "active" && !e.pack);
4927
+ const thirtyDaysAgo = /* @__PURE__ */ new Date();
4928
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
4929
+ const thirtyDaysAgoStr = thirtyDaysAgo.toISOString().split("T")[0];
4930
+ const lowRs = active.filter(
4931
+ (e) => e.activation.retrieval_strength < 0.15 && e.activation.last_accessed < thirtyDaysAgoStr
4932
+ ).map((e) => ({
4933
+ id: e.id,
4934
+ rs: e.activation.retrieval_strength,
4935
+ last_accessed: e.activation.last_accessed
4936
+ }));
4937
+ const normalized = active.map((e) => ({
4938
+ id: e.id,
4939
+ norm: normalizeStatement2(e.statement),
4940
+ rs: e.activation.retrieval_strength
4941
+ }));
4942
+ const parent = /* @__PURE__ */ new Map();
4943
+ const find = (x) => {
4944
+ let root = x;
4945
+ while (parent.has(root) && parent.get(root) !== root) root = parent.get(root);
4946
+ let curr = x;
4947
+ while (curr !== root) {
4948
+ const next = parent.get(curr) ?? curr;
4949
+ parent.set(curr, root);
4950
+ curr = next;
4951
+ }
4952
+ return root;
4953
+ };
4954
+ const union = (a, b) => {
4955
+ const ra = find(a);
4956
+ const rb = find(b);
4957
+ if (ra !== rb) parent.set(ra, rb);
4958
+ };
4959
+ for (const item of normalized) parent.set(item.id, item.id);
4960
+ for (let i = 0; i < normalized.length; i++) {
4961
+ for (let j = i + 1; j < normalized.length; j++) {
4962
+ const a = normalized[i];
4963
+ const b = normalized[j];
4964
+ const maxLen = Math.max(a.norm.length, b.norm.length);
4965
+ if (maxLen === 0) continue;
4966
+ const dist = levenshteinDistance(a.norm, b.norm);
4967
+ if (dist / maxLen < 0.3) {
4968
+ union(a.id, b.id);
4969
+ }
4970
+ }
4971
+ }
4972
+ const clusters = /* @__PURE__ */ new Map();
4973
+ for (const item of normalized) {
4974
+ const root = find(item.id);
4975
+ if (!clusters.has(root)) clusters.set(root, []);
4976
+ clusters.get(root).push(item.id);
4977
+ }
4978
+ const duplicateClusters = [];
4979
+ for (const [, members] of clusters) {
4980
+ if (members.length < 2) continue;
4981
+ const sorted = members.map((id) => ({ id, rs: normalized.find((n) => n.id === id).rs })).sort((a, b) => b.rs - a.rs);
4982
+ duplicateClusters.push({
4983
+ representative: sorted[0].id,
4984
+ duplicates: sorted.slice(1).map((s) => s.id),
4985
+ similarity: 0.7
4986
+ // approximate
4987
+ });
4988
+ }
4989
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4990
+ if (confirm) {
4991
+ const toRetire = /* @__PURE__ */ new Set([
4992
+ ...lowRs.map((e) => e.id),
4993
+ ...duplicateClusters.flatMap((c) => c.duplicates)
4994
+ ]);
4995
+ for (const engram of engrams) {
4996
+ if (toRetire.has(engram.id)) {
4997
+ engram.status = "retired";
4998
+ }
4999
+ }
5000
+ for (const cluster of duplicateClusters) {
5001
+ const rep = engrams.find((e) => e.id === cluster.representative);
5002
+ if (rep) rep.derivation_count += cluster.duplicates.length;
5003
+ }
5004
+ if (toRetire.size > 0) {
5005
+ const content = yaml11.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
5006
+ const tmpPath = engramsPath + ".tmp." + process.pid;
5007
+ fs22.writeFileSync(tmpPath, content);
5008
+ fs22.renameSync(tmpPath, engramsPath);
5009
+ }
5010
+ }
5011
+ return {
5012
+ low_rs_engrams: lowRs,
5013
+ duplicate_clusters: duplicateClusters,
5014
+ action_taken: confirm ? "executed" : "preview",
5015
+ executed_at: confirm ? today : null
5016
+ };
5017
+ }
5018
+
5019
+ // src/tools/knowledge-scan.ts
5020
+ async function handleKnowledgeScan(args2, paths) {
5021
+ switch (args2.action) {
5022
+ case "scan_zettels":
5023
+ return scanZettelsAction(paths);
5024
+ case "scan_status":
5025
+ return scanStatusAction(paths.knowledgeSurfacingPath);
5026
+ case "consolidation_pass":
5027
+ return consolidationAction(paths, args2.confirm);
5028
+ default:
5029
+ throw new Error(`Unknown knowledge.scan action: ${args2.action}`);
5030
+ }
5031
+ }
5032
+ function scanZettelsAction(paths) {
5033
+ const state = loadKnowledgeSurfacing(paths.knowledgeSurfacingPath);
5034
+ const candidates = scanZettels(paths.knowledgePath, state);
5035
+ state.zettel_candidates.push(...candidates);
5036
+ saveKnowledgeSurfacing(paths.knowledgeSurfacingPath, state);
5037
+ return {
5038
+ new_candidates: candidates.length,
5039
+ total_scanned: state.scanned_paths.length,
5040
+ total_pending: state.zettel_candidates.filter((c) => c.status === "pending").length,
5041
+ candidates: candidates.map((c) => ({
5042
+ path: c.path,
5043
+ title: c.title,
5044
+ suggested_statement: c.suggested_statement,
5045
+ suggested_type: c.suggested_type,
5046
+ confidence: c.confidence
5047
+ })),
5048
+ _hints: buildHints({
5049
+ next: candidates.length > 0 ? `Found ${candidates.length} zettel-to-engram candidate(s). Review and use datacore.learn to create engrams.` : "No new candidates found.",
5050
+ related: ["datacore.learn", "datacore.knowledge.scan"]
5051
+ })
5052
+ };
5053
+ }
5054
+ function scanStatusAction(surfacingPath) {
5055
+ const state = loadKnowledgeSurfacing(surfacingPath);
5056
+ const pending = state.zettel_candidates.filter((c) => c.status === "pending").length;
5057
+ const accepted = state.zettel_candidates.filter((c) => c.status === "accepted").length;
5058
+ const rejected = state.zettel_candidates.filter((c) => c.status === "rejected").length;
5059
+ return {
5060
+ last_scan: state.last_zettel_scan,
5061
+ scanned_paths: state.scanned_paths.length,
5062
+ candidates: { pending, accepted, rejected, total: state.zettel_candidates.length },
5063
+ last_consolidation: state.last_consolidation,
5064
+ _hints: buildHints({
5065
+ next: pending > 0 ? `${pending} pending candidate(s). Use scan_zettels to find more, or review candidates.` : "All candidates processed.",
5066
+ related: ["datacore.knowledge.scan"]
5067
+ })
5068
+ };
5069
+ }
5070
+ function consolidationAction(paths, confirm) {
5071
+ const result = consolidationPass(paths.engramsPath, confirm ?? false);
5072
+ const state = loadKnowledgeSurfacing(paths.knowledgeSurfacingPath);
5073
+ state.last_consolidation = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5074
+ state.consolidation_results = result;
5075
+ saveKnowledgeSurfacing(paths.knowledgeSurfacingPath, state);
5076
+ return {
5077
+ low_rs_count: result.low_rs_engrams.length,
5078
+ duplicate_clusters: result.duplicate_clusters.length,
5079
+ action: result.action_taken,
5080
+ low_rs_engrams: result.low_rs_engrams.slice(0, 10),
5081
+ clusters: result.duplicate_clusters.slice(0, 5).map((c) => ({
5082
+ representative: c.representative,
5083
+ duplicates: c.duplicates.length
5084
+ })),
5085
+ _hints: buildHints({
5086
+ next: result.action_taken === "preview" ? `Preview: ${result.low_rs_engrams.length} low-RS + ${result.duplicate_clusters.length} duplicate cluster(s). Set confirm=true to execute.` : `Consolidated: retired ${result.low_rs_engrams.length + result.duplicate_clusters.flatMap((c) => c.duplicates).length} engram(s).`,
5087
+ related: ["datacore.knowledge.scan"]
5088
+ })
5089
+ };
5090
+ }
5091
+
3916
5092
  // src/resources.ts
3917
5093
  import {
3918
5094
  ListResourcesRequestSchema,
3919
5095
  ReadResourceRequestSchema,
3920
5096
  ListResourceTemplatesRequestSchema
3921
5097
  } from "@modelcontextprotocol/sdk/types.js";
3922
- import * as fs19 from "fs";
3923
- import * as path19 from "path";
5098
+ import * as fs23 from "fs";
5099
+ import * as path21 from "path";
3924
5100
  function registerResources(server, storage2) {
3925
5101
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
3926
5102
  resources: [
@@ -4001,11 +5177,11 @@ function registerResources(server, storage2) {
4001
5177
  const journalMatch = uri.match(/^datacore:\/\/journal\/(.+)$/);
4002
5178
  if (journalMatch) {
4003
5179
  const dateStr = journalMatch[1] === "today" ? localDate().date : journalMatch[1];
4004
- const filePath = path19.join(storage2.journalPath, `${dateStr}.md`);
4005
- if (!fs19.existsSync(filePath)) {
5180
+ const filePath = path21.join(storage2.journalPath, `${dateStr}.md`);
5181
+ if (!fs23.existsSync(filePath)) {
4006
5182
  return { contents: [{ uri, mimeType: "text/markdown", text: `No journal entry for ${dateStr}` }] };
4007
5183
  }
4008
- return { contents: [{ uri, mimeType: "text/markdown", text: fs19.readFileSync(filePath, "utf8") }] };
5184
+ return { contents: [{ uri, mimeType: "text/markdown", text: fs23.readFileSync(filePath, "utf8") }] };
4009
5185
  }
4010
5186
  const engramMatch = uri.match(/^datacore:\/\/engrams\/(.+)$/);
4011
5187
  if (engramMatch) {
@@ -4251,8 +5427,8 @@ function registerPrompts(server) {
4251
5427
 
4252
5428
  // src/datacortex.ts
4253
5429
  import { execFile } from "child_process";
4254
- import * as fs20 from "fs";
4255
- import * as path20 from "path";
5430
+ import * as fs24 from "fs";
5431
+ import * as path22 from "path";
4256
5432
  var DatacortexBridge = class {
4257
5433
  pythonPath;
4258
5434
  scriptPath;
@@ -4262,11 +5438,11 @@ var DatacortexBridge = class {
4262
5438
  }
4263
5439
  findBridgeScript(datacorePath) {
4264
5440
  const candidates = [
4265
- path20.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
4266
- path20.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
5441
+ path22.join(datacorePath, ".datacore", "modules", "datacortex", "lib", "bridge.py"),
5442
+ path22.join(datacorePath, ".datacore", "modules", "datacortex", "bridge.py")
4267
5443
  ];
4268
5444
  for (const candidate of candidates) {
4269
- if (fs20.existsSync(candidate)) return candidate;
5445
+ if (fs24.existsSync(candidate)) return candidate;
4270
5446
  }
4271
5447
  return null;
4272
5448
  }
@@ -4318,6 +5494,44 @@ var DatacortexBridge = class {
4318
5494
  }
4319
5495
  };
4320
5496
 
5497
+ // src/session-tracker.ts
5498
+ var SessionTracker = class {
5499
+ sessions = /* @__PURE__ */ new Map();
5500
+ trackInjected(sessionId, engramIds) {
5501
+ if (!sessionId || engramIds.length === 0) return;
5502
+ let set = this.sessions.get(sessionId);
5503
+ if (!set) {
5504
+ set = /* @__PURE__ */ new Set();
5505
+ this.sessions.set(sessionId, set);
5506
+ }
5507
+ for (const id of engramIds) {
5508
+ set.add(id);
5509
+ }
5510
+ }
5511
+ getCoAccessPairs(sessionId) {
5512
+ const set = this.sessions.get(sessionId);
5513
+ if (!set || set.size < 2) return [];
5514
+ const ids = Array.from(set).sort();
5515
+ const pairs = [];
5516
+ for (let i = 0; i < ids.length; i++) {
5517
+ for (let j = i + 1; j < ids.length; j++) {
5518
+ pairs.push([ids[i], ids[j]]);
5519
+ }
5520
+ }
5521
+ return pairs;
5522
+ }
5523
+ getInjectedIds(sessionId) {
5524
+ const set = this.sessions.get(sessionId);
5525
+ return set ? Array.from(set) : [];
5526
+ }
5527
+ clear(sessionId) {
5528
+ this.sessions.delete(sessionId);
5529
+ }
5530
+ get size() {
5531
+ return this.sessions.size;
5532
+ }
5533
+ };
5534
+
4321
5535
  // src/server.ts
4322
5536
  var storage;
4323
5537
  var updateAvailable = null;
@@ -4327,6 +5541,7 @@ var isFirstRun = false;
4327
5541
  var serverRef = null;
4328
5542
  var datacortexBridge = null;
4329
5543
  var engagementService = null;
5544
+ var sessionTracker = new SessionTracker();
4330
5545
  function getEngagementService() {
4331
5546
  if (!engagementService || engagementService.basePath !== storage.basePath) {
4332
5547
  const config = getConfig();
@@ -4382,7 +5597,7 @@ function createServer() {
4382
5597
  serverRef = server;
4383
5598
  return server;
4384
5599
  }
4385
- var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote", "datacore.resolve"]);
5600
+ var ENGRAM_MUTATING_TOOLS = /* @__PURE__ */ new Set(["datacore.learn", "datacore.forget", "datacore.feedback", "datacore.session.end", "datacore.promote", "datacore.resolve", "datacore.schemas", "datacore.exchange", "datacore.knowledge.scan"]);
4386
5601
  async function routeTool(name, args2) {
4387
5602
  const coreTool = TOOLS.find((t) => t.name === name);
4388
5603
  if (coreTool) {
@@ -4396,7 +5611,7 @@ async function routeTool(name, args2) {
4396
5611
  result = await handleLearn(validated, storage.engramsPath, getEngagementService());
4397
5612
  break;
4398
5613
  case "datacore.inject":
4399
- result = await handleInject(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath, basePath: storage.basePath });
5614
+ result = await handleInject(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath, basePath: storage.basePath, schemasPath: storage.schemasPath });
4400
5615
  break;
4401
5616
  case "datacore.search":
4402
5617
  result = await handleSearch(validated, { journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
@@ -4414,10 +5629,10 @@ async function routeTool(name, args2) {
4414
5629
  result = await handleFeedback(validated, storage.engramsPath, storage.packsPath, getEngagementService());
4415
5630
  break;
4416
5631
  case "datacore.session.start":
4417
- result = await handleSessionStart(validated, storage, datacortexBridge, getEngagementService());
5632
+ result = await handleSessionStart(validated, storage, datacortexBridge, getEngagementService(), sessionTracker);
4418
5633
  break;
4419
5634
  case "datacore.session.end":
4420
- result = await handleSessionEnd(validated, storage, getEngagementService());
5635
+ result = await handleSessionEnd(validated, storage, getEngagementService(), sessionTracker);
4421
5636
  break;
4422
5637
  case "datacore.recall":
4423
5638
  result = await handleRecall(validated, { engramsPath: storage.engramsPath, journalPath: storage.journalPath, knowledgePath: storage.knowledgePath }, datacortexBridge);
@@ -4434,6 +5649,15 @@ async function routeTool(name, args2) {
4434
5649
  case "datacore.packs.export":
4435
5650
  result = await handleExport(validated, { engramsPath: storage.engramsPath, packsPath: storage.packsPath }, getEngagementService());
4436
5651
  break;
5652
+ case "datacore.knowledge.scan":
5653
+ result = await handleKnowledgeScan(validated, { knowledgePath: storage.knowledgePath, knowledgeSurfacingPath: storage.knowledgeSurfacingPath, engramsPath: storage.engramsPath });
5654
+ break;
5655
+ case "datacore.exchange":
5656
+ result = await handleExchange(validated, { engramsPath: storage.engramsPath, exchangeInboxPath: storage.exchangeInboxPath, exchangeOutboxPath: storage.exchangeOutboxPath }, getEngagementService());
5657
+ break;
5658
+ case "datacore.schemas":
5659
+ result = await handleSchemas(validated, { schemasPath: storage.schemasPath, engramsPath: storage.engramsPath });
5660
+ break;
4437
5661
  case "datacore.resolve":
4438
5662
  result = await handleResolve(validated, storage.engramsPath, getEngagementService());
4439
5663
  break;
@@ -4452,6 +5676,13 @@ async function routeTool(name, args2) {
4452
5676
  if (ENGRAM_MUTATING_TOOLS.has(name) && serverRef) {
4453
5677
  notifyEngramsChanged(serverRef);
4454
5678
  }
5679
+ if (name === "datacore.inject" && result && typeof result === "object") {
5680
+ const injectResult = result;
5681
+ const sessionId = validated.session_id;
5682
+ if (sessionId && injectResult.injected_personal_ids?.length) {
5683
+ sessionTracker.trackInjected(sessionId, injectResult.injected_personal_ids);
5684
+ }
5685
+ }
4455
5686
  return result;
4456
5687
  }
4457
5688
  const modTool = moduleTools.find((t) => t.fullName === name);