hmem-mcp 5.0.0 → 5.1.21
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 +161 -214
- package/dist/cli-checkpoint.js +102 -40
- package/dist/cli-checkpoint.js.map +1 -1
- package/dist/cli-context-inject.d.ts +7 -6
- package/dist/cli-context-inject.js +27 -130
- package/dist/cli-context-inject.js.map +1 -1
- package/dist/cli-env.d.ts +16 -0
- package/dist/cli-env.js +40 -0
- package/dist/cli-env.js.map +1 -0
- package/dist/cli-hook-startup.d.ts +20 -0
- package/dist/cli-hook-startup.js +101 -0
- package/dist/cli-hook-startup.js.map +1 -0
- package/dist/cli-init.js +97 -188
- package/dist/cli-init.js.map +1 -1
- package/dist/cli-log-exchange.js +63 -3
- package/dist/cli-log-exchange.js.map +1 -1
- package/dist/cli-statusline.d.ts +14 -0
- package/dist/cli-statusline.js +172 -0
- package/dist/cli-statusline.js.map +1 -0
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/hmem-config.d.ts +10 -0
- package/dist/hmem-config.js +63 -13
- package/dist/hmem-config.js.map +1 -1
- package/dist/hmem-store.d.ts +30 -1
- package/dist/hmem-store.js +219 -48
- package/dist/hmem-store.js.map +1 -1
- package/dist/mcp-server.js +202 -75
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/scripts/autoresearch-nightly.sh +84 -0
- package/scripts/hmem-statusline.sh +4 -0
- package/skills/hmem-config/SKILL.md +112 -147
- package/skills/hmem-curate/SKILL.md +56 -6
- package/skills/hmem-new-project/SKILL.md +164 -0
- package/skills/hmem-read/SKILL.md +174 -146
- package/skills/hmem-release/SKILL.md +141 -0
- package/skills/hmem-self-curate/SKILL.md +49 -7
- package/skills/hmem-setup/SKILL.md +169 -87
- package/skills/hmem-sync-setup/SKILL.md +16 -3
- package/skills/hmem-update/SKILL.md +254 -0
- package/skills/hmem-wipe/SKILL.md +47 -21
- package/skills/hmem-write/SKILL.md +38 -14
package/dist/mcp-server.js
CHANGED
|
@@ -116,14 +116,14 @@ function syncPull(hmemPath) {
|
|
|
116
116
|
"pull", "--config", hmemSyncConfig(hmemPath),
|
|
117
117
|
"--hmem-path", hmemPath,
|
|
118
118
|
"--server-url", s.serverUrl, "--token", s.token,
|
|
119
|
-
], { env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32" });
|
|
119
|
+
], { env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32", windowsHide: true });
|
|
120
120
|
if (result.error)
|
|
121
121
|
process.stderr.write(`hmem-sync pull error (${s.name ?? s.serverUrl}): ${result.error.message}\n`);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
125
|
const result = spawnSync("hmem-sync", ["pull", "--config", hmemSyncConfig(hmemPath), "--hmem-path", hmemPath], {
|
|
126
|
-
env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32",
|
|
126
|
+
env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32", windowsHide: true,
|
|
127
127
|
});
|
|
128
128
|
if (result.error)
|
|
129
129
|
process.stderr.write(`hmem-sync pull error: ${result.error.message}\n`);
|
|
@@ -175,12 +175,12 @@ function syncPullThenPush(hmemPath) {
|
|
|
175
175
|
"pull", "--config", hmemSyncConfig(hmemPath),
|
|
176
176
|
"--hmem-path", hmemPath,
|
|
177
177
|
"--server-url", s.serverUrl, "--token", s.token,
|
|
178
|
-
], { env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32" });
|
|
178
|
+
], { env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32", windowsHide: true });
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
else {
|
|
182
182
|
spawnSync("hmem-sync", ["pull", "--config", hmemSyncConfig(hmemPath), "--hmem-path", hmemPath], {
|
|
183
|
-
env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32",
|
|
183
|
+
env: { ...process.env }, encoding: "utf8", shell: process.platform === "win32", windowsHide: true,
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
lastPullAt = Date.now();
|
|
@@ -197,13 +197,13 @@ function syncPush(hmemPath) {
|
|
|
197
197
|
"push", "--config", hmemSyncConfig(hmemPath),
|
|
198
198
|
"--hmem-path", hmemPath,
|
|
199
199
|
"--server-url", s.serverUrl, "--token", s.token,
|
|
200
|
-
], { env: { ...process.env }, shell: process.platform === "win32", stdio: "ignore", detached: true });
|
|
200
|
+
], { env: { ...process.env }, shell: process.platform === "win32", stdio: "ignore", detached: true, windowsHide: true });
|
|
201
201
|
child.unref();
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
else {
|
|
205
205
|
const child = spawn("hmem-sync", ["push", "--config", hmemSyncConfig(hmemPath), "--hmem-path", hmemPath], {
|
|
206
|
-
env: { ...process.env }, shell: process.platform === "win32", stdio: "ignore", detached: true,
|
|
206
|
+
env: { ...process.env }, shell: process.platform === "win32", stdio: "ignore", detached: true, windowsHide: true,
|
|
207
207
|
});
|
|
208
208
|
child.unref();
|
|
209
209
|
}
|
|
@@ -211,11 +211,60 @@ function syncPush(hmemPath) {
|
|
|
211
211
|
// Load hmem config (hmem.config.json in project dir, falls back to defaults)
|
|
212
212
|
const hmemConfig = loadHmemConfig(PROJECT_DIR);
|
|
213
213
|
log(`Config: levels=[${hmemConfig.maxCharsPerLevel.join(",")}] depth=${hmemConfig.maxDepth}`);
|
|
214
|
+
// ---- Version upgrade detection ----
|
|
215
|
+
import { createRequire } from "node:module";
|
|
216
|
+
const _require = createRequire(import.meta.url);
|
|
217
|
+
const PKG_VERSION = _require("../package.json").version;
|
|
218
|
+
/** Check if hmem was upgraded since last session. Auto-syncs skills and returns upgrade notice. */
|
|
219
|
+
function checkVersionUpgrade() {
|
|
220
|
+
try {
|
|
221
|
+
const configPath = path.join(PROJECT_DIR, "hmem.config.json");
|
|
222
|
+
if (!fs.existsSync(configPath))
|
|
223
|
+
return "";
|
|
224
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
225
|
+
const lastSeen = raw?.memory?.lastSeenVersion || raw?.lastSeenVersion;
|
|
226
|
+
if (!lastSeen) {
|
|
227
|
+
// First run with version tracking — save current version, sync skills silently
|
|
228
|
+
saveLastSeenVersion(configPath, raw);
|
|
229
|
+
autoSyncSkills();
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
if (lastSeen !== PKG_VERSION) {
|
|
233
|
+
saveLastSeenVersion(configPath, raw);
|
|
234
|
+
autoSyncSkills();
|
|
235
|
+
return `\n\n⚠ hmem-mcp updated: v${lastSeen} → v${PKG_VERSION}. Skills have been auto-synced. Run /hmem-update for full post-update steps (entry migration, schema enforcement, config check).`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch { }
|
|
239
|
+
return "";
|
|
240
|
+
}
|
|
241
|
+
/** Auto-sync skill files on version upgrade. Runs hmem update-skills in background. */
|
|
242
|
+
function autoSyncSkills() {
|
|
243
|
+
try {
|
|
244
|
+
const child = spawn("hmem", ["update-skills"], {
|
|
245
|
+
detached: true, stdio: "ignore",
|
|
246
|
+
env: { ...process.env },
|
|
247
|
+
});
|
|
248
|
+
child.unref();
|
|
249
|
+
log("Auto-syncing skills after version upgrade");
|
|
250
|
+
}
|
|
251
|
+
catch { }
|
|
252
|
+
}
|
|
253
|
+
function saveLastSeenVersion(configPath, raw) {
|
|
254
|
+
try {
|
|
255
|
+
if (!raw.memory)
|
|
256
|
+
raw.memory = {};
|
|
257
|
+
raw.memory.lastSeenVersion = PKG_VERSION;
|
|
258
|
+
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
}
|
|
262
|
+
let versionUpgradeNotice = checkVersionUpgrade();
|
|
214
263
|
// Session-scoped cache — persists across tool calls within this MCP connection
|
|
215
264
|
const sessionCache = new SessionCache();
|
|
216
265
|
const CONTEXT_THRESHOLD_WARNING = "\n\n⚠ CONTEXT THRESHOLD REACHED (~{tokens}k tokens delivered this session).\n" +
|
|
217
|
-
"
|
|
218
|
-
"
|
|
266
|
+
"Tell the user to run /hmem-wipe — it saves key knowledge and prepares for /clear.\n" +
|
|
267
|
+
"Alternative: flush_context manually, then /clear, then load_project to restore context.";
|
|
219
268
|
const CACHE_RESET_SIGNAL = "/tmp/hmem-cache-reset-signal";
|
|
220
269
|
/** Track tokens in a tool response and append threshold warning if needed. */
|
|
221
270
|
function trackTokens(result) {
|
|
@@ -232,6 +281,11 @@ function trackTokens(result) {
|
|
|
232
281
|
return result;
|
|
233
282
|
const text = result.content.map(c => c.text).join("");
|
|
234
283
|
sessionCache.addTokens(text.length);
|
|
284
|
+
// One-time version upgrade notice (shown once per session)
|
|
285
|
+
if (versionUpgradeNotice) {
|
|
286
|
+
result.content[result.content.length - 1].text += versionUpgradeNotice;
|
|
287
|
+
versionUpgradeNotice = ""; // only show once
|
|
288
|
+
}
|
|
235
289
|
if (sessionCache.checkThreshold(hmemConfig.contextTokenThreshold)) {
|
|
236
290
|
const tokK = Math.round(sessionCache.totalTokensDelivered / 1000);
|
|
237
291
|
result.content[result.content.length - 1].text += CONTEXT_THRESHOLD_WARNING.replace("{tokens}", String(tokK));
|
|
@@ -259,8 +313,14 @@ function formatRecentOEntries(store, limit, exchangeCount, linkedTo, expandAll)
|
|
|
259
313
|
lines.push(` ${o.id} ${o.created_at.substring(0, 10)} ${o.title}`);
|
|
260
314
|
// Expand exchanges: all entries when expandAll, otherwise only latest
|
|
261
315
|
if (expandAll || i === 0) {
|
|
262
|
-
|
|
263
|
-
|
|
316
|
+
// Check for checkpoint summaries — if present, show summary + only exchanges after it
|
|
317
|
+
// Always show: latest summary (if any) + last 5 exchanges verbatim
|
|
318
|
+
const VERBATIM_WINDOW = 5;
|
|
319
|
+
const summaries = store.getCheckpointSummaries(o.id, 1);
|
|
320
|
+
if (summaries.length > 0) {
|
|
321
|
+
lines.push(` [Summary] ${summaries[0].content}`);
|
|
322
|
+
}
|
|
323
|
+
const exchanges = store.getOEntryExchanges(o.id, VERBATIM_WINDOW, true);
|
|
264
324
|
for (const ex of exchanges) {
|
|
265
325
|
const userShort = ex.userText.length > 300 ? ex.userText.substring(0, 300) + "..." : ex.userText;
|
|
266
326
|
const agentShort = ex.agentText.length > 500 ? ex.agentText.substring(0, 500) + "..." : ex.agentText;
|
|
@@ -339,26 +399,30 @@ server.tool("write_memory", "Write a new memory entry to your hierarchical long-
|
|
|
339
399
|
" Level 3: 2 tabs — even more detail\n" +
|
|
340
400
|
" Level 4: 3 tabs — fine-grained detail\n" +
|
|
341
401
|
" Level 5: 4 tabs — raw context/data\n" +
|
|
402
|
+
"Use > lines for body text (shown on drill-down, hidden in listings):\n" +
|
|
403
|
+
" Title line\\n> Body line 1\\n> Body line 2\\n\\tChild title\\n\\t> Child body\n" +
|
|
342
404
|
"The system auto-assigns an ID and timestamp. " +
|
|
343
405
|
`Use prefix to categorize: ${prefixList}.\n\n` +
|
|
344
406
|
"Store types:\n" +
|
|
345
407
|
" personal (default): Your private memory\n", {
|
|
346
408
|
prefix: z.string().toUpperCase().describe(`Memory category: ${prefixList}`),
|
|
347
|
-
content: z.string().min(3).describe("The memory content. Use tab indentation for depth levels.
|
|
348
|
-
"
|
|
349
|
-
"
|
|
350
|
-
"
|
|
351
|
-
"\
|
|
409
|
+
content: z.string().min(3).describe("The memory content. Use tab indentation for depth levels. Use > for body text (hidden in listings, shown on drill-down).\n" +
|
|
410
|
+
"Example:\n" +
|
|
411
|
+
"Council Dashboard for Althing Inc.\n" +
|
|
412
|
+
"> Built a real-time dashboard with React + Vite. ShadcnUI for components, SSE for live updates.\n" +
|
|
413
|
+
"\tFrontend architecture\n" +
|
|
414
|
+
"\t> React + Vite, ShadcnUI components, SSE for real-time updates\n" +
|
|
415
|
+
"\t\tAuth was tricky — EventSource can't send custom headers"),
|
|
352
416
|
links: z.array(z.string()).optional().describe("Optional: IDs of related memories, e.g. ['P0001', 'L0005']"),
|
|
353
|
-
favorite: z.boolean().optional().describe("Mark this entry as a favorite — shown with [♥] in bulk reads and always inlined with L2 detail. " +
|
|
417
|
+
favorite: z.coerce.boolean().optional().describe("Mark this entry as a favorite — shown with [♥] in bulk reads and always inlined with L2 detail. " +
|
|
354
418
|
"Use for reference info you need to see every session, regardless of category."),
|
|
355
419
|
tags: z.array(z.string()).min(1).describe("Required hashtags for cross-cutting search (min 1, recommend 3+). " +
|
|
356
420
|
"E.g. ['#hmem', '#curation']. Max 10, lowercase, must start with #. Shown after title in reads."),
|
|
357
|
-
pinned: z.boolean().optional().describe("Mark this entry as pinned [P] (super-favorite). Pinned entries show full L2 content in bulk reads. " +
|
|
421
|
+
pinned: z.coerce.boolean().optional().describe("Mark this entry as pinned [P] (super-favorite). Pinned entries show full L2 content in bulk reads. " +
|
|
358
422
|
"Use for reference entries you need to see in full every session."),
|
|
359
423
|
store: z.enum(["personal", "company"]).default("personal").describe("Target store: 'personal' or 'company'"),
|
|
360
424
|
min_role: z.enum(["worker", "al", "pl", "ceo"]).default("worker").describe("Minimum role to see this entry"),
|
|
361
|
-
force: z.boolean().optional().describe("Force creation of a new root entry even if existing entries share tags. " +
|
|
425
|
+
force: z.coerce.boolean().optional().describe("Force creation of a new root entry even if existing entries share tags. " +
|
|
362
426
|
"Only use when you intentionally want a separate entry, not a child of an existing one."),
|
|
363
427
|
}, async ({ prefix, content, links, favorite, tags, pinned, store: storeName, min_role: minRole, force }) => {
|
|
364
428
|
const templateName = AGENT_ID.replace(/_\d+$/, "");
|
|
@@ -455,9 +519,11 @@ server.tool("write_memory", "Write a new memory entry to your hierarchical long-
|
|
|
455
519
|
});
|
|
456
520
|
server.tool("update_memory", "Update the text of an existing memory entry or sub-node (your own personal memory). " +
|
|
457
521
|
"Only modifies the text at the specified ID — children are preserved unchanged.\n\n" +
|
|
522
|
+
"Supports > body format: 'New title\\n> Body line 1\\n> Body line 2' splits into title (shown in listings) + body (shown on drill-down).\n\n" +
|
|
458
523
|
"Use cases:\n" +
|
|
459
524
|
"- Correct outdated wording: update_memory(id='L0003', content='corrected summary')\n" +
|
|
460
|
-
"-
|
|
525
|
+
"- Add title/body split: update_memory(id='L0003', content='Short title\\n> Detailed body text')\n" +
|
|
526
|
+
"- Fix a sub-node: update_memory(id='L0003.2', content='node title\\n> node body')\n" +
|
|
461
527
|
"- Mark as obsolete: FIRST write the correction, THEN update with [✓ID] reference:\n" +
|
|
462
528
|
" 1. write_memory(prefix='E', content='Correct fix is...') → E0076\n" +
|
|
463
529
|
" 2. update_memory(id='E0042', content='Wrong — see [✓E0076]', obsolete=true)\n" +
|
|
@@ -469,17 +535,17 @@ server.tool("update_memory", "Update the text of an existing memory entry or sub
|
|
|
469
535
|
id: z.string().describe("ID of the entry or node to update, e.g. 'L0003' or 'L0003.2'"),
|
|
470
536
|
content: z.string().min(1).describe("New text content for this node (plain text, no indentation)"),
|
|
471
537
|
links: z.array(z.string()).optional().describe("Optional: update linked entry IDs (root entries only). Replaces existing links."),
|
|
472
|
-
obsolete: z.boolean().optional().describe("Mark this root entry as no longer valid (root entries only). " +
|
|
538
|
+
obsolete: z.coerce.boolean().optional().describe("Mark this root entry as no longer valid (root entries only). " +
|
|
473
539
|
"Requires [✓ID] correction reference in content (e.g. 'Wrong — see [✓E0076]')."),
|
|
474
|
-
favorite: z.boolean().optional().describe("Set or clear the [♥] favorite flag. Works on root entries and sub-nodes. " +
|
|
540
|
+
favorite: z.coerce.boolean().optional().describe("Set or clear the [♥] favorite flag. Works on root entries and sub-nodes. " +
|
|
475
541
|
"Root favorites are always shown with L2 detail in bulk reads."),
|
|
476
|
-
irrelevant: z.boolean().optional().describe("Mark as irrelevant [-]. Works on root entries and sub-nodes. " +
|
|
542
|
+
irrelevant: z.coerce.boolean().optional().describe("Mark as irrelevant [-]. Works on root entries and sub-nodes. " +
|
|
477
543
|
"No correction entry needed (unlike obsolete). Irrelevant entries/nodes are hidden from output."),
|
|
478
544
|
tags: z.array(z.string()).optional().describe("Set tags on this entry/node. Replaces all existing tags. " +
|
|
479
545
|
"Pass empty array [] to remove all tags. E.g. ['#hmem', '#curation']."),
|
|
480
|
-
pinned: z.boolean().optional().describe("Set or clear the [P] pinned flag (root entries only). " +
|
|
546
|
+
pinned: z.coerce.boolean().optional().describe("Set or clear the [P] pinned flag (root entries only). " +
|
|
481
547
|
"Pinned entries show full L2 content in bulk reads (super-favorite)."),
|
|
482
|
-
active: z.boolean().optional().describe("Mark this root entry as actively relevant [*] (root entries only). " +
|
|
548
|
+
active: z.coerce.boolean().optional().describe("Mark this root entry as actively relevant [*] (root entries only). " +
|
|
483
549
|
"When any entry in a prefix has active=true, only active entries of that prefix are shown with children in bulk reads. " +
|
|
484
550
|
"Non-active entries in the same prefix are shown as title-only (no children)."),
|
|
485
551
|
store: z.enum(["personal", "company"]).default("personal").describe("Target store: 'personal' or 'company'"),
|
|
@@ -560,10 +626,10 @@ server.tool("update_many", "Batch-update multiple memory entries at once. Applie
|
|
|
560
626
|
"Use this instead of calling update_memory multiple times during curation.\n\n" +
|
|
561
627
|
"Example: update_many(ids=['T0005', 'T0012', 'L0044'], irrelevant=true)", {
|
|
562
628
|
ids: z.array(z.string()).min(1).describe("List of entry/node IDs to update, e.g. ['T0005', 'T0012', 'L0044']"),
|
|
563
|
-
irrelevant: z.boolean().optional().describe("Mark all as irrelevant [-]"),
|
|
564
|
-
favorite: z.boolean().optional().describe("Set or clear [♥] favorite on all"),
|
|
565
|
-
active: z.boolean().optional().describe("Set or clear [*] active on all"),
|
|
566
|
-
pinned: z.boolean().optional().describe("Set or clear [P] pinned on all"),
|
|
629
|
+
irrelevant: z.coerce.boolean().optional().describe("Mark all as irrelevant [-]"),
|
|
630
|
+
favorite: z.coerce.boolean().optional().describe("Set or clear [♥] favorite on all"),
|
|
631
|
+
active: z.coerce.boolean().optional().describe("Set or clear [*] active on all"),
|
|
632
|
+
pinned: z.coerce.boolean().optional().describe("Set or clear [P] pinned on all"),
|
|
567
633
|
store: z.enum(["personal", "company"]).default("personal"),
|
|
568
634
|
}, async ({ ids, irrelevant, favorite, active, pinned, store: storeName }) => {
|
|
569
635
|
const templateName = AGENT_ID.replace(/_\d+$/, "");
|
|
@@ -659,10 +725,11 @@ server.tool("append_memory", "Append new child nodes to an existing memory entry
|
|
|
659
725
|
"Use this to extend an existing entry with additional detail without overwriting it.\n\n" +
|
|
660
726
|
"Content uses tab indentation relative to the parent:\n" +
|
|
661
727
|
" 0 tabs = direct child of id\n" +
|
|
662
|
-
" 1 tab = grandchild, etc.\n
|
|
728
|
+
" 1 tab = grandchild, etc.\n" +
|
|
729
|
+
"Use > for body text: 'Node title\\n> Body shown on drill-down\\n\\tChild node'\n\n" +
|
|
663
730
|
"Examples:\n" +
|
|
664
|
-
" append_memory(id='L0003', content='New finding\\n\\tSub-detail') " +
|
|
665
|
-
"→ adds L2 node + L3 child\n" +
|
|
731
|
+
" append_memory(id='L0003', content='New finding\\n> Detailed explanation\\n\\tSub-detail') " +
|
|
732
|
+
"→ adds L2 node (with title + body) + L3 child\n" +
|
|
666
733
|
" append_memory(id='L0003.2', content='Extra note') " +
|
|
667
734
|
"→ adds L3 node under the L2 node L0003.2", {
|
|
668
735
|
id: z.string().describe("Root entry ID or parent node ID to append children to, e.g. 'L0003' or 'L0003.2'"),
|
|
@@ -749,11 +816,11 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
749
816
|
time: z.string().optional().describe("Time filter 'HH:MM' — filter entries by time of day"),
|
|
750
817
|
period: z.string().optional().describe("Time window: '+4h' (after), '-2h' (before), '4h' (±4h symmetric), 'both' (±2h default)"),
|
|
751
818
|
time_around: z.string().optional().describe("Reference entry ID — find entries created around the same time"),
|
|
752
|
-
show_obsolete: z.boolean().optional().describe("Include all obsolete entries (default: only top 3 most-accessed)"),
|
|
753
|
-
show_obsolete_path: z.boolean().optional().describe("When reading an obsolete entry by ID, show the full correction chain instead of just the final valid entry."),
|
|
754
|
-
titles_only: z.boolean().optional().describe("Compact title listing — shows all entries as ID + date + title, without V2 selection or children. " +
|
|
819
|
+
show_obsolete: z.coerce.boolean().optional().describe("Include all obsolete entries (default: only top 3 most-accessed)"),
|
|
820
|
+
show_obsolete_path: z.coerce.boolean().optional().describe("When reading an obsolete entry by ID, show the full correction chain instead of just the final valid entry."),
|
|
821
|
+
titles_only: z.coerce.boolean().optional().describe("Compact title listing — shows all entries as ID + date + title, without V2 selection or children. " +
|
|
755
822
|
"Like a table of contents. Combine with prefix to filter by category."),
|
|
756
|
-
expand: z.boolean().optional().describe("Expand full tree with complete node content (ID queries only). " +
|
|
823
|
+
expand: z.coerce.boolean().optional().describe("Expand full tree with complete node content (ID queries only). " +
|
|
757
824
|
"Use to deep-dive into a project after a long break. " +
|
|
758
825
|
"depth controls how deep (default: 5 = full tree). " +
|
|
759
826
|
"Example: read_memory({ id: 'P0001', expand: true, depth: 3 })"),
|
|
@@ -762,8 +829,8 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
762
829
|
"use after context compression to recover key knowledge. " +
|
|
763
830
|
"Auto-selected if omitted: first bulk read → discover, subsequent → essentials."),
|
|
764
831
|
store: z.enum(["personal", "company"]).default("personal").describe("Source store: 'personal' or 'company'"),
|
|
765
|
-
curator: z.boolean().optional().describe("Set true to show full metadata (access counts, roles, dates). For curators only."),
|
|
766
|
-
show_all: z.boolean().optional().describe("Curation mode: show ALL entries of the selected prefix with depth 3 children. " +
|
|
832
|
+
curator: z.coerce.boolean().optional().describe("Set true to show full metadata (access counts, roles, dates). For curators only."),
|
|
833
|
+
show_all: z.coerce.boolean().optional().describe("Curation mode: show ALL entries of the selected prefix with depth 3 children. " +
|
|
767
834
|
"Bypasses V2 selection and session cache. Use with prefix filter for manageable output."),
|
|
768
835
|
tag: z.string().optional().describe("Filter by hashtag, e.g. '#hmem'. Only entries with this tag are shown in bulk reads. " +
|
|
769
836
|
"Also works with search to find tagged entries."),
|
|
@@ -863,7 +930,8 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
863
930
|
}
|
|
864
931
|
const effectiveDepth = depth || (id ? 2 : 1);
|
|
865
932
|
// Session cache: cached entries shown as titles in subsequent bulk reads
|
|
866
|
-
|
|
933
|
+
// Explicit filters (after, before, prefix, stale_days, tag) bypass V2 selection + cache
|
|
934
|
+
const isBulkListing = !id && !search && !time_around && !after && !before && !prefix && !stale_days && !tag;
|
|
867
935
|
const useCache = isBulkListing && storeName === "personal" && !show_all;
|
|
868
936
|
const cachedIds = useCache ? sessionCache.getCachedIds() : undefined;
|
|
869
937
|
const hiddenIds = useCache ? sessionCache.getHiddenIds() : undefined;
|
|
@@ -886,6 +954,7 @@ server.tool("read_memory", "Read from your hierarchical long-term memory (.hmem)
|
|
|
886
954
|
mode: isBulkListing ? effectiveMode : undefined,
|
|
887
955
|
tag,
|
|
888
956
|
staleDays: stale_days,
|
|
957
|
+
directResults: !isBulkListing && !id && !search && !time_around,
|
|
889
958
|
});
|
|
890
959
|
if (entries.length === 0) {
|
|
891
960
|
const hmemPath = storeName === "company"
|
|
@@ -1123,7 +1192,7 @@ server.tool("import_memory", "Import entries from a .hmem file into your memory.
|
|
|
1123
1192
|
"Deduplicates by L1 content (merges sub-nodes), remaps IDs on conflict.", {
|
|
1124
1193
|
source_path: z.string().describe("Path to .hmem file to import"),
|
|
1125
1194
|
store: z.enum(["personal", "company"]).default("personal").describe("Target store: 'personal' (your own memory) or 'company' (shared company store)"),
|
|
1126
|
-
dry_run: z.boolean().default(false).describe("Preview only — report what would happen without modifying the database"),
|
|
1195
|
+
dry_run: z.coerce.boolean().default(false).describe("Preview only — report what would happen without modifying the database"),
|
|
1127
1196
|
}, async ({ source_path, store: storeName, dry_run }) => {
|
|
1128
1197
|
try {
|
|
1129
1198
|
const hmemStore = storeName === "company"
|
|
@@ -1311,30 +1380,51 @@ server.tool("load_project", "Load a project and activate it. Returns L2 content
|
|
|
1311
1380
|
if (e.level_1 && e.level_1 !== e.title)
|
|
1312
1381
|
lines.push(` ${e.level_1}`);
|
|
1313
1382
|
if (e.children) {
|
|
1383
|
+
const { withBody, withChildren } = hmemConfig.loadProjectExpand;
|
|
1314
1384
|
for (const child of e.children.filter(c => !c.irrelevant)) {
|
|
1315
1385
|
const cId = child.id.replace(e.id, "");
|
|
1316
|
-
const
|
|
1386
|
+
const expandBody = withBody.includes(child.seq);
|
|
1387
|
+
const expandChildTitles = withChildren.includes(child.seq);
|
|
1317
1388
|
lines.push(` ${cId} ${child.title || child.content.substring(0, 60)}`);
|
|
1318
1389
|
if (child.children && child.children.length > 0) {
|
|
1319
1390
|
for (const gc of child.children.filter((g) => !g.irrelevant)) {
|
|
1320
1391
|
const gcId = gc.id.replace(e.id, "");
|
|
1321
|
-
if (
|
|
1322
|
-
//
|
|
1323
|
-
lines.push(` ${gcId} ${gc.content}`);
|
|
1392
|
+
if (expandBody) {
|
|
1393
|
+
// Show L3 title + body content
|
|
1394
|
+
lines.push(` ${gcId} ${gc.title || gc.content.substring(0, 80)}`);
|
|
1395
|
+
if (gc.content && gc.content !== gc.title) {
|
|
1396
|
+
// Show body lines indented
|
|
1397
|
+
for (const bodyLine of gc.content.split("\n")) {
|
|
1398
|
+
lines.push(` ${bodyLine}`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
else if (expandChildTitles) {
|
|
1403
|
+
// Show all L3 children as titles
|
|
1404
|
+
const gcTitle = gc.title || (gc.content.length > 80 ? gc.content.substring(0, 80) : gc.content);
|
|
1405
|
+
lines.push(` ${gcId} ${gcTitle}`);
|
|
1324
1406
|
}
|
|
1325
1407
|
else {
|
|
1326
|
-
//
|
|
1408
|
+
// Default: compact titles only
|
|
1327
1409
|
const gcTitle = gc.title || (gc.content.length > 80 ? gc.content.substring(0, 80) : gc.content);
|
|
1328
1410
|
lines.push(` ${gcId} ${gcTitle}`);
|
|
1329
1411
|
}
|
|
1330
|
-
// L4
|
|
1331
|
-
if (gc.
|
|
1332
|
-
|
|
1412
|
+
// L4 children titles (already loaded via depth=4)
|
|
1413
|
+
if (gc.children && gc.children.length > 0) {
|
|
1414
|
+
const visibleL4 = gc.children.filter((l4) => !l4.irrelevant);
|
|
1415
|
+
for (const l4 of visibleL4) {
|
|
1416
|
+
const l4Id = l4.id.replace(e.id, "");
|
|
1417
|
+
const l4Title = l4.title || (l4.content?.length > 60 ? l4.content.substring(0, 60) + "…" : l4.content || "");
|
|
1418
|
+
lines.push(` ${l4Id} ${l4Title}`);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
else if (gc.child_count && gc.child_count > 0) {
|
|
1422
|
+
lines.push(` [+${gc.child_count}]`);
|
|
1333
1423
|
}
|
|
1334
1424
|
}
|
|
1335
1425
|
}
|
|
1336
1426
|
else if (child.child_count && child.child_count > 0) {
|
|
1337
|
-
lines.push(` [+${child.child_count}
|
|
1427
|
+
lines.push(` [+${child.child_count}]`);
|
|
1338
1428
|
}
|
|
1339
1429
|
}
|
|
1340
1430
|
}
|
|
@@ -1369,14 +1459,31 @@ server.tool("load_project", "Load a project and activate it. Returns L2 content
|
|
|
1369
1459
|
lines.push(` ${r.id} ${r.title}`);
|
|
1370
1460
|
}
|
|
1371
1461
|
}
|
|
1372
|
-
// Inject recent O-
|
|
1462
|
+
// Inject the most recent O-entry linked to this project with last N exchanges
|
|
1463
|
+
// Purpose: seamless continuation of the previous session's conversation
|
|
1373
1464
|
if (hmemConfig.recentOEntries > 0) {
|
|
1374
|
-
const { text, ids } = formatRecentOEntries(hmemStore, hmemConfig.recentOEntries,
|
|
1465
|
+
const { text, ids } = formatRecentOEntries(hmemStore, 1, hmemConfig.recentOEntries, id, true);
|
|
1375
1466
|
if (text) {
|
|
1376
1467
|
lines.push(" " + text.replace(/\n/g, "\n "));
|
|
1377
1468
|
sessionCache.registerDelivered(ids);
|
|
1378
1469
|
}
|
|
1379
1470
|
}
|
|
1471
|
+
// Inject universal conventions (C-entries tagged #universal)
|
|
1472
|
+
try {
|
|
1473
|
+
const conventions = hmemStore.read({
|
|
1474
|
+
prefix: "C", depth: 2, agentRole: (ROLE || "worker"),
|
|
1475
|
+
}).filter(c => !c.obsolete && !c.irrelevant && c.tags?.includes("#universal"));
|
|
1476
|
+
if (conventions.length > 0) {
|
|
1477
|
+
lines.push(" Conventions (#universal):");
|
|
1478
|
+
for (const c of conventions) {
|
|
1479
|
+
lines.push(` ${c.id} ${c.title}`);
|
|
1480
|
+
if (c.level_1 && c.level_1 !== c.title)
|
|
1481
|
+
lines.push(` ${c.level_1}`);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
catch { /* conventions are optional */ }
|
|
1486
|
+
const irrelevantTip = `Tip: update_memory(id, { irrelevant: true }) to hide noisy entries from future loads.`;
|
|
1380
1487
|
const output = lines.join("\n");
|
|
1381
1488
|
const outputTokens = Math.round(output.length / 4);
|
|
1382
1489
|
const totalStats = hmemStore.stats();
|
|
@@ -1390,7 +1497,7 @@ server.tool("load_project", "Load a project and activate it. Returns L2 content
|
|
|
1390
1497
|
return trackTokens({
|
|
1391
1498
|
content: [{
|
|
1392
1499
|
type: "text",
|
|
1393
|
-
text: `✓ Project ${id} activated.${tokenInfo}\n\n${output}`,
|
|
1500
|
+
text: `✓ Project ${id} activated.${tokenInfo}\n${irrelevantTip}\n\n${output}\n\n${irrelevantTip}`,
|
|
1394
1501
|
}],
|
|
1395
1502
|
});
|
|
1396
1503
|
}
|
|
@@ -1595,7 +1702,7 @@ server.tool("get_audit_queue", "CURATOR ONLY (ceo role). Returns agents whose .h
|
|
|
1595
1702
|
"Each agent should be audited in a separate spawn to keep context bounded.", {}, async () => {
|
|
1596
1703
|
if (!isCurator()) {
|
|
1597
1704
|
return {
|
|
1598
|
-
content: [{ type: "text", text: "ERROR: get_audit_queue is only available to the ceo/curator role." }],
|
|
1705
|
+
content: [{ type: "text", text: "ERROR: get_audit_queue is only available to the ceo/curator role. Set HMEM_AGENT_ROLE=ceo in your MCP server config to use curation tools." }],
|
|
1599
1706
|
isError: true,
|
|
1600
1707
|
};
|
|
1601
1708
|
}
|
|
@@ -1681,8 +1788,11 @@ server.tool("read_agent_memory", "CURATOR ONLY (ceo role). Read the full memory
|
|
|
1681
1788
|
const favTag = e.favorite ? " [♥]" : "";
|
|
1682
1789
|
lines.push(`[${e.id}] ${date}${role}${favTag}${obsoleteTag}${irrelevantTag}${access}`);
|
|
1683
1790
|
lines.push(` ${e.title}`);
|
|
1684
|
-
if (e.level_1 !== e.title)
|
|
1685
|
-
|
|
1791
|
+
if (e.level_1 && e.level_1 !== e.title) {
|
|
1792
|
+
for (const bodyLine of e.level_1.split("\n")) {
|
|
1793
|
+
lines.push(` ${bodyLine}`);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1686
1796
|
if (e.children && e.children.length > 0) {
|
|
1687
1797
|
for (const child of e.children) {
|
|
1688
1798
|
const indent = " ".repeat(child.depth - 1);
|
|
@@ -1716,10 +1826,10 @@ server.tool("fix_agent_memory", "CURATOR ONLY (ceo role). Correct a specific ent
|
|
|
1716
1826
|
content: z.string().optional().describe("New text content. For root entries: replaces the L1 summary. " +
|
|
1717
1827
|
"For node IDs: replaces that node's content."),
|
|
1718
1828
|
min_role: z.enum(["worker", "al", "pl", "ceo"]).optional().describe("Update access clearance (root entries only)."),
|
|
1719
|
-
obsolete: z.boolean().optional().describe("Mark or unmark as obsolete (root entries only). " +
|
|
1829
|
+
obsolete: z.coerce.boolean().optional().describe("Mark or unmark as obsolete (root entries only). " +
|
|
1720
1830
|
"Obsolete entries stay in memory but are shown with [⚠ OBSOLETE]."),
|
|
1721
|
-
favorite: z.boolean().optional().describe("Set or clear the [♥] favorite flag (root entries only)."),
|
|
1722
|
-
irrelevant: z.boolean().optional().describe("Mark or unmark as irrelevant (root entries only). Irrelevant entries are hidden from bulk reads. No correction entry needed."),
|
|
1831
|
+
favorite: z.coerce.boolean().optional().describe("Set or clear the [♥] favorite flag (root entries only)."),
|
|
1832
|
+
irrelevant: z.coerce.boolean().optional().describe("Mark or unmark as irrelevant (root entries only). Irrelevant entries are hidden from bulk reads. No correction entry needed."),
|
|
1723
1833
|
}, async ({ agent_name, entry_id, content, min_role, obsolete, favorite, irrelevant }) => {
|
|
1724
1834
|
if (!isCurator()) {
|
|
1725
1835
|
return {
|
|
@@ -1961,7 +2071,7 @@ function formatTitlesOnly(entries, config, curator = false) {
|
|
|
1961
2071
|
lines.push(` ${compactChildId}${cfav} ${short}${grandchildren}`);
|
|
1962
2072
|
}
|
|
1963
2073
|
if (e.hiddenChildrenCount && e.hiddenChildrenCount > 0) {
|
|
1964
|
-
lines.push(` [+${e.hiddenChildrenCount} more
|
|
2074
|
+
lines.push(` [+${e.hiddenChildrenCount} more]`);
|
|
1965
2075
|
}
|
|
1966
2076
|
if (hiddenIrr > 0) {
|
|
1967
2077
|
lines.push(` (+${hiddenIrr} irrelevant hidden)`);
|
|
@@ -2068,9 +2178,11 @@ function renderEntryFormatted(lines, e, curator, expand = false) {
|
|
|
2068
2178
|
else {
|
|
2069
2179
|
lines.push(`${e.id} ${e.title}${tagStr}`);
|
|
2070
2180
|
}
|
|
2071
|
-
// Node drilldown: show
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2181
|
+
// Node drilldown: show body below title
|
|
2182
|
+
if (e.level_1 && e.level_1 !== e.title) {
|
|
2183
|
+
for (const bodyLine of e.level_1.split("\n")) {
|
|
2184
|
+
lines.push(` ${bodyLine}`);
|
|
2185
|
+
}
|
|
2074
2186
|
}
|
|
2075
2187
|
}
|
|
2076
2188
|
else {
|
|
@@ -2096,9 +2208,11 @@ function renderEntryFormatted(lines, e, curator, expand = false) {
|
|
|
2096
2208
|
const syncTag = syncThreshold && e.updated_at && e.updated_at <= syncThreshold ? " ✓" : "";
|
|
2097
2209
|
lines.push(`${e.id}${promotedTag}${activeTag}${pinnedTag}${obsoleteTag}${irrelevantTag}${syncTag} ${e.title}${tagStr}`);
|
|
2098
2210
|
}
|
|
2099
|
-
// Show
|
|
2100
|
-
if (
|
|
2101
|
-
|
|
2211
|
+
// Show body below title when entry is drilled into
|
|
2212
|
+
if (e.level_1 && e.level_1 !== e.title) {
|
|
2213
|
+
for (const bodyLine of e.level_1.split("\n")) {
|
|
2214
|
+
lines.push(` ${bodyLine}`);
|
|
2215
|
+
}
|
|
2102
2216
|
}
|
|
2103
2217
|
}
|
|
2104
2218
|
// Children — filter out irrelevant nodes
|
|
@@ -2114,7 +2228,7 @@ function renderEntryFormatted(lines, e, curator, expand = false) {
|
|
|
2114
2228
|
else if (e.expanded && !expand) {
|
|
2115
2229
|
renderChildrenFormatted(lines, visibleChildren, curator, rootId);
|
|
2116
2230
|
if (e.hiddenChildrenCount && e.hiddenChildrenCount > 0) {
|
|
2117
|
-
lines.push(` [+${e.hiddenChildrenCount} more
|
|
2231
|
+
lines.push(` [+${e.hiddenChildrenCount} more]`);
|
|
2118
2232
|
}
|
|
2119
2233
|
}
|
|
2120
2234
|
else if (e.hiddenChildrenCount !== undefined) {
|
|
@@ -2124,7 +2238,7 @@ function renderEntryFormatted(lines, e, curator, expand = false) {
|
|
|
2124
2238
|
const fav = nodeMarkers(child);
|
|
2125
2239
|
const compactChildId = child.id.replace(rootId, "");
|
|
2126
2240
|
const hint = (child.child_count ?? 0) > 0
|
|
2127
|
-
? ` [+${child.child_count}
|
|
2241
|
+
? ` [+${child.child_count}]`
|
|
2128
2242
|
: "";
|
|
2129
2243
|
if (curator) {
|
|
2130
2244
|
lines.push(` [${child.id}]${fav} ${child.title}${hint}`);
|
|
@@ -2134,7 +2248,7 @@ function renderEntryFormatted(lines, e, curator, expand = false) {
|
|
|
2134
2248
|
}
|
|
2135
2249
|
}
|
|
2136
2250
|
if (e.hiddenChildrenCount > 0) {
|
|
2137
|
-
lines.push(` [+${e.hiddenChildrenCount} more
|
|
2251
|
+
lines.push(` [+${e.hiddenChildrenCount} more]`);
|
|
2138
2252
|
}
|
|
2139
2253
|
}
|
|
2140
2254
|
else {
|
|
@@ -2202,7 +2316,7 @@ function renderChildrenFormatted(lines, children, curator, rootId) {
|
|
|
2202
2316
|
const ctags = formatTagSuffix(child.tags, curator);
|
|
2203
2317
|
const compactId = rootId ? child.id.replace(rootId, "") : child.id;
|
|
2204
2318
|
const hint = (child.child_count ?? 0) > 0
|
|
2205
|
-
? ` [+${child.child_count}
|
|
2319
|
+
? ` [+${child.child_count}]`
|
|
2206
2320
|
: "";
|
|
2207
2321
|
if (curator) {
|
|
2208
2322
|
lines.push(`${indent}[${child.id}]${fav} ${child.title}${ctags}${hint}`);
|
|
@@ -2221,24 +2335,31 @@ function renderChildrenFormatted(lines, children, curator, rootId) {
|
|
|
2221
2335
|
function renderChildrenExpanded(lines, children, curator, rootId) {
|
|
2222
2336
|
for (const child of children) {
|
|
2223
2337
|
const indent = " ".repeat(child.depth - 1);
|
|
2338
|
+
const bodyIndent = indent + " ";
|
|
2224
2339
|
const fav = nodeMarkers(child);
|
|
2225
2340
|
const compactId = rootId ? child.id.replace(rootId, "") : child.id;
|
|
2226
2341
|
const visibleGrandchildren = child.children?.filter(c => !c.irrelevant);
|
|
2227
2342
|
const hasLoadedChildren = visibleGrandchildren && visibleGrandchildren.length > 0;
|
|
2228
2343
|
const isBoundary = !hasLoadedChildren && (child.child_count ?? 0) > 0;
|
|
2344
|
+
const hasBody = child.content && child.content !== child.title;
|
|
2229
2345
|
if (hasLoadedChildren) {
|
|
2230
|
-
// Inner node:
|
|
2346
|
+
// Inner node: title + body + recurse
|
|
2231
2347
|
if (curator) {
|
|
2232
|
-
lines.push(`${indent}[${child.id}]${fav} ${child.
|
|
2348
|
+
lines.push(`${indent}[${child.id}]${fav} ${child.title}`);
|
|
2233
2349
|
}
|
|
2234
2350
|
else {
|
|
2235
|
-
lines.push(`${indent}${compactId}${fav} ${child.
|
|
2351
|
+
lines.push(`${indent}${compactId}${fav} ${child.title}`);
|
|
2352
|
+
}
|
|
2353
|
+
if (hasBody) {
|
|
2354
|
+
for (const bodyLine of child.content.split("\n")) {
|
|
2355
|
+
lines.push(`${bodyIndent}${bodyLine}`);
|
|
2356
|
+
}
|
|
2236
2357
|
}
|
|
2237
2358
|
renderChildrenExpanded(lines, visibleGrandchildren, curator, rootId);
|
|
2238
2359
|
}
|
|
2239
2360
|
else if (isBoundary) {
|
|
2240
2361
|
// Boundary: title only + child count hint
|
|
2241
|
-
const hint = ` [+${child.child_count}
|
|
2362
|
+
const hint = ` [+${child.child_count}]`;
|
|
2242
2363
|
if (curator) {
|
|
2243
2364
|
lines.push(`${indent}[${child.id}]${fav} ${child.title}${hint}`);
|
|
2244
2365
|
}
|
|
@@ -2247,12 +2368,17 @@ function renderChildrenExpanded(lines, children, curator, rootId) {
|
|
|
2247
2368
|
}
|
|
2248
2369
|
}
|
|
2249
2370
|
else {
|
|
2250
|
-
// Leaf node
|
|
2371
|
+
// Leaf node: title + body
|
|
2251
2372
|
if (curator) {
|
|
2252
|
-
lines.push(`${indent}[${child.id}]${fav} ${child.
|
|
2373
|
+
lines.push(`${indent}[${child.id}]${fav} ${child.title}`);
|
|
2253
2374
|
}
|
|
2254
2375
|
else {
|
|
2255
|
-
lines.push(`${indent}${compactId}${fav} ${child.
|
|
2376
|
+
lines.push(`${indent}${compactId}${fav} ${child.title}`);
|
|
2377
|
+
}
|
|
2378
|
+
if (hasBody) {
|
|
2379
|
+
for (const bodyLine of child.content.split("\n")) {
|
|
2380
|
+
lines.push(`${bodyIndent}${bodyLine}`);
|
|
2381
|
+
}
|
|
2256
2382
|
}
|
|
2257
2383
|
}
|
|
2258
2384
|
}
|
|
@@ -2277,6 +2403,7 @@ function checkForUpdates() {
|
|
|
2277
2403
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2278
2404
|
detached: true,
|
|
2279
2405
|
shell: process.platform === "win32",
|
|
2406
|
+
windowsHide: true,
|
|
2280
2407
|
});
|
|
2281
2408
|
child.unref();
|
|
2282
2409
|
let out = "";
|