@exaudeus/memory-mcp 1.9.7 → 1.9.9

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.
@@ -1,4 +1,4 @@
1
- import type { MemoryConfig } from './types.js';
1
+ import type { MemoryConfig, BehaviorConfig } from './types.js';
2
2
  import { type LoadedConfig, type ConfigOrigin } from './config.js';
3
3
  import { MarkdownMemoryStore } from './store.js';
4
4
  /** Health status for a lobe (matches existing LobeHealth in index.ts) */
@@ -27,6 +27,7 @@ export declare class ConfigManager {
27
27
  private stores;
28
28
  private lobeHealth;
29
29
  private configMtime;
30
+ private behaviorConfig?;
30
31
  /** Cached alwaysInclude lobe names — recomputed atomically on reload. */
31
32
  private cachedAlwaysIncludeLobes;
32
33
  protected statFile(path: string): Promise<{
@@ -55,6 +56,7 @@ export declare class ConfigManager {
55
56
  getLobeNames(): readonly string[];
56
57
  getLobeHealth(lobe: string): LobeHealth | undefined;
57
58
  getConfigOrigin(): ConfigOrigin;
59
+ getBehaviorConfig(): BehaviorConfig | undefined;
58
60
  getLobeConfig(lobe: string): MemoryConfig | undefined;
59
61
  /** Returns lobe names where alwaysInclude is true. Cached; rebuilt atomically on hot-reload. */
60
62
  getAlwaysIncludeLobes(): readonly string[];
@@ -28,6 +28,7 @@ export class ConfigManager {
28
28
  this.stores = initialStores;
29
29
  this.lobeHealth = initialHealth;
30
30
  this.configMtime = Date.now(); // Initial mtime (will be updated on first stat)
31
+ this.behaviorConfig = initial.behavior;
31
32
  this.cachedAlwaysIncludeLobes = ConfigManager.computeAlwaysIncludeLobes(this.lobeConfigs);
32
33
  }
33
34
  /** Derive alwaysInclude lobe names from config — pure, no side effects. */
@@ -114,6 +115,7 @@ export class ConfigManager {
114
115
  this.stores = newStores;
115
116
  this.lobeHealth = newHealth;
116
117
  this.configMtime = newMtime;
118
+ this.behaviorConfig = newConfig.behavior;
117
119
  this.cachedAlwaysIncludeLobes = ConfigManager.computeAlwaysIncludeLobes(newConfig.configs);
118
120
  const lobeCount = newConfig.configs.size;
119
121
  const degradedCount = Array.from(newHealth.values()).filter(h => h.status === 'degraded').length;
@@ -139,6 +141,9 @@ export class ConfigManager {
139
141
  getConfigOrigin() {
140
142
  return this.configOrigin;
141
143
  }
144
+ getBehaviorConfig() {
145
+ return this.behaviorConfig;
146
+ }
142
147
  getLobeConfig(lobe) {
143
148
  return this.lobeConfigs.get(lobe);
144
149
  }
package/dist/index.js CHANGED
@@ -497,7 +497,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
497
497
  lobes: lobeInfo,
498
498
  alwaysIncludeLobes: alwaysIncludeNames,
499
499
  configFile: configFileDisplay(),
500
- configSource: configOrigin.source,
500
+ configSource: configManager.getConfigOrigin().source,
501
501
  totalLobes: lobeInfo.length,
502
502
  degradedLobes: lobeInfo.filter((l) => l.health === 'degraded').length,
503
503
  };
@@ -778,8 +778,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
778
778
  }
779
779
  const ctx = resolveToolContext(effectiveLobe);
780
780
  if (!ctx.ok) {
781
+ const availableLobes = configManager.getLobeNames();
782
+ if (availableLobes.length === 0) {
783
+ return {
784
+ content: [{ type: 'text', text: `No lobes configured for preferences. Run memory_bootstrap(lobe: "your-project-name", root: "/absolute/path/to/repo") first, then store the preference with prefer(rule: "...", lobe: "<your-lobe>").` }],
785
+ isError: true,
786
+ };
787
+ }
781
788
  return {
782
- content: [{ type: 'text', text: `No global lobe configured for preferences. Specify a lobe: prefer(rule: "...", lobe: "...").\nAvailable: ${configManager.getLobeNames().join(', ')}` }],
789
+ content: [{ type: 'text', text: `No global lobe configured for preferences. Specify a lobe: prefer(rule: "...", lobe: "...").\nAvailable: ${availableLobes.join(', ')}` }],
783
790
  isError: true,
784
791
  };
785
792
  }
@@ -825,8 +832,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
825
832
  }
826
833
  }
827
834
  if (!effectiveFixLobe) {
835
+ const availableLobes = configManager.getLobeNames();
836
+ if (availableLobes.length === 0) {
837
+ return {
838
+ content: [{ type: 'text', text: `No lobes configured. Run memory_bootstrap(lobe: "your-project-name", root: "/absolute/path/to/repo") first, then retry fix with the entry ID.` }],
839
+ isError: true,
840
+ };
841
+ }
828
842
  return {
829
- content: [{ type: 'text', text: `Entry "${id}" not found in any lobe. Available: ${configManager.getLobeNames().join(', ')}` }],
843
+ content: [{ type: 'text', text: `Entry "${id}" not found in any lobe. Available: ${availableLobes.join(', ')}` }],
830
844
  isError: true,
831
845
  };
832
846
  }
@@ -1260,6 +1274,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1260
1274
  sections.push(crashSection);
1261
1275
  if (degradedSection)
1262
1276
  sections.push(degradedSection);
1277
+ if (allBriefingLobeNames.length === 0) {
1278
+ sections.push('## No Lobes Configured\n\n' +
1279
+ 'No memory lobes exist yet. Run **memory_bootstrap** to create one for this project:\n\n' +
1280
+ '```\nmemory_bootstrap(lobe: "your-project-name", root: "/absolute/path/to/repo")\n```\n\n' +
1281
+ 'After bootstrapping, call **memory_context** again to load project context.');
1282
+ return { content: [{ type: 'text', text: sections.join('\n\n---\n\n') }] };
1283
+ }
1263
1284
  // Collect briefing, stale entries, and entry counts across all lobes
1264
1285
  // (alwaysInclude lobes are in the lobe list — no separate global store query needed)
1265
1286
  const allStale = [];
@@ -1472,6 +1493,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1472
1493
  // Combined stats across all lobes
1473
1494
  const sections = [];
1474
1495
  const allLobeNames = configManager.getLobeNames();
1496
+ if (allLobeNames.length === 0) {
1497
+ return {
1498
+ content: [{ type: 'text', text: 'No lobes configured. Run memory_bootstrap(lobe: "your-project-name", root: "/absolute/path/to/repo") first, then retry memory_stats.' }],
1499
+ };
1500
+ }
1475
1501
  const alwaysIncludeSet = new Set(configManager.getAlwaysIncludeLobes());
1476
1502
  for (const lobeName of allLobeNames) {
1477
1503
  const store = configManager.getStore(lobeName);
@@ -1487,7 +1513,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1487
1513
  const { lobe: rawLobe } = z.object({
1488
1514
  lobe: z.string().optional(),
1489
1515
  }).parse(args ?? {});
1490
- const lobeName = rawLobe ?? lobeNames[0];
1516
+ const currentLobeNames = configManager.getLobeNames();
1517
+ const lobeName = rawLobe ?? (currentLobeNames.length === 1 ? currentLobeNames[0] : undefined);
1491
1518
  const ctx = resolveToolContext(lobeName);
1492
1519
  if (!ctx.ok)
1493
1520
  return contextError(ctx);
@@ -1550,10 +1577,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1550
1577
  await configManager.ensureFresh();
1551
1578
  }
1552
1579
  }
1553
- // Resolve store after this point, rawLobe is never used again
1554
- const ctx = resolveToolContext(rawLobe);
1555
- if (!ctx.ok)
1556
- return contextError(ctx);
1580
+ const effectiveLobe = rawLobe ?? (configManager.getLobeNames().length === 1 ? configManager.getLobeNames()[0] : undefined);
1581
+ if (!effectiveLobe) {
1582
+ return {
1583
+ content: [{
1584
+ type: 'text',
1585
+ text: `memory_bootstrap requires a lobe when multiple lobes exist. Available: ${configManager.getLobeNames().join(', ')}`,
1586
+ }],
1587
+ isError: true,
1588
+ };
1589
+ }
1590
+ const store = configManager.getStore(effectiveLobe);
1591
+ if (!store) {
1592
+ const origin = configManager.getConfigOrigin();
1593
+ const hint = origin.source === 'file'
1594
+ ? `Check ${origin.path} and confirm lobe "${effectiveLobe}" exists and points at a valid repo root.`
1595
+ : `Run memory_bootstrap(lobe: "${effectiveLobe}", root: "/absolute/path/to/repo") again to create the lobe.`;
1596
+ return {
1597
+ content: [{
1598
+ type: 'text',
1599
+ text: `Bootstrap could not resolve lobe "${effectiveLobe}" after config update. ${hint}`,
1600
+ }],
1601
+ isError: true,
1602
+ };
1603
+ }
1604
+ const ctx = { ok: true, store, label: effectiveLobe };
1557
1605
  const results = await ctx.store.bootstrap();
1558
1606
  const stored = results.filter((r) => r.kind === 'stored');
1559
1607
  const failed = results.filter((r) => r.kind !== 'stored');
@@ -1701,7 +1749,7 @@ async function buildDiagnosticsText(showFullCrashHistory) {
1701
1749
  sections.push('');
1702
1750
  // Active behavior config — shows effective values and highlights user overrides
1703
1751
  sections.push('### Active Behavior Config');
1704
- sections.push(formatBehaviorConfigSection(configBehavior));
1752
+ sections.push(formatBehaviorConfigSection(configManager.getBehaviorConfig()));
1705
1753
  sections.push('');
1706
1754
  const latestCrash = await readLatestCrash();
1707
1755
  if (latestCrash) {
@@ -1784,8 +1832,10 @@ async function main() {
1784
1832
  await writeCrashReport(report).catch(() => { });
1785
1833
  }
1786
1834
  }
1787
- // Determine server mode based on lobe health
1788
- if (healthyLobes === 0) {
1835
+ // Determine server mode based on lobe health.
1836
+ // Zero configured lobes is a valid operational state: tools should stay available
1837
+ // so the agent can bootstrap the first project lobe.
1838
+ if (lobeConfigs.size > 0 && healthyLobes === 0) {
1789
1839
  serverMode = {
1790
1840
  kind: 'safe-mode',
1791
1841
  error: `All ${lobeConfigs.size} lobes failed to initialize.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exaudeus/memory-mcp",
3
- "version": "1.9.7",
3
+ "version": "1.9.9",
4
4
  "description": "Codebase memory MCP server - persistent, evolving knowledge for AI coding agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",