@gamaze/hicortex 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,12 +45,14 @@ openclaw gateway restart
45
45
 
46
46
  ## Agent Tools (MCP)
47
47
 
48
- 6 tools available via MCP:
48
+ 8 tools available via MCP:
49
49
 
50
50
  - **hicortex_search** — Semantic search across all stored memories
51
51
  - **hicortex_context** — Get recent decisions and project state
52
52
  - **hicortex_ingest** — Store a memory directly
53
53
  - **hicortex_lessons** — Get actionable lessons from reflection
54
+ - **hicortex_index** — Get the knowledge domain index (what topics are stored)
55
+ - **hicortex_graph** — Graph traversal: neighbors, hubs, shortest paths
54
56
  - **hicortex_update** — Fix incorrect memories (re-embeds on content change)
55
57
  - **hicortex_delete** — Remove memories with cascade cleanup
56
58
 
@@ -104,6 +106,9 @@ Config at `~/.hicortex/config.json`. Created by `init`. Key options:
104
106
  | `reflectBaseUrl` | Separate Ollama instance for reflection |
105
107
  | `authToken` | Bearer token for endpoint auth |
106
108
  | `licenseKey` | License key for higher tiers |
109
+ | `lessonTarget` | Injection target file (default: `~/.claude/CLAUDE.md`) |
110
+ | `moduleIndexTokenBudget` | Max tokens for domain index in injection (default: 500) |
111
+ | `telemetry` | Anonymous usage telemetry, `false` to opt out |
107
112
 
108
113
  Full docs: [hicortex.gamaze.com/docs/configuration.html](https://hicortex.gamaze.com/docs/configuration.html)
109
114
 
@@ -48,6 +48,7 @@ const storage = __importStar(require("./storage.js"));
48
48
  const prompts_js_1 = require("./prompts.js");
49
49
  const node_crypto_1 = require("node:crypto");
50
50
  const features_js_1 = require("./features.js");
51
+ const graph_js_1 = require("./graph.js");
51
52
  const state_js_1 = require("./state.js");
52
53
  // Default config constants (matching Python config.py)
53
54
  const CONSOLIDATE_MAX_LLM_CALLS = 200;
@@ -352,14 +353,49 @@ async function stageDomainCuration(db, llm, budget, dryRun, stateDir) {
352
353
  const totalLessons = lessonRows.reduce((s, r) => s + r.cnt, 0);
353
354
  let domains;
354
355
  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
- }));
356
+ // OSS: Louvain community detection on the memory_links graph (zero LLM cost)
357
+ const graph = (0, graph_js_1.louvainCommunities)(db);
358
+ if (graph.communities.length > 1 && graph.edgeCount >= 5) {
359
+ // Map communities to domains by finding the dominant project in each
360
+ // Pre-load all memory→project mappings in one query (avoids N+1)
361
+ const allProjectRows = db
362
+ .prepare("SELECT id, project FROM memories WHERE project IS NOT NULL")
363
+ .all();
364
+ const memProject = new Map(allProjectRows.map((r) => [r.id, r.project]));
365
+ domains = [];
366
+ for (const comm of graph.communities) {
367
+ const projectCounts = new Map();
368
+ for (const memId of comm.members) {
369
+ const proj = memProject.get(memId);
370
+ if (proj) {
371
+ projectCounts.set(proj, (projectCounts.get(proj) ?? 0) + 1);
372
+ }
373
+ }
374
+ const projects = [...projectCounts.keys()];
375
+ if (projects.length === 0)
376
+ continue;
377
+ // Name domain after the dominant project or combine top 2
378
+ const sorted = [...projectCounts.entries()].sort((a, b) => b[1] - a[1]);
379
+ const name = sorted.length >= 2 && sorted[1][1] > sorted[0][1] * 0.3
380
+ ? `${sorted[0][0]} + ${sorted[1][0]}`
381
+ : sorted[0][0];
382
+ const memoryCount = projects.reduce((s, p) => s + (projectRows.find((r) => r.project === p)?.cnt ?? 0), 0);
383
+ const lessonCount = projects.reduce((s, p) => s + (lessonsByProject.get(p) ?? 0), 0);
384
+ domains.push({ name, projects, memoryCount, lessonCount, keywords: [] });
385
+ }
386
+ domains.sort((a, b) => b.memoryCount - a.memoryCount);
387
+ console.log(`[hicortex] Louvain clustering: ${graph.communities.length} communities, modularity ${graph.modularity.toFixed(3)}`);
388
+ }
389
+ else {
390
+ // Not enough edges for meaningful clustering — fall back to project=domain
391
+ domains = projectRows.map((r) => ({
392
+ name: r.project,
393
+ projects: [r.project],
394
+ memoryCount: r.cnt,
395
+ lessonCount: lessonsByProject.get(r.project) ?? 0,
396
+ keywords: [],
397
+ }));
398
+ }
363
399
  }
364
400
  else {
365
401
  // Pro: LLM-curated domains
@@ -510,6 +546,35 @@ function classifyRelationship(source, target, similarity) {
510
546
  return "relates_to";
511
547
  }
512
548
  // ---------------------------------------------------------------------------
549
+ // Stage 3.5: Hub Detection & Strength Boost
550
+ // ---------------------------------------------------------------------------
551
+ const HUB_BOOST = 0.1;
552
+ const HUB_STRENGTH_CAP = 1.0;
553
+ function stageHubBoost(db, dryRun) {
554
+ const hubs = (0, graph_js_1.detectHubs)(db);
555
+ if (hubs.length === 0)
556
+ return { hubs_found: 0, boosted: 0 };
557
+ let boosted = 0;
558
+ if (!dryRun) {
559
+ const stmt = db.prepare("UPDATE memories SET base_strength = MIN(?, base_strength + ?) WHERE id = ? AND base_strength < ?");
560
+ const tx = db.transaction(() => {
561
+ for (const hub of hubs) {
562
+ const result = stmt.run(HUB_STRENGTH_CAP, HUB_BOOST, hub.id, HUB_STRENGTH_CAP);
563
+ if (result.changes > 0)
564
+ boosted++;
565
+ }
566
+ });
567
+ tx();
568
+ }
569
+ else {
570
+ boosted = hubs.length;
571
+ }
572
+ if (hubs.length > 0) {
573
+ console.log(`[hicortex] Hub detection: ${hubs.length} hubs found, ${boosted} boosted (+${HUB_BOOST})`);
574
+ }
575
+ return { hubs_found: hubs.length, boosted };
576
+ }
577
+ // ---------------------------------------------------------------------------
513
578
  // Stage 4: Decay & Prune
514
579
  // ---------------------------------------------------------------------------
515
580
  function stageDecayPrune(db, dryRun) {
@@ -597,6 +662,8 @@ async function runConsolidation(db, llm, embedFn, dryRun = false, skipReflection
597
662
  report.stages.domain_curation = await stageDomainCuration(db, llm, budget, dryRun, stateDir);
598
663
  // Stage 3: Link Discovery
599
664
  report.stages.links = await stageLinks(db, precheck.newMemories, embedFn, dryRun);
665
+ // Stage 3.5: Hub Detection — boost highly-connected memories
666
+ report.stages.hub_boost = stageHubBoost(db, dryRun);
600
667
  // Stage 4: Decay & Prune
601
668
  report.stages.decay_prune = stageDecayPrune(db, dryRun);
602
669
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Graph analysis for the memory link network.
3
+ *
4
+ * Pure-JS Louvain community detection + hub node identification.
5
+ * Operates on the memory_links table without external dependencies.
6
+ *
7
+ * Louvain algorithm: iteratively merges nodes into communities to maximize
8
+ * modularity. Produces quality comparable to Leiden for the graph sizes
9
+ * we deal with (hundreds to low thousands of nodes).
10
+ */
11
+ import type Database from "better-sqlite3";
12
+ export interface GraphCommunity {
13
+ id: number;
14
+ members: string[];
15
+ size: number;
16
+ }
17
+ export interface HubNode {
18
+ id: string;
19
+ linkCount: number;
20
+ project: string | null;
21
+ domain: string | null;
22
+ content: string;
23
+ }
24
+ export interface GraphAnalysis {
25
+ communities: GraphCommunity[];
26
+ hubs: HubNode[];
27
+ nodeCount: number;
28
+ edgeCount: number;
29
+ modularity: number;
30
+ }
31
+ /**
32
+ * Run Louvain community detection. Returns community assignments.
33
+ */
34
+ export declare function louvainCommunities(db: Database.Database): {
35
+ communities: GraphCommunity[];
36
+ modularity: number;
37
+ nodeCount: number;
38
+ edgeCount: number;
39
+ };
40
+ /**
41
+ * Find hub nodes — memories with link count significantly above median.
42
+ * Returns nodes with links > threshold (default: 2x median, minimum 3 links).
43
+ */
44
+ export declare function detectHubs(db: Database.Database, thresholdMultiplier?: number, minLinks?: number): HubNode[];
45
+ export interface GraphNeighbor {
46
+ id: string;
47
+ relationship: string;
48
+ strength: number;
49
+ direction: "outgoing" | "incoming";
50
+ content: string;
51
+ project: string | null;
52
+ }
53
+ export declare function getNeighbors(db: Database.Database, memoryId: string, limit?: number): GraphNeighbor[];
54
+ export declare function shortestPath(db: Database.Database, fromId: string, toId: string, maxDepth?: number): string[] | null;
package/dist/graph.js ADDED
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ /**
3
+ * Graph analysis for the memory link network.
4
+ *
5
+ * Pure-JS Louvain community detection + hub node identification.
6
+ * Operates on the memory_links table without external dependencies.
7
+ *
8
+ * Louvain algorithm: iteratively merges nodes into communities to maximize
9
+ * modularity. Produces quality comparable to Leiden for the graph sizes
10
+ * we deal with (hundreds to low thousands of nodes).
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.louvainCommunities = louvainCommunities;
14
+ exports.detectHubs = detectHubs;
15
+ exports.getNeighbors = getNeighbors;
16
+ exports.shortestPath = shortestPath;
17
+ function loadGraph(db) {
18
+ const rows = db
19
+ .prepare("SELECT source_id, target_id, strength FROM memory_links")
20
+ .all();
21
+ const adj = new Map();
22
+ const nodes = new Set();
23
+ for (const { source_id, target_id, strength } of rows) {
24
+ nodes.add(source_id);
25
+ nodes.add(target_id);
26
+ const w = strength || 0.5;
27
+ if (!adj.has(source_id))
28
+ adj.set(source_id, []);
29
+ adj.get(source_id).push({ neighbor: target_id, weight: w });
30
+ if (!adj.has(target_id))
31
+ adj.set(target_id, []);
32
+ adj.get(target_id).push({ neighbor: source_id, weight: w });
33
+ }
34
+ return { adj, nodes, edgeCount: rows.length };
35
+ }
36
+ // ---------------------------------------------------------------------------
37
+ // Louvain community detection
38
+ // ---------------------------------------------------------------------------
39
+ /**
40
+ * Compute modularity of the current partition.
41
+ * Q = (1/2m) * sum_ij [ A_ij - k_i*k_j/(2m) ] * delta(c_i, c_j)
42
+ */
43
+ function computeModularity(adj, community, totalWeight) {
44
+ if (totalWeight === 0)
45
+ return 0;
46
+ const m2 = 2 * totalWeight;
47
+ let q = 0;
48
+ for (const [node, neighbors] of adj) {
49
+ const ki = neighbors.reduce((s, e) => s + e.weight, 0);
50
+ const ci = community.get(node);
51
+ for (const { neighbor, weight } of neighbors) {
52
+ const kj = adj.get(neighbor)?.reduce((s, e) => s + e.weight, 0) ?? 0;
53
+ const cj = community.get(neighbor);
54
+ if (ci === cj) {
55
+ q += weight - (ki * kj) / m2;
56
+ }
57
+ }
58
+ }
59
+ return q / m2;
60
+ }
61
+ /**
62
+ * Run Louvain community detection. Returns community assignments.
63
+ */
64
+ function louvainCommunities(db) {
65
+ const { adj, nodes, edgeCount } = loadGraph(db);
66
+ if (nodes.size === 0) {
67
+ return { communities: [], modularity: 0, nodeCount: 0, edgeCount: 0 };
68
+ }
69
+ const totalWeight = [...adj.values()]
70
+ .reduce((s, edges) => s + edges.reduce((s2, e) => s2 + e.weight, 0), 0) / 2;
71
+ // Initialize: each node in its own community
72
+ const community = new Map();
73
+ let nextId = 0;
74
+ for (const node of nodes) {
75
+ community.set(node, nextId++);
76
+ }
77
+ // Phase 1: local moves — repeatedly move nodes to neighbor community with best modularity gain
78
+ let improved = true;
79
+ let iterations = 0;
80
+ const MAX_ITERATIONS = 50;
81
+ while (improved && iterations < MAX_ITERATIONS) {
82
+ improved = false;
83
+ iterations++;
84
+ for (const node of nodes) {
85
+ const currentComm = community.get(node);
86
+ const neighbors = adj.get(node) ?? [];
87
+ // Compute weight to each neighbor community
88
+ const commWeights = new Map();
89
+ for (const { neighbor, weight } of neighbors) {
90
+ const nc = community.get(neighbor);
91
+ commWeights.set(nc, (commWeights.get(nc) ?? 0) + weight);
92
+ }
93
+ // Find best community to move to
94
+ let bestComm = currentComm;
95
+ let bestGain = 0;
96
+ const ki = neighbors.reduce((s, e) => s + e.weight, 0);
97
+ for (const [targetComm, weightToComm] of commWeights) {
98
+ if (targetComm === currentComm)
99
+ continue;
100
+ // Simplified modularity gain: delta_Q ~ weight_to_comm - ki * sum_comm / (2m)
101
+ const sumComm = [...community.entries()]
102
+ .filter(([, c]) => c === targetComm)
103
+ .reduce((s, [n]) => s + (adj.get(n)?.reduce((s2, e) => s2 + e.weight, 0) ?? 0), 0);
104
+ const gain = weightToComm - (ki * sumComm) / (2 * totalWeight);
105
+ if (gain > bestGain) {
106
+ bestGain = gain;
107
+ bestComm = targetComm;
108
+ }
109
+ }
110
+ if (bestComm !== currentComm) {
111
+ community.set(node, bestComm);
112
+ improved = true;
113
+ }
114
+ }
115
+ }
116
+ // Build community list
117
+ const commMembers = new Map();
118
+ for (const [node, comm] of community) {
119
+ if (!commMembers.has(comm))
120
+ commMembers.set(comm, []);
121
+ commMembers.get(comm).push(node);
122
+ }
123
+ // Renumber communities 0..N-1
124
+ const communities = [];
125
+ let idx = 0;
126
+ for (const [, members] of commMembers) {
127
+ if (members.length > 0) {
128
+ communities.push({ id: idx++, members, size: members.length });
129
+ }
130
+ }
131
+ // Sort by size desc
132
+ communities.sort((a, b) => b.size - a.size);
133
+ const modularity = computeModularity(adj, community, totalWeight);
134
+ return { communities, modularity, nodeCount: nodes.size, edgeCount };
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // Hub node detection
138
+ // ---------------------------------------------------------------------------
139
+ /**
140
+ * Find hub nodes — memories with link count significantly above median.
141
+ * Returns nodes with links > threshold (default: 2x median, minimum 3 links).
142
+ */
143
+ function detectHubs(db, thresholdMultiplier = 2, minLinks = 3) {
144
+ const rows = db
145
+ .prepare(`SELECT id, cnt FROM (
146
+ SELECT id, COUNT(*) as cnt FROM (
147
+ SELECT source_id AS id FROM memory_links
148
+ UNION ALL
149
+ SELECT target_id AS id FROM memory_links
150
+ ) GROUP BY id
151
+ ) ORDER BY cnt DESC`)
152
+ .all();
153
+ if (rows.length === 0)
154
+ return [];
155
+ // Compute median link count
156
+ const sorted = rows.map((r) => r.cnt).sort((a, b) => a - b);
157
+ const median = sorted[Math.floor(sorted.length / 2)];
158
+ const threshold = Math.max(median * thresholdMultiplier, minLinks);
159
+ const hubs = [];
160
+ for (const { id, cnt } of rows) {
161
+ if (cnt < threshold)
162
+ break; // sorted desc, no more hubs
163
+ const mem = db
164
+ .prepare("SELECT content, project, domain FROM memories WHERE id = ?")
165
+ .get(id);
166
+ if (!mem)
167
+ continue;
168
+ hubs.push({
169
+ id,
170
+ linkCount: cnt,
171
+ project: mem.project,
172
+ domain: mem.domain,
173
+ content: mem.content.slice(0, 200),
174
+ });
175
+ }
176
+ return hubs;
177
+ }
178
+ function getNeighbors(db, memoryId, limit = 10) {
179
+ const rows = db
180
+ .prepare(`SELECT source_id, target_id, relationship, strength
181
+ FROM memory_links
182
+ WHERE source_id = ? OR target_id = ?
183
+ ORDER BY strength DESC
184
+ LIMIT ?`)
185
+ .all(memoryId, memoryId, limit);
186
+ const results = [];
187
+ for (const row of rows) {
188
+ const isOutgoing = row.source_id === memoryId;
189
+ const neighborId = isOutgoing ? row.target_id : row.source_id;
190
+ const mem = db
191
+ .prepare("SELECT content, project FROM memories WHERE id = ?")
192
+ .get(neighborId);
193
+ if (!mem)
194
+ continue;
195
+ results.push({
196
+ id: neighborId,
197
+ relationship: row.relationship,
198
+ strength: row.strength,
199
+ direction: isOutgoing ? "outgoing" : "incoming",
200
+ content: mem.content.slice(0, 200),
201
+ project: mem.project,
202
+ });
203
+ }
204
+ return results;
205
+ }
206
+ // ---------------------------------------------------------------------------
207
+ // Shortest path (for MCP tool)
208
+ // ---------------------------------------------------------------------------
209
+ function shortestPath(db, fromId, toId, maxDepth = 5) {
210
+ // BFS on the memory_links graph
211
+ const links = db
212
+ .prepare("SELECT source_id, target_id FROM memory_links")
213
+ .all();
214
+ const adj = new Map();
215
+ for (const { source_id, target_id } of links) {
216
+ if (!adj.has(source_id))
217
+ adj.set(source_id, new Set());
218
+ adj.get(source_id).add(target_id);
219
+ if (!adj.has(target_id))
220
+ adj.set(target_id, new Set());
221
+ adj.get(target_id).add(source_id);
222
+ }
223
+ if (!adj.has(fromId) || !adj.has(toId))
224
+ return null;
225
+ const visited = new Set([fromId]);
226
+ const parent = new Map();
227
+ const queue = [{ node: fromId, depth: 0 }];
228
+ while (queue.length > 0) {
229
+ const { node, depth } = queue.shift();
230
+ if (node === toId) {
231
+ // Reconstruct path
232
+ const path = [toId];
233
+ let current = toId;
234
+ while (parent.has(current)) {
235
+ current = parent.get(current);
236
+ path.unshift(current);
237
+ }
238
+ return path;
239
+ }
240
+ if (depth >= maxDepth)
241
+ continue;
242
+ for (const neighbor of adj.get(node) ?? []) {
243
+ if (!visited.has(neighbor)) {
244
+ visited.add(neighbor);
245
+ parent.set(neighbor, node);
246
+ queue.push({ node: neighbor, depth: depth + 1 });
247
+ }
248
+ }
249
+ }
250
+ return null;
251
+ }
@@ -59,6 +59,7 @@ const features_js_1 = require("./features.js");
59
59
  const state_js_1 = require("./state.js");
60
60
  const embedder_js_1 = require("./embedder.js");
61
61
  const storage = __importStar(require("./storage.js"));
62
+ const graph_js_1 = require("./graph.js");
62
63
  const retrieval = __importStar(require("./retrieval.js"));
63
64
  const consolidate_js_1 = require("./consolidate.js");
64
65
  const seed_lesson_js_1 = require("./seed-lesson.js");
@@ -243,6 +244,61 @@ function createMcpServer() {
243
244
  : "No memories yet.";
244
245
  return { content: [{ type: "text", text }] };
245
246
  });
247
+ // -- hicortex_graph --
248
+ server.tool("hicortex_graph", "Query the memory knowledge graph — find connected memories, hub nodes, or paths between memories.", {
249
+ operation: zod_1.z.enum(["neighbors", "hubs", "path"]).describe("Graph operation to perform"),
250
+ id: zod_1.z.string().optional().describe("Memory ID (required for neighbors and path operations)"),
251
+ target_id: zod_1.z.string().optional().describe("Target memory ID (required for path operation)"),
252
+ limit: zod_1.z.coerce.number().optional().describe("Max results (default 10)"),
253
+ domain: zod_1.z.string().optional().describe("Filter hubs by domain"),
254
+ }, async ({ operation, id, target_id, limit: resultLimit, domain: filterDomain }) => {
255
+ if (!db)
256
+ return { content: [{ type: "text", text: "Hicortex not initialized" }], isError: true };
257
+ try {
258
+ if (operation === "neighbors") {
259
+ if (!id)
260
+ return { content: [{ type: "text", text: "id is required for neighbors operation" }], isError: true };
261
+ const resolvedId = resolveMemoryId(db, id);
262
+ if (!resolvedId)
263
+ return { content: [{ type: "text", text: `Memory not found: ${id}` }], isError: true };
264
+ const neighbors = (0, graph_js_1.getNeighbors)(db, resolvedId, resultLimit ?? 10);
265
+ if (neighbors.length === 0)
266
+ return { content: [{ type: "text", text: "No connected memories found." }] };
267
+ const text = neighbors.map((n) => `[${n.direction}] ${n.relationship} (${n.strength.toFixed(2)})\n ${n.id.slice(0, 8)} | ${n.project ?? "global"} | ${n.content}`).join("\n\n");
268
+ return { content: [{ type: "text", text }] };
269
+ }
270
+ if (operation === "hubs") {
271
+ let hubs = (0, graph_js_1.detectHubs)(db);
272
+ if (filterDomain) {
273
+ hubs = hubs.filter((h) => h.domain === filterDomain || h.project === filterDomain);
274
+ }
275
+ if (hubs.length === 0)
276
+ return { content: [{ type: "text", text: "No hub memories found." }] };
277
+ const text = hubs.slice(0, resultLimit ?? 10).map((h) => `**${h.id.slice(0, 8)}** (${h.linkCount} links) | ${h.domain ?? h.project ?? "global"}\n ${h.content}`).join("\n\n");
278
+ return { content: [{ type: "text", text }] };
279
+ }
280
+ if (operation === "path") {
281
+ if (!id || !target_id)
282
+ return { content: [{ type: "text", text: "id and target_id are required for path operation" }], isError: true };
283
+ const fromId = resolveMemoryId(db, id);
284
+ const toId = resolveMemoryId(db, target_id);
285
+ if (!fromId || !toId)
286
+ return { content: [{ type: "text", text: "One or both memory IDs not found" }], isError: true };
287
+ const path = (0, graph_js_1.shortestPath)(db, fromId, toId);
288
+ if (!path)
289
+ return { content: [{ type: "text", text: "No path found between these memories." }] };
290
+ const text = path.map((nodeId, i) => {
291
+ const mem = storage.getMemory(db, nodeId);
292
+ return `${i + 1}. ${nodeId.slice(0, 8)} | ${mem?.project ?? "?"} | ${mem?.content.slice(0, 150) ?? "?"}`;
293
+ }).join("\n");
294
+ return { content: [{ type: "text", text: `Path (${path.length} hops):\n${text}` }] };
295
+ }
296
+ return { content: [{ type: "text", text: `Unknown operation: ${operation}` }], isError: true };
297
+ }
298
+ catch (err) {
299
+ return { content: [{ type: "text", text: `Graph query failed: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
300
+ }
301
+ });
246
302
  return server;
247
303
  }
248
304
  // ---------------------------------------------------------------------------
package/dist/types.d.ts CHANGED
@@ -70,6 +70,10 @@ export interface ConsolidationReport {
70
70
  domains: number;
71
71
  reason?: string;
72
72
  };
73
+ hub_boost?: {
74
+ hubs_found: number;
75
+ boosted: number;
76
+ };
73
77
  links?: {
74
78
  auto_linked: number;
75
79
  failed: number;
@@ -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.6.0",
5
+ "version": "0.7.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.6.0",
3
+ "version": "0.7.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": {