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