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