@xultrax-web/agent-memory-mcp 0.11.0 → 0.11.2

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.
Files changed (3) hide show
  1. package/README.md +41 -7
  2. package/dist/index.js +309 -15
  3. package/package.json +10 -2
package/README.md CHANGED
@@ -27,17 +27,51 @@ agent-memory save-rule no-emojis-ever \
27
27
  --enforce-on commits,chat_responses \
28
28
  --content "No emojis. Anywhere. Ever."
29
29
 
30
- agent-memory emit-companions # writes ./AGENTS.md
30
+ agent-memory emit-companions
31
+ # writes AGENTS.md + CLAUDE.md + .cursor/rules/*.mdc + .gemini/instructions.md
32
+ # (v0.11.1 — all four targets · use --target agents,claude to filter)
31
33
  ```
32
34
 
33
- Set `AGENT_MEMORY_AUTO_EMIT_DIR=/path/to/project` to auto-regenerate companions on every rule save.
35
+ Companion file targets (v0.11.1):
34
36
 
35
- Roadmap for the v0.11.x series:
37
+ | Target | Path | Auto-loaded by |
38
+ | -------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
39
+ | `agents` | `AGENTS.md` | Claude Code, Codex CLI, Cursor, Aider, Devin, Copilot, Gemini CLI, Windsurf, Amazon Q |
40
+ | `claude` | `CLAUDE.md` | Claude Code (5-level hierarchy · managed/global/project/local/subdir) |
41
+ | `cursor` | `.cursor/rules/operator-hard.mdc` (`alwaysApply: true`) + `operator-conventions.mdc` (agent-requested) | Cursor (MDC format) |
42
+ | `gemini` | `.gemini/instructions.md` | Gemini CLI |
36
43
 
37
- - `CLAUDE.md` + `.cursor/rules/*.mdc` + `.gemini/instructions.md` emitters (per-tool native formats)
38
- - Compliance Receipts (Macaroon-style HMAC tokens · protocol-level enforcement of our own destructive tools)
39
- - `check_action` tool (deterministic rule matching · optional Sampling enrichment where clients support it)
40
- - `audit` command (rule conflicts · staleness · receipt-denial log)
44
+ Set `AGENT_MEMORY_AUTO_EMIT_DIR=/path/to/project` to auto-regenerate all companions on every rule save.
45
+
46
+ ### Compliance Receipts (v0.11.2 · primitive · tool wiring in v0.11.3)
47
+
48
+ Receipts are short-lived, HMAC-signed bearer tokens with caveats (Macaroon pattern · [Birgisson et al., NDSS 2014](https://research.google/pubs/pub41892/)). The novel protocol primitive in agent-memory-mcp: server-issued tokens that bind to action + session + rules-version-hash + expiry. Tampering breaks the HMAC. Rule changes invalidate stale receipts (because `rules_version` is part of the signed payload).
49
+
50
+ ```typescript
51
+ import { issueReceipt, validateReceipt } from "@xultrax-web/agent-memory-mcp";
52
+
53
+ // Server-internal: issue a receipt for a destructive action
54
+ const r = issueReceipt({
55
+ caveats: [
56
+ { type: "action", value: "delete_memory" },
57
+ { type: "session", value: "sess_abc123" },
58
+ ],
59
+ ttl_seconds: 60,
60
+ });
61
+
62
+ // Later: validate before executing the destructive op
63
+ const v = validateReceipt(r, {
64
+ required_caveats: [{ type: "action", value: "delete_memory" }],
65
+ });
66
+ if (!v.valid) throw new Error(v.reason);
67
+ ```
68
+
69
+ HMAC key lives at `<MEMORY_DIR>/.keyring/hmac-key` · 32 random bytes · mode `0600`. v0.11.3 wires receipts into `delete_memory` + other destructive tools and adds the `check_action` MCP tool.
70
+
71
+ ### Roadmap for the v0.11.x series:
72
+
73
+ - `check_action` tool · deterministic rule matching · optional Sampling enrichment where clients support it · issues Compliance Receipts when proposed action passes
74
+ - `audit` command · rule conflicts · staleness · receipt-denial log
41
75
 
42
76
  ---
43
77
 
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema
27
27
  import Fuse from "fuse.js";
28
28
  import matter from "gray-matter";
29
29
  import { spawnSync } from "node:child_process";
30
+ import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
30
31
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
31
32
  import { homedir } from "node:os";
32
33
  import { join, resolve } from "node:path";
@@ -1022,6 +1023,7 @@ function buildAgentsMdContent(rules) {
1022
1023
  }
1023
1024
  return parts.join("\n");
1024
1025
  }
1026
+ export const ALL_COMPANION_TARGETS = ["agents", "claude", "cursor", "gemini"];
1025
1027
  function resolveCompanionDir(explicit) {
1026
1028
  if (explicit && explicit.trim().length > 0)
1027
1029
  return explicit.trim();
@@ -1030,22 +1032,137 @@ function resolveCompanionDir(explicit) {
1030
1032
  return envOverride.trim();
1031
1033
  return process.cwd();
1032
1034
  }
1035
+ // CLAUDE.md content · same body as AGENTS.md but with a Claude-Code-specific
1036
+ // header. Claude Code's 5-level hierarchy (managed/global/project/local/subdir)
1037
+ // reads any CLAUDE.md it finds; we generate the project-root file by default.
1038
+ function buildClaudeMdContent(rules) {
1039
+ const body = buildAgentsMdContent(rules);
1040
+ // Replace the AGENTS.md-specific header sentence with a CLAUDE.md one
1041
+ return body.replace(/^# Operator rules\n/, `# Operator rules · Claude Code\n\n> This is your CLAUDE.md — Claude Code reads it on session start.\n\n`);
1042
+ }
1043
+ // .gemini/instructions.md content · same body as AGENTS.md, slightly different
1044
+ // header.
1045
+ function buildGeminiInstructionsContent(rules) {
1046
+ const body = buildAgentsMdContent(rules);
1047
+ return body.replace(/^# Operator rules\n/, `# Operator rules · Gemini CLI\n\n> Loaded by Gemini CLI from .gemini/instructions.md on session start.\n\n`);
1048
+ }
1049
+ // Cursor consumes .cursor/rules/*.mdc files with their own YAML frontmatter.
1050
+ // Per spec: each file <150 lines, alwaysApply file <50 lines, dir total <500.
1051
+ // Strategy: one file per severity (hard / soft) — hard is alwaysApply, soft
1052
+ // is description-driven so the agent pulls it in when relevant.
1053
+ function buildCursorMdcFiles(rules) {
1054
+ const hard = rules.filter((r) => r.severity === "hard");
1055
+ const soft = rules.filter((r) => r.severity !== "hard");
1056
+ const files = [];
1057
+ if (hard.length > 0) {
1058
+ const fm = [
1059
+ "---",
1060
+ `description: "Operator hard rules · always obey · auto-generated from agent-memory-mcp"`,
1061
+ "alwaysApply: true",
1062
+ "---",
1063
+ "",
1064
+ "# Operator hard rules",
1065
+ "",
1066
+ "These rules MUST be obeyed. Violations should be flagged and blocked.",
1067
+ "",
1068
+ ].join("\n");
1069
+ files.push({
1070
+ filename: "operator-hard.mdc",
1071
+ content: fm + hard.map((r) => formatRuleAsMarkdown(r)).join("\n"),
1072
+ });
1073
+ }
1074
+ if (soft.length > 0) {
1075
+ const fm = [
1076
+ "---",
1077
+ `description: "Operator conventions · prefer to obey · pulled in by agent on relevance"`,
1078
+ "alwaysApply: false",
1079
+ "---",
1080
+ "",
1081
+ "# Operator conventions",
1082
+ "",
1083
+ "Soft rules · prefer to obey. The agent may consult these when the context warrants.",
1084
+ "",
1085
+ ].join("\n");
1086
+ files.push({
1087
+ filename: "operator-conventions.mdc",
1088
+ content: fm + soft.map((r) => formatRuleAsMarkdown(r)).join("\n"),
1089
+ });
1090
+ }
1091
+ return files;
1092
+ }
1033
1093
  function emitCompanions(opts = {}) {
1034
1094
  const outDir = resolveCompanionDir(opts.outDir);
1095
+ const targets = opts.targets && opts.targets.length > 0 ? opts.targets : ALL_COMPANION_TARGETS;
1035
1096
  const rules = loadAllRules();
1036
- const agentsPath = join(outDir, "AGENTS.md");
1037
- const content = buildAgentsMdContent(rules);
1038
- // Best-effort directory creation (companion dir may not exist if user
1039
- // passes a fresh path); mkdirSync is idempotent with recursive:true.
1097
+ // Best-effort directory creation; mkdirSync recursive is idempotent.
1040
1098
  try {
1041
1099
  mkdirSync(outDir, { recursive: true });
1042
1100
  }
1043
1101
  catch {
1044
- // Ignore — atomicWriteFile will surface a clearer error if needed.
1102
+ // Ignore — atomicWriteFile surfaces a clearer error if needed.
1103
+ }
1104
+ const emitted = [];
1105
+ if (targets.includes("agents")) {
1106
+ const fp = join(outDir, "AGENTS.md");
1107
+ atomicWriteFile(fp, buildAgentsMdContent(rules));
1108
+ emitted.push(fp);
1109
+ }
1110
+ if (targets.includes("claude")) {
1111
+ const fp = join(outDir, "CLAUDE.md");
1112
+ atomicWriteFile(fp, buildClaudeMdContent(rules));
1113
+ emitted.push(fp);
1114
+ }
1115
+ if (targets.includes("cursor")) {
1116
+ const cursorDir = join(outDir, ".cursor", "rules");
1117
+ try {
1118
+ mkdirSync(cursorDir, { recursive: true });
1119
+ }
1120
+ catch {
1121
+ // Ignore — atomicWriteFile surfaces clearer error if needed.
1122
+ }
1123
+ const files = buildCursorMdcFiles(rules);
1124
+ if (files.length === 0) {
1125
+ // No rules yet — drop a placeholder so the tool knows where to put them
1126
+ const placeholderPath = join(cursorDir, "operator-rules.mdc");
1127
+ const placeholder = [
1128
+ "---",
1129
+ `description: "Operator rules · auto-generated · no rules defined yet"`,
1130
+ "alwaysApply: false",
1131
+ "---",
1132
+ "",
1133
+ "No rules defined yet. Run `agent-memory save-rule` to add the first one.",
1134
+ "",
1135
+ ].join("\n");
1136
+ atomicWriteFile(placeholderPath, placeholder);
1137
+ emitted.push(placeholderPath);
1138
+ }
1139
+ else {
1140
+ for (const f of files) {
1141
+ const fp = join(cursorDir, f.filename);
1142
+ atomicWriteFile(fp, f.content);
1143
+ emitted.push(fp);
1144
+ }
1145
+ }
1045
1146
  }
1046
- atomicWriteFile(agentsPath, content);
1047
- logEvent("emit_companions", { outDir, rules_count: rules.length, files: ["AGENTS.md"] });
1048
- return { outDir, emitted: [agentsPath], rules_count: rules.length };
1147
+ if (targets.includes("gemini")) {
1148
+ const geminiDir = join(outDir, ".gemini");
1149
+ try {
1150
+ mkdirSync(geminiDir, { recursive: true });
1151
+ }
1152
+ catch {
1153
+ // Ignore — atomicWriteFile surfaces clearer error if needed.
1154
+ }
1155
+ const fp = join(geminiDir, "instructions.md");
1156
+ atomicWriteFile(fp, buildGeminiInstructionsContent(rules));
1157
+ emitted.push(fp);
1158
+ }
1159
+ logEvent("emit_companions", {
1160
+ outDir,
1161
+ rules_count: rules.length,
1162
+ targets,
1163
+ files: emitted.map((p) => p.replace(outDir, "").replace(/^[\\/]/, "")),
1164
+ });
1165
+ return { outDir, emitted, rules_count: rules.length, targets };
1049
1166
  }
1050
1167
  function maybeAutoEmitCompanions() {
1051
1168
  const autoDir = process.env.AGENT_MEMORY_AUTO_EMIT_DIR;
@@ -1063,11 +1180,20 @@ function maybeAutoEmitCompanions() {
1063
1180
  }
1064
1181
  function toolEmitCompanions(args) {
1065
1182
  const outDir = typeof args.out_dir === "string" ? args.out_dir : undefined;
1066
- const r = emitCompanions({ outDir });
1183
+ let targets;
1184
+ if (Array.isArray(args.targets)) {
1185
+ targets = args.targets
1186
+ .filter((t) => typeof t === "string")
1187
+ .filter((t) => ALL_COMPANION_TARGETS.includes(t));
1188
+ if (targets.length === 0)
1189
+ targets = undefined;
1190
+ }
1191
+ const r = emitCompanions({ outDir, targets });
1192
+ const files = r.emitted.map((p) => p.replace(r.outDir, "").replace(/^[\\/]/, "")).join(", ");
1067
1193
  if (r.rules_count === 0) {
1068
- return `Emitted ${r.emitted[0]} with no rules yet · run save_rule to add the first one.`;
1194
+ return `Emitted ${r.emitted.length} placeholder file(s) to ${r.outDir} (${files}) · no rules yet · run save_rule to add the first one.`;
1069
1195
  }
1070
- return `Emitted ${r.rules_count} rule${r.rules_count === 1 ? "" : "s"} to ${r.emitted.join(", ")}.`;
1196
+ return `Emitted ${r.rules_count} rule${r.rules_count === 1 ? "" : "s"} across ${r.targets.length} target${r.targets.length === 1 ? "" : "s"} (${r.targets.join(", ")}) → ${r.emitted.length} file${r.emitted.length === 1 ? "" : "s"} at ${r.outDir}: ${files}`;
1071
1197
  }
1072
1198
  function toolListRules(_args) {
1073
1199
  const rules = loadAllRules();
@@ -1145,6 +1271,154 @@ function toolSaveRule(args) {
1145
1271
  });
1146
1272
  }
1147
1273
  // -------------------------------------------------------------
1274
+ // Compliance Receipts · v0.11.2 · the novel protocol primitive
1275
+ // -------------------------------------------------------------
1276
+ //
1277
+ // Receipts are short-lived, HMAC-signed bearer tokens with caveats
1278
+ // (attenuations). Macaroon-style. Issued by `check_action` (v0.11.3),
1279
+ // validated before our own destructive tools execute. Prior art:
1280
+ //
1281
+ // Birgisson et al · "Macaroons: Cookies with Contextual Caveats for
1282
+ // Decentralized Authorization in the Cloud" · Google Research,
1283
+ // NDSS 2014 · https://research.google/pubs/pub41892/
1284
+ //
1285
+ // Why receipts work where MCP Sampling doesn't:
1286
+ // - MCP Sampling is unsupported on Claude Code / Cursor / Cline /
1287
+ // Codex CLI (the primary coding clients) per the MCP client matrix.
1288
+ // - Receipts are server-issued protocol artifacts — they work on every
1289
+ // client because the server controls both ends (issue + validate).
1290
+ // - Receipts bind to: action + session + rules-version-hash + expiry.
1291
+ // Tampering breaks the HMAC. Rule changes invalidate stale receipts.
1292
+ //
1293
+ // Storage:
1294
+ // HMAC key lives at <MEMORY_DIR>/.keyring/hmac-key · 32 random bytes
1295
+ // created on first use with mode 0o600 (owner read/write only).
1296
+ // Caller-rotatable via `agent-memory rotate-key` (a v0.11.x follow-up).
1297
+ //
1298
+ // v0.11.2 ships the PRIMITIVE only — issuance + validation +
1299
+ // canonicalization. Tool wiring (delete_memory + check_action) lands
1300
+ // in v0.11.3.
1301
+ const KEYRING_DIR = join(MEMORY_DIR, ".keyring");
1302
+ const HMAC_KEY_FILE = join(KEYRING_DIR, "hmac-key");
1303
+ const RECEIPT_DEFAULT_TTL_SECONDS = 60;
1304
+ function loadOrCreateHmacKey() {
1305
+ if (existsSync(HMAC_KEY_FILE)) {
1306
+ return readFileSync(HMAC_KEY_FILE);
1307
+ }
1308
+ if (!existsSync(KEYRING_DIR)) {
1309
+ mkdirSync(KEYRING_DIR, { recursive: true });
1310
+ }
1311
+ const key = randomBytes(32); // 256 bits · plenty for HMAC-SHA256
1312
+ // mode 0o600 is owner-only on POSIX; Windows ignores mode but ACLs
1313
+ // default to the user, so practically equivalent for our threat model.
1314
+ writeFileSync(HMAC_KEY_FILE, key, { mode: 0o600 });
1315
+ return key;
1316
+ }
1317
+ /**
1318
+ * Compute the rules-version hash · first 16 hex chars of SHA-256 over
1319
+ * the concatenated bytes of every type=rule memory file, in sorted
1320
+ * filename order. Any rule add / edit / remove changes this hash,
1321
+ * which invalidates outstanding receipts (they were issued against a
1322
+ * different rule set).
1323
+ */
1324
+ function computeRulesVersion() {
1325
+ const rules = loadAllRules();
1326
+ const sortedPaths = rules.map((r) => r.filePath).sort();
1327
+ const hash = createHash("sha256");
1328
+ for (const fp of sortedPaths) {
1329
+ try {
1330
+ hash.update(readFileSync(fp));
1331
+ }
1332
+ catch {
1333
+ // File disappeared between listMemoryFiles + read; skip it.
1334
+ // Next computation will reflect the change.
1335
+ }
1336
+ }
1337
+ return hash.digest("hex").slice(0, 16);
1338
+ }
1339
+ /**
1340
+ * Deterministic canonical form for HMAC input · caveats sorted by
1341
+ * (type, value) so the order in which the caller listed them doesn't
1342
+ * change the signature. JSON with no whitespace (single line) so the
1343
+ * exact byte sequence is reproducible across platforms.
1344
+ */
1345
+ function canonicalizeReceipt(r) {
1346
+ const sortedCaveats = [...r.caveats].sort((a, b) => a.type === b.type ? a.value.localeCompare(b.value) : a.type.localeCompare(b.type));
1347
+ return JSON.stringify({
1348
+ id: r.id,
1349
+ issued_at: r.issued_at,
1350
+ expires_at: r.expires_at,
1351
+ rules_version: r.rules_version,
1352
+ caveats: sortedCaveats,
1353
+ });
1354
+ }
1355
+ function signReceipt(r) {
1356
+ const key = loadOrCreateHmacKey();
1357
+ return createHmac("sha256", key).update(canonicalizeReceipt(r)).digest("hex");
1358
+ }
1359
+ /**
1360
+ * Issue a fresh Compliance Receipt with the given caveats. The receipt
1361
+ * is bound to the current rule-store hash; any rule edit invalidates it.
1362
+ */
1363
+ export function issueReceipt(opts) {
1364
+ const now = Math.floor(Date.now() / 1000);
1365
+ const ttl = Math.max(1, opts.ttl_seconds ?? RECEIPT_DEFAULT_TTL_SECONDS);
1366
+ const base = {
1367
+ id: "rcpt_" + randomBytes(8).toString("hex"),
1368
+ issued_at: now,
1369
+ expires_at: now + ttl,
1370
+ rules_version: computeRulesVersion(),
1371
+ caveats: opts.caveats,
1372
+ };
1373
+ return { ...base, signature: signReceipt(base) };
1374
+ }
1375
+ /**
1376
+ * Validate a Compliance Receipt against the current rule store + caller's
1377
+ * required caveats. Returns {valid: true} on success, otherwise
1378
+ * {valid: false, reason: <human-readable>}.
1379
+ */
1380
+ export function validateReceipt(receipt, opts = {}) {
1381
+ // 1. HMAC verification · constant-time compare to avoid timing leaks.
1382
+ const expected = signReceipt({
1383
+ id: receipt.id,
1384
+ issued_at: receipt.issued_at,
1385
+ expires_at: receipt.expires_at,
1386
+ rules_version: receipt.rules_version,
1387
+ caveats: receipt.caveats,
1388
+ });
1389
+ const expectedBuf = Buffer.from(expected, "hex");
1390
+ const actualBuf = Buffer.from(receipt.signature, "hex");
1391
+ if (expectedBuf.length !== actualBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) {
1392
+ return { valid: false, reason: "invalid signature" };
1393
+ }
1394
+ // 2. Expiry · receipts past their expires_at are dead.
1395
+ const now = Math.floor(Date.now() / 1000);
1396
+ if (now > receipt.expires_at) {
1397
+ return { valid: false, reason: "receipt expired" };
1398
+ }
1399
+ // 3. Rules-version binding · any rule edit since issuance invalidates.
1400
+ const currentRulesVersion = opts.current_rules_version ?? computeRulesVersion();
1401
+ if (receipt.rules_version !== currentRulesVersion) {
1402
+ return {
1403
+ valid: false,
1404
+ reason: `rules changed since receipt issued (was ${receipt.rules_version}, now ${currentRulesVersion})`,
1405
+ };
1406
+ }
1407
+ // 4. Required-caveat check · each required pair must appear on the receipt.
1408
+ if (opts.required_caveats) {
1409
+ for (const required of opts.required_caveats) {
1410
+ const found = receipt.caveats.find((c) => c.type === required.type && c.value === required.value);
1411
+ if (!found) {
1412
+ return {
1413
+ valid: false,
1414
+ reason: `missing required caveat: ${required.type}=${required.value}`,
1415
+ };
1416
+ }
1417
+ }
1418
+ }
1419
+ return { valid: true };
1420
+ }
1421
+ // -------------------------------------------------------------
1148
1422
  // Git sync · multi-machine memory via git remote
1149
1423
  // -------------------------------------------------------------
1150
1424
  //
@@ -1466,7 +1740,7 @@ function actionColor(action) {
1466
1740
  // -------------------------------------------------------------
1467
1741
  // Server wiring
1468
1742
  // -------------------------------------------------------------
1469
- const server = new Server({ name: "agent-memory", version: "0.11.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
1743
+ const server = new Server({ name: "agent-memory", version: "0.11.2" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
1470
1744
  // -------------------------------------------------------------
1471
1745
  // Resource URI scheme
1472
1746
  // -------------------------------------------------------------
@@ -1964,13 +2238,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1964
2238
  },
1965
2239
  {
1966
2240
  name: "emit_companions",
1967
- description: "Regenerate companion rule files (AGENTS.md) from the current rule memories. Writes to the directory in `out_dir`, the AGENT_MEMORY_COMPANION_DIR env var, or the current working directory in that priority order. AGENTS.md is the universal cross-tool standard (Linux Foundation / Agentic AI Foundation).",
2241
+ description: "Regenerate companion rule files from the current rule memories. " +
2242
+ "Writes one or more of: AGENTS.md (universal cross-tool standard, Linux Foundation), " +
2243
+ "CLAUDE.md (Claude Code's 5-level hierarchy), .cursor/rules/*.mdc (Cursor's MDC format · hard rules get alwaysApply:true, soft rules become description-driven), " +
2244
+ ".gemini/instructions.md (Gemini CLI). " +
2245
+ "Default writes ALL four targets. Use `targets` to filter. Output dir resolves from `out_dir`, then AGENT_MEMORY_COMPANION_DIR env, then process.cwd().",
1968
2246
  inputSchema: {
1969
2247
  type: "object",
1970
2248
  properties: {
1971
2249
  out_dir: {
1972
2250
  type: "string",
1973
- description: "Optional output directory. Defaults to AGENT_MEMORY_COMPANION_DIR env var, then process.cwd().",
2251
+ description: "Optional output directory. Defaults to AGENT_MEMORY_COMPANION_DIR env, then process.cwd().",
2252
+ },
2253
+ targets: {
2254
+ type: "array",
2255
+ items: {
2256
+ type: "string",
2257
+ enum: ["agents", "claude", "cursor", "gemini"],
2258
+ },
2259
+ description: "Which companion files to emit. Omit (or pass empty) to emit all four. Examples: ['agents'] for AGENTS.md only, ['claude','cursor'] for Claude Code + Cursor.",
1974
2260
  },
1975
2261
  },
1976
2262
  },
@@ -2312,7 +2598,15 @@ async function cliMain(command, rest) {
2312
2598
  }
2313
2599
  case "emit-companions": {
2314
2600
  const out = flags.out ? String(flags.out) : undefined;
2315
- process.stdout.write(toolEmitCompanions({ out_dir: out }) + "\n");
2601
+ const target = flags.target;
2602
+ let targets;
2603
+ if (typeof target === "string" && target.length > 0) {
2604
+ targets = target
2605
+ .split(",")
2606
+ .map((t) => t.trim())
2607
+ .filter((t) => t.length > 0);
2608
+ }
2609
+ process.stdout.write(toolEmitCompanions({ out_dir: out, targets }) + "\n");
2316
2610
  return 0;
2317
2611
  }
2318
2612
  case "ui": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xultrax-web/agent-memory-mcp",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "mcpName": "io.github.xultrax-web/agent-memory-mcp",
5
5
  "description": "Markdown memory for AI agents. Plain files you can read, edit, grep, and commit. Operator-grade storage with atomic writes, file locking, tags, [[wiki-links]], find_related, git-backed multi-machine sync, and an Ink-based TUI.",
6
6
  "type": "module",
@@ -21,7 +21,8 @@
21
21
  "format:check": "prettier --check .",
22
22
  "test": "vitest run",
23
23
  "test:watch": "vitest",
24
- "prepublishOnly": "npm run build"
24
+ "prepublishOnly": "npm run build",
25
+ "prepare": "husky"
25
26
  },
26
27
  "keywords": [
27
28
  "mcp",
@@ -65,11 +66,18 @@
65
66
  "@types/node": "^22.10.2",
66
67
  "@types/proper-lockfile": "^4.1.4",
67
68
  "@types/react": "^19.2.15",
69
+ "husky": "^9.1.7",
70
+ "lint-staged": "^17.0.5",
68
71
  "prettier": "^3.8.3",
69
72
  "typescript": "^5.7.2",
70
73
  "vitest": "^4.1.7"
71
74
  },
72
75
  "engines": {
73
76
  "node": ">=20"
77
+ },
78
+ "lint-staged": {
79
+ "*.{ts,tsx,js,mjs,cjs,json,jsonc,md,yml,yaml}": [
80
+ "prettier --write"
81
+ ]
74
82
  }
75
83
  }