hmem-mcp 3.0.0 → 3.1.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.
@@ -99,14 +99,8 @@ export async function logExchange() {
99
99
  try {
100
100
  // Find or create active O-entry
101
101
  const activeOId = store.getActiveO();
102
- // Flatten multiline text for appendChildren (newlines → " | " to prevent depth parsing)
103
- const flatUser = userMessage.replace(/\n+/g, " | ").substring(0, 25_000);
104
- const flatAgent = input.last_assistant_message.replace(/\n+/g, " | ").substring(0, 50_000);
105
- const title = extractTitle(userMessage);
106
- // appendChildren format: 0 tabs = L2 (direct child of root O-entry)
107
- // 1 tab = L3, 2 tabs = L4 (user msg), 3 tabs = L5 (agent response)
108
- const chunk = `${title}\n\t\t${flatUser}\n\t\t\t${flatAgent}`;
109
- store.appendChildren(activeOId, chunk);
102
+ // appendExchange stores raw text without newline parsing
103
+ store.appendExchange(activeOId, userMessage, input.last_assistant_message);
110
104
  }
111
105
  catch (e) {
112
106
  console.error(`[hmem log-exchange] ${e}`);
@@ -1 +1 @@
1
- {"version":3,"file":"cli-log-exchange.js","sourceRoot":"","sources":["../src/cli-log-exchange.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli-log-exchange.js","sourceRoot":"","sources":["../src/cli-log-exchange.ts"],"names":[],"mappings":"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"}
@@ -279,7 +279,7 @@ export declare class HmemStore {
279
279
  * For sub-nodes: updates node content only.
280
280
  * Does NOT modify children — use appendChildren to extend the tree.
281
281
  */
282
- updateNode(id: string, newContent: string, links?: string[], obsolete?: boolean, favorite?: boolean, curatorBypass?: boolean, irrelevant?: boolean, tags?: string[], pinned?: boolean, active?: boolean): boolean;
282
+ updateNode(id: string, newContent?: string, links?: string[], obsolete?: boolean, favorite?: boolean, curatorBypass?: boolean, irrelevant?: boolean, tags?: string[], pinned?: boolean, active?: boolean): boolean;
283
283
  /**
284
284
  * Append new child nodes under an existing entry (root or node).
285
285
  * Content is tab-indented relative to the parent:
@@ -292,6 +292,16 @@ export declare class HmemStore {
292
292
  count: number;
293
293
  ids: string[];
294
294
  };
295
+ /**
296
+ * Append a chat exchange (user prompt + agent response) to an O-entry.
297
+ * Inserts 3 nodes as a linear chain WITHOUT content parsing — newlines are preserved.
298
+ * L2: title (auto-extracted from userText)
299
+ * L4: user message (raw, newlines intact)
300
+ * L5: agent response (raw, newlines intact)
301
+ */
302
+ appendExchange(parentId: string, userText: string, agentText: string): {
303
+ id: string;
304
+ };
295
305
  /**
296
306
  * Bump access_count on a root entry or node.
297
307
  * Returns true if the entry was found and bumped.
@@ -570,3 +580,23 @@ export declare function openAgentMemory(projectDir: string, templateName: string
570
580
  * Open (or create) the shared company knowledge store (company.hmem).
571
581
  */
572
582
  export declare function openCompanyMemory(projectDir: string, config?: HmemConfig): HmemStore;
583
+ export interface AgentRouteResult {
584
+ agent: string;
585
+ score: number;
586
+ entryCount: number;
587
+ topEntries: {
588
+ id: string;
589
+ title: string;
590
+ score: number;
591
+ }[];
592
+ }
593
+ /**
594
+ * Route a task to the best-matching agent based on memory content.
595
+ * Scans all agent .hmem files in the project directory and scores them
596
+ * against the provided tags and/or search keywords.
597
+ *
598
+ * Scoring: for each agent store, find entries sharing the given tags
599
+ * with tier-weighted scoring (rare=3, medium=2, common=1).
600
+ * FTS5 keyword matching supplements tag scoring.
601
+ */
602
+ export declare function routeTask(projectDir: string, tags: string[], keywords?: string, limit?: number, config?: HmemConfig): AgentRouteResult[];
@@ -1387,19 +1387,23 @@ export class HmemStore {
1387
1387
  */
1388
1388
  updateNode(id, newContent, links, obsolete, favorite, curatorBypass, irrelevant, tags, pinned, active) {
1389
1389
  this.guardCorrupted();
1390
- const trimmed = newContent.trim();
1390
+ const trimmed = newContent?.trim();
1391
1391
  if (id.includes(".")) {
1392
1392
  // Sub-node in memory_nodes — check char limit for its depth
1393
1393
  const nodeRow = this.db.prepare("SELECT depth, content FROM memory_nodes WHERE id = ?").get(id);
1394
1394
  if (!nodeRow)
1395
1395
  return false;
1396
1396
  const oldContent = nodeRow.content;
1397
- const nodeLimit = this.cfg.maxCharsPerLevel[Math.min(nodeRow.depth - 1, this.cfg.maxCharsPerLevel.length - 1)];
1398
- if (trimmed.length > nodeLimit * HmemStore.CHAR_LIMIT_TOLERANCE) {
1399
- throw new Error(`Content exceeds ${nodeLimit} character limit (${trimmed.length} chars) for L${nodeRow.depth}.`);
1397
+ const sets = [];
1398
+ const params = [];
1399
+ if (trimmed) {
1400
+ const nodeLimit = this.cfg.maxCharsPerLevel[Math.min(nodeRow.depth - 1, this.cfg.maxCharsPerLevel.length - 1)];
1401
+ if (trimmed.length > nodeLimit * HmemStore.CHAR_LIMIT_TOLERANCE) {
1402
+ throw new Error(`Content exceeds ${nodeLimit} character limit (${trimmed.length} chars) for L${nodeRow.depth}.`);
1403
+ }
1404
+ sets.push("content = ?", "title = ?");
1405
+ params.push(trimmed, this.autoExtractTitle(trimmed));
1400
1406
  }
1401
- const sets = ["content = ?", "title = ?"];
1402
- const params = [trimmed, this.autoExtractTitle(trimmed)];
1403
1407
  if (favorite !== undefined) {
1404
1408
  sets.push("favorite = ?");
1405
1409
  params.push(favorite ? 1 : 0);
@@ -1440,14 +1444,17 @@ export class HmemStore {
1440
1444
  return result.changes > 0;
1441
1445
  }
1442
1446
  else {
1443
- // Root entry in memories — check L1 char limit
1444
- const l1Limit = this.cfg.maxCharsPerLevel[0];
1445
- if (trimmed.length > l1Limit * HmemStore.CHAR_LIMIT_TOLERANCE) {
1446
- throw new Error(`Level 1 exceeds ${l1Limit} character limit (${trimmed.length} chars). Keep L1 compact.`);
1447
+ // Root entry in memories
1448
+ if (trimmed) {
1449
+ const l1Limit = this.cfg.maxCharsPerLevel[0];
1450
+ if (trimmed.length > l1Limit * HmemStore.CHAR_LIMIT_TOLERANCE) {
1451
+ throw new Error(`Level 1 exceeds ${l1Limit} character limit (${trimmed.length} chars). Keep L1 compact.`);
1452
+ }
1447
1453
  }
1448
1454
  // Obsolete enforcement: require [✓ID] correction reference
1449
1455
  if (obsolete === true && !curatorBypass) {
1450
- const correctionMatch = trimmed.match(/\[✓([A-Z]\d{4}(?:\.\d+)*)\]/);
1456
+ const contentToCheck = trimmed ?? this.db.prepare("SELECT level_1 FROM memories WHERE id = ?").get(id)?.level_1 ?? "";
1457
+ const correctionMatch = contentToCheck.match(/\[✓([A-Z]\d{4}(?:\.\d+)*)\]/);
1451
1458
  if (!correctionMatch) {
1452
1459
  throw new Error("Cannot mark as obsolete without [✓ID] correction reference — write the correction first.");
1453
1460
  }
@@ -1477,8 +1484,12 @@ export class HmemStore {
1477
1484
  }
1478
1485
  }
1479
1486
  }
1480
- const sets = ["level_1 = ?", "title = ?"];
1481
- const params = [trimmed, this.autoExtractTitle(trimmed)];
1487
+ const sets = [];
1488
+ const params = [];
1489
+ if (trimmed) {
1490
+ sets.push("level_1 = ?", "title = ?");
1491
+ params.push(trimmed, this.autoExtractTitle(trimmed));
1492
+ }
1482
1493
  if (links !== undefined) {
1483
1494
  sets.push("links = ?");
1484
1495
  params.push(links.length > 0 ? JSON.stringify(links) : null);
@@ -1590,6 +1601,36 @@ export class HmemStore {
1590
1601
  }
1591
1602
  return { count: nodes.length, ids: topLevelIds };
1592
1603
  }
1604
+ /**
1605
+ * Append a chat exchange (user prompt + agent response) to an O-entry.
1606
+ * Inserts 3 nodes as a linear chain WITHOUT content parsing — newlines are preserved.
1607
+ * L2: title (auto-extracted from userText)
1608
+ * L4: user message (raw, newlines intact)
1609
+ * L5: agent response (raw, newlines intact)
1610
+ */
1611
+ appendExchange(parentId, userText, agentText) {
1612
+ this.guardCorrupted();
1613
+ const parentIsRoot = !parentId.includes(".");
1614
+ const rootId = parentIsRoot ? parentId : parentId.split(".")[0];
1615
+ const timestamp = new Date().toISOString();
1616
+ // Find next available seq
1617
+ const maxSeq = parentIsRoot
1618
+ ? this.db.prepare("SELECT MAX(seq) as m FROM memory_nodes WHERE parent_id = ? AND depth = 2").get(parentId)?.m ?? 0
1619
+ : this.db.prepare("SELECT MAX(seq) as m FROM memory_nodes WHERE parent_id = ?").get(parentId)?.m ?? 0;
1620
+ const seq = maxSeq + 1;
1621
+ const title = this.autoExtractTitle(userText.split("\n")[0].replace(/[<>\[\]]/g, ""));
1622
+ const l2Id = `${parentId}.${seq}`;
1623
+ const l4Id = `${l2Id}.1`;
1624
+ const l5Id = `${l4Id}.1`;
1625
+ const insertNode = this.db.prepare("INSERT INTO memory_nodes (id, parent_id, root_id, depth, seq, title, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
1626
+ this.db.transaction(() => {
1627
+ insertNode.run(l2Id, parentId, rootId, 2, seq, title, title, timestamp, timestamp);
1628
+ insertNode.run(l4Id, l2Id, rootId, 4, 1, this.autoExtractTitle(userText), userText, timestamp, timestamp);
1629
+ insertNode.run(l5Id, l4Id, rootId, 5, 1, this.autoExtractTitle(agentText), agentText, timestamp, timestamp);
1630
+ this.db.prepare("UPDATE memories SET updated_at = ? WHERE id = ?").run(timestamp, rootId);
1631
+ })();
1632
+ return { id: l2Id };
1633
+ }
1593
1634
  /**
1594
1635
  * Bump access_count on a root entry or node.
1595
1636
  * Returns true if the entry was found and bumped.
@@ -2952,4 +2993,143 @@ export function openCompanyMemory(projectDir, config) {
2952
2993
  const hmemPath = path.join(projectDir, "company.hmem");
2953
2994
  return new HmemStore(hmemPath, config);
2954
2995
  }
2996
+ /**
2997
+ * Route a task to the best-matching agent based on memory content.
2998
+ * Scans all agent .hmem files in the project directory and scores them
2999
+ * against the provided tags and/or search keywords.
3000
+ *
3001
+ * Scoring: for each agent store, find entries sharing the given tags
3002
+ * with tier-weighted scoring (rare=3, medium=2, common=1).
3003
+ * FTS5 keyword matching supplements tag scoring.
3004
+ */
3005
+ export function routeTask(projectDir, tags, keywords, limit = 5, config) {
3006
+ // Discover all agent .hmem files — requires multi-agent setup
3007
+ const agentsDir = path.join(projectDir, "Agents");
3008
+ if (!fs.existsSync(agentsDir))
3009
+ return [];
3010
+ const agentDirs = fs.readdirSync(agentsDir).filter(name => {
3011
+ const agentPath = path.join(agentsDir, name);
3012
+ return fs.statSync(agentPath).isDirectory() &&
3013
+ fs.existsSync(path.join(agentPath, `${name}.hmem`));
3014
+ });
3015
+ const results = [];
3016
+ for (const agentName of agentDirs) {
3017
+ const hmemPath = path.join(agentsDir, agentName, `${agentName}.hmem`);
3018
+ let db;
3019
+ try {
3020
+ db = new Database(hmemPath, { readonly: true });
3021
+ }
3022
+ catch {
3023
+ continue;
3024
+ }
3025
+ try {
3026
+ let agentScore = 0;
3027
+ const topEntries = [];
3028
+ // Phase 1: Tag-based scoring
3029
+ if (tags.length > 0) {
3030
+ // Get global tag frequencies for THIS store
3031
+ const freqRows = db.prepare(`
3032
+ SELECT tag, COUNT(DISTINCT
3033
+ CASE WHEN entry_id LIKE '%.%'
3034
+ THEN SUBSTR(entry_id, 1, INSTR(entry_id, '.') - 1)
3035
+ ELSE entry_id END
3036
+ ) as freq FROM memory_tags GROUP BY tag
3037
+ `).all();
3038
+ const tagFreq = new Map();
3039
+ for (const r of freqRows)
3040
+ tagFreq.set(r.tag, r.freq);
3041
+ // Find entries sharing any of the given tags
3042
+ const normalizedTags = tags.map(t => t.startsWith("#") ? t.toLowerCase() : `#${t.toLowerCase()}`);
3043
+ const placeholders = normalizedTags.map(() => "?").join(", ");
3044
+ const matchRows = db.prepare(`
3045
+ SELECT
3046
+ CASE WHEN entry_id LIKE '%.%'
3047
+ THEN SUBSTR(entry_id, 1, INSTR(entry_id, '.') - 1)
3048
+ ELSE entry_id END as root_id,
3049
+ tag
3050
+ FROM memory_tags WHERE tag IN (${placeholders})
3051
+ `).all(...normalizedTags);
3052
+ // Group by root and score
3053
+ const byRoot = new Map();
3054
+ for (const r of matchRows) {
3055
+ if (r.root_id.startsWith("O"))
3056
+ continue; // skip O-entries
3057
+ let set = byRoot.get(r.root_id);
3058
+ if (!set) {
3059
+ set = new Set();
3060
+ byRoot.set(r.root_id, set);
3061
+ }
3062
+ set.add(r.tag);
3063
+ }
3064
+ for (const [rootId, matchedTags] of byRoot) {
3065
+ let entryScore = 0;
3066
+ for (const tag of matchedTags) {
3067
+ const freq = tagFreq.get(tag) ?? 999;
3068
+ if (freq <= 5)
3069
+ entryScore += 3;
3070
+ else if (freq <= 20)
3071
+ entryScore += 2;
3072
+ else
3073
+ entryScore += 1;
3074
+ }
3075
+ agentScore += entryScore;
3076
+ // Get entry title
3077
+ const row = db.prepare("SELECT title, level_1 FROM memories WHERE id = ? AND obsolete != 1 AND irrelevant != 1").get(rootId);
3078
+ if (row) {
3079
+ topEntries.push({
3080
+ id: rootId,
3081
+ title: row.title || row.level_1?.substring(0, 50) || rootId,
3082
+ score: entryScore,
3083
+ });
3084
+ }
3085
+ }
3086
+ }
3087
+ // Phase 2: FTS5 keyword supplement
3088
+ if (keywords && keywords.trim().length > 0) {
3089
+ try {
3090
+ const words = keywords.trim().split(/\s+/).filter(w => w.length > 3).slice(0, 5);
3091
+ if (words.length > 0) {
3092
+ const orQuery = words.join(" OR ");
3093
+ const ftsRows = db.prepare(`
3094
+ SELECT rm.root_id, rm.node_id FROM hmem_fts_rowid_map rm
3095
+ JOIN hmem_fts f ON f.rowid = rm.fts_rowid
3096
+ WHERE hmem_fts MATCH ? LIMIT 20
3097
+ `).all(orQuery);
3098
+ const ftsRoots = new Set(ftsRows.map(r => r.root_id).filter(id => !id.startsWith("O")));
3099
+ for (const rootId of ftsRoots) {
3100
+ if (topEntries.some(e => e.id === rootId))
3101
+ continue; // already scored via tags
3102
+ const row = db.prepare("SELECT title, level_1 FROM memories WHERE id = ? AND obsolete != 1 AND irrelevant != 1").get(rootId);
3103
+ if (row) {
3104
+ agentScore += 1; // FTS matches get 1 point each
3105
+ topEntries.push({
3106
+ id: rootId,
3107
+ title: row.title || row.level_1?.substring(0, 50) || rootId,
3108
+ score: 1,
3109
+ });
3110
+ }
3111
+ }
3112
+ }
3113
+ }
3114
+ catch { /* FTS5 may not exist in all stores */ }
3115
+ }
3116
+ if (agentScore > 0) {
3117
+ // Sort entries by score, keep top 5
3118
+ topEntries.sort((a, b) => b.score - a.score);
3119
+ results.push({
3120
+ agent: agentName,
3121
+ score: agentScore,
3122
+ entryCount: topEntries.length,
3123
+ topEntries: topEntries.slice(0, 5),
3124
+ });
3125
+ }
3126
+ }
3127
+ finally {
3128
+ db.close();
3129
+ }
3130
+ }
3131
+ // Sort agents by score
3132
+ results.sort((a, b) => b.score - a.score);
3133
+ return results.slice(0, limit);
3134
+ }
2955
3135
  //# sourceMappingURL=hmem-store.js.map