From 8703e0ee13eeea6d222c43d8bdd5e582c1c6e34d Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Sun, 14 Dec 2025 20:55:50 -0500 Subject: [PATCH] refactor: remove redundant legend from search output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove legend from search/timeline results since it's already shown in SessionStart context. Saves ~30 tokens per search result. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- plugin/scripts/worker-service.cjs | 6 +++--- src/services/worker/SearchManager.ts | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/plugin/scripts/worker-service.cjs b/plugin/scripts/worker-service.cjs index eb5723c9..df1f3306 100755 --- a/plugin/scripts/worker-service.cjs +++ b/plugin/scripts/worker-service.cjs @@ -962,7 +962,7 @@ MEMORY PROCESSING CONTINUED 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 t1=require("path");yt();$f();var Nl=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,type:s,obs_type:i,concepts:n,files:o,...l}=r,c=[],u=[],p=[],f=!s||s==="observations",d=!s||s==="sessions",v=!s||s==="prompts";if(t)if(this.chromaSync){let w=!1;try{q.debug("SEARCH","Using ChromaDB semantic search",{typeFilter:s||"all"});let T;s==="observations"?T={doc_type:"observation"}:s==="sessions"?T={doc_type:"session_summary"}:s==="prompts"&&(T={doc_type:"user_prompt"});let k=await this.queryChroma(t,100,T);if(w=!0,q.debug("SEARCH","ChromaDB returned semantic matches",{matchCount:k.ids.length}),k.ids.length>0){let D=Date.now()-7776e6,A=k.metadatas.map((O,j)=>({id:k.ids[j],meta:O,isRecent:O&&O.created_at_epoch>D})).filter(O=>O.isRecent);q.debug("SEARCH","Results within 90-day window",{count:A.length});let $=[],N=[],C=[];for(let O of A){let j=O.meta?.doc_type;j==="observation"&&f?$.push(O.id):j==="session_summary"&&d?N.push(O.id):j==="user_prompt"&&v&&C.push(O.id)}if(q.debug("SEARCH","Categorized results by type",{observations:$.length,sessions:N.length,prompts:C.length}),$.length>0){let O={...l,type:i,concepts:n,files:o};c=this.sessionStore.getObservationsByIds($,O)}N.length>0&&(u=this.sessionStore.getSessionSummariesByIds(N,{orderBy:"date_desc",limit:l.limit,project:l.project})),C.length>0&&(p=this.sessionStore.getUserPromptsByIds(C,{orderBy:"date_desc",limit:l.limit,project:l.project})),q.debug("SEARCH","Hydrated results from SQLite",{observations:c.length,sessions:u.length,prompts:p.length})}else q.debug("SEARCH","ChromaDB found no matches (final result, no FTS5 fallback)",{})}catch(T){q.debug("SEARCH","ChromaDB failed - returning empty results (FTS5 fallback removed)",{error:T.message}),q.debug("SEARCH","Install UVX/Python to enable vector search",{url:"https://docs.astral.sh/uv/getting-started/installation/"}),c=[],u=[],p=[]}}else q.debug("SEARCH","ChromaDB not initialized - returning empty results (FTS5 fallback removed)",{}),q.debug("SEARCH","Install UVX/Python to enable vector search",{url:"https://docs.astral.sh/uv/getting-started/installation/"}),c=[],u=[],p=[];else{q.debug("SEARCH","Filter-only query (no query text), using direct SQLite filtering",{enablesDateFilters:!0});let w={...l,type:i,concepts:n,files:o};f&&(c=this.sessionSearch.searchObservations(void 0,w)),d&&(u=this.sessionSearch.searchSessions(void 0,l)),v&&(p=this.sessionSearch.searchUserPrompts(void 0,l))}let h=c.length+u.length+p.length;if(h===0)return{content:[{type:"text",text:`No results found matching "${t}"`}]};let m=[...c.map(w=>({type:"observation",data:w,epoch:w.created_at_epoch,created_at:w.created_at})),...u.map(w=>({type:"session",data:w,epoch:w.created_at_epoch,created_at:w.created_at})),...p.map(w=>({type:"prompt",data:w,epoch:w.created_at_epoch,created_at:w.created_at}))];l.orderBy==="date_desc"?m.sort((w,T)=>T.epoch-w.epoch):l.orderBy==="date_asc"&&m.sort((w,T)=>w.epoch-T.epoch);let y=m.slice(0,l.limit||20),g=process.cwd(),b=e1(y,w=>w.created_at),R=[];R.push(`Found ${h} result(s) matching "${t}" (${c.length} obs, ${u.length} sessions, ${p.length} prompts)`),R.push("");for(let[w,T]of b){R.push(`### ${w}`),R.push("");let k=new Map;for(let D of T){let A="General";D.type==="observation"&&(A=zs(D.data.files_modified,g)),k.has(A)||k.set(A,[]),k.get(A).push(D)}for(let[D,A]of k){R.push(`**${D}**`),R.push(this.formatter.formatSearchTableHeader());let $="";for(let N of A)if(N.type==="observation"){let C=this.formatter.formatObservationSearchRow(N.data,$);R.push(C.row),$=C.time}else if(N.type==="session"){let C=this.formatter.formatSessionSearchRow(N.data,$);R.push(C.row),$=C.time}else{let C=this.formatter.formatUserPromptSearchRow(N.data,$);R.push(C.row),$=C.time}R.push("")}}return{content:[{type:"text",text:R.join(` -`)}]}}catch(r){return{content:[{type:"text",text:`Search failed: ${r.message}`}],isError:!0}}}async timeline(e){try{let{anchor:r,query:t,depth_before:s=10,depth_after:i=10,project:n}=e,o=process.cwd();if(!r&&!t)return{content:[{type:"text",text:'Error: Must provide either "anchor" or "query" parameter'}],isError:!0};if(r&&t)return{content:[{type:"text",text:'Error: Cannot provide both "anchor" and "query" parameters. Use one or the other.'}],isError:!0};let l,c,u;if(t){let R=[];if(this.chromaSync)try{q.debug("SEARCH","Using hybrid semantic search for timeline query",{});let T=await this.queryChroma(t,100);if(q.debug("SEARCH","Chroma returned semantic matches for timeline",{matchCount:T?.ids?.length??0}),T?.ids&&T.ids.length>0){let k=Date.now()-7776e6,D=T.ids.filter((A,$)=>{let N=T.metadatas[$];return N&&N.created_at_epoch>k});D.length>0&&(R=this.sessionStore.getObservationsByIds(D,{orderBy:"date_desc",limit:1}))}}catch(T){q.debug("SEARCH","Chroma query failed - no results (FTS5 fallback removed)",{error:T.message})}if(R.length===0)return{content:[{type:"text",text:`No observations found matching "${t}". Try a different search query.`}]};let w=R[0];l=w.id,c=w.created_at_epoch,q.debug("SEARCH","Query mode: Using observation as timeline anchor",{observationId:w.id}),u=this.sessionStore.getTimelineAroundObservation(w.id,w.created_at_epoch,s,i,n)}else if(typeof r=="number"){let R=this.sessionStore.getObservationById(r);if(!R)return{content:[{type:"text",text:`Observation #${r} not found`}],isError:!0};l=r,c=R.created_at_epoch,u=this.sessionStore.getTimelineAroundObservation(r,c,s,i,n)}else if(typeof r=="string")if(r.startsWith("S")||r.startsWith("#S")){let R=r.replace(/^#?S/,""),w=parseInt(R,10),T=this.sessionStore.getSessionSummariesByIds([w]);if(T.length===0)return{content:[{type:"text",text:`Session #${w} not found`}],isError:!0};c=T[0].created_at_epoch,l=`S${w}`,u=this.sessionStore.getTimelineAroundTimestamp(c,s,i,n)}else{let R=new Date(r);if(isNaN(R.getTime()))return{content:[{type:"text",text:`Invalid timestamp: ${r}`}],isError:!0};c=R.getTime(),l=r,u=this.sessionStore.getTimelineAroundTimestamp(c,s,i,n)}else return{content:[{type:"text",text:'Invalid anchor: must be observation ID (number), session ID (e.g., "S123"), or ISO timestamp'}],isError:!0};let p=[...(u.observations||[]).map(R=>({type:"observation",data:R,epoch:R.created_at_epoch})),...(u.sessions||[]).map(R=>({type:"session",data:R,epoch:R.created_at_epoch})),...(u.prompts||[]).map(R=>({type:"prompt",data:R,epoch:R.created_at_epoch}))];p.sort((R,w)=>R.epoch-w.epoch);let f=this.timelineService.filterByDepth(p,l,c,s,i);if(!f||f.length===0)return{content:[{type:"text",text:t?`Found observation matching "${t}", but no timeline context available (${s} records before, ${i} records after).`:`No context found around anchor (${s} records before, ${i} records after)`}]};let d=R=>new Date(R).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"}),v=R=>new Date(R).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0}),h=R=>new Date(R).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}),m=R=>R?Math.ceil(R.length/4):0,y=[];if(t){let R=f.find(T=>T.type==="observation"&&T.data.id===l),w=R&&R.type==="observation"?R.data.title||"Untitled":"Unknown";y.push(`# Timeline for query: "${t}"`),y.push(`**Anchor:** Observation #${l} - ${w}`)}else y.push(`# Timeline around anchor: ${l}`);y.push(`**Window:** ${s} records before \u2192 ${i} records after | **Items:** ${f?.length??0}`),y.push(""),y.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),y.push("");let g=new Map;for(let R of f){let w=d(R.epoch);g.has(w)||g.set(w,[]),g.get(w).push(R)}let b=Array.from(g.entries()).sort((R,w)=>{let T=new Date(R[0]).getTime(),k=new Date(w[0]).getTime();return T-k});for(let[R,w]of b){y.push(`### ${R}`),y.push("");let T=null,k="",D=!1;for(let A of w){let $=typeof l=="number"&&A.type==="observation"&&A.data.id===l||typeof l=="string"&&l.startsWith("S")&&A.type==="session"&&`S${A.data.id}`===l;if(A.type==="session"){D&&(y.push(""),D=!1,T=null,k="");let N=A.data,C=N.request||"Session summary",O=`claude-mem://session-summary/${N.id}`,j=$?" \u2190 **ANCHOR**":"";y.push(`**\u{1F3AF} #S${N.id}** ${C} (${h(A.epoch)}) [\u2192](${O})${j}`),y.push("")}else if(A.type==="prompt"){D&&(y.push(""),D=!1,T=null,k="");let N=A.data,C=N.prompt_text.length>100?N.prompt_text.substring(0,100)+"...":N.prompt_text;y.push(`**\u{1F4AC} User Prompt #${N.prompt_number}** (${h(A.epoch)})`),y.push(`> ${C}`),y.push("")}else if(A.type==="observation"){let N=A.data,C=zs(N.files_modified,o);C!==T&&(D&&y.push(""),y.push(`**${C}**`),y.push("| ID | Time | T | Title | Tokens |"),y.push("|----|------|---|-------|--------|"),T=C,D=!0,k="");let O="\u2022";switch(N.type){case"bugfix":O="\u{1F534}";break;case"feature":O="\u{1F7E3}";break;case"refactor":O="\u{1F504}";break;case"change":O="\u2705";break;case"discovery":O="\u{1F535}";break;case"decision":O="\u{1F9E0}";break}let j=v(A.epoch),M=N.title||"Untitled",z=m(N.narrative),X=j!==k?j:"\u2033";k=j;let Z=$?" \u2190 **ANCHOR**":"";y.push(`| #${N.id} | ${X} | ${O} | ${M}${Z} | ~${z} |`)}}D&&y.push("")}return{content:[{type:"text",text:y.join(` +`)}]}}catch(r){return{content:[{type:"text",text:`Search failed: ${r.message}`}],isError:!0}}}async timeline(e){try{let{anchor:r,query:t,depth_before:s=10,depth_after:i=10,project:n}=e,o=process.cwd();if(!r&&!t)return{content:[{type:"text",text:'Error: Must provide either "anchor" or "query" parameter'}],isError:!0};if(r&&t)return{content:[{type:"text",text:'Error: Cannot provide both "anchor" and "query" parameters. Use one or the other.'}],isError:!0};let l,c,u;if(t){let R=[];if(this.chromaSync)try{q.debug("SEARCH","Using hybrid semantic search for timeline query",{});let T=await this.queryChroma(t,100);if(q.debug("SEARCH","Chroma returned semantic matches for timeline",{matchCount:T?.ids?.length??0}),T?.ids&&T.ids.length>0){let k=Date.now()-7776e6,D=T.ids.filter((A,$)=>{let N=T.metadatas[$];return N&&N.created_at_epoch>k});D.length>0&&(R=this.sessionStore.getObservationsByIds(D,{orderBy:"date_desc",limit:1}))}}catch(T){q.debug("SEARCH","Chroma query failed - no results (FTS5 fallback removed)",{error:T.message})}if(R.length===0)return{content:[{type:"text",text:`No observations found matching "${t}". Try a different search query.`}]};let w=R[0];l=w.id,c=w.created_at_epoch,q.debug("SEARCH","Query mode: Using observation as timeline anchor",{observationId:w.id}),u=this.sessionStore.getTimelineAroundObservation(w.id,w.created_at_epoch,s,i,n)}else if(typeof r=="number"){let R=this.sessionStore.getObservationById(r);if(!R)return{content:[{type:"text",text:`Observation #${r} not found`}],isError:!0};l=r,c=R.created_at_epoch,u=this.sessionStore.getTimelineAroundObservation(r,c,s,i,n)}else if(typeof r=="string")if(r.startsWith("S")||r.startsWith("#S")){let R=r.replace(/^#?S/,""),w=parseInt(R,10),T=this.sessionStore.getSessionSummariesByIds([w]);if(T.length===0)return{content:[{type:"text",text:`Session #${w} not found`}],isError:!0};c=T[0].created_at_epoch,l=`S${w}`,u=this.sessionStore.getTimelineAroundTimestamp(c,s,i,n)}else{let R=new Date(r);if(isNaN(R.getTime()))return{content:[{type:"text",text:`Invalid timestamp: ${r}`}],isError:!0};c=R.getTime(),l=r,u=this.sessionStore.getTimelineAroundTimestamp(c,s,i,n)}else return{content:[{type:"text",text:'Invalid anchor: must be observation ID (number), session ID (e.g., "S123"), or ISO timestamp'}],isError:!0};let p=[...(u.observations||[]).map(R=>({type:"observation",data:R,epoch:R.created_at_epoch})),...(u.sessions||[]).map(R=>({type:"session",data:R,epoch:R.created_at_epoch})),...(u.prompts||[]).map(R=>({type:"prompt",data:R,epoch:R.created_at_epoch}))];p.sort((R,w)=>R.epoch-w.epoch);let f=this.timelineService.filterByDepth(p,l,c,s,i);if(!f||f.length===0)return{content:[{type:"text",text:t?`Found observation matching "${t}", but no timeline context available (${s} records before, ${i} records after).`:`No context found around anchor (${s} records before, ${i} records after)`}]};let d=R=>new Date(R).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"}),v=R=>new Date(R).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0}),h=R=>new Date(R).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}),m=R=>R?Math.ceil(R.length/4):0,y=[];if(t){let R=f.find(T=>T.type==="observation"&&T.data.id===l),w=R&&R.type==="observation"?R.data.title||"Untitled":"Unknown";y.push(`# Timeline for query: "${t}"`),y.push(`**Anchor:** Observation #${l} - ${w}`)}else y.push(`# Timeline around anchor: ${l}`);y.push(`**Window:** ${s} records before \u2192 ${i} records after | **Items:** ${f?.length??0}`),y.push("");let g=new Map;for(let R of f){let w=d(R.epoch);g.has(w)||g.set(w,[]),g.get(w).push(R)}let b=Array.from(g.entries()).sort((R,w)=>{let T=new Date(R[0]).getTime(),k=new Date(w[0]).getTime();return T-k});for(let[R,w]of b){y.push(`### ${R}`),y.push("");let T=null,k="",D=!1;for(let A of w){let $=typeof l=="number"&&A.type==="observation"&&A.data.id===l||typeof l=="string"&&l.startsWith("S")&&A.type==="session"&&`S${A.data.id}`===l;if(A.type==="session"){D&&(y.push(""),D=!1,T=null,k="");let N=A.data,C=N.request||"Session summary",O=`claude-mem://session-summary/${N.id}`,j=$?" \u2190 **ANCHOR**":"";y.push(`**\u{1F3AF} #S${N.id}** ${C} (${h(A.epoch)}) [\u2192](${O})${j}`),y.push("")}else if(A.type==="prompt"){D&&(y.push(""),D=!1,T=null,k="");let N=A.data,C=N.prompt_text.length>100?N.prompt_text.substring(0,100)+"...":N.prompt_text;y.push(`**\u{1F4AC} User Prompt #${N.prompt_number}** (${h(A.epoch)})`),y.push(`> ${C}`),y.push("")}else if(A.type==="observation"){let N=A.data,C=zs(N.files_modified,o);C!==T&&(D&&y.push(""),y.push(`**${C}**`),y.push("| ID | Time | T | Title | Tokens |"),y.push("|----|------|---|-------|--------|"),T=C,D=!0,k="");let O="\u2022";switch(N.type){case"bugfix":O="\u{1F534}";break;case"feature":O="\u{1F7E3}";break;case"refactor":O="\u{1F504}";break;case"change":O="\u2705";break;case"discovery":O="\u{1F535}";break;case"decision":O="\u{1F9E0}";break}let j=v(A.epoch),M=N.title||"Untitled",z=m(N.narrative),X=j!==k?j:"\u2033";k=j;let Z=$?" \u2190 **ANCHOR**":"";y.push(`| #${N.id} | ${X} | ${O} | ${M}${Z} | ~${z} |`)}}D&&y.push("")}return{content:[{type:"text",text:y.join(` `)}]}}catch(r){return{content:[{type:"text",text:`Timeline query failed: ${r.message}`}],isError:!0}}}async decisions(e){try{let r=this.normalizeParams(e),{query:t,...s}=r,i=[];if(this.chromaSync)try{if(t){q.debug("SEARCH","Using Chroma semantic search with type=decision filter",{});let c=(await this.queryChroma(t,Math.min((s.limit||20)*2,100),{type:"decision"})).ids;c.length>0&&(i=this.sessionStore.getObservationsByIds(c,{...s,type:"decision"}),i.sort((u,p)=>c.indexOf(u.id)-c.indexOf(p.id)))}else{q.debug("SEARCH","Using metadata-first + semantic ranking for decisions",{});let l=this.sessionSearch.findByType("decision",s);if(l.length>0){let c=l.map(f=>f.id),u=await this.queryChroma("decision",Math.min(c.length,100)),p=[];for(let f of u.ids)c.includes(f)&&!p.includes(f)&&p.push(f);p.length>0&&(i=this.sessionStore.getObservationsByIds(p,{limit:s.limit||20}),i.sort((f,d)=>p.indexOf(f.id)-p.indexOf(d.id)))}}}catch(l){q.debug("SEARCH","Chroma search failed, using SQLite fallback",{error:l.message})}if(i.length===0&&(i=this.sessionSearch.findByType("decision",s)),i.length===0)return{content:[{type:"text",text:"No decision observations found"}]};let n=`Found ${i.length} decision(s) ${this.formatter.formatTableHeader()}`,o=i.map((l,c)=>this.formatter.formatObservationIndex(l,c));return{content:[{type:"text",text:n+` @@ -1002,9 +1002,9 @@ ${this.formatter.formatTableHeader()}`,l=n.map((c,u)=>this.formatter.formatObser `)}]}}catch(r){return{content:[{type:"text",text:`Search failed: ${r.message}`}],isError:!0}}}async getRecentContext(e){try{let r=e.project||(0,t1.basename)(process.cwd()),t=e.limit||3,s=this.sessionStore.getRecentSessionsWithStatus(r,t);if(s.length===0)return{content:[{type:"text",text:`# Recent Session Context No previous sessions found for project "${r}".`}]};let i=[];i.push("# Recent Session Context"),i.push(""),i.push(`Showing last ${s.length} session(s) for **${r}**:`),i.push("");for(let n of s)if(n.sdk_session_id){if(i.push("---"),i.push(""),n.has_summary){let o=this.sessionStore.getSummaryForSession(n.sdk_session_id);if(o){let l=o.prompt_number?` (Prompt #${o.prompt_number})`:"";if(i.push(`**Summary${l}**`),i.push(""),o.request&&i.push(`**Request:** ${o.request}`),o.completed&&i.push(`**Completed:** ${o.completed}`),o.learned&&i.push(`**Learned:** ${o.learned}`),o.next_steps&&i.push(`**Next Steps:** ${o.next_steps}`),o.files_read)try{let u=JSON.parse(o.files_read);Array.isArray(u)&&u.length>0&&i.push(`**Files Read:** ${u.join(", ")}`)}catch{o.files_read.trim()&&i.push(`**Files Read:** ${o.files_read}`)}if(o.files_edited)try{let u=JSON.parse(o.files_edited);Array.isArray(u)&&u.length>0&&i.push(`**Files Edited:** ${u.join(", ")}`)}catch{o.files_edited.trim()&&i.push(`**Files Edited:** ${o.files_edited}`)}let c=new Date(o.created_at).toLocaleString();i.push(`**Date:** ${c}`)}}else if(n.status==="active"){i.push("**In Progress**"),i.push(""),n.user_prompt&&i.push(`**Request:** ${n.user_prompt}`);let o=this.sessionStore.getObservationsForSession(n.sdk_session_id);if(o.length>0){i.push(""),i.push(`**Observations (${o.length}):**`);for(let c of o)i.push(`- ${c.title}`)}else i.push(""),i.push("*No observations yet*");i.push(""),i.push("**Status:** Active - summary pending");let l=new Date(n.started_at).toLocaleString();i.push(`**Date:** ${l}`)}else{i.push(`**${n.status.charAt(0).toUpperCase()+n.status.slice(1)}**`),i.push(""),n.user_prompt&&i.push(`**Request:** ${n.user_prompt}`),i.push(""),i.push(`**Status:** ${n.status} - no summary available`);let o=new Date(n.started_at).toLocaleString();i.push(`**Date:** ${o}`)}i.push("")}return{content:[{type:"text",text:i.join(` -`)}]}}catch(r){return{content:[{type:"text",text:`Failed to get recent context: ${r.message}`}],isError:!0}}}async getContextTimeline(e){try{let{anchor:r,depth_before:t=10,depth_after:s=10,project:i}=e,n=process.cwd(),o,l=r,c;if(typeof r=="number"){let b=this.sessionStore.getObservationById(r);if(!b)return{content:[{type:"text",text:`Observation #${r} not found`}],isError:!0};o=b.created_at_epoch,c=this.sessionStore.getTimelineAroundObservation(r,o,t,s,i)}else if(typeof r=="string")if(r.startsWith("S")||r.startsWith("#S")){let b=r.replace(/^#?S/,""),R=parseInt(b,10),w=this.sessionStore.getSessionSummariesByIds([R]);if(w.length===0)return{content:[{type:"text",text:`Session #${R} not found`}],isError:!0};o=w[0].created_at_epoch,l=`S${R}`,c=this.sessionStore.getTimelineAroundTimestamp(o,t,s,i)}else{let b=new Date(r);if(isNaN(b.getTime()))return{content:[{type:"text",text:`Invalid timestamp: ${r}`}],isError:!0};o=b.getTime(),c=this.sessionStore.getTimelineAroundTimestamp(o,t,s,i)}else return{content:[{type:"text",text:'Invalid anchor: must be observation ID (number), session ID (e.g., "S123"), or ISO timestamp'}],isError:!0};let u=[...c.observations.map(b=>({type:"observation",data:b,epoch:b.created_at_epoch})),...c.sessions.map(b=>({type:"session",data:b,epoch:b.created_at_epoch})),...c.prompts.map(b=>({type:"prompt",data:b,epoch:b.created_at_epoch}))];u.sort((b,R)=>b.epoch-R.epoch);let p=this.timelineService.filterByDepth(u,l,o,t,s);if(!p||p.length===0)return{content:[{type:"text",text:`No context found around ${new Date(o).toLocaleString()} (${t} records before, ${s} records after)`}]};let f=b=>new Date(b).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"}),d=b=>new Date(b).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0}),v=b=>new Date(b).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}),h=b=>b?Math.ceil(b.length/4):0,m=[];m.push(`# Timeline around anchor: ${l}`),m.push(`**Window:** ${t} records before \u2192 ${s} records after | **Items:** ${p?.length??0}`),m.push(""),m.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),m.push("");let y=new Map;for(let b of p){let R=f(b.epoch);y.has(R)||y.set(R,[]),y.get(R).push(b)}let g=Array.from(y.entries()).sort((b,R)=>{let w=new Date(b[0]).getTime(),T=new Date(R[0]).getTime();return w-T});for(let[b,R]of g){m.push(`### ${b}`),m.push("");let w=null,T="",k=!1;for(let D of R){let A=typeof l=="number"&&D.type==="observation"&&D.data.id===l||typeof l=="string"&&l.startsWith("S")&&D.type==="session"&&`S${D.data.id}`===l;if(D.type==="session"){k&&(m.push(""),k=!1,w=null,T="");let $=D.data,N=$.request||"Session summary",C=`claude-mem://session-summary/${$.id}`,O=A?" \u2190 **ANCHOR**":"";m.push(`**\u{1F3AF} #S${$.id}** ${N} (${v(D.epoch)}) [\u2192](${C})${O}`),m.push("")}else if(D.type==="prompt"){k&&(m.push(""),k=!1,w=null,T="");let $=D.data,N=$.prompt_text.length>100?$.prompt_text.substring(0,100)+"...":$.prompt_text;m.push(`**\u{1F4AC} User Prompt #${$.prompt_number}** (${v(D.epoch)})`),m.push(`> ${N}`),m.push("")}else if(D.type==="observation"){let $=D.data,N=zs($.files_modified,n);N!==w&&(k&&m.push(""),m.push(`**${N}**`),m.push("| ID | Time | T | Title | Tokens |"),m.push("|----|------|---|-------|--------|"),w=N,k=!0,T="");let C="\u2022";switch($.type){case"bugfix":C="\u{1F534}";break;case"feature":C="\u{1F7E3}";break;case"refactor":C="\u{1F504}";break;case"change":C="\u2705";break;case"discovery":C="\u{1F535}";break;case"decision":C="\u{1F9E0}";break}let O=d(D.epoch),j=$.title||"Untitled",M=h($.narrative),V=O!==T?O:"\u2033";T=O;let X=A?" \u2190 **ANCHOR**":"";m.push(`| #${$.id} | ${V} | ${C} | ${j}${X} | ~${M} |`)}}k&&m.push("")}return{content:[{type:"text",text:m.join(` +`)}]}}catch(r){return{content:[{type:"text",text:`Failed to get recent context: ${r.message}`}],isError:!0}}}async getContextTimeline(e){try{let{anchor:r,depth_before:t=10,depth_after:s=10,project:i}=e,n=process.cwd(),o,l=r,c;if(typeof r=="number"){let b=this.sessionStore.getObservationById(r);if(!b)return{content:[{type:"text",text:`Observation #${r} not found`}],isError:!0};o=b.created_at_epoch,c=this.sessionStore.getTimelineAroundObservation(r,o,t,s,i)}else if(typeof r=="string")if(r.startsWith("S")||r.startsWith("#S")){let b=r.replace(/^#?S/,""),R=parseInt(b,10),w=this.sessionStore.getSessionSummariesByIds([R]);if(w.length===0)return{content:[{type:"text",text:`Session #${R} not found`}],isError:!0};o=w[0].created_at_epoch,l=`S${R}`,c=this.sessionStore.getTimelineAroundTimestamp(o,t,s,i)}else{let b=new Date(r);if(isNaN(b.getTime()))return{content:[{type:"text",text:`Invalid timestamp: ${r}`}],isError:!0};o=b.getTime(),c=this.sessionStore.getTimelineAroundTimestamp(o,t,s,i)}else return{content:[{type:"text",text:'Invalid anchor: must be observation ID (number), session ID (e.g., "S123"), or ISO timestamp'}],isError:!0};let u=[...c.observations.map(b=>({type:"observation",data:b,epoch:b.created_at_epoch})),...c.sessions.map(b=>({type:"session",data:b,epoch:b.created_at_epoch})),...c.prompts.map(b=>({type:"prompt",data:b,epoch:b.created_at_epoch}))];u.sort((b,R)=>b.epoch-R.epoch);let p=this.timelineService.filterByDepth(u,l,o,t,s);if(!p||p.length===0)return{content:[{type:"text",text:`No context found around ${new Date(o).toLocaleString()} (${t} records before, ${s} records after)`}]};let f=b=>new Date(b).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"}),d=b=>new Date(b).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0}),v=b=>new Date(b).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}),h=b=>b?Math.ceil(b.length/4):0,m=[];m.push(`# Timeline around anchor: ${l}`),m.push(`**Window:** ${t} records before \u2192 ${s} records after | **Items:** ${p?.length??0}`),m.push("");let y=new Map;for(let b of p){let R=f(b.epoch);y.has(R)||y.set(R,[]),y.get(R).push(b)}let g=Array.from(y.entries()).sort((b,R)=>{let w=new Date(b[0]).getTime(),T=new Date(R[0]).getTime();return w-T});for(let[b,R]of g){m.push(`### ${b}`),m.push("");let w=null,T="",k=!1;for(let D of R){let A=typeof l=="number"&&D.type==="observation"&&D.data.id===l||typeof l=="string"&&l.startsWith("S")&&D.type==="session"&&`S${D.data.id}`===l;if(D.type==="session"){k&&(m.push(""),k=!1,w=null,T="");let $=D.data,N=$.request||"Session summary",C=`claude-mem://session-summary/${$.id}`,O=A?" \u2190 **ANCHOR**":"";m.push(`**\u{1F3AF} #S${$.id}** ${N} (${v(D.epoch)}) [\u2192](${C})${O}`),m.push("")}else if(D.type==="prompt"){k&&(m.push(""),k=!1,w=null,T="");let $=D.data,N=$.prompt_text.length>100?$.prompt_text.substring(0,100)+"...":$.prompt_text;m.push(`**\u{1F4AC} User Prompt #${$.prompt_number}** (${v(D.epoch)})`),m.push(`> ${N}`),m.push("")}else if(D.type==="observation"){let $=D.data,N=zs($.files_modified,n);N!==w&&(k&&m.push(""),m.push(`**${N}**`),m.push("| ID | Time | T | Title | Tokens |"),m.push("|----|------|---|-------|--------|"),w=N,k=!0,T="");let C="\u2022";switch($.type){case"bugfix":C="\u{1F534}";break;case"feature":C="\u{1F7E3}";break;case"refactor":C="\u{1F504}";break;case"change":C="\u2705";break;case"discovery":C="\u{1F535}";break;case"decision":C="\u{1F9E0}";break}let O=d(D.epoch),j=$.title||"Untitled",M=h($.narrative),V=O!==T?O:"\u2033";T=O;let X=A?" \u2190 **ANCHOR**":"";m.push(`| #${$.id} | ${V} | ${C} | ${j}${X} | ~${M} |`)}}k&&m.push("")}return{content:[{type:"text",text:m.join(` `)}]}}catch(r){return{content:[{type:"text",text:`Timeline query failed: ${r.message}`}],isError:!0}}}async getTimelineByQuery(e){try{let{query:r,mode:t="auto",depth_before:s=10,depth_after:i=10,limit:n=5,project:o}=e,l=process.cwd(),c=[];if(this.chromaSync)try{q.debug("SEARCH","Using hybrid semantic search for timeline query",{});let u=await this.queryChroma(r,100);if(q.debug("SEARCH","Chroma returned semantic matches for timeline",{matchCount:u.ids.length}),u.ids.length>0){let p=Date.now()-7776e6,f=u.ids.filter((d,v)=>{let h=u.metadatas[v];return h&&h.created_at_epoch>p});q.debug("SEARCH","Results within 90-day window",{count:f.length}),f.length>0&&(c=this.sessionStore.getObservationsByIds(f,{orderBy:"date_desc",limit:t==="auto"?1:n}),q.debug("SEARCH","Hydrated observations from SQLite",{count:c.length}))}}catch(u){q.debug("SEARCH","Chroma query failed - no results (FTS5 fallback removed)",{error:u.message})}if(c.length===0)return{content:[{type:"text",text:`No observations found matching "${r}". Try a different search query.`}]};if(t==="interactive"){let u=[];u.push("# Timeline Anchor Search Results"),u.push(""),u.push(`Found ${c.length} observation(s) matching "${r}"`),u.push(""),u.push("To get timeline context around any of these observations, use the `get_context_timeline` tool with the observation ID as the anchor."),u.push(""),u.push(`**Top ${c.length} matches:**`),u.push("");for(let p=0;p({type:"observation",data:w,epoch:w.created_at_epoch})),...(p.sessions||[]).map(w=>({type:"session",data:w,epoch:w.created_at_epoch})),...(p.prompts||[]).map(w=>({type:"prompt",data:w,epoch:w.created_at_epoch}))];f.sort((w,T)=>w.epoch-T.epoch);let d=this.timelineService.filterByDepth(f,u.id,0,s,i);if(!d||d.length===0)return{content:[{type:"text",text:`Found observation #${u.id} matching "${r}", but no timeline context available (${s} records before, ${i} records after).`}]};let v=w=>new Date(w).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"}),h=w=>new Date(w).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0}),m=w=>new Date(w).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}),y=w=>w?Math.ceil(w.length/4):0,g=[];g.push(`# Timeline for query: "${r}"`),g.push(`**Anchor:** Observation #${u.id} - ${u.title||"Untitled"}`),g.push(`**Window:** ${s} records before \u2192 ${i} records after | **Items:** ${d?.length??0}`),g.push(""),g.push("**Legend:** \u{1F3AF} session-request | \u{1F534} bugfix | \u{1F7E3} feature | \u{1F504} refactor | \u2705 change | \u{1F535} discovery | \u{1F9E0} decision"),g.push("");let b=new Map;for(let w of d){let T=v(w.epoch);b.has(T)||b.set(T,[]),b.get(T).push(w)}let R=Array.from(b.entries()).sort((w,T)=>{let k=new Date(w[0]).getTime(),D=new Date(T[0]).getTime();return k-D});for(let[w,T]of R){g.push(`### ${w}`),g.push("");let k=null,D="",A=!1;for(let $ of T){let N=$.type==="observation"&&$.data.id===u.id;if($.type==="session"){A&&(g.push(""),A=!1,k=null,D="");let C=$.data,O=C.request||"Session summary",j=`claude-mem://session-summary/${C.id}`;g.push(`**\u{1F3AF} #S${C.id}** ${O} (${m($.epoch)}) [\u2192](${j})`),g.push("")}else if($.type==="prompt"){A&&(g.push(""),A=!1,k=null,D="");let C=$.data,O=C.prompt_text.length>100?C.prompt_text.substring(0,100)+"...":C.prompt_text;g.push(`**\u{1F4AC} User Prompt #${C.prompt_number}** (${m($.epoch)})`),g.push(`> ${O}`),g.push("")}else if($.type==="observation"){let C=$.data,O=zs(C.files_modified,l);O!==k&&(A&&g.push(""),g.push(`**${O}**`),g.push("| ID | Time | T | Title | Tokens |"),g.push("|----|------|---|-------|--------|"),k=O,A=!0,D="");let j="\u2022";switch(C.type){case"bugfix":j="\u{1F534}";break;case"feature":j="\u{1F7E3}";break;case"refactor":j="\u{1F504}";break;case"change":j="\u2705";break;case"discovery":j="\u{1F535}";break;case"decision":j="\u{1F9E0}";break}let M=h($.epoch),z=C.title||"Untitled",V=y(C.narrative),Z=M!==D?M:"\u2033";D=M;let K=N?" \u2190 **ANCHOR**":"";g.push(`| #${C.id} | ${Z} | ${j} | ${z}${K} | ~${V} |`)}}A&&g.push("")}return{content:[{type:"text",text:g.join(` +`)}]}}else{let u=c[0];q.debug("SEARCH","Auto mode: Using observation as timeline anchor",{observationId:u.id});let p=this.sessionStore.getTimelineAroundObservation(u.id,u.created_at_epoch,s,i,o),f=[...(p.observations||[]).map(w=>({type:"observation",data:w,epoch:w.created_at_epoch})),...(p.sessions||[]).map(w=>({type:"session",data:w,epoch:w.created_at_epoch})),...(p.prompts||[]).map(w=>({type:"prompt",data:w,epoch:w.created_at_epoch}))];f.sort((w,T)=>w.epoch-T.epoch);let d=this.timelineService.filterByDepth(f,u.id,0,s,i);if(!d||d.length===0)return{content:[{type:"text",text:`Found observation #${u.id} matching "${r}", but no timeline context available (${s} records before, ${i} records after).`}]};let v=w=>new Date(w).toLocaleString("en-US",{month:"short",day:"numeric",year:"numeric"}),h=w=>new Date(w).toLocaleString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0}),m=w=>new Date(w).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}),y=w=>w?Math.ceil(w.length/4):0,g=[];g.push(`# Timeline for query: "${r}"`),g.push(`**Anchor:** Observation #${u.id} - ${u.title||"Untitled"}`),g.push(`**Window:** ${s} records before \u2192 ${i} records after | **Items:** ${d?.length??0}`),g.push("");let b=new Map;for(let w of d){let T=v(w.epoch);b.has(T)||b.set(T,[]),b.get(T).push(w)}let R=Array.from(b.entries()).sort((w,T)=>{let k=new Date(w[0]).getTime(),D=new Date(T[0]).getTime();return k-D});for(let[w,T]of R){g.push(`### ${w}`),g.push("");let k=null,D="",A=!1;for(let $ of T){let N=$.type==="observation"&&$.data.id===u.id;if($.type==="session"){A&&(g.push(""),A=!1,k=null,D="");let C=$.data,O=C.request||"Session summary",j=`claude-mem://session-summary/${C.id}`;g.push(`**\u{1F3AF} #S${C.id}** ${O} (${m($.epoch)}) [\u2192](${j})`),g.push("")}else if($.type==="prompt"){A&&(g.push(""),A=!1,k=null,D="");let C=$.data,O=C.prompt_text.length>100?C.prompt_text.substring(0,100)+"...":C.prompt_text;g.push(`**\u{1F4AC} User Prompt #${C.prompt_number}** (${m($.epoch)})`),g.push(`> ${O}`),g.push("")}else if($.type==="observation"){let C=$.data,O=zs(C.files_modified,l);O!==k&&(A&&g.push(""),g.push(`**${O}**`),g.push("| ID | Time | T | Title | Tokens |"),g.push("|----|------|---|-------|--------|"),k=O,A=!0,D="");let j="\u2022";switch(C.type){case"bugfix":j="\u{1F534}";break;case"feature":j="\u{1F7E3}";break;case"refactor":j="\u{1F504}";break;case"change":j="\u2705";break;case"discovery":j="\u{1F535}";break;case"decision":j="\u{1F9E0}";break}let M=h($.epoch),z=C.title||"Untitled",V=y(C.narrative),Z=M!==D?M:"\u2033";D=M;let K=N?" \u2190 **ANCHOR**":"";g.push(`| #${C.id} | ${Z} | ${j} | ${z}${K} | ~${V} |`)}}A&&g.push("")}return{content:[{type:"text",text:g.join(` `)}]}}}catch(r){return{content:[{type:"text",text:`Timeline query failed: ${r.message}`}],isError:!0}}}};Xi();var d5=4,jl=class{formatSearchTips(){return` --- \u{1F4A1} Search Strategy: diff --git a/src/services/worker/SearchManager.ts b/src/services/worker/SearchManager.ts index 017a3e10..854a6f4c 100644 --- a/src/services/worker/SearchManager.ts +++ b/src/services/worker/SearchManager.ts @@ -522,9 +522,6 @@ export class SearchManager { lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems?.length ?? 0}`); lines.push(''); - // Legend - lines.push(`**Legend:** 🎯 session-request | 🔴 bugfix | 🟣 feature | 🔄 refactor | ✅ change | 🔵 discovery | 🧠 decision`); - lines.push(''); // Group by day const dayMap = new Map(); @@ -1637,9 +1634,6 @@ export class SearchManager { lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems?.length ?? 0}`); lines.push(''); - // Legend - lines.push(`**Legend:** 🎯 session-request | 🔴 bugfix | 🟣 feature | 🔄 refactor | ✅ change | 🔵 discovery | 🧠 decision`); - lines.push(''); // Group by day const dayMap = new Map(); @@ -1933,9 +1927,6 @@ export class SearchManager { lines.push(`**Window:** ${depth_before} records before → ${depth_after} records after | **Items:** ${filteredItems?.length ?? 0}`); lines.push(''); - // Legend - lines.push(`**Legend:** 🎯 session-request | 🔴 bugfix | 🟣 feature | 🔄 refactor | ✅ change | 🔵 discovery | 🧠 decision`); - lines.push(''); // Group by day const dayMap = new Map();