@gamaze/hicortex 0.5.3 → 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 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 projectIndex = buildProjectIndex(db);
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 (projectIndex.length > 0) {
111
+ if (indexLines.length > 0) {
106
112
  blockParts.push("", "### Memory Index");
107
- blockParts.push(projectIndex.join(" | "));
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
- * Build compact project index: "hicortex: 18 | boat: 24 | health: 45"
149
+ * Read moduleIndexTokenBudget from config.json. Defaults to 500.
141
150
  */
142
- function buildProjectIndex(db) {
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 [];
@@ -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
  */
@@ -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)
@@ -321,6 +322,136 @@ async function stageReflection(db, memories, llm, budget, embedFn, dryRun) {
321
322
  }
322
323
  }
323
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
+ // ---------------------------------------------------------------------------
324
455
  // Stage 3: Link Discovery (vector similarity auto-link)
325
456
  // ---------------------------------------------------------------------------
326
457
  async function stageLinks(db, memories, embedFn, dryRun) {
@@ -416,7 +547,7 @@ function stageDecayPrune(db, dryRun) {
416
547
  /**
417
548
  * Run the full consolidation pipeline. Returns a structured report.
418
549
  */
419
- async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection = false) {
550
+ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection = false, stateDir) {
420
551
  const start = new Date();
421
552
  const report = {
422
553
  started_at: start.toISOString(),
@@ -462,6 +593,8 @@ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection
462
593
  else {
463
594
  report.stages.reflection = await stageReflection(db, precheck.newMemories, llm, budget, embedFn, dryRun);
464
595
  }
596
+ // Stage 2.7: Domain Curation
597
+ report.stages.domain_curation = await stageDomainCuration(db, llm, budget, dryRun, stateDir);
465
598
  // Stage 3: Link Discovery
466
599
  report.stages.links = await stageLinks(db, precheck.newMemories, embedFn, dryRun);
467
600
  // Stage 4: Decay & Prune
@@ -476,7 +609,7 @@ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection
476
609
  (0, state_js_1.updateState)((s) => {
477
610
  s.lastConsolidated = new Date().toISOString();
478
611
  return s;
479
- });
612
+ }, stateDir);
480
613
  }
481
614
  report.budget = budget.summary();
482
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.
@@ -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
  /**
@@ -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
- const selected = await (0, extensions_js_1.getLessonSelector)().select(data.lessons, { maxLessons });
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
- const projectIndex = data.index.projects.map(p => `${p.name}: ${p.count}`);
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 (projectIndex.length > 0) {
602
+ if (indexLines.length > 0) {
590
603
  blockParts.push("", "### Memory Index");
591
- blockParts.push(projectIndex.join(" | "));
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/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
@@ -75,6 +75,7 @@ const ALLOWED_UPDATE_FIELDS = new Set([
75
75
  "source_agent",
76
76
  "source_session",
77
77
  "project",
78
+ "domain",
78
79
  "privacy",
79
80
  "memory_type",
80
81
  "updated_at",
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;
@@ -64,6 +65,11 @@ export interface ConsolidationReport {
64
65
  skipped?: boolean;
65
66
  reason?: string;
66
67
  };
68
+ domain_curation?: {
69
+ curated: boolean;
70
+ domains: number;
71
+ reason?: string;
72
+ };
67
73
  links?: {
68
74
  auto_linked: number;
69
75
  failed: number;
@@ -105,6 +111,22 @@ export interface LicenseInfo {
105
111
  email?: string;
106
112
  expires_at?: string;
107
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
+ }
108
130
  /** Options for inserting a memory. */
109
131
  export interface InsertMemoryOptions {
110
132
  sourceAgent?: string;
@@ -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.3",
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.5.3",
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": {