claude-mem-lite 2.5.4 → 2.9.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.
package/install.mjs CHANGED
@@ -1854,6 +1854,75 @@ const RESOURCE_METADATA = {
1854
1854
 
1855
1855
  };
1856
1856
 
1857
+ // ─── Marketing / SEO resources → on_request mode ────────────────────────────
1858
+ // These resources are rarely useful during programming sessions.
1859
+ // They only surface when the user explicitly asks for marketing/SEO help.
1860
+ const MARKETING_ON_REQUEST = new Set([
1861
+ // Skills — marketing domain
1862
+ 'skill:ab-test-setup',
1863
+ 'skill:ad-creative',
1864
+ 'skill:ai-seo',
1865
+ 'skill:analytics-tracking',
1866
+ 'skill:churn-prevention',
1867
+ 'skill:cold-email',
1868
+ 'skill:competitor-alternatives',
1869
+ 'skill:content-strategy',
1870
+ 'skill:copy-editing',
1871
+ 'skill:copywriting',
1872
+ 'skill:email-sequence',
1873
+ 'skill:form-cro',
1874
+ 'skill:free-tool-strategy',
1875
+ 'skill:launch-strategy',
1876
+ 'skill:marketing-ideas',
1877
+ 'skill:marketing-psychology',
1878
+ 'skill:onboarding-cro',
1879
+ 'skill:page-cro',
1880
+ 'skill:paid-ads',
1881
+ 'skill:paywall-upgrade-cro',
1882
+ 'skill:popup-cro',
1883
+ 'skill:pricing-strategy',
1884
+ 'skill:product-marketing-context',
1885
+ 'skill:programmatic-seo',
1886
+ 'skill:referral-program',
1887
+ 'skill:schema-markup',
1888
+ 'skill:mktg-seo-audit',
1889
+ 'skill:signup-flow-cro',
1890
+ 'skill:social-content',
1891
+ // Skills — SEO domain
1892
+ 'skill:seo-audit',
1893
+ 'skill:seo-competitor-pages',
1894
+ 'skill:seo-content',
1895
+ 'skill:seo-geo',
1896
+ 'skill:seo-hreflang',
1897
+ 'skill:seo-images',
1898
+ 'skill:seo-page',
1899
+ 'skill:seo-plan',
1900
+ 'skill:seo-programmatic',
1901
+ 'skill:seo-schema',
1902
+ 'skill:seo-sitemap',
1903
+ 'skill:seo-technical',
1904
+ // Agents — SEO
1905
+ 'agent:seo-content-agent',
1906
+ 'agent:seo-performance-agent',
1907
+ 'agent:seo-schema-agent',
1908
+ 'agent:seo-sitemap-agent',
1909
+ 'agent:seo-technical-agent',
1910
+ 'agent:seo-visual-agent',
1911
+ 'agent:seo-analysis-monitoring',
1912
+ 'agent:seo-content-creation',
1913
+ 'agent:seo-technical-optimization',
1914
+ // Agents — marketing / sales
1915
+ 'agent:content-marketing',
1916
+ 'agent:customer-sales-automation',
1917
+ ]);
1918
+
1919
+ // Stamp recommendation_mode into metadata so both insert and update paths read it
1920
+ for (const key of MARKETING_ON_REQUEST) {
1921
+ if (RESOURCE_METADATA[key]) {
1922
+ RESOURCE_METADATA[key].recommendation_mode = 'on_request';
1923
+ }
1924
+ }
1925
+
1857
1926
  /**
1858
1927
  * Apply curated metadata to existing resource DB entries.
1859
1928
  * Fixes existing installs that have generic name-echo metadata.
@@ -1865,6 +1934,7 @@ function reindexKnownResources(rdb) {
1865
1934
  intent_tags = ?, domain_tags = ?,
1866
1935
  capability_summary = ?, trigger_patterns = ?,
1867
1936
  invocation_name = CASE WHEN ? != '' THEN ? ELSE invocation_name END,
1937
+ recommendation_mode = CASE WHEN ? != '' THEN ? ELSE recommendation_mode END,
1868
1938
  updated_at = datetime('now')
1869
1939
  WHERE type = ? AND name = ?
1870
1940
  `);
@@ -1876,10 +1946,12 @@ function reindexKnownResources(rdb) {
1876
1946
  const type = key.slice(0, sep);
1877
1947
  const name = key.slice(sep + 1);
1878
1948
  const invName = meta.invocation_name || '';
1949
+ const recMode = meta.recommendation_mode || '';
1879
1950
  update.run(
1880
1951
  meta.intent_tags, meta.domain_tags,
1881
1952
  meta.capability_summary, meta.trigger_patterns,
1882
1953
  invName, invName,
1954
+ recMode, recMode,
1883
1955
  type, name
1884
1956
  );
1885
1957
  }
@@ -1897,9 +1969,9 @@ function registerVirtualResources(rdb) {
1897
1969
  const insert = rdb.prepare(`
1898
1970
  INSERT OR IGNORE INTO resources (name, type, status, source, local_path, invocation_name,
1899
1971
  intent_tags, domain_tags, capability_summary, trigger_patterns,
1900
- keywords, tech_stack, use_cases,
1972
+ keywords, tech_stack, use_cases, recommendation_mode,
1901
1973
  created_at, updated_at)
1902
- VALUES (?, ?, 'active', 'preinstalled', '', ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
1974
+ VALUES (?, ?, 'active', 'preinstalled', '', ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
1903
1975
  `);
1904
1976
 
1905
1977
  // Backfill FTS5 fields for existing resources that have empty keywords/tech_stack/use_cases
@@ -1930,6 +2002,7 @@ function registerVirtualResources(rdb) {
1930
2002
  meta.keywords || '',
1931
2003
  meta.tech_stack || '',
1932
2004
  meta.use_cases || '',
2005
+ meta.recommendation_mode || 'proactive',
1933
2006
  );
1934
2007
  count += changes;
1935
2008
 
@@ -1989,11 +2062,11 @@ async function install() {
1989
2062
  const SOURCE_FILES = [
1990
2063
  'server.mjs', 'server-internals.mjs', 'tool-schemas.mjs',
1991
2064
  'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs',
1992
- 'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs',
2065
+ 'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs',
1993
2066
  'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'skill.md',
1994
2067
  'registry.mjs', 'registry-scanner.mjs', 'registry-indexer.mjs',
1995
2068
  'registry-retriever.mjs', 'resource-discovery.mjs',
1996
- 'dispatch.mjs', 'dispatch-inject.mjs', 'dispatch-feedback.mjs', 'dispatch-workflow.mjs',
2069
+ 'dispatch.mjs', 'dispatch-inject.mjs', 'dispatch-feedback.mjs', 'dispatch-patterns.mjs', 'dispatch-workflow.mjs',
1997
2070
  ];
1998
2071
 
1999
2072
  if (IS_DEV) {
@@ -2742,6 +2815,70 @@ function writeSettings(settings) {
2742
2815
  renameSync(tmp, SETTINGS_PATH);
2743
2816
  }
2744
2817
 
2818
+ // ─── Manual Update ───────────────────────────────────────────────────────────
2819
+
2820
+ async function manualUpdate() {
2821
+ console.log('\nclaude-mem-lite update\n');
2822
+
2823
+ // Force check by importing hook-update (bypasses throttle for manual use)
2824
+ const { checkForUpdate, getCurrentVersion } = await import('./hook-update.mjs');
2825
+ log('Checking for updates...');
2826
+ const result = await checkForUpdate();
2827
+
2828
+ if (result?.updated) {
2829
+ ok(`Updated: v${result.from} → v${result.to}`);
2830
+ } else if (result?.updateAvailable) {
2831
+ warn(`v${result.to} available but install failed — try: node install.mjs install`);
2832
+ } else {
2833
+ const ver = getCurrentVersion();
2834
+ ok(`Already up to date (v${ver})`);
2835
+ }
2836
+ console.log('');
2837
+ }
2838
+
2839
+ // ─── Release: Sync Versions ─────────────────────────────────────────────────
2840
+
2841
+ function syncVersions() {
2842
+ console.log('\nclaude-mem-lite release — sync versions\n');
2843
+
2844
+ const pkg = JSON.parse(readFileSync(join(PROJECT_DIR, 'package.json'), 'utf8'));
2845
+ const version = pkg.version;
2846
+ log(`package.json version: ${version}`);
2847
+
2848
+ // Sync plugin.json
2849
+ const pluginJsonPath = join(PROJECT_DIR, '.claude-plugin', 'plugin.json');
2850
+ if (existsSync(pluginJsonPath)) {
2851
+ const pluginJson = JSON.parse(readFileSync(pluginJsonPath, 'utf8'));
2852
+ if (pluginJson.version !== version) {
2853
+ pluginJson.version = version;
2854
+ writeFileSync(pluginJsonPath, JSON.stringify(pluginJson, null, 2) + '\n');
2855
+ ok(`plugin.json: ${pluginJson.version} → ${version}`);
2856
+ } else {
2857
+ ok(`plugin.json: already ${version}`);
2858
+ }
2859
+ } else {
2860
+ warn('plugin.json not found');
2861
+ }
2862
+
2863
+ // Sync marketplace.json
2864
+ const marketJsonPath = join(PROJECT_DIR, '.claude-plugin', 'marketplace.json');
2865
+ if (existsSync(marketJsonPath)) {
2866
+ const marketJson = JSON.parse(readFileSync(marketJsonPath, 'utf8'));
2867
+ const plugin = marketJson.plugins?.[0];
2868
+ if (plugin && plugin.version !== version) {
2869
+ plugin.version = version;
2870
+ writeFileSync(marketJsonPath, JSON.stringify(marketJson, null, 2) + '\n');
2871
+ ok(`marketplace.json: ${plugin.version} → ${version}`);
2872
+ } else if (plugin) {
2873
+ ok(`marketplace.json: already ${version}`);
2874
+ }
2875
+ } else {
2876
+ warn('marketplace.json not found');
2877
+ }
2878
+
2879
+ console.log('');
2880
+ }
2881
+
2745
2882
  // ─── Main ───────────────────────────────────────────────────────────────────
2746
2883
 
2747
2884
  switch (cmd) {
@@ -2757,6 +2894,12 @@ switch (cmd) {
2757
2894
  case 'doctor':
2758
2895
  await doctor();
2759
2896
  break;
2897
+ case 'update':
2898
+ await manualUpdate();
2899
+ break;
2900
+ case 'release':
2901
+ syncVersions();
2902
+ break;
2760
2903
  default:
2761
2904
  if (IS_NPX) {
2762
2905
  // npx claude-mem-lite (no args) → auto install
@@ -2772,6 +2915,8 @@ Usage:
2772
2915
  node install.mjs uninstall --purge Remove and delete all data
2773
2916
  node install.mjs status Show current status
2774
2917
  node install.mjs doctor Diagnose issues
2918
+ node install.mjs update Check for and install updates
2919
+ node install.mjs release Sync version to plugin.json + marketplace.json
2775
2920
 
2776
2921
  npx claude-mem-lite Install via npx (one-liner)
2777
2922
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mem-lite",
3
- "version": "2.5.4",
3
+ "version": "2.9.2",
4
4
  "description": "Lightweight persistent memory system for Claude Code",
5
5
  "type": "module",
6
6
  "engines": {
@@ -33,10 +33,12 @@
33
33
  "hook-episode.mjs",
34
34
  "hook-context.mjs",
35
35
  "hook-handoff.mjs",
36
+ "hook-update.mjs",
36
37
  "haiku-client.mjs",
37
38
  "dispatch.mjs",
38
39
  "dispatch-inject.mjs",
39
40
  "dispatch-feedback.mjs",
41
+ "dispatch-patterns.mjs",
40
42
  "dispatch-workflow.mjs",
41
43
  "registry.mjs",
42
44
  "registry-retriever.mjs",
@@ -1127,6 +1127,19 @@
1127
1127
  "natural"
1128
1128
  ]
1129
1129
  },
1130
+ {
1131
+ "name": "claudeception",
1132
+ "type": "skill",
1133
+ "repo": "https://github.com/blader/Claudeception",
1134
+ "path": ".",
1135
+ "tags": [
1136
+ "meta",
1137
+ "skill-extraction",
1138
+ "learning",
1139
+ "continuous",
1140
+ "autonomous"
1141
+ ]
1142
+ },
1130
1143
  {
1131
1144
  "name": "anthropic-architect",
1132
1145
  "type": "skill",
File without changes
@@ -23,6 +23,7 @@ export const DISPATCH_SYNONYMS = {
23
23
  'plan': ['planning', 'architecture', 'spec', 'blueprint', 'rfc', 'proposal', 'roadmap'],
24
24
  'build': ['compile', 'bundle', 'webpack', 'vite', 'typescript', 'tsc', 'esbuild', 'rollup', 'parcel', 'babel', 'swc', 'transpile'],
25
25
  'lint': ['eslint', 'prettier', 'biome', 'stylelint', 'format', 'style'],
26
+ 'search': ['lookup', 'latest', 'best-practices', 'perplexity'],
26
27
  // Chinese intent mappings
27
28
  '清理': ['refactor', 'clean', 'lint', 'format', 'simplify'],
28
29
  '测试': ['test', 'testing', 'tdd', 'qa', 'spec', 'jest', 'vitest', 'pytest'],
@@ -44,6 +45,7 @@ export const DISPATCH_SYNONYMS = {
44
45
  '打包': ['bundle', 'build', 'webpack', 'vite'],
45
46
  '容器': ['docker', 'container', 'kubernetes', 'infrastructure'],
46
47
  '运维': ['devops', 'infrastructure', 'deploy', 'docker'],
48
+ '搜索': ['search', 'lookup', 'latest', 'perplexity'],
47
49
  };
48
50
 
49
51
  // ─── CJK Tokenization ───────────────────────────────────────────────────────
@@ -98,6 +100,9 @@ const CJK_INTENT_MAP = {
98
100
  '接口': 'api', '路由': 'route',
99
101
  // plan
100
102
  '规划': 'planning', '架构': 'architecture', '方案': 'plan', '设计方案': 'architecture',
103
+ // search — only web/info search, NOT code search (grep/find)
104
+ '联网搜索': 'search', '网上搜索': 'search', '查资料': 'search', '找资料': 'search',
105
+ '搜索最新': 'search', '搜索资料': 'search', '搜索文档': 'search',
101
106
  };
102
107
 
103
108
  // Merge all CJK keys from both maps, longest-first to avoid partial matches
@@ -346,30 +351,30 @@ const COMPOSITE_EXPR = `(
346
351
  bm25(resources_fts, 5.0, 3.0, 3.0, 2.0, 2.0, 1.0, 1.0, 1.0) * 0.4
347
352
  - COALESCE(r.repo_stars * 1.0 / (r.repo_stars + 100.0), 0) * 0.15
348
353
  - (
349
- (r.success_count + 1.0) / (r.recommend_count + 2.0) * 0.5
354
+ (COALESCE(r.success_count, 0) + 1.0) / (COALESCE(r.recommend_count, 0) + 2.0) * 0.5
350
355
  + COALESCE(
351
356
  (SELECT (SUM(CASE WHEN i.outcome='success' THEN 1 ELSE 0 END) + 1.0)
352
357
  / (COUNT(*) + 2.0)
353
358
  FROM invocations i WHERE i.resource_id = r.id
354
359
  AND i.created_at > datetime('now', '-30 days')),
355
- (r.success_count + 1.0) / (r.recommend_count + 2.0)
360
+ (COALESCE(r.success_count, 0) + 1.0) / (COALESCE(r.recommend_count, 0) + 2.0)
356
361
  ) * 0.5
357
362
  ) * 0.15
358
363
  - (
359
- (r.adopt_count + 1.0) / (r.recommend_count + 2.0) * 0.5
364
+ (COALESCE(r.adopt_count, 0) + 1.0) / (COALESCE(r.recommend_count, 0) + 2.0) * 0.5
360
365
  + COALESCE(
361
366
  (SELECT (SUM(CASE WHEN i.adopted=1 THEN 1 ELSE 0 END) + 1.0)
362
367
  / (COUNT(*) + 2.0)
363
368
  FROM invocations i WHERE i.resource_id = r.id
364
369
  AND i.created_at > datetime('now', '-30 days')),
365
- (r.adopt_count + 1.0) / (r.recommend_count + 2.0)
370
+ (COALESCE(r.adopt_count, 0) + 1.0) / (COALESCE(r.recommend_count, 0) + 2.0)
366
371
  ) * 0.5
367
372
  ) * 0.10
368
- - CASE WHEN r.recommend_count < 10
369
- THEN 0.10 * (1.0 - r.recommend_count * 1.0 / 10.0)
373
+ - CASE WHEN COALESCE(r.recommend_count, 0) < 10
374
+ THEN 0.10 * (1.0 - COALESCE(r.recommend_count, 0) * 1.0 / 10.0)
370
375
  ELSE 0 END
371
- + CASE WHEN r.recommend_count > 15
372
- AND (r.adopt_count + 1.0) / (r.recommend_count + 2.0) < 0.1
376
+ + CASE WHEN COALESCE(r.recommend_count, 0) > 8
377
+ AND (COALESCE(r.adopt_count, 0) + 1.0) / (COALESCE(r.recommend_count, 0) + 2.0) < 0.1
373
378
  THEN 0.10
374
379
  ELSE 0 END
375
380
  )`;
File without changes
package/registry.mjs CHANGED
@@ -37,6 +37,9 @@ const RESOURCES_SCHEMA = `
37
37
  recommend_count INTEGER DEFAULT 0,
38
38
  adopt_count INTEGER DEFAULT 0,
39
39
  success_count INTEGER DEFAULT 0,
40
+ silenced_until TEXT,
41
+ cooldown_hours INTEGER DEFAULT 0,
42
+ recommendation_mode TEXT DEFAULT 'proactive',
40
43
  indexed_at TEXT,
41
44
  created_at TEXT DEFAULT (datetime('now')),
42
45
  updated_at TEXT DEFAULT (datetime('now'))
@@ -106,7 +109,7 @@ const INVOCATIONS_SCHEMA = `
106
109
  adopted INTEGER DEFAULT 0,
107
110
  outcome TEXT CHECK(outcome IN ('success','partial','failure','skipped','ignored') OR outcome IS NULL),
108
111
  score REAL,
109
- rejection_reason TEXT CHECK(rejection_reason IN ('alternative','manual','context_switch','session_end','unknown') OR rejection_reason IS NULL),
112
+ rejection_reason TEXT CHECK(rejection_reason IN ('alternative','manual','context_switch','session_end','unknown','no_events','unclassified') OR rejection_reason IS NULL),
110
113
  created_at TEXT DEFAULT (datetime('now'))
111
114
  );
112
115
 
@@ -115,6 +118,9 @@ const INVOCATIONS_SCHEMA = `
115
118
 
116
119
  CREATE INDEX IF NOT EXISTS idx_inv_session
117
120
  ON invocations(session_id);
121
+
122
+ CREATE INDEX IF NOT EXISTS idx_inv_created_at
123
+ ON invocations(created_at);
118
124
  `;
119
125
 
120
126
  const PREINSTALLED_SCHEMA = `
@@ -156,13 +162,15 @@ export function ensureRegistryDb(dbPath) {
156
162
 
157
163
  db.exec(RESOURCES_SCHEMA);
158
164
 
159
- // Migrate: add invocation_name column if missing (safe for existing DBs)
165
+ // Migrate: add missing columns to resources (single PRAGMA call for all)
160
166
  try {
161
- const cols = db.prepare("PRAGMA table_info(resources)").all();
162
- if (!cols.some(c => c.name === 'invocation_name')) {
163
- db.exec("ALTER TABLE resources ADD COLUMN invocation_name TEXT DEFAULT ''");
164
- }
165
- } catch (e) { debugCatch(e, 'invocation_name-migration'); }
167
+ const resCols = new Set(db.prepare("PRAGMA table_info(resources)").all().map(c => c.name));
168
+ if (!resCols.has('invocation_name')) db.exec("ALTER TABLE resources ADD COLUMN invocation_name TEXT DEFAULT ''");
169
+ if (!resCols.has('silenced_until')) db.exec("ALTER TABLE resources ADD COLUMN silenced_until TEXT");
170
+ if (!resCols.has('cooldown_hours')) db.exec("ALTER TABLE resources ADD COLUMN cooldown_hours INTEGER DEFAULT 0");
171
+ // recommendation_mode: 'proactive' (default, actively recommended), 'on_request' (only when explicitly asked)
172
+ if (!resCols.has('recommendation_mode')) db.exec("ALTER TABLE resources ADD COLUMN recommendation_mode TEXT DEFAULT 'proactive'");
173
+ } catch (e) { debugCatch(e, 'resources-column-migration'); }
166
174
 
167
175
  // FTS5 + triggers: only create if not exists
168
176
  const hasFts = db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='resources_fts'`).get();
File without changes
package/schema.mjs CHANGED
File without changes
File without changes
File without changes
package/server.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
6
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
- import { jaccardSimilarity, truncate, typeIcon, sanitizeFtsQuery, relaxFtsQueryToOr, inferProject, computeMinHash, estimateJaccardFromMinHash, scrubSecrets, cjkBigrams, fmtDate, isoWeekKey, debugLog, debugCatch, COMPRESSED_AUTO, COMPRESSED_PENDING_PURGE } from './utils.mjs';
7
+ import { jaccardSimilarity, truncate, typeIcon, sanitizeFtsQuery, relaxFtsQueryToOr, inferProject, computeMinHash, estimateJaccardFromMinHash, scrubSecrets, cjkBigrams, fmtDate, isoWeekKey, debugLog, debugCatch, COMPRESSED_PENDING_PURGE } from './utils.mjs';
8
8
  import { ensureDb, DB_PATH, REGISTRY_DB_PATH } from './schema.mjs';
9
9
  import { reRankWithContext, markSuperseded, extractPRFTerms, expandQueryByConcepts, autoBoostIfNeeded, runIdleCleanup } from './server-internals.mjs';
10
10
  import { memSearchSchema, memTimelineSchema, memGetSchema, memDeleteSchema, memSaveSchema, memStatsSchema, memCompressSchema, memMaintainSchema, memRegistrySchema } from './tool-schemas.mjs';
@@ -56,6 +56,27 @@ function getRegistryDb() {
56
56
 
57
57
  // inferProject, jaccardSimilarity, sanitizeFtsQuery, typeIcon, truncate, fmtDate imported from utils.mjs
58
58
 
59
+ // ─── Project Name Resolution ────────────────────────────────────────────────
60
+ // Users naturally type short names like "mem" but inferProject() stores
61
+ // "projects--mem" (parent--base from CWD). resolveProject() bridges this gap.
62
+
63
+ const _projectCache = new Map();
64
+
65
+ function resolveProject(name) {
66
+ if (!name) return name;
67
+ if (_projectCache.has(name)) return _projectCache.get(name);
68
+ // Already a canonical name (contains "--")? Use as-is.
69
+ if (name.includes('--')) { _projectCache.set(name, name); return name; }
70
+ // Short name: prefer the canonical "parent--name" form (from inferProject())
71
+ // which typically has far more data than manually-saved short names.
72
+ const suffixed = db.prepare(
73
+ 'SELECT project FROM observations WHERE project LIKE ? GROUP BY project ORDER BY COUNT(*) DESC LIMIT 1'
74
+ ).get(`%--${name}`);
75
+ const resolved = suffixed ? suffixed.project : name;
76
+ _projectCache.set(name, resolved);
77
+ return resolved;
78
+ }
79
+
59
80
  // ─── Scoring Model Constants ────────────────────────────────────────────────
60
81
  //
61
82
  // Composite scoring: BM25(weights) × recency_decay × [project_boost] × [importance] × [access_bonus]
@@ -97,8 +118,12 @@ const server = new McpServer(
97
118
  '- Non-obvious debugging discovery → mem_save with type="bugfix"',
98
119
  '- Key architecture decision → mem_save with type="decision"',
99
120
  '- Important pattern or convention found → mem_save with type="discovery"',
121
+ '- Do NOT save: ephemeral task state, git history, obvious code patterns, or info derivable from code.',
100
122
  '',
101
- 'WORKFLOW: mem_search → mem_timeline(anchor=ID) → mem_get(ids=[...]) for full context.',
123
+ 'SEARCH WORKFLOW: mem_search → mem_timeline(anchor=ID) for surrounding context → mem_get(ids=[...]) for full details.',
124
+ 'Search tips: use short keywords (2-3 words), not full sentences. Filter with obs_type when relevant.',
125
+ '',
126
+ 'MAINTENANCE: mem_compress consolidates old observations. mem_maintain runs dedup/cleanup/reindex.',
102
127
  ].join('\n'),
103
128
  },
104
129
  );
@@ -214,15 +239,15 @@ function searchObservations(ctx) {
214
239
  const orQuery = relaxFtsQueryToOr(ftsQuery);
215
240
  if (orQuery) {
216
241
  try {
217
- const orRows = db.prepare(buildObsFtsQuery('full', { multiplier: 0.8, withSnippet: true, withOffset: true }))
242
+ const orRows = db.prepare(buildObsFtsQuery('full', { multiplier: 0.5, withSnippet: true, withOffset: true }))
218
243
  .all(...buildObsFtsParams({ now, projectBoost, ftsQuery: orQuery, args, epochFrom, epochTo, limit: perSourceLimit, offset: perSourceOffset }));
219
244
  for (const r of orRows) results.push(ftsRowToResult(r, { snippet: true }));
220
245
  } catch (e) { debugCatch(e, 'searchObservations-or-fallback'); }
221
246
  }
222
247
  }
223
248
 
224
- // Two-phase query expansion for sparse results
225
- if (rows.length > 0 && results.length < limit) {
249
+ // Two-phase query expansion for sparse results (only when well below limit)
250
+ if (rows.length > 0 && results.length < Math.ceil(limit / 2)) {
226
251
  const existingIds = new Set(results.map(r => r.id));
227
252
  expandObsByConceptCo(ctx, now, existingIds, results);
228
253
  expandObsByPRF(ctx, now, rows.length, existingIds, results);
@@ -404,11 +429,17 @@ function searchPrompts(ctx) {
404
429
 
405
430
  function formatSearchOutput(paginatedResults, args, ftsQuery, totalCount, isCrossSource) {
406
431
  if (paginatedResults.length === 0) {
407
- const hint = ['No results found.'];
408
- if (args.query) {
409
- const expanded = ftsQuery || args.query;
410
- if (expanded !== args.query) hint.push(`Searched as: ${expanded}`);
411
- hint.push('Tip: check spelling, try broader terms, or use mem_stats to see available data.');
432
+ const hint = [];
433
+ if (args.query && !ftsQuery) {
434
+ hint.push(`Query "${args.query}" was filtered (FTS5 keywords/special chars only).`);
435
+ hint.push('Tip: use content words instead of operators (AND, OR, NOT, NEAR).');
436
+ } else {
437
+ hint.push('No results found.');
438
+ if (args.query) {
439
+ const expanded = ftsQuery || args.query;
440
+ if (expanded !== args.query) hint.push(`Searched as: ${expanded}`);
441
+ hint.push('Tip: check spelling, try broader terms, or use mem_stats to see available data.');
442
+ }
412
443
  }
413
444
  return { content: [{ type: 'text', text: hint.join('\n') }] };
414
445
  }
@@ -446,6 +477,7 @@ server.registerTool(
446
477
  inputSchema: memSearchSchema,
447
478
  },
448
479
  safeHandler(async (args) => {
480
+ if (args.project) args = { ...args, project: resolveProject(args.project) };
449
481
  const limit = args.limit ?? 20;
450
482
  const offset = args.offset ?? 0;
451
483
  const ftsQuery = sanitizeFtsQuery(args.query);
@@ -463,8 +495,13 @@ server.registerTool(
463
495
  if (epochTo !== null && args.date_to && /^\d{4}-\d{2}-\d{2}$/.test(args.date_to)) {
464
496
  epochTo += 86400000 - 1; // extend to 23:59:59.999
465
497
  }
466
- if (epochFrom !== null && isNaN(epochFrom)) throw new Error(`Invalid date_from: ${args.date_from}`);
467
- if (epochTo !== null && isNaN(epochTo)) throw new Error(`Invalid date_to: ${args.date_to}`);
498
+ if (epochFrom !== null && isNaN(epochFrom)) throw new Error(`Invalid date_from: "${args.date_from}" (use ISO 8601 or YYYY-MM-DD)`);
499
+ if (epochTo !== null && isNaN(epochTo)) throw new Error(`Invalid date_to: "${args.date_to}" (use ISO 8601 or YYYY-MM-DD)`);
500
+
501
+ // Early return when query was provided but sanitized to nothing (all FTS5 keywords/special chars)
502
+ if (args.query && !ftsQuery && !epochFrom && !epochTo && !args.obs_type && !args.importance) {
503
+ return formatSearchOutput([], args, ftsQuery, 0, false);
504
+ }
468
505
 
469
506
  const ctx = { ftsQuery, searchType, args, epochFrom, epochTo, perSourceLimit, perSourceOffset, currentProject, limit };
470
507
  const results = [];
@@ -506,6 +543,7 @@ server.registerTool(
506
543
  inputSchema: memTimelineSchema,
507
544
  },
508
545
  safeHandler(async (args) => {
546
+ if (args.project) args = { ...args, project: resolveProject(args.project) };
509
547
  const before = args.before ?? 5;
510
548
  const after = args.after ?? 5;
511
549
  let anchorId = args.anchor;
@@ -705,7 +743,10 @@ server.registerTool(
705
743
  });
706
744
  const result = deleteTx();
707
745
 
708
- return { content: [{ type: 'text', text: `Deleted ${result.changes} observation(s).` }] };
746
+ const missing = args.ids.filter(id => !rows.some(r => r.id === id));
747
+ const msg = [`Deleted ${result.changes} observation(s).`];
748
+ if (missing.length > 0) msg.push(`Note: ID(s) ${missing.join(', ')} not found.`);
749
+ return { content: [{ type: 'text', text: msg.join(' ') }] };
709
750
  })
710
751
  );
711
752
 
@@ -718,6 +759,7 @@ server.registerTool(
718
759
  inputSchema: memSaveSchema,
719
760
  },
720
761
  safeHandler(async (args) => {
762
+ if (args.project) args = { ...args, project: resolveProject(args.project) };
721
763
  const now = new Date();
722
764
  const project = args.project || inferProject();
723
765
  const type = args.type || 'discovery';
@@ -770,6 +812,7 @@ server.registerTool(
770
812
  inputSchema: memStatsSchema,
771
813
  },
772
814
  safeHandler(async (args) => {
815
+ if (args.project) args = { ...args, project: resolveProject(args.project) };
773
816
  const days = args.days ?? 30;
774
817
  const cutoff = Date.now() - days * 86400000;
775
818
  const projectFilter = args.project ? 'AND project = ?' : '';
@@ -865,6 +908,7 @@ server.registerTool(
865
908
  inputSchema: memCompressSchema,
866
909
  },
867
910
  safeHandler(async (args) => {
911
+ if (args.project) args = { ...args, project: resolveProject(args.project) };
868
912
  const preview = args.preview !== false;
869
913
  const ageDays = args.age_days ?? 60;
870
914
  const cutoff = Date.now() - ageDays * 86400000;
@@ -977,6 +1021,7 @@ server.registerTool(
977
1021
  inputSchema: memMaintainSchema,
978
1022
  },
979
1023
  safeHandler(async (args) => {
1024
+ if (args.project) args = { ...args, project: resolveProject(args.project) };
980
1025
  const STALE_AGE_MS = 30 * 86400000;
981
1026
  const SIMILARITY_THRESHOLD = 0.7;
982
1027
  const SCAN_LIMIT = 500;
package/skill.md CHANGED
File without changes