@clauderecallhq/cli 0.66.0 → 0.68.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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /* Claude Recall (proprietary). See LICENSE for terms. */
3
- var En=Object.defineProperty;var $=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ie=(e,t)=>{for(var s in t)En(e,s,{get:t[s],enumerable:!0})};import{createRequire as Tn}from"node:module";var Sn,bn,Rn,xe,ve,at,ct=$(()=>{"use strict";{let e=process.emit.bind(process);process.emit=function(t,...s){let n=s[0];return t==="warning"&&n instanceof Error&&n.name==="ExperimentalWarning"&&/SQLite/i.test(n.message)?!1:e(t,...s)}}Sn=Tn(import.meta.url),bn=["node","sqlite"].join(":"),Rn=Sn(bn),xe=class{inner;constructor(t){this.inner=t}get(...t){return t.length===0?this.inner.get():this.inner.get(...t)}all(...t){return t.length===0?this.inner.all():this.inner.all(...t)}run(...t){let s=t.length===0?this.inner.run():this.inner.run(...t);return{changes:s.changes,lastInsertRowid:s.lastInsertRowid}}iterate(...t){return t.length===0?this.inner.iterate():this.inner.iterate(...t)}},ve=class{inner;extensionLoadingEnabled=!1;txDepth=0;constructor(t,s={}){this.inner=new Rn.DatabaseSync(t,{readOnly:s.readonly??!1,allowExtension:!0})}prepare(t){return new xe(this.inner.prepare(t))}exec(t){this.inner.exec(t)}close(){this.inner.close()}pragma(t,s={}){if(t.includes("=")){this.inner.exec(`PRAGMA ${t}`);return}if(s.simple){let n=this.inner.prepare(`PRAGMA ${t}`).get();return n&&typeof n=="object"?Object.values(n)[0]:void 0}return this.inner.prepare(`PRAGMA ${t}`).all()}transaction(t){return((...n)=>{this.txDepth===0?this.inner.exec("BEGIN"):this.inner.exec(`SAVEPOINT sp_${this.txDepth}`),this.txDepth+=1;try{let i=t(...n);return this.txDepth-=1,this.txDepth===0?this.inner.exec("COMMIT"):this.inner.exec(`RELEASE sp_${this.txDepth}`),i}catch(i){this.txDepth-=1;try{this.txDepth===0?this.inner.exec("ROLLBACK"):(this.inner.exec(`ROLLBACK TO sp_${this.txDepth}`),this.inner.exec(`RELEASE sp_${this.txDepth}`))}catch{}throw i}})}loadExtension(t,s){this.extensionLoadingEnabled||(this.inner.enableLoadExtension(!0),this.extensionLoadingEnabled=!0),s===void 0?this.inner.loadExtension(t):this.inner.loadExtension(t,s)}},at=ve});import{homedir as lt}from"node:os";import{join as Ce,basename as Yo}from"node:path";import{existsSync as yn,mkdirSync as Nn,chmodSync as wn,readdirSync as Ko,statSync as zo}from"node:fs";function x(){yn(S)||Nn(S,{recursive:!0,mode:448}),process.platform!=="win32"&&wn(S,448)}var Jo,S,oe,F=$(()=>{"use strict";Jo=Ce(lt(),".claude","projects"),S=process.env.RECALL_HOME?process.env.RECALL_HOME:Ce(lt(),".recall"),oe=Ce(S,"db.sqlite")});function ut(e){let t=e.prepare("PRAGMA table_info(sessions)").all(),s=new Set(t.map(g=>g.name)),n=[["total_input_tokens","INTEGER"],["total_output_tokens","INTEGER"],["total_cache_create_tokens","INTEGER"],["total_cache_read_tokens","INTEGER"],["primary_model","TEXT"],["auto_title","TEXT"],["auto_title_source","TEXT"],["auto_title_generated_at","INTEGER"],["auto_title_history","TEXT"],["verification_status","TEXT"],["verification_computed_at","INTEGER"],["title_quality","TEXT"],["title_quality_computed_at","INTEGER"]];for(let[g,_]of n)s.has(g)||e.exec(`ALTER TABLE sessions ADD COLUMN ${g} ${_}`);let i=e.prepare("PRAGMA table_info(collection_sessions)").all(),r=new Set(i.map(g=>g.name)),o=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[g,_]of o)r.has(g)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${g} ${_}`);let a=e.prepare("PRAGMA table_info(session_notes)").all(),c=new Set(a.map(g=>g.name)),l=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[g,_]of l)c.has(g)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${g} ${_}`);let d=e.prepare("PRAGMA table_info(threads)").all();new Set(d.map(g=>g.name)).has("folder_id")||(e.exec("ALTER TABLE threads ADD COLUMN folder_id TEXT REFERENCES thread_folders(id) ON DELETE SET NULL"),e.exec("CREATE INDEX IF NOT EXISTS idx_threads_folder ON threads(folder_id)"));let u=e.prepare("PRAGMA table_info(thread_folders)").all(),m=new Set(u.map(g=>g.name));m.has("project_scope")||(e.exec("ALTER TABLE thread_folders ADD COLUMN project_scope TEXT"),e.exec("CREATE INDEX IF NOT EXISTS idx_thread_folders_project ON thread_folders(project_scope)")),m.has("sort_order")||e.exec("ALTER TABLE thread_folders ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"),e.exec(`
3
+ var En=Object.defineProperty;var $=(e,t)=>()=>(e&&(t=e(e=0)),t);var Ie=(e,t)=>{for(var s in t)En(e,s,{get:t[s],enumerable:!0})};import{createRequire as Tn}from"node:module";var Sn,bn,Rn,xe,ve,at,ct=$(()=>{"use strict";{let e=process.emit.bind(process);process.emit=function(t,...s){let n=s[0];return t==="warning"&&n instanceof Error&&n.name==="ExperimentalWarning"&&/SQLite/i.test(n.message)?!1:e(t,...s)}}Sn=Tn(import.meta.url),bn=["node","sqlite"].join(":"),Rn=Sn(bn),xe=class{inner;constructor(t){this.inner=t}get(...t){return t.length===0?this.inner.get():this.inner.get(...t)}all(...t){return t.length===0?this.inner.all():this.inner.all(...t)}run(...t){let s=t.length===0?this.inner.run():this.inner.run(...t);return{changes:s.changes,lastInsertRowid:s.lastInsertRowid}}iterate(...t){return t.length===0?this.inner.iterate():this.inner.iterate(...t)}},ve=class{inner;extensionLoadingEnabled=!1;txDepth=0;constructor(t,s={}){this.inner=new Rn.DatabaseSync(t,{readOnly:s.readonly??!1,allowExtension:!0})}prepare(t){return new xe(this.inner.prepare(t))}exec(t){this.inner.exec(t)}close(){this.inner.close()}pragma(t,s={}){if(t.includes("=")){this.inner.exec(`PRAGMA ${t}`);return}if(s.simple){let n=this.inner.prepare(`PRAGMA ${t}`).get();return n&&typeof n=="object"?Object.values(n)[0]:void 0}return this.inner.prepare(`PRAGMA ${t}`).all()}transaction(t){return((...n)=>{this.txDepth===0?this.inner.exec("BEGIN"):this.inner.exec(`SAVEPOINT sp_${this.txDepth}`),this.txDepth+=1;try{let i=t(...n);return this.txDepth-=1,this.txDepth===0?this.inner.exec("COMMIT"):this.inner.exec(`RELEASE sp_${this.txDepth}`),i}catch(i){this.txDepth-=1;try{this.txDepth===0?this.inner.exec("ROLLBACK"):(this.inner.exec(`ROLLBACK TO sp_${this.txDepth}`),this.inner.exec(`RELEASE sp_${this.txDepth}`))}catch{}throw i}})}loadExtension(t,s){this.extensionLoadingEnabled||(this.inner.enableLoadExtension(!0),this.extensionLoadingEnabled=!0),s===void 0?this.inner.loadExtension(t):this.inner.loadExtension(t,s)}},at=ve});import{homedir as lt}from"node:os";import{join as Ce,basename as Go}from"node:path";import{existsSync as yn,mkdirSync as Nn,chmodSync as wn,readdirSync as zo,statSync as Ko}from"node:fs";function x(){yn(b)||Nn(b,{recursive:!0,mode:448}),process.platform!=="win32"&&wn(b,448)}var Jo,b,oe,M=$(()=>{"use strict";Jo=process.env.CLAUDE_PROJECTS_DIR?process.env.CLAUDE_PROJECTS_DIR:Ce(lt(),".claude","projects"),b=process.env.RECALL_HOME?process.env.RECALL_HOME:Ce(lt(),".recall"),oe=Ce(b,"db.sqlite")});function ut(e){let t=e.prepare("PRAGMA table_info(sessions)").all(),s=new Set(t.map(g=>g.name)),n=[["total_input_tokens","INTEGER"],["total_output_tokens","INTEGER"],["total_cache_create_tokens","INTEGER"],["total_cache_read_tokens","INTEGER"],["primary_model","TEXT"],["auto_title","TEXT"],["auto_title_source","TEXT"],["auto_title_generated_at","INTEGER"],["auto_title_history","TEXT"],["verification_status","TEXT"],["verification_computed_at","INTEGER"],["title_quality","TEXT"],["title_quality_computed_at","INTEGER"],["archive_status","TEXT NOT NULL DEFAULT 'live'"],["archived_at","TEXT"]];for(let[g,_]of n)s.has(g)||e.exec(`ALTER TABLE sessions ADD COLUMN ${g} ${_}`);let i=e.prepare("PRAGMA table_info(collection_sessions)").all(),r=new Set(i.map(g=>g.name)),o=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[g,_]of o)r.has(g)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${g} ${_}`);let a=e.prepare("PRAGMA table_info(session_notes)").all(),c=new Set(a.map(g=>g.name)),l=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[g,_]of l)c.has(g)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${g} ${_}`);let d=e.prepare("PRAGMA table_info(threads)").all();new Set(d.map(g=>g.name)).has("folder_id")||(e.exec("ALTER TABLE threads ADD COLUMN folder_id TEXT REFERENCES thread_folders(id) ON DELETE SET NULL"),e.exec("CREATE INDEX IF NOT EXISTS idx_threads_folder ON threads(folder_id)"));let u=e.prepare("PRAGMA table_info(thread_folders)").all(),p=new Set(u.map(g=>g.name));p.has("project_scope")||(e.exec("ALTER TABLE thread_folders ADD COLUMN project_scope TEXT"),e.exec("CREATE INDEX IF NOT EXISTS idx_thread_folders_project ON thread_folders(project_scope)")),p.has("sort_order")||e.exec("ALTER TABLE thread_folders ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"),e.exec(`
4
4
  CREATE TABLE IF NOT EXISTS message_embeddings (
5
5
  message_uuid TEXT PRIMARY KEY REFERENCES messages(uuid) ON DELETE CASCADE,
6
6
  session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
@@ -251,20 +251,70 @@ CREATE TABLE IF NOT EXISTS chunk_queue (
251
251
  );
252
252
  CREATE INDEX IF NOT EXISTS idx_chunk_queue_session ON chunk_queue(session_id);
253
253
 
254
- CREATE TRIGGER IF NOT EXISTS messages_vec_ai AFTER INSERT ON messages
255
- WHEN new.is_sidechain = 0 AND new.content_text IS NOT NULL
254
+ -- v0.67.0 -- retention/archive (P6). Power-user durability: sessions older
255
+ -- than the user chosen window can be moved to messages_archive so the hot
256
+ -- DB stays small without dropping data. Sessions metadata stays hot for
257
+ -- list/search by title; only the message bodies move. The "recall archive
258
+ -- restore <id>" command reverses the move. Source JSONLs are still the
259
+ -- immutable ground truth -- the archive table is a fast-path; in extremis
260
+ -- a session can be reindexed from its file_path on disk.
261
+ --
262
+ -- archive_status on the sessions row drives the UI: archived sessions show
263
+ -- a badge and a "restore to load full transcript" affordance.
264
+ CREATE TABLE IF NOT EXISTS messages_archive (
265
+ uuid TEXT PRIMARY KEY,
266
+ session_id TEXT NOT NULL,
267
+ parent_uuid TEXT,
268
+ type TEXT,
269
+ role TEXT,
270
+ timestamp TEXT,
271
+ is_sidechain INTEGER NOT NULL DEFAULT 0,
272
+ content_text TEXT,
273
+ tool_names TEXT,
274
+ raw_json TEXT,
275
+ archived_at TEXT NOT NULL DEFAULT (datetime('now'))
276
+ );
277
+ CREATE INDEX IF NOT EXISTS idx_messages_archive_session ON messages_archive(session_id);
278
+
279
+ -- v0.67.0 \u2014 semantic enqueue gate. Without this, every INSERT/DELETE/UPDATE
280
+ -- on messages enqueued a chunk_queue row, regardless of whether the embedder
281
+ -- worker was running. Power users with semantic disabled (the default)
282
+ -- accumulated 50M+ orphan rows in days, growing the DB by ~1 GB/day.
283
+ -- The gate is a single key in app_settings, kept in sync with
284
+ -- ~/.recall/config.json:semantic.enabled by the daemon at boot and on every
285
+ -- semantic-config write.
286
+ CREATE TABLE IF NOT EXISTS app_settings (
287
+ key TEXT PRIMARY KEY,
288
+ value TEXT NOT NULL
289
+ );
290
+ INSERT OR IGNORE INTO app_settings(key, value) VALUES ('semantic_enabled', '0');
291
+
292
+ -- Drop unconditional v0.10/v0.11-era triggers; replaced below with the gated
293
+ -- versions. Idempotent: SQLite no-ops when the trigger does not exist, so
294
+ -- this is safe to run on every boot and on fresh databases.
295
+ DROP TRIGGER IF EXISTS messages_vec_ai;
296
+ DROP TRIGGER IF EXISTS messages_vec_ad;
297
+ DROP TRIGGER IF EXISTS messages_vec_au;
298
+
299
+ CREATE TRIGGER messages_vec_ai AFTER INSERT ON messages
300
+ WHEN new.is_sidechain = 0
301
+ AND new.content_text IS NOT NULL
302
+ AND (SELECT value FROM app_settings WHERE key = 'semantic_enabled') = '1'
256
303
  BEGIN
257
304
  INSERT INTO chunk_queue(session_id, message_uuid, action, enqueued_at)
258
305
  VALUES (new.session_id, new.uuid, 'embed', datetime('now'));
259
306
  END;
260
307
 
261
- CREATE TRIGGER IF NOT EXISTS messages_vec_ad AFTER DELETE ON messages BEGIN
308
+ CREATE TRIGGER messages_vec_ad AFTER DELETE ON messages
309
+ WHEN (SELECT value FROM app_settings WHERE key = 'semantic_enabled') = '1'
310
+ BEGIN
262
311
  INSERT INTO chunk_queue(session_id, message_uuid, action, enqueued_at)
263
312
  VALUES (old.session_id, old.uuid, 'delete', datetime('now'));
264
313
  END;
265
314
 
266
- CREATE TRIGGER IF NOT EXISTS messages_vec_au AFTER UPDATE OF content_text ON messages
315
+ CREATE TRIGGER messages_vec_au AFTER UPDATE OF content_text ON messages
267
316
  WHEN new.is_sidechain = 0
317
+ AND (SELECT value FROM app_settings WHERE key = 'semantic_enabled') = '1'
268
318
  BEGIN
269
319
  INSERT INTO chunk_queue(session_id, message_uuid, action, enqueued_at)
270
320
  VALUES (new.session_id, new.uuid, 'reembed', datetime('now'));
@@ -618,8 +668,8 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_target
618
668
  ON bug_synthesis_results(scope, target_id, created_at DESC);
619
669
  CREATE INDEX IF NOT EXISTS idx_synth_results_created
620
670
  ON bug_synthesis_results(created_at DESC);
621
- `});import*as mt from"sqlite-vec";function p(){if(O)return O;x(),O=new at(oe),mt.load(O),O.pragma("cache_size = -64000"),O.pragma("mmap_size = 268435456"),O.pragma("temp_store = MEMORY"),O.pragma("busy_timeout = 5000"),O.pragma("journal_size_limit = 67108864"),O.pragma("wal_autocheckpoint = 1000"),O.exec(dt),ut(O);try{O.exec("PRAGMA optimize")}catch{}return O}function gt(){if(O){try{O.exec("PRAGMA optimize")}catch{}try{O.exec("INSERT INTO messages_fts(messages_fts, rank) VALUES('merge', 4);")}catch{}try{O.exec("INSERT INTO sessions_fts(sessions_fts, rank) VALUES('merge', 4);")}catch{}try{O.pragma("wal_checkpoint(TRUNCATE)")}catch{}O.close(),O=null}}var O,L=$(()=>{"use strict";ct();F();pt();O=null});import{writeFileSync as Cn}from"node:fs";import{join as kn}from"node:path";function V(e){return e.trim().toLowerCase().replace(/^#+/,"").replace(/[\s/\\]+/g,"-").replace(/[^a-z0-9\-._]/g,"").slice(0,40)}function ge(e,t){let s=V(t);if(!s)throw new Error("tag must contain at least one alphanumeric character");let n=p(),i=new Date().toISOString();return n.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,s)?{tag:s,added:!1}:(n.transaction(()=>{n.prepare("INSERT INTO session_tags (session_id, tag, created_at) VALUES (?, ?, ?)").run(e,s,i),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'add', ?)").run(e,s,i)})(),Et(),{tag:s,added:!0})}function ft(e,t){let s=V(t);if(!s)return{tag:"",removed:!1};let n=p(),i=new Date().toISOString();return n.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,s)?(n.transaction(()=>{n.prepare("DELETE FROM session_tags WHERE session_id = ? AND tag = ?").run(e,s),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'remove', ?)").run(e,s,i)})(),Et(),{tag:s,removed:!0}):{tag:s,removed:!1}}function _e(e){return p().prepare("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(e).map(t=>t.tag)}function ht(){return p().prepare(`SELECT tag, COUNT(*) AS count FROM session_tags
622
- GROUP BY tag ORDER BY count DESC, tag ASC`).all()}function Et(){try{x();let e=p(),t=e.prepare("SELECT session_id, tag, created_at FROM session_tags ORDER BY session_id, tag").all(),s=e.prepare("SELECT id, session_id, tag, action, at FROM tag_events ORDER BY at ASC, id ASC").all(),n={schema:"claude-recall.tags.v1",backed_up_at:new Date().toISOString(),current:t,events:s};Cn(Dn,JSON.stringify(n,null,2))}catch(e){console.error("[tags] backup failed:",e)}}var Dn,fe=$(()=>{"use strict";L();F();Dn=kn(S,"tags.json")});function Fn(e,t){let s=e.filter(r=>r.content_text&&r.content_text.trim().length>0);if(s.length<=t)return s;let n=new Set;n.add(0),n.add(s.length-1);let i=(s.length-2)/Math.max(1,t-2);for(let r=1;r<t-1;r++)n.add(Math.floor(r*i));return Array.from(n).sort((r,o)=>r-o).slice(0,t).map(r=>s[r])}function he(e){let t=p(),s={limit:e.limit??500},n=e.sessionIds&&e.sessionIds.length>0,i=n?"1=1":"s.message_count > 2";if(n){let o=e.sessionIds.map((a,c)=>`@sid_${c}`).join(", ");i+=` AND s.id IN (${o})`,e.sessionIds.forEach((a,c)=>{s[`sid_${c}`]=a})}return e.untaggedOnly&&(i+=" AND NOT EXISTS (SELECT 1 FROM session_tags st WHERE st.session_id = s.id)"),e.project&&(i+=" AND p.name = @project",s.project=e.project),e.collectionId&&(i+=" AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id = @col)",s.col=e.collectionId),t.prepare(`SELECT s.id, p.name AS project, s.git_branch,
671
+ `});import*as mt from"sqlite-vec";function m(){if(O)return O;x(),O=new at(oe),mt.load(O),O.pragma("cache_size = -64000"),O.pragma("mmap_size = 268435456"),O.pragma("temp_store = MEMORY"),O.pragma("busy_timeout = 5000"),O.pragma("journal_size_limit = 67108864"),O.pragma("wal_autocheckpoint = 1000"),O.exec(dt),ut(O);try{O.exec("PRAGMA optimize")}catch{}return O}function gt(){if(O){try{O.exec("PRAGMA optimize")}catch{}try{O.exec("INSERT INTO messages_fts(messages_fts, rank) VALUES('merge', 4);")}catch{}try{O.exec("INSERT INTO sessions_fts(sessions_fts, rank) VALUES('merge', 4);")}catch{}try{O.pragma("wal_checkpoint(TRUNCATE)")}catch{}O.close(),O=null}}var O,L=$(()=>{"use strict";ct();M();pt();O=null});import{writeFileSync as Cn}from"node:fs";import{join as kn}from"node:path";function V(e){return e.trim().toLowerCase().replace(/^#+/,"").replace(/[\s/\\]+/g,"-").replace(/[^a-z0-9\-._]/g,"").slice(0,40)}function ge(e,t){let s=V(t);if(!s)throw new Error("tag must contain at least one alphanumeric character");let n=m(),i=new Date().toISOString();return n.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,s)?{tag:s,added:!1}:(n.transaction(()=>{n.prepare("INSERT INTO session_tags (session_id, tag, created_at) VALUES (?, ?, ?)").run(e,s,i),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'add', ?)").run(e,s,i)})(),Et(),{tag:s,added:!0})}function ft(e,t){let s=V(t);if(!s)return{tag:"",removed:!1};let n=m(),i=new Date().toISOString();return n.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(e,s)?(n.transaction(()=>{n.prepare("DELETE FROM session_tags WHERE session_id = ? AND tag = ?").run(e,s),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'remove', ?)").run(e,s,i)})(),Et(),{tag:s,removed:!0}):{tag:s,removed:!1}}function _e(e){return m().prepare("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(e).map(t=>t.tag)}function ht(){return m().prepare(`SELECT tag, COUNT(*) AS count FROM session_tags
672
+ GROUP BY tag ORDER BY count DESC, tag ASC`).all()}function Et(){try{x();let e=m(),t=e.prepare("SELECT session_id, tag, created_at FROM session_tags ORDER BY session_id, tag").all(),s=e.prepare("SELECT id, session_id, tag, action, at FROM tag_events ORDER BY at ASC, id ASC").all(),n={schema:"claude-recall.tags.v1",backed_up_at:new Date().toISOString(),current:t,events:s};Cn(Dn,JSON.stringify(n,null,2))}catch(e){console.error("[tags] backup failed:",e)}}var Dn,fe=$(()=>{"use strict";L();M();Dn=kn(b,"tags.json")});function Fn(e,t){let s=e.filter(r=>r.content_text&&r.content_text.trim().length>0);if(s.length<=t)return s;let n=new Set;n.add(0),n.add(s.length-1);let i=(s.length-2)/Math.max(1,t-2);for(let r=1;r<t-1;r++)n.add(Math.floor(r*i));return Array.from(n).sort((r,o)=>r-o).slice(0,t).map(r=>s[r])}function he(e){let t=m(),s={limit:e.limit??500},n=e.sessionIds&&e.sessionIds.length>0,i=n?"1=1":"s.message_count > 2";if(n){let o=e.sessionIds.map((a,c)=>`@sid_${c}`).join(", ");i+=` AND s.id IN (${o})`,e.sessionIds.forEach((a,c)=>{s[`sid_${c}`]=a})}return e.untaggedOnly&&(i+=" AND NOT EXISTS (SELECT 1 FROM session_tags st WHERE st.session_id = s.id)"),e.project&&(i+=" AND p.name = @project",s.project=e.project),e.collectionId&&(i+=" AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id = @col)",s.col=e.collectionId),t.prepare(`SELECT s.id, p.name AS project, s.git_branch,
623
673
  NULLIF(sa.alias, '') AS alias,
624
674
  COALESCE(s.first_user_message, '') AS first_user_message
625
675
  FROM sessions s
@@ -632,62 +682,62 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
632
682
  ORDER BY COALESCE(timestamp, ''), rowid`).all(o.id),l=Fn(a,5).map(d=>`${d.role}: ${d.content_text.slice(0,400)}`).join(`
633
683
  ---
634
684
  `);return{id:o.id,project:o.project,git_branch:o.git_branch,alias:o.alias,first_user_message:o.first_user_message,message_sample:l,current_tags:_e(o.id)}})}var ke=$(()=>{"use strict";L();fe()});import{z as j}from"zod";function Me(e){let t=e.minTags??2,s=e.maxTags??4,n=e.untaggedOnly??!e.sessionId,i=["Auto-tag my Claude Recall sessions using the MCP tools available to you.","","1. Call `list_tags` first to see which tags I already use (prefer those for consistency over inventing new ones).","2. Call `list_sessions_to_tag` with these filters:"],r=[];return e.sessionId?(r.push("limit: 1"),i.push(` ${r.join(", ")}`),i.push(` Then match the session id ${e.sessionId} from the returned list.`)):(n&&r.push("untaggedOnly: true"),e.project&&r.push(`project: "${e.project}"`),e.collectionId&&r.push(`collectionId: "${e.collectionId}"`),r.push(`limit: ${e.limit??100}`),i.push(` ${r.join(", ")}`)),i.push(""),i.push(`3. For each session returned, look at the alias, first user message, git branch, and sampled messages. Pick ${t}-${s} concise, lowercase, hyphen-separated tags describing:`),i.push(" - domain/subsystem (auth, db, frontend, billing, etc.)"),i.push(" - kind of work (bugfix, feature, refactor, research)"),i.push(" - prominent tools or libraries if relevant"),i.push(""),i.push("4. Call `apply_tags` once per session to write the tags."),i.push(""),i.push("Work through EVERY session returned \u2014 do not stop partway. When done, reply with a short summary of how many sessions were tagged. Do not ask clarifying questions; just do the work."),i.join(`
635
- `)}function Yn(e){let t=e.mode==="detailed";return[`Summarize Claude Recall session ${e.sessionId} using the MCP tools available to you.`,"",`1. Call \`context_for_session\` with id "${e.sessionId}" and mode "condensed" to get the transcript as markdown.`,t?"2. Write a 1-paragraph overview (\u22643 sentences) of what this session was for, then 5-8 bullet points covering:":"2. Write 3-5 bullet points covering:"," - What was accomplished (shipped, decided, learned)"," - What was tried and abandoned"," - Any explicit open questions or follow-ups","","Rules:",'- Be concrete. Name files, functions, and decisions. Avoid vague "discussed X".',"- If nothing was actually shipped or decided, say so plainly.",'- Reply with just the summary \u2014 no preamble, no "Here is the summary:".'].join(`
636
- `)}function Kn(e){return[`Extract every architectural or product decision made in Claude Recall session ${e.sessionId}.`,"",`1. Call \`context_for_session\` with id "${e.sessionId}" and mode "full" (so tool I/O is included \u2014 sometimes the decision lives in a commit message or diff).`,"2. For each distinct decision, emit one block:",""," - **Decision:** one sentence."," - **Why:** the stated rationale (quote if possible).",' - **Alternatives considered:** bullet list, or "none mentioned".',' - **Where it landed:** file path / function name / commit SHA if identifiable, otherwise "TBD".',"","Rules:","- Include only decisions that were actually made, not ideas merely discussed.","- If an alternative was rejected, list it with a one-line reason.","- Group related decisions under a short heading when useful.",'- If the session made zero real decisions, reply exactly with: "No decisions made in this session."',"- No preamble, no closing, just the decision blocks."].join(`
685
+ `)}function Gn(e){let t=e.mode==="detailed";return[`Summarize Claude Recall session ${e.sessionId} using the MCP tools available to you.`,"",`1. Call \`context_for_session\` with id "${e.sessionId}" and mode "condensed" to get the transcript as markdown.`,t?"2. Write a 1-paragraph overview (\u22643 sentences) of what this session was for, then 5-8 bullet points covering:":"2. Write 3-5 bullet points covering:"," - What was accomplished (shipped, decided, learned)"," - What was tried and abandoned"," - Any explicit open questions or follow-ups","","Rules:",'- Be concrete. Name files, functions, and decisions. Avoid vague "discussed X".',"- If nothing was actually shipped or decided, say so plainly.",'- Reply with just the summary \u2014 no preamble, no "Here is the summary:".'].join(`
686
+ `)}function zn(e){return[`Extract every architectural or product decision made in Claude Recall session ${e.sessionId}.`,"",`1. Call \`context_for_session\` with id "${e.sessionId}" and mode "full" (so tool I/O is included \u2014 sometimes the decision lives in a commit message or diff).`,"2. For each distinct decision, emit one block:",""," - **Decision:** one sentence."," - **Why:** the stated rationale (quote if possible).",' - **Alternatives considered:** bullet list, or "none mentioned".',' - **Where it landed:** file path / function name / commit SHA if identifiable, otherwise "TBD".',"","Rules:","- Include only decisions that were actually made, not ideas merely discussed.","- If an alternative was rejected, list it with a one-line reason.","- Group related decisions under a short heading when useful.",'- If the session made zero real decisions, reply exactly with: "No decisions made in this session."',"- No preamble, no closing, just the decision blocks."].join(`
637
687
  `)}function Jn(e){let t=e.limit??5;return[`Find ${t} Recall sessions most similar to session ${e.sessionId}.`,"",`1. Call \`get_session\` with id "${e.sessionId}" \u2014 note its alias, first user message, tags, and git branch.`,'2. Derive 2-3 short search queries from that content (topic words, library names, error strings \u2014 NOT generic words like "fix" or "add").',`3. Call \`search\` once per query (limit: ${Math.max(5,t*2)}). Dedupe hits by session_id.`,"4. Also call `list_sessions` with the same tag(s) if the target session has any (pick the most specific tag).","5. Union the results. Exclude the target session itself. Rank by a mix of:"," - Shared tags (strongest signal)"," - Matching search hits across multiple queries"," - Recency as a tiebreaker","",`6. Return the top ${t} as a numbered list. For each:`," - **<short_id> \xB7 <project>** \u2014 <alias or first_user_message truncated>",' - One sentence on WHY it is similar (not a generic "same topic" \u2014 be specific).',"","If fewer than 2 genuinely-similar sessions exist, say so rather than padding with weak matches.","No preamble. Just the ranked list."].join(`
638
- `)}var Bn,Wn,Gn,zn,qn,Vn,Qn,Zn,St,Ue=$(()=>{"use strict";Bn={project:j.string().optional().describe("Exact project name match (optional)."),collectionId:j.string().optional().describe("Restrict to sessions in this collection (optional)."),sessionId:j.string().optional().describe("Full session UUID to tag just one session (optional)."),untaggedOnly:j.boolean().optional().describe("Skip sessions that already have any tag (default: true)."),limit:j.number().int().min(1).max(500).optional().describe("Max sessions to process (default: 100)."),minTags:j.number().int().min(1).max(10).optional().describe("Minimum tags per session (default: 2)."),maxTags:j.number().int().min(1).max(10).optional().describe("Maximum tags per session (default: 4).")};Wn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to summarize."),mode:j.enum(["brief","detailed"]).optional().describe("brief = 3-5 bullets; detailed = paragraph + bullets. Default: brief.")};Gn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")};zn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to find similar sessions to."),limit:j.number().int().min(1).max(20).optional().describe("How many similar sessions to surface (default: 5).")};qn={name:"auto_tag_sessions",title:"Auto-tag Recall sessions",description:"Have the agent auto-tag Recall sessions using the Recall MCP tools. Scope can be restricted to a project, collection, or single session.",argsSchema:Bn,build:Me,allowedTools:["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"]},Vn={name:"summarize_session",title:"Summarize a session",description:"Produce a concise, concrete summary of one session \u2014 what shipped, what was tried, what's still open.",argsSchema:Wn,build:Yn,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Qn={name:"extract_decisions",title:"Extract architectural decisions",description:"Scan a session and emit one structured block per architectural / product decision: what, why, alternatives, where it landed.",argsSchema:Gn,build:Kn,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Zn={name:"find_similar_sessions",title:"Find similar sessions",description:"Given a session, find other sessions that touched the same topic / library / error \u2014 ranked with reasons.",argsSchema:zn,build:Jn,allowedTools:["mcp__recall__get_session","mcp__recall__search","mcp__recall__list_sessions","mcp__recall__list_tags"]},St=[qn,Vn,Qn,Zn]});function ts(e,t){let s=Ui.get(e);if(!(!s||s.size===0))for(let n of s)try{n(t)}catch{}}var Ui,ss=$(()=>{"use strict";Ui=new Map});var rs={};Ie(rs,{buildScanPrompt:()=>ns,isClaudeCliAvailable:()=>Bi,runClaudeCliScan:()=>zi,spawnClaudePrompt:()=>Ji});import{execFileSync as Pi,execSync as $i,spawn as ji}from"node:child_process";function Hi(){if(le)return le;try{le=$i("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{le="claude"}return le}function Bi(){try{return Pi("command",["-v","claude"],{stdio:"ignore"}),!0}catch{return!1}}function ns(e){return Me({project:e.project,collectionId:e.collectionId,sessionId:e.sessionIds&&e.sessionIds.length===1?e.sessionIds[0]:void 0,untaggedOnly:e.untaggedOnly,limit:e.limit})}function Wi(e,t){let s=t.get(e);return s||e.slice(0,8)}function Yi(e){try{return he(e).map(s=>({id:s.id,label:s.alias&&s.alias.trim().length>0?s.alias:s.first_user_message&&s.first_user_message.trim().length>0?s.first_user_message.slice(0,60):s.id.slice(0,8)}))}catch{return[]}}function Gi(e){let{scanId:t,total:s,labelTable:n}=e,i=new Set;return r=>{let o=r.trim();if(!o.startsWith("{"))return;let a;try{a=JSON.parse(o)}catch{return}if(!a||typeof a!="object")return;let c=a;if(!(c.type!=="assistant"||!c.message?.content))for(let l of c.message.content){if(l?.type!=="tool_use"||l.name!=="mcp__recall__apply_tags")continue;let d=l.input,u=typeof d?.sessionId=="string"?d.sessionId:null;!u||i.has(u)||(i.add(u),ts(t,{type:"progress",current:i.size,total:s,sessionId:u,sessionLabel:Wi(u,n)}))}}}function Ki(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
688
+ `)}var Bn,Wn,Yn,Kn,qn,Vn,Qn,Zn,St,Ue=$(()=>{"use strict";Bn={project:j.string().optional().describe("Exact project name match (optional)."),collectionId:j.string().optional().describe("Restrict to sessions in this collection (optional)."),sessionId:j.string().optional().describe("Full session UUID to tag just one session (optional)."),untaggedOnly:j.boolean().optional().describe("Skip sessions that already have any tag (default: true)."),limit:j.number().int().min(1).max(500).optional().describe("Max sessions to process (default: 100)."),minTags:j.number().int().min(1).max(10).optional().describe("Minimum tags per session (default: 2)."),maxTags:j.number().int().min(1).max(10).optional().describe("Maximum tags per session (default: 4).")};Wn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to summarize."),mode:j.enum(["brief","detailed"]).optional().describe("brief = 3-5 bullets; detailed = paragraph + bullets. Default: brief.")};Yn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")};Kn={sessionId:j.string().describe("Session UUID (or 8+-char prefix) to find similar sessions to."),limit:j.number().int().min(1).max(20).optional().describe("How many similar sessions to surface (default: 5).")};qn={name:"auto_tag_sessions",title:"Auto-tag Recall sessions",description:"Have the agent auto-tag Recall sessions using the Recall MCP tools. Scope can be restricted to a project, collection, or single session.",argsSchema:Bn,build:Me,allowedTools:["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"]},Vn={name:"summarize_session",title:"Summarize a session",description:"Produce a concise, concrete summary of one session \u2014 what shipped, what was tried, what's still open.",argsSchema:Wn,build:Gn,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Qn={name:"extract_decisions",title:"Extract architectural decisions",description:"Scan a session and emit one structured block per architectural / product decision: what, why, alternatives, where it landed.",argsSchema:Yn,build:zn,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Zn={name:"find_similar_sessions",title:"Find similar sessions",description:"Given a session, find other sessions that touched the same topic / library / error \u2014 ranked with reasons.",argsSchema:Kn,build:Jn,allowedTools:["mcp__recall__get_session","mcp__recall__search","mcp__recall__list_sessions","mcp__recall__list_tags"]},St=[qn,Vn,Qn,Zn]});function ts(e,t){let s=Ui.get(e);if(!(!s||s.size===0))for(let n of s)try{n(t)}catch{}}var Ui,ss=$(()=>{"use strict";Ui=new Map});var rs={};Ie(rs,{buildScanPrompt:()=>ns,isClaudeCliAvailable:()=>Bi,runClaudeCliScan:()=>Ki,spawnClaudePrompt:()=>Ji});import{execFileSync as Pi,execSync as $i,spawn as ji}from"node:child_process";function Hi(){if(le)return le;try{le=$i("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{le="claude"}return le}function Bi(){try{return Pi("command",["-v","claude"],{stdio:"ignore"}),!0}catch{return!1}}function ns(e){return Me({project:e.project,collectionId:e.collectionId,sessionId:e.sessionIds&&e.sessionIds.length===1?e.sessionIds[0]:void 0,untaggedOnly:e.untaggedOnly,limit:e.limit})}function Wi(e,t){let s=t.get(e);return s||e.slice(0,8)}function Gi(e){try{return he(e).map(s=>({id:s.id,label:s.alias&&s.alias.trim().length>0?s.alias:s.first_user_message&&s.first_user_message.trim().length>0?s.first_user_message.slice(0,60):s.id.slice(0,8)}))}catch{return[]}}function Yi(e){let{scanId:t,total:s,labelTable:n}=e,i=new Set;return r=>{let o=r.trim();if(!o.startsWith("{"))return;let a;try{a=JSON.parse(o)}catch{return}if(!a||typeof a!="object")return;let c=a;if(!(c.type!=="assistant"||!c.message?.content))for(let l of c.message.content){if(l?.type!=="tool_use"||l.name!=="mcp__recall__apply_tags")continue;let d=l.input,u=typeof d?.sessionId=="string"?d.sessionId:null;!u||i.has(u)||(i.add(u),ts(t,{type:"progress",current:i.size,total:s,sessionId:u,sessionLabel:Wi(u,n)}))}}}function zi(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
639
689
  `);for(;n!==-1;){let i=t.slice(0,n);t=t.slice(n+1),i.length>0&&e(i),n=t.indexOf(`
640
- `)}}}async function zi(e,t={},s){let n=!!t.scanId,i=n?Yi(e):[],r=new Map(i.map(c=>[c.id,c.label])),o=i.length,a;return n&&t.scanId&&(a=Gi({scanId:t.scanId,total:o,labelTable:r})),is({prompt:ns(e),allowedTools:Xi.split(","),opts:t,onProgress:s,onStdoutLine:a,outputFormat:n?"stream-json":"json"})}async function Ji(e,t,s={},n){return is({prompt:e,allowedTools:t,opts:s,onProgress:n,outputFormat:"json"})}function is(e){let{prompt:t,allowedTools:s,opts:n,onProgress:i,onStdoutLine:r,outputFormat:o}=e,a=["-p",t,"--output-format",o,"--allowedTools",s.join(","),"--permission-mode","bypassPermissions"];return o==="stream-json"&&a.push("--verbose"),n.model&&a.push("--model",n.model),new Promise(c=>{let l=ji(Hi(),a,{stdio:["ignore","pipe","pipe"]}),d=[],u=[],m=r?Ki(r):void 0;l.stdout.on("data",_=>{d.push(_),m&&m(_)}),l.stderr.on("data",_=>{if(u.push(_),i){let f=_.toString("utf8").trim();f&&i(f)}});let g=setTimeout(()=>{l.kill("SIGKILL")},1800*1e3);l.on("close",_=>{clearTimeout(g),c({success:_===0,stdout:Buffer.concat(d).toString("utf8"),stderr:Buffer.concat(u).toString("utf8"),exitCode:_})}),l.on("error",_=>{clearTimeout(g),c({success:!1,stdout:"",stderr:String(_),exitCode:null})})})}var Xi,le,os=$(()=>{"use strict";ke();Ue();ss();Xi=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});import Y from"chalk";import{formatDistanceToNowStrict as Cl,parseISO as kl}from"date-fns";var E,it=$(()=>{"use strict";E={dim:Y.gray,bold:Y.bold,project:Y.cyan,user:Y.blue,assistant:Y.green,tool:Y.magenta,warn:Y.yellow,err:Y.red,ok:Y.green,accent:Y.hex("#f97316")}});var gn={};Ie(gn,{buildHealthReport:()=>mn,buildPipelineDiagnostic:()=>pn,getFreeDiskBytes:()=>Ro,runDoctor:()=>bo});import{existsSync as rn,readFileSync as po,statSync as on,statfsSync as an}from"node:fs";import{join as cn}from"node:path";import*as ln from"node:http";function _o(e){let t=[];for(let s of e)if(s.alias){if(go.test(s.alias)){t.push({session_id:s.session_id,alias:s.alias,violation:"fabricated-origin-label",cwd:s.cwd});continue}if(s.cwd){let n=s.cwd.replace(/\/+$/,"").split("/").pop();n&&s.alias.startsWith(`${n} \xB7 `)&&t.push({session_id:s.session_id,alias:s.alias,violation:"fabricated-cwd-branch",cwd:s.cwd})}}return t}function fo(){let e=cn(S,"daemon.port");if(!rn(e))return null;try{let t=po(e,"utf8").trim();if(t.startsWith("{")){let n=JSON.parse(t);return typeof n.port=="number"?n.port:null}let s=Number.parseInt(t,10);return Number.isFinite(s)&&s>0&&s<65536?s:null}catch{return null}}function ho(e,t,s=1500){return new Promise(n=>{let i=ln.request({host:"127.0.0.1",port:e,path:t,method:"GET",timeout:s,headers:{host:"127.0.0.1","user-agent":"recall-doctor"}},r=>{let o=[];r.on("data",a=>o.push(Buffer.isBuffer(a)?a:Buffer.from(a))),r.on("end",()=>{if(!r.statusCode||r.statusCode<200||r.statusCode>=300){n(null);return}try{n(JSON.parse(Buffer.concat(o).toString("utf8")))}catch{n(null)}})});i.on("error",()=>n(null)),i.on("timeout",()=>{i.destroy(),n(null)}),i.end()})}function Eo(){let e=cn(S,"terminals.json");if(!rn(e))return{exists:!1,mtimeMs:null,ageSeconds:null};try{let t=on(e),s=Math.floor((Date.now()-t.mtimeMs)/1e3);return{exists:!0,mtimeMs:t.mtimeMs,ageSeconds:s}}catch{return{exists:!1,mtimeMs:null,ageSeconds:null}}}function To(){let e=new Date(Date.now()-rt*36e5).toISOString();try{let t=p().prepare(`SELECT
690
+ `)}}}async function Ki(e,t={},s){let n=!!t.scanId,i=n?Gi(e):[],r=new Map(i.map(c=>[c.id,c.label])),o=i.length,a;return n&&t.scanId&&(a=Yi({scanId:t.scanId,total:o,labelTable:r})),is({prompt:ns(e),allowedTools:Xi.split(","),opts:t,onProgress:s,onStdoutLine:a,outputFormat:n?"stream-json":"json"})}async function Ji(e,t,s={},n){return is({prompt:e,allowedTools:t,opts:s,onProgress:n,outputFormat:"json"})}function is(e){let{prompt:t,allowedTools:s,opts:n,onProgress:i,onStdoutLine:r,outputFormat:o}=e,a=["-p",t,"--output-format",o,"--allowedTools",s.join(","),"--permission-mode","bypassPermissions"];return o==="stream-json"&&a.push("--verbose"),n.model&&a.push("--model",n.model),new Promise(c=>{let l=ji(Hi(),a,{stdio:["ignore","pipe","pipe"]}),d=[],u=[],p=r?zi(r):void 0;l.stdout.on("data",_=>{d.push(_),p&&p(_)}),l.stderr.on("data",_=>{if(u.push(_),i){let f=_.toString("utf8").trim();f&&i(f)}});let g=setTimeout(()=>{l.kill("SIGKILL")},1800*1e3);l.on("close",_=>{clearTimeout(g),c({success:_===0,stdout:Buffer.concat(d).toString("utf8"),stderr:Buffer.concat(u).toString("utf8"),exitCode:_})}),l.on("error",_=>{clearTimeout(g),c({success:!1,stdout:"",stderr:String(_),exitCode:null})})})}var Xi,le,os=$(()=>{"use strict";ke();Ue();ss();Xi=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});import G from"chalk";import{formatDistanceToNowStrict as Cl,parseISO as kl}from"date-fns";var E,it=$(()=>{"use strict";E={dim:G.gray,bold:G.bold,project:G.cyan,user:G.blue,assistant:G.green,tool:G.magenta,warn:G.yellow,err:G.red,ok:G.green,accent:G.hex("#f97316")}});var gn={};Ie(gn,{buildHealthReport:()=>mn,buildPipelineDiagnostic:()=>pn,getFreeDiskBytes:()=>Ro,runDoctor:()=>bo});import{existsSync as rn,readFileSync as po,statSync as on,statfsSync as an}from"node:fs";import{join as cn}from"node:path";import*as ln from"node:http";function _o(e){let t=[];for(let s of e)if(s.alias){if(go.test(s.alias)){t.push({session_id:s.session_id,alias:s.alias,violation:"fabricated-origin-label",cwd:s.cwd});continue}if(s.cwd){let n=s.cwd.replace(/\/+$/,"").split("/").pop();n&&s.alias.startsWith(`${n} \xB7 `)&&t.push({session_id:s.session_id,alias:s.alias,violation:"fabricated-cwd-branch",cwd:s.cwd})}}return t}function fo(){let e=cn(b,"daemon.port");if(!rn(e))return null;try{let t=po(e,"utf8").trim();if(t.startsWith("{")){let n=JSON.parse(t);return typeof n.port=="number"?n.port:null}let s=Number.parseInt(t,10);return Number.isFinite(s)&&s>0&&s<65536?s:null}catch{return null}}function ho(e,t,s=1500){return new Promise(n=>{let i=ln.request({host:"127.0.0.1",port:e,path:t,method:"GET",timeout:s,headers:{host:"127.0.0.1","user-agent":"recall-doctor"}},r=>{let o=[];r.on("data",a=>o.push(Buffer.isBuffer(a)?a:Buffer.from(a))),r.on("end",()=>{if(!r.statusCode||r.statusCode<200||r.statusCode>=300){n(null);return}try{n(JSON.parse(Buffer.concat(o).toString("utf8")))}catch{n(null)}})});i.on("error",()=>n(null)),i.on("timeout",()=>{i.destroy(),n(null)}),i.end()})}function Eo(){let e=cn(b,"terminals.json");if(!rn(e))return{exists:!1,mtimeMs:null,ageSeconds:null};try{let t=on(e),s=Math.floor((Date.now()-t.mtimeMs)/1e3);return{exists:!0,mtimeMs:t.mtimeMs,ageSeconds:s}}catch{return{exists:!1,mtimeMs:null,ageSeconds:null}}}function To(){let e=new Date(Date.now()-rt*36e5).toISOString();try{let t=m().prepare(`SELECT
641
691
  COUNT(*) AS total,
642
692
  SUM(CASE WHEN sa.alias IS NULL OR sa.alias = '' THEN 1 ELSE 0 END) AS without_alias,
643
693
  SUM(CASE WHEN (sa.alias IS NULL OR sa.alias = '')
644
694
  AND s.auto_title_source = 'heuristic' THEN 1 ELSE 0 END) AS heuristic_only
645
695
  FROM sessions s
646
696
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
647
- WHERE s.started_at >= ?`).get(e),s=t.total??0,n=t.without_alias??0,i=t.heuristic_only??0,r=s>0?i/s:0;return{total:s,withoutAlias:n,heuristicOnly:i,fractionHeuristic:r}}catch{return{total:0,withoutAlias:0,heuristicOnly:0,fractionHeuristic:0}}}async function pn(){let e=fo(),t=Eo(),s=To(),n=!1,i=null,r=null,o=null,a=null;if(e){let d=await ho(e,"/api/health");d&&(n=!0,i=typeof d.uptimeSeconds=="number"?d.uptimeSeconds:null,r=typeof d.version=="string"?d.version:null,o=typeof d.pipeline?.silentTerminalRejections=="number"?d.pipeline.silentTerminalRejections:null,a=d.pipeline?.lastTerminalSyncAt??null)}let c=[];if(n||c.push("Daemon not reachable on 127.0.0.1 \u2014 start it with `recall start` before further diagnosis."),o!==null&&o>0&&c.push(`Daemon rejected ${o.toLocaleString()} /api/terminal/* request(s) without a valid X-Recall-Token. The editor extension is outdated relative to this daemon \u2014 tab names are not flowing through. Reinstall: \`code --install-extension extensions/vscode/clauderecall-vscode-*.vsix\`, then reload the extension host (Cmd/Ctrl+Shift+P \u2192 "Developer: Restart Extension Host").`),n&&i!==null&&i>30)if(!a)c.push(`Daemon has been running ${Math.round(i/60)} min but has not yet seen a single successful /api/terminal/sync. Either no editor extension is installed/active, or every attempt is being rejected (see counter above).`);else{let d=Date.now()-Date.parse(a);Number.isFinite(d)&&d>dn&&c.push(`Last successful /api/terminal/sync was ${Math.round(d/6e4)} min ago \u2014 extension may have crashed, been disabled, or is failing auth. Run \`recall doctor\` again after restarting the extension host.`)}return!n&&t.exists&&t.ageSeconds!==null&&t.ageSeconds>24*3600&&c.push(`~/.recall/terminals.json is ${Math.round(t.ageSeconds/3600)}h old \u2014 pipeline has not produced fresh data recently. Likely root cause is the same as the rejection counter would show; start the daemon and re-run.`),s.total>=3&&s.fractionHeuristic>=un&&c.push(`${s.heuristicOnly}/${s.total} sessions in the last ${rt}h (${Math.round(s.fractionHeuristic*100)}%) fell back to the heuristic first-message title. A healthy pipeline rate is < 20%. Either the extension is not syncing tab names, or the correlator cannot disambiguate. Reinstall the extension and verify the rejection counter drops to 0.`),{state:n?c.length>0?"degraded":"ok":"down",flags:c,daemon:{running:n,port:e,uptimeSeconds:i,version:r},runtime:{silentTerminalRejections:o,lastTerminalSyncAt:a},terminalsJson:t,recentSessions:s}}function Z(e){return e<1024?`${e} B`:e<1024**2?`${(e/1024).toFixed(1)} KB`:e<1024**3?`${(e/1024**2).toFixed(1)} MB`:`${(e/1024**3).toFixed(2)} GB`}function sn(e){try{return on(e).size}catch{return 0}}function nn(e){try{return p().prepare(`SELECT COUNT(*) AS n FROM ${e}_data WHERE block = 1`).get().n}catch{return 0}}function mn(e){let t=p(),s=t.pragma("page_size",{simple:!0})||4096,n=t.pragma("page_count",{simple:!0})||0,i=t.pragma("freelist_count",{simple:!0})||0;e?.("Checking database integrity");let r="ok";try{let h=t.pragma("quick_check").map(R=>R.quick_check);r=h.length===1&&h[0]==="ok"?"ok":h.join("; ")}catch(f){r=`check failed: ${f.message}`}let o=sn(oe),a=sn(`${oe}-wal`),c=0,l=0;try{let f=an(S);c=Number(f.bavail)*Number(f.bsize),l=Number(f.blocks)*Number(f.bsize)}catch{}e?.("Counting rows");let d=t.prepare(`SELECT
697
+ WHERE s.started_at >= ?`).get(e),s=t.total??0,n=t.without_alias??0,i=t.heuristic_only??0,r=s>0?i/s:0;return{total:s,withoutAlias:n,heuristicOnly:i,fractionHeuristic:r}}catch{return{total:0,withoutAlias:0,heuristicOnly:0,fractionHeuristic:0}}}async function pn(){let e=fo(),t=Eo(),s=To(),n=!1,i=null,r=null,o=null,a=null,c=null;if(e){let u=await ho(e,"/api/health");if(u){n=!0,i=typeof u.uptimeSeconds=="number"?u.uptimeSeconds:null,r=typeof u.version=="string"?u.version:null,o=typeof u.pipeline?.silentTerminalRejections=="number"?u.pipeline.silentTerminalRejections:null,a=u.pipeline?.lastTerminalSyncAt??null;let p=u.pipeline?.autoExtract;p&&(c={circuitBroken:p.circuitBroken===!0,reason:p.reason??null,brokenAt:typeof p.brokenAt=="number"?p.brokenAt:null,consecutiveZeroTokenRuns:typeof p.consecutiveZeroTokenRuns=="number"?p.consecutiveZeroTokenRuns:0})}}let l=[];if(n||l.push("Daemon not reachable on 127.0.0.1 \u2014 start it with `recall start` before further diagnosis."),o!==null&&o>0&&l.push(`Daemon rejected ${o.toLocaleString()} /api/terminal/* request(s) without a valid X-Recall-Token. The editor extension is outdated relative to this daemon \u2014 tab names are not flowing through. Reinstall: \`code --install-extension extensions/vscode/clauderecall-vscode-*.vsix\`, then reload the extension host (Cmd/Ctrl+Shift+P \u2192 "Developer: Restart Extension Host").`),n&&i!==null&&i>30)if(!a)l.push(`Daemon has been running ${Math.round(i/60)} min but has not yet seen a single successful /api/terminal/sync. Either no editor extension is installed/active, or every attempt is being rejected (see counter above).`);else{let u=Date.now()-Date.parse(a);Number.isFinite(u)&&u>dn&&l.push(`Last successful /api/terminal/sync was ${Math.round(u/6e4)} min ago \u2014 extension may have crashed, been disabled, or is failing auth. Run \`recall doctor\` again after restarting the extension host.`)}return!n&&t.exists&&t.ageSeconds!==null&&t.ageSeconds>24*3600&&l.push(`~/.recall/terminals.json is ${Math.round(t.ageSeconds/3600)}h old \u2014 pipeline has not produced fresh data recently. Likely root cause is the same as the rejection counter would show; start the daemon and re-run.`),c?.circuitBroken&&l.push(`Auto-extract circuit breaker tripped \u2014 ${c.reason??"reason unknown"}. Run \`recall semantic auto-extract off\` then \`... on\` to reset, or restart the daemon.`),s.total>=3&&s.fractionHeuristic>=un&&l.push(`${s.heuristicOnly}/${s.total} sessions in the last ${rt}h (${Math.round(s.fractionHeuristic*100)}%) fell back to the heuristic first-message title. A healthy pipeline rate is < 20%. Either the extension is not syncing tab names, or the correlator cannot disambiguate. Reinstall the extension and verify the rejection counter drops to 0.`),{state:n?l.length>0?"degraded":"ok":"down",flags:l,daemon:{running:n,port:e,uptimeSeconds:i,version:r},runtime:{silentTerminalRejections:o,lastTerminalSyncAt:a,autoExtract:c},terminalsJson:t,recentSessions:s}}function Z(e){return e<1024?`${e} B`:e<1024**2?`${(e/1024).toFixed(1)} KB`:e<1024**3?`${(e/1024**2).toFixed(1)} MB`:`${(e/1024**3).toFixed(2)} GB`}function sn(e){try{return on(e).size}catch{return 0}}function nn(e){try{return m().prepare(`SELECT COUNT(*) AS n FROM ${e}_data WHERE block = 1`).get().n}catch{return 0}}function mn(e){let t=m(),s=t.pragma("page_size",{simple:!0})||4096,n=t.pragma("page_count",{simple:!0})||0,i=t.pragma("freelist_count",{simple:!0})||0;e?.("Checking database integrity");let r="ok";try{let y=t.pragma("quick_check").map(F=>F.quick_check);r=y.length===1&&y[0]==="ok"?"ok":y.join("; ")}catch(S){r=`check failed: ${S.message}`}let o=sn(oe),a=sn(`${oe}-wal`),c=0,l=0;try{let S=an(b);c=Number(S.bavail)*Number(S.bsize),l=Number(S.blocks)*Number(S.bsize)}catch{}e?.("Counting rows");let d=t.prepare(`SELECT
648
698
  (SELECT COUNT(*) FROM projects) AS projects,
649
699
  (SELECT COUNT(*) FROM sessions) AS sessions,
650
700
  (SELECT COUNT(*) FROM messages) AS messages,
651
- (SELECT COUNT(*) FROM message_usage) AS message_usage`).get(),u=0;try{u=t.prepare("SELECT COUNT(*) AS n FROM vec_chunks").get().n}catch{}e?.("Measuring FTS fragmentation");let m=[];c>0&&c<1*1024**3&&m.push(`Disk free is ${Z(c)} \u2014 heavy operations (synthesis, extract-outputs, vector ingest) may fail.`),a>50*1024**2&&m.push(`WAL is ${Z(a)} \u2014 run \`recall optimize\` to truncate it.`),i>n*.2&&n>1e3&&m.push(`${i.toLocaleString()} free pages (${(i/n*100).toFixed(0)}% of file) \u2014 \`recall optimize --vacuum\` will reclaim them.`);let g=nn("messages_fts"),_=nn("sessions_fts");return g>16&&m.push(`messages_fts has ${g} segments \u2014 \`recall optimize\` will merge them.`),{db:{sizeBytes:o,walSizeBytes:a,pageCount:n,pageSize:s,freelistCount:i,freelistBytes:i*s,integrity:r},disk:{freeBytes:c,totalBytes:l},fts:{messages:{fragments:g},sessions:{fragments:_}},vectors:{rows:u},rows:{projects:d.projects,sessions:d.sessions,messages:d.messages,messageUsage:d.message_usage},warnings:m}}function So(e){if(!e)return{stage:()=>{},done:()=>{}};let t=!!process.stderr.isTTY,s="",n=0,i=()=>{if(!s)return;let r=Date.now()-n,o=r<1e3?`${r}ms`:`${(r/1e3).toFixed(1)}s`;t?process.stderr.write(`\r\x1B[2K ${E.ok("\u2713")} ${s} ${E.dim(`(${o})`)}
701
+ (SELECT COUNT(*) FROM message_usage) AS message_usage`).get(),u=0;try{u=t.prepare("SELECT COUNT(*) AS n FROM vec_chunks").get().n}catch{}e?.("Measuring FTS fragmentation");let p=[];c>0&&c<1*1024**3&&p.push(`Disk free is ${Z(c)} \u2014 heavy operations (synthesis, extract-outputs, vector ingest) may fail.`),a>50*1024**2&&p.push(`WAL is ${Z(a)} \u2014 run \`recall optimize\` to truncate it.`),i>n*.2&&n>1e3&&p.push(`${i.toLocaleString()} free pages (${(i/n*100).toFixed(0)}% of file) \u2014 \`recall optimize --vacuum\` will reclaim them.`);let g=nn("messages_fts"),_=nn("sessions_fts");g>16&&p.push(`messages_fts has ${g} segments \u2014 \`recall optimize\` will merge them.`);let f=0;try{f=t.prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}catch{}let h=!1;try{h=t.prepare("SELECT value FROM app_settings WHERE key = 'semantic_enabled'").get()?.value==="1"}catch{}return!h&&f>0?p.push(`${f.toLocaleString()} rows in chunk_queue with semantic disabled \u2014 schema gate is stale; restart daemon (v0.67+) to migrate.`):f>1e5&&p.push(`chunk_queue has ${f.toLocaleString()} pending rows \u2014 embedder is behind. \`recall semantic backfill\` to drain manually.`),{db:{sizeBytes:o,walSizeBytes:a,pageCount:n,pageSize:s,freelistCount:i,freelistBytes:i*s,integrity:r},disk:{freeBytes:c,totalBytes:l},fts:{messages:{fragments:g},sessions:{fragments:_}},vectors:{rows:u},rows:{projects:d.projects,sessions:d.sessions,messages:d.messages,messageUsage:d.message_usage},chunkQueue:{size:f,semanticEnabled:h},warnings:p}}function So(e){if(!e)return{stage:()=>{},done:()=>{}};let t=!!process.stderr.isTTY,s="",n=0,i=()=>{if(!s)return;let r=Date.now()-n,o=r<1e3?`${r}ms`:`${(r/1e3).toFixed(1)}s`;t?process.stderr.write(`\r\x1B[2K ${E.ok("\u2713")} ${s} ${E.dim(`(${o})`)}
652
702
  `):process.stderr.write(` \u2713 ${s} (${o})
653
703
  `),s=""};return{stage(r){i(),s=r,n=Date.now(),t?process.stderr.write(` ${E.dim("\u2026")} ${s}`):process.stderr.write(` \u2026 ${s}
654
- `)},done:i}}async function bo(e={}){let t=So(!e.json);t.stage("Scanning session aliases");let s=p().prepare(`SELECT sa.session_id AS session_id, sa.alias AS alias, s.cwd AS cwd
704
+ `)},done:i}}async function bo(e={}){let t=So(!e.json);t.stage("Scanning session aliases");let s=m().prepare(`SELECT sa.session_id AS session_id, sa.alias AS alias, s.cwd AS cwd
655
705
  FROM session_aliases sa
656
706
  LEFT JOIN sessions s ON s.id = sa.session_id
657
707
  WHERE sa.alias IS NOT NULL AND sa.alias != ''`).all(),n=_o(s),i=mn(t.stage);t.stage("Probing daemon");let r=await pn();if(t.done(),e.json){process.stdout.write(JSON.stringify({scanned:s.length,violations:n.length,items:n,health:i,pipeline:r},null,2)),process.stdout.write(`
658
- `);let c=r.state==="degraded";return n.length===0&&i.db.integrity==="ok"&&!c?0:1}if(console.log(E.dim("\u2014 System health \u2014")),console.log(` Database ${Z(i.db.sizeBytes)} (${i.rows.messages.toLocaleString()} messages across ${i.rows.sessions.toLocaleString()} sessions, ${i.rows.projects.toLocaleString()} projects)`),console.log(` WAL ${Z(i.db.walSizeBytes)} (capped at 64 MB; truncated on clean shutdown)`),console.log(` Free pages ${i.db.freelistCount.toLocaleString()} (${Z(i.db.freelistBytes)} reclaimable via VACUUM)`),console.log(` FTS segments messages=${i.fts.messages.fragments}, sessions=${i.fts.sessions.fragments} (lower is faster \u2014 \`recall optimize\` merges them)`),console.log(` Vector rows ${i.vectors.rows.toLocaleString()}`),i.disk.totalBytes>0){let c=i.disk.freeBytes/i.disk.totalBytes*100;console.log(` Disk free ${Z(i.disk.freeBytes)} of ${Z(i.disk.totalBytes)} (${c.toFixed(1)}%)`)}if(console.log(` Integrity ${i.db.integrity==="ok"?E.ok("ok"):E.err(i.db.integrity)}`),i.warnings.length>0){console.log("");for(let c of i.warnings)console.log(` ${E.warn("!")} ${c}`)}if(console.log(""),console.log(E.dim("\u2014 Pipeline health (tab-name \u2192 session alias) \u2014")),r.daemon.running?console.log(` Daemon ${E.ok("running")} (port ${r.daemon.port}, version ${r.daemon.version??"?"}, up ${r.daemon.uptimeSeconds!==null?Math.round(r.daemon.uptimeSeconds/60)+" min":"?"})`):console.log(` Daemon ${E.warn("not reachable")}`),r.runtime.silentTerminalRejections!==null){let c=r.runtime.silentTerminalRejections;console.log(` Auth rejections ${c===0?E.ok("0"):E.err(c.toLocaleString())} (extension /api/terminal/* requests denied without a valid X-Recall-Token)`)}if(r.runtime.lastTerminalSyncAt!==null){let c=Date.now()-Date.parse(r.runtime.lastTerminalSyncAt),l=Number.isFinite(c)?Math.round(c/6e4):null,d=l!==null&&c>dn;console.log(` Last ext sync ${d?E.warn(`${l} min ago`):E.ok(l===0?"just now":`${l} min ago`)} (most recent successful POST /api/terminal/sync)`)}else r.daemon.running&&console.log(` Last ext sync ${E.warn("never")} (no extension has called /api/terminal/sync since the daemon started)`);if(r.terminalsJson.exists&&r.terminalsJson.ageSeconds!==null){let c=Math.round(r.terminalsJson.ageSeconds/3600),l=r.terminalsJson.ageSeconds>24*3600;console.log(` terminals.json ${l?E.warn(`${c}h old`):E.ok(`${c}h old`)} (persisted registry mtime \u2014 fresh means extensions are connecting)`)}let o=r.recentSessions;if(o.total>0){let c=Math.round(o.fractionHeuristic*100),l=o.fractionHeuristic>=un&&o.total>=3;console.log(` Recent titles ${l?E.err(`${c}% heuristic`):E.ok(`${c}% heuristic`)} (${o.heuristicOnly}/${o.total} sessions in last ${rt}h fell back to first-message title)`)}if(r.flags.length>0){console.log("");for(let c of r.flags)console.log(` ${E.warn("!")} ${c}`)}if(console.log(""),console.log(E.dim("\u2014 Tab-name invariant \u2014")),n.length===0)return console.log(E.ok(` \u2713 holds across ${s.length.toLocaleString()} aliased session${s.length===1?"":"s"}`)),console.log(E.dim(" No fabricated origin labels (`VS Code \xB7 cwd \xB7 branch`) found. No deprecated cwd-branch synthesis found.")),i.db.integrity==="ok"?0:1;console.log(E.err(`\u2717 ${n.length} invariant violation${n.length===1?"":"s"} found across ${s.length.toLocaleString()} aliased sessions`)),console.log("");let a=new Map;for(let c of n){let l=a.get(c.violation)??[];l.push(c),a.set(c.violation,l)}for(let[c,l]of a){console.log(E.warn(` ${c} (${l.length})`));for(let d of l.slice(0,10))console.log(` ${d.session_id.slice(0,8)} ${E.dim("\u2192")} ${JSON.stringify(d.alias)}`);l.length>10&&console.log(E.dim(` \u2026 and ${l.length-10} more (rerun with --json for the full list)`)),console.log("")}return console.log(E.dim('Remediation: `recall name <id-prefix> ""` clears a bad alias so the heuristic title takes over,\nor `recall name <id-prefix> "<actual tab name>"` sets it to the real value.')),1}function Ro(){try{let e=an(S);return Number(e.bavail)*Number(e.bsize)}catch{return 0}}var mo,go,dn,rt,un,_n=$(()=>{"use strict";it();L();F();mo=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],go=new RegExp(`^(${mo.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);dn=5*6e4,rt=24,un=.5});var fn={};Ie(fn,{runOptimize:()=>Ao});import{existsSync as yo,readFileSync as No}from"node:fs";import{join as wo}from"node:path";function Lo(){let e=wo(S,"daemon.pid");if(!yo(e))return!1;try{let t=parseInt(No(e,"utf-8").trim(),10);return!Number.isFinite(t)||t<=0?!1:(process.kill(t,0),!0)}catch{return!1}}async function ue(e,t){let s=Date.now();try{return t(),{step:e,ok:!0,durationMs:Date.now()-s}}catch(n){return{step:e,ok:!1,durationMs:Date.now()-s,error:n.message}}}async function Ao(e={}){let t=p(),s=[];if(e.vacuum&&Lo())return e.json?(process.stdout.write(JSON.stringify({ok:!1,error:"daemon-running",message:"VACUUM requires the daemon to be stopped. Run `recall stop`, then re-run with --vacuum."},null,2)+`
708
+ `);let c=r.state==="degraded";return n.length===0&&i.db.integrity==="ok"&&!c?0:1}console.log(E.dim("\u2014 System health \u2014")),console.log(` Database ${Z(i.db.sizeBytes)} (${i.rows.messages.toLocaleString()} messages across ${i.rows.sessions.toLocaleString()} sessions, ${i.rows.projects.toLocaleString()} projects)`),console.log(` WAL ${Z(i.db.walSizeBytes)} (capped at 64 MB; truncated on clean shutdown)`),console.log(` Free pages ${i.db.freelistCount.toLocaleString()} (${Z(i.db.freelistBytes)} reclaimable via VACUUM)`);{let c=i.fts.messages.fragments,l=i.fts.sessions.fragments,d=c===0&&l===0;console.log(` FTS segments messages=${c}, sessions=${l}`+(d?" (merged \u2014 search uses the index)":" (lower is faster \u2014 `recall optimize` merges them)"))}if(i.chunkQueue.size>0){let l=i.chunkQueue.size>1e5?E.warn(i.chunkQueue.size.toLocaleString()):i.chunkQueue.size.toLocaleString();console.log(` Embed queue ${l}`+(i.chunkQueue.semanticEnabled?" (worker is enabled; should drain over time)":" (semantic disabled \u2014 should be 0; if not, schema migration may be stale)"))}if(console.log(` Vector rows ${i.vectors.rows.toLocaleString()}`),i.disk.totalBytes>0){let c=i.disk.freeBytes/i.disk.totalBytes*100;console.log(` Disk free ${Z(i.disk.freeBytes)} of ${Z(i.disk.totalBytes)} (${c.toFixed(1)}%)`)}if(console.log(` Integrity ${i.db.integrity==="ok"?E.ok("ok"):E.err(i.db.integrity)}`),i.warnings.length>0){console.log("");for(let c of i.warnings)console.log(` ${E.warn("!")} ${c}`)}if(console.log(""),console.log(E.dim("\u2014 Pipeline health (tab-name \u2192 session alias) \u2014")),r.daemon.running?console.log(` Daemon ${E.ok("running")} (port ${r.daemon.port}, version ${r.daemon.version??"?"}, up ${r.daemon.uptimeSeconds!==null?Math.round(r.daemon.uptimeSeconds/60)+" min":"?"})`):console.log(` Daemon ${E.warn("not reachable")}`),r.runtime.silentTerminalRejections!==null){let c=r.runtime.silentTerminalRejections;console.log(` Auth rejections ${c===0?E.ok("0"):E.err(c.toLocaleString())} (extension /api/terminal/* requests denied without a valid X-Recall-Token)`)}if(r.runtime.autoExtract){let c=r.runtime.autoExtract;c.circuitBroken?console.log(` Auto-extract ${E.err("circuit broken")} (${c.reason??"unknown"} \u2014 toggle off/on to reset)`):c.consecutiveZeroTokenRuns>0&&console.log(` Auto-extract ${E.warn(`${c.consecutiveZeroTokenRuns} zero-token run(s)`)} (breaker trips at 3)`)}if(r.runtime.lastTerminalSyncAt!==null){let c=Date.now()-Date.parse(r.runtime.lastTerminalSyncAt),l=Number.isFinite(c)?Math.round(c/6e4):null,d=l!==null&&c>dn;console.log(` Last ext sync ${d?E.warn(`${l} min ago`):E.ok(l===0?"just now":`${l} min ago`)} (most recent successful POST /api/terminal/sync)`)}else r.daemon.running&&console.log(` Last ext sync ${E.warn("never")} (no extension has called /api/terminal/sync since the daemon started)`);if(r.terminalsJson.exists&&r.terminalsJson.ageSeconds!==null){let c=Math.round(r.terminalsJson.ageSeconds/3600),l=r.terminalsJson.ageSeconds>24*3600;console.log(` terminals.json ${l?E.warn(`${c}h old`):E.ok(`${c}h old`)} (persisted registry mtime \u2014 fresh means extensions are connecting)`)}let o=r.recentSessions;if(o.total>0){let c=Math.round(o.fractionHeuristic*100),l=o.fractionHeuristic>=un&&o.total>=3;console.log(` Recent titles ${l?E.err(`${c}% heuristic`):E.ok(`${c}% heuristic`)} (${o.heuristicOnly}/${o.total} sessions in last ${rt}h fell back to first-message title)`)}if(r.flags.length>0){console.log("");for(let c of r.flags)console.log(` ${E.warn("!")} ${c}`)}if(console.log(""),console.log(E.dim("\u2014 Tab-name invariant \u2014")),n.length===0)return console.log(E.ok(` \u2713 holds across ${s.length.toLocaleString()} aliased session${s.length===1?"":"s"}`)),console.log(E.dim(" No fabricated origin labels (`VS Code \xB7 cwd \xB7 branch`) found. No deprecated cwd-branch synthesis found.")),i.db.integrity==="ok"?0:1;console.log(E.err(`\u2717 ${n.length} invariant violation${n.length===1?"":"s"} found across ${s.length.toLocaleString()} aliased sessions`)),console.log("");let a=new Map;for(let c of n){let l=a.get(c.violation)??[];l.push(c),a.set(c.violation,l)}for(let[c,l]of a){console.log(E.warn(` ${c} (${l.length})`));for(let d of l.slice(0,10))console.log(` ${d.session_id.slice(0,8)} ${E.dim("\u2192")} ${JSON.stringify(d.alias)}`);l.length>10&&console.log(E.dim(` \u2026 and ${l.length-10} more (rerun with --json for the full list)`)),console.log("")}return console.log(E.dim('Remediation: `recall name <id-prefix> ""` clears a bad alias so the heuristic title takes over,\nor `recall name <id-prefix> "<actual tab name>"` sets it to the real value.')),1}function Ro(){try{let e=an(b);return Number(e.bavail)*Number(e.bsize)}catch{return 0}}var mo,go,dn,rt,un,_n=$(()=>{"use strict";it();L();M();mo=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],go=new RegExp(`^(${mo.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);dn=5*6e4,rt=24,un=.5});var fn={};Ie(fn,{runOptimize:()=>Ao});import{existsSync as yo,readFileSync as No}from"node:fs";import{join as wo}from"node:path";function Lo(){let e=wo(b,"daemon.pid");if(!yo(e))return!1;try{let t=parseInt(No(e,"utf-8").trim(),10);return!Number.isFinite(t)||t<=0?!1:(process.kill(t,0),!0)}catch{return!1}}async function ue(e,t){let s=Date.now();try{return t(),{step:e,ok:!0,durationMs:Date.now()-s}}catch(n){return{step:e,ok:!1,durationMs:Date.now()-s,error:n.message}}}async function Ao(e={}){let t=m(),s=[];if(e.vacuum&&Lo())return e.json?(process.stdout.write(JSON.stringify({ok:!1,error:"daemon-running",message:"VACUUM requires the daemon to be stopped. Run `recall stop`, then re-run with --vacuum."},null,2)+`
659
709
  `),2):(console.error(E.err("\u2717 VACUUM requires the daemon to be stopped. Run `recall stop` first, then re-run with --vacuum.")),2);s.push(await ue("wal_checkpoint(TRUNCATE)",()=>{t.pragma("wal_checkpoint(TRUNCATE)")})),s.push(await ue("messages_fts optimize",()=>{t.exec("INSERT INTO messages_fts(messages_fts) VALUES('optimize');")})),s.push(await ue("sessions_fts optimize",()=>{t.exec("INSERT INTO sessions_fts(sessions_fts) VALUES('optimize');")})),s.push(await ue("PRAGMA optimize",()=>{t.exec("PRAGMA optimize")})),e.vacuum&&s.push(await ue("VACUUM",()=>{t.exec("VACUUM")}));let n=s.filter(i=>!i.ok);if(e.json)return process.stdout.write(JSON.stringify({ok:n.length===0,steps:s,vacuum:!!e.vacuum},null,2)+`
660
- `),n.length===0?0:1;for(let i of s){let r=i.ok?E.ok("\u2713"):E.err("\u2717"),o=`${i.durationMs} ms`;console.log(` ${r} ${i.step.padEnd(28)} ${E.dim(o)}`),i.error&&console.log(` ${E.err(i.error)}`)}return n.length===0?(console.log(""),console.log(E.ok("All maintenance passes completed.")),e.vacuum||console.log(E.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from deleted rows.")),0):(console.log(""),console.log(E.warn(`${n.length} step(s) failed \u2014 review the errors above.`)),1)}var hn=$(()=>{"use strict";it();L();F()});L();import{McpServer as Oo}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Io}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as T}from"zod";import{fileURLToPath as xo}from"node:url";var Ln=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,An=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,On=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function In(e){return e.replace(Ln,"").trim()}function xn(e){let t=e.replace(An,"[tool call]");return t=t.replace(On,"[tool result]"),t=t.replace(/_\(unknown block: thinking\)_/g,""),t=t.replace(/(?:\[tool call\]|\[tool result\])(?:\s*(?:\[tool call\]|\[tool result\]))+/g,"[tool activity]"),t=t.replace(/\n{3,}/g,`
710
+ `),n.length===0?0:1;for(let i of s){let r=i.ok?E.ok("\u2713"):E.err("\u2717"),o=`${i.durationMs} ms`;console.log(` ${r} ${i.step.padEnd(28)} ${E.dim(o)}`),i.error&&console.log(` ${E.err(i.error)}`)}return n.length===0?(console.log(""),console.log(E.ok("All maintenance passes completed.")),e.vacuum||console.log(E.dim(" Run `recall optimize --vacuum` (with the daemon stopped) to reclaim disk pages from deleted rows.")),0):(console.log(""),console.log(E.warn(`${n.length} step(s) failed \u2014 review the errors above.`)),1)}var hn=$(()=>{"use strict";it();L();M()});L();import{McpServer as Oo}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Io}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as T}from"zod";import{fileURLToPath as xo}from"node:url";var Ln=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,An=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,On=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function In(e){return e.replace(Ln,"").trim()}function xn(e){let t=e.replace(An,"[tool call]");return t=t.replace(On,"[tool result]"),t=t.replace(/_\(unknown block: thinking\)_/g,""),t=t.replace(/(?:\[tool call\]|\[tool result\])(?:\s*(?:\[tool call\]|\[tool result\]))+/g,"[tool activity]"),t=t.replace(/\n{3,}/g,`
661
711
 
662
- `),t.trim()}function vn(e){return e.role??e.type??"message"}function _t(e,t,s={}){let n=s.mode??"condensed",i=s.includeSidechain===!0,r=s.since?Date.parse(s.since):0,o=t.filter(d=>!(!i&&d.is_sidechain===1||r&&d.timestamp&&Date.parse(d.timestamp)<r)),a=[];s.prelude&&(a.push(s.prelude.trim()),a.push("")),a.push(`# Claude Recall, past session context (${n})`),a.push(""),a.push(`- **Project**: ${e.project_name}`),e.decoded_path&&a.push(`- **Path**: \`${e.decoded_path}\``),a.push(`- **Session ID**: \`${e.id}\``),e.started_at&&a.push(`- **Started**: ${e.started_at}`),e.ended_at&&a.push(`- **Ended**: ${e.ended_at}`),e.git_branch&&a.push(`- **Branch**: \`${e.git_branch}\``),a.push(`- **Messages**: ${o.length}`),a.push(""),a.push("> This is a transcript of a previous Claude Code session, included for context. Refer back to it when the user asks about past decisions, code written, or problems debugged in this work."),a.push(""),a.push("---"),a.push("");let c=0,l=0;for(let d of o){let u=d.content_text??"",m=In(u);n==="condensed"&&(m=xn(m));let g=m.length>0,_=!!d.tool_names&&d.tool_names.length>0;if(!g&&!_){l+=1;continue}let f=vn(d),h=d.timestamp?` \`${d.timestamp}\``:"";a.push(`## ${f}${h}`),a.push(""),_&&n==="condensed"&&(a.push(`_tools used: ${d.tool_names}_`),a.push("")),g&&(a.push(m),a.push("")),c+=1}return a.push("---"),a.push(""),a.push(`_${c} messages included_`+(l?`, ${l} empty skipped`:"")+(i?"":", subagent/sidechain hidden")+"."),a.join(`
663
- `)}fe();ke();import{existsSync as Mn,mkdirSync as pa,readFileSync as Un,writeFileSync as ma,chmodSync as ga}from"node:fs";import{homedir as Pn}from"node:os";import{join as Tt}from"node:path";import{z as K}from"zod";function $n(){return process.env.RECALL_HOME??Tt(Pn(),".recall")}function jn(){return Tt($n(),"config.json")}var Xn=K.object({enabled:K.boolean().default(!1),backend:K.enum(["api","mcp"]).default("api"),apiKey:K.string().optional(),model:K.string().default("claude-opus-4-7"),maxTagsPerSession:K.number().int().min(1).max(10).default(4),minTagsPerSession:K.number().int().min(1).max(10).default(2),autopilot:K.boolean().default(!1)}),De={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function Hn(){let e=jn();if(!Mn(e))return{};try{return JSON.parse(Un(e,"utf8"))}catch(t){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",t),{}}}function Fe(){let e=Hn().autoTag;if(!e)return{...De};let t=Xn.safeParse({...De,...e});return t.success?t.data:{...De}}Ue();L();fe();import{z as I}from"zod";L();F();import{writeFileSync as ei}from"node:fs";import{join as ti}from"node:path";var si=ti(S,"aliases.json");function bt(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function ni(){return p().prepare("SELECT session_id, alias, updated_at, previous_aliases FROM session_aliases").all().map(t=>({session_id:t.session_id,alias:t.alias,updated_at:t.updated_at,previous_aliases:bt(t.previous_aliases)}))}function Rt(e,t){let s=t.trim();if(!s)throw new Error("alias must be non-empty");let n=p(),i=new Date().toISOString(),r=n.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),o=[];return r&&(o=bt(r.previous_aliases),r.alias!==s&&o.push({alias:r.alias,replaced_at:i})),n.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
712
+ `),t.trim()}function vn(e){return e.role??e.type??"message"}function _t(e,t,s={}){let n=s.mode??"condensed",i=s.includeSidechain===!0,r=s.since?Date.parse(s.since):0,o=t.filter(d=>!(!i&&d.is_sidechain===1||r&&d.timestamp&&Date.parse(d.timestamp)<r)),a=[];s.prelude&&(a.push(s.prelude.trim()),a.push("")),a.push(`# Claude Recall, past session context (${n})`),a.push(""),a.push(`- **Project**: ${e.project_name}`),e.decoded_path&&a.push(`- **Path**: \`${e.decoded_path}\``),a.push(`- **Session ID**: \`${e.id}\``),e.started_at&&a.push(`- **Started**: ${e.started_at}`),e.ended_at&&a.push(`- **Ended**: ${e.ended_at}`),e.git_branch&&a.push(`- **Branch**: \`${e.git_branch}\``),a.push(`- **Messages**: ${o.length}`),a.push(""),a.push("> This is a transcript of a previous Claude Code session, included for context. Refer back to it when the user asks about past decisions, code written, or problems debugged in this work."),a.push(""),a.push("---"),a.push("");let c=0,l=0;for(let d of o){let u=d.content_text??"",p=In(u);n==="condensed"&&(p=xn(p));let g=p.length>0,_=!!d.tool_names&&d.tool_names.length>0;if(!g&&!_){l+=1;continue}let f=vn(d),h=d.timestamp?` \`${d.timestamp}\``:"";a.push(`## ${f}${h}`),a.push(""),_&&n==="condensed"&&(a.push(`_tools used: ${d.tool_names}_`),a.push("")),g&&(a.push(p),a.push("")),c+=1}return a.push("---"),a.push(""),a.push(`_${c} messages included_`+(l?`, ${l} empty skipped`:"")+(i?"":", subagent/sidechain hidden")+"."),a.join(`
713
+ `)}fe();ke();import{existsSync as Mn,mkdirSync as pa,readFileSync as Un,writeFileSync as ma,chmodSync as ga}from"node:fs";import{homedir as Pn}from"node:os";import{join as Tt}from"node:path";import{z}from"zod";function $n(){return process.env.RECALL_HOME??Tt(Pn(),".recall")}function jn(){return Tt($n(),"config.json")}var Xn=z.object({enabled:z.boolean().default(!1),backend:z.enum(["api","mcp"]).default("api"),apiKey:z.string().optional(),model:z.string().default("claude-opus-4-7"),maxTagsPerSession:z.number().int().min(1).max(10).default(4),minTagsPerSession:z.number().int().min(1).max(10).default(2),autopilot:z.boolean().default(!1)}),De={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function Hn(){let e=jn();if(!Mn(e))return{};try{return JSON.parse(Un(e,"utf8"))}catch(t){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",t),{}}}function Fe(){let e=Hn().autoTag;if(!e)return{...De};let t=Xn.safeParse({...De,...e});return t.success?t.data:{...De}}Ue();L();fe();import{z as I}from"zod";L();M();import{writeFileSync as ei}from"node:fs";import{join as ti}from"node:path";var si=ti(b,"aliases.json");function bt(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function ni(){return m().prepare("SELECT session_id, alias, updated_at, previous_aliases FROM session_aliases").all().map(t=>({session_id:t.session_id,alias:t.alias,updated_at:t.updated_at,previous_aliases:bt(t.previous_aliases)}))}function Rt(e,t){let s=t.trim();if(!s)throw new Error("alias must be non-empty");let n=m(),i=new Date().toISOString(),r=n.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),o=[];return r&&(o=bt(r.previous_aliases),r.alias!==s&&o.push({alias:r.alias,replaced_at:i})),n.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
664
714
  VALUES (?, ?, ?, ?)
665
715
  ON CONFLICT(session_id) DO UPDATE SET
666
716
  alias = excluded.alias,
667
717
  updated_at = excluded.updated_at,
668
- previous_aliases = excluded.previous_aliases`).run(e,s,i,JSON.stringify(o)),ii(),{session_id:e,alias:s,updated_at:i,previous_aliases:o}}function ii(){try{x();let e=ni(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};ei(si,JSON.stringify(t,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}L();F();import{writeFileSync as ri,mkdirSync as oi,existsSync as ai}from"node:fs";import{join as yt}from"node:path";var Pe=yt(S,"notes");function Nt(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function ci(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t.filter(s=>!!s&&typeof s=="object"&&typeof s.synopsis=="string"&&typeof s.replaced_at=="string"):[]}catch{return[]}}function li(){x(),ai(Pe)||oi(Pe,{recursive:!0})}function di(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:Nt(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:ci(e.auto_synopsis_history)}}var ui="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function $e(e){let t=p().prepare(`SELECT ${ui} FROM session_notes WHERE session_id = ?`).get(e);return t?di(t):null}function wt(e,t){let s=p(),n=new Date().toISOString(),i=s.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),r=[];return i&&(r=Nt(i.previous_versions),i.content!==t&&i.content.length>0&&r.push({content:i.content,replaced_at:n})),s.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
718
+ previous_aliases = excluded.previous_aliases`).run(e,s,i,JSON.stringify(o)),ii(),{session_id:e,alias:s,updated_at:i,previous_aliases:o}}function ii(){try{x();let e=ni(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};ei(si,JSON.stringify(t,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}L();M();import{writeFileSync as ri,mkdirSync as oi,existsSync as ai}from"node:fs";import{join as yt}from"node:path";var Pe=yt(b,"notes");function Nt(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function ci(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t.filter(s=>!!s&&typeof s=="object"&&typeof s.synopsis=="string"&&typeof s.replaced_at=="string"):[]}catch{return[]}}function li(){x(),ai(Pe)||oi(Pe,{recursive:!0})}function di(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:Nt(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:ci(e.auto_synopsis_history)}}var ui="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function $e(e){let t=m().prepare(`SELECT ${ui} FROM session_notes WHERE session_id = ?`).get(e);return t?di(t):null}function wt(e,t){let s=m(),n=new Date().toISOString(),i=s.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),r=[];return i&&(r=Nt(i.previous_versions),i.content!==t&&i.content.length>0&&r.push({content:i.content,replaced_at:n})),s.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
669
719
  VALUES (?, ?, ?, ?)
670
720
  ON CONFLICT(session_id) DO UPDATE SET
671
721
  content = excluded.content,
672
722
  updated_at = excluded.updated_at,
673
723
  previous_versions = excluded.previous_versions`).run(e,t,n,JSON.stringify(r)),pi(e,t,n),$e(e)??{session_id:e,content:t,updated_at:n,previous_versions:r,auto_synopsis:null,auto_synopsis_generated_at:null,auto_synopsis_history:[]}}function pi(e,t,s){try{li();let n=yt(Pe,`${e}.md`),i=`<!-- Claude Recall note \xB7 session ${e} \xB7 updated ${s} -->
674
- `;ri(n,i+t)}catch(n){console.error("[notes] mirror write failed:",n)}}L();F();import{randomUUID as mi}from"node:crypto";import{writeFileSync as gi,readFileSync as ka,existsSync as Da}from"node:fs";import{join as _i}from"node:path";var fi=_i(S,"collections.json"),Lt=8;function hi(e){return{...e}}function je(e,t,s,n=null,i=new Date().toISOString()){p().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
675
- VALUES (?, ?, ?, ?, ?)`).run(e,n,t,s?JSON.stringify(s):null,i)}function Ei(e){let t=p().prepare("SELECT * FROM collections WHERE id = ?").get(e);if(!t)throw new Error(`collection not found: ${e}`);return t}function Ti(e){if(!e)return 0;let t=0,s=e,n=new Set,i=p();for(;s;){if(n.has(s))throw new Error("collection cycle detected");n.add(s);let r=i.prepare("SELECT parent_id FROM collections WHERE id = ?").get(s);if(!r)break;t+=1,s=r.parent_id}return t}function At(e){let t=p().prepare("SELECT * FROM collections WHERE id = ?").get(e);return t?hi(t):null}function Ot(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");if(t.length>120)throw new Error("name too long (max 120 chars)");let s=p(),n=new Date().toISOString(),i=mi();if(e.parent_id){if(!At(e.parent_id))throw new Error("parent collection not found");if(Ti(e.parent_id)>=Lt-1)throw new Error(`max collection depth is ${Lt}`)}return s.transaction(()=>{s.prepare(`INSERT INTO collections
724
+ `;ri(n,i+t)}catch(n){console.error("[notes] mirror write failed:",n)}}L();M();import{randomUUID as mi}from"node:crypto";import{writeFileSync as gi,readFileSync as ka,existsSync as Da}from"node:fs";import{join as _i}from"node:path";var fi=_i(b,"collections.json"),Lt=8;function hi(e){return{...e}}function je(e,t,s,n=null,i=new Date().toISOString()){m().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
725
+ VALUES (?, ?, ?, ?, ?)`).run(e,n,t,s?JSON.stringify(s):null,i)}function Ei(e){let t=m().prepare("SELECT * FROM collections WHERE id = ?").get(e);if(!t)throw new Error(`collection not found: ${e}`);return t}function Ti(e){if(!e)return 0;let t=0,s=e,n=new Set,i=m();for(;s;){if(n.has(s))throw new Error("collection cycle detected");n.add(s);let r=i.prepare("SELECT parent_id FROM collections WHERE id = ?").get(s);if(!r)break;t+=1,s=r.parent_id}return t}function At(e){let t=m().prepare("SELECT * FROM collections WHERE id = ?").get(e);return t?hi(t):null}function Ot(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");if(t.length>120)throw new Error("name too long (max 120 chars)");let s=m(),n=new Date().toISOString(),i=mi();if(e.parent_id){if(!At(e.parent_id))throw new Error("parent collection not found");if(Ti(e.parent_id)>=Lt-1)throw new Error(`max collection depth is ${Lt}`)}return s.transaction(()=>{s.prepare(`INSERT INTO collections
676
726
  (id, name, description, icon, color, parent_id, sort_key, created_at, updated_at, archived_at)
677
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(i,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",n,n),je(i,"create",{name:t,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,n)})(),Xe(),At(i)}function It(e,t,s=null,n={}){let i=p();if(Ei(e),!i.prepare("SELECT 1 FROM sessions WHERE id = ?").get(t))throw new Error(`session not found: ${t}`);if(i.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{added:!1};let a=n.source??"manual",c=n.rule_id??null;if(a==="auto"&&!c)throw new Error("auto membership requires a rule_id");let l=new Date().toISOString();return i.transaction(()=>{i.prepare(`INSERT INTO collection_sessions (collection_id, session_id, added_at, note, source, rule_id)
678
- VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,l,s,a,c),je(e,"add",{note:s,source:a,rule_id:c},t,l)})(),Xe(),{added:!0}}function xt(e,t){let s=p();if(!s.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{removed:!1};let i=new Date().toISOString();return s.transaction(()=>{s.prepare("DELETE FROM collection_sessions WHERE collection_id = ? AND session_id = ?").run(e,t),je(e,"remove",null,t,i)})(),Xe(),{removed:!0}}function Si(){return p().prepare(`SELECT id, collection_id, session_id, action, payload, at
727
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(i,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",n,n),je(i,"create",{name:t,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,n)})(),Xe(),At(i)}function It(e,t,s=null,n={}){let i=m();if(Ei(e),!i.prepare("SELECT 1 FROM sessions WHERE id = ?").get(t))throw new Error(`session not found: ${t}`);if(i.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{added:!1};let a=n.source??"manual",c=n.rule_id??null;if(a==="auto"&&!c)throw new Error("auto membership requires a rule_id");let l=new Date().toISOString();return i.transaction(()=>{i.prepare(`INSERT INTO collection_sessions (collection_id, session_id, added_at, note, source, rule_id)
728
+ VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,l,s,a,c),je(e,"add",{note:s,source:a,rule_id:c},t,l)})(),Xe(),{added:!0}}function xt(e,t){let s=m();if(!s.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{removed:!1};let i=new Date().toISOString();return s.transaction(()=>{s.prepare("DELETE FROM collection_sessions WHERE collection_id = ? AND session_id = ?").run(e,t),je(e,"remove",null,t,i)})(),Xe(),{removed:!0}}function Si(){return m().prepare(`SELECT id, collection_id, session_id, action, payload, at
679
729
  FROM collection_events
680
- ORDER BY at ASC, id ASC`).all()}function Xe(){try{x();let e=p(),t=e.prepare(`SELECT id, name, description, icon, color, parent_id, sort_key,
730
+ ORDER BY at ASC, id ASC`).all()}function Xe(){try{x();let e=m(),t=e.prepare(`SELECT id, name, description, icon, color, parent_id, sort_key,
681
731
  created_at, updated_at, archived_at
682
732
  FROM collections
683
733
  ORDER BY COALESCE(parent_id, ''), sort_key, LOWER(name)`).all(),s=e.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
684
734
  FROM collection_sessions
685
- ORDER BY collection_id, added_at`).all(),n=Si(),i={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:t,memberships:s,events:n};gi(fi,JSON.stringify(i,null,2))}catch(e){console.error("[collections] backup failed:",e)}}L();var Be=60,bi=6e4,se=class extends Error{retryAfterMs;constructor(t){super(`MCP write rate limit exceeded \u2014 try again in ${Math.ceil(t/1e3)}s`),this.name="RateLimitError",this.retryAfterMs=t}};function He(e){let t=new Date().toISOString(),s;try{s=JSON.stringify(e.args??null)}catch{s='"<unserializable>"'}p().prepare(`INSERT INTO mcp_audit_events (tool, args_json, result, error_message, caller, at)
686
- VALUES (?, ?, ?, ?, ?, ?)`).run(e.tool,s,e.result,e.errorMessage??null,e.caller??null,t)}var z=class{capacity;windowMs;hits=[];constructor(t=Be,s=bi){this.capacity=t,this.windowMs=s}consume(t=Date.now()){if(this.evict(t),this.hits.length>=this.capacity){let s=this.hits[0],n=Math.max(1,s+this.windowMs-t);throw new se(n)}this.hits.push(t)}remaining(t=Date.now()){return this.evict(t),Math.max(0,this.capacity-this.hits.length)}reset(){this.hits.length=0}evict(t){let s=t-this.windowMs;for(;this.hits.length>0&&this.hits[0]<s;)this.hits.shift()}};async function N(e){try{e.limiter.consume()}catch(t){throw t instanceof se&&He({tool:e.tool,args:e.args,result:"rate_limited",errorMessage:t.message,caller:e.caller}),t}try{let t=await e.run();return He({tool:e.tool,args:e.args,result:"ok",caller:e.caller}),t}catch(t){let s=t instanceof Error?t.message:String(t);throw He({tool:e.tool,args:e.args,result:"error",errorMessage:s,caller:e.caller}),t}}L();import{z as Ri}from"zod";function X(e){let t=e.trim();if(t.length<4)return null;let s=p();if(t.length>=32)return s.prepare("SELECT id FROM sessions WHERE id = ?").get(t)?.id??null;let n=s.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(`${t}%`);return n.length===1?n[0].id:null}function Ee(e){return{content:[{type:"text",text:e}],isError:!0}}function y(e){return e instanceof se?Ee(e.message):e instanceof Ri.ZodError?Ee(`invalid input: ${e.message}`):e instanceof Error&&e.message.startsWith("SQLITE_")?Ee("database constraint error"):Ee(e instanceof Error?e.message:String(e))}function Q(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function J(e){return{content:[{type:"text",text:e}],isError:!0}}var yi=`
735
+ ORDER BY collection_id, added_at`).all(),n=Si(),i={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:t,memberships:s,events:n};gi(fi,JSON.stringify(i,null,2))}catch(e){console.error("[collections] backup failed:",e)}}L();var Be=60,bi=6e4,se=class extends Error{retryAfterMs;constructor(t){super(`MCP write rate limit exceeded \u2014 try again in ${Math.ceil(t/1e3)}s`),this.name="RateLimitError",this.retryAfterMs=t}};function He(e){let t=new Date().toISOString(),s;try{s=JSON.stringify(e.args??null)}catch{s='"<unserializable>"'}m().prepare(`INSERT INTO mcp_audit_events (tool, args_json, result, error_message, caller, at)
736
+ VALUES (?, ?, ?, ?, ?, ?)`).run(e.tool,s,e.result,e.errorMessage??null,e.caller??null,t)}var K=class{capacity;windowMs;hits=[];constructor(t=Be,s=bi){this.capacity=t,this.windowMs=s}consume(t=Date.now()){if(this.evict(t),this.hits.length>=this.capacity){let s=this.hits[0],n=Math.max(1,s+this.windowMs-t);throw new se(n)}this.hits.push(t)}remaining(t=Date.now()){return this.evict(t),Math.max(0,this.capacity-this.hits.length)}reset(){this.hits.length=0}evict(t){let s=t-this.windowMs;for(;this.hits.length>0&&this.hits[0]<s;)this.hits.shift()}};async function w(e){try{e.limiter.consume()}catch(t){throw t instanceof se&&He({tool:e.tool,args:e.args,result:"rate_limited",errorMessage:t.message,caller:e.caller}),t}try{let t=await e.run();return He({tool:e.tool,args:e.args,result:"ok",caller:e.caller}),t}catch(t){let s=t instanceof Error?t.message:String(t);throw He({tool:e.tool,args:e.args,result:"error",errorMessage:s,caller:e.caller}),t}}L();import{z as Ri}from"zod";function X(e){let t=e.trim();if(t.length<4)return null;let s=m();if(t.length>=32)return s.prepare("SELECT id FROM sessions WHERE id = ?").get(t)?.id??null;let n=s.prepare("SELECT id FROM sessions WHERE id LIKE ? LIMIT 2").all(`${t}%`);return n.length===1?n[0].id:null}function Ee(e){return{content:[{type:"text",text:e}],isError:!0}}function N(e){return e instanceof se?Ee(e.message):e instanceof Ri.ZodError?Ee(`invalid input: ${e.message}`):e instanceof Error&&e.message.startsWith("SQLITE_")?Ee("database constraint error"):Ee(e instanceof Error?e.message:String(e))}function Q(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function J(e){return{content:[{type:"text",text:e}],isError:!0}}var yi=`
687
737
 
688
738
  ---
689
739
 
690
- `,Ni=5e5;function vt(e,t={}){let s=t.limiter??new z;e.registerTool("add_tag",{title:"Add tag to a session",description:"Apply a single tag to a session. Tags are normalized server-side (lowercase, hyphens, strip #). Idempotent.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to add. Normalized: lowercase, dashes, max 40 chars.")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await N({tool:"add_tag",args:{sessionId:i,tag:n.tag},limiter:s,run:()=>ge(i,n.tag)});return Q({sessionId:i,...r})}catch(i){return y(i)}}),e.registerTool("remove_tag",{title:"Remove tag from a session",description:"Remove a single tag from a session. The removal is recorded in tag_events (append-only) so history is preserved.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to remove (normalized server-side).")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=V(n.tag);if(!r)return J("tag must contain at least one alphanumeric character");let o=await N({tool:"remove_tag",args:{sessionId:i,tag:r},limiter:s,run:()=>ft(i,r)});return Q({sessionId:i,...o})}catch(i){return y(i)}}),e.registerTool("set_alias",{title:"Set session alias",description:"Set a human-friendly alias for a session. Previous alias is archived to previous_aliases (never destroyed). Session UUID remains the immutable primary key.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),alias:I.string().min(1).max(120).describe("New alias (non-empty, max 120 chars).")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await N({tool:"set_alias",args:{sessionId:i,alias:n.alias},limiter:s,run:()=>Rt(i,n.alias)});return Q(r)}catch(i){return y(i)}}),e.registerTool("append_note",{title:"Append to session note",description:"Append markdown to a session note. If a note already exists, the new content is added below a `---` separator. Previous version is archived in previous_versions.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),markdown:I.string().min(1).max(5e4).describe("Markdown to append.")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await N({tool:"append_note",args:{sessionId:i,length:n.markdown.length},limiter:s,run:()=>{let o=$e(i),a=o&&o.content.length>0?`${o.content}${yi}${n.markdown}`:n.markdown;if(a.length>Ni)throw new Error(`note would exceed the 500 KB cumulative limit (current: ${o?.content.length??0} bytes, adding: ${n.markdown.length} bytes)`);return wt(i,a)}});return Q(r)}catch(i){return y(i)}}),e.registerTool("create_collection",{title:"Create a collection",description:"Create a new collection for grouping sessions. Optionally nest under a parent. Soft-deletion only.",inputSchema:{name:I.string().min(1).max(120),parent_id:I.string().optional().describe("Parent collection id (omit for root)."),icon:I.string().max(32).optional(),color:I.string().max(32).optional(),description:I.string().max(2e3).optional()}},async n=>{try{let i=await N({tool:"create_collection",args:n,limiter:s,run:()=>Ot({name:n.name,parent_id:n.parent_id??null,icon:n.icon??null,color:n.color??null,description:n.description??null})});return Q(i)}catch(i){return y(i)}}),e.registerTool("add_session_to_collection",{title:"Add session to collection",description:"Add a session to a collection. Idempotent: re-adding returns added=false.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix."),note:I.string().max(2e3).optional()}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);if(!p().prepare("SELECT 1 FROM collections WHERE id = ?").get(n.collectionId))return J("collection not found");let a=await N({tool:"add_session_to_collection",args:{collectionId:n.collectionId,sessionId:i,note:n.note??null},limiter:s,run:()=>It(n.collectionId,i,n.note??null)});return Q({collectionId:n.collectionId,sessionId:i,...a})}catch(i){return y(i)}}),e.registerTool("remove_session_from_collection",{title:"Remove session from collection",description:"Remove a session from a collection. The removal is recorded in collection_events (append-only); the underlying session is untouched.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix.")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await N({tool:"remove_session_from_collection",args:{collectionId:n.collectionId,sessionId:i},limiter:s,run:()=>xt(n.collectionId,i)});return Q({collectionId:n.collectionId,sessionId:i,...r})}catch(i){return y(i)}})}import{z as M}from"zod";L();F();import{randomUUID as Ct}from"node:crypto";import{writeFileSync as kt,readFileSync as sc,existsSync as wi,mkdirSync as Li}from"node:fs";import{join as We}from"node:path";var Te=We(S,"threads"),Ai=We(Te,"index.json");function Dt(){x(),wi(Te)||Li(Te,{recursive:!0})}function Ye(e,t,s){return{id:e.id,name:e.name,summary:e.summary,created_at:e.created_at,closed_at:e.closed_at,archived:e.archived===1,session_count:t.session_count,origin_count:t.origin_count,project:s?.project??null,project_count:s?.project_count??0,folder_id:e.folder_id??null}}function Ft(e){let t=new Map;if(e.length===0)return t;let s=p(),n=e.map(()=>"?").join(","),i=s.prepare(`SELECT te.thread_id AS thread_id,
740
+ `,Ni=5e5;function vt(e,t={}){let s=t.limiter??new K;e.registerTool("add_tag",{title:"Add tag to a session",description:"Apply a single tag to a session. Tags are normalized server-side (lowercase, hyphens, strip #). Idempotent.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to add. Normalized: lowercase, dashes, max 40 chars.")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await w({tool:"add_tag",args:{sessionId:i,tag:n.tag},limiter:s,run:()=>ge(i,n.tag)});return Q({sessionId:i,...r})}catch(i){return N(i)}}),e.registerTool("remove_tag",{title:"Remove tag from a session",description:"Remove a single tag from a session. The removal is recorded in tag_events (append-only) so history is preserved.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),tag:I.string().min(1).describe("Tag to remove (normalized server-side).")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=V(n.tag);if(!r)return J("tag must contain at least one alphanumeric character");let o=await w({tool:"remove_tag",args:{sessionId:i,tag:r},limiter:s,run:()=>ft(i,r)});return Q({sessionId:i,...o})}catch(i){return N(i)}}),e.registerTool("set_alias",{title:"Set session alias",description:"Set a human-friendly alias for a session. Previous alias is archived to previous_aliases (never destroyed). Session UUID remains the immutable primary key.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),alias:I.string().min(1).max(120).describe("New alias (non-empty, max 120 chars).")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await w({tool:"set_alias",args:{sessionId:i,alias:n.alias},limiter:s,run:()=>Rt(i,n.alias)});return Q(r)}catch(i){return N(i)}}),e.registerTool("append_note",{title:"Append to session note",description:"Append markdown to a session note. If a note already exists, the new content is added below a `---` separator. Previous version is archived in previous_versions.",inputSchema:{sessionId:I.string().describe("Full session UUID or 8+-character prefix."),markdown:I.string().min(1).max(5e4).describe("Markdown to append.")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await w({tool:"append_note",args:{sessionId:i,length:n.markdown.length},limiter:s,run:()=>{let o=$e(i),a=o&&o.content.length>0?`${o.content}${yi}${n.markdown}`:n.markdown;if(a.length>Ni)throw new Error(`note would exceed the 500 KB cumulative limit (current: ${o?.content.length??0} bytes, adding: ${n.markdown.length} bytes)`);return wt(i,a)}});return Q(r)}catch(i){return N(i)}}),e.registerTool("create_collection",{title:"Create a collection",description:"Create a new collection for grouping sessions. Optionally nest under a parent. Soft-deletion only.",inputSchema:{name:I.string().min(1).max(120),parent_id:I.string().optional().describe("Parent collection id (omit for root)."),icon:I.string().max(32).optional(),color:I.string().max(32).optional(),description:I.string().max(2e3).optional()}},async n=>{try{let i=await w({tool:"create_collection",args:n,limiter:s,run:()=>Ot({name:n.name,parent_id:n.parent_id??null,icon:n.icon??null,color:n.color??null,description:n.description??null})});return Q(i)}catch(i){return N(i)}}),e.registerTool("add_session_to_collection",{title:"Add session to collection",description:"Add a session to a collection. Idempotent: re-adding returns added=false.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix."),note:I.string().max(2e3).optional()}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);if(!m().prepare("SELECT 1 FROM collections WHERE id = ?").get(n.collectionId))return J("collection not found");let a=await w({tool:"add_session_to_collection",args:{collectionId:n.collectionId,sessionId:i,note:n.note??null},limiter:s,run:()=>It(n.collectionId,i,n.note??null)});return Q({collectionId:n.collectionId,sessionId:i,...a})}catch(i){return N(i)}}),e.registerTool("remove_session_from_collection",{title:"Remove session from collection",description:"Remove a session from a collection. The removal is recorded in collection_events (append-only); the underlying session is untouched.",inputSchema:{collectionId:I.string().min(1),sessionId:I.string().describe("Full session UUID or 8+-character prefix.")}},async n=>{try{let i=X(n.sessionId);if(!i)return J(`session not found or prefix ambiguous: ${n.sessionId}`);let r=await w({tool:"remove_session_from_collection",args:{collectionId:n.collectionId,sessionId:i},limiter:s,run:()=>xt(n.collectionId,i)});return Q({collectionId:n.collectionId,sessionId:i,...r})}catch(i){return N(i)}})}import{z as U}from"zod";L();M();import{randomUUID as Ct}from"node:crypto";import{writeFileSync as kt,readFileSync as sc,existsSync as wi,mkdirSync as Li}from"node:fs";import{join as We}from"node:path";var Te=We(b,"threads"),Ai=We(Te,"index.json");function Dt(){x(),wi(Te)||Li(Te,{recursive:!0})}function Ge(e,t,s){return{id:e.id,name:e.name,summary:e.summary,created_at:e.created_at,closed_at:e.closed_at,archived:e.archived===1,session_count:t.session_count,origin_count:t.origin_count,project:s?.project??null,project_count:s?.project_count??0,folder_id:e.folder_id??null}}function Ft(e){let t=new Map;if(e.length===0)return t;let s=m(),n=e.map(()=>"?").join(","),i=s.prepare(`SELECT te.thread_id AS thread_id,
691
741
  p.name AS project,
692
742
  COUNT(*) AS n,
693
743
  SUM(CASE WHEN te.role = 'origin' THEN 1 ELSE 0 END) AS origin_n
@@ -695,7 +745,7 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
695
745
  LEFT JOIN sessions s ON s.id = te.session_id
696
746
  LEFT JOIN projects p ON p.id = s.project_id
697
747
  WHERE te.thread_id IN (${n})
698
- GROUP BY te.thread_id, p.name`).all(...e),r=new Map;for(let o of i){let a=r.get(o.thread_id);a||(a=[],r.set(o.thread_id,a)),a.push(o)}for(let[o,a]of r){let c=a.filter(u=>u.project!==null),l=c.length,d=null;c.length>0&&(d=[...c].sort((m,g)=>g.n-m.n||g.origin_n-m.origin_n||(m.project??"").localeCompare(g.project??""))[0].project),t.set(o,{project:d,project_count:l})}return t}function Mt(e){let t=e.auto_title_source;return{thread_id:e.thread_id,session_id:e.session_id,parent_session_id:e.parent_session_id,role:e.role,confidence:e.confidence,source:e.source,added_at:e.added_at,alias:e.alias,auto_title:e.auto_title,auto_title_source:t==="agent"||t==="heuristic"?t:null,alias_source:e.alias?"manual":null,first_user_message:e.first_user_message,project:e.project}}function Ut(e){let s=p().prepare(`SELECT NULLIF(sa.alias, '') AS alias,
748
+ GROUP BY te.thread_id, p.name`).all(...e),r=new Map;for(let o of i){let a=r.get(o.thread_id);a||(a=[],r.set(o.thread_id,a)),a.push(o)}for(let[o,a]of r){let c=a.filter(u=>u.project!==null),l=c.length,d=null;c.length>0&&(d=[...c].sort((p,g)=>g.n-p.n||g.origin_n-p.origin_n||(p.project??"").localeCompare(g.project??""))[0].project),t.set(o,{project:d,project_count:l})}return t}function Mt(e){let t=e.auto_title_source;return{thread_id:e.thread_id,session_id:e.session_id,parent_session_id:e.parent_session_id,role:e.role,confidence:e.confidence,source:e.source,added_at:e.added_at,alias:e.alias,auto_title:e.auto_title,auto_title_source:t==="agent"||t==="heuristic"?t:null,alias_source:e.alias?"manual":null,first_user_message:e.first_user_message,project:e.project}}function Ut(e){let s=m().prepare(`SELECT NULLIF(sa.alias, '') AS alias,
699
749
  s.auto_title AS auto_title,
700
750
  s.auto_title_source AS auto_title_source,
701
751
  s.first_user_message AS first_user_message,
@@ -703,11 +753,11 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
703
753
  FROM (SELECT ? AS sid) q
704
754
  LEFT JOIN sessions s ON s.id = q.sid
705
755
  LEFT JOIN session_aliases sa ON sa.session_id = q.sid
706
- LEFT JOIN projects p ON p.id = s.project_id`).get(e),n=s?.auto_title_source??null;return{alias:s?.alias??null,auto_title:s?.auto_title??null,auto_title_source:n==="agent"||n==="heuristic"?n:null,first_user_message:s?.first_user_message??null,project:s?.project??null}}function Ge(e){let s=p().prepare(`SELECT
756
+ LEFT JOIN projects p ON p.id = s.project_id`).get(e),n=s?.auto_title_source??null;return{alias:s?.alias??null,auto_title:s?.auto_title??null,auto_title_source:n==="agent"||n==="heuristic"?n:null,first_user_message:s?.first_user_message??null,project:s?.project??null}}function Ye(e){let s=m().prepare(`SELECT
707
757
  COUNT(*) AS session_count,
708
758
  SUM(CASE WHEN role = 'origin' THEN 1 ELSE 0 END) AS origin_count
709
- FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function B(e){let t=k(e);t&&(Dt(),kt(We(Te,`${e}.json`),JSON.stringify(t,null,2)),Pt())}function Pt(){Dt();let e=Ke({includeArchived:!0});kt(Ai,JSON.stringify({threads:e},null,2))}function Se(e){let t=e.name.trim();if(!t)throw new Error("thread name cannot be empty");let s=p(),n=Ct(),i=new Date().toISOString();s.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(n,t,e.summary?.trim()||null,i),e.originSessionId&&s.prepare(`INSERT INTO thread_edges (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
710
- VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,i),B(n);let r=k(n);if(!r)throw new Error("thread creation succeeded but read-back failed");return r}function Ke(e={}){let t=p(),s=e.includeArchived?"":"WHERE archived = 0",n=t.prepare(`SELECT * FROM threads ${s} ORDER BY created_at DESC`).all(),i=Ft(n.map(r=>r.id));return n.map(r=>Ye(r,Ge(r.id),i.get(r.id)))}function k(e){let t=p(),s=t.prepare("SELECT * FROM threads WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT e.*,
759
+ FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function B(e){let t=k(e);t&&(Dt(),kt(We(Te,`${e}.json`),JSON.stringify(t,null,2)),Pt())}function Pt(){Dt();let e=ze({includeArchived:!0});kt(Ai,JSON.stringify({threads:e},null,2))}function Se(e){let t=e.name.trim();if(!t)throw new Error("thread name cannot be empty");let s=m(),n=Ct(),i=new Date().toISOString();s.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(n,t,e.summary?.trim()||null,i),e.originSessionId&&s.prepare(`INSERT INTO thread_edges (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
760
+ VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,i),B(n);let r=k(n);if(!r)throw new Error("thread creation succeeded but read-back failed");return r}function ze(e={}){let t=m(),s=e.includeArchived?"":"WHERE archived = 0",n=t.prepare(`SELECT * FROM threads ${s} ORDER BY created_at DESC`).all(),i=Ft(n.map(r=>r.id));return n.map(r=>Ge(r,Ye(r.id),i.get(r.id)))}function k(e){let t=m(),s=t.prepare("SELECT * FROM threads WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT e.*,
711
761
  NULLIF(sa.alias, '') AS alias,
712
762
  s.auto_title AS auto_title,
713
763
  s.auto_title_source AS auto_title_source,
@@ -718,10 +768,10 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
718
768
  LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
719
769
  LEFT JOIN projects p ON p.id = s.project_id
720
770
  WHERE e.thread_id = ?
721
- ORDER BY e.added_at ASC`).all(e).map(Mt),i=Ft([e]).get(e);return{...Ye(s,Ge(s.id),i),edges:n}}function $t(e){return p().prepare(`SELECT t.* FROM threads t
771
+ ORDER BY e.added_at ASC`).all(e).map(Mt),i=Ft([e]).get(e);return{...Ge(s,Ye(s.id),i),edges:n}}function $t(e){return m().prepare(`SELECT t.* FROM threads t
722
772
  JOIN thread_edges e ON e.thread_id = t.id
723
773
  WHERE e.session_id = ? AND t.archived = 0
724
- ORDER BY t.created_at DESC`).all(e).map(n=>Ye(n,Ge(n.id)))}function be(e){let t=p();if(!t.prepare("SELECT * FROM threads WHERE id = ?").get(e.threadId))throw new Error(`thread ${e.threadId} not found`);let n=new Date().toISOString(),i=e.parentSessionId??null,r=e.role??(i?"child":"origin"),o=e.confidence??1,a=e.source??"manual";if(o<0||o>1)throw new Error("confidence must be 0..1");t.prepare(`INSERT INTO thread_edges
774
+ ORDER BY t.created_at DESC`).all(e).map(n=>Ge(n,Ye(n.id)))}function be(e){let t=m();if(!t.prepare("SELECT * FROM threads WHERE id = ?").get(e.threadId))throw new Error(`thread ${e.threadId} not found`);let n=new Date().toISOString(),i=e.parentSessionId??null,r=e.role??(i?"child":"origin"),o=e.confidence??1,a=e.source??"manual";if(o<0||o>1)throw new Error("confidence must be 0..1");t.prepare(`INSERT INTO thread_edges
725
775
  (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
726
776
  VALUES (?, ?, ?, ?, ?, ?, ?)
727
777
  ON CONFLICT(thread_id, session_id) DO UPDATE SET
@@ -729,25 +779,25 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
729
779
  role = excluded.role,
730
780
  confidence = excluded.confidence,
731
781
  source = excluded.source,
732
- added_at = excluded.added_at`).run(e.threadId,e.sessionId,i,r,o,a,n),B(e.threadId);let c=Ut(e.sessionId);return{thread_id:e.threadId,session_id:e.sessionId,parent_session_id:i,role:r,confidence:o,source:a,added_at:n,alias:c.alias,auto_title:c.auto_title,auto_title_source:c.auto_title_source,alias_source:c.alias?"manual":null,first_user_message:c.first_user_message,project:c.project}}function jt(e,t){let n=p().prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e,t);return n.changes>0&&B(e),{removed:n.changes}}function ae(e,t,s){let n=p(),i=n.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e,t);if(!i)throw new Error("edge not found; add the session first");if(s!==null){if(s===t)throw new Error("cycle detected: session cannot be its own parent");let a=n.prepare("SELECT parent_session_id FROM thread_edges WHERE thread_id = ? AND session_id = ?"),c=s,l=new Set;for(;c!==null;){if(c===t)throw new Error(`cycle detected: setting parent of ${t} to ${s} would create a loop`);if(l.has(c))break;l.add(c),c=a.get(e,c)?.parent_session_id??null}}let r=s?"child":"origin";n.prepare(`UPDATE thread_edges
782
+ added_at = excluded.added_at`).run(e.threadId,e.sessionId,i,r,o,a,n),B(e.threadId);let c=Ut(e.sessionId);return{thread_id:e.threadId,session_id:e.sessionId,parent_session_id:i,role:r,confidence:o,source:a,added_at:n,alias:c.alias,auto_title:c.auto_title,auto_title_source:c.auto_title_source,alias_source:c.alias?"manual":null,first_user_message:c.first_user_message,project:c.project}}function jt(e,t){let n=m().prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e,t);return n.changes>0&&B(e),{removed:n.changes}}function ae(e,t,s){let n=m(),i=n.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e,t);if(!i)throw new Error("edge not found; add the session first");if(s!==null){if(s===t)throw new Error("cycle detected: session cannot be its own parent");let a=n.prepare("SELECT parent_session_id FROM thread_edges WHERE thread_id = ? AND session_id = ?"),c=s,l=new Set;for(;c!==null;){if(c===t)throw new Error(`cycle detected: setting parent of ${t} to ${s} would create a loop`);if(l.has(c))break;l.add(c),c=a.get(e,c)?.parent_session_id??null}}let r=s?"child":"origin";n.prepare(`UPDATE thread_edges
733
783
  SET parent_session_id = ?, role = ?, added_at = ?
734
- WHERE thread_id = ? AND session_id = ?`).run(s,r,new Date().toISOString(),e,t),B(e);let o=Ut(t);return Mt({...i,parent_session_id:s,role:r,added_at:new Date().toISOString(),alias:o.alias,auto_title:o.auto_title,auto_title_source:o.auto_title_source,first_user_message:o.first_user_message,project:o.project})}function Xt(e,t){let s=t.trim();if(!s)throw new Error("name cannot be empty");p().prepare("UPDATE threads SET name = ? WHERE id = ?").run(s,e),B(e);let i=k(e);if(!i)throw new Error(`thread ${e} not found`);return i}function Ht(e){p().prepare("UPDATE threads SET closed_at = ? WHERE id = ?").run(new Date().toISOString(),e),B(e);let s=k(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Bt(e){p().prepare("UPDATE threads SET closed_at = NULL WHERE id = ?").run(e),B(e);let s=k(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Wt(e){p().prepare("UPDATE threads SET archived = 1 WHERE id = ?").run(e),B(e);let s=k(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Yt(e,t){if(e===t)throw new Error("cannot merge a thread into itself");let s=p(),n=new Date().toISOString();s.transaction(()=>{let r=s.prepare("SELECT * FROM thread_edges WHERE thread_id = ?").all(e);for(let o of r)s.prepare(`INSERT INTO thread_edges
784
+ WHERE thread_id = ? AND session_id = ?`).run(s,r,new Date().toISOString(),e,t),B(e);let o=Ut(t);return Mt({...i,parent_session_id:s,role:r,added_at:new Date().toISOString(),alias:o.alias,auto_title:o.auto_title,auto_title_source:o.auto_title_source,first_user_message:o.first_user_message,project:o.project})}function Xt(e,t){let s=t.trim();if(!s)throw new Error("name cannot be empty");m().prepare("UPDATE threads SET name = ? WHERE id = ?").run(s,e),B(e);let i=k(e);if(!i)throw new Error(`thread ${e} not found`);return i}function Ht(e){m().prepare("UPDATE threads SET closed_at = ? WHERE id = ?").run(new Date().toISOString(),e),B(e);let s=k(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Bt(e){m().prepare("UPDATE threads SET closed_at = NULL WHERE id = ?").run(e),B(e);let s=k(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Wt(e){m().prepare("UPDATE threads SET archived = 1 WHERE id = ?").run(e),B(e);let s=k(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Gt(e,t){if(e===t)throw new Error("cannot merge a thread into itself");let s=m(),n=new Date().toISOString();s.transaction(()=>{let r=s.prepare("SELECT * FROM thread_edges WHERE thread_id = ?").all(e);for(let o of r)s.prepare(`INSERT INTO thread_edges
735
785
  (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
736
786
  VALUES (?, ?, ?, ?, ?, ?, ?)
737
787
  ON CONFLICT(thread_id, session_id) DO UPDATE SET
738
788
  parent_session_id = COALESCE(thread_edges.parent_session_id, excluded.parent_session_id),
739
789
  role = CASE WHEN thread_edges.role = 'origin' OR excluded.role = 'origin' THEN 'origin' ELSE 'child' END,
740
790
  confidence = MAX(thread_edges.confidence, excluded.confidence),
741
- source = thread_edges.source`).run(t,o.session_id,o.parent_session_id,o.role,o.confidence,o.source,n);s.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),B(t),Pt();let i=k(t);if(!i)throw new Error("merge destination disappeared");return i}function Gt(e){if(e.sessionIds.length===0)throw new Error("no sessions to split off");let t=p(),s=new Date().toISOString(),n=Ct();t.transaction(()=>{t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, NULL, ?)").run(n,e.newThreadName.trim(),s);for(let r of e.sessionIds){let o=t.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e.threadId,r);o&&(t.prepare(`INSERT INTO thread_edges
791
+ source = thread_edges.source`).run(t,o.session_id,o.parent_session_id,o.role,o.confidence,o.source,n);s.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),B(t),Pt();let i=k(t);if(!i)throw new Error("merge destination disappeared");return i}function Yt(e){if(e.sessionIds.length===0)throw new Error("no sessions to split off");let t=m(),s=new Date().toISOString(),n=Ct();t.transaction(()=>{t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, NULL, ?)").run(n,e.newThreadName.trim(),s);for(let r of e.sessionIds){let o=t.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e.threadId,r);o&&(t.prepare(`INSERT INTO thread_edges
742
792
  (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
743
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(n,r,o.parent_session_id,o.role,o.confidence,o.source,s),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,r))}})(),B(e.threadId),B(n);let i=k(n);if(!i)throw new Error("split destination disappeared");return i}L();F();import{writeFileSync as xi,mkdirSync as vi,existsSync as Ci}from"node:fs";import{join as qt}from"node:path";L();var Kt=80;function zt(e){if(e.alias&&e.alias.trim())return e.alias.trim();if(e.auto_title&&e.auto_title.trim())return e.auto_title.trim();let t=(e.first_user_message??"").trim();if(!t)return e.id.slice(0,8);let s=t.split(`
744
- `)[0].trim();return s.length>Kt?s.slice(0,Kt)+"\u2026":s}function Oi(e){return p().prepare(`SELECT s.id AS id,
793
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(n,r,o.parent_session_id,o.role,o.confidence,o.source,s),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,r))}})(),B(e.threadId),B(n);let i=k(n);if(!i)throw new Error("split destination disappeared");return i}L();M();import{writeFileSync as xi,mkdirSync as vi,existsSync as Ci}from"node:fs";import{join as qt}from"node:path";L();var zt=80;function Kt(e){if(e.alias&&e.alias.trim())return e.alias.trim();if(e.auto_title&&e.auto_title.trim())return e.auto_title.trim();let t=(e.first_user_message??"").trim();if(!t)return e.id.slice(0,8);let s=t.split(`
794
+ `)[0].trim();return s.length>zt?s.slice(0,zt)+"\u2026":s}function Oi(e){return m().prepare(`SELECT s.id AS id,
745
795
  sa.alias AS alias,
746
796
  s.auto_title AS auto_title,
747
797
  s.first_user_message AS first_user_message
748
798
  FROM sessions s
749
799
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
750
- WHERE s.id = ?`).get(e)??null}function Ii(e){let t=Oi(e);return t?zt(t):e.slice(0,8)}function Jt(e){if(!e)return null;let t=p(),s=t.prepare(`SELECT e.thread_id AS thread_id,
800
+ WHERE s.id = ?`).get(e)??null}function Ii(e){let t=Oi(e);return t?Kt(t):e.slice(0,8)}function Jt(e){if(!e)return null;let t=m(),s=t.prepare(`SELECT e.thread_id AS thread_id,
751
801
  t.name AS thread_name,
752
802
  e.parent_session_id AS parent_session_id,
753
803
  e.added_at AS added_at
@@ -766,7 +816,7 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
766
816
  LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
767
817
  WHERE e.thread_id = ?
768
818
  AND e.session_id NOT IN (${r})
769
- ORDER BY e.added_at ASC`).all(s.thread_id,...i).map(c=>({id:c.session_id,title:c.id?zt(c):c.session_id.slice(0,8)}));return{thread_id:s.thread_id,thread_name:s.thread_name,parent_session:n,siblings:a}}var Je=qt(S,"titles");var ce=5,Re=15,ki=500;function Vt(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>!!s&&typeof s=="object"&&typeof s.title=="string"&&typeof s.replaced_at=="string")}catch{}return[]}function Qt(e){let t=p(),s=t.prepare(`SELECT rowid AS rid, content_text
819
+ ORDER BY e.added_at ASC`).all(s.thread_id,...i).map(c=>({id:c.session_id,title:c.id?Kt(c):c.session_id.slice(0,8)}));return{thread_id:s.thread_id,thread_name:s.thread_name,parent_session:n,siblings:a}}var Je=qt(b,"titles");var ce=5,Re=15,ki=500;function Vt(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>!!s&&typeof s=="object"&&typeof s.title=="string"&&typeof s.replaced_at=="string")}catch{}return[]}function Qt(e){let t=m(),s=t.prepare(`SELECT rowid AS rid, content_text
770
820
  FROM messages
771
821
  WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
772
822
  AND content_text IS NOT NULL AND content_text != ''
@@ -776,26 +826,26 @@ CREATE INDEX IF NOT EXISTS idx_synth_results_created
776
826
  WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
777
827
  AND content_text IS NOT NULL AND content_text != ''
778
828
  ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
779
- LIMIT ?`).all(e,Re),i=new Map;for(let d of s)i.set(d.rid,d.content_text);for(let d of n)i.set(d.rid,d.content_text);if(i.size===0)throw new Error("no user messages available to summarise");let r=Array.from(i.entries()).sort((d,u)=>d[0]-u[0]).map(([,d])=>({content_text:d})),o=s.length===ce&&n.length===Re&&i.size===ce+Re,a=r.map((d,u)=>{let m=(d.content_text??"").slice(0,ki);return o&&u===ce?`--- (middle of session omitted) ---
780
- ${u+1}. ${m}`:`${u+1}. ${m}`}).join(`
829
+ LIMIT ?`).all(e,Re),i=new Map;for(let d of s)i.set(d.rid,d.content_text);for(let d of n)i.set(d.rid,d.content_text);if(i.size===0)throw new Error("no user messages available to summarise");let r=Array.from(i.entries()).sort((d,u)=>d[0]-u[0]).map(([,d])=>({content_text:d})),o=s.length===ce&&n.length===Re&&i.size===ce+Re,a=r.map((d,u)=>{let p=(d.content_text??"").slice(0,ki);return o&&u===ce?`--- (middle of session omitted) ---
830
+ ${u+1}. ${p}`:`${u+1}. ${p}`}).join(`
781
831
  `),c=null;try{c=Jt(e)}catch(d){console.error("[autoTitle] thread context resolution failed:",d),c=null}let l=[];return c&&(l.push(Di(c)),l.push("")),l.push(`You will receive a sample of user messages from a Claude Code session: the first ${ce}`,`messages (initial intent) and the last ${Re} messages (current direction).`,"Write a single descriptive title, max 50 characters, focused on what the user is","currently trying to accomplish. If initial intent and current direction differ, prefer","the current direction. Output ONLY the title, with no quotes and no trailing punctuation.","","Messages:",a),l.join(`
782
- `)}var ze=5;function Di(e){let t=[];t.push("Thread context:"),t.push(`- This session is part of thread "${e.thread_name}".`),e.parent_session&&t.push(`- Parent session: "${e.parent_session.title}"`);let s=e.siblings.length;if(s>0){let i=e.siblings.slice(0,ze).map(o=>`"${o.title}"`).join(", "),r=s>ze?`, and ${s-ze} more`:"";t.push(`- Sibling sessions (${s}): ${i}${r}`)}return t.push(""),t.push("Generate a title that reflects this session's role in the thread."),t.push('If siblings use a pattern like "Phase A / Phase B" or "Wave 1 of 4",'),t.push("follow the same pattern."),t.join(`
783
- `)}function Zt(e,t,s){let n=t.trim();if(!n)return;let i=p(),r=i.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
832
+ `)}var Ke=5;function Di(e){let t=[];t.push("Thread context:"),t.push(`- This session is part of thread "${e.thread_name}".`),e.parent_session&&t.push(`- Parent session: "${e.parent_session.title}"`);let s=e.siblings.length;if(s>0){let i=e.siblings.slice(0,Ke).map(o=>`"${o.title}"`).join(", "),r=s>Ke?`, and ${s-Ke} more`:"";t.push(`- Sibling sessions (${s}): ${i}${r}`)}return t.push(""),t.push("Generate a title that reflects this session's role in the thread."),t.push('If siblings use a pattern like "Phase A / Phase B" or "Wave 1 of 4",'),t.push("follow the same pattern."),t.join(`
833
+ `)}function Zt(e,t,s){let n=t.trim();if(!n)return;let i=m(),r=i.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
784
834
  FROM sessions WHERE id = ?`).get(e);if(!r||s==="heuristic"&&r.auto_title_source==="agent"&&r.auto_title||r.auto_title===n&&r.auto_title_source===s)return;let o=Vt(r.auto_title_history),a=new Date().toISOString();r.auto_title&&r.auto_title_source&&o.push({title:r.auto_title,source:r.auto_title_source,replaced_at:a}),i.prepare(`UPDATE sessions
785
835
  SET auto_title = ?,
786
836
  auto_title_source = ?,
787
837
  auto_title_generated_at = ?,
788
838
  auto_title_history = ?
789
- WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(o),e),Mi(e,n,s,a)}function es(e){let t=p().prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
839
+ WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(o),e),Mi(e,n,s,a)}function es(e){let t=m().prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
790
840
  FROM sessions WHERE id = ?`).get(e);return t?{auto_title:t.auto_title,auto_title_source:t.auto_title_source??null,auto_title_generated_at:t.auto_title_generated_at,auto_title_history:Vt(t.auto_title_history)}:null}function Fi(){x(),Ci(Je)||vi(Je,{recursive:!0})}function Mi(e,t,s,n){try{Fi();let i=qt(Je,`${e}.txt`),r=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${s} \xB7 updated ${n}
791
841
  `;xi(i,r+t+`
792
842
  `)}catch(i){console.error("[autoTitle] mirror write failed:",i)}}var qi=50;function Vi(e){if(e.length===0)return[];let t=[...e].sort((c,l)=>c.added_at<l.added_at?-1:c.added_at>l.added_at?1:0),s=new Map,n=new Set,i=[];for(let c of t)if(c.parent_session_id){let l=s.get(c.parent_session_id)??[];l.push(c),s.set(c.parent_session_id,l)}let r=t.filter(c=>c.role==="origin"),a=[...r.length>0?r:t.filter(c=>!c.parent_session_id)];for(;a.length>0;){let c=a.shift();if(n.has(c.session_id))continue;n.add(c.session_id),i.push(c.session_id);let l=s.get(c.session_id)??[];for(let d of l)n.has(d.session_id)||a.push(d)}for(let c of t)n.has(c.session_id)||(n.add(c.session_id),i.push(c.session_id));return i}function Qi(e){let t=Qt(e.sessionId),s=["",`BULK CONTEXT: You are titling session ${e.current} of ${e.total} in this thread.`,"The parent and earlier siblings already have titles (shown above). YOUR JOB:","",'1. Identify the naming pattern. If parent is "Build Feature X" and earlier',' siblings are "Phase A: API design" and "Phase B: client wiring", the pattern',' is "Phase {LETTER}: {topic}".',"2. If no pattern is yet established (this is the first child), INVENT a pattern",' that will scale to N children. Good patterns: "Phase A/B/C", "Wave 1 of M",',' "Step N", or a domain-specific structure if the thread name suggests one.',"3. Output a title that follows the pattern AND describes what THIS session"," does in 50 characters max.","","Output ONLY the title, no quotes, no trailing punctuation."].join(`
793
843
  `);return`${t}
794
- ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(os(),rs));if(!s())throw new Error("claude CLI not found on PATH");let n=await t(e.prompt,[],{model:e.model});if(!n.success)throw new Error(`claude CLI exited ${n.exitCode}: ${n.stderr.slice(-500)}`);let i=tr(n.stdout);if(!i)throw new Error("claude CLI returned an empty title");return i.slice(0,qi)}function tr(e){let t=e.trim();if(!t)return"";try{let s=JSON.parse(t);if(s&&typeof s=="object"){let n=s.result;if(typeof n=="string")return as(n)}}catch{}return as(t)}function as(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function ls(e,t={}){let s=k(e);if(!s)throw new Error(`thread not found: ${e}`);let n=Vi(s.edges),i=n.length,r=t.force??!1,o=t.signal,a=[],c=[],l=[];for(let d=0;d<n.length;d++){let u=n[d],m=d+1;if(o?.aborted){let _={sessionId:u,reason:"cancelled"};c.push(_),t.onSkipped?.(_);continue}if(!r&&Zi(u)==="agent"){let _={sessionId:u,reason:"already-titled"};c.push(_),t.onSkipped?.(_);continue}let g;try{if(cs)g=await cs({sessionId:u,current:m,total:i});else{let _=Qi({sessionId:u,current:m,total:i});g=await er({prompt:_,model:t.model})}}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:f};l.push(h),t.onFailed?.(h);continue}try{Zt(u,g,"agent")}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:`setAutoTitle failed: ${f}`};l.push(h),t.onFailed?.(h);continue}a.push(u),t.onProgress?.({current:m,total:i,sessionId:u,title:g})}return{generated:a,skipped:c,failed:l}}var cs=null;L();import{execFile as fr}from"node:child_process";import{promisify as hr}from"node:util";import{readlink as Er,readFile as _s}from"node:fs/promises";import{platform as Ne}from"node:os";import{readFileSync as sr,statSync as nr}from"node:fs";var ir=200*1024*1024;var Ve=.5,ps=Ve,rr=[{maxGapMs:3600*1e3,weight:.7,label:"<1h gap"},{maxGapMs:14400*1e3,weight:.4,label:"<4h gap"},{maxGapMs:1440*60*1e3,weight:.2,label:"<24h gap"}],or=["let's commit","lets commit","now commit","inspect the diff","inspect this diff","review the diff","review this diff","diff of all changes","diff of changes","based on what we just did","based on the things done","based on all the things","continue from","continuing from","pick up where","next step","now fix","now lets","now let's","from the previous session","from our last session","from the last session"];function ds(e){if(!e)return null;if(e.startsWith("/")){let s=e.split(" \xB7 ");if(s.length>1)return s[1].trim().toLowerCase()}let t=e.split(" \xB7 ");return t.length>1?t[t.length-1].trim().toLowerCase():null}function ar(e,t){let s=t-e;if(s<0)return{weight:0,label:null};for(let n of rr)if(s<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function cr(e){if(e.length===0)return{weight:0,matched:null,matchedIndex:-1};let t=[.4,.35,.3,.25,.2,.15,.1],s=Math.min(e.length,t.length);for(let n=0;n<s;n++){let i=e[n];if(!i)continue;let r=i.toLowerCase();for(let o of or)if(r.includes(o))return{weight:t[n],matched:o,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function qe(e,t){if(!e||!t||e.length!==t.length)return 0;let s=0;for(let n=0;n<e.length;n++)s+=e[n]*t[n];return s<-1?-1:s>1?1:s}function lr(e,t){if(e.length===0||t.length===0)return 0;let s=0;for(let n of e)for(let i of t){let r=qe(n,i);r>s&&(s=r)}return s}function dr(e,t){let s=qe(e.mean_embedding,t.mean_embedding),n=qe(e.tail_pool,t.head_pool),i=lr(e.sample_chunks,t.sample_chunks),r=0,o=null;if(s>r&&(r=s,o="mean"),n>r&&(r=n,o="asymmetric"),i>r&&(r=i,o="max_pool"),r<.65)return{weight:0,cosine:r,mode:null};if(r>=.85)return{weight:.3,cosine:r,mode:o};let a=(r-.65)/.2*.3;return{weight:Math.round(a*100)/100,cosine:r,mode:o}}function ur(e,t){return e.cluster_id===null||t.cluster_id===null?{weight:0,same:!1}:e.cluster_id!==t.cluster_id?{weight:0,same:!1}:{weight:.05,same:!0}}function pr(e,t){if(e.size===0||t.size===0)return{weight:0,count:0};let s=0;for(let i of t)e.has(i)&&s++;return s===0?{weight:0,count:0}:{weight:Math.min(.4,s*.1),count:s}}function mr(e,t){let s=ds(e),n=ds(t);return s&&n&&s===n?{weight:.1,brand:s}:{weight:0,brand:null}}function us(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function gr(e,t){let s=0,n=null,i=!1;if(e.authored_paths.size>0){let r=t.recent_user_messages.slice(0,3).join(`
795
- `).toLowerCase();for(let o of e.authored_paths){let a=o.toLowerCase();if(t.touched_files.has(o)||r.includes(a)){s+=.5,n=o;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let r=us(t.recent_user_messages[0]);if(r.length>=200)for(let o of e.authored_content){let a=us(o);if(a.length<200)continue;let c=Math.min(a.length,240),l=a.slice(0,c);if(r.includes(l)){s+=.4,i=!0;break}}}return{weight:Math.min(.6,s),pathMatch:n,contentMatch:i}}function _r(e,t,s=ps){if(t.started_at_ms<=e.started_at_ms)return null;let n=e.ended_at_ms??e.started_at_ms;if(t.started_at_ms<n)return null;let i=ar(n,t.started_at_ms),r=t.recent_user_messages.length>0?t.recent_user_messages:t.first_user_message?[t.first_user_message]:[],o=cr(r),a=pr(e.touched_files,t.touched_files),c=mr(e.auto_title,t.auto_title),l=dr(e,t),d=ur(e,t),u=gr(e,t),m=i.weight+o.weight+a.weight+c.weight+l.weight+d.weight+u.weight;if(m<s)return null;let g=[];if(i.label&&g.push(`temporal ${i.label} (+${i.weight})`),o.matched){let _=o.matchedIndex===0?"opening message":`message #${o.matchedIndex+1}`;g.push(`continuation phrase "${o.matched}" in ${_} (+${o.weight})`)}if(a.count>0&&g.push(`${a.count} file${a.count===1?"":"s"} overlap (+${a.weight.toFixed(1)})`),c.brand&&g.push(`shared brand "${c.brand}" (+${c.weight})`),l.weight>0&&l.mode&&g.push(`semantic ${l.mode==="asymmetric"?"tail\u2192head":l.mode==="max_pool"?"best-chunk":"mean"} ${l.cosine.toFixed(2)} (+${l.weight.toFixed(2)})`),d.same&&g.push(`same cluster (+${d.weight})`),u.weight>0){let _=[];u.pathMatch&&_.push(`opened authored path "${u.pathMatch.split("/").pop()}"`),u.contentMatch&&_.push("verbatim-paste of authored content"),g.push(`doc-authorship: ${_.join(" + ")} (+${u.weight.toFixed(2)})`)}return{parent_id:e.id,child_id:t.id,confidence:Math.min(1,m),signals:{temporal:i.weight,continuation:o.weight,file_overlap:a.weight,same_brand:c.weight,semantic:l.weight,cluster:d.weight,doc_authorship:u.weight},reasons:g}}function ms(e,t=ps){let s=[];for(let n=0;n<e.length;n++){let i=e[n],r=null;for(let o=0;o<n;o++){let a=e[o],c=_r(a,i,t);c&&(!r||c.confidence>r.confidence)&&(r=c)}r&&s.push(r)}return s}function gs(e,t={}){let s=t.maxUserMessages??5,n=t.userMessageMaxLen??2e3,i=new Set,r=[],o=new Set,a=[],c;try{if(nr(e).size>ir)return{touched_files:i,recent_user_messages:r,authored_paths:o,authored_content:a};c=sr(e,"utf8")}catch{return{touched_files:i,recent_user_messages:r,authored_paths:o,authored_content:a}}let l=0;for(;l<c.length;){let d=c.indexOf(`
796
- `,l),u=d===-1?c.length:d,m=c.slice(l,u);if(l=d===-1?c.length:d+1,!m.trim())continue;let g;try{g=JSON.parse(m)}catch{continue}let _=g;if(_.type==="user"&&_.message?.role==="user"&&typeof _.message.content=="string"&&r.length<s){let h=_.message.content.trim();h&&r.push(h.length>n?h.slice(0,n):h)}let f=_.message?.content;if(Array.isArray(f))for(let h of f){if(!h||typeof h!="object")continue;let R=h;if(R.type!=="tool_use")continue;let w=R.input??{},U=typeof w.file_path=="string"?w.file_path:null;if(U){let b=ye(U);b&&i.add(b)}if((R.name==="Write"||R.name==="Edit"||R.name==="MultiEdit")&&U){let b=ye(U);b&&o.add(b);let G=typeof w.content=="string"?w.content:typeof w.new_string=="string"?w.new_string:null;G&&G.length>=200&&a.push(G.length>4096?G.slice(0,4096):G)}if(R.name==="Bash"&&typeof w.command=="string")for(let b of w.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let G=ye(b[1]);G&&i.add(G)}if((R.name==="Glob"||R.name==="Grep")&&typeof w.pattern=="string"){let b=ye(w.pattern);b&&!b.includes("*")&&i.add(b)}}}return{touched_files:i,recent_user_messages:r,authored_paths:o,authored_content:a}}function ye(e){let t=e.trim().replace(/^['"]|['"]$/g,"");return!t||t.length<4||/[<>|;&\$`]/.test(t)?null:t}var Ze=hr(fr),Tr=6,fs="Active ",hs=" sessions \u2014 ",Sr=6e4;async function br(){if(Ne()==="win32")return[];for(let t of["/bin/ps","/usr/bin/ps"])try{let{stdout:s}=await Ze(t,["-eo","pid=,comm="],{timeout:2e3}),n=[];for(let i of s.split(`
844
+ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(os(),rs));if(!s())throw new Error("claude CLI not found on PATH");let n=await t(e.prompt,[],{model:e.model});if(!n.success)throw new Error(`claude CLI exited ${n.exitCode}: ${n.stderr.slice(-500)}`);let i=tr(n.stdout);if(!i)throw new Error("claude CLI returned an empty title");return i.slice(0,qi)}function tr(e){let t=e.trim();if(!t)return"";try{let s=JSON.parse(t);if(s&&typeof s=="object"){let n=s.result;if(typeof n=="string")return as(n)}}catch{}return as(t)}function as(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function ls(e,t={}){let s=k(e);if(!s)throw new Error(`thread not found: ${e}`);let n=Vi(s.edges),i=n.length,r=t.force??!1,o=t.signal,a=[],c=[],l=[];for(let d=0;d<n.length;d++){let u=n[d],p=d+1;if(o?.aborted){let _={sessionId:u,reason:"cancelled"};c.push(_),t.onSkipped?.(_);continue}if(!r&&Zi(u)==="agent"){let _={sessionId:u,reason:"already-titled"};c.push(_),t.onSkipped?.(_);continue}let g;try{if(cs)g=await cs({sessionId:u,current:p,total:i});else{let _=Qi({sessionId:u,current:p,total:i});g=await er({prompt:_,model:t.model})}}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:f};l.push(h),t.onFailed?.(h);continue}try{Zt(u,g,"agent")}catch(_){let f=_ instanceof Error?_.message:String(_??"unknown error"),h={sessionId:u,error:`setAutoTitle failed: ${f}`};l.push(h),t.onFailed?.(h);continue}a.push(u),t.onProgress?.({current:p,total:i,sessionId:u,title:g})}return{generated:a,skipped:c,failed:l}}var cs=null;L();import{execFile as fr}from"node:child_process";import{promisify as hr}from"node:util";import{readlink as Er,readFile as _s}from"node:fs/promises";import{platform as Ne}from"node:os";import{readFileSync as sr,statSync as nr}from"node:fs";var ir=200*1024*1024;var Ve=.5,ps=Ve,rr=[{maxGapMs:3600*1e3,weight:.7,label:"<1h gap"},{maxGapMs:14400*1e3,weight:.4,label:"<4h gap"},{maxGapMs:1440*60*1e3,weight:.2,label:"<24h gap"}],or=["let's commit","lets commit","now commit","inspect the diff","inspect this diff","review the diff","review this diff","diff of all changes","diff of changes","based on what we just did","based on the things done","based on all the things","continue from","continuing from","pick up where","next step","now fix","now lets","now let's","from the previous session","from our last session","from the last session"];function ds(e){if(!e)return null;if(e.startsWith("/")){let s=e.split(" \xB7 ");if(s.length>1)return s[1].trim().toLowerCase()}let t=e.split(" \xB7 ");return t.length>1?t[t.length-1].trim().toLowerCase():null}function ar(e,t){let s=t-e;if(s<0)return{weight:0,label:null};for(let n of rr)if(s<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function cr(e){if(e.length===0)return{weight:0,matched:null,matchedIndex:-1};let t=[.4,.35,.3,.25,.2,.15,.1],s=Math.min(e.length,t.length);for(let n=0;n<s;n++){let i=e[n];if(!i)continue;let r=i.toLowerCase();for(let o of or)if(r.includes(o))return{weight:t[n],matched:o,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function qe(e,t){if(!e||!t||e.length!==t.length)return 0;let s=0;for(let n=0;n<e.length;n++)s+=e[n]*t[n];return s<-1?-1:s>1?1:s}function lr(e,t){if(e.length===0||t.length===0)return 0;let s=0;for(let n of e)for(let i of t){let r=qe(n,i);r>s&&(s=r)}return s}function dr(e,t){let s=qe(e.mean_embedding,t.mean_embedding),n=qe(e.tail_pool,t.head_pool),i=lr(e.sample_chunks,t.sample_chunks),r=0,o=null;if(s>r&&(r=s,o="mean"),n>r&&(r=n,o="asymmetric"),i>r&&(r=i,o="max_pool"),r<.65)return{weight:0,cosine:r,mode:null};if(r>=.85)return{weight:.3,cosine:r,mode:o};let a=(r-.65)/.2*.3;return{weight:Math.round(a*100)/100,cosine:r,mode:o}}function ur(e,t){return e.cluster_id===null||t.cluster_id===null?{weight:0,same:!1}:e.cluster_id!==t.cluster_id?{weight:0,same:!1}:{weight:.05,same:!0}}function pr(e,t){if(e.size===0||t.size===0)return{weight:0,count:0};let s=0;for(let i of t)e.has(i)&&s++;return s===0?{weight:0,count:0}:{weight:Math.min(.4,s*.1),count:s}}function mr(e,t){let s=ds(e),n=ds(t);return s&&n&&s===n?{weight:.1,brand:s}:{weight:0,brand:null}}function us(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function gr(e,t){let s=0,n=null,i=!1;if(e.authored_paths.size>0){let r=t.recent_user_messages.slice(0,3).join(`
845
+ `).toLowerCase();for(let o of e.authored_paths){let a=o.toLowerCase();if(t.touched_files.has(o)||r.includes(a)){s+=.5,n=o;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let r=us(t.recent_user_messages[0]);if(r.length>=200)for(let o of e.authored_content){let a=us(o);if(a.length<200)continue;let c=Math.min(a.length,240),l=a.slice(0,c);if(r.includes(l)){s+=.4,i=!0;break}}}return{weight:Math.min(.6,s),pathMatch:n,contentMatch:i}}function _r(e,t,s=ps){if(t.started_at_ms<=e.started_at_ms)return null;let n=e.ended_at_ms??e.started_at_ms;if(t.started_at_ms<n)return null;let i=ar(n,t.started_at_ms),r=t.recent_user_messages.length>0?t.recent_user_messages:t.first_user_message?[t.first_user_message]:[],o=cr(r),a=pr(e.touched_files,t.touched_files),c=mr(e.auto_title,t.auto_title),l=dr(e,t),d=ur(e,t),u=gr(e,t),p=i.weight+o.weight+a.weight+c.weight+l.weight+d.weight+u.weight;if(p<s)return null;let g=[];if(i.label&&g.push(`temporal ${i.label} (+${i.weight})`),o.matched){let _=o.matchedIndex===0?"opening message":`message #${o.matchedIndex+1}`;g.push(`continuation phrase "${o.matched}" in ${_} (+${o.weight})`)}if(a.count>0&&g.push(`${a.count} file${a.count===1?"":"s"} overlap (+${a.weight.toFixed(1)})`),c.brand&&g.push(`shared brand "${c.brand}" (+${c.weight})`),l.weight>0&&l.mode&&g.push(`semantic ${l.mode==="asymmetric"?"tail\u2192head":l.mode==="max_pool"?"best-chunk":"mean"} ${l.cosine.toFixed(2)} (+${l.weight.toFixed(2)})`),d.same&&g.push(`same cluster (+${d.weight})`),u.weight>0){let _=[];u.pathMatch&&_.push(`opened authored path "${u.pathMatch.split("/").pop()}"`),u.contentMatch&&_.push("verbatim-paste of authored content"),g.push(`doc-authorship: ${_.join(" + ")} (+${u.weight.toFixed(2)})`)}return{parent_id:e.id,child_id:t.id,confidence:Math.min(1,p),signals:{temporal:i.weight,continuation:o.weight,file_overlap:a.weight,same_brand:c.weight,semantic:l.weight,cluster:d.weight,doc_authorship:u.weight},reasons:g}}function ms(e,t=ps){let s=[];for(let n=0;n<e.length;n++){let i=e[n],r=null;for(let o=0;o<n;o++){let a=e[o],c=_r(a,i,t);c&&(!r||c.confidence>r.confidence)&&(r=c)}r&&s.push(r)}return s}function gs(e,t={}){let s=t.maxUserMessages??5,n=t.userMessageMaxLen??2e3,i=new Set,r=[],o=new Set,a=[],c;try{if(nr(e).size>ir)return{touched_files:i,recent_user_messages:r,authored_paths:o,authored_content:a};c=sr(e,"utf8")}catch{return{touched_files:i,recent_user_messages:r,authored_paths:o,authored_content:a}}let l=0;for(;l<c.length;){let d=c.indexOf(`
846
+ `,l),u=d===-1?c.length:d,p=c.slice(l,u);if(l=d===-1?c.length:d+1,!p.trim())continue;let g;try{g=JSON.parse(p)}catch{continue}let _=g;if(_.type==="user"&&_.message?.role==="user"&&typeof _.message.content=="string"&&r.length<s){let h=_.message.content.trim();h&&r.push(h.length>n?h.slice(0,n):h)}let f=_.message?.content;if(Array.isArray(f))for(let h of f){if(!h||typeof h!="object")continue;let S=h;if(S.type!=="tool_use")continue;let y=S.input??{},F=typeof y.file_path=="string"?y.file_path:null;if(F){let R=ye(F);R&&i.add(R)}if((S.name==="Write"||S.name==="Edit"||S.name==="MultiEdit")&&F){let R=ye(F);R&&o.add(R);let Y=typeof y.content=="string"?y.content:typeof y.new_string=="string"?y.new_string:null;Y&&Y.length>=200&&a.push(Y.length>4096?Y.slice(0,4096):Y)}if(S.name==="Bash"&&typeof y.command=="string")for(let R of y.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let Y=ye(R[1]);Y&&i.add(Y)}if((S.name==="Glob"||S.name==="Grep")&&typeof y.pattern=="string"){let R=ye(y.pattern);R&&!R.includes("*")&&i.add(R)}}}return{touched_files:i,recent_user_messages:r,authored_paths:o,authored_content:a}}function ye(e){let t=e.trim().replace(/^['"]|['"]$/g,"");return!t||t.length<4||/[<>|;&\$`]/.test(t)?null:t}var Ze=hr(fr),Tr=6,fs="Active ",hs=" sessions \u2014 ",Sr=6e4;async function br(){if(Ne()==="win32")return[];for(let t of["/bin/ps","/usr/bin/ps"])try{let{stdout:s}=await Ze(t,["-eo","pid=,comm="],{timeout:2e3}),n=[];for(let i of s.split(`
797
847
  `)){let r=i.trim().match(/^(\d+)\s+(.+)$/);if(!r)continue;let o=Number(r[1]),a=r[2].trim();(a==="claude"||a.endsWith("/claude")||a.endsWith("/bin/claude"))&&Number.isFinite(o)&&n.push(o)}return n}catch{continue}return[]}async function Rr(e){let t=Ne();if(t==="linux")try{return(await Er(`/proc/${e}/cwd`)).replace(/\/+$/,"")}catch{return null}if(t==="darwin"||t==="freebsd"||t==="openbsd")for(let s of["/usr/sbin/lsof","/usr/bin/lsof"])try{let{stdout:n}=await Ze(s,["-a","-p",String(e),"-d","cwd","-Fn"],{timeout:2e3});for(let i of n.split(`
798
- `))if(i.startsWith("n"))return i.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function yr(e){let t=Ne();if(t==="linux")try{let s=await _s(`/proc/${e}/stat`,"utf8"),n=s.lastIndexOf(")");if(n===-1)return null;let i=s.slice(n+1).trim().split(/\s+/),r=Number(i[19]);if(!Number.isFinite(r))return null;let o=await _s("/proc/uptime","utf8"),a=Number(o.split(/\s+/)[0]);return Number.isFinite(a)?Date.now()-a*1e3+r/100*1e3:null}catch{return null}if(t==="darwin"||t==="freebsd"||t==="openbsd")for(let s of["/bin/ps","/usr/bin/ps"])try{let{stdout:n}=await Ze(s,["-o","lstart=","-p",String(e)],{timeout:2e3}),i=Date.parse(n.trim());return Number.isFinite(i)?i:null}catch{continue}return null}async function Nr(e,t){let s=await br();if(s.length===0)return null;let n=e.replace(/\/+$/,""),i=[];for(let o of s){let a=await Rr(o);if(a&&(a===n||a.startsWith(n+"/"))){let c=await yr(o);i.push({pid:o,startMs:c})}}if(i.length===0)return new Set;let r=new Set;for(let{startMs:o}of i){if(o==null)continue;let a=null,c=Sr;for(let l of t){if(r.has(l.session_id)||!l.started_at)continue;let d=Date.parse(l.started_at);if(!Number.isFinite(d))continue;let u=Math.abs(d-o);u<c&&(c=u,a=l)}a&&r.add(a.session_id)}return r}function wr(e){let s=p().prepare("SELECT id, name, decoded_path FROM projects WHERE id = ? LIMIT 1").get(e);if(!s)throw new Error(`project ${e} not found`);return s}function Lr(e,t){let s=p(),n=`${fs}${t}${hs}%`,i=s.prepare(`SELECT t.id
848
+ `))if(i.startsWith("n"))return i.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function yr(e){let t=Ne();if(t==="linux")try{let s=await _s(`/proc/${e}/stat`,"utf8"),n=s.lastIndexOf(")");if(n===-1)return null;let i=s.slice(n+1).trim().split(/\s+/),r=Number(i[19]);if(!Number.isFinite(r))return null;let o=await _s("/proc/uptime","utf8"),a=Number(o.split(/\s+/)[0]);return Number.isFinite(a)?Date.now()-a*1e3+r/100*1e3:null}catch{return null}if(t==="darwin"||t==="freebsd"||t==="openbsd")for(let s of["/bin/ps","/usr/bin/ps"])try{let{stdout:n}=await Ze(s,["-o","lstart=","-p",String(e)],{timeout:2e3}),i=Date.parse(n.trim());return Number.isFinite(i)?i:null}catch{continue}return null}async function Nr(e,t){let s=await br();if(s.length===0)return null;let n=e.replace(/\/+$/,""),i=[];for(let o of s){let a=await Rr(o);if(a&&(a===n||a.startsWith(n+"/"))){let c=await yr(o);i.push({pid:o,startMs:c})}}if(i.length===0)return new Set;let r=new Set;for(let{startMs:o}of i){if(o==null)continue;let a=null,c=Sr;for(let l of t){if(r.has(l.session_id)||!l.started_at)continue;let d=Date.parse(l.started_at);if(!Number.isFinite(d))continue;let u=Math.abs(d-o);u<c&&(c=u,a=l)}a&&r.add(a.session_id)}return r}function wr(e){let s=m().prepare("SELECT id, name, decoded_path FROM projects WHERE id = ? LIMIT 1").get(e);if(!s)throw new Error(`project ${e} not found`);return s}function Lr(e,t){let s=m(),n=`${fs}${t}${hs}%`,i=s.prepare(`SELECT t.id
799
849
  FROM threads t
800
850
  WHERE t.archived = 0
801
851
  AND t.name LIKE ?
@@ -806,7 +856,7 @@ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){
806
856
  WHERE s.project_id = ?
807
857
  AND t.archived = 0
808
858
  AND t.name LIKE ?
809
- LIMIT 1`).get(e,n);return r?k(r.id):null}function Es(e){let t=e?new Date(e):new Date,s=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),i=String(t.getDate()).padStart(2,"0");return`${s}-${n}-${i}`}function Qe(e,t){let s=p(),n=t>0,i=n?Date.now()-t*60*60*1e3:0;return n?s.prepare(`SELECT s.id AS session_id,
859
+ LIMIT 1`).get(e,n);return r?k(r.id):null}function Es(e){let t=e?new Date(e):new Date,s=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),i=String(t.getDate()).padStart(2,"0");return`${s}-${n}-${i}`}function Qe(e,t){let s=m(),n=t>0,i=n?Date.now()-t*60*60*1e3:0;return n?s.prepare(`SELECT s.id AS session_id,
810
860
  sa.alias AS alias,
811
861
  s.auto_title AS auto_title,
812
862
  s.first_user_message AS first_user_message,
@@ -826,24 +876,24 @@ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){
826
876
  FROM sessions s
827
877
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
828
878
  WHERE s.project_id = ?
829
- ORDER BY s.started_at ASC`).all(e)}function Ar(e){let t=p(),s=[];for(let n of e){if(!n.started_at)continue;let i=Date.parse(n.started_at);if(!Number.isFinite(i))continue;let r=t.prepare("SELECT file_path, ended_at FROM sessions WHERE id = ?").get(n.session_id);if(!r?.file_path)continue;let o=r.ended_at?Date.parse(r.ended_at):null,a=gs(r.file_path,{maxUserMessages:7});s.push({id:n.session_id,started_at_ms:i,ended_at_ms:Number.isFinite(o)?o:null,first_user_message:n.first_user_message,recent_user_messages:a.recent_user_messages,auto_title:n.auto_title,touched_files:a.touched_files,mean_embedding:null,head_pool:null,tail_pool:null,sample_chunks:[],cluster_id:null,authored_paths:a.authored_paths,authored_content:a.authored_content})}return s}function Or(e){let s=p().prepare(`SELECT session_id, parent_session_id, source
879
+ ORDER BY s.started_at ASC`).all(e)}function Ar(e){let t=m(),s=[];for(let n of e){if(!n.started_at)continue;let i=Date.parse(n.started_at);if(!Number.isFinite(i))continue;let r=t.prepare("SELECT file_path, ended_at FROM sessions WHERE id = ?").get(n.session_id);if(!r?.file_path)continue;let o=r.ended_at?Date.parse(r.ended_at):null,a=gs(r.file_path,{maxUserMessages:7});s.push({id:n.session_id,started_at_ms:i,ended_at_ms:Number.isFinite(o)?o:null,first_user_message:n.first_user_message,recent_user_messages:a.recent_user_messages,auto_title:n.auto_title,touched_files:a.touched_files,mean_embedding:null,head_pool:null,tail_pool:null,sample_chunks:[],cluster_id:null,authored_paths:a.authored_paths,authored_content:a.authored_content})}return s}function Or(e){let s=m().prepare(`SELECT session_id, parent_session_id, source
830
880
  FROM thread_edges
831
- WHERE thread_id = ?`).all(e),n=new Map;for(let i of s)n.set(i.session_id,{parent_session_id:i.parent_session_id,source:i.source});return n}async function et(e,t={}){let s=wr(e),n=t.windowHours??Tr,i=t.scoreThreshold??Ve,r=t.useLivePids??!0,o=[],a=[];if(r&&s.decoded_path){let f=Qe(e,0),h=await Nr(s.decoded_path,f);if(h===null){let w=Ne()==="win32"?"Windows live-PID detection is not yet supported \u2014 falling back to the rolling mtime window.":"No live `claude` processes detected \u2014 falling back to the rolling mtime window. Output may include sessions that are no longer open.";o.push(w),a=Qe(e,n)}else h.size===0?(o.push(`No active terminals open in ${s.name} (cwd=${s.decoded_path}). Open a Claude terminal in this repo and re-run.`),a=[]):a=f.filter(R=>h.has(R.session_id))}else a=Qe(e,n);a.length===0&&!o.length&&o.push(`No active sessions in ${s.name} within the last ${n}h.`);let c=Lr(e,s.name),l=new Set(c?c.edges.map(f=>f.session_id):[]),d=a.filter(f=>!l.has(f.session_id)),u=Ar(a);u.sort((f,h)=>f.started_at_ms-h.started_at_ms);let m=ms(u,i),g=c?c.edges.filter(f=>f.source!=="auto-active"&&(f.parent_session_id||f.role==="origin")).map(f=>({session_id:f.session_id,parent_session_id:f.parent_session_id})):[],_=c?c.name:`${fs}${s.name}${hs}${Es(t.todayIso)}`;return{project:s,thread:{id:c?.id??null,name:_,exists:!!c,existing_session_count:c?.edges.length??0},candidates:a,proposed_additions:d,proposed_edges:m,preserved_manual_edges:g,warnings:o}}function Ts(e){let t={thread_id:"",added:0,edges_set:0,preserved_manual:e.preserved_manual_edges.length},s;e.thread.exists&&e.thread.id?s=e.thread.id:s=Se({name:e.thread.name,summary:`Auto-captured by sync-active on ${Es()}. Members are sessions in ${e.project.name} that were active within the rolling window. Re-runnable: subsequent runs append new active sessions and never overwrite manual edges.`}).id,t.thread_id=s;let n=Or(s);for(let o of e.candidates)n.has(o.session_id)||(be({threadId:s,sessionId:o.session_id,source:"auto-active",confidence:.5}),t.added++,n.set(o.session_id,{parent_session_id:null,source:"auto-active"}));for(let o of e.proposed_edges){let a=n.get(o.child_id);if(a&&a.source==="auto-active"&&a.parent_session_id!==o.parent_id&&n.has(o.parent_id))try{ae(s,o.child_id,o.parent_id),t.edges_set++,n.set(o.child_id,{parent_session_id:o.parent_id,source:a.source})}catch{}}let r=p().prepare(`SELECT session_id FROM thread_edges
881
+ WHERE thread_id = ?`).all(e),n=new Map;for(let i of s)n.set(i.session_id,{parent_session_id:i.parent_session_id,source:i.source});return n}async function et(e,t={}){let s=wr(e),n=t.windowHours??Tr,i=t.scoreThreshold??Ve,r=t.useLivePids??!0,o=[],a=[];if(r&&s.decoded_path){let f=Qe(e,0),h=await Nr(s.decoded_path,f);if(h===null){let y=Ne()==="win32"?"Windows live-PID detection is not yet supported \u2014 falling back to the rolling mtime window.":"No live `claude` processes detected \u2014 falling back to the rolling mtime window. Output may include sessions that are no longer open.";o.push(y),a=Qe(e,n)}else h.size===0?(o.push(`No active terminals open in ${s.name} (cwd=${s.decoded_path}). Open a Claude terminal in this repo and re-run.`),a=[]):a=f.filter(S=>h.has(S.session_id))}else a=Qe(e,n);a.length===0&&!o.length&&o.push(`No active sessions in ${s.name} within the last ${n}h.`);let c=Lr(e,s.name),l=new Set(c?c.edges.map(f=>f.session_id):[]),d=a.filter(f=>!l.has(f.session_id)),u=Ar(a);u.sort((f,h)=>f.started_at_ms-h.started_at_ms);let p=ms(u,i),g=c?c.edges.filter(f=>f.source!=="auto-active"&&(f.parent_session_id||f.role==="origin")).map(f=>({session_id:f.session_id,parent_session_id:f.parent_session_id})):[],_=c?c.name:`${fs}${s.name}${hs}${Es(t.todayIso)}`;return{project:s,thread:{id:c?.id??null,name:_,exists:!!c,existing_session_count:c?.edges.length??0},candidates:a,proposed_additions:d,proposed_edges:p,preserved_manual_edges:g,warnings:o}}function Ts(e){let t={thread_id:"",added:0,edges_set:0,preserved_manual:e.preserved_manual_edges.length},s;e.thread.exists&&e.thread.id?s=e.thread.id:s=Se({name:e.thread.name,summary:`Auto-captured by sync-active on ${Es()}. Members are sessions in ${e.project.name} that were active within the rolling window. Re-runnable: subsequent runs append new active sessions and never overwrite manual edges.`}).id,t.thread_id=s;let n=Or(s);for(let o of e.candidates)n.has(o.session_id)||(be({threadId:s,sessionId:o.session_id,source:"auto-active",confidence:.5}),t.added++,n.set(o.session_id,{parent_session_id:null,source:"auto-active"}));for(let o of e.proposed_edges){let a=n.get(o.child_id);if(a&&a.source==="auto-active"&&a.parent_session_id!==o.parent_id&&n.has(o.parent_id))try{ae(s,o.child_id,o.parent_id),t.edges_set++,n.set(o.child_id,{parent_session_id:o.parent_id,source:a.source})}catch{}}let r=m().prepare(`SELECT session_id FROM thread_edges
832
882
  WHERE thread_id = ?
833
883
  AND source = 'auto-active'
834
884
  AND parent_session_id IS NULL
835
- AND role = 'child'`).all(s);for(let o of r)try{ae(s,o.session_id,null)}catch{}return t}function C(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function Ir(e){return{content:[{type:"text",text:e}],isError:!0}}function D(e,t){return M.object(e).strict().parse(t)}var Cs=M.string().uuid(),W=Cs.describe("Thread id (UUID)."),q=Cs.describe("Session UUID (exact, not a prefix)."),Ss={include_archived:M.boolean().optional().describe("Include archived threads (default false).")},bs={id:W},Rs={session_id:q},ys={name:M.string().min(1).max(200).describe("Human-readable thread name."),summary:M.string().max(4e3).optional().describe("Optional short description."),origin_session_id:q.optional().describe("Seed the thread with this session as its origin.")},Ns={thread_id:W,session_id:q,parent_session_id:q.optional().describe("If present, the edge is role=child with this parent; otherwise role=origin."),role:M.enum(["origin","child"]).optional()},ws={thread_id:W,session_id:q,parent_session_id:q.nullable().describe("New parent (null to clear and promote the edge back to role=origin).")},Ls={thread_id:W,session_id:q},As={thread_id:W,name:M.string().min(1).max(200)},ne={thread_id:W},Os={source_id:W.describe("Thread to dissolve \u2014 its edges move into dest_id."),dest_id:W.describe("Thread that absorbs source_id.")},Is={thread_id:W,session_ids:M.array(q).min(1).max(500),new_thread_name:M.string().min(1).max(200)},xs={thread_id:W,force:M.boolean().optional().describe("When true, regenerate titles for sessions that already have an agent-sourced title. Default false skips them.")},vs={project_id:M.number().int().positive().describe("Numeric project id from list_projects. The sync is repo-scoped and never crosses projects."),mode:M.enum(["preflight","apply"]).describe("preflight returns the proposed plan without writing; apply writes the thread + edges and is idempotent."),window_hours:M.number().min(.5).max(168).optional().describe("Rolling activity window in hours. Default 6.")};function ks(e){e.registerTool("thread_list",{title:"List threads",description:"All threads (v0.15a intent groups), newest first. Excludes archived threads unless include_archived is true.",inputSchema:Ss},async t=>{try{let{include_archived:s}=D(Ss,t);return C(Ke({includeArchived:s??!1}))}catch(s){return y(s)}}),e.registerTool("thread_get",{title:"Get thread with edges",description:"Full thread detail including every session edge (origin + children).",inputSchema:bs},async t=>{try{let{id:s}=D(bs,t),n=k(s);return n?C(n):Ir(`thread not found: ${s}`)}catch(s){return y(s)}}),e.registerTool("thread_for_session",{title:"List threads containing a session",description:"Return non-archived threads that reference this session (as origin or child).",inputSchema:Rs},async t=>{try{let{session_id:s}=D(Rs,t);return C($t(s))}catch(s){return y(s)}})}function Ds(e,t={}){let s=t.limiter??new z;e.registerTool("thread_create",{title:"Create a thread",description:"Create a new thread. Optionally seed it with an origin session. Name is required; summary is optional.",inputSchema:ys},async n=>{try{let i=D(ys,n),r=await N({tool:"thread_create",args:i,limiter:s,run:()=>Se({name:i.name,summary:i.summary??null,originSessionId:i.origin_session_id})});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_add_session",{title:"Add session to thread",description:"Attach a session to a thread. If parent_session_id is provided the edge is role=child; otherwise role=origin. Upsert: re-adding updates the edge.",inputSchema:Ns},async n=>{try{let i=D(Ns,n),r=await N({tool:"thread_add_session",args:i,limiter:s,run:()=>be({threadId:i.thread_id,sessionId:i.session_id,parentSessionId:i.parent_session_id??null,role:i.role,source:"manual",confidence:1})});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_set_parent",{title:"Set edge parent within thread",description:"Change the parent session of an existing thread edge. Pass null to clear the parent and promote the edge to role=origin.",inputSchema:ws},async n=>{try{let i=D(ws,n),r=await N({tool:"thread_set_parent",args:i,limiter:s,run:()=>ae(i.thread_id,i.session_id,i.parent_session_id)});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_remove_session",{title:"Remove session from thread",description:"Detach a session from a thread. The session itself is untouched; only the edge is removed. The updated thread is re-mirrored to disk.",inputSchema:Ls},async n=>{try{let i=D(Ls,n),r=await N({tool:"thread_remove_session",args:i,limiter:s,run:()=>jt(i.thread_id,i.session_id)});return C({thread_id:i.thread_id,session_id:i.session_id,...r})}catch(i){return y(i)}}),e.registerTool("thread_rename",{title:"Rename thread",description:"Change the display name of a thread.",inputSchema:As},async n=>{try{let i=D(As,n),r=await N({tool:"thread_rename",args:i,limiter:s,run:()=>Xt(i.thread_id,i.name)});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_close",{title:"Close thread",description:"Mark the thread as closed (sets closed_at). Thread remains listed; reopen to clear.",inputSchema:ne},async n=>{try{let i=D(ne,n),r=await N({tool:"thread_close",args:i,limiter:s,run:()=>Ht(i.thread_id)});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_reopen",{title:"Reopen thread",description:"Clear closed_at on a closed thread.",inputSchema:ne},async n=>{try{let i=D(ne,n),r=await N({tool:"thread_reopen",args:i,limiter:s,run:()=>Bt(i.thread_id)});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_archive",{title:"Archive thread",description:"Soft-delete a thread by setting archived=1. Archived threads are hidden from thread_list by default but never hard-deleted; data is preserved in SQLite and the JSON mirror.",inputSchema:ne},async n=>{try{let i=D(ne,n),r=await N({tool:"thread_archive",args:i,limiter:s,run:()=>Wt(i.thread_id)});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_merge",{title:"Merge two threads",description:"Move every edge from source_id into dest_id, then delete source_id. Origin roles are preserved when present on either side.",inputSchema:Os},async n=>{try{let i=D(Os,n),r=await N({tool:"thread_merge",args:i,limiter:s,run:()=>Yt(i.source_id,i.dest_id)});return C(r)}catch(i){return y(i)}}),e.registerTool("thread_split",{title:"Split sessions into a new thread",description:"Peel the specified session_ids out of thread_id into a brand-new thread named new_thread_name. Non-member session_ids are silently skipped.",inputSchema:Is},async n=>{try{let i=D(Is,n),r=await N({tool:"thread_split",args:i,limiter:s,run:()=>Gt({threadId:i.thread_id,sessionIds:i.session_ids,newThreadName:i.new_thread_name})});return C(r)}catch(i){return y(i)}}),e.registerTool("sync_active_sessions",{title:"Sync currently active sessions in a repo into a thread",description:'Captures the Captain Code Pattern: scans every Claude session whose JSONL was touched within the rolling window in the given project, infers parent/child edges via the 7-signal scorer (temporal, continuation, file overlap, brand, semantic, cluster, doc-authorship), and stitches them into one thread. Idempotent: re-running reuses the existing "Active <project> sessions \u2014 *" thread, only appends new sessions, and never overwrites edges with source=manual. Use mode=preflight first to show the user the proposed plan, then mode=apply to write.',inputSchema:vs},async n=>{try{let i=D(vs,n);if(i.mode==="preflight"){let o=await et(i.project_id,{windowHours:i.window_hours});return C({plan:o})}let r=await N({tool:"sync_active_sessions",args:i,limiter:s,run:async()=>{let o=await et(i.project_id,{windowHours:i.window_hours}),a=Ts(o);return{plan:o,result:a}}});return C(r)}catch(i){return y(i)}}),e.registerTool("generate_thread_titles",{title:"Generate titles for every session in a thread",description:"Walk a thread DAG in topology order (origins first by added_at, then children breadth-first) and generate a coherent agent title for each session. Each successive title sees already-titled ancestors and earlier siblings as strong context so a naming pattern emerges. Set force=true to regenerate already-titled sessions; default false skips them. Returns the final {generated, skipped, failed} summary after the whole walk completes.",inputSchema:xs},async n=>{try{let i=D(xs,n),r=await N({tool:"generate_thread_titles",args:i,limiter:s,run:async()=>{let o=await ls(i.thread_id,{force:i.force??!1});if(o.failed.length>0&&o.generated.length===0&&o.skipped.length===0)throw new Error(`all ${o.failed.length} session(s) failed title generation`);return o}});return C(r)}catch(i){return y(i)}})}F();var xr="BAAI/bge-base-en-v1.5",vr=768,Fs=16,Cr="Represent this sentence for searching relevant passages: ",Ms=null,kr=!1;function de(){return{loaded:kr,modelId:xr,dim:vr}}async function Us(e){if(!Ms)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=[];for(let s=0;s<e.length;s+=Fs){let n=e.slice(s,s+Fs),r=(await Ms(n,{pooling:"cls",normalize:!0})).tolist();for(let o=0;o<n.length;o++){let a=r[o],c=Array.isArray(a[0])?a[0]:a;t.push(new Float32Array(c))}}return t}async function Ps(e){let t=Cr+e,[s]=await Us([t]);return s}L();F();import{writeFileSync as Dr}from"node:fs";import{join as Fr}from"node:path";var Mr=Fr(S,"recall-events.json");function $s(e,t,s,n="cli"){p().prepare(`
885
+ AND role = 'child'`).all(s);for(let o of r)try{ae(s,o.session_id,null)}catch{}return t}function C(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function Ir(e){return{content:[{type:"text",text:e}],isError:!0}}function D(e,t){return U.object(e).strict().parse(t)}var Cs=U.string().uuid(),W=Cs.describe("Thread id (UUID)."),q=Cs.describe("Session UUID (exact, not a prefix)."),Ss={include_archived:U.boolean().optional().describe("Include archived threads (default false).")},bs={id:W},Rs={session_id:q},ys={name:U.string().min(1).max(200).describe("Human-readable thread name."),summary:U.string().max(4e3).optional().describe("Optional short description."),origin_session_id:q.optional().describe("Seed the thread with this session as its origin.")},Ns={thread_id:W,session_id:q,parent_session_id:q.optional().describe("If present, the edge is role=child with this parent; otherwise role=origin."),role:U.enum(["origin","child"]).optional()},ws={thread_id:W,session_id:q,parent_session_id:q.nullable().describe("New parent (null to clear and promote the edge back to role=origin).")},Ls={thread_id:W,session_id:q},As={thread_id:W,name:U.string().min(1).max(200)},ne={thread_id:W},Os={source_id:W.describe("Thread to dissolve \u2014 its edges move into dest_id."),dest_id:W.describe("Thread that absorbs source_id.")},Is={thread_id:W,session_ids:U.array(q).min(1).max(500),new_thread_name:U.string().min(1).max(200)},xs={thread_id:W,force:U.boolean().optional().describe("When true, regenerate titles for sessions that already have an agent-sourced title. Default false skips them.")},vs={project_id:U.number().int().positive().describe("Numeric project id from list_projects. The sync is repo-scoped and never crosses projects."),mode:U.enum(["preflight","apply"]).describe("preflight returns the proposed plan without writing; apply writes the thread + edges and is idempotent."),window_hours:U.number().min(.5).max(168).optional().describe("Rolling activity window in hours. Default 6.")};function ks(e){e.registerTool("thread_list",{title:"List threads",description:"All threads (v0.15a intent groups), newest first. Excludes archived threads unless include_archived is true.",inputSchema:Ss},async t=>{try{let{include_archived:s}=D(Ss,t);return C(ze({includeArchived:s??!1}))}catch(s){return N(s)}}),e.registerTool("thread_get",{title:"Get thread with edges",description:"Full thread detail including every session edge (origin + children).",inputSchema:bs},async t=>{try{let{id:s}=D(bs,t),n=k(s);return n?C(n):Ir(`thread not found: ${s}`)}catch(s){return N(s)}}),e.registerTool("thread_for_session",{title:"List threads containing a session",description:"Return non-archived threads that reference this session (as origin or child).",inputSchema:Rs},async t=>{try{let{session_id:s}=D(Rs,t);return C($t(s))}catch(s){return N(s)}})}function Ds(e,t={}){let s=t.limiter??new K;e.registerTool("thread_create",{title:"Create a thread",description:"Create a new thread. Optionally seed it with an origin session. Name is required; summary is optional.",inputSchema:ys},async n=>{try{let i=D(ys,n),r=await w({tool:"thread_create",args:i,limiter:s,run:()=>Se({name:i.name,summary:i.summary??null,originSessionId:i.origin_session_id})});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_add_session",{title:"Add session to thread",description:"Attach a session to a thread. If parent_session_id is provided the edge is role=child; otherwise role=origin. Upsert: re-adding updates the edge.",inputSchema:Ns},async n=>{try{let i=D(Ns,n),r=await w({tool:"thread_add_session",args:i,limiter:s,run:()=>be({threadId:i.thread_id,sessionId:i.session_id,parentSessionId:i.parent_session_id??null,role:i.role,source:"manual",confidence:1})});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_set_parent",{title:"Set edge parent within thread",description:"Change the parent session of an existing thread edge. Pass null to clear the parent and promote the edge to role=origin.",inputSchema:ws},async n=>{try{let i=D(ws,n),r=await w({tool:"thread_set_parent",args:i,limiter:s,run:()=>ae(i.thread_id,i.session_id,i.parent_session_id)});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_remove_session",{title:"Remove session from thread",description:"Detach a session from a thread. The session itself is untouched; only the edge is removed. The updated thread is re-mirrored to disk.",inputSchema:Ls},async n=>{try{let i=D(Ls,n),r=await w({tool:"thread_remove_session",args:i,limiter:s,run:()=>jt(i.thread_id,i.session_id)});return C({thread_id:i.thread_id,session_id:i.session_id,...r})}catch(i){return N(i)}}),e.registerTool("thread_rename",{title:"Rename thread",description:"Change the display name of a thread.",inputSchema:As},async n=>{try{let i=D(As,n),r=await w({tool:"thread_rename",args:i,limiter:s,run:()=>Xt(i.thread_id,i.name)});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_close",{title:"Close thread",description:"Mark the thread as closed (sets closed_at). Thread remains listed; reopen to clear.",inputSchema:ne},async n=>{try{let i=D(ne,n),r=await w({tool:"thread_close",args:i,limiter:s,run:()=>Ht(i.thread_id)});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_reopen",{title:"Reopen thread",description:"Clear closed_at on a closed thread.",inputSchema:ne},async n=>{try{let i=D(ne,n),r=await w({tool:"thread_reopen",args:i,limiter:s,run:()=>Bt(i.thread_id)});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_archive",{title:"Archive thread",description:"Soft-delete a thread by setting archived=1. Archived threads are hidden from thread_list by default but never hard-deleted; data is preserved in SQLite and the JSON mirror.",inputSchema:ne},async n=>{try{let i=D(ne,n),r=await w({tool:"thread_archive",args:i,limiter:s,run:()=>Wt(i.thread_id)});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_merge",{title:"Merge two threads",description:"Move every edge from source_id into dest_id, then delete source_id. Origin roles are preserved when present on either side.",inputSchema:Os},async n=>{try{let i=D(Os,n),r=await w({tool:"thread_merge",args:i,limiter:s,run:()=>Gt(i.source_id,i.dest_id)});return C(r)}catch(i){return N(i)}}),e.registerTool("thread_split",{title:"Split sessions into a new thread",description:"Peel the specified session_ids out of thread_id into a brand-new thread named new_thread_name. Non-member session_ids are silently skipped.",inputSchema:Is},async n=>{try{let i=D(Is,n),r=await w({tool:"thread_split",args:i,limiter:s,run:()=>Yt({threadId:i.thread_id,sessionIds:i.session_ids,newThreadName:i.new_thread_name})});return C(r)}catch(i){return N(i)}}),e.registerTool("sync_active_sessions",{title:"Sync currently active sessions in a repo into a thread",description:'Captures the Captain Code Pattern: scans every Claude session whose JSONL was touched within the rolling window in the given project, infers parent/child edges via the 7-signal scorer (temporal, continuation, file overlap, brand, semantic, cluster, doc-authorship), and stitches them into one thread. Idempotent: re-running reuses the existing "Active <project> sessions \u2014 *" thread, only appends new sessions, and never overwrites edges with source=manual. Use mode=preflight first to show the user the proposed plan, then mode=apply to write.',inputSchema:vs},async n=>{try{let i=D(vs,n);if(i.mode==="preflight"){let o=await et(i.project_id,{windowHours:i.window_hours});return C({plan:o})}let r=await w({tool:"sync_active_sessions",args:i,limiter:s,run:async()=>{let o=await et(i.project_id,{windowHours:i.window_hours}),a=Ts(o);return{plan:o,result:a}}});return C(r)}catch(i){return N(i)}}),e.registerTool("generate_thread_titles",{title:"Generate titles for every session in a thread",description:"Walk a thread DAG in topology order (origins first by added_at, then children breadth-first) and generate a coherent agent title for each session. Each successive title sees already-titled ancestors and earlier siblings as strong context so a naming pattern emerges. Set force=true to regenerate already-titled sessions; default false skips them. Returns the final {generated, skipped, failed} summary after the whole walk completes.",inputSchema:xs},async n=>{try{let i=D(xs,n),r=await w({tool:"generate_thread_titles",args:i,limiter:s,run:async()=>{let o=await ls(i.thread_id,{force:i.force??!1});if(o.failed.length>0&&o.generated.length===0&&o.skipped.length===0)throw new Error(`all ${o.failed.length} session(s) failed title generation`);return o}});return C(r)}catch(i){return N(i)}})}M();var xr="BAAI/bge-base-en-v1.5",vr=768,Fs=16,Cr="Represent this sentence for searching relevant passages: ",Ms=null,kr=!1;function de(){return{loaded:kr,modelId:xr,dim:vr}}async function Us(e){if(!Ms)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=[];for(let s=0;s<e.length;s+=Fs){let n=e.slice(s,s+Fs),r=(await Ms(n,{pooling:"cls",normalize:!0})).tolist();for(let o=0;o<n.length;o++){let a=r[o],c=Array.isArray(a[0])?a[0]:a;t.push(new Float32Array(c))}}return t}async function Ps(e){let t=Cr+e,[s]=await Us([t]);return s}L();M();import{writeFileSync as Dr}from"node:fs";import{join as Fr}from"node:path";var Mr=Fr(b,"recall-events.json");function $s(e,t,s,n="cli"){m().prepare(`
836
886
  INSERT INTO recall_events (session_id, recalled_at, token_count, mode, caller)
837
887
  VALUES (?, datetime('now'), ?, ?, ?)
838
- `).run(e,t,s,n),Ur()}function Ur(){x();let t=p().prepare("SELECT id, session_id, recalled_at, token_count, mode, caller FROM recall_events ORDER BY recalled_at DESC").all();Dr(Mr,JSON.stringify(t,null,2)+`
839
- `,"utf-8")}L();async function js(e,t=50){let s=await Ps(e),n=p(),i=Buffer.from(s.buffer,s.byteOffset,s.byteLength);return n.prepare(`SELECT v.rowid, v.distance, cm.session_id, cm.text, cm.message_uuids
888
+ `).run(e,t,s,n),Ur()}function Ur(){x();let t=m().prepare("SELECT id, session_id, recalled_at, token_count, mode, caller FROM recall_events ORDER BY recalled_at DESC").all();Dr(Mr,JSON.stringify(t,null,2)+`
889
+ `,"utf-8")}L();async function js(e,t=50){let s=await Ps(e),n=m(),i=Buffer.from(s.buffer,s.byteOffset,s.byteLength);return n.prepare(`SELECT v.rowid, v.distance, cm.session_id, cm.text, cm.message_uuids
840
890
  FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
841
- WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(i,t).map(o=>({sessionId:o.session_id,chunkRowid:o.rowid,distance:o.distance,text:o.text,messageUuids:JSON.parse(o.message_uuids)}))}async function Xs(e,t=10,s=.65){let n=p(),i=n.prepare("SELECT rowid FROM chunk_meta WHERE session_id = ? ORDER BY rowid LIMIT 1").get(e);if(!i)return[];let r=n.prepare("SELECT embedding FROM vec_chunks WHERE rowid = ?").get(i.rowid);if(!r)return[];let o=n.prepare(`SELECT v.rowid, v.distance, cm.session_id FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
842
- WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(r.embedding,t*5),a=new Map;for(let l of o){if(l.session_id===e)continue;let d=a.get(l.session_id);(d===void 0||l.distance<d)&&a.set(l.session_id,l.distance)}let c=[];for(let[l,d]of a){let u=1-d;u>=s&&c.push({sessionId:l,similarity:u})}return c.sort((l,d)=>d.similarity-l.similarity),c.slice(0,t)}function Pr(){let e=process.env.RECALL_RRF_K;if(e){let t=parseInt(e,10);if(!isNaN(t)&&t>=1&&t<=1e3)return t}return 60}function Hs(e){let t=Pr(),s=new Map;for(let i of e)for(let r=0;r<i.length;r++){let o=i[r],a=1/(t+r+1),c=s.get(o.id);c?(c.score+=a,c.lanes.push(o.lane),r+1<c.bestRank&&(c.bestRank=r+1,c.bestData=o.data)):s.set(o.id,{score:a,lanes:[o.lane],bestRank:r+1,bestData:o.data})}let n=[];for(let[i,r]of s)n.push({id:i,score:r.score,lanes:r.lanes,data:r.bestData});return n.sort((i,r)=>r.score-i.score),n}L();L();var $r=!1,jr=null;function Xr(){return p().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}function Bs(){return{running:$r,queueDepth:Xr(),lastProcessedAt:jr}}import{existsSync as Hr,mkdirSync as gl,rmSync as _l,createWriteStream as fl,statSync as hl}from"node:fs";import{join as Ws}from"node:path";F();var Br=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function Wr(){return Ws(S,"models","BAAI","bge-base-en-v1.5")}function Ys(){let e=Wr();return Br.every(t=>Hr(Ws(e,t.path)))}L();L();F();import{join as tt}from"node:path";var Yr=new Set(["pending","approved","rejected"]),Gr=new Set(["L1","L2","L3","L4","user"]),Nl=tt(S,"links"),Kr=tt(S,"suggestions"),wl=tt(Kr,"index.json");function Gs(e){try{return JSON.parse(e)}catch{return e}}function zr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,source:e.source,evidence:Gs(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function Jr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,evidence:Gs(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function qr(e){if(!Gr.has(e))throw new Error(`invalid inferred_by: ${e}`)}function st(e){return p().prepare(`SELECT * FROM session_links
891
+ WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(i,t).map(o=>({sessionId:o.session_id,chunkRowid:o.rowid,distance:o.distance,text:o.text,messageUuids:JSON.parse(o.message_uuids)}))}async function Xs(e,t=10,s=.65){let n=m(),i=n.prepare("SELECT rowid FROM chunk_meta WHERE session_id = ? ORDER BY rowid LIMIT 1").get(e);if(!i)return[];let r=n.prepare("SELECT embedding FROM vec_chunks WHERE rowid = ?").get(i.rowid);if(!r)return[];let o=n.prepare(`SELECT v.rowid, v.distance, cm.session_id FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
892
+ WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(r.embedding,t*5),a=new Map;for(let l of o){if(l.session_id===e)continue;let d=a.get(l.session_id);(d===void 0||l.distance<d)&&a.set(l.session_id,l.distance)}let c=[];for(let[l,d]of a){let u=1-d;u>=s&&c.push({sessionId:l,similarity:u})}return c.sort((l,d)=>d.similarity-l.similarity),c.slice(0,t)}function Pr(){let e=process.env.RECALL_RRF_K;if(e){let t=parseInt(e,10);if(!isNaN(t)&&t>=1&&t<=1e3)return t}return 60}function Hs(e){let t=Pr(),s=new Map;for(let i of e)for(let r=0;r<i.length;r++){let o=i[r],a=1/(t+r+1),c=s.get(o.id);c?(c.score+=a,c.lanes.push(o.lane),r+1<c.bestRank&&(c.bestRank=r+1,c.bestData=o.data)):s.set(o.id,{score:a,lanes:[o.lane],bestRank:r+1,bestData:o.data})}let n=[];for(let[i,r]of s)n.push({id:i,score:r.score,lanes:r.lanes,data:r.bestData});return n.sort((i,r)=>r.score-i.score),n}L();L();var $r=!1,jr=null;function Xr(){return m().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}function Bs(){return{running:$r,queueDepth:Xr(),lastProcessedAt:jr}}import{existsSync as Hr,mkdirSync as gl,rmSync as _l,createWriteStream as fl,statSync as hl}from"node:fs";import{join as Ws}from"node:path";M();var Br=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function Wr(){return Ws(b,"models","BAAI","bge-base-en-v1.5")}function Gs(){let e=Wr();return Br.every(t=>Hr(Ws(e,t.path)))}L();L();M();import{join as tt}from"node:path";var Gr=new Set(["pending","approved","rejected"]),Yr=new Set(["L1","L2","L3","L4","user"]),Nl=tt(b,"links"),zr=tt(b,"suggestions"),wl=tt(zr,"index.json");function Ys(e){try{return JSON.parse(e)}catch{return e}}function Kr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,source:e.source,evidence:Ys(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function Jr(e){return{id:e.id,source_session_id:e.source_session_id,target_session_id:e.target_session_id,link_type:e.link_type,confidence:e.confidence,evidence:Ys(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function qr(e){if(!Yr.has(e))throw new Error(`invalid inferred_by: ${e}`)}function st(e){return m().prepare(`SELECT * FROM session_links
843
893
  WHERE source_session_id = ? OR target_session_id = ?
844
- ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(zr)}function nt(e={}){let t=p(),s=[],n=[];if(e.status){if(!Yr.has(e.status))throw new Error(`invalid status: ${e.status}`);s.push("status = ?"),n.push(e.status)}e.sourceSessionId&&(s.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(s.push("target_session_id = ?"),n.push(e.targetSessionId)),e.inferredBy&&(qr(e.inferredBy),s.push("inferred_by = ?"),n.push(e.inferredBy));let i=s.length?`WHERE ${s.join(" AND ")}`:"",r=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_link_suggestions ${i}
894
+ ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(Kr)}function nt(e={}){let t=m(),s=[],n=[];if(e.status){if(!Gr.has(e.status))throw new Error(`invalid status: ${e.status}`);s.push("status = ?"),n.push(e.status)}e.sourceSessionId&&(s.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(s.push("target_session_id = ?"),n.push(e.targetSessionId)),e.inferredBy&&(qr(e.inferredBy),s.push("inferred_by = ?"),n.push(e.inferredBy));let i=s.length?`WHERE ${s.join(" AND ")}`:"",r=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_link_suggestions ${i}
845
895
  ORDER BY confidence DESC, created_at DESC
846
- LIMIT ?`).all(...n,r).map(Jr)}var Vr=4e3,Qr=2,Zr=30,eo=.2,to={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function we(e){return e?Math.ceil(e.length/4):0}function Ks(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/Zr);return Math.max(eo,t)}function zs(e,t){if(!e||!t)return 0;let s=Date.parse(e),n=Date.parse(t);return!Number.isFinite(s)||!Number.isFinite(n)?0:Math.abs(n-s)/(1e3*60*60*24)}function Js(e){return p().prepare(`SELECT s.id,
896
+ LIMIT ?`).all(...n,r).map(Jr)}var Vr=4e3,Qr=2,Zr=30,eo=.2,to={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function we(e){return e?Math.ceil(e.length/4):0}function zs(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/Zr);return Math.max(eo,t)}function Ks(e,t){if(!e||!t)return 0;let s=Date.parse(e),n=Date.parse(t);return!Number.isFinite(s)||!Number.isFinite(n)?0:Math.abs(n-s)/(1e3*60*60*24)}function Js(e){return m().prepare(`SELECT s.id,
847
897
  NULLIF(sa.alias, '') AS alias,
848
898
  s.auto_title,
849
899
  s.auto_title_source,
@@ -854,25 +904,25 @@ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){
854
904
  FROM sessions s
855
905
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
856
906
  LEFT JOIN projects p ON p.id = s.project_id
857
- WHERE s.id = ?`).get(e)??null}function qs(e){let s=p().prepare("SELECT summary FROM session_semantic WHERE session_id = ?").get(e);if(!s||!s.summary)return null;let n=s.summary.trim();return n.length>0?n:null}function Vs(e){let t=e.alias?.trim(),s=e.auto_title?.trim(),n=e.first_user_message?.trim();return s&&e.auto_title_source==="agent"?s:t||s||(n?n.slice(0,80):e.id.slice(0,8))}function so(e){let s=p().prepare(`SELECT id, auto_title, started_at
907
+ WHERE s.id = ?`).get(e)??null}function qs(e){let s=m().prepare("SELECT summary FROM session_semantic WHERE session_id = ?").get(e);if(!s||!s.summary)return null;let n=s.summary.trim();return n.length>0?n:null}function Vs(e){let t=e.alias?.trim(),s=e.auto_title?.trim(),n=e.first_user_message?.trim();return s&&e.auto_title_source==="agent"?s:t||s||(n?n.slice(0,80):e.id.slice(0,8))}function so(e){let s=m().prepare(`SELECT id, auto_title, started_at
858
908
  FROM sessions
859
909
  WHERE project_id = ?
860
- ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,i=new Set,r=[];for(let m of s){if(!m.auto_title||!m.auto_title.startsWith("/")){r.push({id:m.id,brand:null,skill:null});continue}let g=m.auto_title.split(" \xB7 "),_=g[0].trim(),f=g.length>1?g.slice(1).join(" \xB7 ").trim():null;r.push({id:m.id,brand:f||null,skill:_||null}),f&&n.add(f),_&&i.add(_)}let o=[...n].sort(),a=new Map;o.forEach((m,g)=>a.set(m,g));let c=[...i].sort(),l=new Map;c.forEach((m,g)=>l.set(m,g));let d=new Map,u=new Map;for(let m of r){if(!m.brand||!m.skill)continue;let g=a.get(m.brand),_=l.get(m.skill);if(g===void 0||_===void 0)continue;let f=`${g}.${_}`,h=(d.get(f)??0)+1;d.set(f,h),u.set(m.id,`${g}.${_}.${h}`)}return{byId:u}}function no(e){return{table:e!==null?so(e):null,originProjectId:e,cache:new Map}}function Le(e,t){let s=e.cache.get(t);if(s)return s;let n=Js(t);if(!n)return null;let i=e.table&&n.project_id===e.originProjectId?e.table.byId.get(t)??null:null,r={session_id:n.id,title:Vs(n),decimal:i,summary:qs(n.id),project:n.project,started_at:n.started_at};return e.cache.set(t,r),r}function io(e,t){let n=p().prepare(`SELECT DISTINCT te.parent_session_id AS pid
910
+ ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,i=new Set,r=[];for(let p of s){if(!p.auto_title||!p.auto_title.startsWith("/")){r.push({id:p.id,brand:null,skill:null});continue}let g=p.auto_title.split(" \xB7 "),_=g[0].trim(),f=g.length>1?g.slice(1).join(" \xB7 ").trim():null;r.push({id:p.id,brand:f||null,skill:_||null}),f&&n.add(f),_&&i.add(_)}let o=[...n].sort(),a=new Map;o.forEach((p,g)=>a.set(p,g));let c=[...i].sort(),l=new Map;c.forEach((p,g)=>l.set(p,g));let d=new Map,u=new Map;for(let p of r){if(!p.brand||!p.skill)continue;let g=a.get(p.brand),_=l.get(p.skill);if(g===void 0||_===void 0)continue;let f=`${g}.${_}`,h=(d.get(f)??0)+1;d.set(f,h),u.set(p.id,`${g}.${_}.${h}`)}return{byId:u}}function no(e){return{table:e!==null?so(e):null,originProjectId:e,cache:new Map}}function Le(e,t){let s=e.cache.get(t);if(s)return s;let n=Js(t);if(!n)return null;let i=e.table&&n.project_id===e.originProjectId?e.table.byId.get(t)??null:null,r={session_id:n.id,title:Vs(n),decimal:i,summary:qs(n.id),project:n.project,started_at:n.started_at};return e.cache.set(t,r),r}function io(e,t){let n=m().prepare(`SELECT DISTINCT te.parent_session_id AS pid
861
911
  FROM thread_edges te
862
912
  WHERE te.session_id = ?
863
- AND te.parent_session_id IS NOT NULL`).all(t),i=[];for(let r of n){if(!r.pid)continue;let o=Le(e,r.pid);o&&i.push(o)}return i}function ro(e,t){let n=p().prepare(`SELECT DISTINCT te.session_id AS sid
913
+ AND te.parent_session_id IS NOT NULL`).all(t),i=[];for(let r of n){if(!r.pid)continue;let o=Le(e,r.pid);o&&i.push(o)}return i}function ro(e,t){let n=m().prepare(`SELECT DISTINCT te.session_id AS sid
864
914
  FROM thread_edges te
865
- WHERE te.parent_session_id = ?`).all(t),i=[];for(let r of n){if(!r.sid)continue;let o=Le(e,r.sid);o&&i.push(o)}return i}function Qs(e){let t=to[e.linkType]??.5,s=ie(e.confidence),n=t*s,i=Ks(e.daysApart),r=e.embeddingCosine??.5,o=ie(e.pagerank);if(e.scoring==="pagerank")return ie(o);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?ie(n):ie(r);let a=.35*n+.2*i+.2*r+.25*o;return ie(a)}function ie(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function oo(e,t,s,n,i){let r=new Map;function o(a,c){if(a===c)return;let l=r.get(a);l||(l=new Set,r.set(a,l)),l.add(c)}for(let a of t)o(a.source_session_id,a.target_session_id),o(a.target_session_id,a.source_session_id);for(let a of s)o(e,a.session_id);for(let a of s)o(a.session_id,e);for(let a of n)o(e,a.session_id);for(let a of n)o(a.session_id,e);if(i>1){let a=new Set([e]),c=new Set([e]);for(let l=1;l<i;l++){let d=new Set;for(let u of a){let m=r.get(u);if(m)for(let g of m){if(c.has(g))continue;let _=st(g).filter(f=>f.approved);for(let f of _)o(f.source_session_id,f.target_session_id),o(f.target_session_id,f.source_session_id);c.add(g),d.add(g)}}if(d.size===0)break;for(let u of d)a.add(u)}}return{edges:r}}function ao(e,t={}){let s=t.iterations??12,n=t.damping??.85,i=Array.from(e.edges.keys());if(i.length===0)return new Map;let r=1/i.length,o=new Map(i.map(l=>[l,r]));for(let l=0;l<s;l++){let d=new Map(i.map(u=>[u,(1-n)/i.length]));for(let u of i){let m=e.edges.get(u);if(!m||m.size===0)continue;let g=(o.get(u)??0)/m.size;for(let _ of m)d.set(_,(d.get(_)??0)+n*g)}o=d}let a=0;for(let l of o.values())l>a&&(a=l);if(a<=0)return o;let c=new Map;for(let[l,d]of o)c.set(l,d/a);return c}var Zs=240;function en(e,t){let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,t-1).trimEnd()}\u2026`}function co(e){let t=e.decimal?`${e.decimal} `:"",s=e.session_id.slice(0,8),n="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",i=`- ${t}${e.title} (${s})${n}`;if(e.summary){let r=en(e.summary,Zs);return`${i}
866
- ${r}`}return i}function lo(e,t,s){let n=[],i=[],r=0,o=e.decimal?`${e.decimal}: `:"",a=`# Neighborhood for ${e.session_id} (${o}${e.title})`;if(n.push(a),r+=we(a),e.summary){let c=en(e.summary,Zs*4);n.push(c),r+=we(c)}n.push("");for(let c of t){if(c.refs.length===0)continue;let l=`## ${c.heading}`,d=we(l),u=[],m=0;for(let g of c.refs){let _=co(g),f=we(_);if(r+d+m+f>s){i.push({session_id:g.session_id,title:g.title,decimal:g.decimal,summary:g.summary,project:g.project,started_at:g.started_at});continue}u.push(_),m+=f}if(u.length>0){n.push(l);for(let g of u)n.push(g);n.push(""),r+=d+m}}for(;n.length>0&&n[n.length-1]==="";)n.pop();return{bundle:n.join(`
915
+ WHERE te.parent_session_id = ?`).all(t),i=[];for(let r of n){if(!r.sid)continue;let o=Le(e,r.sid);o&&i.push(o)}return i}function Qs(e){let t=to[e.linkType]??.5,s=ie(e.confidence),n=t*s,i=zs(e.daysApart),r=e.embeddingCosine??.5,o=ie(e.pagerank);if(e.scoring==="pagerank")return ie(o);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?ie(n):ie(r);let a=.35*n+.2*i+.2*r+.25*o;return ie(a)}function ie(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function oo(e,t,s,n,i){let r=new Map;function o(a,c){if(a===c)return;let l=r.get(a);l||(l=new Set,r.set(a,l)),l.add(c)}for(let a of t)o(a.source_session_id,a.target_session_id),o(a.target_session_id,a.source_session_id);for(let a of s)o(e,a.session_id);for(let a of s)o(a.session_id,e);for(let a of n)o(e,a.session_id);for(let a of n)o(a.session_id,e);if(i>1){let a=new Set([e]),c=new Set([e]);for(let l=1;l<i;l++){let d=new Set;for(let u of a){let p=r.get(u);if(p)for(let g of p){if(c.has(g))continue;let _=st(g).filter(f=>f.approved);for(let f of _)o(f.source_session_id,f.target_session_id),o(f.target_session_id,f.source_session_id);c.add(g),d.add(g)}}if(d.size===0)break;for(let u of d)a.add(u)}}return{edges:r}}function ao(e,t={}){let s=t.iterations??12,n=t.damping??.85,i=Array.from(e.edges.keys());if(i.length===0)return new Map;let r=1/i.length,o=new Map(i.map(l=>[l,r]));for(let l=0;l<s;l++){let d=new Map(i.map(u=>[u,(1-n)/i.length]));for(let u of i){let p=e.edges.get(u);if(!p||p.size===0)continue;let g=(o.get(u)??0)/p.size;for(let _ of p)d.set(_,(d.get(_)??0)+n*g)}o=d}let a=0;for(let l of o.values())l>a&&(a=l);if(a<=0)return o;let c=new Map;for(let[l,d]of o)c.set(l,d/a);return c}var Zs=240;function en(e,t){let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,t-1).trimEnd()}\u2026`}function co(e){let t=e.decimal?`${e.decimal} `:"",s=e.session_id.slice(0,8),n="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",i=`- ${t}${e.title} (${s})${n}`;if(e.summary){let r=en(e.summary,Zs);return`${i}
916
+ ${r}`}return i}function lo(e,t,s){let n=[],i=[],r=0,o=e.decimal?`${e.decimal}: `:"",a=`# Neighborhood for ${e.session_id} (${o}${e.title})`;if(n.push(a),r+=we(a),e.summary){let c=en(e.summary,Zs*4);n.push(c),r+=we(c)}n.push("");for(let c of t){if(c.refs.length===0)continue;let l=`## ${c.heading}`,d=we(l),u=[],p=0;for(let g of c.refs){let _=co(g),f=we(_);if(r+d+p+f>s){i.push({session_id:g.session_id,title:g.title,decimal:g.decimal,summary:g.summary,project:g.project,started_at:g.started_at});continue}u.push(_),p+=f}if(u.length>0){n.push(l);for(let g of u)n.push(g);n.push(""),r+=d+p}}for(;n.length>0&&n[n.length-1]==="";)n.pop();return{bundle:n.join(`
867
917
  `)+`
868
- `,budgetUsed:r,truncated:i}}function uo(e,t,s,n,i,r){let o=[];for(let a of s){if(n&&!n.has(a.link_type))continue;let c=null;if(a.source_session_id===t.session_id?c=a.target_session_id:a.target_session_id===t.session_id&&(c=a.source_session_id),!c)continue;let l=Le(e,c);if(!l)continue;let d=zs(t.started_at,l.started_at),u=Qs({confidence:a.confidence,linkType:a.link_type,daysApart:d,embeddingCosine:null,pagerank:r.get(c)??0,scoring:i});o.push({...l,score:u,evidence:`(suggestion, ${a.inferred_by}) confidence=${a.confidence.toFixed(2)} ${Math.round(d)}d apart`,link_type:a.link_type})}return o}function tn(e,t={}){let s=Math.max(100,Math.floor(t.budget??Vr)),n=t.scoring??"hybrid",i=Math.max(1,Math.min(5,t.maxDepth??Qr)),r=t.includeWikiLinks??!0,o=t.includeSuggestions??!1,a=t.edgeTypes?new Set(t.edgeTypes):null,c=Js(e);if(!c)throw new Error(`session not found: ${e}`);let l=no(c.project_id),d={session_id:c.id,title:Vs(c),decimal:l.table?.byId.get(c.id)??null,summary:qs(c.id),project:c.project,started_at:c.started_at};l.cache.set(c.id,d);let u=io(l,e),m=ro(l,e),g=st(e).filter(A=>A.approved).filter(A=>!a||a.has(A.link_type)).filter(A=>r||A.link_type!=="wiki_link"),_=oo(e,g,u,m,i),f=ao(_),h=[],R=[],w=[],U=[];for(let A of g){let ee=A.source_session_id===e?A.target_session_id:A.source_session_id,re=Le(l,ee);if(!re)continue;let te=zs(d.started_at,re.started_at),Ae=Qs({confidence:A.confidence,linkType:A.link_type,daysApart:te,embeddingCosine:null,pagerank:f.get(ee)??0,scoring:n}),Oe=Ks(te),P=`${A.link_type} confidence=${A.confidence.toFixed(2)} recency=${Oe.toFixed(2)} (${Math.round(te)}d apart)`,me={...re,score:Ae,evidence:P,link_type:A.link_type};A.link_type==="citation"?h.push(me):A.link_type==="similar"?R.push(me):A.link_type==="wiki_link"?U.push(me):w.push(me)}if(o){let A=nt({sourceSessionId:e,status:"pending",limit:100}),ee=nt({targetSessionId:e,status:"pending",limit:100}),re=[...A,...ee],te=new Set,Ae=re.filter(P=>te.has(P.id)?!1:(te.add(P.id),!0)),Oe=uo(l,d,Ae,a,n,f);for(let P of Oe)P.link_type==="citation"?h.push(P):P.link_type==="similar"?R.push(P):P.link_type==="wiki_link"?U.push(P):w.push(P)}let b=(A,ee)=>ee.score-A.score;h.sort(b),R.sort(b),w.sort(b),U.sort(b);let pe=lo(d,[{heading:"Parents",refs:u},{heading:"Children",refs:m},{heading:"Citations (approved)",refs:h},{heading:"Similar sessions",refs:R},{heading:"Cousins (skill track + temporal)",refs:w},{heading:"Wiki links (manual)",refs:U}],s);return{origin:d,parents:u,children:m,citations:h,similar:R,cousins:w,wikiLinks:U,bundle:pe.bundle,budgetUsed:pe.budgetUsed,budgetRemaining:Math.max(0,s-pe.budgetUsed),truncated:pe.truncated}}import{readFileSync as vo}from"node:fs";import{dirname as Co,join as ko}from"node:path";var Do=(()=>{try{let e=Co(xo(import.meta.url));return ko(e,"..","..","package.json")}catch{return"package.json"}})(),Fo=(()=>{try{return JSON.parse(vo(Do,"utf8")).version??"0.0.0"}catch{return"0.0.0"}})();function Mo(){let e=process.env.RECALL_MCP_ALLOW_WRITES;return e==="1"||e==="true"}function v(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function ot(e){return{content:[{type:"text",text:e}]}}function H(e){return{content:[{type:"text",text:e}],isError:!0}}function Uo(){let e=new Oo({name:"claude-recall",version:Fo}),t=Mo();if(e.registerTool("list_projects",{title:"List projects",description:"Every Claude Code project currently indexed by Recall, with session and message counts and the most recent activity timestamp.",inputSchema:{}},async()=>{let n=p().prepare(`SELECT p.name,
918
+ `,budgetUsed:r,truncated:i}}function uo(e,t,s,n,i,r){let o=[];for(let a of s){if(n&&!n.has(a.link_type))continue;let c=null;if(a.source_session_id===t.session_id?c=a.target_session_id:a.target_session_id===t.session_id&&(c=a.source_session_id),!c)continue;let l=Le(e,c);if(!l)continue;let d=Ks(t.started_at,l.started_at),u=Qs({confidence:a.confidence,linkType:a.link_type,daysApart:d,embeddingCosine:null,pagerank:r.get(c)??0,scoring:i});o.push({...l,score:u,evidence:`(suggestion, ${a.inferred_by}) confidence=${a.confidence.toFixed(2)} ${Math.round(d)}d apart`,link_type:a.link_type})}return o}function tn(e,t={}){let s=Math.max(100,Math.floor(t.budget??Vr)),n=t.scoring??"hybrid",i=Math.max(1,Math.min(5,t.maxDepth??Qr)),r=t.includeWikiLinks??!0,o=t.includeSuggestions??!1,a=t.edgeTypes?new Set(t.edgeTypes):null,c=Js(e);if(!c)throw new Error(`session not found: ${e}`);let l=no(c.project_id),d={session_id:c.id,title:Vs(c),decimal:l.table?.byId.get(c.id)??null,summary:qs(c.id),project:c.project,started_at:c.started_at};l.cache.set(c.id,d);let u=io(l,e),p=ro(l,e),g=st(e).filter(A=>A.approved).filter(A=>!a||a.has(A.link_type)).filter(A=>r||A.link_type!=="wiki_link"),_=oo(e,g,u,p,i),f=ao(_),h=[],S=[],y=[],F=[];for(let A of g){let ee=A.source_session_id===e?A.target_session_id:A.source_session_id,re=Le(l,ee);if(!re)continue;let te=Ks(d.started_at,re.started_at),Ae=Qs({confidence:A.confidence,linkType:A.link_type,daysApart:te,embeddingCosine:null,pagerank:f.get(ee)??0,scoring:n}),Oe=zs(te),P=`${A.link_type} confidence=${A.confidence.toFixed(2)} recency=${Oe.toFixed(2)} (${Math.round(te)}d apart)`,me={...re,score:Ae,evidence:P,link_type:A.link_type};A.link_type==="citation"?h.push(me):A.link_type==="similar"?S.push(me):A.link_type==="wiki_link"?F.push(me):y.push(me)}if(o){let A=nt({sourceSessionId:e,status:"pending",limit:100}),ee=nt({targetSessionId:e,status:"pending",limit:100}),re=[...A,...ee],te=new Set,Ae=re.filter(P=>te.has(P.id)?!1:(te.add(P.id),!0)),Oe=uo(l,d,Ae,a,n,f);for(let P of Oe)P.link_type==="citation"?h.push(P):P.link_type==="similar"?S.push(P):P.link_type==="wiki_link"?F.push(P):y.push(P)}let R=(A,ee)=>ee.score-A.score;h.sort(R),S.sort(R),y.sort(R),F.sort(R);let pe=lo(d,[{heading:"Parents",refs:u},{heading:"Children",refs:p},{heading:"Citations (approved)",refs:h},{heading:"Similar sessions",refs:S},{heading:"Cousins (skill track + temporal)",refs:y},{heading:"Wiki links (manual)",refs:F}],s);return{origin:d,parents:u,children:p,citations:h,similar:S,cousins:y,wikiLinks:F,bundle:pe.bundle,budgetUsed:pe.budgetUsed,budgetRemaining:Math.max(0,s-pe.budgetUsed),truncated:pe.truncated}}import{readFileSync as vo}from"node:fs";import{dirname as Co,join as ko}from"node:path";var Do=(()=>{try{let e=Co(xo(import.meta.url));return ko(e,"..","..","package.json")}catch{return"package.json"}})(),Fo=(()=>{try{return JSON.parse(vo(Do,"utf8")).version??"0.0.0"}catch{return"0.0.0"}})();function Mo(){let e=process.env.RECALL_MCP_ALLOW_WRITES;return e==="1"||e==="true"}function v(e){return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}function ot(e){return{content:[{type:"text",text:e}]}}function H(e){return{content:[{type:"text",text:e}],isError:!0}}function Uo(){let e=new Oo({name:"claude-recall",version:Fo}),t=Mo();if(e.registerTool("list_projects",{title:"List projects",description:"Every Claude Code project currently indexed by Recall, with session and message counts and the most recent activity timestamp.",inputSchema:{}},async()=>{let n=m().prepare(`SELECT p.name,
869
919
  COUNT(s.id) AS session_count,
870
920
  COALESCE(SUM(s.message_count), 0) AS message_count,
871
921
  MAX(s.started_at) AS latest
872
922
  FROM projects p
873
923
  LEFT JOIN sessions s ON s.project_id = p.id
874
924
  GROUP BY p.id
875
- ORDER BY MAX(COALESCE(s.started_at, '')) DESC`).all();return v(n)}),e.registerTool("list_sessions",{title:"List sessions",description:"Recent sessions with alias, tags, and message counts. Optional filters for project, tag, and date range.",inputSchema:{project:T.string().optional().describe("Substring match against project name or decoded filesystem path."),tag:T.string().optional().describe("Only sessions carrying this tag (leading # optional)."),since:T.string().optional().describe("Only sessions started at or after this ISO timestamp or YYYY-MM-DD date."),until:T.string().optional().describe("Only sessions started at or before this ISO timestamp or YYYY-MM-DD date."),limit:T.number().int().min(1).max(500).optional()}},async({project:s,tag:n,since:i,until:r,limit:o})=>{let a=p(),c={limit:o??100},l="s.message_count > 2";if(s&&(l+=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj)",c.proj=`%${s}%`),i&&(l+=" AND s.started_at >= @since",c.since=i),r&&(l+=" AND s.started_at <= @until",c.until=/^\d{4}-\d{2}-\d{2}$/.test(r)?`${r}T23:59:59.999Z`:r),n){let m=V(n);m&&(l+=" AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag)",c.tag=m)}let u=a.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
925
+ ORDER BY MAX(COALESCE(s.started_at, '')) DESC`).all();return v(n)}),e.registerTool("list_sessions",{title:"List sessions",description:"Recent sessions with alias, tags, and message counts. Optional filters for project, tag, and date range.",inputSchema:{project:T.string().optional().describe("Substring match against project name or decoded filesystem path."),tag:T.string().optional().describe("Only sessions carrying this tag (leading # optional)."),since:T.string().optional().describe("Only sessions started at or after this ISO timestamp or YYYY-MM-DD date."),until:T.string().optional().describe("Only sessions started at or before this ISO timestamp or YYYY-MM-DD date."),limit:T.number().int().min(1).max(500).optional()}},async({project:s,tag:n,since:i,until:r,limit:o})=>{let a=m(),c={limit:o??100},l="s.message_count > 2";if(s&&(l+=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj)",c.proj=`%${s}%`),i&&(l+=" AND s.started_at >= @since",c.since=i),r&&(l+=" AND s.started_at <= @until",c.until=/^\d{4}-\d{2}-\d{2}$/.test(r)?`${r}T23:59:59.999Z`:r),n){let p=V(n);p&&(l+=" AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag)",c.tag=p)}let u=a.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
876
926
  s.message_count, s.first_user_message, s.git_branch,
877
927
  NULLIF(sa.alias, '') AS alias,
878
928
  CASE WHEN sn.content IS NOT NULL AND sn.content != '' THEN 1 ELSE 0 END AS has_notes,
@@ -887,7 +937,7 @@ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){
887
937
  LEFT JOIN session_notes sn ON sn.session_id = s.id
888
938
  WHERE ${l}
889
939
  ORDER BY COALESCE(s.started_at, '') DESC
890
- LIMIT @limit`).all(c).map(({tags_csv:m,...g})=>({...g,tags:m?m.split(","):[]}));return v(u)}),e.registerTool("list_tags",{title:"List tags",description:"Every tag currently applied to a session, with its count, most popular first.",inputSchema:{}},async()=>v(ht())),e.registerTool("search",{title:"Search messages",description:"Full-text search over every message in every indexed session. Use `#tag-name` tokens inside the query string to also filter by tag; plain terms are ANDed together.",inputSchema:{query:T.string().describe("Text to find. Supports inline `#tag-name` tokens to narrow to sessions with the tag."),project:T.string().optional().describe("Substring match against project name or path."),limit:T.number().int().min(1).max(200).optional()}},async({query:s,project:n,limit:i})=>{let r=p(),o=s.trim();if(!o)return v({query:"",hits:[],tags:[]});let a=o.split(/\s+/).filter(Boolean),c=a.filter(f=>f.startsWith("#")).map(V).filter(f=>!!f),d=a.filter(f=>!f.startsWith("#")).map(f=>`"${f.replace(/"/g,"")}"`).join(" "),u=Math.max(1,Math.min(200,i??30));if(!d&&c.length>0){let f=`
940
+ LIMIT @limit`).all(c).map(({tags_csv:p,...g})=>({...g,tags:p?p.split(","):[]}));return v(u)}),e.registerTool("list_tags",{title:"List tags",description:"Every tag currently applied to a session, with its count, most popular first.",inputSchema:{}},async()=>v(ht())),e.registerTool("search",{title:"Search messages",description:"Full-text search over every message in every indexed session. Use `#tag-name` tokens inside the query string to also filter by tag; plain terms are ANDed together.",inputSchema:{query:T.string().describe("Text to find. Supports inline `#tag-name` tokens to narrow to sessions with the tag."),project:T.string().optional().describe("Substring match against project name or path."),limit:T.number().int().min(1).max(200).optional()}},async({query:s,project:n,limit:i})=>{let r=m(),o=s.trim();if(!o)return v({query:"",hits:[],tags:[]});let a=o.split(/\s+/).filter(Boolean),c=a.filter(f=>f.startsWith("#")).map(V).filter(f=>!!f),d=a.filter(f=>!f.startsWith("#")).map(f=>`"${f.replace(/"/g,"")}"`).join(" "),u=Math.max(1,Math.min(200,i??30));if(!d&&c.length>0){let f=`
891
941
  SELECT s.id AS session_id,
892
942
  s.id AS message_uuid,
893
943
  p.name AS project,
@@ -900,7 +950,7 @@ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){
900
950
  JOIN projects p ON p.id = s.project_id
901
951
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
902
952
  WHERE 1=1
903
- `,h={limit:u};return n&&(f+=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj)",h.proj=`%${n}%`),c.forEach((R,w)=>{f+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${w})`,h[`tag_${w}`]=R}),f+=" ORDER BY COALESCE(s.started_at, '') DESC LIMIT @limit",v({query:o,hits:r.prepare(f).all(h),tags:c})}if(!d)return v({query:o,hits:[],tags:c});let m=`
953
+ `,h={limit:u};return n&&(f+=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj)",h.proj=`%${n}%`),c.forEach((S,y)=>{f+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${y})`,h[`tag_${y}`]=S}),f+=" ORDER BY COALESCE(s.started_at, '') DESC LIMIT @limit",v({query:o,hits:r.prepare(f).all(h),tags:c})}if(!d)return v({query:o,hits:[],tags:c});let p=`
904
954
  SELECT m.session_id AS session_id,
905
955
  m.uuid AS message_uuid,
906
956
  p.name AS project,
@@ -915,7 +965,7 @@ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){
915
965
  JOIN projects p ON p.id = s.project_id
916
966
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
917
967
  WHERE messages_fts MATCH @fts
918
- `,g={fts:d,limit:u};n&&(m+=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj)",g.proj=`%${n}%`),c.forEach((f,h)=>{m+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${h})`,g[`tag_${h}`]=f}),m+=" ORDER BY bm25(messages_fts) LIMIT @limit";let _=r.prepare(m).all(g);if(de().loaded)try{let f=await js(o,u),h=_.map(b=>({id:String(b.session_id),data:b,lane:"bm25"})),R=f.map(b=>({id:b.sessionId,data:{session_id:b.sessionId,snippet:b.text,matched_via:"vector"},lane:"vector"})),U=Hs([h,R]).slice(0,u).map(b=>({...b.data,session_id:b.id,rrf_score:b.score,lanes:b.lanes}));return v({query:o,hits:U,tags:c,fusion:"rrf"})}catch{}return v({query:o,hits:_,tags:c})}),e.registerTool("find_similar_sessions",{title:"Find similar sessions",description:"Find sessions semantically similar to a given session using vector embeddings (Pro-only). Returns related sessions ranked by cosine similarity.",inputSchema:{session_id:T.string().uuid().describe("Session UUID to find similar sessions for."),limit:T.number().int().min(1).max(50).optional().describe("Max results (default 10)."),min_cosine:T.number().min(0).max(1).optional().describe("Minimum cosine similarity threshold (default 0.65).")}},async({session_id:s,limit:n,min_cosine:i})=>{if(!de().loaded)return v({upgrade_required:!0,reason:"Semantic vector search requires Pro with the embedding model installed.",buy_url:"https://clauderecall.com/pro"});try{let r=await Xs(s,n??10,i??.65);return v({session_id:s,similar:r})}catch(r){return H(r instanceof Error?r.message:"vector search failed")}}),e.registerTool("semantic_status",{title:"Semantic search status",description:"Health snapshot of the semantic vector search tier: model status, worker status, queue depth.",inputSchema:{}},async()=>{let s=de(),n=Bs(),i=Ys();return v({embedder:s,worker:n,modelInstalled:i})}),e.registerTool("get_session",{title:"Get session transcript",description:"Return the full metadata and ordered messages for a session. Accepts a full UUID or an 8+-character id prefix.",inputSchema:{id:T.string().describe("Session id (full UUID or 8+-character prefix).")}},async({id:s})=>{let n=X(s);if(!n)return H(`session not found or prefix ambiguous: ${s}`);let i=p(),r=i.prepare(`SELECT s.id, s.project_id, s.started_at, s.ended_at,
968
+ `,g={fts:d,limit:u};n&&(p+=" AND (p.name LIKE @proj OR p.decoded_path LIKE @proj)",g.proj=`%${n}%`),c.forEach((f,h)=>{p+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${h})`,g[`tag_${h}`]=f}),p+=" ORDER BY bm25(messages_fts) LIMIT @limit";let _=r.prepare(p).all(g);if(de().loaded)try{let f=await js(o,u),h=_.map(R=>({id:String(R.session_id),data:R,lane:"bm25"})),S=f.map(R=>({id:R.sessionId,data:{session_id:R.sessionId,snippet:R.text,matched_via:"vector"},lane:"vector"})),F=Hs([h,S]).slice(0,u).map(R=>({...R.data,session_id:R.id,rrf_score:R.score,lanes:R.lanes}));return v({query:o,hits:F,tags:c,fusion:"rrf"})}catch{}return v({query:o,hits:_,tags:c})}),e.registerTool("find_similar_sessions",{title:"Find similar sessions",description:"Find sessions semantically similar to a given session using vector embeddings (Pro-only). Returns related sessions ranked by cosine similarity.",inputSchema:{session_id:T.string().uuid().describe("Session UUID to find similar sessions for."),limit:T.number().int().min(1).max(50).optional().describe("Max results (default 10)."),min_cosine:T.number().min(0).max(1).optional().describe("Minimum cosine similarity threshold (default 0.65).")}},async({session_id:s,limit:n,min_cosine:i})=>{if(!de().loaded)return v({upgrade_required:!0,reason:"Semantic vector search requires Pro with the embedding model installed.",buy_url:"https://clauderecall.com/pro"});try{let r=await Xs(s,n??10,i??.65);return v({session_id:s,similar:r})}catch(r){return H(r instanceof Error?r.message:"vector search failed")}}),e.registerTool("semantic_status",{title:"Semantic search status",description:"Health snapshot of the semantic vector search tier: model status, worker status, queue depth.",inputSchema:{}},async()=>{let s=de(),n=Bs(),i=Gs();return v({embedder:s,worker:n,modelInstalled:i})}),e.registerTool("get_session",{title:"Get session transcript",description:"Return the full metadata and ordered messages for a session. Accepts a full UUID or an 8+-character id prefix.",inputSchema:{id:T.string().describe("Session id (full UUID or 8+-character prefix).")}},async({id:s})=>{let n=X(s);if(!n)return H(`session not found or prefix ambiguous: ${s}`);let i=m(),r=i.prepare(`SELECT s.id, s.project_id, s.started_at, s.ended_at,
919
969
  s.message_count, s.user_message_count, s.assistant_message_count,
920
970
  s.first_user_message, s.git_branch, s.version, s.indexed_at,
921
971
  p.name AS project_name,
@@ -925,9 +975,9 @@ ${s}`}function Zi(e){return es(e)?.auto_title_source??null}async function er(e){
925
975
  LEFT JOIN session_aliases sa ON sa.session_id = s.id
926
976
  WHERE s.id = ?`).get(n);if(!r)return H(`session metadata missing for ${n}`);let o=_e(n),a=i.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
927
977
  FROM messages WHERE session_id = ?
928
- ORDER BY COALESCE(timestamp, ''), rowid`).all(n);return v({session:{...r,tags:o},messages:a})}),e.registerTool("context_for_session",{title:"Export session as context",description:"Render a past session as markdown ready to paste into a fresh Claude conversation. This is the flagship Recall operation: pipe any previous session back into a new chat as memory.",inputSchema:{id:T.string().describe("Session id (full UUID or 8+-character prefix)."),mode:T.enum(["condensed","full"]).optional().describe("`condensed` (default) strips tool-call JSON; `full` keeps everything."),includeSidechain:T.boolean().optional().describe("Include subagent / sidechain messages (default false)."),prelude:T.string().max(1e4).optional().describe("Optional header prepended above the transcript (max 10 000 chars)."),since:T.string().optional().describe("Only messages at or after this ISO timestamp.")}},async({id:s,mode:n,includeSidechain:i,prelude:r,since:o})=>{let a=X(s);if(!a)return H(`session not found or prefix ambiguous: ${s}`);let c=p(),l=c.prepare(`SELECT s.id, p.name AS project_name,
978
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(n);return v({session:{...r,tags:o},messages:a})}),e.registerTool("context_for_session",{title:"Export session as context",description:"Render a past session as markdown ready to paste into a fresh Claude conversation. This is the flagship Recall operation: pipe any previous session back into a new chat as memory.",inputSchema:{id:T.string().describe("Session id (full UUID or 8+-character prefix)."),mode:T.enum(["condensed","full"]).optional().describe("`condensed` (default) strips tool-call JSON; `full` keeps everything."),includeSidechain:T.boolean().optional().describe("Include subagent / sidechain messages (default false)."),prelude:T.string().max(1e4).optional().describe("Optional header prepended above the transcript (max 10 000 chars)."),since:T.string().optional().describe("Only messages at or after this ISO timestamp.")}},async({id:s,mode:n,includeSidechain:i,prelude:r,since:o})=>{let a=X(s);if(!a)return H(`session not found or prefix ambiguous: ${s}`);let c=m(),l=c.prepare(`SELECT s.id, p.name AS project_name,
929
979
  s.started_at, s.ended_at, s.message_count, s.git_branch
930
980
  FROM sessions s JOIN projects p ON p.id = s.project_id
931
981
  WHERE s.id = ?`).get(a);if(!l)return H(`session metadata missing for ${a}`);let d=c.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
932
982
  FROM messages WHERE session_id = ?
933
- ORDER BY COALESCE(timestamp, ''), rowid`).all(a),u=_t(l,d,{mode:n??"condensed",includeSidechain:i??!1,prelude:r??null,since:o??null}),m=o?"since":n??"condensed";return $s(a,Math.ceil(u.length/4),m,"mcp"),ot(u)}),e.registerTool("recall_neighborhood",{title:"Recall: neighborhood context bundle",description:"Build a ranked, budget-bounded markdown bundle for a session: parents + children from thread_edges, plus approved citations / similar / cousins / wiki_links from the cognitive graph. Pipe-friendly output ready to seed a fresh conversation. Reads only approved edges by default \u2014 pending suggestions are opt-in.",inputSchema:{session_id:T.string().describe("Session UUID or 8+-character prefix."),budget:T.number().int().min(100).max(5e4).optional().describe("Token budget for the assembled bundle. Default 4000."),scoring:T.enum(["pagerank","embedding-rerank","hybrid"]).optional().describe("Scoring mode (default hybrid)."),edge_types:T.array(T.enum(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"])).optional().describe("Restrict to certain link types. Default: all approved types."),max_depth:T.number().int().min(1).max(5).optional().describe("Pagerank traversal depth on the local subgraph (default 2)."),include_wiki_links:T.boolean().optional().describe("Include manual wiki_link rows. Default true."),include_suggestions:T.boolean().optional().describe("Surface pending session_link_suggestions. Debug only; default false."),format:T.enum(["markdown","json"]).optional().describe("markdown (default) returns the bundle as text; json returns the full NeighborhoodResult.")}},async({session_id:s,budget:n,scoring:i,edge_types:r,max_depth:o,include_wiki_links:a,include_suggestions:c,format:l})=>{let d=X(s);if(!d)return H(`session not found or prefix ambiguous: ${s}`);try{let u=tn(d,{budget:n,scoring:i,edgeTypes:r,maxDepth:o,includeWikiLinks:a,includeSuggestions:c});return l==="json"?v(u):ot(u.bundle)}catch(u){return H(u instanceof Error?u.message:String(u))}}),e.registerTool("doctor",{title:"Health check",description:"Read-only diagnostic snapshot of the local Claude Recall database: total size, WAL size, free pages, FTS5 fragmentation for messages and sessions, vector index row count, free disk space, integrity check, and row counts per surface table. Returns structured JSON. Equivalent to running `recall doctor --json` from the shell. Safe to call as often as needed; no side effects.",inputSchema:{}},async()=>{let{buildHealthReport:s}=await Promise.resolve().then(()=>(_n(),gn));return v(s())}),ks(e),t){let s=Number(process.env.RECALL_MCP_RATE_LIMIT),n=Number.isFinite(s)&&s>0?s:Be,i=p(),r=new Date(Date.now()-6e4).toISOString(),o=i.prepare("SELECT at FROM mcp_audit_events WHERE at >= ? ORDER BY at ASC").all(r),a=new z(n);for(let c of o){let l=new Date(c.at).getTime();if(Number.isFinite(l))try{a.consume(l)}catch{break}}e.registerTool("list_sessions_to_tag",{title:"List sessions to tag",description:"Return session summaries and sampled messages formatted for an agent to propose tags. Respects the same scope filters used by the Recall UI scan. Only returns data if auto-tagging is enabled in config.",inputSchema:{untaggedOnly:T.boolean().optional().describe("Only sessions with zero tags (default false)."),project:T.string().optional().describe("Exact project name match."),collectionId:T.string().optional().describe("Only sessions in this collection."),limit:T.number().int().min(1).max(200).optional()}},async c=>{let l=Fe();if(!l.enabled)return H("auto-tagging is disabled; enable it in Recall settings before scanning");let d=he({untaggedOnly:c.untaggedOnly,project:c.project,collectionId:c.collectionId,limit:c.limit??50});return v({count:d.length,sessions:d,guidance:`Produce ${l.minTagsPerSession}-${l.maxTagsPerSession} tags per session. Prefer existing tags from list_tags for consistency. Use apply_tags to write results.`})}),e.registerTool("apply_tags",{title:"Apply tags to a session",description:"Add one or more tags to a session (merge-mode, never removes existing). Only works when auto-tagging is enabled. Accepts full UUID or 8+-character id prefix.",inputSchema:{sessionId:T.string().describe("Full session UUID or 8+-character prefix."),tags:T.array(T.string()).min(1).max(10).describe("Tags to add. Normalized server-side (lowercase, hyphens, strip #).")}},async({sessionId:c,tags:l})=>{if(!Fe().enabled)return H("auto-tagging is disabled; enable it in Recall settings before writing tags");let u=X(c);if(!u)return H(`session not found or prefix ambiguous: ${c}`);try{let m=await N({tool:"apply_tags",args:{sessionId:u,tags:l},limiter:a,run:()=>{let g=[],_=[];for(let f of l)try{let{tag:h,added:R}=ge(u,f);R?g.push(h):_.push({tag:h,reason:"already present"})}catch(h){_.push({tag:f,reason:h instanceof Error?h.message:String(h)})}return{sessionId:u,applied:g,skipped:_}}});return v(m)}catch(m){return m instanceof Error&&m.message.startsWith("SQLITE_")?H("database constraint error"):H(m instanceof Error?m.message:String(m))}}),e.registerTool("optimize",{title:"Optimize the database",description:"Run the local maintenance pass: WAL checkpoint (TRUNCATE), FTS5 segment merge for messages and sessions, refresh planner stats. Equivalent to `recall optimize` from the shell. Safe while the daemon is running. Set vacuum=true to also reclaim free pages \u2014 but VACUUM rewrites the entire DB and requires the daemon to be stopped, so it errors out if the daemon is up. Write-mode only.",inputSchema:{vacuum:T.boolean().optional().describe("Also run VACUUM. Requires the daemon to be stopped.")}},async({vacuum:c})=>{let{runOptimize:l}=await Promise.resolve().then(()=>(hn(),fn)),d=process.stdout.write.bind(process.stdout),u="";process.stdout.write=m=>(u+=typeof m=="string"?m:Buffer.from(m).toString("utf-8"),!0);try{await l({vacuum:!!c,json:!0})}finally{process.stdout.write=d}try{return v(JSON.parse(u.trim()))}catch{return ot(u.trim())}}),vt(e,{limiter:a}),Ds(e,{limiter:a}),console.error(`[claude-recall-mcp] MCP writes ENABLED \u2014 write tools registered (rate limit ${n}/min)`),console.error("[claude-recall-mcp] MCP thread writes ENABLED")}else console.error("[claude-recall-mcp] MCP writes DISABLED (read-only) \u2014 pass --allow-writes to enable"),console.error("[claude-recall-mcp] MCP thread writes DISABLED (read-only)");for(let s of St)e.registerPrompt(s.name,{title:s.title,description:s.description,argsSchema:s.argsSchema},async n=>({messages:[{role:"user",content:{type:"text",text:s.build(n)}}]}));return e}async function Po(){let e=Uo(),t=new Io;await e.connect(t);let s=async()=>{try{await e.close()}catch{}gt(),process.exit(0)};process.on("SIGINT",s),process.on("SIGTERM",s)}var $o=(()=>{try{let e=process.argv[1];return e?import.meta.url===new URL(`file://${e}`).href:!1}catch{return!1}})();$o&&Po().catch(e=>{console.error("[claude-recall-mcp] fatal:",e),process.exit(1)});export{Uo as buildServer};
983
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(a),u=_t(l,d,{mode:n??"condensed",includeSidechain:i??!1,prelude:r??null,since:o??null}),p=o?"since":n??"condensed";return $s(a,Math.ceil(u.length/4),p,"mcp"),ot(u)}),e.registerTool("recall_neighborhood",{title:"Recall: neighborhood context bundle",description:"Build a ranked, budget-bounded markdown bundle for a session: parents + children from thread_edges, plus approved citations / similar / cousins / wiki_links from the cognitive graph. Pipe-friendly output ready to seed a fresh conversation. Reads only approved edges by default \u2014 pending suggestions are opt-in.",inputSchema:{session_id:T.string().describe("Session UUID or 8+-character prefix."),budget:T.number().int().min(100).max(5e4).optional().describe("Token budget for the assembled bundle. Default 4000."),scoring:T.enum(["pagerank","embedding-rerank","hybrid"]).optional().describe("Scoring mode (default hybrid)."),edge_types:T.array(T.enum(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"])).optional().describe("Restrict to certain link types. Default: all approved types."),max_depth:T.number().int().min(1).max(5).optional().describe("Pagerank traversal depth on the local subgraph (default 2)."),include_wiki_links:T.boolean().optional().describe("Include manual wiki_link rows. Default true."),include_suggestions:T.boolean().optional().describe("Surface pending session_link_suggestions. Debug only; default false."),format:T.enum(["markdown","json"]).optional().describe("markdown (default) returns the bundle as text; json returns the full NeighborhoodResult.")}},async({session_id:s,budget:n,scoring:i,edge_types:r,max_depth:o,include_wiki_links:a,include_suggestions:c,format:l})=>{let d=X(s);if(!d)return H(`session not found or prefix ambiguous: ${s}`);try{let u=tn(d,{budget:n,scoring:i,edgeTypes:r,maxDepth:o,includeWikiLinks:a,includeSuggestions:c});return l==="json"?v(u):ot(u.bundle)}catch(u){return H(u instanceof Error?u.message:String(u))}}),e.registerTool("doctor",{title:"Health check",description:"Read-only diagnostic snapshot of the local Claude Recall database: total size, WAL size, free pages, FTS5 fragmentation for messages and sessions, vector index row count, free disk space, integrity check, and row counts per surface table. Returns structured JSON. Equivalent to running `recall doctor --json` from the shell. Safe to call as often as needed; no side effects.",inputSchema:{}},async()=>{let{buildHealthReport:s}=await Promise.resolve().then(()=>(_n(),gn));return v(s())}),ks(e),t){let s=Number(process.env.RECALL_MCP_RATE_LIMIT),n=Number.isFinite(s)&&s>0?s:Be,i=m(),r=new Date(Date.now()-6e4).toISOString(),o=i.prepare("SELECT at FROM mcp_audit_events WHERE at >= ? ORDER BY at ASC").all(r),a=new K(n);for(let c of o){let l=new Date(c.at).getTime();if(Number.isFinite(l))try{a.consume(l)}catch{break}}e.registerTool("list_sessions_to_tag",{title:"List sessions to tag",description:"Return session summaries and sampled messages formatted for an agent to propose tags. Respects the same scope filters used by the Recall UI scan. Only returns data if auto-tagging is enabled in config.",inputSchema:{untaggedOnly:T.boolean().optional().describe("Only sessions with zero tags (default false)."),project:T.string().optional().describe("Exact project name match."),collectionId:T.string().optional().describe("Only sessions in this collection."),limit:T.number().int().min(1).max(200).optional()}},async c=>{let l=Fe();if(!l.enabled)return H("auto-tagging is disabled; enable it in Recall settings before scanning");let d=he({untaggedOnly:c.untaggedOnly,project:c.project,collectionId:c.collectionId,limit:c.limit??50});return v({count:d.length,sessions:d,guidance:`Produce ${l.minTagsPerSession}-${l.maxTagsPerSession} tags per session. Prefer existing tags from list_tags for consistency. Use apply_tags to write results.`})}),e.registerTool("apply_tags",{title:"Apply tags to a session",description:"Add one or more tags to a session (merge-mode, never removes existing). Only works when auto-tagging is enabled. Accepts full UUID or 8+-character id prefix.",inputSchema:{sessionId:T.string().describe("Full session UUID or 8+-character prefix."),tags:T.array(T.string()).min(1).max(10).describe("Tags to add. Normalized server-side (lowercase, hyphens, strip #).")}},async({sessionId:c,tags:l})=>{if(!Fe().enabled)return H("auto-tagging is disabled; enable it in Recall settings before writing tags");let u=X(c);if(!u)return H(`session not found or prefix ambiguous: ${c}`);try{let p=await w({tool:"apply_tags",args:{sessionId:u,tags:l},limiter:a,run:()=>{let g=[],_=[];for(let f of l)try{let{tag:h,added:S}=ge(u,f);S?g.push(h):_.push({tag:h,reason:"already present"})}catch(h){_.push({tag:f,reason:h instanceof Error?h.message:String(h)})}return{sessionId:u,applied:g,skipped:_}}});return v(p)}catch(p){return p instanceof Error&&p.message.startsWith("SQLITE_")?H("database constraint error"):H(p instanceof Error?p.message:String(p))}}),e.registerTool("optimize",{title:"Optimize the database",description:"Run the local maintenance pass: WAL checkpoint (TRUNCATE), FTS5 segment merge for messages and sessions, refresh planner stats. Equivalent to `recall optimize` from the shell. Safe while the daemon is running. Set vacuum=true to also reclaim free pages \u2014 but VACUUM rewrites the entire DB and requires the daemon to be stopped, so it errors out if the daemon is up. Write-mode only.",inputSchema:{vacuum:T.boolean().optional().describe("Also run VACUUM. Requires the daemon to be stopped.")}},async({vacuum:c})=>{let{runOptimize:l}=await Promise.resolve().then(()=>(hn(),fn)),d=process.stdout.write.bind(process.stdout),u="";process.stdout.write=p=>(u+=typeof p=="string"?p:Buffer.from(p).toString("utf-8"),!0);try{await l({vacuum:!!c,json:!0})}finally{process.stdout.write=d}try{return v(JSON.parse(u.trim()))}catch{return ot(u.trim())}}),vt(e,{limiter:a}),Ds(e,{limiter:a}),console.error(`[claude-recall-mcp] MCP writes ENABLED \u2014 write tools registered (rate limit ${n}/min)`),console.error("[claude-recall-mcp] MCP thread writes ENABLED")}else console.error("[claude-recall-mcp] MCP writes DISABLED (read-only) \u2014 pass --allow-writes to enable"),console.error("[claude-recall-mcp] MCP thread writes DISABLED (read-only)");for(let s of St)e.registerPrompt(s.name,{title:s.title,description:s.description,argsSchema:s.argsSchema},async n=>({messages:[{role:"user",content:{type:"text",text:s.build(n)}}]}));return e}async function Po(){let e=Uo(),t=new Io;await e.connect(t);let s=async()=>{try{await e.close()}catch{}gt(),process.exit(0)};process.on("SIGINT",s),process.on("SIGTERM",s)}var $o=(()=>{try{let e=process.argv[1];return e?import.meta.url===new URL(`file://${e}`).href:!1}catch{return!1}})();$o&&Po().catch(e=>{console.error("[claude-recall-mcp] fatal:",e),process.exit(1)});export{Uo as buildServer};