@plur-ai/core 0.7.7 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-GRDNBUIJ.js +452 -0
- package/dist/{chunk-KMVQYBNP.js → chunk-MY4XVDCE.js} +1 -192
- package/dist/chunk-UETCDULF.js +196 -0
- package/dist/{embeddings-2IODIQAF.js → embeddings-EX7QPXJS.js} +2 -1
- package/dist/index.d.ts +693 -193
- package/dist/index.js +1320 -420
- package/dist/learn-async-VXBH3TYE.js +184 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,19 +1,42 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
computeIdf,
|
|
3
3
|
embeddingSearch,
|
|
4
4
|
engramSearchText,
|
|
5
5
|
ftsScore,
|
|
6
6
|
ftsTokenize,
|
|
7
|
+
searchEngrams
|
|
8
|
+
} from "./chunk-UETCDULF.js";
|
|
9
|
+
import {
|
|
10
|
+
EngramSchemaPassthrough,
|
|
11
|
+
appendHistory,
|
|
12
|
+
buildBatchDedupPrompt,
|
|
13
|
+
buildDedupPrompt,
|
|
14
|
+
computeContentHash,
|
|
15
|
+
generateEngramId,
|
|
16
|
+
generateEventId,
|
|
17
|
+
listHistoryMonths,
|
|
18
|
+
loadAllPacks,
|
|
19
|
+
loadEngrams,
|
|
20
|
+
loadPack,
|
|
21
|
+
logger,
|
|
22
|
+
normalizeStatement,
|
|
23
|
+
parseDedupResponse,
|
|
24
|
+
readHistory,
|
|
25
|
+
readHistoryForEngram,
|
|
26
|
+
saveEngrams,
|
|
27
|
+
storePrefix
|
|
28
|
+
} from "./chunk-GRDNBUIJ.js";
|
|
29
|
+
import {
|
|
30
|
+
atomicWrite,
|
|
7
31
|
getSyncStatus,
|
|
8
|
-
searchEngrams,
|
|
9
32
|
sync,
|
|
10
33
|
withLock
|
|
11
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-MY4XVDCE.js";
|
|
12
35
|
import "./chunk-2ZDO52B4.js";
|
|
13
36
|
|
|
14
37
|
// src/index.ts
|
|
15
|
-
import * as
|
|
16
|
-
import
|
|
38
|
+
import * as fs4 from "fs";
|
|
39
|
+
import yaml6 from "js-yaml";
|
|
17
40
|
|
|
18
41
|
// src/storage.ts
|
|
19
42
|
import { existsSync, mkdirSync } from "fs";
|
|
@@ -37,284 +60,8 @@ function detectPlurStorage(explicitPath) {
|
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
// src/storage-indexed.ts
|
|
40
|
-
import { existsSync as
|
|
63
|
+
import { existsSync as existsSync2 } from "fs";
|
|
41
64
|
import { createRequire } from "module";
|
|
42
|
-
|
|
43
|
-
// src/engrams.ts
|
|
44
|
-
import * as fs from "fs";
|
|
45
|
-
import * as yaml from "js-yaml";
|
|
46
|
-
|
|
47
|
-
// src/schemas/engram.ts
|
|
48
|
-
import { z } from "zod";
|
|
49
|
-
var ActivationSchema = z.object({
|
|
50
|
-
retrieval_strength: z.number().min(0).max(1),
|
|
51
|
-
storage_strength: z.number().min(0).max(1),
|
|
52
|
-
frequency: z.number().int().min(0),
|
|
53
|
-
last_accessed: z.string()
|
|
54
|
-
});
|
|
55
|
-
var KnowledgeTypeSchema = z.object({
|
|
56
|
-
memory_class: z.enum(["semantic", "episodic", "procedural", "metacognitive"]),
|
|
57
|
-
cognitive_level: z.enum(["remember", "understand", "apply", "analyze", "evaluate", "create"])
|
|
58
|
-
});
|
|
59
|
-
var KnowledgeAnchorSchema = z.object({
|
|
60
|
-
path: z.string(),
|
|
61
|
-
relevance: z.enum(["primary", "supporting", "example"]).default("supporting"),
|
|
62
|
-
snippet: z.string().max(200).optional(),
|
|
63
|
-
snippet_extracted_at: z.string().optional()
|
|
64
|
-
});
|
|
65
|
-
var AssociationSchema = z.object({
|
|
66
|
-
target_type: z.enum(["engram", "document"]),
|
|
67
|
-
target: z.string(),
|
|
68
|
-
strength: z.number().min(0).max(0.95),
|
|
69
|
-
type: z.enum(["semantic", "temporal", "causal", "co_accessed"]),
|
|
70
|
-
updated_at: z.string().optional()
|
|
71
|
-
});
|
|
72
|
-
var DualCodingSchema = z.object({
|
|
73
|
-
example: z.string().optional(),
|
|
74
|
-
analogy: z.string().optional()
|
|
75
|
-
}).refine(
|
|
76
|
-
(d) => d.example || d.analogy,
|
|
77
|
-
"At least one of example or analogy must be provided"
|
|
78
|
-
);
|
|
79
|
-
var RelationsSchema = z.object({
|
|
80
|
-
broader: z.array(z.string()).default([]),
|
|
81
|
-
narrower: z.array(z.string()).default([]),
|
|
82
|
-
related: z.array(z.string()).default([]),
|
|
83
|
-
conflicts: z.array(z.string()).default([])
|
|
84
|
-
});
|
|
85
|
-
var ProvenanceSchema = z.object({
|
|
86
|
-
origin: z.string(),
|
|
87
|
-
chain: z.array(z.string()).default([]),
|
|
88
|
-
signature: z.string().nullable().default(null),
|
|
89
|
-
license: z.string().default("cc-by-sa-4.0")
|
|
90
|
-
});
|
|
91
|
-
var FeedbackSignalsSchema = z.object({
|
|
92
|
-
positive: z.number().int().default(0),
|
|
93
|
-
negative: z.number().int().default(0),
|
|
94
|
-
neutral: z.number().int().default(0)
|
|
95
|
-
});
|
|
96
|
-
var EntityRefSchema = z.object({
|
|
97
|
-
name: z.string(),
|
|
98
|
-
type: z.enum([
|
|
99
|
-
"person",
|
|
100
|
-
"organization",
|
|
101
|
-
"technology",
|
|
102
|
-
"concept",
|
|
103
|
-
"project",
|
|
104
|
-
"tool",
|
|
105
|
-
"place",
|
|
106
|
-
"event",
|
|
107
|
-
"standard",
|
|
108
|
-
"other"
|
|
109
|
-
]),
|
|
110
|
-
uri: z.string().url().optional()
|
|
111
|
-
});
|
|
112
|
-
var TemporalSchema = z.object({
|
|
113
|
-
learned_at: z.string(),
|
|
114
|
-
valid_from: z.string().optional(),
|
|
115
|
-
valid_until: z.string().optional(),
|
|
116
|
-
ingested_at: z.string().optional()
|
|
117
|
-
});
|
|
118
|
-
var UsageStatsSchema = z.object({
|
|
119
|
-
injections: z.number().int().default(0),
|
|
120
|
-
hits: z.number().int().default(0),
|
|
121
|
-
misses: z.number().int().default(0),
|
|
122
|
-
last_hit_at: z.string().optional()
|
|
123
|
-
});
|
|
124
|
-
var EpisodicFieldsSchema = z.object({
|
|
125
|
-
emotional_weight: z.number().int().min(1).max(10).default(5),
|
|
126
|
-
confidence: z.number().int().min(1).max(10).default(5),
|
|
127
|
-
trigger_context: z.string().optional(),
|
|
128
|
-
journal_ref: z.string().optional()
|
|
129
|
-
});
|
|
130
|
-
var ExchangeMetadataSchema = z.object({
|
|
131
|
-
fitness_score: z.number().min(0).max(1).optional(),
|
|
132
|
-
environmental_diversity: z.number().int().default(0),
|
|
133
|
-
adoption_count: z.number().int().default(0),
|
|
134
|
-
contradiction_rate: z.number().min(0).max(1).default(0)
|
|
135
|
-
});
|
|
136
|
-
var EngramSchema = z.object({
|
|
137
|
-
// Identity
|
|
138
|
-
id: z.string().regex(/^(ENG|ABS|META)-[A-Za-z0-9-]+$/),
|
|
139
|
-
version: z.number().int().min(1).default(2),
|
|
140
|
-
status: z.enum(["active", "dormant", "retired", "candidate"]),
|
|
141
|
-
consolidated: z.boolean().default(false),
|
|
142
|
-
type: z.enum(["behavioral", "terminological", "procedural", "architectural"]),
|
|
143
|
-
scope: z.string(),
|
|
144
|
-
visibility: z.enum(["private", "public", "template"]).default("private"),
|
|
145
|
-
// Content
|
|
146
|
-
statement: z.string().min(1),
|
|
147
|
-
rationale: z.string().optional(),
|
|
148
|
-
contraindications: z.array(z.string()).optional(),
|
|
149
|
-
// Lineage
|
|
150
|
-
source: z.string().optional(),
|
|
151
|
-
source_patterns: z.array(z.string()).optional(),
|
|
152
|
-
derivation_count: z.number().int().min(0).default(1),
|
|
153
|
-
pack: z.string().nullable().default(null),
|
|
154
|
-
abstract: z.string().nullable().default(null),
|
|
155
|
-
derived_from: z.string().nullable().default(null),
|
|
156
|
-
// Classification
|
|
157
|
-
knowledge_type: KnowledgeTypeSchema.optional(),
|
|
158
|
-
domain: z.string().optional(),
|
|
159
|
-
tags: z.array(z.string()).default([]),
|
|
160
|
-
// Activation (ACT-R model)
|
|
161
|
-
activation: ActivationSchema.default({
|
|
162
|
-
retrieval_strength: 0.7,
|
|
163
|
-
storage_strength: 1,
|
|
164
|
-
frequency: 0,
|
|
165
|
-
last_accessed: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
166
|
-
}),
|
|
167
|
-
// Relations & grounding
|
|
168
|
-
relations: RelationsSchema.optional(),
|
|
169
|
-
associations: z.array(AssociationSchema).default([]),
|
|
170
|
-
knowledge_anchors: z.array(KnowledgeAnchorSchema).default([]),
|
|
171
|
-
dual_coding: DualCodingSchema.optional(),
|
|
172
|
-
// Provenance
|
|
173
|
-
provenance: ProvenanceSchema.optional(),
|
|
174
|
-
// Feedback
|
|
175
|
-
feedback_signals: FeedbackSignalsSchema.default({ positive: 0, negative: 0, neutral: 0 }),
|
|
176
|
-
// === NEW OPTIONAL FIELDS (v2.1) ===
|
|
177
|
-
/** Typed entity references extracted from statement. Enables graph queries. */
|
|
178
|
-
entities: z.array(EntityRefSchema).optional(),
|
|
179
|
-
/** Temporal validity window. When is this knowledge true? */
|
|
180
|
-
temporal: TemporalSchema.optional(),
|
|
181
|
-
/** Automatic usage tracking. Injections, hits, misses. */
|
|
182
|
-
usage: UsageStatsSchema.optional(),
|
|
183
|
-
/** Episodic context: emotional weight, confidence, trigger. */
|
|
184
|
-
episodic: EpisodicFieldsSchema.optional(),
|
|
185
|
-
/** Exchange marketplace metadata: fitness, adoption, diversity. */
|
|
186
|
-
exchange: ExchangeMetadataSchema.optional(),
|
|
187
|
-
/** Extensible key-value data for domain-specific fields. */
|
|
188
|
-
structured_data: z.record(z.string(), z.unknown()).optional(),
|
|
189
|
-
/** Polarity classification: 'do' for directives, 'dont' for prohibitions, null for unclassified. */
|
|
190
|
-
polarity: z.enum(["do", "dont"]).nullable().default(null)
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// src/schemas/pack.ts
|
|
194
|
-
import { z as z2 } from "zod";
|
|
195
|
-
var PackManifestSchema = z2.object({
|
|
196
|
-
name: z2.string(),
|
|
197
|
-
version: z2.string(),
|
|
198
|
-
description: z2.string().optional(),
|
|
199
|
-
creator: z2.string().optional(),
|
|
200
|
-
license: z2.string().default("cc-by-sa-4.0"),
|
|
201
|
-
tags: z2.array(z2.string()).default([]),
|
|
202
|
-
metadata: z2.object({
|
|
203
|
-
id: z2.string().optional(),
|
|
204
|
-
injection_policy: z2.enum(["on_match", "on_request", "always"]).default("on_match"),
|
|
205
|
-
match_terms: z2.array(z2.string()).default([]),
|
|
206
|
-
domain: z2.string().optional(),
|
|
207
|
-
engram_count: z2.number().optional()
|
|
208
|
-
}).optional(),
|
|
209
|
-
"x-datacore": z2.object({
|
|
210
|
-
id: z2.string(),
|
|
211
|
-
injection_policy: z2.enum(["on_match", "on_request"]),
|
|
212
|
-
match_terms: z2.array(z2.string()).default([]),
|
|
213
|
-
domain: z2.string().optional(),
|
|
214
|
-
engram_count: z2.number().int().min(0)
|
|
215
|
-
}).optional()
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// src/logger.ts
|
|
219
|
-
var level = process.env.PLUR_LOG_LEVEL || "warning";
|
|
220
|
-
var levels = { debug: 0, info: 1, warning: 2, error: 3 };
|
|
221
|
-
var threshold = levels[level] ?? 2;
|
|
222
|
-
var logger = {
|
|
223
|
-
debug: (...args) => {
|
|
224
|
-
if (threshold <= 0) console.error("[plur:debug]", ...args);
|
|
225
|
-
},
|
|
226
|
-
info: (...args) => {
|
|
227
|
-
if (threshold <= 1) console.error("[plur:info]", ...args);
|
|
228
|
-
},
|
|
229
|
-
warning: (...args) => {
|
|
230
|
-
if (threshold <= 2) console.error("[plur:warning]", ...args);
|
|
231
|
-
},
|
|
232
|
-
error: (...args) => {
|
|
233
|
-
if (threshold <= 3) console.error("[plur:error]", ...args);
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// src/engrams.ts
|
|
238
|
-
function loadEngrams(filePath) {
|
|
239
|
-
if (!fs.existsSync(filePath)) return [];
|
|
240
|
-
try {
|
|
241
|
-
const raw = yaml.load(fs.readFileSync(filePath, "utf8"));
|
|
242
|
-
if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
|
|
243
|
-
const valid = [];
|
|
244
|
-
let skipped = 0;
|
|
245
|
-
for (const entry of raw.engrams) {
|
|
246
|
-
const result = EngramSchema.safeParse(entry);
|
|
247
|
-
if (result.success) valid.push(result.data);
|
|
248
|
-
else skipped++;
|
|
249
|
-
}
|
|
250
|
-
if (skipped > 0) logger.warning(`Skipped ${skipped} invalid engram(s) in ${filePath}`);
|
|
251
|
-
return valid;
|
|
252
|
-
} catch (err) {
|
|
253
|
-
logger.error(`Failed to parse engrams file ${filePath}: ${err}`);
|
|
254
|
-
return [];
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function saveEngrams(filePath, engrams) {
|
|
258
|
-
const content = yaml.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
259
|
-
atomicWrite(filePath, content);
|
|
260
|
-
}
|
|
261
|
-
function parseSkillMdFrontmatter(filePath) {
|
|
262
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
263
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
264
|
-
if (!match) throw new Error(`No frontmatter found in ${filePath}`);
|
|
265
|
-
return yaml.load(match[1]);
|
|
266
|
-
}
|
|
267
|
-
function loadPack(packDir) {
|
|
268
|
-
const skillMdPath = `${packDir}/SKILL.md`;
|
|
269
|
-
const manifestYamlPath = `${packDir}/manifest.yaml`;
|
|
270
|
-
const engramsPath = `${packDir}/engrams.yaml`;
|
|
271
|
-
let rawManifest;
|
|
272
|
-
if (fs.existsSync(skillMdPath)) {
|
|
273
|
-
rawManifest = parseSkillMdFrontmatter(skillMdPath);
|
|
274
|
-
} else if (fs.existsSync(manifestYamlPath)) {
|
|
275
|
-
rawManifest = yaml.load(fs.readFileSync(manifestYamlPath, "utf8"));
|
|
276
|
-
} else {
|
|
277
|
-
throw new Error(`No SKILL.md or manifest.yaml found in ${packDir}`);
|
|
278
|
-
}
|
|
279
|
-
const manifest = PackManifestSchema.parse(rawManifest);
|
|
280
|
-
const engrams = loadEngrams(engramsPath);
|
|
281
|
-
return { manifest, engrams };
|
|
282
|
-
}
|
|
283
|
-
function loadAllPacks(packsDir) {
|
|
284
|
-
if (!fs.existsSync(packsDir)) return [];
|
|
285
|
-
const packs = [];
|
|
286
|
-
for (const entry of fs.readdirSync(packsDir)) {
|
|
287
|
-
const packDir = `${packsDir}/${entry}`;
|
|
288
|
-
if (!fs.statSync(packDir).isDirectory()) continue;
|
|
289
|
-
if (!fs.existsSync(`${packDir}/SKILL.md`) && !fs.existsSync(`${packDir}/manifest.yaml`)) continue;
|
|
290
|
-
try {
|
|
291
|
-
packs.push(loadPack(packDir));
|
|
292
|
-
} catch (err) {
|
|
293
|
-
logger.warning(`Failed to load pack ${entry}: ${err}`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return packs;
|
|
297
|
-
}
|
|
298
|
-
function storePrefix(scope) {
|
|
299
|
-
const parts = scope.split(/[:\-_./]/).filter(Boolean);
|
|
300
|
-
if (parts.length >= 2) {
|
|
301
|
-
const p2 = parts[1];
|
|
302
|
-
return (parts[0][0] + p2[0] + (p2[1] || p2[0])).toUpperCase();
|
|
303
|
-
}
|
|
304
|
-
const w = parts[0] || scope;
|
|
305
|
-
if (w.length >= 3) return (w[0] + w[Math.floor(w.length / 2)] + w[w.length - 1]).toUpperCase();
|
|
306
|
-
return (w[0] + (w[1] || w[0]) + (w[2] || w[0])).toUpperCase();
|
|
307
|
-
}
|
|
308
|
-
function generateEngramId(existing) {
|
|
309
|
-
const now = /* @__PURE__ */ new Date();
|
|
310
|
-
const date = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
311
|
-
const prefix = `ENG-${date.slice(0, 4)}-${date.slice(4)}-`;
|
|
312
|
-
const existingNums = existing.filter((e) => e.id.startsWith(prefix)).map((e) => parseInt(e.id.slice(prefix.length), 10)).filter((n) => !isNaN(n));
|
|
313
|
-
const next = existingNums.length > 0 ? Math.max(...existingNums) + 1 : 1;
|
|
314
|
-
return `${prefix}${String(next).padStart(3, "0")}`;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/storage-indexed.ts
|
|
318
65
|
var require2 = createRequire(import.meta.url);
|
|
319
66
|
var Database = null;
|
|
320
67
|
function getDatabase() {
|
|
@@ -367,7 +114,7 @@ var IndexedStorage = class {
|
|
|
367
114
|
}
|
|
368
115
|
/** Load all engrams from SQLite index. Auto-rebuilds if db missing. */
|
|
369
116
|
loadAll() {
|
|
370
|
-
if (!
|
|
117
|
+
if (!existsSync2(this.dbPath)) {
|
|
371
118
|
this.reindex();
|
|
372
119
|
}
|
|
373
120
|
const db = this.getDb();
|
|
@@ -376,7 +123,7 @@ var IndexedStorage = class {
|
|
|
376
123
|
}
|
|
377
124
|
/** Load engrams with SQL-level filtering. */
|
|
378
125
|
loadFiltered(filter) {
|
|
379
|
-
if (!
|
|
126
|
+
if (!existsSync2(this.dbPath)) {
|
|
380
127
|
this.reindex();
|
|
381
128
|
}
|
|
382
129
|
const db = this.getDb();
|
|
@@ -400,7 +147,7 @@ var IndexedStorage = class {
|
|
|
400
147
|
}
|
|
401
148
|
/** Count engrams with optional status filter. */
|
|
402
149
|
count(filter) {
|
|
403
|
-
if (!
|
|
150
|
+
if (!existsSync2(this.dbPath)) {
|
|
404
151
|
this.reindex();
|
|
405
152
|
}
|
|
406
153
|
const db = this.getDb();
|
|
@@ -465,39 +212,63 @@ var IndexedStorage = class {
|
|
|
465
212
|
};
|
|
466
213
|
|
|
467
214
|
// src/config.ts
|
|
468
|
-
import { existsSync as
|
|
469
|
-
import
|
|
215
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
216
|
+
import yaml from "js-yaml";
|
|
470
217
|
|
|
471
218
|
// src/schemas/config.ts
|
|
472
|
-
import { z
|
|
473
|
-
var StoreEntrySchema =
|
|
474
|
-
path:
|
|
475
|
-
scope:
|
|
476
|
-
shared:
|
|
477
|
-
readonly:
|
|
219
|
+
import { z } from "zod";
|
|
220
|
+
var StoreEntrySchema = z.object({
|
|
221
|
+
path: z.string(),
|
|
222
|
+
scope: z.string(),
|
|
223
|
+
shared: z.boolean().default(false),
|
|
224
|
+
readonly: z.boolean().default(false)
|
|
478
225
|
});
|
|
479
|
-
var
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
226
|
+
var LlmTierConfigSchema = z.object({
|
|
227
|
+
dedup_tier: z.enum(["fast", "balanced", "thorough"]).default("fast"),
|
|
228
|
+
profile_tier: z.enum(["fast", "balanced", "thorough"]).default("balanced"),
|
|
229
|
+
meta_tier: z.enum(["fast", "balanced", "thorough"]).default("thorough")
|
|
230
|
+
}).partial();
|
|
231
|
+
var ProfileConfigSchema = z.object({
|
|
232
|
+
enabled: z.boolean().default(true),
|
|
233
|
+
cache_ttl_hours: z.number().default(24)
|
|
234
|
+
}).partial();
|
|
235
|
+
var DedupConfigSchema = z.object({
|
|
236
|
+
enabled: z.boolean().default(true),
|
|
237
|
+
threshold: z.number().min(0).max(1).default(0.85),
|
|
238
|
+
mode: z.enum(["llm", "cosine", "off"]).default("llm")
|
|
239
|
+
}).partial();
|
|
240
|
+
var StorageConfigSchema = z.object({
|
|
241
|
+
backend: z.enum(["yaml", "sqlite"]).default("yaml"),
|
|
242
|
+
path: z.string().optional()
|
|
243
|
+
}).partial();
|
|
244
|
+
var PlurConfigSchema = z.object({
|
|
245
|
+
auto_learn: z.boolean().default(true),
|
|
246
|
+
auto_capture: z.boolean().default(true),
|
|
247
|
+
injection_budget: z.number().default(2e3),
|
|
248
|
+
decay_enabled: z.boolean().default(true),
|
|
249
|
+
decay_threshold: z.number().default(0.15),
|
|
250
|
+
packs: z.array(z.string()).default([]),
|
|
251
|
+
injection: z.object({
|
|
252
|
+
spread_cap: z.number().default(3),
|
|
253
|
+
spread_budget: z.number().default(480),
|
|
254
|
+
co_access: z.boolean().default(true)
|
|
490
255
|
}).default({}),
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
256
|
+
dedup: DedupConfigSchema.default({}),
|
|
257
|
+
decay_baseline: z.string().optional(),
|
|
258
|
+
allow_secrets: z.boolean().default(false),
|
|
259
|
+
index: z.boolean().default(true),
|
|
260
|
+
storage: StorageConfigSchema.default({}),
|
|
261
|
+
stores: z.array(StoreEntrySchema).default([]),
|
|
262
|
+
llm: LlmTierConfigSchema.default({}),
|
|
263
|
+
profile: ProfileConfigSchema.default({}),
|
|
264
|
+
registry_url: z.string().url().optional()
|
|
494
265
|
}).partial();
|
|
495
266
|
|
|
496
267
|
// src/config.ts
|
|
497
268
|
function loadConfig(configPath) {
|
|
498
|
-
if (!
|
|
269
|
+
if (!existsSync3(configPath)) return PlurConfigSchema.parse({});
|
|
499
270
|
try {
|
|
500
|
-
const raw =
|
|
271
|
+
const raw = yaml.load(readFileSync(configPath, "utf8"));
|
|
501
272
|
return PlurConfigSchema.parse(raw ?? {});
|
|
502
273
|
} catch {
|
|
503
274
|
return PlurConfigSchema.parse({});
|
|
@@ -523,6 +294,28 @@ function decayedCoAccessStrength(strength, daysSinceUpdate, lambda = 0.01) {
|
|
|
523
294
|
const floor = 0.02;
|
|
524
295
|
return floor + (strength - floor) * Math.exp(-lambda * daysSinceUpdate);
|
|
525
296
|
}
|
|
297
|
+
function confidenceDecay(retrievalStrength, lastPositiveFeedbackDate, commitment, decayBaseline, now) {
|
|
298
|
+
if (commitment === "locked") return retrievalStrength;
|
|
299
|
+
const CONFIDENCE_DECAY_FLOOR = 0.1;
|
|
300
|
+
const GRACE_PERIOD_DAYS = 90;
|
|
301
|
+
const MONTHLY_MULTIPLIER = 0.95;
|
|
302
|
+
const current = now || /* @__PURE__ */ new Date();
|
|
303
|
+
let referenceDate;
|
|
304
|
+
if (lastPositiveFeedbackDate) {
|
|
305
|
+
referenceDate = new Date(lastPositiveFeedbackDate);
|
|
306
|
+
} else if (decayBaseline) {
|
|
307
|
+
referenceDate = new Date(decayBaseline);
|
|
308
|
+
} else {
|
|
309
|
+
return retrievalStrength;
|
|
310
|
+
}
|
|
311
|
+
const daysSinceRef = Math.max(0, Math.floor((current.getTime() - referenceDate.getTime()) / MS_PER_DAY));
|
|
312
|
+
if (daysSinceRef <= GRACE_PERIOD_DAYS) return retrievalStrength;
|
|
313
|
+
const daysOverGrace = daysSinceRef - GRACE_PERIOD_DAYS;
|
|
314
|
+
const monthsOverGrace = daysOverGrace / 30;
|
|
315
|
+
const multiplier = Math.pow(MONTHLY_MULTIPLIER, monthsOverGrace);
|
|
316
|
+
const decayed = retrievalStrength * multiplier;
|
|
317
|
+
return Math.max(CONFIDENCE_DECAY_FLOOR, decayed);
|
|
318
|
+
}
|
|
526
319
|
|
|
527
320
|
// src/polarity.ts
|
|
528
321
|
var DONT_PATTERNS = [
|
|
@@ -571,6 +364,18 @@ function confidenceBand(score) {
|
|
|
571
364
|
return "low";
|
|
572
365
|
}
|
|
573
366
|
|
|
367
|
+
// src/fresh-tail.ts
|
|
368
|
+
var FRESH_TAIL_DAYS = 7;
|
|
369
|
+
var FRESH_TAIL_MAX_BOOST = 0.2;
|
|
370
|
+
function freshTailBoost(createdAt, commitment, now) {
|
|
371
|
+
if (commitment && !["exploring", "leaning"].includes(commitment)) return 0;
|
|
372
|
+
const created = new Date(createdAt);
|
|
373
|
+
const today = now ?? /* @__PURE__ */ new Date();
|
|
374
|
+
const daysSinceCreation = (today.getTime() - created.getTime()) / (1e3 * 60 * 60 * 24);
|
|
375
|
+
if (daysSinceCreation < 0 || daysSinceCreation > FRESH_TAIL_DAYS) return 0;
|
|
376
|
+
return FRESH_TAIL_MAX_BOOST * (1 - daysSinceCreation / FRESH_TAIL_DAYS);
|
|
377
|
+
}
|
|
378
|
+
|
|
574
379
|
// src/inject.ts
|
|
575
380
|
var DEFAULT_MAX_TOKENS = 8e3;
|
|
576
381
|
var DEFAULT_MIN_RELEVANCE = 0.3;
|
|
@@ -595,7 +400,7 @@ function tokenize(text) {
|
|
|
595
400
|
}
|
|
596
401
|
function anchorBoost(engram, taskWords) {
|
|
597
402
|
if (!engram.knowledge_anchors?.length) return 0;
|
|
598
|
-
const
|
|
403
|
+
const threshold = taskWords.size <= 1 ? 1 : 2;
|
|
599
404
|
let boost = 0;
|
|
600
405
|
for (const anchor of engram.knowledge_anchors) {
|
|
601
406
|
if (!anchor.snippet) continue;
|
|
@@ -604,7 +409,7 @@ function anchorBoost(engram, taskWords) {
|
|
|
604
409
|
for (const word of taskWords) {
|
|
605
410
|
if (snippetWords.has(word)) overlap++;
|
|
606
411
|
}
|
|
607
|
-
if (overlap >=
|
|
412
|
+
if (overlap >= threshold) boost += 0.5;
|
|
608
413
|
}
|
|
609
414
|
return Math.min(boost, 2);
|
|
610
415
|
}
|
|
@@ -655,7 +460,12 @@ function scoreEngram(engram, promptLower, promptWords, packMatchTerms, scopeFilt
|
|
|
655
460
|
if (statementWords.has(word)) termHits += 0.5;
|
|
656
461
|
}
|
|
657
462
|
if (termHits === 0) return 0;
|
|
658
|
-
|
|
463
|
+
let rs = isPack ? engram.activation.retrieval_strength : decayedStrength(engram.activation.retrieval_strength, daysSince(engram.activation.last_accessed));
|
|
464
|
+
if (!isPack) {
|
|
465
|
+
const fb = engram.feedback_signals;
|
|
466
|
+
const lastPositive = fb && fb.positive > 0 ? engram.activation.last_accessed : null;
|
|
467
|
+
rs = confidenceDecay(rs, lastPositive, engram.commitment, void 0);
|
|
468
|
+
}
|
|
659
469
|
let score = termHits * rs;
|
|
660
470
|
const feedback = engram.feedback_signals;
|
|
661
471
|
if (feedback) {
|
|
@@ -712,6 +522,11 @@ function selectAndSpread(ctx, personalEngrams, packs, config, embeddingBoosts) {
|
|
|
712
522
|
} else if (raw > 0 && embBoost > 0) {
|
|
713
523
|
raw += embBoost;
|
|
714
524
|
}
|
|
525
|
+
if (raw > 0) {
|
|
526
|
+
const createdAt = engram.temporal?.learned_at ?? engram.activation.last_accessed;
|
|
527
|
+
const ftBoost = freshTailBoost(createdAt, engram.commitment, /* @__PURE__ */ new Date());
|
|
528
|
+
if (ftBoost > 0) raw += ftBoost;
|
|
529
|
+
}
|
|
715
530
|
if (raw > 0) {
|
|
716
531
|
scored.push({ ...engram, keyword_match: raw, raw_score: raw, score: raw });
|
|
717
532
|
}
|
|
@@ -813,33 +628,83 @@ function selectAndSpread(ctx, personalEngrams, packs, config, embeddingBoosts) {
|
|
|
813
628
|
const wireConsider = agentConsider.map(stripScoring);
|
|
814
629
|
const wireDirectives = [];
|
|
815
630
|
const wireConstraints = [];
|
|
631
|
+
const cognitiveDemoted = [];
|
|
816
632
|
for (const wire of wireAll) {
|
|
817
633
|
const polarity = wire.polarity ?? classifyPolarity(wire.statement);
|
|
818
|
-
|
|
634
|
+
const commitment = wire.commitment;
|
|
635
|
+
if (commitment) {
|
|
636
|
+
const mult = { locked: 1, decided: 0.9, leaning: 0.7, exploring: 0.5 };
|
|
637
|
+
wire.confidence_score *= mult[commitment] ?? 1;
|
|
638
|
+
}
|
|
639
|
+
const cogLevel = wire.knowledge_type?.cognitive_level;
|
|
640
|
+
if (cogLevel === "remember" || cogLevel === "understand") {
|
|
641
|
+
cognitiveDemoted.push(wire);
|
|
642
|
+
} else if (polarity === "dont") {
|
|
643
|
+
wireConstraints.push(wire);
|
|
644
|
+
} else if (cogLevel === "apply" || cogLevel === "analyze") {
|
|
819
645
|
wireConstraints.push(wire);
|
|
820
646
|
} else {
|
|
821
647
|
wireDirectives.push(wire);
|
|
822
648
|
}
|
|
823
649
|
}
|
|
650
|
+
const allWireConsider = [...wireConsider, ...cognitiveDemoted];
|
|
824
651
|
const considerTokens = dip19PoolTokens + spreadTokens;
|
|
825
652
|
return {
|
|
826
653
|
directives: wireDirectives,
|
|
827
654
|
constraints: wireConstraints,
|
|
828
|
-
consider:
|
|
655
|
+
consider: allWireConsider,
|
|
829
656
|
tokens_used: { directives: directiveTokens, consider: considerTokens }
|
|
830
657
|
};
|
|
831
658
|
}
|
|
659
|
+
function formatLayer1(engram) {
|
|
660
|
+
const display = engram.summary ?? engram.statement.slice(0, 60);
|
|
661
|
+
return `[${engram.id}] ${display}`;
|
|
662
|
+
}
|
|
663
|
+
function formatLayer2(engram) {
|
|
664
|
+
return `[${engram.id}] ${engram.statement}`;
|
|
665
|
+
}
|
|
666
|
+
function formatLayer3(engram) {
|
|
667
|
+
const lines = [`[${engram.id}] ${engram.statement}`];
|
|
668
|
+
if (engram.rationale) lines.push(` Rationale: ${engram.rationale}`);
|
|
669
|
+
const meta = [];
|
|
670
|
+
if (engram.domain) meta.push(`Domain: ${engram.domain}`);
|
|
671
|
+
if (engram.confidence_score != null) meta.push(`Confidence: ${engram.confidence_score.toFixed(2)}`);
|
|
672
|
+
if (engram.activation?.last_accessed) meta.push(`Last verified: ${engram.activation.last_accessed}`);
|
|
673
|
+
if (meta.length > 0) lines.push(` ${meta.join(" | ")}`);
|
|
674
|
+
return lines.join("\n");
|
|
675
|
+
}
|
|
676
|
+
function assignLayer(bucket) {
|
|
677
|
+
switch (bucket) {
|
|
678
|
+
case "directives":
|
|
679
|
+
return 3;
|
|
680
|
+
case "constraints":
|
|
681
|
+
return 2;
|
|
682
|
+
case "consider":
|
|
683
|
+
return 1;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function formatWithLayer(engrams, layer) {
|
|
687
|
+
if (engrams.length === 0) return "";
|
|
688
|
+
switch (layer) {
|
|
689
|
+
case 1:
|
|
690
|
+
return engrams.map(formatLayer1).join(" | ");
|
|
691
|
+
case 2:
|
|
692
|
+
return engrams.map(formatLayer2).join("\n");
|
|
693
|
+
case 3:
|
|
694
|
+
return engrams.map(formatLayer3).join("\n");
|
|
695
|
+
}
|
|
696
|
+
}
|
|
832
697
|
|
|
833
698
|
// src/episodes.ts
|
|
834
|
-
import { existsSync as
|
|
835
|
-
import
|
|
699
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
700
|
+
import yaml2 from "js-yaml";
|
|
836
701
|
function generateEpisodeId() {
|
|
837
702
|
const ts = Date.now();
|
|
838
703
|
const rand = Math.random().toString(36).slice(2, 6);
|
|
839
704
|
return `EP-${ts}-${rand}`;
|
|
840
705
|
}
|
|
841
|
-
function captureEpisode(
|
|
842
|
-
const episodes = loadEpisodes(
|
|
706
|
+
function captureEpisode(path3, summary, context) {
|
|
707
|
+
const episodes = loadEpisodes(path3);
|
|
843
708
|
const episode = {
|
|
844
709
|
id: generateEpisodeId(),
|
|
845
710
|
summary,
|
|
@@ -850,11 +715,11 @@ function captureEpisode(path2, summary, context) {
|
|
|
850
715
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
851
716
|
};
|
|
852
717
|
episodes.push(episode);
|
|
853
|
-
atomicWrite(
|
|
718
|
+
atomicWrite(path3, yaml2.dump(episodes, { lineWidth: 120, noRefs: true }));
|
|
854
719
|
return episode;
|
|
855
720
|
}
|
|
856
|
-
function queryTimeline(
|
|
857
|
-
let episodes = loadEpisodes(
|
|
721
|
+
function queryTimeline(path3, query) {
|
|
722
|
+
let episodes = loadEpisodes(path3);
|
|
858
723
|
if (query?.since) episodes = episodes.filter((e) => new Date(e.timestamp) >= query.since);
|
|
859
724
|
if (query?.until) episodes = episodes.filter((e) => new Date(e.timestamp) <= query.until);
|
|
860
725
|
if (query?.agent) episodes = episodes.filter((e) => e.agent === query.agent);
|
|
@@ -865,10 +730,10 @@ function queryTimeline(path2, query) {
|
|
|
865
730
|
}
|
|
866
731
|
return episodes;
|
|
867
732
|
}
|
|
868
|
-
function loadEpisodes(
|
|
869
|
-
if (!
|
|
733
|
+
function loadEpisodes(path3) {
|
|
734
|
+
if (!existsSync4(path3)) return [];
|
|
870
735
|
try {
|
|
871
|
-
const raw =
|
|
736
|
+
const raw = yaml2.load(readFileSync2(path3, "utf8"));
|
|
872
737
|
return Array.isArray(raw) ? raw : [];
|
|
873
738
|
} catch {
|
|
874
739
|
return [];
|
|
@@ -876,7 +741,7 @@ function loadEpisodes(path2) {
|
|
|
876
741
|
}
|
|
877
742
|
|
|
878
743
|
// src/conflict.ts
|
|
879
|
-
function detectConflicts(newEngram, existing,
|
|
744
|
+
function detectConflicts(newEngram, existing, threshold = 0.4) {
|
|
880
745
|
const newScope = newEngram.scope || "global";
|
|
881
746
|
const newTokens = ftsTokenize(newEngram.statement);
|
|
882
747
|
if (newTokens.length === 0) return [];
|
|
@@ -884,7 +749,7 @@ function detectConflicts(newEngram, existing, threshold2 = 0.4) {
|
|
|
884
749
|
if (e.status !== "active") return false;
|
|
885
750
|
if ((e.scope || "global") !== newScope) return false;
|
|
886
751
|
const score = ftsScore(e, newTokens);
|
|
887
|
-
return score >=
|
|
752
|
+
return score >= threshold;
|
|
888
753
|
});
|
|
889
754
|
}
|
|
890
755
|
|
|
@@ -1059,11 +924,112 @@ async function expandedSearch(engrams, query, limit, llm, storagePath) {
|
|
|
1059
924
|
return merged.slice(0, effectiveLimit);
|
|
1060
925
|
}
|
|
1061
926
|
|
|
927
|
+
// src/search-orchestrator.ts
|
|
928
|
+
function isKeywordQuery(query) {
|
|
929
|
+
const words = query.trim().split(/\s+/);
|
|
930
|
+
if (words.length >= 5) return false;
|
|
931
|
+
const nlSignals = /^(what|where|when|how|why|which|who|can|do|does|is|are|should|would|could)\b/i;
|
|
932
|
+
if (nlSignals.test(query.trim())) return false;
|
|
933
|
+
if (query.includes("?")) return false;
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
function normalizedBm25Scores(engrams, query) {
|
|
937
|
+
const queryTokens = ftsTokenize(query);
|
|
938
|
+
if (queryTokens.length === 0) return /* @__PURE__ */ new Map();
|
|
939
|
+
const idfWeights = computeIdf(engrams, queryTokens);
|
|
940
|
+
const avgDocLength = engrams.length > 0 ? engrams.reduce((sum, e) => sum + ftsTokenize(engramSearchText(e)).length, 0) / engrams.length : 0;
|
|
941
|
+
const rawScores = [];
|
|
942
|
+
for (const e of engrams) {
|
|
943
|
+
const score = ftsScore(e, queryTokens, idfWeights, avgDocLength);
|
|
944
|
+
if (score > 0) rawScores.push({ id: e.id, score });
|
|
945
|
+
}
|
|
946
|
+
if (rawScores.length === 0) return /* @__PURE__ */ new Map();
|
|
947
|
+
const min = Math.min(...rawScores.map((r) => r.score));
|
|
948
|
+
const max = Math.max(...rawScores.map((r) => r.score));
|
|
949
|
+
const range = max - min || 1;
|
|
950
|
+
const normalized = /* @__PURE__ */ new Map();
|
|
951
|
+
for (const { id, score } of rawScores) {
|
|
952
|
+
normalized.set(id, (score - min) / range);
|
|
953
|
+
}
|
|
954
|
+
return normalized;
|
|
955
|
+
}
|
|
956
|
+
async function recallAuto(engrams, query, limit, storagePath, llm) {
|
|
957
|
+
if (engrams.length === 0) return { results: [], strategy_used: "bm25" };
|
|
958
|
+
if (isKeywordQuery(query)) {
|
|
959
|
+
const bm25Results = searchEngrams(engrams, query, limit);
|
|
960
|
+
const scores2 = normalizedBm25Scores(engrams, query);
|
|
961
|
+
const maxScore2 = bm25Results.length > 0 ? scores2.get(bm25Results[0].id) ?? 0 : 0;
|
|
962
|
+
if (bm25Results.length >= 3 && maxScore2 >= 0.3) {
|
|
963
|
+
return { results: bm25Results, strategy_used: "bm25" };
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
const hybridResults2 = await hybridSearch(engrams, query, limit, storagePath);
|
|
967
|
+
if (hybridResults2.length >= 3) return { results: hybridResults2, strategy_used: "hybrid" };
|
|
968
|
+
} catch {
|
|
969
|
+
}
|
|
970
|
+
if (llm) {
|
|
971
|
+
try {
|
|
972
|
+
const expandedResults = await expandedSearch(engrams, query, limit, llm, storagePath);
|
|
973
|
+
return { results: expandedResults, strategy_used: "expanded" };
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return { results: bm25Results, strategy_used: "bm25" };
|
|
978
|
+
}
|
|
979
|
+
let hybridResults = [];
|
|
980
|
+
try {
|
|
981
|
+
hybridResults = await hybridSearch(engrams, query, limit, storagePath);
|
|
982
|
+
} catch {
|
|
983
|
+
const bm25Results = searchEngrams(engrams, query, limit);
|
|
984
|
+
return { results: bm25Results, strategy_used: "bm25" };
|
|
985
|
+
}
|
|
986
|
+
const scores = normalizedBm25Scores(engrams, query);
|
|
987
|
+
const maxScore = hybridResults.length > 0 ? scores.get(hybridResults[0].id) ?? 0 : 0;
|
|
988
|
+
if (hybridResults.length >= 3 && maxScore >= 0.3) {
|
|
989
|
+
return { results: hybridResults, strategy_used: "hybrid" };
|
|
990
|
+
}
|
|
991
|
+
if (llm) {
|
|
992
|
+
try {
|
|
993
|
+
const expandedResults = await expandedSearch(engrams, query, limit, llm, storagePath);
|
|
994
|
+
return { results: expandedResults, strategy_used: "expanded" };
|
|
995
|
+
} catch {
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (hybridResults.length > 0) return { results: hybridResults, strategy_used: "hybrid" };
|
|
999
|
+
return { results: searchEngrams(engrams, query, limit), strategy_used: "bm25" };
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/summary.ts
|
|
1003
|
+
var SUMMARY_MAX_LENGTH = 60;
|
|
1004
|
+
var STATEMENT_THRESHOLD = 200;
|
|
1005
|
+
function needsSummary(statement, cognitiveLevel) {
|
|
1006
|
+
if (statement.length <= STATEMENT_THRESHOLD) return false;
|
|
1007
|
+
if (cognitiveLevel && !["remember", "understand"].includes(cognitiveLevel)) return false;
|
|
1008
|
+
return true;
|
|
1009
|
+
}
|
|
1010
|
+
function generateSummary(statement) {
|
|
1011
|
+
const sentenceEnd = statement.search(/[.!?]\s/);
|
|
1012
|
+
if (sentenceEnd > 0 && sentenceEnd <= SUMMARY_MAX_LENGTH) {
|
|
1013
|
+
return statement.slice(0, sentenceEnd + 1);
|
|
1014
|
+
}
|
|
1015
|
+
if (statement.length <= SUMMARY_MAX_LENGTH) return statement;
|
|
1016
|
+
const truncated = statement.slice(0, SUMMARY_MAX_LENGTH);
|
|
1017
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
1018
|
+
if (lastSpace > SUMMARY_MAX_LENGTH * 0.5) {
|
|
1019
|
+
return truncated.slice(0, lastSpace) + "...";
|
|
1020
|
+
}
|
|
1021
|
+
return truncated + "...";
|
|
1022
|
+
}
|
|
1023
|
+
function autoSummary(statement, cognitiveLevel) {
|
|
1024
|
+
if (!needsSummary(statement, cognitiveLevel)) return void 0;
|
|
1025
|
+
return generateSummary(statement);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1062
1028
|
// src/packs.ts
|
|
1063
|
-
import * as
|
|
1029
|
+
import * as fs from "fs";
|
|
1064
1030
|
import * as path from "path";
|
|
1065
1031
|
import * as crypto from "crypto";
|
|
1066
|
-
import
|
|
1032
|
+
import yaml3 from "js-yaml";
|
|
1067
1033
|
|
|
1068
1034
|
// src/secrets.ts
|
|
1069
1035
|
var SECRET_PATTERNS = [
|
|
@@ -1094,17 +1060,17 @@ function registryPath(packsDir) {
|
|
|
1094
1060
|
}
|
|
1095
1061
|
function loadRegistry(packsDir) {
|
|
1096
1062
|
const p = registryPath(packsDir);
|
|
1097
|
-
if (!
|
|
1063
|
+
if (!fs.existsSync(p)) return [];
|
|
1098
1064
|
try {
|
|
1099
|
-
const raw =
|
|
1065
|
+
const raw = yaml3.load(fs.readFileSync(p, "utf8"));
|
|
1100
1066
|
return Array.isArray(raw?.packs) ? raw.packs : [];
|
|
1101
1067
|
} catch {
|
|
1102
1068
|
return [];
|
|
1103
1069
|
}
|
|
1104
1070
|
}
|
|
1105
1071
|
function saveRegistry(packsDir, entries) {
|
|
1106
|
-
const content =
|
|
1107
|
-
|
|
1072
|
+
const content = yaml3.dump({ packs: entries }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
1073
|
+
fs.writeFileSync(registryPath(packsDir), content);
|
|
1108
1074
|
}
|
|
1109
1075
|
function addToRegistry(packsDir, entry) {
|
|
1110
1076
|
const entries = loadRegistry(packsDir);
|
|
@@ -1118,7 +1084,7 @@ function removeFromRegistry(packsDir, name) {
|
|
|
1118
1084
|
saveRegistry(packsDir, entries);
|
|
1119
1085
|
}
|
|
1120
1086
|
function previewPack(source) {
|
|
1121
|
-
if (!
|
|
1087
|
+
if (!fs.existsSync(source)) throw new Error(`Pack source not found: ${source}`);
|
|
1122
1088
|
const pack = loadPack(source);
|
|
1123
1089
|
const security = scanPrivacy(pack.engrams);
|
|
1124
1090
|
const warnings = [];
|
|
@@ -1181,7 +1147,7 @@ function detectConflicts2(newEngrams, existingEngrams) {
|
|
|
1181
1147
|
return conflicts;
|
|
1182
1148
|
}
|
|
1183
1149
|
function installPack(packsDir, source, existingEngrams) {
|
|
1184
|
-
if (!
|
|
1150
|
+
if (!fs.existsSync(source)) throw new Error(`Pack source not found: ${source}`);
|
|
1185
1151
|
const preview = previewPack(source);
|
|
1186
1152
|
const secretIssues = preview.security.issues.filter((i) => i.type === "secret");
|
|
1187
1153
|
if (secretIssues.length > 0) {
|
|
@@ -1191,17 +1157,17 @@ ${details}`);
|
|
|
1191
1157
|
}
|
|
1192
1158
|
const sourceName = path.basename(source);
|
|
1193
1159
|
const destDir = path.join(packsDir, sourceName);
|
|
1194
|
-
if (!
|
|
1195
|
-
const files =
|
|
1160
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
1161
|
+
const files = fs.readdirSync(source);
|
|
1196
1162
|
for (const file of files) {
|
|
1197
1163
|
const srcPath = path.join(source, file);
|
|
1198
1164
|
const destPath = path.join(destDir, file);
|
|
1199
|
-
if (
|
|
1200
|
-
|
|
1165
|
+
if (fs.statSync(srcPath).isFile()) {
|
|
1166
|
+
fs.copyFileSync(srcPath, destPath);
|
|
1201
1167
|
}
|
|
1202
1168
|
}
|
|
1203
1169
|
const engramsPath = path.join(destDir, "engrams.yaml");
|
|
1204
|
-
const newEngrams =
|
|
1170
|
+
const newEngrams = fs.existsSync(engramsPath) ? loadEngrams(engramsPath) : [];
|
|
1205
1171
|
const conflicts = existingEngrams ? detectConflicts2(newEngrams, existingEngrams) : [];
|
|
1206
1172
|
const integrity = `sha256:${computePackHash(destDir)}`;
|
|
1207
1173
|
const registryEntry = {
|
|
@@ -1217,8 +1183,8 @@ ${details}`);
|
|
|
1217
1183
|
}
|
|
1218
1184
|
function uninstallPack(packsDir, name) {
|
|
1219
1185
|
let packDir = path.join(packsDir, name);
|
|
1220
|
-
if (!
|
|
1221
|
-
const entries =
|
|
1186
|
+
if (!fs.existsSync(packDir)) {
|
|
1187
|
+
const entries = fs.existsSync(packsDir) ? fs.readdirSync(packsDir) : [];
|
|
1222
1188
|
const match = entries.find((e) => e.toLowerCase() === name.toLowerCase());
|
|
1223
1189
|
if (match) {
|
|
1224
1190
|
packDir = path.join(packsDir, match);
|
|
@@ -1239,17 +1205,17 @@ function uninstallPack(packsDir, name) {
|
|
|
1239
1205
|
}
|
|
1240
1206
|
removeFromRegistry(packsDir, name);
|
|
1241
1207
|
if (manifestName && manifestName !== name) removeFromRegistry(packsDir, manifestName);
|
|
1242
|
-
|
|
1208
|
+
fs.rmSync(packDir, { recursive: true, force: true });
|
|
1243
1209
|
return { name, removed: true, engram_count: count };
|
|
1244
1210
|
}
|
|
1245
1211
|
function listPacks(packsDir) {
|
|
1246
|
-
if (!
|
|
1212
|
+
if (!fs.existsSync(packsDir)) return [];
|
|
1247
1213
|
const registry = loadRegistry(packsDir);
|
|
1248
1214
|
const registryMap = new Map(registry.map((r) => [r.name, r]));
|
|
1249
1215
|
const result = [];
|
|
1250
|
-
for (const entry of
|
|
1216
|
+
for (const entry of fs.readdirSync(packsDir)) {
|
|
1251
1217
|
const packDir = path.join(packsDir, entry);
|
|
1252
|
-
if (!
|
|
1218
|
+
if (!fs.statSync(packDir).isDirectory()) continue;
|
|
1253
1219
|
try {
|
|
1254
1220
|
const pack = loadPack(packDir);
|
|
1255
1221
|
const currentIntegrity = `sha256:${computePackHash(packDir)}`;
|
|
@@ -1266,7 +1232,7 @@ function listPacks(packsDir) {
|
|
|
1266
1232
|
});
|
|
1267
1233
|
} catch {
|
|
1268
1234
|
const engramsPath = path.join(packDir, "engrams.yaml");
|
|
1269
|
-
const engrams =
|
|
1235
|
+
const engrams = fs.existsSync(engramsPath) ? loadEngrams(engramsPath) : [];
|
|
1270
1236
|
const reg = registryMap.get(entry);
|
|
1271
1237
|
result.push({
|
|
1272
1238
|
name: entry,
|
|
@@ -1356,8 +1322,8 @@ function exportPack(engrams, outputDir, manifest) {
|
|
|
1356
1322
|
);
|
|
1357
1323
|
const safeEngrams = engrams.filter((e) => !blockedIds.has(e.id));
|
|
1358
1324
|
const matchTerms = deriveMatchTerms(safeEngrams);
|
|
1359
|
-
if (!
|
|
1360
|
-
const frontmatter =
|
|
1325
|
+
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
1326
|
+
const frontmatter = yaml3.dump({
|
|
1361
1327
|
name: manifest.name,
|
|
1362
1328
|
version: manifest.version,
|
|
1363
1329
|
description: manifest.description,
|
|
@@ -1368,7 +1334,7 @@ function exportPack(engrams, outputDir, manifest) {
|
|
|
1368
1334
|
engram_count: safeEngrams.length
|
|
1369
1335
|
}
|
|
1370
1336
|
});
|
|
1371
|
-
|
|
1337
|
+
fs.writeFileSync(
|
|
1372
1338
|
path.join(outputDir, "SKILL.md"),
|
|
1373
1339
|
`---
|
|
1374
1340
|
${frontmatter}---
|
|
@@ -1405,10 +1371,10 @@ ${manifest.description || ""}
|
|
|
1405
1371
|
}
|
|
1406
1372
|
return cleaned;
|
|
1407
1373
|
});
|
|
1408
|
-
const content =
|
|
1409
|
-
|
|
1374
|
+
const content = yaml3.dump({ engrams: exportEngrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
1375
|
+
fs.writeFileSync(path.join(outputDir, "engrams.yaml"), content);
|
|
1410
1376
|
const integrity = computePackHash(outputDir);
|
|
1411
|
-
|
|
1377
|
+
fs.writeFileSync(path.join(outputDir, "INTEGRITY"), `sha256:${integrity}
|
|
1412
1378
|
`);
|
|
1413
1379
|
return {
|
|
1414
1380
|
path: outputDir,
|
|
@@ -1422,14 +1388,14 @@ function computePackHash(packDir) {
|
|
|
1422
1388
|
const hash = crypto.createHash("sha256");
|
|
1423
1389
|
const skillMd = path.join(packDir, "SKILL.md");
|
|
1424
1390
|
const manifestYaml = path.join(packDir, "manifest.yaml");
|
|
1425
|
-
if (
|
|
1426
|
-
hash.update(
|
|
1427
|
-
} else if (
|
|
1428
|
-
hash.update(
|
|
1391
|
+
if (fs.existsSync(skillMd)) {
|
|
1392
|
+
hash.update(fs.readFileSync(skillMd));
|
|
1393
|
+
} else if (fs.existsSync(manifestYaml)) {
|
|
1394
|
+
hash.update(fs.readFileSync(manifestYaml));
|
|
1429
1395
|
}
|
|
1430
1396
|
const engramsPath = path.join(packDir, "engrams.yaml");
|
|
1431
|
-
if (
|
|
1432
|
-
hash.update(
|
|
1397
|
+
if (fs.existsSync(engramsPath)) {
|
|
1398
|
+
hash.update(fs.readFileSync(engramsPath));
|
|
1433
1399
|
}
|
|
1434
1400
|
return hash.digest("hex");
|
|
1435
1401
|
}
|
|
@@ -1550,7 +1516,7 @@ async function computeSimilarityMatrix(templates) {
|
|
|
1550
1516
|
const n = templates.length;
|
|
1551
1517
|
const matrix = Array.from({ length: n }, () => Array(n).fill(0));
|
|
1552
1518
|
try {
|
|
1553
|
-
const { embed, cosineSimilarity } = await import("./embeddings-
|
|
1519
|
+
const { embed, cosineSimilarity } = await import("./embeddings-EX7QPXJS.js");
|
|
1554
1520
|
const embeddings = [];
|
|
1555
1521
|
for (const t of templates) {
|
|
1556
1522
|
embeddings.push(await embed(t));
|
|
@@ -1580,11 +1546,11 @@ async function computeSimilarityMatrix(templates) {
|
|
|
1580
1546
|
}
|
|
1581
1547
|
var EMBEDDING_THRESHOLD = 0.65;
|
|
1582
1548
|
var TOKEN_THRESHOLD = 0.35;
|
|
1583
|
-
async function clusterByStructure(analyses,
|
|
1549
|
+
async function clusterByStructure(analyses, threshold) {
|
|
1584
1550
|
if (analyses.length < 2) return [];
|
|
1585
1551
|
const templates = analyses.map((a) => analysisTemplate(a));
|
|
1586
1552
|
const { matrix, method } = await computeSimilarityMatrix(templates);
|
|
1587
|
-
const effectiveThreshold =
|
|
1553
|
+
const effectiveThreshold = threshold ?? (method === "embedding" ? EMBEDDING_THRESHOLD : TOKEN_THRESHOLD);
|
|
1588
1554
|
const seenClusters = /* @__PURE__ */ new Set();
|
|
1589
1555
|
const clusters = [];
|
|
1590
1556
|
let clusterId = 0;
|
|
@@ -1836,6 +1802,8 @@ Return ONLY valid JSON, no markdown fencing.`;
|
|
|
1836
1802
|
abstract: null,
|
|
1837
1803
|
derived_from: null,
|
|
1838
1804
|
polarity: null,
|
|
1805
|
+
engram_version: 1,
|
|
1806
|
+
episode_ids: [],
|
|
1839
1807
|
knowledge_type: {
|
|
1840
1808
|
memory_class: "metacognitive",
|
|
1841
1809
|
cognitive_level: "evaluate"
|
|
@@ -2165,15 +2133,15 @@ Meta-engrams flagged as "[structural transfer \u2014 untested in current domain]
|
|
|
2165
2133
|
}
|
|
2166
2134
|
|
|
2167
2135
|
// src/schemas/meta-engram.ts
|
|
2168
|
-
import { z as
|
|
2169
|
-
var StructuralTemplateSchema =
|
|
2170
|
-
goal_type:
|
|
2171
|
-
constraint_type:
|
|
2172
|
-
outcome_type:
|
|
2173
|
-
template:
|
|
2136
|
+
import { z as z2 } from "zod";
|
|
2137
|
+
var StructuralTemplateSchema = z2.object({
|
|
2138
|
+
goal_type: z2.string().min(1),
|
|
2139
|
+
constraint_type: z2.string().min(1),
|
|
2140
|
+
outcome_type: z2.string().min(1),
|
|
2141
|
+
template: z2.string().min(1),
|
|
2174
2142
|
/** Structural frame — declares which relational pattern this meta-engram uses.
|
|
2175
2143
|
* Allows flexible analogy beyond rigid [goal]+[constraint]->[outcome]. */
|
|
2176
|
-
structure_type:
|
|
2144
|
+
structure_type: z2.enum([
|
|
2177
2145
|
"goal-constraint-outcome",
|
|
2178
2146
|
"feedback-loop",
|
|
2179
2147
|
"causal-chain",
|
|
@@ -2182,47 +2150,672 @@ var StructuralTemplateSchema = z4.object({
|
|
|
2182
2150
|
"freeform"
|
|
2183
2151
|
]).default("goal-constraint-outcome"),
|
|
2184
2152
|
/** For patterns that don't fit the standard template fields — the LLM's own structural description */
|
|
2185
|
-
freeform_structure:
|
|
2153
|
+
freeform_structure: z2.string().optional()
|
|
2186
2154
|
});
|
|
2187
|
-
var EvidenceEntrySchema =
|
|
2188
|
-
engram_id:
|
|
2189
|
-
domain:
|
|
2190
|
-
mapping_rationale:
|
|
2191
|
-
alignment_score:
|
|
2155
|
+
var EvidenceEntrySchema = z2.object({
|
|
2156
|
+
engram_id: z2.string(),
|
|
2157
|
+
domain: z2.string(),
|
|
2158
|
+
mapping_rationale: z2.string(),
|
|
2159
|
+
alignment_score: z2.number().min(0).max(1)
|
|
2192
2160
|
});
|
|
2193
|
-
var FalsificationSchema =
|
|
2194
|
-
expected_conditions:
|
|
2195
|
-
expected_exceptions:
|
|
2196
|
-
test_prediction:
|
|
2161
|
+
var FalsificationSchema = z2.object({
|
|
2162
|
+
expected_conditions: z2.string(),
|
|
2163
|
+
expected_exceptions: z2.string(),
|
|
2164
|
+
test_prediction: z2.string().optional()
|
|
2197
2165
|
});
|
|
2198
|
-
var MetaConfidenceSchema =
|
|
2199
|
-
evidence_count:
|
|
2200
|
-
domain_count:
|
|
2201
|
-
structural_depth:
|
|
2202
|
-
validation_ratio:
|
|
2203
|
-
composite:
|
|
2166
|
+
var MetaConfidenceSchema = z2.object({
|
|
2167
|
+
evidence_count: z2.number().int().min(0),
|
|
2168
|
+
domain_count: z2.number().int().min(0),
|
|
2169
|
+
structural_depth: z2.number().int().min(1).max(5),
|
|
2170
|
+
validation_ratio: z2.number().min(0).max(1).default(0),
|
|
2171
|
+
composite: z2.number().min(0).max(1)
|
|
2204
2172
|
});
|
|
2205
|
-
var DomainCoverageSchema =
|
|
2206
|
-
validated:
|
|
2207
|
-
failed:
|
|
2208
|
-
predicted:
|
|
2173
|
+
var DomainCoverageSchema = z2.object({
|
|
2174
|
+
validated: z2.array(z2.string()),
|
|
2175
|
+
failed: z2.array(z2.string()).default([]),
|
|
2176
|
+
predicted: z2.array(z2.string()).default([])
|
|
2209
2177
|
});
|
|
2210
|
-
var HierarchyPositionSchema =
|
|
2211
|
-
level:
|
|
2212
|
-
parent:
|
|
2213
|
-
children:
|
|
2178
|
+
var HierarchyPositionSchema = z2.object({
|
|
2179
|
+
level: z2.enum(["mop", "top"]),
|
|
2180
|
+
parent: z2.string().nullable().default(null),
|
|
2181
|
+
children: z2.array(z2.string()).default([])
|
|
2214
2182
|
});
|
|
2215
|
-
var MetaFieldSchema =
|
|
2183
|
+
var MetaFieldSchema = z2.object({
|
|
2216
2184
|
structure: StructuralTemplateSchema,
|
|
2217
|
-
evidence:
|
|
2185
|
+
evidence: z2.array(EvidenceEntrySchema).min(2),
|
|
2218
2186
|
domain_coverage: DomainCoverageSchema,
|
|
2219
2187
|
falsification: FalsificationSchema,
|
|
2220
2188
|
confidence: MetaConfidenceSchema,
|
|
2221
2189
|
hierarchy: HierarchyPositionSchema,
|
|
2222
|
-
pipeline_version:
|
|
2223
|
-
last_validated:
|
|
2190
|
+
pipeline_version: z2.string(),
|
|
2191
|
+
last_validated: z2.string().optional()
|
|
2224
2192
|
});
|
|
2225
2193
|
|
|
2194
|
+
// src/model-routing.ts
|
|
2195
|
+
var DEFAULT_MODEL_MAP = {
|
|
2196
|
+
fast: "claude-haiku-4-0",
|
|
2197
|
+
balanced: "claude-sonnet-4-20250514",
|
|
2198
|
+
thorough: "claude-opus-4-20250514"
|
|
2199
|
+
};
|
|
2200
|
+
var DEFAULT_TIERS = {
|
|
2201
|
+
dedup_tier: "fast",
|
|
2202
|
+
profile_tier: "balanced",
|
|
2203
|
+
meta_tier: "thorough"
|
|
2204
|
+
};
|
|
2205
|
+
function selectModel(tier, customMap) {
|
|
2206
|
+
const map = { ...DEFAULT_MODEL_MAP, ...customMap };
|
|
2207
|
+
return map[tier];
|
|
2208
|
+
}
|
|
2209
|
+
function resolveOperationTier(operation, config) {
|
|
2210
|
+
const key = `${operation}_tier`;
|
|
2211
|
+
return config?.[key] ?? DEFAULT_TIERS[key];
|
|
2212
|
+
}
|
|
2213
|
+
function selectModelForOperation(operation, config, customMap) {
|
|
2214
|
+
const tier = resolveOperationTier(operation, config);
|
|
2215
|
+
return selectModel(tier, customMap);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// src/profile.ts
|
|
2219
|
+
import * as fs2 from "fs";
|
|
2220
|
+
import * as path2 from "path";
|
|
2221
|
+
var PROFILE_PROMPT = `You are analyzing a user's memory engrams to create a brief cognitive profile.
|
|
2222
|
+
Below are the user's stored learnings, grouped by domain. Synthesize a concise profile (3-5 sentences) describing:
|
|
2223
|
+
- Key preferences and working style
|
|
2224
|
+
- Technical domains and expertise areas
|
|
2225
|
+
- Constraints and things to avoid
|
|
2226
|
+
- Notable patterns in their knowledge
|
|
2227
|
+
|
|
2228
|
+
Be specific and actionable. Write in second person ("You prefer...", "You work with...").
|
|
2229
|
+
|
|
2230
|
+
Engrams by domain:
|
|
2231
|
+
{engrams_by_domain}
|
|
2232
|
+
|
|
2233
|
+
Profile:`;
|
|
2234
|
+
function getCachePath(storagePath) {
|
|
2235
|
+
return path2.join(storagePath, "profile_cache.json");
|
|
2236
|
+
}
|
|
2237
|
+
function loadProfileCache(storagePath) {
|
|
2238
|
+
try {
|
|
2239
|
+
return JSON.parse(fs2.readFileSync(getCachePath(storagePath), "utf8"));
|
|
2240
|
+
} catch {
|
|
2241
|
+
return null;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
function saveProfileCache(storagePath, cache2) {
|
|
2245
|
+
fs2.writeFileSync(getCachePath(storagePath), JSON.stringify(cache2, null, 2));
|
|
2246
|
+
}
|
|
2247
|
+
function markProfileDirty(storagePath) {
|
|
2248
|
+
const cache2 = loadProfileCache(storagePath);
|
|
2249
|
+
if (cache2) {
|
|
2250
|
+
cache2.dirty = true;
|
|
2251
|
+
saveProfileCache(storagePath, cache2);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function profileNeedsRegeneration(cache2, cacheTtlHours = 24) {
|
|
2255
|
+
if (!cache2) return true;
|
|
2256
|
+
if (!cache2.dirty) return false;
|
|
2257
|
+
const hoursSince = (Date.now() - new Date(cache2.generated_at).getTime()) / (1e3 * 60 * 60);
|
|
2258
|
+
return hoursSince >= cacheTtlHours;
|
|
2259
|
+
}
|
|
2260
|
+
function clusterByDomain(engrams) {
|
|
2261
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
2262
|
+
for (const e of engrams) {
|
|
2263
|
+
const domain = e.domain?.split(".")[0] ?? "general";
|
|
2264
|
+
const list = clusters.get(domain) ?? [];
|
|
2265
|
+
list.push(e);
|
|
2266
|
+
clusters.set(domain, list);
|
|
2267
|
+
}
|
|
2268
|
+
return clusters;
|
|
2269
|
+
}
|
|
2270
|
+
function formatClusters(clusters) {
|
|
2271
|
+
const lines = [];
|
|
2272
|
+
for (const [domain, engrams] of clusters) {
|
|
2273
|
+
lines.push(`
|
|
2274
|
+
### ${domain} (${engrams.length} engrams)`);
|
|
2275
|
+
for (const e of engrams.slice(0, 10)) lines.push(`- ${e.statement}`);
|
|
2276
|
+
if (engrams.length > 10) lines.push(` ... and ${engrams.length - 10} more`);
|
|
2277
|
+
}
|
|
2278
|
+
return lines.join("\n");
|
|
2279
|
+
}
|
|
2280
|
+
async function generateProfile(engrams, llm, storagePath, cacheTtlHours = 24) {
|
|
2281
|
+
const cache2 = loadProfileCache(storagePath);
|
|
2282
|
+
if (cache2 && !profileNeedsRegeneration(cache2, cacheTtlHours)) return cache2.profile;
|
|
2283
|
+
if (engrams.length === 0) return null;
|
|
2284
|
+
const clusters = clusterByDomain(engrams);
|
|
2285
|
+
const prompt = PROFILE_PROMPT.replace("{engrams_by_domain}", formatClusters(clusters));
|
|
2286
|
+
try {
|
|
2287
|
+
const profile = await llm(prompt);
|
|
2288
|
+
if (!profile?.trim()) return cache2?.profile ?? null;
|
|
2289
|
+
saveProfileCache(storagePath, {
|
|
2290
|
+
profile: profile.trim(),
|
|
2291
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2292
|
+
engram_count: engrams.length,
|
|
2293
|
+
dirty: false
|
|
2294
|
+
});
|
|
2295
|
+
return profile.trim();
|
|
2296
|
+
} catch {
|
|
2297
|
+
return cache2?.profile ?? null;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
function getProfileForInjection(storagePath) {
|
|
2301
|
+
const cache2 = loadProfileCache(storagePath);
|
|
2302
|
+
return cache2?.profile ?? null;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// src/migrations/runner.ts
|
|
2306
|
+
import * as fs3 from "fs";
|
|
2307
|
+
import * as yaml4 from "js-yaml";
|
|
2308
|
+
|
|
2309
|
+
// src/migrations/20260406-001-add-commitment.ts
|
|
2310
|
+
var migration = {
|
|
2311
|
+
id: "20260406-001-add-commitment",
|
|
2312
|
+
description: "Add commitment field (default: decided for existing, leaning for new)",
|
|
2313
|
+
up(engrams) {
|
|
2314
|
+
return engrams.map((e) => {
|
|
2315
|
+
const clone = { ...e };
|
|
2316
|
+
if (!clone.commitment) {
|
|
2317
|
+
clone.commitment = e.status === "active" ? "decided" : "leaning";
|
|
2318
|
+
}
|
|
2319
|
+
return clone;
|
|
2320
|
+
});
|
|
2321
|
+
},
|
|
2322
|
+
down(engrams) {
|
|
2323
|
+
return engrams.map((e) => {
|
|
2324
|
+
const clone = { ...e };
|
|
2325
|
+
delete clone.commitment;
|
|
2326
|
+
delete clone.locked_at;
|
|
2327
|
+
delete clone.locked_reason;
|
|
2328
|
+
return clone;
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
|
|
2333
|
+
// src/migrations/20260406-002-add-content-hash.ts
|
|
2334
|
+
var migration2 = {
|
|
2335
|
+
id: "20260406-002-add-content-hash",
|
|
2336
|
+
description: "Add content_hash field (SHA256 of normalized statement)",
|
|
2337
|
+
up(engrams) {
|
|
2338
|
+
return engrams.map((e) => {
|
|
2339
|
+
const clone = { ...e };
|
|
2340
|
+
if (!clone.content_hash) {
|
|
2341
|
+
clone.content_hash = computeContentHash(e.statement);
|
|
2342
|
+
}
|
|
2343
|
+
return clone;
|
|
2344
|
+
});
|
|
2345
|
+
},
|
|
2346
|
+
down(engrams) {
|
|
2347
|
+
return engrams.map((e) => {
|
|
2348
|
+
const clone = { ...e };
|
|
2349
|
+
delete clone.content_hash;
|
|
2350
|
+
return clone;
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
// src/migrations/20260406-003-populate-memory-class.ts
|
|
2356
|
+
var TYPE_TO_MEMORY_CLASS = {
|
|
2357
|
+
behavioral: "semantic",
|
|
2358
|
+
terminological: "semantic",
|
|
2359
|
+
procedural: "procedural",
|
|
2360
|
+
architectural: "semantic"
|
|
2361
|
+
};
|
|
2362
|
+
var migration3 = {
|
|
2363
|
+
id: "20260406-003-populate-memory-class",
|
|
2364
|
+
description: "Populate existing knowledge_type.memory_class based on type",
|
|
2365
|
+
up(engrams) {
|
|
2366
|
+
return engrams.map((e) => {
|
|
2367
|
+
const raw = e;
|
|
2368
|
+
if (raw.knowledge_type?.memory_class) return e;
|
|
2369
|
+
const memoryClass = TYPE_TO_MEMORY_CLASS[e.type] ?? "semantic";
|
|
2370
|
+
const cogLevel = raw.knowledge_type?.cognitive_level ?? "remember";
|
|
2371
|
+
return {
|
|
2372
|
+
...e,
|
|
2373
|
+
knowledge_type: { memory_class: memoryClass, cognitive_level: cogLevel }
|
|
2374
|
+
};
|
|
2375
|
+
});
|
|
2376
|
+
},
|
|
2377
|
+
down(engrams) {
|
|
2378
|
+
return engrams.map((e) => {
|
|
2379
|
+
const raw = e;
|
|
2380
|
+
if (raw.knowledge_type) {
|
|
2381
|
+
const { ...rest } = raw;
|
|
2382
|
+
delete rest.knowledge_type;
|
|
2383
|
+
return rest;
|
|
2384
|
+
}
|
|
2385
|
+
return e;
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
};
|
|
2389
|
+
|
|
2390
|
+
// src/migrations/20260406-004-populate-cognitive-level.ts
|
|
2391
|
+
var TYPE_TO_COGNITIVE = {
|
|
2392
|
+
behavioral: "apply",
|
|
2393
|
+
terminological: "remember",
|
|
2394
|
+
procedural: "apply",
|
|
2395
|
+
architectural: "evaluate"
|
|
2396
|
+
};
|
|
2397
|
+
var TYPE_TO_MEMORY_CLASS2 = {
|
|
2398
|
+
behavioral: "semantic",
|
|
2399
|
+
terminological: "semantic",
|
|
2400
|
+
procedural: "procedural",
|
|
2401
|
+
architectural: "semantic"
|
|
2402
|
+
};
|
|
2403
|
+
var migration4 = {
|
|
2404
|
+
id: "20260406-004-populate-cognitive-level",
|
|
2405
|
+
description: "Populate existing knowledge_type.cognitive_level based on type",
|
|
2406
|
+
up(engrams) {
|
|
2407
|
+
return engrams.map((e) => {
|
|
2408
|
+
const clone = { ...e };
|
|
2409
|
+
const cogLevel = TYPE_TO_COGNITIVE[e.type] ?? "remember";
|
|
2410
|
+
const memClass = TYPE_TO_MEMORY_CLASS2[e.type] ?? "semantic";
|
|
2411
|
+
if (!clone.knowledge_type) {
|
|
2412
|
+
clone.knowledge_type = { cognitive_level: cogLevel, memory_class: memClass };
|
|
2413
|
+
} else {
|
|
2414
|
+
if (!clone.knowledge_type.cognitive_level) {
|
|
2415
|
+
clone.knowledge_type = { ...clone.knowledge_type, cognitive_level: cogLevel };
|
|
2416
|
+
}
|
|
2417
|
+
if (!clone.knowledge_type.memory_class) {
|
|
2418
|
+
clone.knowledge_type = { ...clone.knowledge_type, memory_class: memClass };
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
return clone;
|
|
2422
|
+
});
|
|
2423
|
+
},
|
|
2424
|
+
down(engrams) {
|
|
2425
|
+
return engrams.map((e) => {
|
|
2426
|
+
const clone = { ...e };
|
|
2427
|
+
delete clone.knowledge_type;
|
|
2428
|
+
return clone;
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
};
|
|
2432
|
+
|
|
2433
|
+
// src/migrations/20260406-005-add-version-field.ts
|
|
2434
|
+
var migration5 = {
|
|
2435
|
+
id: "20260406-005-add-version-field",
|
|
2436
|
+
description: "Add engram_version: 1 and episode_ids: [] to all engrams",
|
|
2437
|
+
up(engrams) {
|
|
2438
|
+
return engrams.map((e) => {
|
|
2439
|
+
const raw = e;
|
|
2440
|
+
return {
|
|
2441
|
+
...e,
|
|
2442
|
+
engram_version: raw.engram_version ?? 1,
|
|
2443
|
+
episode_ids: raw.episode_ids ?? []
|
|
2444
|
+
};
|
|
2445
|
+
});
|
|
2446
|
+
},
|
|
2447
|
+
down(engrams) {
|
|
2448
|
+
return engrams.map((e) => {
|
|
2449
|
+
const raw = e;
|
|
2450
|
+
const { engram_version, episode_ids, previous_version_ref, ...rest } = raw;
|
|
2451
|
+
return rest;
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
|
|
2456
|
+
// src/migrations/runner.ts
|
|
2457
|
+
var ALL_MIGRATIONS = [migration, migration2, migration3, migration4, migration5];
|
|
2458
|
+
var CURRENT_SCHEMA_VERSION = ALL_MIGRATIONS.length;
|
|
2459
|
+
function getSchemaVersion(configPath) {
|
|
2460
|
+
if (!fs3.existsSync(configPath)) return 0;
|
|
2461
|
+
try {
|
|
2462
|
+
const raw = yaml4.load(fs3.readFileSync(configPath, "utf8"));
|
|
2463
|
+
if (!raw || typeof raw.schema_version !== "number") return 0;
|
|
2464
|
+
return raw.schema_version;
|
|
2465
|
+
} catch {
|
|
2466
|
+
return 0;
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
function setSchemaVersion(configPath, version) {
|
|
2470
|
+
let configData = {};
|
|
2471
|
+
try {
|
|
2472
|
+
const raw = fs3.readFileSync(configPath, "utf8");
|
|
2473
|
+
if (raw) configData = yaml4.load(raw) ?? {};
|
|
2474
|
+
} catch {
|
|
2475
|
+
}
|
|
2476
|
+
configData.schema_version = version;
|
|
2477
|
+
fs3.writeFileSync(configPath, yaml4.dump(configData, { lineWidth: 120, noRefs: true }));
|
|
2478
|
+
}
|
|
2479
|
+
function createBackup(engramsPath, version) {
|
|
2480
|
+
if (!fs3.existsSync(engramsPath)) return null;
|
|
2481
|
+
const backupPath = `${engramsPath}.bak.${version}`;
|
|
2482
|
+
fs3.copyFileSync(engramsPath, backupPath);
|
|
2483
|
+
return backupPath;
|
|
2484
|
+
}
|
|
2485
|
+
function restoreBackup(engramsPath, backupPath) {
|
|
2486
|
+
fs3.copyFileSync(backupPath, engramsPath);
|
|
2487
|
+
}
|
|
2488
|
+
function runMigrations(engramsPath, configPath, options) {
|
|
2489
|
+
const currentVersion = getSchemaVersion(configPath);
|
|
2490
|
+
const pending = ALL_MIGRATIONS.slice(currentVersion);
|
|
2491
|
+
if (pending.length === 0) {
|
|
2492
|
+
return { applied: [], schema_version: currentVersion, backup_path: null };
|
|
2493
|
+
}
|
|
2494
|
+
const backupPath = options?.dryRun ? null : createBackup(engramsPath, currentVersion);
|
|
2495
|
+
let engrams = loadEngrams(engramsPath);
|
|
2496
|
+
const applied = [];
|
|
2497
|
+
for (const migration6 of pending) {
|
|
2498
|
+
logger.info(`Running migration: ${migration6.id} \u2014 ${migration6.description}`);
|
|
2499
|
+
try {
|
|
2500
|
+
engrams = migration6.up(engrams);
|
|
2501
|
+
applied.push(migration6.id);
|
|
2502
|
+
} catch (err) {
|
|
2503
|
+
logger.error(`Migration ${migration6.id} failed: ${err}`);
|
|
2504
|
+
if (backupPath) {
|
|
2505
|
+
restoreBackup(engramsPath, backupPath);
|
|
2506
|
+
logger.info(`Restored engrams.yaml from backup: ${backupPath}`);
|
|
2507
|
+
}
|
|
2508
|
+
throw new Error(`Migration ${migration6.id} failed: ${err}. Engrams restored from backup.`);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
if (!options?.dryRun) {
|
|
2512
|
+
saveEngrams(engramsPath, engrams);
|
|
2513
|
+
const newVersion = currentVersion + applied.length;
|
|
2514
|
+
setSchemaVersion(configPath, newVersion);
|
|
2515
|
+
}
|
|
2516
|
+
return {
|
|
2517
|
+
applied,
|
|
2518
|
+
schema_version: currentVersion + applied.length,
|
|
2519
|
+
backup_path: backupPath
|
|
2520
|
+
};
|
|
2521
|
+
}
|
|
2522
|
+
function rollbackMigrations(engramsPath, configPath, targetVersion) {
|
|
2523
|
+
const currentVersion = getSchemaVersion(configPath);
|
|
2524
|
+
if (targetVersion >= currentVersion) {
|
|
2525
|
+
return { applied: [], schema_version: currentVersion, backup_path: null };
|
|
2526
|
+
}
|
|
2527
|
+
if (targetVersion < 0) {
|
|
2528
|
+
throw new Error("Target version cannot be negative");
|
|
2529
|
+
}
|
|
2530
|
+
const backupPath = createBackup(engramsPath, currentVersion);
|
|
2531
|
+
let engrams = loadEngrams(engramsPath);
|
|
2532
|
+
const rolledBack = [];
|
|
2533
|
+
const toRollback = ALL_MIGRATIONS.slice(targetVersion, currentVersion).reverse();
|
|
2534
|
+
for (const migration6 of toRollback) {
|
|
2535
|
+
logger.info(`Rolling back migration: ${migration6.id}`);
|
|
2536
|
+
try {
|
|
2537
|
+
engrams = migration6.down(engrams);
|
|
2538
|
+
rolledBack.push(migration6.id);
|
|
2539
|
+
} catch (err) {
|
|
2540
|
+
logger.error(`Rollback of ${migration6.id} failed: ${err}`);
|
|
2541
|
+
if (backupPath) {
|
|
2542
|
+
restoreBackup(engramsPath, backupPath);
|
|
2543
|
+
logger.info(`Restored engrams.yaml from backup: ${backupPath}`);
|
|
2544
|
+
}
|
|
2545
|
+
throw new Error(`Rollback of ${migration6.id} failed: ${err}. Engrams restored from backup.`);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
saveEngrams(engramsPath, engrams);
|
|
2549
|
+
setSchemaVersion(configPath, targetVersion);
|
|
2550
|
+
return {
|
|
2551
|
+
applied: rolledBack,
|
|
2552
|
+
schema_version: targetVersion,
|
|
2553
|
+
backup_path: backupPath
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// src/store/yaml-store.ts
|
|
2558
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2559
|
+
import { readFile } from "fs/promises";
|
|
2560
|
+
import * as yaml5 from "js-yaml";
|
|
2561
|
+
|
|
2562
|
+
// src/store/async-fs.ts
|
|
2563
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2564
|
+
import { writeFile, rename, mkdir } from "fs/promises";
|
|
2565
|
+
import { dirname } from "path";
|
|
2566
|
+
async function asyncAtomicWrite(filePath, content) {
|
|
2567
|
+
const dir = dirname(filePath);
|
|
2568
|
+
if (!existsSync7(dir)) await mkdir(dir, { recursive: true });
|
|
2569
|
+
const tmp = filePath + ".tmp";
|
|
2570
|
+
await writeFile(tmp, content);
|
|
2571
|
+
await rename(tmp, filePath);
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
// src/store/async-lock.ts
|
|
2575
|
+
import { writeFile as writeFile2, unlink, stat } from "fs/promises";
|
|
2576
|
+
import { constants } from "fs";
|
|
2577
|
+
function sleep(ms) {
|
|
2578
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2579
|
+
}
|
|
2580
|
+
async function withAsyncLock(filePath, fn, options) {
|
|
2581
|
+
const lockPath = filePath + ".lock";
|
|
2582
|
+
const maxRetries = options?.maxRetries ?? 5;
|
|
2583
|
+
const baseDelay = options?.baseDelay ?? 100;
|
|
2584
|
+
const staleThreshold = options?.staleThreshold ?? 1e4;
|
|
2585
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2586
|
+
try {
|
|
2587
|
+
await writeFile2(lockPath, `${process.pid}`, { flag: constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL });
|
|
2588
|
+
break;
|
|
2589
|
+
} catch (err) {
|
|
2590
|
+
if (err.code !== "EEXIST") throw err;
|
|
2591
|
+
try {
|
|
2592
|
+
const s = await stat(lockPath);
|
|
2593
|
+
if (Date.now() - s.mtimeMs > staleThreshold) {
|
|
2594
|
+
await unlink(lockPath).catch(() => {
|
|
2595
|
+
});
|
|
2596
|
+
continue;
|
|
2597
|
+
}
|
|
2598
|
+
} catch {
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
if (attempt === maxRetries) {
|
|
2602
|
+
throw new Error(`Failed to acquire lock on ${filePath} after ${maxRetries} retries`);
|
|
2603
|
+
}
|
|
2604
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
2605
|
+
await sleep(delay);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
try {
|
|
2609
|
+
return await fn();
|
|
2610
|
+
} finally {
|
|
2611
|
+
await unlink(lockPath).catch(() => {
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
// src/store/yaml-store.ts
|
|
2617
|
+
var YamlStore = class {
|
|
2618
|
+
filePath;
|
|
2619
|
+
constructor(filePath) {
|
|
2620
|
+
this.filePath = filePath;
|
|
2621
|
+
}
|
|
2622
|
+
async load() {
|
|
2623
|
+
if (!existsSync8(this.filePath)) return [];
|
|
2624
|
+
try {
|
|
2625
|
+
const content = await readFile(this.filePath, "utf8");
|
|
2626
|
+
const raw = yaml5.load(content);
|
|
2627
|
+
if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
|
|
2628
|
+
const valid = [];
|
|
2629
|
+
let skipped = 0;
|
|
2630
|
+
for (const entry of raw.engrams) {
|
|
2631
|
+
const result = EngramSchemaPassthrough.safeParse(entry);
|
|
2632
|
+
if (result.success) valid.push(result.data);
|
|
2633
|
+
else skipped++;
|
|
2634
|
+
}
|
|
2635
|
+
if (skipped > 0) logger.warning(`Skipped ${skipped} invalid engram(s) in ${this.filePath}`);
|
|
2636
|
+
return valid;
|
|
2637
|
+
} catch (err) {
|
|
2638
|
+
logger.error(`Failed to parse engrams file ${this.filePath}: ${err}`);
|
|
2639
|
+
return [];
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
async save(engrams) {
|
|
2643
|
+
await withAsyncLock(this.filePath, async () => {
|
|
2644
|
+
const content = yaml5.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
2645
|
+
await asyncAtomicWrite(this.filePath, content);
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
async append(engram) {
|
|
2649
|
+
await withAsyncLock(this.filePath, async () => {
|
|
2650
|
+
const engrams = await this._loadRaw();
|
|
2651
|
+
engrams.push(engram);
|
|
2652
|
+
const content = yaml5.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
2653
|
+
await asyncAtomicWrite(this.filePath, content);
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
async getById(id) {
|
|
2657
|
+
const engrams = await this.load();
|
|
2658
|
+
return engrams.find((e) => e.id === id) ?? null;
|
|
2659
|
+
}
|
|
2660
|
+
async remove(id) {
|
|
2661
|
+
return await withAsyncLock(this.filePath, async () => {
|
|
2662
|
+
const engrams = await this._loadRaw();
|
|
2663
|
+
const idx = engrams.findIndex((e) => e.id === id);
|
|
2664
|
+
if (idx === -1) return false;
|
|
2665
|
+
engrams.splice(idx, 1);
|
|
2666
|
+
const content = yaml5.dump({ engrams }, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
2667
|
+
await asyncAtomicWrite(this.filePath, content);
|
|
2668
|
+
return true;
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
async count(filter) {
|
|
2672
|
+
const engrams = await this.load();
|
|
2673
|
+
if (filter?.status) {
|
|
2674
|
+
return engrams.filter((e) => e.status === filter.status).length;
|
|
2675
|
+
}
|
|
2676
|
+
return engrams.length;
|
|
2677
|
+
}
|
|
2678
|
+
async close() {
|
|
2679
|
+
}
|
|
2680
|
+
/** Raw load without validation — for internal mutate-and-save operations. */
|
|
2681
|
+
async _loadRaw() {
|
|
2682
|
+
if (!existsSync8(this.filePath)) return [];
|
|
2683
|
+
try {
|
|
2684
|
+
const content = await readFile(this.filePath, "utf8");
|
|
2685
|
+
const raw = yaml5.load(content);
|
|
2686
|
+
if (!raw?.engrams || !Array.isArray(raw.engrams)) return [];
|
|
2687
|
+
const valid = [];
|
|
2688
|
+
for (const entry of raw.engrams) {
|
|
2689
|
+
const result = EngramSchemaPassthrough.safeParse(entry);
|
|
2690
|
+
if (result.success) valid.push(result.data);
|
|
2691
|
+
}
|
|
2692
|
+
return valid;
|
|
2693
|
+
} catch {
|
|
2694
|
+
return [];
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
|
|
2699
|
+
// src/store/sqlite-store.ts
|
|
2700
|
+
import { createRequire as createRequire2 } from "module";
|
|
2701
|
+
var require3 = createRequire2(import.meta.url);
|
|
2702
|
+
var Database2 = null;
|
|
2703
|
+
function getDatabase2() {
|
|
2704
|
+
if (!Database2) {
|
|
2705
|
+
try {
|
|
2706
|
+
Database2 = require3("better-sqlite3");
|
|
2707
|
+
} catch {
|
|
2708
|
+
throw new Error(
|
|
2709
|
+
"better-sqlite3 is required for sqlite backend. Install it with: npm install better-sqlite3"
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
return Database2;
|
|
2714
|
+
}
|
|
2715
|
+
var SqliteStore = class {
|
|
2716
|
+
dbPath;
|
|
2717
|
+
db = null;
|
|
2718
|
+
constructor(dbPath) {
|
|
2719
|
+
this.dbPath = dbPath;
|
|
2720
|
+
}
|
|
2721
|
+
getDb() {
|
|
2722
|
+
if (!this.db) {
|
|
2723
|
+
const DB = getDatabase2();
|
|
2724
|
+
this.db = new DB(this.dbPath);
|
|
2725
|
+
this.db.pragma("journal_mode = WAL");
|
|
2726
|
+
this.db.exec(`
|
|
2727
|
+
CREATE TABLE IF NOT EXISTS engrams (
|
|
2728
|
+
id TEXT PRIMARY KEY,
|
|
2729
|
+
status TEXT NOT NULL,
|
|
2730
|
+
scope TEXT NOT NULL,
|
|
2731
|
+
domain TEXT,
|
|
2732
|
+
last_accessed TEXT,
|
|
2733
|
+
data TEXT NOT NULL
|
|
2734
|
+
);
|
|
2735
|
+
CREATE INDEX IF NOT EXISTS idx_status ON engrams(status);
|
|
2736
|
+
CREATE INDEX IF NOT EXISTS idx_scope ON engrams(scope);
|
|
2737
|
+
CREATE INDEX IF NOT EXISTS idx_domain ON engrams(domain);
|
|
2738
|
+
`);
|
|
2739
|
+
}
|
|
2740
|
+
return this.db;
|
|
2741
|
+
}
|
|
2742
|
+
async load() {
|
|
2743
|
+
const db = this.getDb();
|
|
2744
|
+
const rows = db.prepare("SELECT data FROM engrams").all();
|
|
2745
|
+
return rows.map((r) => JSON.parse(r.data));
|
|
2746
|
+
}
|
|
2747
|
+
async save(engrams) {
|
|
2748
|
+
const db = this.getDb();
|
|
2749
|
+
const upsert = db.prepare(`
|
|
2750
|
+
INSERT OR REPLACE INTO engrams (id, status, scope, domain, last_accessed, data)
|
|
2751
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2752
|
+
`);
|
|
2753
|
+
const tx = db.transaction(() => {
|
|
2754
|
+
db.exec("DELETE FROM engrams");
|
|
2755
|
+
for (const e of engrams) {
|
|
2756
|
+
upsert.run(e.id, e.status, e.scope, e.domain ?? null, e.activation.last_accessed, JSON.stringify(e));
|
|
2757
|
+
}
|
|
2758
|
+
});
|
|
2759
|
+
tx();
|
|
2760
|
+
}
|
|
2761
|
+
async append(engram) {
|
|
2762
|
+
const db = this.getDb();
|
|
2763
|
+
db.prepare(`
|
|
2764
|
+
INSERT OR REPLACE INTO engrams (id, status, scope, domain, last_accessed, data)
|
|
2765
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
2766
|
+
`).run(engram.id, engram.status, engram.scope, engram.domain ?? null, engram.activation.last_accessed, JSON.stringify(engram));
|
|
2767
|
+
}
|
|
2768
|
+
async getById(id) {
|
|
2769
|
+
const db = this.getDb();
|
|
2770
|
+
const row = db.prepare("SELECT data FROM engrams WHERE id = ?").get(id);
|
|
2771
|
+
if (!row) return null;
|
|
2772
|
+
return JSON.parse(row.data);
|
|
2773
|
+
}
|
|
2774
|
+
async remove(id) {
|
|
2775
|
+
const db = this.getDb();
|
|
2776
|
+
const result = db.prepare("DELETE FROM engrams WHERE id = ?").run(id);
|
|
2777
|
+
return result.changes > 0;
|
|
2778
|
+
}
|
|
2779
|
+
async count(filter) {
|
|
2780
|
+
const db = this.getDb();
|
|
2781
|
+
if (filter?.status) {
|
|
2782
|
+
return db.prepare("SELECT COUNT(*) as c FROM engrams WHERE status = ?").get(filter.status).c;
|
|
2783
|
+
}
|
|
2784
|
+
return db.prepare("SELECT COUNT(*) as c FROM engrams").get().c;
|
|
2785
|
+
}
|
|
2786
|
+
async close() {
|
|
2787
|
+
if (this.db) {
|
|
2788
|
+
this.db.close();
|
|
2789
|
+
this.db = null;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
/** Import engrams from an array (e.g., from YAML migration). */
|
|
2793
|
+
async importFrom(engrams) {
|
|
2794
|
+
await this.save(engrams);
|
|
2795
|
+
}
|
|
2796
|
+
/** Export all engrams as an array (e.g., for YAML migration). */
|
|
2797
|
+
async exportAll() {
|
|
2798
|
+
return this.load();
|
|
2799
|
+
}
|
|
2800
|
+
};
|
|
2801
|
+
|
|
2802
|
+
// src/store/factory.ts
|
|
2803
|
+
import { join as join4 } from "path";
|
|
2804
|
+
function createStore(config) {
|
|
2805
|
+
switch (config.backend) {
|
|
2806
|
+
case "sqlite":
|
|
2807
|
+
return new SqliteStore(join4(config.path, "engrams.db"));
|
|
2808
|
+
case "yaml":
|
|
2809
|
+
default:
|
|
2810
|
+
return new YamlStore(join4(config.path, "engrams.yaml"));
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
async function migrateStore(from, to) {
|
|
2814
|
+
const engrams = await from.load();
|
|
2815
|
+
await to.save(engrams);
|
|
2816
|
+
return engrams.length;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2226
2819
|
// src/version-check.ts
|
|
2227
2820
|
var cache = /* @__PURE__ */ new Map();
|
|
2228
2821
|
async function checkForUpdate(packageName, currentVersion, onResult) {
|
|
@@ -2275,6 +2868,18 @@ function isNewer(a, b) {
|
|
|
2275
2868
|
}
|
|
2276
2869
|
|
|
2277
2870
|
// src/index.ts
|
|
2871
|
+
var COMMITMENT_MULTIPLIER = {
|
|
2872
|
+
locked: 1,
|
|
2873
|
+
decided: 0.9,
|
|
2874
|
+
leaning: 0.7,
|
|
2875
|
+
exploring: 0.5
|
|
2876
|
+
};
|
|
2877
|
+
var TYPE_TO_COGNITIVE2 = {
|
|
2878
|
+
behavioral: "apply",
|
|
2879
|
+
terminological: "remember",
|
|
2880
|
+
procedural: "apply",
|
|
2881
|
+
architectural: "evaluate"
|
|
2882
|
+
};
|
|
2278
2883
|
var INGEST_PATTERNS = [
|
|
2279
2884
|
{ re: /(?:we decided|the decision is|agreed to)\s+(.+?)\.?$/gim, type: "architectural" },
|
|
2280
2885
|
{ re: /(?:always|never|must|should)\s+(.+?)\.?$/gim, type: "behavioral" },
|
|
@@ -2287,6 +2892,8 @@ var Plur = class {
|
|
|
2287
2892
|
config;
|
|
2288
2893
|
indexedStorage = null;
|
|
2289
2894
|
_engramCache = /* @__PURE__ */ new Map();
|
|
2895
|
+
_llmFailureCount = 0;
|
|
2896
|
+
_llmDisabledUntil = null;
|
|
2290
2897
|
constructor(options) {
|
|
2291
2898
|
this.paths = detectPlurStorage(options?.path);
|
|
2292
2899
|
this.config = loadConfig(this.paths.config);
|
|
@@ -2334,17 +2941,17 @@ var Plur = class {
|
|
|
2334
2941
|
return all;
|
|
2335
2942
|
}
|
|
2336
2943
|
/** Load engrams from a path with mtime-based caching */
|
|
2337
|
-
_loadCached(
|
|
2944
|
+
_loadCached(path3) {
|
|
2338
2945
|
let mtime = 0;
|
|
2339
2946
|
try {
|
|
2340
|
-
mtime =
|
|
2947
|
+
mtime = fs4.statSync(path3).mtimeMs;
|
|
2341
2948
|
} catch {
|
|
2342
2949
|
return [];
|
|
2343
2950
|
}
|
|
2344
|
-
const cached = this._engramCache.get(
|
|
2951
|
+
const cached = this._engramCache.get(path3);
|
|
2345
2952
|
if (cached && cached.mtime === mtime) return cached.engrams;
|
|
2346
|
-
const engrams = loadEngrams(
|
|
2347
|
-
this._engramCache.set(
|
|
2953
|
+
const engrams = loadEngrams(path3);
|
|
2954
|
+
this._engramCache.set(path3, { mtime, engrams });
|
|
2348
2955
|
return engrams;
|
|
2349
2956
|
}
|
|
2350
2957
|
/** Find which store owns an engram by ID. For namespaced IDs, strips prefix to find in store. */
|
|
@@ -2367,7 +2974,35 @@ var Plur = class {
|
|
|
2367
2974
|
}
|
|
2368
2975
|
return null;
|
|
2369
2976
|
}
|
|
2370
|
-
/**
|
|
2977
|
+
/** Content hash fast-path dedup. */
|
|
2978
|
+
_hashDedup(statement, engrams) {
|
|
2979
|
+
const hash = computeContentHash(statement);
|
|
2980
|
+
for (const e of engrams) {
|
|
2981
|
+
if (e.status === "active" && e.content_hash === hash) return e;
|
|
2982
|
+
}
|
|
2983
|
+
return null;
|
|
2984
|
+
}
|
|
2985
|
+
_isLlmDedupAvailable() {
|
|
2986
|
+
if (this._llmDisabledUntil !== null) {
|
|
2987
|
+
if (Date.now() < this._llmDisabledUntil) return false;
|
|
2988
|
+
this._llmDisabledUntil = null;
|
|
2989
|
+
this._llmFailureCount = 0;
|
|
2990
|
+
}
|
|
2991
|
+
return true;
|
|
2992
|
+
}
|
|
2993
|
+
_recordLlmFailure() {
|
|
2994
|
+
this._llmFailureCount++;
|
|
2995
|
+
if (this._llmFailureCount >= 3) {
|
|
2996
|
+
this._llmDisabledUntil = Date.now() + 60 * 60 * 1e3;
|
|
2997
|
+
logger.warning("LLM dedup circuit breaker tripped \u2014 disabled for 1 hour");
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
_recordLlmSuccess() {
|
|
3001
|
+
this._llmFailureCount = 0;
|
|
3002
|
+
}
|
|
3003
|
+
/** Create engram with content hash + commitment + cognitive level.
|
|
3004
|
+
* Fast-path hash dedup returns existing on exact match.
|
|
3005
|
+
*/
|
|
2371
3006
|
learn(statement, context) {
|
|
2372
3007
|
if (!this.config.allow_secrets) {
|
|
2373
3008
|
const secrets = detectSecrets(statement);
|
|
@@ -2378,17 +3013,34 @@ var Plur = class {
|
|
|
2378
3013
|
return withLock(this.paths.engrams, () => {
|
|
2379
3014
|
const engrams = loadEngrams(this.paths.engrams);
|
|
2380
3015
|
const allEngrams = this._loadAllEngrams();
|
|
3016
|
+
const hashMatch = this._hashDedup(statement, allEngrams);
|
|
3017
|
+
if (hashMatch) return hashMatch;
|
|
2381
3018
|
const id = generateEngramId(allEngrams);
|
|
2382
3019
|
const scope = context?.scope ?? "global";
|
|
2383
3020
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3021
|
+
const type = context?.type ?? "behavioral";
|
|
3022
|
+
const cogLevel = TYPE_TO_COGNITIVE2[type] ?? "remember";
|
|
3023
|
+
const commitment = context?.commitment ?? "leaning";
|
|
2384
3024
|
const conflictingEngrams = detectConflicts({ statement, scope }, allEngrams);
|
|
2385
3025
|
const conflictIds = conflictingEngrams.map((e) => e.id);
|
|
3026
|
+
const TYPE_TO_MEMORY_CLASS3 = {
|
|
3027
|
+
behavioral: "semantic",
|
|
3028
|
+
terminological: "semantic",
|
|
3029
|
+
procedural: "procedural",
|
|
3030
|
+
architectural: "semantic"
|
|
3031
|
+
};
|
|
3032
|
+
const engramType = context?.type ?? "behavioral";
|
|
3033
|
+
const memoryClass = context?.memory_class ?? TYPE_TO_MEMORY_CLASS3[engramType] ?? "semantic";
|
|
3034
|
+
const episodeIds = [];
|
|
3035
|
+
if (context?.session_episode_id) {
|
|
3036
|
+
episodeIds.push(context.session_episode_id);
|
|
3037
|
+
}
|
|
2386
3038
|
const engram = {
|
|
2387
3039
|
id,
|
|
2388
3040
|
version: 2,
|
|
2389
3041
|
status: "active",
|
|
2390
3042
|
consolidated: false,
|
|
2391
|
-
type
|
|
3043
|
+
type,
|
|
2392
3044
|
scope,
|
|
2393
3045
|
visibility: context?.visibility ?? (context?.domain ? "public" : "private"),
|
|
2394
3046
|
statement,
|
|
@@ -2402,6 +3054,7 @@ var Plur = class {
|
|
|
2402
3054
|
last_accessed: now.slice(0, 10)
|
|
2403
3055
|
},
|
|
2404
3056
|
feedback_signals: { positive: 0, negative: 0, neutral: 0 },
|
|
3057
|
+
knowledge_type: { memory_class: memoryClass, cognitive_level: cogLevel },
|
|
2405
3058
|
knowledge_anchors: (context?.knowledge_anchors ?? []).map((a) => ({
|
|
2406
3059
|
path: a.path,
|
|
2407
3060
|
relevance: a.relevance ?? "supporting",
|
|
@@ -2415,6 +3068,13 @@ var Plur = class {
|
|
|
2415
3068
|
derived_from: context?.derived_from ?? null,
|
|
2416
3069
|
dual_coding: context?.dual_coding,
|
|
2417
3070
|
polarity: null,
|
|
3071
|
+
content_hash: computeContentHash(statement),
|
|
3072
|
+
commitment,
|
|
3073
|
+
locked_at: commitment === "locked" ? now : void 0,
|
|
3074
|
+
locked_reason: commitment === "locked" ? context?.locked_reason : void 0,
|
|
3075
|
+
summary: autoSummary(statement, void 0),
|
|
3076
|
+
engram_version: 1,
|
|
3077
|
+
episode_ids: episodeIds ?? [],
|
|
2418
3078
|
relations: conflictIds.length > 0 ? {
|
|
2419
3079
|
broader: [],
|
|
2420
3080
|
narrower: [],
|
|
@@ -2425,9 +3085,42 @@ var Plur = class {
|
|
|
2425
3085
|
engrams.push(engram);
|
|
2426
3086
|
saveEngrams(this.paths.engrams, engrams);
|
|
2427
3087
|
this._syncIndex();
|
|
3088
|
+
appendHistory(this.paths.root, {
|
|
3089
|
+
event: "engram_created",
|
|
3090
|
+
engram_id: engram.id,
|
|
3091
|
+
timestamp: now,
|
|
3092
|
+
data: { type: engram.type, scope: engram.scope, source: engram.source }
|
|
3093
|
+
});
|
|
2428
3094
|
return engram;
|
|
2429
3095
|
});
|
|
2430
3096
|
}
|
|
3097
|
+
/** Build deps for learn-async module. */
|
|
3098
|
+
_learnAsyncDeps() {
|
|
3099
|
+
return {
|
|
3100
|
+
hashDedup: (statement) => this._hashDedup(statement, this._loadAllEngrams()),
|
|
3101
|
+
recallHybrid: (query, options) => this.recallHybrid(query, options),
|
|
3102
|
+
recall: (query, options) => this.recall(query, options),
|
|
3103
|
+
learn: (statement, context) => this.learn(statement, context),
|
|
3104
|
+
getById: (id) => this.getById(id),
|
|
3105
|
+
engramsPath: this.paths.engrams,
|
|
3106
|
+
rootPath: this.paths.root,
|
|
3107
|
+
dedupConfig: this.config.dedup ?? {},
|
|
3108
|
+
isLlmAvailable: () => this._isLlmDedupAvailable(),
|
|
3109
|
+
recordLlmSuccess: () => this._recordLlmSuccess(),
|
|
3110
|
+
recordLlmFailure: () => this._recordLlmFailure(),
|
|
3111
|
+
syncIndex: () => this._syncIndex()
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
/** Async learn with LLM-driven deduplication (Ideas 1+2+19). */
|
|
3115
|
+
async learnAsync(statement, context) {
|
|
3116
|
+
const { learnAsync: learnAsyncImpl } = await import("./learn-async-VXBH3TYE.js");
|
|
3117
|
+
return learnAsyncImpl(this._learnAsyncDeps(), statement, context);
|
|
3118
|
+
}
|
|
3119
|
+
/** Batch learn with LLM dedup. */
|
|
3120
|
+
async learnBatch(statements, llm) {
|
|
3121
|
+
const { learnBatch: learnBatchImpl } = await import("./learn-async-VXBH3TYE.js");
|
|
3122
|
+
return learnBatchImpl(this._learnAsyncDeps(), statements, llm);
|
|
3123
|
+
}
|
|
2431
3124
|
/**
|
|
2432
3125
|
* Search engrams, filter by scope/domain/strength, reactivate accessed.
|
|
2433
3126
|
* Supports two modes:
|
|
@@ -2474,6 +3167,13 @@ var Plur = class {
|
|
|
2474
3167
|
this._reactivateResults(results);
|
|
2475
3168
|
return results;
|
|
2476
3169
|
}
|
|
3170
|
+
async recallAutoSearch(query, options) {
|
|
3171
|
+
const filtered = this._filterEngrams(options);
|
|
3172
|
+
const limit = options?.limit ?? 20;
|
|
3173
|
+
const result = await recallAuto(filtered, query, limit, this.paths.root, options?.llm);
|
|
3174
|
+
this._reactivateResults(result.results);
|
|
3175
|
+
return result;
|
|
3176
|
+
}
|
|
2477
3177
|
/** Get a single engram by ID, regardless of status. Searches primary + all stores. */
|
|
2478
3178
|
getById(id) {
|
|
2479
3179
|
const engrams = this._loadAllEngrams();
|
|
@@ -2610,13 +3310,9 @@ var Plur = class {
|
|
|
2610
3310
|
},
|
|
2611
3311
|
embeddingBoosts
|
|
2612
3312
|
);
|
|
2613
|
-
const
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
};
|
|
2617
|
-
const directivesStr = formatEngrams(result.directives);
|
|
2618
|
-
const constraintsStr = formatEngrams(result.constraints);
|
|
2619
|
-
const considerStr = formatEngrams(result.consider);
|
|
3313
|
+
const directivesStr = formatWithLayer(result.directives, assignLayer("directives"));
|
|
3314
|
+
const constraintsStr = formatWithLayer(result.constraints, assignLayer("constraints"));
|
|
3315
|
+
const considerStr = formatWithLayer(result.consider, assignLayer("consider"));
|
|
2620
3316
|
const count = result.directives.length + result.constraints.length + result.consider.length;
|
|
2621
3317
|
const tokensUsed = result.tokens_used.directives + result.tokens_used.consider;
|
|
2622
3318
|
const injected_ids = [
|
|
@@ -2645,11 +3341,20 @@ var Plur = class {
|
|
|
2645
3341
|
engram.feedback_signals[signal] += 1;
|
|
2646
3342
|
if (signal === "positive") {
|
|
2647
3343
|
engram.activation.retrieval_strength = Math.min(1, engram.activation.retrieval_strength + 0.05);
|
|
3344
|
+
const e = engram;
|
|
3345
|
+
if (e.commitment === "exploring") e.commitment = "leaning";
|
|
3346
|
+
else if (e.commitment === "leaning") e.commitment = "decided";
|
|
2648
3347
|
} else if (signal === "negative") {
|
|
2649
3348
|
engram.activation.retrieval_strength = Math.max(0, engram.activation.retrieval_strength - 0.1);
|
|
2650
3349
|
}
|
|
2651
3350
|
saveEngrams(this.paths.engrams, engrams);
|
|
2652
3351
|
this._syncIndex();
|
|
3352
|
+
appendHistory(this.paths.root, {
|
|
3353
|
+
event: "feedback_received",
|
|
3354
|
+
engram_id: id,
|
|
3355
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3356
|
+
data: { signal }
|
|
3357
|
+
});
|
|
2653
3358
|
return true;
|
|
2654
3359
|
});
|
|
2655
3360
|
if (found) return;
|
|
@@ -2724,6 +3429,12 @@ var Plur = class {
|
|
|
2724
3429
|
}
|
|
2725
3430
|
saveEngrams(this.paths.engrams, engrams);
|
|
2726
3431
|
this._syncIndex();
|
|
3432
|
+
appendHistory(this.paths.root, {
|
|
3433
|
+
event: "engram_retired",
|
|
3434
|
+
engram_id: id,
|
|
3435
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3436
|
+
data: { reason: reason ?? null }
|
|
3437
|
+
});
|
|
2727
3438
|
return true;
|
|
2728
3439
|
});
|
|
2729
3440
|
if (foundInPrimary) return;
|
|
@@ -2775,12 +3486,12 @@ var Plur = class {
|
|
|
2775
3486
|
}
|
|
2776
3487
|
/** Search packs for an engram by ID and apply feedback, writing back to the pack's engrams.yaml. */
|
|
2777
3488
|
_feedbackPack(id, signal) {
|
|
2778
|
-
if (!
|
|
2779
|
-
for (const entry of
|
|
3489
|
+
if (!fs4.existsSync(this.paths.packs)) throw new Error(`Engram not found: ${id}`);
|
|
3490
|
+
for (const entry of fs4.readdirSync(this.paths.packs)) {
|
|
2780
3491
|
const packDir = `${this.paths.packs}/${entry}`;
|
|
2781
|
-
if (!
|
|
3492
|
+
if (!fs4.statSync(packDir).isDirectory()) continue;
|
|
2782
3493
|
const engramsPath = `${packDir}/engrams.yaml`;
|
|
2783
|
-
if (!
|
|
3494
|
+
if (!fs4.existsSync(engramsPath)) continue;
|
|
2784
3495
|
const engrams = loadEngrams(engramsPath);
|
|
2785
3496
|
const engram = engrams.find((e) => e.id === id);
|
|
2786
3497
|
if (!engram) continue;
|
|
@@ -2859,6 +3570,12 @@ var Plur = class {
|
|
|
2859
3570
|
listPacks() {
|
|
2860
3571
|
return listPacks(this.paths.packs);
|
|
2861
3572
|
}
|
|
3573
|
+
// SP5 methods (deferred — vault-export, registry not yet merged)
|
|
3574
|
+
// exportToVault, discoverPacks, getRegistryUrl will be added when SP5 merges
|
|
3575
|
+
/** Get the PLUR storage root path. */
|
|
3576
|
+
getStorageRoot() {
|
|
3577
|
+
return this.paths.root;
|
|
3578
|
+
}
|
|
2862
3579
|
/** Sync engrams to git. Initializes repo on first call, commits + push/pull on subsequent calls. */
|
|
2863
3580
|
sync(remote) {
|
|
2864
3581
|
return sync(this.paths.root, remote);
|
|
@@ -2867,17 +3584,158 @@ var Plur = class {
|
|
|
2867
3584
|
syncStatus() {
|
|
2868
3585
|
return getSyncStatus(this.paths.root);
|
|
2869
3586
|
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Promote an episode to an episodic engram (SP2 Idea 3).
|
|
3589
|
+
* Creates a new engram with memory_class='episodic' from an episode's summary.
|
|
3590
|
+
*/
|
|
3591
|
+
episodeToEngram(episodeId, context) {
|
|
3592
|
+
const episodes = queryTimeline(this.paths.episodes);
|
|
3593
|
+
const episode = episodes.find((e) => e.id === episodeId);
|
|
3594
|
+
if (!episode) throw new Error(`Episode not found: ${episodeId}`);
|
|
3595
|
+
const engram = this.learn(episode.summary, {
|
|
3596
|
+
...context,
|
|
3597
|
+
type: context?.type ?? "behavioral",
|
|
3598
|
+
source: context?.source ?? `episode:${episodeId}`,
|
|
3599
|
+
memory_class: "episodic",
|
|
3600
|
+
session_episode_id: episodeId
|
|
3601
|
+
});
|
|
3602
|
+
appendHistory(this.paths.root, {
|
|
3603
|
+
event: "engram_promoted",
|
|
3604
|
+
engram_id: engram.id,
|
|
3605
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3606
|
+
data: { from_episode: episodeId }
|
|
3607
|
+
});
|
|
3608
|
+
return engram;
|
|
3609
|
+
}
|
|
3610
|
+
/**
|
|
3611
|
+
* Get history events for a specific engram (SP2 Idea 7).
|
|
3612
|
+
* Returns all events across all months for the given engram ID.
|
|
3613
|
+
*/
|
|
3614
|
+
getEngramHistory(engramId) {
|
|
3615
|
+
return readHistoryForEngram(this.paths.root, engramId);
|
|
3616
|
+
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Report a failure for a procedural engram (SP2 Idea 18).
|
|
3619
|
+
* If LLM is provided, generates an improved procedure and updates the engram.
|
|
3620
|
+
* Without LLM, logs the failure without rewriting.
|
|
3621
|
+
* Returns the updated engram and the failure episode.
|
|
3622
|
+
*/
|
|
3623
|
+
async reportFailure(engramId, failureContext, llm) {
|
|
3624
|
+
const engram = this.getById(engramId);
|
|
3625
|
+
if (!engram) throw new Error(`Engram not found: ${engramId}`);
|
|
3626
|
+
const memClass = engram.knowledge_type?.memory_class;
|
|
3627
|
+
if (memClass !== "procedural" && engram.type !== "procedural") {
|
|
3628
|
+
throw new Error(`Only procedural engrams can evolve. This engram has type=${engram.type}, memory_class=${memClass}`);
|
|
3629
|
+
}
|
|
3630
|
+
const history = readHistoryForEngram(this.paths.root, engramId);
|
|
3631
|
+
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
|
|
3632
|
+
const recentEvolutions = history.filter(
|
|
3633
|
+
(e) => e.event === "procedure_evolved" && e.timestamp > dayAgo
|
|
3634
|
+
);
|
|
3635
|
+
if (recentEvolutions.length >= 3) {
|
|
3636
|
+
throw new Error(`Rate limit: engram ${engramId} has been evolved ${recentEvolutions.length} times in the last 24h (max 3)`);
|
|
3637
|
+
}
|
|
3638
|
+
const episode = this.capture(`Failure report for ${engramId}: ${failureContext}`, {
|
|
3639
|
+
tags: ["failure", "procedure-evolution"]
|
|
3640
|
+
});
|
|
3641
|
+
const failureEventId = generateEventId();
|
|
3642
|
+
appendHistory(this.paths.root, {
|
|
3643
|
+
event: "failure_reported",
|
|
3644
|
+
engram_id: engramId,
|
|
3645
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3646
|
+
data: { failure_context: failureContext, episode_id: episode.id, event_id: failureEventId }
|
|
3647
|
+
});
|
|
3648
|
+
let evolved = false;
|
|
3649
|
+
if (llm) {
|
|
3650
|
+
try {
|
|
3651
|
+
const prompt = `You are improving a procedural memory based on a failure report.
|
|
3652
|
+
|
|
3653
|
+
Current procedure: "${engram.statement}"
|
|
3654
|
+
Failure report: "${failureContext}"
|
|
3655
|
+
${recentEvolutions.length > 0 ? `
|
|
3656
|
+
Previous revisions in last 24h: ${recentEvolutions.length}` : ""}
|
|
3657
|
+
|
|
3658
|
+
Generate an improved version of the procedure that prevents this failure. Return ONLY the improved procedure statement, nothing else.`;
|
|
3659
|
+
const improved = await llm(prompt);
|
|
3660
|
+
if (improved && improved.trim().length > 0) {
|
|
3661
|
+
const eventId = generateEventId();
|
|
3662
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3663
|
+
return withLock(this.paths.engrams, () => {
|
|
3664
|
+
const engrams = loadEngrams(this.paths.engrams);
|
|
3665
|
+
const idx = engrams.findIndex((e) => e.id === engramId);
|
|
3666
|
+
if (idx === -1) throw new Error(`Engram not found in store: ${engramId}`);
|
|
3667
|
+
const raw = engrams[idx];
|
|
3668
|
+
const oldStatement = raw.statement;
|
|
3669
|
+
const oldVersion = raw.engram_version ?? 1;
|
|
3670
|
+
raw.statement = improved.trim();
|
|
3671
|
+
raw.engram_version = oldVersion + 1;
|
|
3672
|
+
raw.previous_version_ref = { event_id: eventId, changed_at: now };
|
|
3673
|
+
if (!raw.episode_ids) raw.episode_ids = [];
|
|
3674
|
+
raw.episode_ids.push(episode.id);
|
|
3675
|
+
saveEngrams(this.paths.engrams, engrams);
|
|
3676
|
+
this._syncIndex();
|
|
3677
|
+
appendHistory(this.paths.root, {
|
|
3678
|
+
event: "procedure_evolved",
|
|
3679
|
+
engram_id: engramId,
|
|
3680
|
+
timestamp: now,
|
|
3681
|
+
data: {
|
|
3682
|
+
event_id: eventId,
|
|
3683
|
+
old_statement: oldStatement,
|
|
3684
|
+
new_statement: improved.trim(),
|
|
3685
|
+
old_version: oldVersion,
|
|
3686
|
+
new_version: oldVersion + 1,
|
|
3687
|
+
failure_context: failureContext,
|
|
3688
|
+
failure_episode_id: episode.id
|
|
3689
|
+
}
|
|
3690
|
+
});
|
|
3691
|
+
evolved = true;
|
|
3692
|
+
return { engram: engrams[idx], episode, evolved };
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
} catch {
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
withLock(this.paths.engrams, () => {
|
|
3699
|
+
const engrams = loadEngrams(this.paths.engrams);
|
|
3700
|
+
const idx = engrams.findIndex((e) => e.id === engramId);
|
|
3701
|
+
if (idx !== -1) {
|
|
3702
|
+
const raw = engrams[idx];
|
|
3703
|
+
if (!raw.episode_ids) raw.episode_ids = [];
|
|
3704
|
+
raw.episode_ids.push(episode.id);
|
|
3705
|
+
saveEngrams(this.paths.engrams, engrams);
|
|
3706
|
+
this._syncIndex();
|
|
3707
|
+
}
|
|
3708
|
+
});
|
|
3709
|
+
const updated = this.getById(engramId);
|
|
3710
|
+
return { engram: updated ?? engram, episode, evolved };
|
|
3711
|
+
}
|
|
2870
3712
|
/** Return system health info. */
|
|
2871
3713
|
status() {
|
|
2872
3714
|
const engrams = this._loadAllEngrams();
|
|
2873
3715
|
const episodes = queryTimeline(this.paths.episodes);
|
|
2874
3716
|
const packs = listPacks(this.paths.packs);
|
|
3717
|
+
const active = engrams.filter((e) => e.status !== "retired");
|
|
3718
|
+
const lockedCount = active.filter((e) => e.commitment === "locked").length;
|
|
3719
|
+
const tensionPairs = /* @__PURE__ */ new Set();
|
|
3720
|
+
for (const e of active) {
|
|
3721
|
+
if (!e.relations?.conflicts?.length) continue;
|
|
3722
|
+
for (const cid of e.relations.conflicts) {
|
|
3723
|
+
tensionPairs.add([e.id, cid].sort().join(":"));
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
const versionedCount = engrams.filter((e) => {
|
|
3727
|
+
const raw = e;
|
|
3728
|
+
return (raw.engram_version ?? 1) > 1;
|
|
3729
|
+
}).length;
|
|
2875
3730
|
return {
|
|
2876
|
-
engram_count:
|
|
3731
|
+
engram_count: active.length,
|
|
2877
3732
|
episode_count: episodes.length,
|
|
2878
3733
|
pack_count: packs.length,
|
|
2879
3734
|
storage_root: this.paths.root,
|
|
2880
|
-
config: this.config
|
|
3735
|
+
config: this.config,
|
|
3736
|
+
locked_count: lockedCount,
|
|
3737
|
+
tension_count: tensionPairs.size,
|
|
3738
|
+
versioned_engram_count: versionedCount
|
|
2881
3739
|
};
|
|
2882
3740
|
}
|
|
2883
3741
|
/** Register an additional engram store. */
|
|
@@ -2893,12 +3751,12 @@ var Plur = class {
|
|
|
2893
3751
|
}];
|
|
2894
3752
|
let configData = {};
|
|
2895
3753
|
try {
|
|
2896
|
-
const raw =
|
|
2897
|
-
if (raw) configData =
|
|
3754
|
+
const raw = fs4.readFileSync(this.paths.config, "utf8");
|
|
3755
|
+
if (raw) configData = yaml6.load(raw) ?? {};
|
|
2898
3756
|
} catch {
|
|
2899
3757
|
}
|
|
2900
3758
|
configData.stores = stores;
|
|
2901
|
-
|
|
3759
|
+
fs4.writeFileSync(this.paths.config, yaml6.dump(configData, { lineWidth: 120, noRefs: true }));
|
|
2902
3760
|
this.config = loadConfig(this.paths.config);
|
|
2903
3761
|
}
|
|
2904
3762
|
/** List all configured stores. */
|
|
@@ -2923,6 +3781,9 @@ var Plur = class {
|
|
|
2923
3781
|
}
|
|
2924
3782
|
};
|
|
2925
3783
|
export {
|
|
3784
|
+
ALL_MIGRATIONS,
|
|
3785
|
+
COMMITMENT_MULTIPLIER,
|
|
3786
|
+
CURRENT_SCHEMA_VERSION,
|
|
2926
3787
|
DomainCoverageSchema,
|
|
2927
3788
|
EvidenceEntrySchema,
|
|
2928
3789
|
FalsificationSchema,
|
|
@@ -2933,25 +3794,64 @@ export {
|
|
|
2933
3794
|
PLATITUDE_PATTERNS,
|
|
2934
3795
|
Plur,
|
|
2935
3796
|
SessionBreadcrumbs,
|
|
3797
|
+
SqliteStore,
|
|
2936
3798
|
StructuralTemplateSchema,
|
|
3799
|
+
YamlStore,
|
|
2937
3800
|
alignCluster,
|
|
2938
3801
|
analyzeStructure,
|
|
3802
|
+
appendHistory,
|
|
3803
|
+
assignLayer,
|
|
3804
|
+
asyncAtomicWrite,
|
|
3805
|
+
autoSummary,
|
|
3806
|
+
buildBatchDedupPrompt,
|
|
3807
|
+
buildDedupPrompt,
|
|
2939
3808
|
checkForUpdate,
|
|
2940
3809
|
classifyPolarity,
|
|
2941
3810
|
clearVersionCache,
|
|
2942
3811
|
clusterByStructure,
|
|
2943
3812
|
computeConfidence,
|
|
3813
|
+
computeContentHash,
|
|
2944
3814
|
computeMetaConfidence,
|
|
2945
3815
|
confidenceBand,
|
|
3816
|
+
createStore,
|
|
2946
3817
|
detectPlurStorage,
|
|
2947
3818
|
detectSecrets,
|
|
2948
3819
|
engramSearchText,
|
|
2949
3820
|
extractMetaEngrams,
|
|
3821
|
+
formatLayer1,
|
|
3822
|
+
formatLayer2,
|
|
3823
|
+
formatLayer3,
|
|
3824
|
+
formatWithLayer,
|
|
2950
3825
|
formulateMetaEngram,
|
|
3826
|
+
freshTailBoost,
|
|
3827
|
+
generateEventId,
|
|
2951
3828
|
generateGuardrails,
|
|
3829
|
+
generateProfile,
|
|
3830
|
+
generateSummary,
|
|
2952
3831
|
getCachedUpdateCheck,
|
|
3832
|
+
getProfileForInjection,
|
|
3833
|
+
getSchemaVersion,
|
|
2953
3834
|
isPlatitude,
|
|
3835
|
+
listHistoryMonths,
|
|
3836
|
+
loadProfileCache,
|
|
3837
|
+
markProfileDirty,
|
|
3838
|
+
migrateStore,
|
|
3839
|
+
needsSummary,
|
|
3840
|
+
normalizeStatement,
|
|
2954
3841
|
organizeHierarchy,
|
|
3842
|
+
parseDedupResponse,
|
|
3843
|
+
profileNeedsRegeneration,
|
|
3844
|
+
readHistory,
|
|
3845
|
+
readHistoryForEngram,
|
|
3846
|
+
recallAuto,
|
|
3847
|
+
resolveOperationTier,
|
|
3848
|
+
rollbackMigrations,
|
|
3849
|
+
runMigrations,
|
|
3850
|
+
saveProfileCache,
|
|
3851
|
+
selectModel,
|
|
3852
|
+
selectModelForOperation,
|
|
3853
|
+
setSchemaVersion,
|
|
2955
3854
|
tokenSimilarity,
|
|
2956
|
-
validateMetaEngram
|
|
3855
|
+
validateMetaEngram,
|
|
3856
|
+
withAsyncLock
|
|
2957
3857
|
};
|