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.
- package/dist/hmem-store.d.ts +20 -0
- package/dist/hmem-store.js +139 -0
- package/dist/hmem-store.js.map +1 -1
- package/dist/mcp-server.js +33 -1
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/skills/hmem-config/SKILL.md +63 -2
package/dist/hmem-store.d.ts
CHANGED
|
@@ -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[];
|
package/dist/hmem-store.js
CHANGED
|
@@ -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
|