@gamaze/hicortex 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-md.js +52 -6
- package/dist/consolidate.d.ts +1 -1
- package/dist/consolidate.js +170 -9
- package/dist/db.js +10 -0
- package/dist/distiller.d.ts +7 -2
- package/dist/distiller.js +16 -3
- package/dist/extensions.d.ts +3 -0
- package/dist/mcp-server.js +21 -0
- package/dist/nightly.js +18 -5
- package/dist/prompts.d.ts +5 -0
- package/dist/prompts.js +24 -0
- package/dist/redact.d.ts +45 -0
- package/dist/redact.js +101 -0
- package/dist/state.d.ts +3 -1
- package/dist/storage.js +1 -0
- package/dist/types.d.ts +23 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/claude-md.js
CHANGED
|
@@ -51,6 +51,7 @@ const node_os_1 = require("node:os");
|
|
|
51
51
|
const storage = __importStar(require("./storage.js"));
|
|
52
52
|
const features_js_1 = require("./features.js");
|
|
53
53
|
const extensions_js_1 = require("./extensions.js");
|
|
54
|
+
const state_js_1 = require("./state.js");
|
|
54
55
|
const START_MARKER = "<!-- HICORTEX-LEARNINGS:START -->";
|
|
55
56
|
const END_MARKER = "<!-- HICORTEX-LEARNINGS:END -->";
|
|
56
57
|
const DEFAULT_CLAUDE_MD = (0, node_path_1.join)((0, node_os_1.homedir)(), ".claude", "CLAUDE.md");
|
|
@@ -63,11 +64,15 @@ async function injectLessons(db, options = {}) {
|
|
|
63
64
|
const claudeMdPath = options.claudeMdPath ?? DEFAULT_CLAUDE_MD;
|
|
64
65
|
// Determine limits based on license
|
|
65
66
|
const maxLessons = (0, features_js_1.lessonsLimit)();
|
|
67
|
+
// Load MODULE_INDEX from state for domain-aware selection
|
|
68
|
+
const state = (0, state_js_1.loadState)(options.stateDir);
|
|
69
|
+
const moduleIndex = state.moduleIndex;
|
|
66
70
|
// --- Lessons ---
|
|
67
71
|
const lessons = storage.getLessons(db, 30, options.project);
|
|
68
72
|
const selected = await (0, extensions_js_1.getLessonSelector)().select(lessons, {
|
|
69
73
|
maxLessons,
|
|
70
74
|
project: options.project,
|
|
75
|
+
moduleIndex,
|
|
71
76
|
});
|
|
72
77
|
const lessonLines = selected.map((l) => {
|
|
73
78
|
const titleMatch = l.content.match(/## Lesson: (.+)/);
|
|
@@ -78,7 +83,8 @@ async function injectLessons(db, options = {}) {
|
|
|
78
83
|
return `- ${title}${meta ? ` (${meta})` : ""}`;
|
|
79
84
|
});
|
|
80
85
|
// --- Memory Index ---
|
|
81
|
-
const
|
|
86
|
+
const tokenBudget = readModuleIndexTokenBudget();
|
|
87
|
+
const indexLines = buildModuleIndex(db, tokenBudget, moduleIndex);
|
|
82
88
|
const totalCount = storage.countMemories(db);
|
|
83
89
|
const lessonCount = lessons.length;
|
|
84
90
|
const sourceCount = countSources(db);
|
|
@@ -102,9 +108,9 @@ async function injectLessons(db, options = {}) {
|
|
|
102
108
|
blockParts.push(...projectContext);
|
|
103
109
|
}
|
|
104
110
|
// Memory index
|
|
105
|
-
if (
|
|
111
|
+
if (indexLines.length > 0) {
|
|
106
112
|
blockParts.push("", "### Memory Index");
|
|
107
|
-
blockParts.push(
|
|
113
|
+
blockParts.push(...indexLines);
|
|
108
114
|
blockParts.push(`${totalCount} memories, ${lessonCount} lessons, ${sourceCount} agents. Search with \`hicortex_search\`.`);
|
|
109
115
|
}
|
|
110
116
|
blockParts.push(END_MARKER);
|
|
@@ -136,17 +142,57 @@ async function injectLessons(db, options = {}) {
|
|
|
136
142
|
(0, node_fs_1.writeFileSync)(claudeMdPath, content);
|
|
137
143
|
return { lessonsCount: selected.length, path: claudeMdPath };
|
|
138
144
|
}
|
|
145
|
+
const DEFAULT_MODULE_INDEX_TOKEN_BUDGET = 500;
|
|
146
|
+
// BPE tokenizers average 3.5-4.5 chars/token for English prose. 4 is conservative.
|
|
147
|
+
const CHARS_PER_TOKEN = 4;
|
|
139
148
|
/**
|
|
140
|
-
*
|
|
149
|
+
* Read moduleIndexTokenBudget from config.json. Defaults to 500.
|
|
141
150
|
*/
|
|
142
|
-
function
|
|
151
|
+
function readModuleIndexTokenBudget() {
|
|
152
|
+
try {
|
|
153
|
+
const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".hicortex", "config.json");
|
|
154
|
+
const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
155
|
+
return typeof config.moduleIndexTokenBudget === "number"
|
|
156
|
+
? config.moduleIndexTokenBudget
|
|
157
|
+
: DEFAULT_MODULE_INDEX_TOKEN_BUDGET;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return DEFAULT_MODULE_INDEX_TOKEN_BUDGET;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build structured MODULE_INDEX block for injection.
|
|
165
|
+
* Uses domain-grouped format if MODULE_INDEX is available,
|
|
166
|
+
* falls back to flat "project: count" format otherwise.
|
|
167
|
+
*/
|
|
168
|
+
function buildModuleIndex(db, tokenBudget, moduleIndex) {
|
|
169
|
+
if (moduleIndex && moduleIndex.domains.length > 0) {
|
|
170
|
+
const charBudget = tokenBudget * CHARS_PER_TOKEN;
|
|
171
|
+
let charCount = 0;
|
|
172
|
+
const lines = [];
|
|
173
|
+
for (const domain of moduleIndex.domains) {
|
|
174
|
+
const kwStr = domain.keywords.length > 0
|
|
175
|
+
? `: ${domain.keywords.join(", ")}`
|
|
176
|
+
: "";
|
|
177
|
+
const domainLine = `${domain.name} (${domain.memoryCount} memories, ${domain.lessonCount} lessons)${kwStr}`;
|
|
178
|
+
const projectLine = ` ${domain.projects.join(" | ")}`;
|
|
179
|
+
const blockChars = domainLine.length + projectLine.length + 2; // +2 for newlines
|
|
180
|
+
if (charCount + blockChars > charBudget && lines.length > 0)
|
|
181
|
+
break;
|
|
182
|
+
lines.push(domainLine);
|
|
183
|
+
lines.push(projectLine);
|
|
184
|
+
charCount += blockChars;
|
|
185
|
+
}
|
|
186
|
+
return lines;
|
|
187
|
+
}
|
|
188
|
+
// Fallback: flat project index (OSS default)
|
|
143
189
|
try {
|
|
144
190
|
const rows = db
|
|
145
191
|
.prepare(`SELECT project, COUNT(*) as cnt FROM memories
|
|
146
192
|
WHERE project IS NOT NULL
|
|
147
193
|
GROUP BY project ORDER BY cnt DESC LIMIT 10`)
|
|
148
194
|
.all();
|
|
149
|
-
return rows.map((r) => `${r.project}: ${r.cnt}`);
|
|
195
|
+
return [rows.map((r) => `${r.project}: ${r.cnt}`).join(" | ")];
|
|
150
196
|
}
|
|
151
197
|
catch {
|
|
152
198
|
return [];
|
package/dist/consolidate.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export declare function parseJsonLenient<T>(text: string, fallback: T): T;
|
|
|
24
24
|
/**
|
|
25
25
|
* Run the full consolidation pipeline. Returns a structured report.
|
|
26
26
|
*/
|
|
27
|
-
export declare function runConsolidation(db: Database.Database, llm: LlmClient, embedFn: EmbedFn, dryRun?: boolean, skipReflection?: boolean): Promise<ConsolidationReport>;
|
|
27
|
+
export declare function runConsolidation(db: Database.Database, llm: LlmClient, embedFn: EmbedFn, dryRun?: boolean, skipReflection?: boolean, stateDir?: string): Promise<ConsolidationReport>;
|
|
28
28
|
/**
|
|
29
29
|
* Calculate milliseconds until the next occurrence of a given hour (local time).
|
|
30
30
|
*/
|
package/dist/consolidate.js
CHANGED
|
@@ -46,6 +46,7 @@ exports.scheduleConsolidation = scheduleConsolidation;
|
|
|
46
46
|
const retrieval_js_1 = require("./retrieval.js");
|
|
47
47
|
const storage = __importStar(require("./storage.js"));
|
|
48
48
|
const prompts_js_1 = require("./prompts.js");
|
|
49
|
+
const node_crypto_1 = require("node:crypto");
|
|
49
50
|
const features_js_1 = require("./features.js");
|
|
50
51
|
const state_js_1 = require("./state.js");
|
|
51
52
|
// Default config constants (matching Python config.py)
|
|
@@ -273,14 +274,42 @@ async function stageReflection(db, memories, llm, budget, embedFn, dryRun) {
|
|
|
273
274
|
break;
|
|
274
275
|
}
|
|
275
276
|
const embedding = await embedFn(content);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
277
|
+
// Contradiction check: find semantically similar existing lessons.
|
|
278
|
+
// If a very similar lesson exists, ask the LLM whether the new one
|
|
279
|
+
// contradicts it. If yes, suppress the new lesson to prevent the
|
|
280
|
+
// "false coherence" failure mode (wrong lessons reinforcing themselves).
|
|
281
|
+
const similarLessons = storage.vectorSearch(db, embedding, 3)
|
|
282
|
+
.filter((n) => {
|
|
283
|
+
const sim = 1.0 - n.distance;
|
|
284
|
+
return sim > 0.80 && n.memory_type === "lesson";
|
|
282
285
|
});
|
|
283
|
-
|
|
286
|
+
let contradicted = false;
|
|
287
|
+
if (similarLessons.length > 0 && budget.use("contradiction_check")) {
|
|
288
|
+
const existingText = similarLessons[0].content.slice(0, 300);
|
|
289
|
+
const newText = content.slice(0, 300);
|
|
290
|
+
try {
|
|
291
|
+
const verdict = await llm.completeFast(`Two lessons from an AI memory system. Do they CONTRADICT each other (opposite advice on the same topic)?\n\n` +
|
|
292
|
+
`EXISTING: ${existingText}\n\nNEW: ${newText}\n\n` +
|
|
293
|
+
`Answer ONLY "yes" or "no". If the new lesson updates/refines the existing one (not contradicts), answer "no".`, 16);
|
|
294
|
+
if (verdict.toLowerCase().trim().startsWith("yes")) {
|
|
295
|
+
contradicted = true;
|
|
296
|
+
console.log(`[hicortex] Lesson suppressed (contradicts existing): "${lessonText.slice(0, 80)}"`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// LLM call failed — don't suppress, store the lesson
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (!contradicted) {
|
|
304
|
+
storage.insertMemory(db, content, embedding, {
|
|
305
|
+
sourceAgent: "hicortex/reflection",
|
|
306
|
+
project,
|
|
307
|
+
memoryType: "lesson",
|
|
308
|
+
baseStrength: baseStrength[severity] ?? 0.8,
|
|
309
|
+
privacy: "WORK",
|
|
310
|
+
});
|
|
311
|
+
generated++;
|
|
312
|
+
}
|
|
284
313
|
}
|
|
285
314
|
catch {
|
|
286
315
|
// Failed to store lesson
|
|
@@ -293,6 +322,136 @@ async function stageReflection(db, memories, llm, budget, embedFn, dryRun) {
|
|
|
293
322
|
}
|
|
294
323
|
}
|
|
295
324
|
// ---------------------------------------------------------------------------
|
|
325
|
+
// Stage 2.7: Domain Curation (MODULE_INDEX)
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
async function stageDomainCuration(db, llm, budget, dryRun, stateDir) {
|
|
328
|
+
// Gather all projects with memory and lesson counts
|
|
329
|
+
const projectRows = db
|
|
330
|
+
.prepare(`SELECT project, COUNT(*) as cnt FROM memories
|
|
331
|
+
WHERE project IS NOT NULL GROUP BY project ORDER BY cnt DESC`)
|
|
332
|
+
.all();
|
|
333
|
+
if (projectRows.length === 0) {
|
|
334
|
+
return { curated: false, domains: 0, reason: "no_projects" };
|
|
335
|
+
}
|
|
336
|
+
const lessonRows = db
|
|
337
|
+
.prepare(`SELECT project, COUNT(*) as cnt FROM memories
|
|
338
|
+
WHERE project IS NOT NULL AND memory_type = 'lesson'
|
|
339
|
+
GROUP BY project`)
|
|
340
|
+
.all();
|
|
341
|
+
const lessonsByProject = new Map(lessonRows.map((r) => [r.project, r.cnt]));
|
|
342
|
+
// Cache check: skip if project set unchanged
|
|
343
|
+
const sortedNames = projectRows.map((r) => r.project).sort();
|
|
344
|
+
const projectSetHash = (0, node_crypto_1.createHash)("sha256")
|
|
345
|
+
.update(JSON.stringify(sortedNames))
|
|
346
|
+
.digest("hex");
|
|
347
|
+
const state = (0, state_js_1.loadState)(stateDir);
|
|
348
|
+
if (state.moduleIndex?.projectSetHash === projectSetHash) {
|
|
349
|
+
return { curated: false, domains: state.moduleIndex.domains.length, reason: "project_set_unchanged" };
|
|
350
|
+
}
|
|
351
|
+
const totalMemories = projectRows.reduce((s, r) => s + r.cnt, 0);
|
|
352
|
+
const totalLessons = lessonRows.reduce((s, r) => s + r.cnt, 0);
|
|
353
|
+
let domains;
|
|
354
|
+
if (!(0, features_js_1.isPro)()) {
|
|
355
|
+
// OSS fallback: each project is its own domain
|
|
356
|
+
domains = projectRows.map((r) => ({
|
|
357
|
+
name: r.project,
|
|
358
|
+
projects: [r.project],
|
|
359
|
+
memoryCount: r.cnt,
|
|
360
|
+
lessonCount: lessonsByProject.get(r.project) ?? 0,
|
|
361
|
+
keywords: [],
|
|
362
|
+
}));
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// Pro: LLM-curated domains
|
|
366
|
+
if (!budget.use("domain_curation")) {
|
|
367
|
+
return { curated: false, domains: 0, reason: "budget_exhausted" };
|
|
368
|
+
}
|
|
369
|
+
const projectLines = projectRows
|
|
370
|
+
.map((r) => `${r.project}: ${r.cnt} / ${lessonsByProject.get(r.project) ?? 0}`)
|
|
371
|
+
.join("\n");
|
|
372
|
+
try {
|
|
373
|
+
const raw = await llm.completeFast((0, prompts_js_1.domainCuration)(projectLines), 1024);
|
|
374
|
+
const parsed = parseJsonLenient(raw, []);
|
|
375
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
376
|
+
console.warn("[hicortex] Domain curation: LLM returned empty/invalid response, using fallback");
|
|
377
|
+
domains = projectRows.map((r) => ({
|
|
378
|
+
name: r.project,
|
|
379
|
+
projects: [r.project],
|
|
380
|
+
memoryCount: r.cnt,
|
|
381
|
+
lessonCount: lessonsByProject.get(r.project) ?? 0,
|
|
382
|
+
keywords: [],
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
domains = [];
|
|
387
|
+
const assigned = new Set();
|
|
388
|
+
const knownProjects = new Set(sortedNames);
|
|
389
|
+
for (const item of parsed) {
|
|
390
|
+
if (typeof item !== "object" || item === null)
|
|
391
|
+
continue;
|
|
392
|
+
const d = item;
|
|
393
|
+
const name = String(d.name ?? "");
|
|
394
|
+
const projects = Array.isArray(d.projects)
|
|
395
|
+
? d.projects.map(String).filter((p) => !assigned.has(p) && knownProjects.has(p))
|
|
396
|
+
: [];
|
|
397
|
+
const keywords = Array.isArray(d.keywords)
|
|
398
|
+
? d.keywords.map(String).slice(0, 5)
|
|
399
|
+
: [];
|
|
400
|
+
if (!name || projects.length === 0)
|
|
401
|
+
continue;
|
|
402
|
+
for (const p of projects)
|
|
403
|
+
assigned.add(p);
|
|
404
|
+
const memoryCount = projects.reduce((s, p) => s + (projectRows.find((r) => r.project === p)?.cnt ?? 0), 0);
|
|
405
|
+
const lessonCount = projects.reduce((s, p) => s + (lessonsByProject.get(p) ?? 0), 0);
|
|
406
|
+
domains.push({ name, projects, memoryCount, lessonCount, keywords });
|
|
407
|
+
}
|
|
408
|
+
// Catch unassigned projects
|
|
409
|
+
const unassigned = sortedNames.filter((p) => !assigned.has(p));
|
|
410
|
+
if (unassigned.length > 0) {
|
|
411
|
+
const memoryCount = unassigned.reduce((s, p) => s + (projectRows.find((r) => r.project === p)?.cnt ?? 0), 0);
|
|
412
|
+
const lessonCount = unassigned.reduce((s, p) => s + (lessonsByProject.get(p) ?? 0), 0);
|
|
413
|
+
domains.push({ name: "Miscellaneous", projects: unassigned, memoryCount, lessonCount, keywords: [] });
|
|
414
|
+
}
|
|
415
|
+
// Sort by memoryCount desc
|
|
416
|
+
domains.sort((a, b) => b.memoryCount - a.memoryCount);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
console.warn(`[hicortex] Domain curation LLM failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
421
|
+
domains = projectRows.map((r) => ({
|
|
422
|
+
name: r.project,
|
|
423
|
+
projects: [r.project],
|
|
424
|
+
memoryCount: r.cnt,
|
|
425
|
+
lessonCount: lessonsByProject.get(r.project) ?? 0,
|
|
426
|
+
keywords: [],
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const moduleIndex = {
|
|
431
|
+
domains,
|
|
432
|
+
projectSetHash,
|
|
433
|
+
curatedAt: new Date().toISOString(),
|
|
434
|
+
totalMemories,
|
|
435
|
+
totalLessons,
|
|
436
|
+
};
|
|
437
|
+
if (!dryRun) {
|
|
438
|
+
// Persist MODULE_INDEX to state.json
|
|
439
|
+
(0, state_js_1.updateState)((s) => { s.moduleIndex = moduleIndex; }, stateDir);
|
|
440
|
+
// Batch-update domain column on memories
|
|
441
|
+
const updateStmt = db.prepare("UPDATE memories SET domain = ? WHERE project = ?");
|
|
442
|
+
const tx = db.transaction(() => {
|
|
443
|
+
for (const domain of domains) {
|
|
444
|
+
for (const project of domain.projects) {
|
|
445
|
+
updateStmt.run(domain.name, project);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
tx();
|
|
450
|
+
}
|
|
451
|
+
console.log(`[hicortex] Domain curation: ${domains.length} domains from ${projectRows.length} projects`);
|
|
452
|
+
return { curated: true, domains: domains.length };
|
|
453
|
+
}
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
296
455
|
// Stage 3: Link Discovery (vector similarity auto-link)
|
|
297
456
|
// ---------------------------------------------------------------------------
|
|
298
457
|
async function stageLinks(db, memories, embedFn, dryRun) {
|
|
@@ -388,7 +547,7 @@ function stageDecayPrune(db, dryRun) {
|
|
|
388
547
|
/**
|
|
389
548
|
* Run the full consolidation pipeline. Returns a structured report.
|
|
390
549
|
*/
|
|
391
|
-
async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection = false) {
|
|
550
|
+
async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection = false, stateDir) {
|
|
392
551
|
const start = new Date();
|
|
393
552
|
const report = {
|
|
394
553
|
started_at: start.toISOString(),
|
|
@@ -434,6 +593,8 @@ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection
|
|
|
434
593
|
else {
|
|
435
594
|
report.stages.reflection = await stageReflection(db, precheck.newMemories, llm, budget, embedFn, dryRun);
|
|
436
595
|
}
|
|
596
|
+
// Stage 2.7: Domain Curation
|
|
597
|
+
report.stages.domain_curation = await stageDomainCuration(db, llm, budget, dryRun, stateDir);
|
|
437
598
|
// Stage 3: Link Discovery
|
|
438
599
|
report.stages.links = await stageLinks(db, precheck.newMemories, embedFn, dryRun);
|
|
439
600
|
// Stage 4: Decay & Prune
|
|
@@ -448,7 +609,7 @@ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection
|
|
|
448
609
|
(0, state_js_1.updateState)((s) => {
|
|
449
610
|
s.lastConsolidated = new Date().toISOString();
|
|
450
611
|
return s;
|
|
451
|
-
});
|
|
612
|
+
}, stateDir);
|
|
452
613
|
}
|
|
453
614
|
report.budget = budget.summary();
|
|
454
615
|
report.completed_at = new Date().toISOString();
|
package/dist/db.js
CHANGED
|
@@ -216,6 +216,16 @@ const MIGRATIONS = [
|
|
|
216
216
|
}
|
|
217
217
|
},
|
|
218
218
|
},
|
|
219
|
+
{
|
|
220
|
+
version: 3,
|
|
221
|
+
name: "add_domain",
|
|
222
|
+
up: (db) => {
|
|
223
|
+
if (!hasColumn(db, "memories", "domain")) {
|
|
224
|
+
db.exec("ALTER TABLE memories ADD COLUMN domain TEXT");
|
|
225
|
+
}
|
|
226
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_domain ON memories(domain)");
|
|
227
|
+
},
|
|
228
|
+
},
|
|
219
229
|
];
|
|
220
230
|
/**
|
|
221
231
|
* Run all pending migrations against the database.
|
package/dist/distiller.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* not from filesystem scanning.
|
|
5
5
|
*/
|
|
6
6
|
import type { LlmClient } from "./llm.js";
|
|
7
|
+
import { type RedactionConfig } from "./redact.js";
|
|
7
8
|
/**
|
|
8
9
|
* Estimate a safe chunk size in chars based on the LLM provider and model.
|
|
9
10
|
* - API providers (Anthropic, OpenAI, claude-cli): no chunking needed (large context windows)
|
|
@@ -14,9 +15,13 @@ import type { LlmClient } from "./llm.js";
|
|
|
14
15
|
*/
|
|
15
16
|
export declare function detectChunkSize(provider: string, model: string, baseUrl?: string): Promise<number>;
|
|
16
17
|
/**
|
|
17
|
-
* Convert
|
|
18
|
+
* Convert session messages to a filtered transcript string.
|
|
19
|
+
* Handles OC hook format, CC JSONL, and Pi JSONL.
|
|
20
|
+
*
|
|
21
|
+
* If redactionConfig is provided (or defaults to enabled), secrets and PII
|
|
22
|
+
* are scrubbed from the final text BEFORE it reaches any LLM or storage.
|
|
18
23
|
*/
|
|
19
|
-
export declare function extractConversationText(messages: unknown[]): string;
|
|
24
|
+
export declare function extractConversationText(messages: unknown[], redactionConfig?: RedactionConfig): string;
|
|
20
25
|
/**
|
|
21
26
|
* Send filtered conversation to LLM for knowledge extraction.
|
|
22
27
|
* For large transcripts, chunks into segments to avoid overwhelming small models.
|
package/dist/distiller.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.detectChunkSize = detectChunkSize;
|
|
|
9
9
|
exports.extractConversationText = extractConversationText;
|
|
10
10
|
exports.distillSession = distillSession;
|
|
11
11
|
const prompts_js_1 = require("./prompts.js");
|
|
12
|
+
const redact_js_1 = require("./redact.js");
|
|
12
13
|
const MAX_TRANSCRIPT_CHARS = 80_000;
|
|
13
14
|
const MIN_CONVERSATION_CHARS = 200;
|
|
14
15
|
// Chunk size limits by model parameter count (for local/CPU inference)
|
|
@@ -162,9 +163,13 @@ function cleanMessageContent(text) {
|
|
|
162
163
|
return text.trim();
|
|
163
164
|
}
|
|
164
165
|
/**
|
|
165
|
-
* Convert
|
|
166
|
+
* Convert session messages to a filtered transcript string.
|
|
167
|
+
* Handles OC hook format, CC JSONL, and Pi JSONL.
|
|
168
|
+
*
|
|
169
|
+
* If redactionConfig is provided (or defaults to enabled), secrets and PII
|
|
170
|
+
* are scrubbed from the final text BEFORE it reaches any LLM or storage.
|
|
166
171
|
*/
|
|
167
|
-
function extractConversationText(messages) {
|
|
172
|
+
function extractConversationText(messages, redactionConfig) {
|
|
168
173
|
const parts = [];
|
|
169
174
|
for (const msg of messages) {
|
|
170
175
|
if (typeof msg !== "object" || msg === null)
|
|
@@ -196,7 +201,15 @@ function extractConversationText(messages) {
|
|
|
196
201
|
const role = msgRole === "user" ? "USER" : "ASSISTANT";
|
|
197
202
|
parts.push(`${role}: ${text}`);
|
|
198
203
|
}
|
|
199
|
-
|
|
204
|
+
let result = parts.join("\n\n");
|
|
205
|
+
// Redact secrets and PII before the text reaches any LLM or storage.
|
|
206
|
+
// This is the last step — after all cleaning/filtering but before return.
|
|
207
|
+
const { text: redacted, count } = (0, redact_js_1.redact)(result, redactionConfig);
|
|
208
|
+
if (count > 0) {
|
|
209
|
+
console.log(`[hicortex] Redacted ${count} secret(s) from transcript`);
|
|
210
|
+
}
|
|
211
|
+
result = redacted;
|
|
212
|
+
return result;
|
|
200
213
|
}
|
|
201
214
|
/**
|
|
202
215
|
* Send filtered conversation to LLM for knowledge extraction.
|
package/dist/extensions.d.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* - LessonValidator — speculative; no validation exists today. Will add
|
|
29
29
|
* when the first Pro use case demands it.
|
|
30
30
|
*/
|
|
31
|
+
import type { ModuleIndex } from "./types.js";
|
|
31
32
|
/**
|
|
32
33
|
* Minimum fields a lesson must have for the selector to work.
|
|
33
34
|
*
|
|
@@ -57,6 +58,8 @@ export interface LessonSelectorContext {
|
|
|
57
58
|
agentId?: string;
|
|
58
59
|
/** Optional: current task description, for relevance scoring (Pro). */
|
|
59
60
|
currentTask?: string;
|
|
61
|
+
/** MODULE_INDEX for domain-aware lesson selection (Pro). */
|
|
62
|
+
moduleIndex?: ModuleIndex;
|
|
60
63
|
}
|
|
61
64
|
export interface LessonSelector {
|
|
62
65
|
/**
|
package/dist/mcp-server.js
CHANGED
|
@@ -224,6 +224,25 @@ function createMcpServer() {
|
|
|
224
224
|
return { content: [{ type: "text", text: `Lessons fetch failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
225
225
|
}
|
|
226
226
|
});
|
|
227
|
+
// -- hicortex_index --
|
|
228
|
+
server.tool("hicortex_index", "Get the knowledge domain index — shows what topics and projects are stored in memory, grouped by domain.", {}, async () => {
|
|
229
|
+
const state = (0, state_js_1.loadState)(stateDir);
|
|
230
|
+
const moduleIndex = state.moduleIndex;
|
|
231
|
+
if (moduleIndex && moduleIndex.domains.length > 0) {
|
|
232
|
+
const text = moduleIndex.domains.map((d) => `**${d.name}** (${d.memoryCount} memories, ${d.lessonCount} lessons)\n` +
|
|
233
|
+
` Projects: ${d.projects.join(", ")}` +
|
|
234
|
+
(d.keywords.length > 0 ? `\n Keywords: ${d.keywords.join(", ")}` : "")).join("\n\n");
|
|
235
|
+
return { content: [{ type: "text", text }] };
|
|
236
|
+
}
|
|
237
|
+
// Fallback: flat project counts
|
|
238
|
+
if (!db)
|
|
239
|
+
return { content: [{ type: "text", text: "No index available" }] };
|
|
240
|
+
const rows = db.prepare("SELECT project, COUNT(*) as cnt FROM memories WHERE project IS NOT NULL GROUP BY project ORDER BY cnt DESC LIMIT 20").all();
|
|
241
|
+
const text = rows.length > 0
|
|
242
|
+
? rows.map((r) => `${r.project}: ${r.cnt} memories`).join("\n")
|
|
243
|
+
: "No memories yet.";
|
|
244
|
+
return { content: [{ type: "text", text }] };
|
|
245
|
+
});
|
|
227
246
|
return server;
|
|
228
247
|
}
|
|
229
248
|
// ---------------------------------------------------------------------------
|
|
@@ -385,6 +404,7 @@ async function startServer(options = {}) {
|
|
|
385
404
|
.all();
|
|
386
405
|
const sourceCount = db.prepare("SELECT COUNT(DISTINCT source_agent) as cnt FROM memories").get().cnt;
|
|
387
406
|
const lessonCount = lessons.length;
|
|
407
|
+
const state = (0, state_js_1.loadState)();
|
|
388
408
|
res.json({
|
|
389
409
|
lessons: lessons.map(l => ({
|
|
390
410
|
content: l.content,
|
|
@@ -398,6 +418,7 @@ async function startServer(options = {}) {
|
|
|
398
418
|
sourceCount,
|
|
399
419
|
projects: projects.map(p => ({ name: p.project, count: p.cnt })),
|
|
400
420
|
},
|
|
421
|
+
moduleIndex: state.moduleIndex ?? null,
|
|
401
422
|
});
|
|
402
423
|
}
|
|
403
424
|
catch (err) {
|
package/dist/nightly.js
CHANGED
|
@@ -559,7 +559,9 @@ async function injectLessonsFromServer(serverUrl, authToken) {
|
|
|
559
559
|
}
|
|
560
560
|
const data = await resp.json();
|
|
561
561
|
const maxLessons = (0, features_js_1.lessonsLimit)();
|
|
562
|
-
|
|
562
|
+
// Use moduleIndex from server response, fall back to local state
|
|
563
|
+
const moduleIndex = data.moduleIndex ?? (0, state_js_1.loadState)().moduleIndex;
|
|
564
|
+
const selected = await (0, extensions_js_1.getLessonSelector)().select(data.lessons, { maxLessons, moduleIndex });
|
|
563
565
|
// Format lessons
|
|
564
566
|
const lessonLines = selected.map((l) => {
|
|
565
567
|
const titleMatch = l.content.match(/## Lesson: (.+)/);
|
|
@@ -569,8 +571,19 @@ async function injectLessonsFromServer(serverUrl, authToken) {
|
|
|
569
571
|
const meta = [severityMatch?.[1], typeMatch?.[1]].filter(Boolean).join(", ");
|
|
570
572
|
return `- ${title}${meta ? ` (${meta})` : ""}`;
|
|
571
573
|
});
|
|
572
|
-
// Format project index
|
|
573
|
-
|
|
574
|
+
// Format module/project index
|
|
575
|
+
let indexLines;
|
|
576
|
+
if (moduleIndex && moduleIndex.domains.length > 0) {
|
|
577
|
+
indexLines = [];
|
|
578
|
+
for (const domain of moduleIndex.domains) {
|
|
579
|
+
const kwStr = domain.keywords.length > 0 ? `: ${domain.keywords.join(", ")}` : "";
|
|
580
|
+
indexLines.push(`${domain.name} (${domain.memoryCount} memories, ${domain.lessonCount} lessons)${kwStr}`);
|
|
581
|
+
indexLines.push(` ${domain.projects.join(" | ")}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
indexLines = [data.index.projects.map(p => `${p.name}: ${p.count}`).join(" | ")];
|
|
586
|
+
}
|
|
574
587
|
// Build block
|
|
575
588
|
const START_MARKER = "<!-- HICORTEX-LEARNINGS:START -->";
|
|
576
589
|
const END_MARKER = "<!-- HICORTEX-LEARNINGS:END -->";
|
|
@@ -586,9 +599,9 @@ async function injectLessonsFromServer(serverUrl, authToken) {
|
|
|
586
599
|
blockParts.push("- Save important decisions with `hicortex_ingest`");
|
|
587
600
|
blockParts.push("- Lessons will appear here after the first nightly run");
|
|
588
601
|
}
|
|
589
|
-
if (
|
|
602
|
+
if (indexLines.length > 0) {
|
|
590
603
|
blockParts.push("", "### Memory Index");
|
|
591
|
-
blockParts.push(
|
|
604
|
+
blockParts.push(...indexLines);
|
|
592
605
|
blockParts.push(`${data.index.total} memories, ${data.index.lessonCount} lessons, ${data.index.sourceCount} agents. Search with \`hicortex_search\`.`);
|
|
593
606
|
}
|
|
594
607
|
blockParts.push(END_MARKER);
|
package/dist/prompts.d.ts
CHANGED
|
@@ -14,3 +14,8 @@ export declare function reflection(memoriesBlock: string, recentLessons?: string
|
|
|
14
14
|
* Distillation prompt. Extracts knowledge from a session transcript.
|
|
15
15
|
*/
|
|
16
16
|
export declare function distillation(projectName: string, date: string, transcript: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Domain curation prompt. Groups projects into knowledge domains.
|
|
19
|
+
* Used during consolidation (Pro only, one call per nightly when projects change).
|
|
20
|
+
*/
|
|
21
|
+
export declare function domainCuration(projectLines: string): string;
|
package/dist/prompts.js
CHANGED
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.importanceScoring = importanceScoring;
|
|
8
8
|
exports.reflection = reflection;
|
|
9
9
|
exports.distillation = distillation;
|
|
10
|
+
exports.domainCuration = domainCuration;
|
|
10
11
|
/**
|
|
11
12
|
* Importance scoring prompt. Takes a {memories_block} with indexed memories.
|
|
12
13
|
*/
|
|
@@ -145,3 +146,26 @@ RULES:
|
|
|
145
146
|
- If nothing worth extracting, output ONLY: "NO_EXTRACT"
|
|
146
147
|
`;
|
|
147
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Domain curation prompt. Groups projects into knowledge domains.
|
|
151
|
+
* Used during consolidation (Pro only, one call per nightly when projects change).
|
|
152
|
+
*/
|
|
153
|
+
function domainCuration(projectLines) {
|
|
154
|
+
return `You are a knowledge organizer. Given project names with memory and lesson counts, group them into logical knowledge DOMAINS (3-8 domains).
|
|
155
|
+
|
|
156
|
+
PROJECTS (name: memories / lessons):
|
|
157
|
+
${projectLines}
|
|
158
|
+
|
|
159
|
+
For each domain, output a JSON object:
|
|
160
|
+
- "name": Short domain label (2-4 words, Title Case)
|
|
161
|
+
- "projects": Array of project names belonging to this domain
|
|
162
|
+
- "keywords": 3-5 representative keywords for this domain
|
|
163
|
+
|
|
164
|
+
Rules:
|
|
165
|
+
- Every project must appear in exactly one domain
|
|
166
|
+
- Projects with only 1-2 memories can go in a "Miscellaneous" domain
|
|
167
|
+
- Prefer fewer domains over many tiny ones
|
|
168
|
+
- Domain names should be descriptive and distinct
|
|
169
|
+
|
|
170
|
+
Respond with ONLY a JSON array. No explanations.`;
|
|
171
|
+
}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-ingestion redaction — scrubs secrets and PII from transcript text
|
|
3
|
+
* BEFORE it reaches the distillation LLM or storage.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists:
|
|
6
|
+
* - Session transcripts contain tool output: file reads, command output,
|
|
7
|
+
* env var dumps. These regularly contain API keys, tokens, and paths.
|
|
8
|
+
* - The distillation LLM is often remote (e.g., Ollama on MBP via
|
|
9
|
+
* Tailscale). Secrets in the transcript travel over the network.
|
|
10
|
+
* - Even if the LLM correctly classifies the memory as SENSITIVE, the
|
|
11
|
+
* secret is already stored and searchable via hicortex_search.
|
|
12
|
+
* - Redaction runs BEFORE the LLM sees the text, eliminating the risk.
|
|
13
|
+
*
|
|
14
|
+
* Default patterns cover common API key formats, bearer tokens, absolute
|
|
15
|
+
* paths, and generic key=value secrets. Users can add custom patterns via
|
|
16
|
+
* config.json "redaction.extraPatterns".
|
|
17
|
+
*
|
|
18
|
+
* The replacement is always [REDACTED] (or configurable). This preserves
|
|
19
|
+
* the structure of the text so the LLM can still extract useful knowledge
|
|
20
|
+
* from the surrounding context.
|
|
21
|
+
*/
|
|
22
|
+
/** Result of a redaction pass. */
|
|
23
|
+
export interface RedactionResult {
|
|
24
|
+
/** The redacted text. */
|
|
25
|
+
text: string;
|
|
26
|
+
/** Number of individual redactions applied. */
|
|
27
|
+
count: number;
|
|
28
|
+
}
|
|
29
|
+
/** Configuration for redaction, read from config.json. */
|
|
30
|
+
export interface RedactionConfig {
|
|
31
|
+
/** Master switch. Default: true. */
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
/** Additional regex patterns (strings, compiled to RegExp with 'g' flag). */
|
|
34
|
+
extraPatterns?: string[];
|
|
35
|
+
/** Replacement string. Default: "[REDACTED]". */
|
|
36
|
+
replacement?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Redact secrets and PII from text.
|
|
40
|
+
*
|
|
41
|
+
* @param text The raw transcript text to redact
|
|
42
|
+
* @param config Optional configuration (extra patterns, replacement string)
|
|
43
|
+
* @returns The redacted text and count of redactions applied
|
|
44
|
+
*/
|
|
45
|
+
export declare function redact(text: string, config?: RedactionConfig): RedactionResult;
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pre-ingestion redaction — scrubs secrets and PII from transcript text
|
|
4
|
+
* BEFORE it reaches the distillation LLM or storage.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists:
|
|
7
|
+
* - Session transcripts contain tool output: file reads, command output,
|
|
8
|
+
* env var dumps. These regularly contain API keys, tokens, and paths.
|
|
9
|
+
* - The distillation LLM is often remote (e.g., Ollama on MBP via
|
|
10
|
+
* Tailscale). Secrets in the transcript travel over the network.
|
|
11
|
+
* - Even if the LLM correctly classifies the memory as SENSITIVE, the
|
|
12
|
+
* secret is already stored and searchable via hicortex_search.
|
|
13
|
+
* - Redaction runs BEFORE the LLM sees the text, eliminating the risk.
|
|
14
|
+
*
|
|
15
|
+
* Default patterns cover common API key formats, bearer tokens, absolute
|
|
16
|
+
* paths, and generic key=value secrets. Users can add custom patterns via
|
|
17
|
+
* config.json "redaction.extraPatterns".
|
|
18
|
+
*
|
|
19
|
+
* The replacement is always [REDACTED] (or configurable). This preserves
|
|
20
|
+
* the structure of the text so the LLM can still extract useful knowledge
|
|
21
|
+
* from the surrounding context.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.redact = redact;
|
|
25
|
+
/**
|
|
26
|
+
* Default redaction patterns. Each targets a specific class of secret.
|
|
27
|
+
* Order matters: more specific patterns should come first to avoid
|
|
28
|
+
* partial matches by generic patterns.
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_PATTERNS = [
|
|
31
|
+
// Anthropic API keys: sk-ant-api03-...
|
|
32
|
+
{ name: "anthropic_key", pattern: /sk-ant-[a-zA-Z0-9\-_]{20,}/g },
|
|
33
|
+
// OpenAI API keys: sk-proj-... or sk-...
|
|
34
|
+
{ name: "openai_key", pattern: /sk-(?:proj-)?[a-zA-Z0-9]{20,}/g },
|
|
35
|
+
// Hicortex license keys: hctx-... (case-insensitive — keys could appear uppercased in logs)
|
|
36
|
+
{ name: "hicortex_key", pattern: /hctx-[a-f0-9]{16}/gi },
|
|
37
|
+
// GitHub Personal Access Tokens: ghp_...
|
|
38
|
+
{ name: "github_pat", pattern: /ghp_[a-zA-Z0-9]{36}/g },
|
|
39
|
+
// GitHub OAuth tokens: gho_...
|
|
40
|
+
{ name: "github_oauth", pattern: /gho_[a-zA-Z0-9]{36}/g },
|
|
41
|
+
// Google API keys: AIza...
|
|
42
|
+
{ name: "google_key", pattern: /AIza[a-zA-Z0-9_\-]{35}/g },
|
|
43
|
+
// AWS access keys: AKIA...
|
|
44
|
+
{ name: "aws_key", pattern: /AKIA[A-Z0-9]{16}/g },
|
|
45
|
+
// Stripe live/test keys: sk_live_..., sk_test_...
|
|
46
|
+
{ name: "stripe_key", pattern: /sk_(?:live|test)_[a-zA-Z0-9]{20,}/g },
|
|
47
|
+
// Bearer tokens in headers (case-insensitive — headers are case-insensitive)
|
|
48
|
+
{ name: "bearer_token", pattern: /[Bb]earer\s+[a-zA-Z0-9._\-]{20,}/g },
|
|
49
|
+
// Generic secret assignments: password=..., secret_key=..., token: ...
|
|
50
|
+
// Matches key=value and key: value patterns with common secret key names.
|
|
51
|
+
// The key name can have underscores/hyphens and optional suffixes (SECRET_KEY, api-key, etc.)
|
|
52
|
+
// Negative lookahead for [REDACTED] prevents double-counting when a prior pattern
|
|
53
|
+
// already replaced the value (e.g., bearer_token fires, then generic_secret sees
|
|
54
|
+
// "token: [REDACTED]" and would otherwise match again).
|
|
55
|
+
{ name: "generic_secret", pattern: /(?:password|secret(?:[_-]?key)?|token|api[_-]?key|private[_-]?key|access[_-]?key)\s*[:=]\s*["']?(?!\[REDACTED\])[^\s"']{8,}["']?/gi },
|
|
56
|
+
// Absolute macOS paths: /Users/<username>/...
|
|
57
|
+
// Negative lookbehind avoids matching URL paths like https://api.example.com/Users/list
|
|
58
|
+
{ name: "macos_path", pattern: /(?<![:/])\/Users\/[a-zA-Z0-9._-]+/g },
|
|
59
|
+
// Absolute Linux home paths: /home/<username>/...
|
|
60
|
+
// Same lookbehind to avoid URL false positives
|
|
61
|
+
{ name: "linux_path", pattern: /(?<![:/])\/home\/[a-zA-Z0-9._-]+/g },
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* Redact secrets and PII from text.
|
|
65
|
+
*
|
|
66
|
+
* @param text The raw transcript text to redact
|
|
67
|
+
* @param config Optional configuration (extra patterns, replacement string)
|
|
68
|
+
* @returns The redacted text and count of redactions applied
|
|
69
|
+
*/
|
|
70
|
+
function redact(text, config) {
|
|
71
|
+
if (config?.enabled === false)
|
|
72
|
+
return { text, count: 0 };
|
|
73
|
+
const replacement = config?.replacement ?? "[REDACTED]";
|
|
74
|
+
let count = 0;
|
|
75
|
+
let result = text;
|
|
76
|
+
// Apply default patterns
|
|
77
|
+
for (const { pattern } of DEFAULT_PATTERNS) {
|
|
78
|
+
// Reset lastIndex for global regexes (they're stateful)
|
|
79
|
+
pattern.lastIndex = 0;
|
|
80
|
+
result = result.replace(pattern, () => {
|
|
81
|
+
count++;
|
|
82
|
+
return replacement;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Apply user-configured extra patterns
|
|
86
|
+
if (config?.extraPatterns) {
|
|
87
|
+
for (const patternStr of config.extraPatterns) {
|
|
88
|
+
try {
|
|
89
|
+
const re = new RegExp(patternStr, "g");
|
|
90
|
+
result = result.replace(re, () => {
|
|
91
|
+
count++;
|
|
92
|
+
return replacement;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Invalid regex — skip silently (don't crash the pipeline)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { text: result, count };
|
|
101
|
+
}
|
package/dist/state.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Note: ~/.hicortex/config.json is intentionally NOT merged here. Config is
|
|
17
17
|
* user-edited and tracked separately from machine state.
|
|
18
18
|
*/
|
|
19
|
-
import type { LicenseInfo } from "./types.js";
|
|
19
|
+
import type { LicenseInfo, ModuleIndex } from "./types.js";
|
|
20
20
|
/** Persisted tier information — reflects the last successful validation. */
|
|
21
21
|
export interface PersistedTier {
|
|
22
22
|
/** Tier name from the validation API response. */
|
|
@@ -35,6 +35,8 @@ export interface HicortexState {
|
|
|
35
35
|
tier?: PersistedTier;
|
|
36
36
|
/** Anonymous telemetry UUID — generated once, never linked to personal info. */
|
|
37
37
|
telemetryId?: string;
|
|
38
|
+
/** Cached MODULE_INDEX from domain curation (generated during consolidation). */
|
|
39
|
+
moduleIndex?: ModuleIndex;
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
42
|
* Load the state file. Returns an empty state if the file is missing
|
package/dist/storage.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface Memory {
|
|
|
14
14
|
source_agent: string;
|
|
15
15
|
source_session: string | null;
|
|
16
16
|
project: string | null;
|
|
17
|
+
domain: string | null;
|
|
17
18
|
privacy: "PUBLIC" | "WORK" | "PERSONAL" | "SENSITIVE";
|
|
18
19
|
memory_type: "episode" | "lesson" | "fact" | "decision";
|
|
19
20
|
updated_at: string | null;
|
|
@@ -59,10 +60,16 @@ export interface ConsolidationReport {
|
|
|
59
60
|
};
|
|
60
61
|
reflection?: {
|
|
61
62
|
lessons_generated: number;
|
|
63
|
+
contradictions_suppressed?: number;
|
|
62
64
|
failed?: boolean;
|
|
63
65
|
skipped?: boolean;
|
|
64
66
|
reason?: string;
|
|
65
67
|
};
|
|
68
|
+
domain_curation?: {
|
|
69
|
+
curated: boolean;
|
|
70
|
+
domains: number;
|
|
71
|
+
reason?: string;
|
|
72
|
+
};
|
|
66
73
|
links?: {
|
|
67
74
|
auto_linked: number;
|
|
68
75
|
failed: number;
|
|
@@ -104,6 +111,22 @@ export interface LicenseInfo {
|
|
|
104
111
|
email?: string;
|
|
105
112
|
expires_at?: string;
|
|
106
113
|
}
|
|
114
|
+
/** A knowledge domain grouping related projects. */
|
|
115
|
+
export interface ModuleDomain {
|
|
116
|
+
name: string;
|
|
117
|
+
projects: string[];
|
|
118
|
+
memoryCount: number;
|
|
119
|
+
lessonCount: number;
|
|
120
|
+
keywords: string[];
|
|
121
|
+
}
|
|
122
|
+
/** Auto-generated knowledge routing index, cached in state.json. */
|
|
123
|
+
export interface ModuleIndex {
|
|
124
|
+
domains: ModuleDomain[];
|
|
125
|
+
projectSetHash: string;
|
|
126
|
+
curatedAt: string;
|
|
127
|
+
totalMemories: number;
|
|
128
|
+
totalLessons: number;
|
|
129
|
+
}
|
|
107
130
|
/** Options for inserting a memory. */
|
|
108
131
|
export interface InsertMemoryOptions {
|
|
109
132
|
sourceAgent?: string;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "hicortex",
|
|
3
3
|
"name": "Hicortex — Long-term Memory That Learns",
|
|
4
4
|
"description": "Your agents remember past decisions, avoid repeated mistakes, and get smarter every day. Nightly reflection generates actionable lessons that automatically update agent behavior.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.6.0",
|
|
6
6
|
"kind": "lifecycle",
|
|
7
7
|
"skills": ["./skills/hicortex-memory", "./skills/hicortex-learn", "./skills/hicortex-activate"],
|
|
8
8
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gamaze/hicortex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Human-like memory for self-improving AI agents. Automatic capturing, nightly reflection, and cross-agent learning. Works with Claude Code and OpenClaw.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|