hmem-mcp 3.0.1 → 3.1.1

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.
@@ -580,3 +580,23 @@ export declare function openAgentMemory(projectDir: string, templateName: string
580
580
  * Open (or create) the shared company knowledge store (company.hmem).
581
581
  */
582
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[];
@@ -2993,4 +2993,143 @@ export function openCompanyMemory(projectDir, config) {
2993
2993
  const hmemPath = path.join(projectDir, "company.hmem");
2994
2994
  return new HmemStore(hmemPath, config);
2995
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
+ }
2996
3135
  //# sourceMappingURL=hmem-store.js.map