Fix project filter and update export/import docs

Critical bug fix:
- Pass project filter to getSessionSummariesByIds() and getUserPromptsByIds() in SearchManager
- Previously only observations were filtered by project, sessions and prompts leaked from other projects

Documentation improvements:
- Update "FTS5 search" to "hybrid search" (accurate terminology)
- Add privacy warning about sensitive data in exports
- Document --project parameter for filtered exports
- Add "Export by Project" examples to advanced usage

Verified with test export using --project=claude-mem filter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2025-12-10 20:38:21 -05:00
parent 4e7ed75fa9
commit 5b338ba34e
3 changed files with 19 additions and 6 deletions

View File

@@ -920,7 +920,7 @@ MEMORY PROCESSING CONTINUED
`,n=[];t&&(i+=" WHERE s.project = ?",n.push(t)),i+=" ORDER BY up.created_at_epoch DESC LIMIT ? OFFSET ?",n.push(r+1,e);let l=s.prepare(i).all(...n);return{items:l.slice(0,r),hasMore:l.length>r,offset:e,limit:r}}paginate(e,r,t,s,i){let n=this.dbManager.getSessionStore().db,o=`SELECT ${r} FROM ${e}`,l=[];i&&(o+=" WHERE project = ?",l.push(i)),o+=" ORDER BY created_at_epoch DESC LIMIT ? OFFSET ?",l.push(s+1,t);let u=n.prepare(o).all(...l);return{items:u.slice(0,s),hasMore:u.length>s,offset:t,limit:s}}};xt();var Rl=class{dbManager;defaultSettings={sidebarOpen:!0,selectedProject:null,theme:"system"};constructor(e){this.dbManager=e}getSettings(){let e=this.dbManager.getSessionStore().db;try{let t=e.prepare("SELECT key, value FROM viewer_settings").all(),s={...this.defaultSettings};for(let i of t){let n=i.key;n in s&&(s[n]=JSON.parse(i.value))}return s}catch(r){return V.debug("WORKER","Failed to load settings, using defaults",{},r),{...this.defaultSettings}}}updateSettings(e){let t=this.dbManager.getSessionStore().db.prepare(`
INSERT OR REPLACE INTO viewer_settings (key, value)
VALUES (?, ?)
`);for(let[s,i]of Object.entries(e))t.run(s,JSON.stringify(i));return this.getSettings()}};var Uw=require("path");var Pl=class{constructor(e,r,t,s,i){this.sessionSearch=e;this.sessionStore=r;this.chromaSync=t;this.formatter=s;this.timelineService=i}async queryChroma(e,r,t){return await this.chromaSync.queryChroma(e,r,t)}normalizeParams(e){let r={...e};return r.concepts&&typeof r.concepts=="string"&&(r.concepts=r.concepts.split(",").map(t=>t.trim()).filter(Boolean)),r.files&&typeof r.files=="string"&&(r.files=r.files.split(",").map(t=>t.trim()).filter(Boolean)),r.obs_type&&typeof r.obs_type=="string"&&(r.obs_type=r.obs_type.split(",").map(t=>t.trim()).filter(Boolean)),r.type&&typeof r.type=="string"&&r.type.includes(",")&&(r.type=r.type.split(",").map(t=>t.trim()).filter(Boolean)),(r.dateStart||r.dateEnd)&&(r.dateRange={start:r.dateStart,end:r.dateEnd},delete r.dateStart,delete r.dateEnd),r}async search(e){try{let r=this.normalizeParams(e),{query:t,format:s="index",type:i,obs_type:n,concepts:o,files:l,...c}=r,u=[],p=[],m=[],d=!i||i==="observations",v=!i||i==="sessions",h=!i||i==="prompts";if(t)if(this.chromaSync){let w=!1;try{ae(`[mcp-server] Using ChromaDB semantic search (type filter: ${i||"all"})`);let P;i==="observations"?P={doc_type:"observation"}:i==="sessions"?P={doc_type:"session_summary"}:i==="prompts"&&(P={doc_type:"user_prompt"});let T=await this.queryChroma(t,100,P);if(w=!0,ae(`[mcp-server] ChromaDB returned ${T.ids.length} semantic matches`),T.ids.length>0){let k=Date.now()-7776e6,j=T.metadatas.map((C,O)=>({id:T.ids[O],meta:C,isRecent:C&&C.created_at_epoch>k})).filter(C=>C.isRecent);ae(`[mcp-server] ${j.length} results within 90-day window`);let I=[],$=[],N=[];for(let C of j){let O=C.meta?.doc_type;O==="observation"&&d?I.push(C.id):O==="session_summary"&&v?$.push(C.id):O==="user_prompt"&&h&&N.push(C.id)}if(ae(`[mcp-server] Categorized: ${I.length} obs, ${$.length} sessions, ${N.length} prompts`),I.length>0){let C={...c,type:n,concepts:o,files:l};u=this.sessionStore.getObservationsByIds(I,C)}$.length>0&&(p=this.sessionStore.getSessionSummariesByIds($,{orderBy:"date_desc",limit:c.limit})),N.length>0&&(m=this.sessionStore.getUserPromptsByIds(N,{orderBy:"date_desc",limit:c.limit})),ae(`[mcp-server] Hydrated ${u.length} obs, ${p.length} sessions, ${m.length} prompts from SQLite`)}else ae("[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)")}catch(P){ae("[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):",P.message),ae("[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/"),u=[],p=[],m=[]}}else ae("[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)"),ae("[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/"),u=[],p=[],m=[];else{ae("[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)");let w={...c,type:n,concepts:o,files:l};d&&(u=this.sessionSearch.searchObservations(void 0,w)),v&&(p=this.sessionSearch.searchSessions(void 0,c)),h&&(m=this.sessionSearch.searchUserPrompts(void 0,c))}let f=u.length+p.length+m.length;if(f===0)return s==="json"?{observations:[],sessions:[],prompts:[]}:{content:[{type:"text",text:`No results found matching "${t}"`}]};let y=[...u.map(w=>({type:"observation",data:w,epoch:w.created_at_epoch})),...p.map(w=>({type:"session",data:w,epoch:w.created_at_epoch})),...m.map(w=>({type:"prompt",data:w,epoch:w.created_at_epoch}))];c.orderBy==="date_desc"?y.sort((w,P)=>P.epoch-w.epoch):c.orderBy==="date_asc"&&y.sort((w,P)=>w.epoch-P.epoch);let g=y.slice(0,c.limit||20);if(s==="json")return{observations:u,sessions:p,prompts:m};let b;if(s==="index"){let w=`Found ${f} result(s) matching "${t}" (${u.length} obs, ${p.length} sessions, ${m.length} prompts):
`);for(let[s,i]of Object.entries(e))t.run(s,JSON.stringify(i));return this.getSettings()}};var Uw=require("path");var Pl=class{constructor(e,r,t,s,i){this.sessionSearch=e;this.sessionStore=r;this.chromaSync=t;this.formatter=s;this.timelineService=i}async queryChroma(e,r,t){return await this.chromaSync.queryChroma(e,r,t)}normalizeParams(e){let r={...e};return r.concepts&&typeof r.concepts=="string"&&(r.concepts=r.concepts.split(",").map(t=>t.trim()).filter(Boolean)),r.files&&typeof r.files=="string"&&(r.files=r.files.split(",").map(t=>t.trim()).filter(Boolean)),r.obs_type&&typeof r.obs_type=="string"&&(r.obs_type=r.obs_type.split(",").map(t=>t.trim()).filter(Boolean)),r.type&&typeof r.type=="string"&&r.type.includes(",")&&(r.type=r.type.split(",").map(t=>t.trim()).filter(Boolean)),(r.dateStart||r.dateEnd)&&(r.dateRange={start:r.dateStart,end:r.dateEnd},delete r.dateStart,delete r.dateEnd),r}async search(e){try{let r=this.normalizeParams(e),{query:t,format:s="index",type:i,obs_type:n,concepts:o,files:l,...c}=r,u=[],p=[],m=[],d=!i||i==="observations",v=!i||i==="sessions",h=!i||i==="prompts";if(t)if(this.chromaSync){let w=!1;try{ae(`[mcp-server] Using ChromaDB semantic search (type filter: ${i||"all"})`);let P;i==="observations"?P={doc_type:"observation"}:i==="sessions"?P={doc_type:"session_summary"}:i==="prompts"&&(P={doc_type:"user_prompt"});let T=await this.queryChroma(t,100,P);if(w=!0,ae(`[mcp-server] ChromaDB returned ${T.ids.length} semantic matches`),T.ids.length>0){let k=Date.now()-7776e6,j=T.metadatas.map((C,O)=>({id:T.ids[O],meta:C,isRecent:C&&C.created_at_epoch>k})).filter(C=>C.isRecent);ae(`[mcp-server] ${j.length} results within 90-day window`);let I=[],$=[],N=[];for(let C of j){let O=C.meta?.doc_type;O==="observation"&&d?I.push(C.id):O==="session_summary"&&v?$.push(C.id):O==="user_prompt"&&h&&N.push(C.id)}if(ae(`[mcp-server] Categorized: ${I.length} obs, ${$.length} sessions, ${N.length} prompts`),I.length>0){let C={...c,type:n,concepts:o,files:l};u=this.sessionStore.getObservationsByIds(I,C)}$.length>0&&(p=this.sessionStore.getSessionSummariesByIds($,{orderBy:"date_desc",limit:c.limit,project:c.project})),N.length>0&&(m=this.sessionStore.getUserPromptsByIds(N,{orderBy:"date_desc",limit:c.limit,project:c.project})),ae(`[mcp-server] Hydrated ${u.length} obs, ${p.length} sessions, ${m.length} prompts from SQLite`)}else ae("[mcp-server] ChromaDB found no matches (this is final - NOT falling back to FTS5)")}catch(P){ae("[mcp-server] ChromaDB failed - returning empty results (FTS5 fallback removed):",P.message),ae("[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/"),u=[],p=[],m=[]}}else ae("[mcp-server] ChromaDB not initialized - returning empty results (FTS5 fallback removed)"),ae("[mcp-server] Install UVX/Python to enable vector search: https://docs.astral.sh/uv/getting-started/installation/"),u=[],p=[],m=[];else{ae("[mcp-server] Filter-only query (no query text), using direct SQLite filtering (enables date filters)");let w={...c,type:n,concepts:o,files:l};d&&(u=this.sessionSearch.searchObservations(void 0,w)),v&&(p=this.sessionSearch.searchSessions(void 0,c)),h&&(m=this.sessionSearch.searchUserPrompts(void 0,c))}let f=u.length+p.length+m.length;if(f===0)return s==="json"?{observations:[],sessions:[],prompts:[]}:{content:[{type:"text",text:`No results found matching "${t}"`}]};let y=[...u.map(w=>({type:"observation",data:w,epoch:w.created_at_epoch})),...p.map(w=>({type:"session",data:w,epoch:w.created_at_epoch})),...m.map(w=>({type:"prompt",data:w,epoch:w.created_at_epoch}))];c.orderBy==="date_desc"?y.sort((w,P)=>P.epoch-w.epoch):c.orderBy==="date_asc"&&y.sort((w,P)=>w.epoch-P.epoch);let g=y.slice(0,c.limit||20);if(s==="json")return{observations:u,sessions:p,prompts:m};let b;if(s==="index"){let w=`Found ${f} result(s) matching "${t}" (${u.length} obs, ${p.length} sessions, ${m.length} prompts):
`,P=g.map((T,k)=>T.type==="observation"?this.formatter.formatObservationIndex(T.data,k):T.type==="session"?this.formatter.formatSessionIndex(T.data,k):this.formatter.formatUserPromptIndex(T.data,k));b=w+P.join(`