@clauderecallhq/cli 0.12.5 → 0.61.3

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.
Files changed (218) hide show
  1. package/LICENSE +37 -17
  2. package/README.md +110 -22
  3. package/dist/cli.js +1641 -353
  4. package/dist/daemon/entrypoint.js +1872 -54
  5. package/dist/mcp-server.js +930 -0
  6. package/dist/share/fonts/Inter-Bold.woff +0 -0
  7. package/dist/share/fonts/Inter-Regular.woff +0 -0
  8. package/dist/share/fonts/JetBrainsMono-Regular.woff +0 -0
  9. package/dist/web/assets/_brand-Bw9uSB4O.js +1 -0
  10. package/dist/web/assets/captureNode-9CVj9vYC.js +2 -0
  11. package/dist/web/assets/card-a-minimal-ujNERzX7.js +1 -0
  12. package/dist/web/assets/card-b-terminal-DpJ_tVpg.js +1 -0
  13. package/dist/web/assets/card-c-gradient-CZXVGuNd.js +1 -0
  14. package/dist/web/assets/card-d-dashboard-CHKD-PnB.js +1 -0
  15. package/dist/web/assets/dist-CWaokT35.js +56 -0
  16. package/dist/web/assets/index-B-HrjaDy.css +1 -0
  17. package/dist/web/assets/index-BZYcD76T.js +633 -0
  18. package/dist/web/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
  19. package/dist/web/assets/patterns-BPeZb9N0.js +1 -0
  20. package/dist/web/assets/stats-BSwqSiFU.js +1 -0
  21. package/dist/web/assets/thread-D2AXyhOx.js +1 -0
  22. package/dist/web/index.html +8 -2
  23. package/package.json +58 -18
  24. package/scripts/postinstall.mjs +38 -0
  25. package/dist/cli.js.map +0 -1
  26. package/dist/commands/activate.js +0 -69
  27. package/dist/commands/activate.js.map +0 -1
  28. package/dist/commands/audit-secrets.js +0 -103
  29. package/dist/commands/audit-secrets.js.map +0 -1
  30. package/dist/commands/blame.js +0 -35
  31. package/dist/commands/blame.js.map +0 -1
  32. package/dist/commands/config-verification.js +0 -18
  33. package/dist/commands/config-verification.js.map +0 -1
  34. package/dist/commands/context.js +0 -144
  35. package/dist/commands/context.js.map +0 -1
  36. package/dist/commands/correlate.js +0 -70
  37. package/dist/commands/correlate.js.map +0 -1
  38. package/dist/commands/digest.js +0 -78
  39. package/dist/commands/digest.js.map +0 -1
  40. package/dist/commands/health.js +0 -62
  41. package/dist/commands/health.js.map +0 -1
  42. package/dist/commands/index.js +0 -247
  43. package/dist/commands/index.js.map +0 -1
  44. package/dist/commands/install-extension.js +0 -138
  45. package/dist/commands/install-extension.js.map +0 -1
  46. package/dist/commands/license.js +0 -39
  47. package/dist/commands/license.js.map +0 -1
  48. package/dist/commands/list.js +0 -47
  49. package/dist/commands/list.js.map +0 -1
  50. package/dist/commands/mcp.js +0 -29
  51. package/dist/commands/mcp.js.map +0 -1
  52. package/dist/commands/open.js +0 -28
  53. package/dist/commands/open.js.map +0 -1
  54. package/dist/commands/paste.js +0 -154
  55. package/dist/commands/paste.js.map +0 -1
  56. package/dist/commands/projects.js +0 -36
  57. package/dist/commands/projects.js.map +0 -1
  58. package/dist/commands/search.js +0 -67
  59. package/dist/commands/search.js.map +0 -1
  60. package/dist/commands/semantic.js +0 -173
  61. package/dist/commands/semantic.js.map +0 -1
  62. package/dist/commands/show.js +0 -121
  63. package/dist/commands/show.js.map +0 -1
  64. package/dist/commands/start.js +0 -47
  65. package/dist/commands/start.js.map +0 -1
  66. package/dist/commands/stats.js +0 -133
  67. package/dist/commands/stats.js.map +0 -1
  68. package/dist/commands/status.js +0 -45
  69. package/dist/commands/status.js.map +0 -1
  70. package/dist/commands/stop.js +0 -29
  71. package/dist/commands/stop.js.map +0 -1
  72. package/dist/commands/thread.js +0 -396
  73. package/dist/commands/thread.js.map +0 -1
  74. package/dist/context/formatter.js +0 -103
  75. package/dist/context/formatter.js.map +0 -1
  76. package/dist/daemon/auto-tag-config.js +0 -103
  77. package/dist/daemon/auto-tag-config.js.map +0 -1
  78. package/dist/daemon/auto-tag-config.test.js +0 -72
  79. package/dist/daemon/auto-tag-config.test.js.map +0 -1
  80. package/dist/daemon/auto-title-config.js +0 -70
  81. package/dist/daemon/auto-title-config.js.map +0 -1
  82. package/dist/daemon/bulk-title-jobs.js +0 -170
  83. package/dist/daemon/bulk-title-jobs.js.map +0 -1
  84. package/dist/daemon/correlator.js +0 -320
  85. package/dist/daemon/correlator.js.map +0 -1
  86. package/dist/daemon/discover.js +0 -316
  87. package/dist/daemon/discover.js.map +0 -1
  88. package/dist/daemon/editor-detection.js +0 -186
  89. package/dist/daemon/editor-detection.js.map +0 -1
  90. package/dist/daemon/entrypoint.js.map +0 -1
  91. package/dist/daemon/git-correlator.js +0 -256
  92. package/dist/daemon/git-correlator.js.map +0 -1
  93. package/dist/daemon/mcp-installer.js +0 -108
  94. package/dist/daemon/mcp-installer.js.map +0 -1
  95. package/dist/daemon/onboarding-state.js +0 -140
  96. package/dist/daemon/onboarding-state.js.map +0 -1
  97. package/dist/daemon/pidfile.js +0 -57
  98. package/dist/daemon/pidfile.js.map +0 -1
  99. package/dist/daemon/ports.js +0 -48
  100. package/dist/daemon/ports.js.map +0 -1
  101. package/dist/daemon/scanProgressRegistry.js +0 -62
  102. package/dist/daemon/scanProgressRegistry.js.map +0 -1
  103. package/dist/daemon/server.js +0 -2010
  104. package/dist/daemon/server.js.map +0 -1
  105. package/dist/daemon/tag-scanner/anthropic-client.js +0 -40
  106. package/dist/daemon/tag-scanner/anthropic-client.js.map +0 -1
  107. package/dist/daemon/tag-scanner/autopilot.js +0 -131
  108. package/dist/daemon/tag-scanner/autopilot.js.map +0 -1
  109. package/dist/daemon/tag-scanner/claude-cli-driver.js +0 -250
  110. package/dist/daemon/tag-scanner/claude-cli-driver.js.map +0 -1
  111. package/dist/daemon/tag-scanner/orchestrator.js +0 -88
  112. package/dist/daemon/tag-scanner/orchestrator.js.map +0 -1
  113. package/dist/daemon/tag-scanner/prompt.js +0 -46
  114. package/dist/daemon/tag-scanner/prompt.js.map +0 -1
  115. package/dist/daemon/tag-scanner/prompt.test.js +0 -48
  116. package/dist/daemon/tag-scanner/prompt.test.js.map +0 -1
  117. package/dist/daemon/tag-scanner/scan-state.js +0 -49
  118. package/dist/daemon/tag-scanner/scan-state.js.map +0 -1
  119. package/dist/daemon/tag-scanner/session-fetcher.js +0 -82
  120. package/dist/daemon/tag-scanner/session-fetcher.js.map +0 -1
  121. package/dist/daemon/tag-scanner/session-fetcher.test.js +0 -34
  122. package/dist/daemon/tag-scanner/session-fetcher.test.js.map +0 -1
  123. package/dist/daemon/tag-scanner/validator.js +0 -50
  124. package/dist/daemon/tag-scanner/validator.js.map +0 -1
  125. package/dist/daemon/tag-scanner/validator.test.js +0 -41
  126. package/dist/daemon/tag-scanner/validator.test.js.map +0 -1
  127. package/dist/daemon/terminal-registry.js +0 -443
  128. package/dist/daemon/terminal-registry.js.map +0 -1
  129. package/dist/daemon/ui.js +0 -64
  130. package/dist/daemon/ui.js.map +0 -1
  131. package/dist/daemon/watcher.js +0 -256
  132. package/dist/daemon/watcher.js.map +0 -1
  133. package/dist/db/client.js +0 -22
  134. package/dist/db/client.js.map +0 -1
  135. package/dist/db/schema.js +0 -496
  136. package/dist/db/schema.js.map +0 -1
  137. package/dist/license/api-base.js +0 -13
  138. package/dist/license/api-base.js.map +0 -1
  139. package/dist/license/manager.js +0 -43
  140. package/dist/license/manager.js.map +0 -1
  141. package/dist/license/public-key.js +0 -19
  142. package/dist/license/public-key.js.map +0 -1
  143. package/dist/license/storage.js +0 -27
  144. package/dist/license/storage.js.map +0 -1
  145. package/dist/license/verify.js +0 -23
  146. package/dist/license/verify.js.map +0 -1
  147. package/dist/mcp/audit.js +0 -126
  148. package/dist/mcp/audit.js.map +0 -1
  149. package/dist/mcp/prompts.js +0 -180
  150. package/dist/mcp/prompts.js.map +0 -1
  151. package/dist/mcp/server.js +0 -502
  152. package/dist/mcp/server.js.map +0 -1
  153. package/dist/mcp/thread-tools.js +0 -363
  154. package/dist/mcp/thread-tools.js.map +0 -1
  155. package/dist/mcp/write-tools.js +0 -239
  156. package/dist/mcp/write-tools.js.map +0 -1
  157. package/dist/parser/jsonl.js +0 -150
  158. package/dist/parser/jsonl.js.map +0 -1
  159. package/dist/semantic/chunker.js +0 -47
  160. package/dist/semantic/chunker.js.map +0 -1
  161. package/dist/semantic/config.js +0 -74
  162. package/dist/semantic/config.js.map +0 -1
  163. package/dist/semantic/embedder.js +0 -54
  164. package/dist/semantic/embedder.js.map +0 -1
  165. package/dist/semantic/fusion.js +0 -38
  166. package/dist/semantic/fusion.js.map +0 -1
  167. package/dist/semantic/model-download.js +0 -69
  168. package/dist/semantic/model-download.js.map +0 -1
  169. package/dist/semantic/pipeline.js +0 -375
  170. package/dist/semantic/pipeline.js.map +0 -1
  171. package/dist/semantic/query.js +0 -42
  172. package/dist/semantic/query.js.map +0 -1
  173. package/dist/semantic/worker.js +0 -78
  174. package/dist/semantic/worker.js.map +0 -1
  175. package/dist/stats/backfill.js +0 -151
  176. package/dist/stats/backfill.js.map +0 -1
  177. package/dist/stats/health.js +0 -102
  178. package/dist/stats/health.js.map +0 -1
  179. package/dist/stats/query.js +0 -385
  180. package/dist/stats/query.js.map +0 -1
  181. package/dist/utils/aliases.js +0 -107
  182. package/dist/utils/aliases.js.map +0 -1
  183. package/dist/utils/autoCollections.js +0 -635
  184. package/dist/utils/autoCollections.js.map +0 -1
  185. package/dist/utils/autoTitle.js +0 -348
  186. package/dist/utils/autoTitle.js.map +0 -1
  187. package/dist/utils/collections.js +0 -446
  188. package/dist/utils/collections.js.map +0 -1
  189. package/dist/utils/format.js +0 -46
  190. package/dist/utils/format.js.map +0 -1
  191. package/dist/utils/notes.js +0 -270
  192. package/dist/utils/notes.js.map +0 -1
  193. package/dist/utils/paths.js +0 -50
  194. package/dist/utils/paths.js.map +0 -1
  195. package/dist/utils/pricing.js +0 -257
  196. package/dist/utils/pricing.js.map +0 -1
  197. package/dist/utils/secret-scanner.js +0 -166
  198. package/dist/utils/secret-scanner.js.map +0 -1
  199. package/dist/utils/sessionLabel.js +0 -64
  200. package/dist/utils/sessionLabel.js.map +0 -1
  201. package/dist/utils/tags.js +0 -97
  202. package/dist/utils/tags.js.map +0 -1
  203. package/dist/utils/thread-context.js +0 -129
  204. package/dist/utils/thread-context.js.map +0 -1
  205. package/dist/utils/threadFilter.js +0 -18
  206. package/dist/utils/threadFilter.js.map +0 -1
  207. package/dist/utils/threads-titler.js +0 -298
  208. package/dist/utils/threads-titler.js.map +0 -1
  209. package/dist/utils/threads.js +0 -383
  210. package/dist/utils/threads.js.map +0 -1
  211. package/dist/utils/usage.js +0 -76
  212. package/dist/utils/usage.js.map +0 -1
  213. package/dist/verification/compute.js +0 -88
  214. package/dist/verification/compute.js.map +0 -1
  215. package/dist/verification/config.js +0 -34
  216. package/dist/verification/config.js.map +0 -1
  217. package/dist/web/assets/index-CIr6J4Fw.js +0 -1201
  218. package/dist/web/assets/index-Ctc8g9Jw.css +0 -1
@@ -1,55 +1,1873 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Daemon process entrypoint.
4
- * Started in detached mode by `recall start`; handles file watching + HTTP serving.
5
- */
6
- import { startServer } from './server.js';
7
- import { startWatcher } from './watcher.js';
8
- import { pickFreePort } from './ports.js';
9
- import { writeDaemonInfo, clearDaemonInfo } from './pidfile.js';
10
- import { detectSuggestions } from '../utils/autoCollections.js';
11
- const SUGGESTION_SCAN_INTERVAL_MS = 6 * 60 * 60 * 1000;
12
- const SUGGESTION_STARTUP_DELAY_MS = 60 * 1000;
13
- async function main() {
14
- const port = await pickFreePort();
15
- const server = await startServer(port);
16
- writeDaemonInfo({
17
- pid: process.pid,
18
- port,
19
- startedAt: new Date().toISOString(),
20
- });
21
- const watcher = startWatcher();
22
- // v0.15 T6 suggestion detection. Deferred by a minute on startup so the
23
- // first-run index pass dominates cold-start CPU, then rescanned every 6h.
24
- // Each run is idempotent (UNIQUE(type,pattern)) so a missed interval —
25
- // because the machine was asleep, say — costs nothing.
26
- const runSuggestionScan = () => {
27
- try {
28
- detectSuggestions();
29
- }
30
- catch (err) {
31
- console.error('[daemon] suggestion scan failed:', err);
32
- }
33
- };
34
- const startupTimer = setTimeout(runSuggestionScan, SUGGESTION_STARTUP_DELAY_MS);
35
- const scanTimer = setInterval(runSuggestionScan, SUGGESTION_SCAN_INTERVAL_MS);
36
- const shutdown = (signal) => {
37
- console.log(`[daemon] received ${signal}, shutting down`);
38
- clearTimeout(startupTimer);
39
- clearInterval(scanTimer);
40
- void watcher.close();
41
- server.close();
42
- clearDaemonInfo();
43
- process.exit(0);
44
- };
45
- process.on('SIGTERM', () => shutdown('SIGTERM'));
46
- process.on('SIGINT', () => shutdown('SIGINT'));
47
- process.on('SIGHUP', () => shutdown('SIGHUP'));
48
- console.log(`[daemon] ready on http://127.0.0.1:${port} pid=${process.pid}`);
49
- }
50
- main().catch((err) => {
51
- console.error('[daemon] fatal:', err);
52
- clearDaemonInfo();
53
- process.exit(1);
54
- });
55
- //# sourceMappingURL=entrypoint.js.map
2
+ /* Claude Recall (proprietary). See LICENSE for terms. */
3
+ var Fu=Object.defineProperty;var we=(e,t)=>()=>(e&&(t=e(e=0)),t);var Oo=(e,t)=>{for(var s in t)Fu(e,s,{get:t[s],enumerable:!0})};import{homedir as Lo}from"node:os";import{join as bn,basename as RT}from"node:path";import{existsSync as Pu,mkdirSync as Uu,chmodSync as $u,readdirSync as AT,statSync as NT}from"node:fs";function J(){Pu($)||Uu($,{recursive:!0,mode:448}),process.platform!=="win32"&&$u($,448)}var Co,$,Sn,Z=we(()=>{"use strict";Co=bn(Lo(),".claude","projects"),$=process.env.RECALL_HOME?process.env.RECALL_HOME:bn(Lo(),".recall"),Sn=bn($,"db.sqlite")});function Jo(e){let t=e.prepare("PRAGMA table_info(sessions)").all(),s=new Set(t.map(S=>S.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[S,y]of n)s.has(S)||e.exec(`ALTER TABLE sessions ADD COLUMN ${S} ${y}`);let r=e.prepare("PRAGMA table_info(collection_sessions)").all(),o=new Set(r.map(S=>S.name)),a=[["source","TEXT NOT NULL DEFAULT 'manual'"],["rule_id","TEXT"]];for(let[S,y]of a)o.has(S)||e.exec(`ALTER TABLE collection_sessions ADD COLUMN ${S} ${y}`);let c=e.prepare("PRAGMA table_info(session_notes)").all(),d=new Set(c.map(S=>S.name)),u=[["auto_synopsis","TEXT"],["auto_synopsis_generated_at","INTEGER"],["auto_synopsis_history","TEXT"]];for(let[S,y]of u)d.has(S)||e.exec(`ALTER TABLE session_notes ADD COLUMN ${S} ${y}`);let g=e.prepare("PRAGMA table_info(threads)").all();new Set(g.map(S=>S.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 h=e.prepare("PRAGMA table_info(thread_folders)").all(),b=new Set(h.map(S=>S.name));b.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)")),b.has("sort_order")||e.exec("ALTER TABLE thread_folders ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0"),e.exec(`
4
+ CREATE TABLE IF NOT EXISTS message_embeddings (
5
+ message_uuid TEXT PRIMARY KEY REFERENCES messages(uuid) ON DELETE CASCADE,
6
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
7
+ embedding BLOB NOT NULL,
8
+ embedding_model_id TEXT NOT NULL DEFAULT 'bge-base-en-v1.5',
9
+ embedding_dim INTEGER NOT NULL DEFAULT 768,
10
+ text_length INTEGER NOT NULL,
11
+ generated_at TEXT NOT NULL
12
+ );
13
+ CREATE INDEX IF NOT EXISTS idx_message_embeddings_session ON message_embeddings(session_id);
14
+ CREATE INDEX IF NOT EXISTS idx_message_embeddings_generated ON message_embeddings(generated_at DESC);
15
+ `)}var qo,Xo=we(()=>{"use strict";qo=`
16
+ PRAGMA journal_mode = WAL;
17
+ PRAGMA synchronous = NORMAL;
18
+ PRAGMA foreign_keys = ON;
19
+
20
+ CREATE TABLE IF NOT EXISTS projects (
21
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
22
+ encoded_path TEXT UNIQUE NOT NULL,
23
+ decoded_path TEXT NOT NULL,
24
+ name TEXT NOT NULL
25
+ );
26
+
27
+ CREATE TABLE IF NOT EXISTS sessions (
28
+ id TEXT PRIMARY KEY,
29
+ project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
30
+ file_path TEXT NOT NULL,
31
+ file_mtime REAL NOT NULL,
32
+ started_at TEXT,
33
+ ended_at TEXT,
34
+ message_count INTEGER NOT NULL DEFAULT 0,
35
+ user_message_count INTEGER NOT NULL DEFAULT 0,
36
+ assistant_message_count INTEGER NOT NULL DEFAULT 0,
37
+ first_user_message TEXT,
38
+ cwd TEXT,
39
+ git_branch TEXT,
40
+ version TEXT,
41
+ indexed_at TEXT NOT NULL
42
+ );
43
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
44
+ CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at DESC);
45
+
46
+ CREATE TABLE IF NOT EXISTS messages (
47
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ uuid TEXT UNIQUE NOT NULL,
49
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
50
+ parent_uuid TEXT,
51
+ type TEXT NOT NULL,
52
+ role TEXT,
53
+ timestamp TEXT,
54
+ is_sidechain INTEGER NOT NULL DEFAULT 0,
55
+ content_text TEXT,
56
+ tool_names TEXT,
57
+ raw_json TEXT NOT NULL
58
+ );
59
+ CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, timestamp);
60
+
61
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
62
+ content_text,
63
+ tool_names,
64
+ content='messages',
65
+ content_rowid='rowid',
66
+ tokenize = 'porter unicode61'
67
+ );
68
+
69
+ CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
70
+ INSERT INTO messages_fts(rowid, content_text, tool_names)
71
+ VALUES (new.rowid, new.content_text, new.tool_names);
72
+ END;
73
+
74
+ CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
75
+ INSERT INTO messages_fts(messages_fts, rowid, content_text, tool_names)
76
+ VALUES ('delete', old.rowid, old.content_text, old.tool_names);
77
+ END;
78
+
79
+ CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
80
+ INSERT INTO messages_fts(messages_fts, rowid, content_text, tool_names)
81
+ VALUES ('delete', old.rowid, old.content_text, old.tool_names);
82
+ INSERT INTO messages_fts(rowid, content_text, tool_names)
83
+ VALUES (new.rowid, new.content_text, new.tool_names);
84
+ END;
85
+
86
+ -- v0.4.1 \u2014 session aliases. The UUID stays the primary key forever; aliases
87
+ -- are a display layer on top. Every edit archives the prior value into
88
+ -- previous_aliases (JSON array) \u2014 we never destroy history.
89
+ CREATE TABLE IF NOT EXISTS session_aliases (
90
+ session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
91
+ alias TEXT NOT NULL,
92
+ updated_at TEXT NOT NULL,
93
+ previous_aliases TEXT NOT NULL DEFAULT '[]'
94
+ );
95
+
96
+ -- v0.4.2 \u2014 per-session markdown notes.
97
+ -- Every save archives the prior content into previous_versions and also mirrors
98
+ -- the full note out to ~/.recall/notes/<session_id>.md on disk. Nothing is ever
99
+ -- destroyed: an empty string means "cleared" with full history preserved.
100
+ CREATE TABLE IF NOT EXISTS session_notes (
101
+ session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
102
+ content TEXT NOT NULL DEFAULT '',
103
+ updated_at TEXT NOT NULL,
104
+ previous_versions TEXT NOT NULL DEFAULT '[]'
105
+ );
106
+
107
+ -- v0.4.3 \u2014 session tags. Many-to-many. Never destructively delete:
108
+ -- tag_events is an append-only log of every add and remove across all time,
109
+ -- so the full history of what was tagged when and why is always recoverable.
110
+ CREATE TABLE IF NOT EXISTS session_tags (
111
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
112
+ tag TEXT NOT NULL,
113
+ created_at TEXT NOT NULL,
114
+ PRIMARY KEY (session_id, tag)
115
+ );
116
+ CREATE INDEX IF NOT EXISTS idx_session_tags_tag ON session_tags(tag);
117
+
118
+ CREATE TABLE IF NOT EXISTS tag_events (
119
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
120
+ session_id TEXT NOT NULL,
121
+ tag TEXT NOT NULL,
122
+ action TEXT NOT NULL, -- 'add' | 'remove'
123
+ at TEXT NOT NULL
124
+ );
125
+ CREATE INDEX IF NOT EXISTS idx_tag_events_session ON tag_events(session_id, at DESC);
126
+ CREATE INDEX IF NOT EXISTS idx_tag_events_tag ON tag_events(tag, at DESC);
127
+
128
+ -- v0.4.5 \u2014 opt-in clipboard archive. This is the ONE table where a user can
129
+ -- truly purge a row on demand (not soft-delete), because it may contain
130
+ -- accidentally-archived secrets. This carve-out is documented as a deliberate
131
+ -- exception to the "never delete" rule specifically for this data class.
132
+ CREATE TABLE IF NOT EXISTS paste_archives (
133
+ id TEXT PRIMARY KEY,
134
+ created_at TEXT NOT NULL,
135
+ content TEXT NOT NULL,
136
+ size_bytes INTEGER NOT NULL,
137
+ source TEXT NOT NULL DEFAULT 'cli', -- 'cli' | 'cli-piped' | 'ui' | \u2026
138
+ label TEXT -- optional user-supplied short description
139
+ );
140
+ CREATE INDEX IF NOT EXISTS idx_paste_archives_created ON paste_archives(created_at DESC);
141
+
142
+ -- v0.8 \u2014 collections. User-curated hand-picked groupings of sessions that
143
+ -- cut across the coarse-grained cwd-based "projects". A collection can nest
144
+ -- as a tree (parent_id \u2192 null means root). Soft-deletion only: archived_at
145
+ -- hides rows from default views, the row itself stays forever.
146
+ --
147
+ -- Durability: SQLite (this table + collection_events append-only log) plus a
148
+ -- plain-text mirror at ~/.recall/collections.json rewritten on every mutation.
149
+ CREATE TABLE IF NOT EXISTS collections (
150
+ id TEXT PRIMARY KEY,
151
+ name TEXT NOT NULL,
152
+ description TEXT,
153
+ icon TEXT,
154
+ color TEXT,
155
+ parent_id TEXT REFERENCES collections(id) ON DELETE SET NULL,
156
+ sort_key TEXT NOT NULL DEFAULT '',
157
+ created_at TEXT NOT NULL,
158
+ updated_at TEXT NOT NULL,
159
+ archived_at TEXT
160
+ );
161
+ CREATE INDEX IF NOT EXISTS idx_collections_parent ON collections(parent_id);
162
+ CREATE INDEX IF NOT EXISTS idx_collections_archived ON collections(archived_at);
163
+
164
+ CREATE TABLE IF NOT EXISTS collection_sessions (
165
+ collection_id TEXT NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
166
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
167
+ added_at TEXT NOT NULL,
168
+ note TEXT,
169
+ PRIMARY KEY (collection_id, session_id)
170
+ );
171
+ CREATE INDEX IF NOT EXISTS idx_collection_sessions_session ON collection_sessions(session_id);
172
+
173
+ CREATE TABLE IF NOT EXISTS collection_events (
174
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
175
+ collection_id TEXT NOT NULL,
176
+ session_id TEXT,
177
+ action TEXT NOT NULL, -- 'create' | 'rename' | 'describe' | 'recolor' | 'add' | 'remove' | 'archive' | 'restore' | 'move' | 'reorder'
178
+ payload TEXT, -- JSON context (old_name, new_parent, etc.)
179
+ at TEXT NOT NULL
180
+ );
181
+ CREATE INDEX IF NOT EXISTS idx_collection_events_at ON collection_events(at DESC);
182
+ CREATE INDEX IF NOT EXISTS idx_collection_events_collection ON collection_events(collection_id, at DESC);
183
+
184
+ -- v0.11 \u2014 semantic search (Tier 0: shells out to the user's local claude CLI to
185
+ -- summarize each session into 3-sentence prose + a keyword set). Both columns
186
+ -- are TEXT and feed sessions_fts so a conceptual query can hit this index in
187
+ -- addition to the per-message FTS5 index. Pipeline is OFF by default; users opt
188
+ -- in via "recall semantic on". Plain-text mirror at ~/.recall/semantic/<id>.json.
189
+ CREATE TABLE IF NOT EXISTS session_semantic (
190
+ session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
191
+ summary TEXT NOT NULL DEFAULT '',
192
+ keywords TEXT NOT NULL DEFAULT '', -- comma-separated
193
+ model TEXT,
194
+ source_message_count INTEGER NOT NULL DEFAULT 0,
195
+ generated_at TEXT NOT NULL
196
+ );
197
+
198
+ CREATE VIRTUAL TABLE IF NOT EXISTS sessions_fts USING fts5(
199
+ summary,
200
+ keywords,
201
+ content='session_semantic',
202
+ content_rowid='rowid',
203
+ tokenize = 'porter unicode61'
204
+ );
205
+
206
+ CREATE TRIGGER IF NOT EXISTS session_semantic_ai AFTER INSERT ON session_semantic BEGIN
207
+ INSERT INTO sessions_fts(rowid, summary, keywords)
208
+ VALUES (new.rowid, new.summary, new.keywords);
209
+ END;
210
+
211
+ CREATE TRIGGER IF NOT EXISTS session_semantic_ad AFTER DELETE ON session_semantic BEGIN
212
+ INSERT INTO sessions_fts(sessions_fts, rowid, summary, keywords)
213
+ VALUES ('delete', old.rowid, old.summary, old.keywords);
214
+ END;
215
+
216
+ CREATE TRIGGER IF NOT EXISTS session_semantic_au AFTER UPDATE ON session_semantic BEGIN
217
+ INSERT INTO sessions_fts(sessions_fts, rowid, summary, keywords)
218
+ VALUES ('delete', old.rowid, old.summary, old.keywords);
219
+ INSERT INTO sessions_fts(rowid, summary, keywords)
220
+ VALUES (new.rowid, new.summary, new.keywords);
221
+ END;
222
+
223
+ -- v0.7 \u2014 vector search tier (Pro-only). Chunks are per-conversational-turn
224
+ -- segments of messages, embedded via local bge-base-en-v1.5 (768d). The vector
225
+ -- data is a derived cache; if lost, it is recomputable from messages.
226
+ -- Tables stay empty on Free tier (no model downloaded, no worker started).
227
+
228
+ CREATE VIRTUAL TABLE IF NOT EXISTS vec_chunks USING vec0(
229
+ embedding float[768] distance_metric=cosine
230
+ );
231
+
232
+ CREATE TABLE IF NOT EXISTS chunk_meta (
233
+ rowid INTEGER PRIMARY KEY,
234
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
235
+ message_uuids TEXT NOT NULL DEFAULT '[]',
236
+ text TEXT NOT NULL DEFAULT '',
237
+ embedding_model_id TEXT NOT NULL DEFAULT 'bge-base-en-v1.5',
238
+ embedding_dim INTEGER NOT NULL DEFAULT 768,
239
+ stale INTEGER NOT NULL DEFAULT 0,
240
+ generated_at TEXT NOT NULL DEFAULT ''
241
+ );
242
+ CREATE INDEX IF NOT EXISTS idx_chunk_meta_session ON chunk_meta(session_id);
243
+ CREATE INDEX IF NOT EXISTS idx_chunk_meta_stale ON chunk_meta(stale) WHERE stale = 1;
244
+
245
+ CREATE TABLE IF NOT EXISTS chunk_queue (
246
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
247
+ session_id TEXT NOT NULL,
248
+ message_uuid TEXT,
249
+ action TEXT NOT NULL CHECK (action IN ('embed', 'reembed', 'delete')),
250
+ enqueued_at TEXT NOT NULL DEFAULT (datetime('now'))
251
+ );
252
+ CREATE INDEX IF NOT EXISTS idx_chunk_queue_session ON chunk_queue(session_id);
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
256
+ BEGIN
257
+ INSERT INTO chunk_queue(session_id, message_uuid, action, enqueued_at)
258
+ VALUES (new.session_id, new.uuid, 'embed', datetime('now'));
259
+ END;
260
+
261
+ CREATE TRIGGER IF NOT EXISTS messages_vec_ad AFTER DELETE ON messages BEGIN
262
+ INSERT INTO chunk_queue(session_id, message_uuid, action, enqueued_at)
263
+ VALUES (old.session_id, old.uuid, 'delete', datetime('now'));
264
+ END;
265
+
266
+ CREATE TRIGGER IF NOT EXISTS messages_vec_au AFTER UPDATE OF content_text ON messages
267
+ WHEN new.is_sidechain = 0
268
+ BEGIN
269
+ INSERT INTO chunk_queue(session_id, message_uuid, action, enqueued_at)
270
+ VALUES (new.session_id, new.uuid, 'reembed', datetime('now'));
271
+ END;
272
+
273
+ -- v0.10a \u2014 cost / token analytics. Claude Code already writes per-assistant-
274
+ -- message usage + model into the source JSONLs; we persist the raw counts
275
+ -- here and derive dollar amounts at render time (pricing changes; token
276
+ -- counts don't). message_usage is 1:1 with messages (keyed by message_uuid)
277
+ -- so a reindex rebuilds it cleanly; rollup columns on sessions are a
278
+ -- cache for fast list rendering.
279
+ CREATE TABLE IF NOT EXISTS message_usage (
280
+ message_uuid TEXT PRIMARY KEY REFERENCES messages(uuid) ON DELETE CASCADE,
281
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
282
+ model TEXT,
283
+ input_tokens INTEGER NOT NULL DEFAULT 0,
284
+ output_tokens INTEGER NOT NULL DEFAULT 0,
285
+ cache_create_tokens INTEGER NOT NULL DEFAULT 0,
286
+ cache_read_tokens INTEGER NOT NULL DEFAULT 0,
287
+ timestamp TEXT
288
+ );
289
+ CREATE INDEX IF NOT EXISTS idx_message_usage_session ON message_usage(session_id);
290
+ CREATE INDEX IF NOT EXISTS idx_message_usage_model ON message_usage(model);
291
+ -- Windowed stats queries (7d / 30d) filter on mu.timestamp >= since.
292
+ -- Without this index every refresh full-scans message_usage.
293
+ CREATE INDEX IF NOT EXISTS idx_message_usage_timestamp
294
+ ON message_usage(timestamp);
295
+ -- Daily-bucket aggregation groups by substr(timestamp, 1, 10) and joins
296
+ -- on session_id. The composite covers the common "by session in window"
297
+ -- access pattern and lets the engine skip the row lookup for many plans.
298
+ CREATE INDEX IF NOT EXISTS idx_message_usage_session_ts
299
+ ON message_usage(session_id, timestamp);
300
+
301
+ -- v0.10b \u2014 git correlation. For any session whose cwd is a git worktree we
302
+ -- run a read-only \`git log\` scoped to that cwd for the [started_at, ended_at]
303
+ -- window and record every resulting commit. Composite PK (session_id,
304
+ -- commit_sha) lets a single commit belong to multiple sessions (rare, but
305
+ -- happens when a long session ends right as another starts). The reverse
306
+ -- index on commit_sha powers \`recall blame <sha>\`.
307
+ --
308
+ -- correlated_at lets the correlator skip sessions it has already processed
309
+ -- recently; cwd_snapshot captures the directory we ran git in so stale rows
310
+ -- can be identified if the user later points the session elsewhere.
311
+ CREATE TABLE IF NOT EXISTS session_commits (
312
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
313
+ commit_sha TEXT NOT NULL,
314
+ committed_at TEXT,
315
+ subject TEXT,
316
+ cwd_snapshot TEXT,
317
+ correlated_at TEXT NOT NULL,
318
+ PRIMARY KEY (session_id, commit_sha)
319
+ );
320
+ CREATE INDEX IF NOT EXISTS idx_session_commits_sha ON session_commits(commit_sha);
321
+ CREATE INDEX IF NOT EXISTS idx_session_commits_session ON session_commits(session_id);
322
+ -- v0.13 \u2014 MCP write audit log. Every write tool invocation appends one row,
323
+ -- regardless of outcome (ok / error / rate_limited). Tags and collections
324
+ -- already have their own append-only event tables (tag_events,
325
+ -- collection_events) \u2014 this table covers note/alias writes and provides a
326
+ -- single chronological feed across all MCP write activity for review.
327
+ CREATE TABLE IF NOT EXISTS mcp_audit_events (
328
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
329
+ tool TEXT NOT NULL,
330
+ args_json TEXT NOT NULL,
331
+ result TEXT NOT NULL, -- 'ok' | 'error' | 'rate_limited'
332
+ error_message TEXT,
333
+ caller TEXT,
334
+ at TEXT NOT NULL
335
+ );
336
+ CREATE INDEX IF NOT EXISTS idx_mcp_audit_events_at ON mcp_audit_events(at DESC);
337
+ CREATE INDEX IF NOT EXISTS idx_mcp_audit_events_tool ON mcp_audit_events(tool, at DESC);
338
+
339
+ -- v0.15 T6 \u2014 auto-collections. Rules match sessions by cwd prefix, project
340
+ -- id, tag, plan-file reference, or git branch prefix, and insert auto
341
+ -- memberships into collection_sessions (tagged with source='auto' + the
342
+ -- originating rule_id). Manual memberships (source='manual') are never
343
+ -- touched by the rule engine \u2014 so a user's hand-curated pick always wins.
344
+ --
345
+ -- Suggestions are the discovery half: the daemon periodically surveys the
346
+ -- corpus, detects clusters that *would* make good auto-collections, and
347
+ -- stashes them here for the user to accept or dismiss. UNIQUE(type,pattern)
348
+ -- means the same cluster won't be re-suggested every scan.
349
+ --
350
+ -- Durability mirror: ~/.recall/auto-rules/{rules.json,suggestions.json}
351
+ -- rewritten on every mutation. Deleting a rule removes ONLY its auto
352
+ -- memberships (matched via rule_id); the target collection stays so the
353
+ -- user can keep whatever they manually added into it.
354
+ CREATE TABLE IF NOT EXISTS auto_collection_rules (
355
+ id TEXT PRIMARY KEY,
356
+ name TEXT NOT NULL,
357
+ type TEXT NOT NULL CHECK (type IN ('cwd-prefix','project-id','tag','plan-file','git-branch-prefix')),
358
+ pattern TEXT NOT NULL,
359
+ collection_id TEXT NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
360
+ priority INTEGER NOT NULL DEFAULT 100,
361
+ enabled INTEGER NOT NULL DEFAULT 1,
362
+ created_at TEXT NOT NULL,
363
+ created_by TEXT NOT NULL DEFAULT 'user' -- 'user' | 'suggestion-accepted' | 'seed'
364
+ );
365
+ CREATE INDEX IF NOT EXISTS idx_auto_rules_type_enabled ON auto_collection_rules(type, enabled);
366
+ CREATE INDEX IF NOT EXISTS idx_auto_rules_collection ON auto_collection_rules(collection_id);
367
+
368
+ CREATE TABLE IF NOT EXISTS auto_collection_suggestions (
369
+ id TEXT PRIMARY KEY,
370
+ type TEXT NOT NULL,
371
+ pattern TEXT NOT NULL,
372
+ suggested_name TEXT NOT NULL,
373
+ suggested_parent_collection_id TEXT,
374
+ session_count INTEGER NOT NULL,
375
+ detected_at TEXT NOT NULL,
376
+ dismissed INTEGER NOT NULL DEFAULT 0,
377
+ UNIQUE(type, pattern)
378
+ );
379
+ CREATE INDEX IF NOT EXISTS idx_auto_suggestions_detected ON auto_collection_suggestions(detected_at DESC);
380
+
381
+ -- v0.15 Threads. The headline intent-grouping primitive: a DAG of sessions
382
+ -- connected by shared purpose (one or more origin sessions plus their
383
+ -- children). Additive to Projects and Collections; neither is refactored.
384
+ -- thread_edges is many-to-many so a session can belong to multiple threads
385
+ -- (planning session that seeds two features). parent_session_id is
386
+ -- nullable; when null, the row is an origin. confidence = 1.0 for manual
387
+ -- edges; < 1.0 for auto-detected edges (v0.15b). source tracks provenance
388
+ -- for the audit/undo paths.
389
+ CREATE TABLE IF NOT EXISTS threads (
390
+ id TEXT PRIMARY KEY,
391
+ name TEXT NOT NULL,
392
+ summary TEXT,
393
+ created_at TEXT NOT NULL,
394
+ closed_at TEXT,
395
+ archived INTEGER NOT NULL DEFAULT 0
396
+ );
397
+
398
+ CREATE TABLE IF NOT EXISTS thread_edges (
399
+ thread_id TEXT NOT NULL,
400
+ session_id TEXT NOT NULL,
401
+ parent_session_id TEXT,
402
+ role TEXT NOT NULL CHECK(role IN ('origin','child')),
403
+ confidence REAL NOT NULL DEFAULT 1.0 CHECK(confidence >= 0 AND confidence <= 1),
404
+ source TEXT NOT NULL DEFAULT 'manual',
405
+ added_at TEXT NOT NULL,
406
+ PRIMARY KEY (thread_id, session_id),
407
+ FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE
408
+ );
409
+ CREATE INDEX IF NOT EXISTS idx_thread_edges_session ON thread_edges(session_id);
410
+ CREATE INDEX IF NOT EXISTS idx_thread_edges_parent ON thread_edges(parent_session_id);
411
+ CREATE INDEX IF NOT EXISTS idx_thread_edges_thread_role ON thread_edges(thread_id, role);
412
+
413
+ -- v0.6 #4: user-defined SUBFOLDERS that nest inside the auto-derived
414
+ -- per-project folders the Threads sidebar already renders. Critical
415
+ -- design rule: auto-project folders are NOT in this table \u2014 they are
416
+ -- computed at render time from project membership of each thread's
417
+ -- sessions. So even with this table empty, every project folder still
418
+ -- renders and every thread still has a visible home. That's the
419
+ -- safety property that the previous full-replacement attempt lost
420
+ -- (when no user folders existed, every thread fell into a single
421
+ -- collapsed "(unfiled)" bucket and the sidebar looked broken).
422
+ --
423
+ -- project_scope: the project this user folder is nested under. NULL
424
+ -- means top-level (cross-project, e.g. "v0.6 Launch" that holds
425
+ -- threads from multiple repos).
426
+ -- parent_folder_id: another USER folder this one nests inside. NULL
427
+ -- means it's a direct child of project_scope's auto folder, or
428
+ -- top-level if project_scope is also NULL.
429
+ CREATE TABLE IF NOT EXISTS thread_folders (
430
+ id TEXT PRIMARY KEY,
431
+ name TEXT NOT NULL,
432
+ parent_folder_id TEXT REFERENCES thread_folders(id) ON DELETE CASCADE,
433
+ project_scope TEXT,
434
+ created_at TEXT NOT NULL,
435
+ archived INTEGER NOT NULL DEFAULT 0,
436
+ sort_order INTEGER NOT NULL DEFAULT 0
437
+ );
438
+ CREATE INDEX IF NOT EXISTS idx_thread_folders_parent ON thread_folders(parent_folder_id);
439
+ CREATE INDEX IF NOT EXISTS idx_thread_folders_project ON thread_folders(project_scope);
440
+
441
+ -- v0.17 -- recall event log. Every \`recall context\` invocation writes one
442
+ -- row. Powers share-card metadata ("recalled today") and monthly wrap
443
+ -- aggregation (total recalls, most-recalled session, etc.). Append-only;
444
+ -- no deletes. Plain-text mirror at ~/.recall/recall-events.json rewritten
445
+ -- on mutation.
446
+ CREATE TABLE IF NOT EXISTS recall_events (
447
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
448
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
449
+ recalled_at TEXT NOT NULL,
450
+ token_count INTEGER NOT NULL DEFAULT 0,
451
+ mode TEXT NOT NULL DEFAULT 'full',
452
+ caller TEXT NOT NULL DEFAULT 'cli'
453
+ );
454
+ CREATE INDEX IF NOT EXISTS idx_recall_events_session ON recall_events(session_id, recalled_at DESC);
455
+ CREATE INDEX IF NOT EXISTS idx_recall_events_at ON recall_events(recalled_at DESC);
456
+
457
+ -- v0.18 cog-graph Phase C \u2014 multi-edge schema.
458
+ --
459
+ -- Two parallel edge systems live side by side and MUST NOT be merged:
460
+ -- 1. thread_edges (above) \u2014 hierarchical DAG, intent grouping.
461
+ -- 2. session_links (below) \u2014 non-hierarchical: citations, semantic
462
+ -- similarity, skill tracks, bug-pattern membership, manual wiki
463
+ -- links, temporal proximity. Joined at query time; never schema-
464
+ -- level merged.
465
+ --
466
+ -- Every row carries provenance (source + evidence JSON + confidence) so
467
+ -- "why does this edge exist?" always has an answer. The unique constraint
468
+ -- on (source, target, link_type) is the soft idempotency key \u2014 re-running
469
+ -- inference upserts instead of duplicating. Approval gating via
470
+ -- session_link_suggestions (the queue) \u2192 session_links (the live store)
471
+ -- keeps trust intact: nothing auto-promotes without user opt-in.
472
+ --
473
+ -- Mirror layout: ~/.recall/links/<source-session-id>.json (per-source
474
+ -- denormalised; rewritten on mutation), ~/.recall/suggestions/index.json
475
+ -- (single file rewritten on mutation).
476
+ CREATE TABLE IF NOT EXISTS session_links (
477
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
478
+ source_session_id TEXT NOT NULL,
479
+ target_session_id TEXT NOT NULL,
480
+ link_type TEXT NOT NULL CHECK(link_type IN (
481
+ 'citation','similar','skill_track','bug_pattern',
482
+ 'wiki_link','temporal_proximity'
483
+ )),
484
+ confidence REAL NOT NULL CHECK(confidence BETWEEN 0 AND 1),
485
+ source TEXT NOT NULL,
486
+ evidence TEXT NOT NULL,
487
+ approved INTEGER NOT NULL DEFAULT 0,
488
+ created_at TEXT NOT NULL,
489
+ updated_at TEXT NOT NULL,
490
+ UNIQUE(source_session_id, target_session_id, link_type)
491
+ );
492
+ CREATE INDEX IF NOT EXISTS idx_links_source ON session_links(source_session_id);
493
+ CREATE INDEX IF NOT EXISTS idx_links_target ON session_links(target_session_id);
494
+ CREATE INDEX IF NOT EXISTS idx_links_type ON session_links(link_type);
495
+
496
+ -- v0.18 cog-graph Phase C \u2014 per-session structured outputs.
497
+ --
498
+ -- The "what did this session produce" index. Phase D's LLM-augmented
499
+ -- extractor sub-agent populates this row (Phase C ships the empty
500
+ -- table). Stays empty in Phase C. extractor_version lets us re-derive
501
+ -- when the extraction prompt or schema improves without losing the
502
+ -- existing rows. raw_extraction is the full sub-agent output blob so we
503
+ -- can re-derive structured fields without re-running extraction.
504
+ --
505
+ -- Mirror: ~/.recall/output-index/<session-id>.json rewritten on mutation.
506
+ CREATE TABLE IF NOT EXISTS session_output_index (
507
+ session_id TEXT PRIMARY KEY,
508
+ files_written TEXT,
509
+ brands_mentioned TEXT,
510
+ terms_introduced TEXT,
511
+ plan_ids_referenced TEXT,
512
+ bug_signatures TEXT,
513
+ raw_extraction TEXT,
514
+ extracted_at TEXT NOT NULL,
515
+ extractor_version INTEGER NOT NULL DEFAULT 1
516
+ );
517
+
518
+ -- v0.18 cog-graph Phase C \u2014 pending edge suggestions awaiting review.
519
+ --
520
+ -- Auto-inferred edges (from L1/L2/L3/L4 inference jobs) land here first
521
+ -- with status='pending'. User accepts \u2192 status='approved' AND a row gets
522
+ -- created in session_links with approved=1. User rejects \u2192 status=
523
+ -- 'rejected' (tombstone \u2014 prevents re-proposing the same pair from the
524
+ -- same inferer). Phase F builds the queue UI; Phase C ships the empty
525
+ -- table. The unique key includes inferred_by so two layers (e.g. L2
526
+ -- citation match + L3 embedding match) can both contribute evidence
527
+ -- without one stomping the other.
528
+ CREATE TABLE IF NOT EXISTS session_link_suggestions (
529
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
530
+ source_session_id TEXT NOT NULL,
531
+ target_session_id TEXT NOT NULL,
532
+ link_type TEXT NOT NULL,
533
+ confidence REAL NOT NULL,
534
+ evidence TEXT NOT NULL,
535
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected')),
536
+ inferred_by TEXT NOT NULL,
537
+ created_at TEXT NOT NULL,
538
+ decided_at TEXT,
539
+ UNIQUE(source_session_id, target_session_id, link_type, inferred_by)
540
+ );
541
+ CREATE INDEX IF NOT EXISTS idx_suggestions_pending ON session_link_suggestions(status, created_at) WHERE status = 'pending';
542
+ CREATE INDEX IF NOT EXISTS idx_suggestions_source ON session_link_suggestions(source_session_id);
543
+ CREATE INDEX IF NOT EXISTS idx_suggestions_target ON session_link_suggestions(target_session_id);
544
+
545
+ -- v0.18 cog-graph Phase C \u2014 bug-pattern clusters. Empty in Phase C;
546
+ -- populated by Phase H (HDBSCAN over bug-signature embeddings). Tables
547
+ -- ship now so the schema is stable across the milestone. resolved_in_
548
+ -- session_id is set when the user marks "this fix worked" \u2014 surfaced
549
+ -- the next time a session's bug signature falls into the same cluster.
550
+ CREATE TABLE IF NOT EXISTS bug_pattern_clusters (
551
+ id TEXT PRIMARY KEY,
552
+ signature_hash TEXT NOT NULL,
553
+ example_message TEXT NOT NULL,
554
+ occurrence_count INTEGER NOT NULL,
555
+ first_seen_at TEXT NOT NULL,
556
+ last_seen_at TEXT NOT NULL,
557
+ resolved_in_session_id TEXT,
558
+ fix_summary TEXT
559
+ );
560
+ CREATE INDEX IF NOT EXISTS idx_bug_clusters_signature ON bug_pattern_clusters(signature_hash);
561
+ CREATE INDEX IF NOT EXISTS idx_bug_clusters_last_seen ON bug_pattern_clusters(last_seen_at DESC);
562
+
563
+ CREATE TABLE IF NOT EXISTS bug_pattern_members (
564
+ cluster_id TEXT NOT NULL REFERENCES bug_pattern_clusters(id) ON DELETE CASCADE,
565
+ session_id TEXT NOT NULL,
566
+ matched_at TEXT NOT NULL,
567
+ PRIMARY KEY (cluster_id, session_id)
568
+ );
569
+ CREATE INDEX IF NOT EXISTS idx_bug_members_session ON bug_pattern_members(session_id);
570
+
571
+ -- v0.20 / project rollups. The user's mental model is "I have ~18 repos"
572
+ -- but Recall's project model is "every cwd is a project" (~32 entries).
573
+ -- Macro repos are a deterministic, manual grouping layer the user
574
+ -- defines: pick N projects, label them as one logical repo. Display
575
+ -- surfaces collapse member projects into the macro repo when the user
576
+ -- toggles "by macro repo." See docs/internal/project-rollups.md.
577
+ --
578
+ -- We deliberately reject auto-grouping: a wrong auto-rollup is worse
579
+ -- than no rollup. Membership is always explicit, set by the user.
580
+ CREATE TABLE IF NOT EXISTS macro_repos (
581
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
582
+ name TEXT NOT NULL UNIQUE,
583
+ description TEXT,
584
+ created_at TEXT NOT NULL,
585
+ updated_at TEXT NOT NULL
586
+ );
587
+
588
+ CREATE TABLE IF NOT EXISTS macro_repo_members (
589
+ macro_repo_id INTEGER NOT NULL REFERENCES macro_repos(id) ON DELETE CASCADE,
590
+ project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
591
+ added_at TEXT NOT NULL,
592
+ PRIMARY KEY (macro_repo_id, project_id)
593
+ );
594
+ CREATE INDEX IF NOT EXISTS idx_macro_repo_members_project ON macro_repo_members(project_id);
595
+
596
+ -- v0.20 / synthesis result persistence. Every successful Bug Pattern
597
+ -- synthesis run writes its full Markdown output here so the user can
598
+ -- revisit reports later without re-spending tokens. Keyed by
599
+ -- (scope, target_id, mode, created_at) \u2014 multiple runs over time on the
600
+ -- same target are kept as a history (newest-first browse). Token spend
601
+ -- is recorded for audit. NOT subject to the three-layer durability rule
602
+ -- because the source data (bug_pattern_clusters + member sessions) can
603
+ -- always re-derive the synthesis.
604
+ CREATE TABLE IF NOT EXISTS bug_synthesis_results (
605
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
606
+ scope TEXT NOT NULL CHECK(scope IN ('cluster', 'project')),
607
+ target_id TEXT NOT NULL,
608
+ mode TEXT NOT NULL CHECK(mode IN ('synopsis', 'priorities', 'root_cause')),
609
+ model TEXT NOT NULL,
610
+ output_markdown TEXT NOT NULL,
611
+ input_tokens INTEGER NOT NULL DEFAULT 0,
612
+ output_tokens INTEGER NOT NULL DEFAULT 0,
613
+ context_summary TEXT NOT NULL DEFAULT '{}',
614
+ created_at TEXT NOT NULL,
615
+ job_id TEXT
616
+ );
617
+ CREATE INDEX IF NOT EXISTS idx_synth_results_target
618
+ ON bug_synthesis_results(scope, target_id, created_at DESC);
619
+ CREATE INDEX IF NOT EXISTS idx_synth_results_created
620
+ ON bug_synthesis_results(created_at DESC);
621
+ `});import dp from"better-sqlite3";import*as Go from"sqlite-vec";function f(){if(ie)return ie;J(),ie=new dp(Sn),Go.load(ie),ie.pragma("cache_size = -64000"),ie.pragma("mmap_size = 268435456"),ie.pragma("temp_store = MEMORY"),ie.pragma("busy_timeout = 5000"),ie.pragma("journal_size_limit = 67108864"),ie.pragma("wal_autocheckpoint = 1000"),ie.exec(qo),Jo(ie);try{ie.exec("PRAGMA optimize")}catch{}return ie}var ie,H=we(()=>{"use strict";Z();Xo();ie=null});import{writeFileSync as wp}from"node:fs";import{join as Rp}from"node:path";function De(e){return e.trim().toLowerCase().replace(/^#+/,"").replace(/[\s/\\]+/g,"-").replace(/[^a-z0-9\-._]/g,"").slice(0,40)}function Je(e,t){let s=De(t);if(!s)throw new Error("tag must contain at least one alphanumeric character");let n=f(),r=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,r),n.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'add', ?)").run(e,s,r)})(),Qo(),{tag:s,added:!0})}function Zo(e,t){let s=De(t);if(!s)return{tag:"",removed:!1};let n=f(),r=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,r)})(),Qo(),{tag:s,removed:!0}):{tag:s,removed:!1}}function wt(e){return f().prepare("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(e).map(t=>t.tag)}function Xe(){return f().prepare(`SELECT tag, COUNT(*) AS count FROM session_tags
622
+ GROUP BY tag ORDER BY count DESC, tag ASC`).all()}function Qo(){try{J();let e=f(),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};wp(kp,JSON.stringify(n,null,2))}catch(e){console.error("[tags] backup failed:",e)}}var kp,Ge=we(()=>{"use strict";H();Z();kp=Rp($,"tags.json")});function Ap(e,t){let s=e.filter(o=>o.content_text&&o.content_text.trim().length>0);if(s.length<=t)return s;let n=new Set;n.add(0),n.add(s.length-1);let r=(s.length-2)/Math.max(1,t-2);for(let o=1;o<t-1;o++)n.add(Math.floor(o*r));return Array.from(n).sort((o,a)=>o-a).slice(0,t).map(o=>s[o])}function Ye(e){let t=f(),s={limit:e.limit??500},n=e.sessionIds&&e.sessionIds.length>0,r=n?"1=1":"s.message_count > 2";if(n){let a=e.sessionIds.map((c,d)=>`@sid_${d}`).join(", ");r+=` AND s.id IN (${a})`,e.sessionIds.forEach((c,d)=>{s[`sid_${d}`]=c})}return e.untaggedOnly&&(r+=" AND NOT EXISTS (SELECT 1 FROM session_tags st WHERE st.session_id = s.id)"),e.project&&(r+=" AND p.name = @project",s.project=e.project),e.collectionId&&(r+=" 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
+ NULLIF(sa.alias, '') AS alias,
624
+ COALESCE(s.first_user_message, '') AS first_user_message
625
+ FROM sessions s
626
+ JOIN projects p ON p.id = s.project_id
627
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
628
+ WHERE ${r}
629
+ ORDER BY COALESCE(s.started_at, '') DESC
630
+ LIMIT @limit`).all(s).map(a=>{let c=t.prepare(`SELECT role, COALESCE(content_text, '') AS content_text
631
+ FROM messages WHERE session_id = ?
632
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(a.id),u=Ap(c,5).map(g=>`${g.role}: ${g.content_text.slice(0,400)}`).join(`
633
+ ---
634
+ `);return{id:a.id,project:a.project,git_branch:a.git_branch,alias:a.alias,first_user_message:a.first_user_message,message_sample:u,current_tags:wt(a.id)}})}var rs=we(()=>{"use strict";H();Ge()});import{z as ue}from"zod";function kn(e){let t=e.minTags??2,s=e.maxTags??4,n=e.untaggedOnly??!e.sessionId,r=["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:"],o=[];return e.sessionId?(o.push("limit: 1"),r.push(` ${o.join(", ")}`),r.push(` Then match the session id ${e.sessionId} from the returned list.`)):(n&&o.push("untaggedOnly: true"),e.project&&o.push(`project: "${e.project}"`),e.collectionId&&o.push(`collectionId: "${e.collectionId}"`),o.push(`limit: ${e.limit??100}`),r.push(` ${o.join(", ")}`)),r.push(""),r.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:`),r.push(" - domain/subsystem (auth, db, frontend, billing, etc.)"),r.push(" - kind of work (bugfix, feature, refactor, research)"),r.push(" - prominent tools or libraries if relevant"),r.push(""),r.push("4. Call `apply_tags` once per session to write the tags."),r.push(""),r.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."),r.join(`
635
+ `)}function Op(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 Cp(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
+ `)}function vp(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
+ `)}function ei(e){return An.find(t=>t.name===e)}var Np,xp,Lp,Ip,jp,Mp,Dp,Fp,An,Nn=we(()=>{"use strict";Np={project:ue.string().optional().describe("Exact project name match (optional)."),collectionId:ue.string().optional().describe("Restrict to sessions in this collection (optional)."),sessionId:ue.string().optional().describe("Full session UUID to tag just one session (optional)."),untaggedOnly:ue.boolean().optional().describe("Skip sessions that already have any tag (default: true)."),limit:ue.number().int().min(1).max(500).optional().describe("Max sessions to process (default: 100)."),minTags:ue.number().int().min(1).max(10).optional().describe("Minimum tags per session (default: 2)."),maxTags:ue.number().int().min(1).max(10).optional().describe("Maximum tags per session (default: 4).")};xp={sessionId:ue.string().describe("Session UUID (or 8+-char prefix) to summarize."),mode:ue.enum(["brief","detailed"]).optional().describe("brief = 3-5 bullets; detailed = paragraph + bullets. Default: brief.")};Lp={sessionId:ue.string().describe("Session UUID (or 8+-char prefix) to extract decisions from.")};Ip={sessionId:ue.string().describe("Session UUID (or 8+-char prefix) to find similar sessions to."),limit:ue.number().int().min(1).max(20).optional().describe("How many similar sessions to surface (default: 5).")};jp={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:Np,build:kn,allowedTools:["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"]},Mp={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:xp,build:Op,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Dp={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:Lp,build:Cp,allowedTools:["mcp__recall__get_session","mcp__recall__context_for_session"]},Fp={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:Ip,build:vp,allowedTools:["mcp__recall__get_session","mcp__recall__search","mcp__recall__list_sessions","mcp__recall__list_tags"]},An=[jp,Mp,Dp,Fp]});function os(e,t){let s=Rt.get(e);if(!(!s||s.size===0))for(let n of s)try{n(t)}catch{}}function ti(e,t){let s=Rt.get(e);return s||(s=new Set,Rt.set(e,s)),s.add(t),()=>{let n=Rt.get(e);n&&(n.delete(t),n.size===0&&Rt.delete(e))}}var Rt,xn=we(()=>{"use strict";Rt=new Map});var Pe={};Oo(Pe,{buildScanPrompt:()=>si,isClaudeCliAvailable:()=>oe,runClaudeCliScan:()=>On,spawnClaudePrompt:()=>Fe});import{execFileSync as Pp,execSync as Up,spawn as $p}from"node:child_process";function Hp(){if(kt)return kt;try{kt=Up("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{kt="claude"}return kt}function oe(){try{return Pp("command",["-v","claude"],{stdio:"ignore"}),!0}catch{return!1}}function si(e){return kn({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 Wp(e,t){let s=t.get(e);return s||e.slice(0,8)}function qp(e){try{return Ye(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 Jp(e){let{scanId:t,total:s,labelTable:n}=e,r=new Set;return o=>{let a=o.trim();if(!a.startsWith("{"))return;let c;try{c=JSON.parse(a)}catch{return}if(!c||typeof c!="object")return;let d=c;if(!(d.type!=="assistant"||!d.message?.content))for(let u of d.message.content){if(u?.type!=="tool_use"||u.name!=="mcp__recall__apply_tags")continue;let g=u.input,h=typeof g?.sessionId=="string"?g.sessionId:null;!h||r.has(h)||(r.add(h),os(t,{type:"progress",current:r.size,total:s,sessionId:h,sessionLabel:Wp(h,n)}))}}}function Xp(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
639
+ `);for(;n!==-1;){let r=t.slice(0,n);t=t.slice(n+1),r.length>0&&e(r),n=t.indexOf(`
640
+ `)}}}async function On(e,t={},s){let n=!!t.scanId,r=n?qp(e):[],o=new Map(r.map(d=>[d.id,d.label])),a=r.length,c;return n&&t.scanId&&(c=Jp({scanId:t.scanId,total:a,labelTable:o})),ni({prompt:si(e),allowedTools:Bp.split(","),opts:t,onProgress:s,onStdoutLine:c,outputFormat:n?"stream-json":"json"})}async function Fe(e,t,s={},n){return ni({prompt:e,allowedTools:t,opts:s,onProgress:n,outputFormat:"json"})}function ni(e){let{prompt:t,allowedTools:s,opts:n,onProgress:r,onStdoutLine:o,outputFormat:a}=e,c=["-p",t,"--output-format",a,"--allowedTools",s.join(","),"--permission-mode","bypassPermissions"];return a==="stream-json"&&c.push("--verbose"),n.model&&c.push("--model",n.model),new Promise(d=>{let u=$p(Hp(),c,{stdio:["ignore","pipe","pipe"]}),g=[],h=[],b=o?Xp(o):void 0;u.stdout.on("data",y=>{g.push(y),b&&b(y)}),u.stderr.on("data",y=>{if(h.push(y),r){let k=y.toString("utf8").trim();k&&r(k)}});let S=setTimeout(()=>{u.kill("SIGKILL")},1800*1e3);u.on("close",y=>{clearTimeout(S),d({success:y===0,stdout:Buffer.concat(g).toString("utf8"),stderr:Buffer.concat(h).toString("utf8"),exitCode:y})}),u.on("error",y=>{clearTimeout(S),d({success:!1,stdout:"",stderr:String(y),exitCode:null})})})}var Bp,kt,ge=we(()=>{"use strict";rs();Nn();xn();Bp=["mcp__recall__list_tags","mcp__recall__list_sessions_to_tag","mcp__recall__apply_tags"].join(",")});var $c={};Oo($c,{EmbedderUnavailableError:()=>Ut,embed:()=>$t,embedQuery:()=>hr,getEmbedderStatus:()=>ke,loadEmbedder:()=>Ws,unloadEmbedder:()=>Yf});import{join as Pc}from"node:path";function Gf(){return Pc($,"models","bge-base-en-v1.5")}async function Ws(){if(Hs&&Pt)return;let e;try{e=await import("@huggingface/transformers")}catch(n){throw new Ut(n)}let{pipeline:t,env:s}=e;s.localModelPath=Pc($,"models"),s.allowRemoteModels=!1;try{Pt=await t("feature-extraction",Uc,{local_files_only:!0,cache_dir:Gf()}),Hs=!0}catch(n){throw n instanceof Error&&/Cannot find module|onnxruntime_binding/.test(n.message)?new Ut(n):n}}function ke(){return{loaded:Hs,modelId:Uc,dim:Jf}}async function $t(e){if(!Pt)throw new Error("[embedder] Model not loaded. Call loadEmbedder() before embedding.");let t=[];for(let s=0;s<e.length;s+=Fc){let n=e.slice(s,s+Fc),o=(await Pt(n,{pooling:"cls",normalize:!0})).tolist();for(let a=0;a<n.length;a++){let c=o[a],d=Array.isArray(c[0])?c[0]:c;t.push(new Float32Array(d))}}return t}async function hr(e){let t=Xf+e,[s]=await $t([t]);return s}function Yf(){Pt=null,Hs=!1}var Uc,Jf,Fc,Xf,Pt,Hs,Ut,ot=we(()=>{"use strict";Z();Uc="BAAI/bge-base-en-v1.5",Jf=768,Fc=16,Xf="Represent this sentence for searching relevant passages: ",Pt=null,Hs=!1;Ut=class extends Error{cause;constructor(t){let s=t instanceof Error?t.message:String(t);super(["Semantic search is unavailable on this platform.","",`Reason: ${s}`,"",`Platform: ${process.platform}/${process.arch}, Node ${process.version}`,"","Claude Recall supports macOS (arm64/x64), Linux (x64/arm64), and Windows (x64).","Core CLI features (search, list, context, daemon) work everywhere.","Only `recall semantic *` requires the on-device embedder.","","See: https://clauderecall.com/docs (Supported platforms) \u2014 or file an issue at","https://gitlab.com/clauderecallhq/clauderecallhq/-/issues with the platform line above."].join(`
641
+ `)),this.name="EmbedderUnavailableError",this.cause=t}}});import{Hono as Bb}from"hono";import{serve as Hb}from"@hono/node-server";Z();import{existsSync as Bu,readFileSync as Hu,writeFileSync as LT,unlinkSync as CT}from"node:fs";import{join as Wu}from"node:path";var Io=Wu($,"license.json");function St(){if(!Bu(Io))return null;try{let e=Hu(Io,"utf8"),t=JSON.parse(e);return typeof t.license_jwt!="string"||t.license_jwt.length===0?null:t}catch{return null}}import{jwtVerify as qu,importSPKI as Ju}from"jose";var vo=`-----BEGIN PUBLIC KEY-----
642
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZysO2FffTLdyxQnTmnt78/ayvqz9
643
+ kEgDDGWLdQN7jrx/W+Nxz9yOJbwTPDI4jv24IztWSEtJuqH+0KvrDbdfFA==
644
+ -----END PUBLIC KEY-----
645
+ `,Tn="ES256",jo="clauderecall.com",Mo="clauderecall-cli";var ss=null;async function Xu(){return ss||(ss=await Ju(vo,Tn),ss)}async function Do(e){try{let t=await Xu(),{payload:s}=await qu(e,t,{issuer:jo,audience:Mo,algorithms:[Tn]});return{valid:!0,claims:s}}catch(t){return{valid:!1,reason:t instanceof Error?t.message:"verification failed"}}}import{createHash as Gu}from"node:crypto";import{hostname as Yu,userInfo as Ku,platform as zu,arch as Vu}from"node:os";function Fo(){let e="unknown";try{e=Ku().username}catch{}let t=[Yu(),e,zu(),Vu()];return Gu("sha256").update(t.join("\0")).digest("hex")}Z();import{existsSync as Zu,readFileSync as Qu,writeFileSync as ep}from"node:fs";import{join as tp}from"node:path";function Po(){let e=process.env.RECALL_API_BASE;if(e&&e.length>0){let t=e.replace(/\/$/,""),s;try{s=new URL(t)}catch{throw new Error(`RECALL_API_BASE is not a valid URL: ${t}`)}let n=s.hostname==="127.0.0.1"||s.hostname==="localhost"||s.hostname==="::1";if(s.protocol==="https:"||s.protocol==="http:"&&n)return t;throw new Error(`RECALL_API_BASE must be HTTPS, or HTTP with loopback hostname. Got: ${t}`)}return"https://clauderecall.com"}var yn=tp($,"license-check.json"),sp=1440*60*1e3,np=720*60*60*1e3,rp=1e4;function Uo(){if(!Zu(yn))return null;try{let e=JSON.parse(Qu(yn,"utf8"));return typeof e.license_key!="string"||typeof e.last_checked_at!="string"||typeof e.revoked!="boolean"?null:e}catch{return null}}function op(e){J(),ep(yn,JSON.stringify(e,null,2)+`
646
+ `,{mode:384})}async function ip(e,t){let s=null,n=null;try{s=new AbortController,n=setTimeout(()=>s?.abort(),rp);let r=await fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({license_key:e}),signal:s.signal});if(!r.ok)return null;let o=await r.json();return typeof o?.revoked!="boolean"?null:o}catch{return null}finally{n&&clearTimeout(n)}}async function $o(e,t={}){let s=Uo(),n=t.apiUrl??`${Po()}/api/license/check`,r=s?.license_key===e,o=!s||!r||Date.now()-new Date(s.last_checked_at).getTime()>=sp;if(!t.force&&!o)return s;let a=await ip(e,n);if(!a)return r?s:null;let c={license_key:e,last_checked_at:new Date().toISOString(),revoked:a.revoked,reason:a.reason??null};return op(c),c}function Bo(e){let t=Uo();return!t||t.license_key!==e?null:t.revoked?{revoked:!0,reason:t.reason?`license revoked: ${t.reason}`:"license revoked by issuer"}:Date.now()-new Date(t.last_checked_at).getTime()>np?{revoked:!0,reason:"license has not been validated with the server in 30+ days. Reconnect to the internet and run `recall license check`"}:null}var ap=Date.UTC(2026,5,1,7,0,0);var cp=1440*60*1e3,zT=60*cp;async function Tt(){let e=St();if(!e)return{tier:"free"};let t=await Do(e.license_jwt);if(!t.valid||!t.claims)return{tier:"free",invalid_reason:t.reason};if(t.claims.machine_fp&&t.claims.machine_fp!==Fo())return{tier:"free",invalid_reason:"machine fingerprint mismatch \u2014 re-activate on this device"};let s=Bo(e.license_key);return s?.revoked?{tier:"free",invalid_reason:s.reason}:lp(e,t.claims)}async function Ho(e){let t=St();if(!t)return{ran:!1,revoked:!1,reason:null,last_checked_at:null};let s=await $o(t.license_key,{force:e?.force??!1});return s?{ran:!0,revoked:s.revoked,reason:s.reason,last_checked_at:s.last_checked_at}:{ran:!0,revoked:!1,reason:null,last_checked_at:null}}function lp(e,t){let s=t.test_mode===!0&&process.env.NODE_ENV==="production";return{tier:s?"free":"pro",key_short:e.key_short,customer_email:e.customer_email,activated_at:e.activated_at,test_mode:e.test_mode,...s?{test_mode_blocked:!0}:{},expires_at:typeof t.exp=="number"?new Date(t.exp*1e3).toISOString():null}}async function Wo(){return(await Tt()).tier==="pro"}H();import{serveStatic as su}from"@hono/node-server/serve-static";import{existsSync as Wb,readFileSync as ro}from"node:fs";import{stat as qb,readFile as Jb,realpath as Xb}from"node:fs/promises";import{timingSafeEqual as Gb}from"node:crypto";import{homedir as Yb}from"node:os";import{dirname as au,join as _o}from"node:path";import{fileURLToPath as cu}from"node:url";import{existsSync as Ey,readFileSync as by,statSync as Sy,statfsSync as up}from"node:fs";import be from"chalk";import{formatDistanceToNowStrict as gy,parseISO as _y}from"date-fns";var Yo={dim:be.gray,bold:be.bold,project:be.cyan,user:be.blue,assistant:be.green,tool:be.magenta,warn:be.yellow,err:be.red,ok:be.green,accent:be.hex("#f97316")};H();Z();var pp=["VS Code","VS Code Insiders","Cursor","Windsurf","Warp","iTerm","Terminal","WezTerm","Windows Terminal","Kitty","Alacritty"],Ry=new RegExp(`^(${pp.map(e=>e.replace(/ /g,"\\s")).join("|")})\\s\xB7\\s`);var ky=5*6e4;function wn(){try{let e=up($);return Number(e.bavail)*Number(e.bsize)}catch{return 0}}function Ko(e){let{projects:t,sessions:s,messages:n,port:r,version:o}=e;return`<!DOCTYPE html>
647
+ <html lang="en">
648
+ <head>
649
+ <meta charset="utf-8" />
650
+ <title>Claude Recall \u2014 local</title>
651
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
652
+ <style>
653
+ :root {
654
+ --bg: #0b0c0f;
655
+ --fg: #e7e9ee;
656
+ --dim: #8b9098;
657
+ --accent: #f97316;
658
+ --card: #15171c;
659
+ --line: #22252d;
660
+ }
661
+ * { box-sizing: border-box; }
662
+ html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif; }
663
+ main { max-width: 760px; margin: 0 auto; padding: 6rem 2rem 4rem; }
664
+ h1 { font-size: 2.4rem; font-weight: 700; letter-spacing: -0.02em; margin: 0 0 0.4rem; }
665
+ .tag { color: var(--accent); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; font-size: 0.75rem; margin: 0 0 0.8rem; }
666
+ p { color: var(--dim); font-size: 1.05rem; line-height: 1.55; margin: 0 0 1.2rem; }
667
+ .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 2.5rem 0; }
668
+ .stat { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 1.25rem; }
669
+ .stat .n { font-size: 2rem; font-weight: 700; letter-spacing: -0.02em; }
670
+ .stat .k { color: var(--dim); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; margin-top: 0.25rem; }
671
+ code { background: var(--card); padding: 0.12em 0.42em; border-radius: 4px; border: 1px solid var(--line); font-size: 0.9em; }
672
+ footer { color: var(--dim); font-size: 0.8rem; margin-top: 4rem; border-top: 1px solid var(--line); padding-top: 1.5rem; }
673
+ a { color: var(--accent); text-decoration: none; }
674
+ a:hover { text-decoration: underline; }
675
+ .soon { display: inline-block; background: rgba(249, 115, 22, 0.12); color: var(--accent); border: 1px solid rgba(249, 115, 22, 0.3); border-radius: 999px; padding: 0.2rem 0.7rem; font-size: 0.75rem; font-weight: 600; margin-left: 0.5rem; }
676
+ </style>
677
+ </head>
678
+ <body>
679
+ <main>
680
+ <p class="tag">Claude Recall \xB7 local-only</p>
681
+ <h1>Your sessions are indexed.</h1>
682
+ <p>The full browser UI with projects tree, search, and transcript view ships in <strong>v0.3</strong>.<br />Everything is already running on <code>127.0.0.1:${r}</code> \u2014 nothing ever leaves your machine.</p>
683
+
684
+ <div class="grid">
685
+ <div class="stat"><div class="n">${t.toLocaleString()}</div><div class="k">Projects</div></div>
686
+ <div class="stat"><div class="n">${s.toLocaleString()}</div><div class="k">Sessions</div></div>
687
+ <div class="stat"><div class="n">${n.toLocaleString()}</div><div class="k">Messages</div></div>
688
+ </div>
689
+
690
+ <p>For now, keep driving from the terminal:</p>
691
+ <p><code>recall search "your query"</code> &nbsp; <code>recall list</code> &nbsp; <code>recall show &lt;id&gt;</code></p>
692
+
693
+ <p><strong>Up next</strong> <span class="soon">v0.3</span> three-pane React + Tailwind UI right here. <span class="soon">v0.4</span> context re-injection into new Claude sessions.</p>
694
+
695
+ <footer>
696
+ v${o} \xB7 daemon pid ${process.pid} \xB7 running on http://127.0.0.1:${r}<br />
697
+ data stays on your disk at <code>~/.recall/db.sqlite</code>
698
+ </footer>
699
+ </main>
700
+ </body>
701
+ </html>`}var mp=/<(local-command-caveat|local-command-stdout|command-name|command-message|command-args|system-reminder|user-prompt-submit-hook|task-notification)[\s\S]*?<\/\1>/gi,gp=/⚡ \*\*Tool call · `[^`]+`\*\*\s*\n+```json\n[\s\S]*?\n```/g,_p=/\*\*Tool result\*\*\s*\n+```\n[\s\S]*?\n```/g;function fp(e){return e.replace(mp,"").trim()}function hp(e){let t=e.replace(gp,"[tool call]");return t=t.replace(_p,"[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,`
702
+
703
+ `),t.trim()}function Ep(e){return e.role??e.type??"message"}function zo(e,t,s={}){let n=s.mode??"condensed",r=s.includeSidechain===!0,o=s.since?Date.parse(s.since):0,a=t.filter(g=>!(!r&&g.is_sidechain===1||o&&g.timestamp&&Date.parse(g.timestamp)<o)),c=[];s.prelude&&(c.push(s.prelude.trim()),c.push("")),c.push(`# Claude Recall, past session context (${n})`),c.push(""),c.push(`- **Project**: ${e.project_name}`),e.decoded_path&&c.push(`- **Path**: \`${e.decoded_path}\``),c.push(`- **Session ID**: \`${e.id}\``),e.started_at&&c.push(`- **Started**: ${e.started_at}`),e.ended_at&&c.push(`- **Ended**: ${e.ended_at}`),e.git_branch&&c.push(`- **Branch**: \`${e.git_branch}\``),c.push(`- **Messages**: ${a.length}`),c.push(""),c.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."),c.push(""),c.push("---"),c.push("");let d=0,u=0;for(let g of a){let h=g.content_text??"",b=fp(h);n==="condensed"&&(b=hp(b));let S=b.length>0,y=!!g.tool_names&&g.tool_names.length>0;if(!S&&!y){u+=1;continue}let k=Ep(g),w=g.timestamp?` \`${g.timestamp}\``:"";c.push(`## ${k}${w}`),c.push(""),y&&n==="condensed"&&(c.push(`_tools used: ${g.tool_names}_`),c.push("")),S&&(c.push(b),c.push("")),d+=1}return c.push("---"),c.push(""),c.push(`_${d} messages included_`+(u?`, ${u} empty skipped`:"")+(r?"":", subagent/sidechain hidden")+"."),c.join(`
704
+ `)}H();Z();import{writeFileSync as bp}from"node:fs";import{join as Sp}from"node:path";var Tp=Sp($,"aliases.json");function Rn(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function Se(e){return f().prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(e)?.alias??null}function yp(){return f().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:Rn(t.previous_aliases)}))}function me(e,t){let s=t.trim();if(!s)throw new Error("alias must be non-empty");let n=f(),r=new Date().toISOString(),o=n.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e),a=[];return o&&(a=Rn(o.previous_aliases),o.alias!==s&&a.push({alias:o.alias,replaced_at:r})),n.prepare(`INSERT INTO session_aliases (session_id, alias, updated_at, previous_aliases)
705
+ VALUES (?, ?, ?, ?)
706
+ ON CONFLICT(session_id) DO UPDATE SET
707
+ alias = excluded.alias,
708
+ updated_at = excluded.updated_at,
709
+ previous_aliases = excluded.previous_aliases`).run(e,s,r,JSON.stringify(a)),Vo(),{session_id:e,alias:s,updated_at:r,previous_aliases:a}}function ns(e){let t=f(),s=new Date().toISOString(),n=t.prepare("SELECT alias, previous_aliases FROM session_aliases WHERE session_id = ?").get(e);if(!n)return;let r=Rn(n.previous_aliases);r.push({alias:n.alias,replaced_at:s}),t.prepare(`UPDATE session_aliases SET alias = '', updated_at = ?, previous_aliases = ?
710
+ WHERE session_id = ?`).run(s,JSON.stringify(r),e),Vo()}function Vo(){try{J();let e=yp(),t={schema:"claude-recall.aliases.v1",backed_up_at:new Date().toISOString(),aliases:e};bp(Tp,JSON.stringify(t,null,2))}catch(e){console.error("[aliases] backup failed:",e)}}function yt(e){if(!e.sessionStartedAt)return{allowed:!1,reason:"missing-session-started-at"};if(!e.terminalOpenedAt)return{allowed:!1,reason:"missing-terminal-opened-at"};let t=Date.parse(e.sessionStartedAt),s=Date.parse(e.terminalOpenedAt);return Number.isFinite(t)?Number.isFinite(s)?s-t>6e4?{allowed:!1,reason:"terminal-postdates-session"}:{allowed:!0}:{allowed:!1,reason:"missing-terminal-opened-at"}:{allowed:!1,reason:"missing-session-started-at"}}H();Z();import{writeFileSync as Gp,mkdirSync as Yp,existsSync as Kp}from"node:fs";import{join as oi}from"node:path";var Ln=oi($,"notes"),ri=200,zp=12e3,Vp=800,Zp=8e3;function ii(e){try{let t=JSON.parse(e);if(Array.isArray(t))return t}catch{}return[]}function ai(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 Qp(){J(),Kp(Ln)||Yp(Ln,{recursive:!0})}function em(e){return{session_id:e.session_id,content:e.content,updated_at:e.updated_at,previous_versions:ii(e.previous_versions),auto_synopsis:e.auto_synopsis??null,auto_synopsis_generated_at:e.auto_synopsis_generated_at??null,auto_synopsis_history:ai(e.auto_synopsis_history)}}var tm="session_id, content, updated_at, previous_versions, auto_synopsis, auto_synopsis_generated_at, auto_synopsis_history";function is(e){let t=f().prepare(`SELECT ${tm} FROM session_notes WHERE session_id = ?`).get(e);return t?em(t):null}function ci(e,t){let s=f(),n=new Date().toISOString(),r=s.prepare("SELECT content, previous_versions FROM session_notes WHERE session_id = ?").get(e),o=[];return r&&(o=ii(r.previous_versions),r.content!==t&&r.content.length>0&&o.push({content:r.content,replaced_at:n})),s.prepare(`INSERT INTO session_notes (session_id, content, updated_at, previous_versions)
711
+ VALUES (?, ?, ?, ?)
712
+ ON CONFLICT(session_id) DO UPDATE SET
713
+ content = excluded.content,
714
+ updated_at = excluded.updated_at,
715
+ previous_versions = excluded.previous_versions`).run(e,t,n,JSON.stringify(o)),nm(e,t,n),is(e)??{session_id:e,content:t,updated_at:n,previous_versions:o,auto_synopsis:null,auto_synopsis_generated_at:null,auto_synopsis_history:[]}}async function li(e){let s=f().prepare(`SELECT rowid AS rid, role, content_text
716
+ FROM messages
717
+ WHERE session_id = ? AND is_sidechain = 0
718
+ AND content_text IS NOT NULL AND content_text != ''
719
+ AND role IN ('user', 'assistant')
720
+ ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
721
+ LIMIT ?`).all(e,ri);if(s.length===0)throw new Error("no messages available to summarise");let n=zp,r=[];for(let b of s){if(n<=0)break;let S=(b.content_text??"").slice(0,Vp);r.push({rid:b.rid,role:b.role,content:S}),n-=S.length}r.reverse();let o=s.length===ri||n<=0,a=r.map(b=>`**${b.role}**: ${b.content}`).join(`
722
+
723
+ `),c=["You will receive a sampled chronological transcript from a Claude Code session.",o?"The sample is the most recent slice that fits in the context budget; older messages were dropped.":"The full session is included.","","Write a clean markdown synopsis of the session. Use these sections \u2014 omit any that genuinely have nothing to say:","","## Goal","One sentence \u2014 what the user was trying to accomplish.","","## What was done","Bullet list \u2014 concrete actions, code changes, decisions. Skip pleasantries.","","## Key decisions","Bullet list \u2014 non-obvious choices and the reason behind them.","","## Files touched","Bullet list \u2014 file paths mentioned in the conversation. Omit the section if none.","","## Open follow-ups","Bullet list \u2014 anything left undone or flagged for later. Omit the section if none.","","Output ONLY the markdown \u2014 no surrounding prose, no code fences around the whole thing, no closing summary.","","---","",a].join(`
724
+ `),{spawnClaudePrompt:d,isClaudeCliAvailable:u}=await Promise.resolve().then(()=>(ge(),Pe));if(!u())throw new Error("claude CLI not found on PATH");let g=await d(c,[],{});if(!g.success)throw new Error(`claude CLI exited ${g.exitCode}: ${g.stderr.slice(-500)}`);let h=sm(g.stdout);if(!h)throw new Error("claude CLI returned an empty synopsis");return h.slice(0,Zp)}function sm(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 n.trim()}}catch{}return t}function di(e,t){let s=f(),n=new Date().toISOString(),r=Date.now(),o=s.prepare("SELECT auto_synopsis, auto_synopsis_history, content, updated_at FROM session_notes WHERE session_id = ?").get(e),a=ai(o?.auto_synopsis_history??null);return o?.auto_synopsis&&o.auto_synopsis!==t&&o.auto_synopsis.length>0&&a.push({synopsis:o.auto_synopsis,replaced_at:n}),o?s.prepare(`UPDATE session_notes
725
+ SET auto_synopsis = ?,
726
+ auto_synopsis_generated_at = ?,
727
+ auto_synopsis_history = ?
728
+ WHERE session_id = ?`).run(t,r,JSON.stringify(a),e):s.prepare(`INSERT INTO session_notes
729
+ (session_id, content, updated_at, previous_versions, auto_synopsis,
730
+ auto_synopsis_generated_at, auto_synopsis_history)
731
+ VALUES (?, '', ?, '[]', ?, ?, ?)`).run(e,n,t,r,JSON.stringify(a)),is(e)}function nm(e,t,s){try{Qp();let n=oi(Ln,`${e}.md`),r=`<!-- Claude Recall note \xB7 session ${e} \xB7 updated ${s} -->
732
+ `;Gp(n,r+t)}catch(n){console.error("[notes] mirror write failed:",n)}}Ge();H();Z();import{randomUUID as rm}from"node:crypto";import{writeFileSync as om,readFileSync as nw,existsSync as rw}from"node:fs";import{join as im}from"node:path";var am=im($,"collections.json"),as=8;function cs(e){return{...e}}function _e(e,t,s,n=null,r=new Date().toISOString()){f().prepare(`INSERT INTO collection_events (collection_id, session_id, action, payload, at)
733
+ VALUES (?, ?, ?, ?, ?)`).run(e,n,t,s?JSON.stringify(s):null,r)}function ls(e){let t=f().prepare("SELECT * FROM collections WHERE id = ?").get(e);if(!t)throw new Error(`collection not found: ${e}`);return t}function ui(e){if(!e)return 0;let t=0,s=e,n=new Set,r=f();for(;s;){if(n.has(s))throw new Error("collection cycle detected");n.add(s);let o=r.prepare("SELECT parent_id FROM collections WHERE id = ?").get(s);if(!o)break;t+=1,s=o.parent_id}return t}function cm(e,t){let s=f(),n=e,r=new Set;for(;n;){if(r.has(n))return!1;if(r.add(n),n===t)return!0;let o=s.prepare("SELECT parent_id FROM collections WHERE id = ?").get(n);if(!o)return!1;n=o.parent_id}return!1}function pi(e=!1){return f().prepare(`SELECT c.*,
734
+ (SELECT COUNT(*) FROM collection_sessions cs WHERE cs.collection_id = c.id) AS session_count
735
+ FROM collections c
736
+ ${e?"":"WHERE c.archived_at IS NULL"}
737
+ ORDER BY c.parent_id IS NOT NULL, c.parent_id, c.sort_key, LOWER(c.name)`).all().map(n=>({...cs(n),session_count:n.session_count}))}function xe(e){let t=f().prepare("SELECT * FROM collections WHERE id = ?").get(e);return t?cs(t):null}function mi(e,t=!0){let s=f(),n=t?Cn(e):[e];if(n.length===0)return[];let r=n.map(()=>"?").join(",");return s.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
738
+ FROM collection_sessions
739
+ WHERE collection_id IN (${r})
740
+ ORDER BY added_at DESC`).all(...n)}function Cn(e){let t=f(),s=[e],n=[e],r=new Set([e]);for(;n.length>0;){let o=n.map(()=>"?").join(","),a=t.prepare(`SELECT id FROM collections WHERE parent_id IN (${o})`).all(...n),c=[];for(let d of a)r.has(d.id)||(r.add(d.id),s.push(d.id),c.push(d.id));n=c}return s}function gi(e){return f().prepare(`SELECT c.* FROM collections c
741
+ JOIN collection_sessions cs ON cs.collection_id = c.id
742
+ WHERE cs.session_id = ? AND c.archived_at IS NULL
743
+ ORDER BY LOWER(c.name)`).all(e)}function At(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=f(),n=new Date().toISOString(),r=rm();if(e.parent_id){if(!xe(e.parent_id))throw new Error("parent collection not found");if(ui(e.parent_id)>=as-1)throw new Error(`max collection depth is ${as}`)}return s.transaction(()=>{s.prepare(`INSERT INTO collections
744
+ (id, name, description, icon, color, parent_id, sort_key, created_at, updated_at, archived_at)
745
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(r,t,e.description??null,e.icon??null,e.color??null,e.parent_id??null,e.sort_key??"",n,n),_e(r,"create",{name:t,parent_id:e.parent_id??null,icon:e.icon??null,color:e.color??null},null,n)})(),Ue(),xe(r)}function _i(e,t){let s=f(),n=ls(e),r=new Date().toISOString(),o={name:t.name!==void 0?t.name.trim():n.name,description:t.description!==void 0?t.description:n.description,icon:t.icon!==void 0?t.icon:n.icon,color:t.color!==void 0?t.color:n.color,parent_id:t.parent_id!==void 0?t.parent_id:n.parent_id,sort_key:t.sort_key!==void 0?t.sort_key:n.sort_key};if(!o.name)throw new Error("name required");if(o.name.length>120)throw new Error("name too long (max 120 chars)");if(t.parent_id!==void 0&&t.parent_id!==n.parent_id&&t.parent_id){if(t.parent_id===e)throw new Error("cannot set parent to self");if(!xe(t.parent_id))throw new Error("parent collection not found");if(cm(t.parent_id,e))throw new Error("cannot move collection into one of its descendants");if(ui(t.parent_id)>=as-1)throw new Error(`max collection depth is ${as}`)}return s.transaction(()=>{s.prepare(`UPDATE collections
746
+ SET name = ?, description = ?, icon = ?, color = ?,
747
+ parent_id = ?, sort_key = ?, updated_at = ?
748
+ WHERE id = ?`).run(o.name,o.description,o.icon,o.color,o.parent_id,o.sort_key,r,e),t.name!==void 0&&t.name!==n.name&&_e(e,"rename",{from:n.name,to:o.name},null,r),t.description!==void 0&&t.description!==n.description&&_e(e,"describe",{description:o.description},null,r),(t.icon!==void 0&&t.icon!==n.icon||t.color!==void 0&&t.color!==n.color)&&_e(e,"recolor",{icon:o.icon,color:o.color},null,r),t.parent_id!==void 0&&t.parent_id!==n.parent_id&&_e(e,"move",{from:n.parent_id,to:o.parent_id},null,r),t.sort_key!==void 0&&t.sort_key!==n.sort_key&&_e(e,"reorder",{from:n.sort_key,to:o.sort_key},null,r)})(),Ue(),xe(e)}function fi(e){let t=f(),s=ls(e);if(s.archived_at)return cs(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = ?, updated_at = ? WHERE id = ?").run(n,n,e),_e(e,"archive",{name:s.name},null,n)})(),Ue(),xe(e)}function hi(e){let t=f(),s=ls(e);if(!s.archived_at)return cs(s);let n=new Date().toISOString();return t.transaction(()=>{t.prepare("UPDATE collections SET archived_at = NULL, updated_at = ? WHERE id = ?").run(n,e),_e(e,"restore",{name:s.name},null,n)})(),Ue(),xe(e)}function Nt(e,t,s=null,n={}){let r=f();if(ls(e),!r.prepare("SELECT 1 FROM sessions WHERE id = ?").get(t))throw new Error(`session not found: ${t}`);if(r.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{added:!1};let c=n.source??"manual",d=n.rule_id??null;if(c==="auto"&&!d)throw new Error("auto membership requires a rule_id");let u=new Date().toISOString();return r.transaction(()=>{r.prepare(`INSERT INTO collection_sessions (collection_id, session_id, added_at, note, source, rule_id)
749
+ VALUES (?, ?, ?, ?, ?, ?)`).run(e,t,u,s,c,d),_e(e,"add",{note:s,source:c,rule_id:d},t,u)})(),Ue(),{added:!0}}function Ei(e,t){let s=f();if(!s.prepare("SELECT 1 FROM collection_sessions WHERE collection_id = ? AND session_id = ?").get(e,t))return{removed:!1};let r=new Date().toISOString();return s.transaction(()=>{s.prepare("DELETE FROM collection_sessions WHERE collection_id = ? AND session_id = ?").run(e,t),_e(e,"remove",null,t,r)})(),Ue(),{removed:!0}}function ds(e){let t=f(),s=t.prepare(`SELECT collection_id, session_id FROM collection_sessions
750
+ WHERE rule_id = ?`).all(e);if(s.length===0)return{removed:0};let n=new Date().toISOString();return t.transaction(()=>{t.prepare("DELETE FROM collection_sessions WHERE rule_id = ?").run(e);for(let o of s)_e(o.collection_id,"remove",{rule_id:e},o.session_id,n)})(),Ue(),{removed:s.length}}function lm(){return f().prepare(`SELECT id, collection_id, session_id, action, payload, at
751
+ FROM collection_events
752
+ ORDER BY at ASC, id ASC`).all()}function Ue(){try{J();let e=f(),t=e.prepare(`SELECT id, name, description, icon, color, parent_id, sort_key,
753
+ created_at, updated_at, archived_at
754
+ FROM collections
755
+ ORDER BY COALESCE(parent_id, ''), sort_key, LOWER(name)`).all(),s=e.prepare(`SELECT collection_id, session_id, added_at, note, source, rule_id
756
+ FROM collection_sessions
757
+ ORDER BY collection_id, added_at`).all(),n=lm(),r={schema:"claude-recall.collections.v1",backed_up_at:new Date().toISOString(),collections:t,memberships:s,events:n};om(am,JSON.stringify(r,null,2))}catch(e){console.error("[collections] backup failed:",e)}}H();Z();import{randomUUID as jn}from"node:crypto";import{existsSync as dm,mkdirSync as um,writeFileSync as bi}from"node:fs";import{homedir as pm}from"node:os";import{basename as mm,join as Mn}from"node:path";var us=Mn($,"auto-rules"),gm=Mn(us,"rules.json"),_m=Mn(us,"suggestions.json"),In="Repositories",fm="Topics",Si=3;var hm=5,Em=2,bm=[/\bROADMAP\.md\b/g,/\bPROJECT\.md\b/g,/\bdocs\/[A-Za-z0-9._-]+\.md\b/g,/\.planning\/[A-Za-z0-9._\-/]+/g];function Dn(e){return{id:e.id,name:e.name,type:e.type,pattern:e.pattern,collection_id:e.collection_id,priority:e.priority,enabled:e.enabled!==0,created_at:e.created_at,created_by:e.created_by}}function Sm(e){return{id:e.id,type:e.type,pattern:e.pattern,suggested_name:e.suggested_name,suggested_parent_collection_id:e.suggested_parent_collection_id,session_count:e.session_count,detected_at:e.detected_at,dismissed:e.dismissed!==0}}function Tm(e){switch(e){case"cwd-prefix":case"project-id":case"git-branch-prefix":return In;case"tag":return fm;case"plan-file":return null}}function ym(e){let s=f().prepare("SELECT id FROM collections WHERE name = ? AND parent_id IS NULL AND archived_at IS NULL").get(e);if(s)return s.id;let o=At({name:e,icon:e===In?"\u{1F4E6}":"\u{1F3F7}",sort_key:e===In?"0000-repos":"0001-topics"});return f().prepare(`INSERT INTO auto_collection_rules
758
+ (id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
759
+ VALUES (?, ?, 'cwd-prefix', '__seed__', ?, 1000, 0, ?, 'seed')`).run(jn(),`seed:${e}`,o.id,new Date().toISOString()),o.id}function wm(e,t,s){let n;if(s!==void 0)n=s;else{let o=Tm(t);n=o?ym(o):null}return At({name:e,parent_id:n}).id}function ps(e){let t=f().prepare("SELECT * FROM auto_collection_rules WHERE id = ?").get(e);return t?Dn(t):null}function Rm(e){let t=f();switch(e.type){case"cwd-prefix":return t.prepare("SELECT id FROM sessions WHERE cwd IS NOT NULL AND cwd LIKE ? ESCAPE '\\'").all(ms(e.pattern)+"%").map(n=>n.id);case"git-branch-prefix":return t.prepare("SELECT id FROM sessions WHERE git_branch IS NOT NULL AND git_branch LIKE ? ESCAPE '\\'").all(ms(e.pattern)+"%").map(n=>n.id);case"project-id":{let s=Number(e.pattern);return Number.isFinite(s)?t.prepare("SELECT id FROM sessions WHERE project_id = ?").all(s).map(r=>r.id):[]}case"tag":return t.prepare("SELECT session_id FROM session_tags WHERE tag = ?").all(e.pattern).map(n=>n.session_id);case"plan-file":return t.prepare(`SELECT id, first_user_message FROM sessions
760
+ WHERE first_user_message IS NOT NULL AND first_user_message LIKE ?`).all("%"+e.pattern+"%").map(n=>n.id)}}function Ti(e,t,s=3){let n=f(),r=`s.id AS id, s.cwd AS cwd, s.started_at AS started_at,
761
+ sa.alias AS alias, s.auto_title AS auto_title, s.first_user_message AS first_user_message`,o="LEFT JOIN session_aliases sa ON sa.session_id = s.id",a=[];switch(e){case"cwd-prefix":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
762
+ WHERE s.cwd IS NOT NULL AND s.cwd LIKE ? ESCAPE '\\'
763
+ ORDER BY s.started_at DESC LIMIT ?`).all(ms(t)+"%",s);break;case"git-branch-prefix":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
764
+ WHERE s.git_branch IS NOT NULL AND s.git_branch LIKE ? ESCAPE '\\'
765
+ ORDER BY s.started_at DESC LIMIT ?`).all(ms(t)+"%",s);break;case"project-id":{let c=Number(t);Number.isFinite(c)&&(a=n.prepare(`SELECT ${r} FROM sessions s ${o}
766
+ WHERE s.project_id = ?
767
+ ORDER BY s.started_at DESC LIMIT ?`).all(c,s));break}case"tag":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
768
+ JOIN session_tags st ON st.session_id = s.id
769
+ WHERE st.tag = ?
770
+ ORDER BY s.started_at DESC LIMIT ?`).all(t,s);break;case"plan-file":a=n.prepare(`SELECT ${r} FROM sessions s ${o}
771
+ WHERE s.first_user_message IS NOT NULL
772
+ AND s.first_user_message LIKE ?
773
+ ORDER BY s.started_at DESC LIMIT ?`).all("%"+t+"%",s);break}return a.map(c=>({id:c.id,title:km(c),cwd:c.cwd,started_at:c.started_at}))}function km(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`session ${e.id.slice(0,8)}`;let s=t.split(`
774
+ `)[0].trim();return s.length>80?s.slice(0,80)+"\u2026":s}function Am(e,t,s=f()){switch(e.type){case"cwd-prefix":return!!t.cwd&&t.cwd.startsWith(e.pattern);case"git-branch-prefix":return!!t.git_branch&&t.git_branch.startsWith(e.pattern);case"project-id":{let n=Number(e.pattern);return Number.isFinite(n)&&t.project_id===n}case"tag":return!!s.prepare("SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?").get(t.id,e.pattern);case"plan-file":return!!t.first_user_message&&t.first_user_message.includes(e.pattern)}}function yi(e){let t=f(),s=t.prepare("SELECT id, project_id, cwd, git_branch, first_user_message FROM sessions WHERE id = ?").get(e);if(!s)return{added:0};let n=t.prepare(`SELECT * FROM auto_collection_rules
775
+ WHERE enabled = 1 AND created_by != 'seed'
776
+ ORDER BY priority, created_at`).all(),r=0;for(let o of n){let a=Dn(o);if(Am(a,s,t))try{Nt(a.collection_id,e,null,{source:"auto",rule_id:a.id}).added&&(r+=1)}catch(c){console.error(`[auto-collections] failed to apply rule ${a.id} to session ${e}:`,c)}}return{added:r}}function vn(e){if(!e.enabled)return{added:0};let t=0;for(let s of Rm(e))try{Nt(e.collection_id,s,null,{source:"auto",rule_id:e.id}).added&&(t+=1)}catch(n){console.error(`[auto-collections] backfill failed for rule ${e.id} / session ${s}:`,n)}return{added:t}}function Fn(e){let t=(e.name??"").trim();if(!t)throw new Error("name required");let s=(e.pattern??"").trim();if(!s)throw new Error("pattern required");let n=f(),r=new Date().toISOString(),o=jn(),a=e.collection_id;a||(a=wm(t,e.type,e.parent_collection_id)),n.prepare(`INSERT INTO auto_collection_rules
777
+ (id, name, type, pattern, collection_id, priority, enabled, created_at, created_by)
778
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(o,t,e.type,s,a,e.priority??100,e.enabled===!1?0:1,r,e.created_by??"user"),n.prepare("DELETE FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").run(e.type,s);let c=ps(o);return vn(c),Ke(),c}function wi(e={}){let t=e.includeSeed?"SELECT * FROM auto_collection_rules ORDER BY priority, created_at":"SELECT * FROM auto_collection_rules WHERE created_by != 'seed' ORDER BY priority, created_at";return f().prepare(t).all().map(Dn)}function Ri(e,t){let s=f(),n=ps(e);if(!n)throw new Error(`rule not found: ${e}`);let r={name:t.name!==void 0?t.name.trim():n.name,pattern:t.pattern!==void 0?t.pattern.trim():n.pattern,enabled:t.enabled!==void 0?t.enabled:n.enabled,priority:t.priority!==void 0?t.priority:n.priority};if(!r.name)throw new Error("name required");if(!r.pattern)throw new Error("pattern required");s.prepare(`UPDATE auto_collection_rules
779
+ SET name = ?, pattern = ?, enabled = ?, priority = ?
780
+ WHERE id = ?`).run(r.name,r.pattern,r.enabled?1:0,r.priority,e);let o=ps(e);return t.pattern!==void 0&&t.pattern!==n.pattern?(ds(e),o.enabled&&vn(o)):t.enabled!==void 0&&t.enabled!==n.enabled&&(o.enabled?vn(o):ds(e)),Ke(),o}function ki(e){let t=f();if(!ps(e))return{removed:0};let n=ds(e);return t.prepare("DELETE FROM auto_collection_rules WHERE id = ?").run(e),Ke(),n}function gs(e={}){let t=e.includeDismissed?"SELECT * FROM auto_collection_suggestions ORDER BY detected_at DESC":"SELECT * FROM auto_collection_suggestions WHERE dismissed = 0 ORDER BY detected_at DESC";return f().prepare(t).all().map(Sm)}function Ai(e){f().prepare("UPDATE auto_collection_suggestions SET dismissed = 1 WHERE id = ?").run(e),Ke()}function Ni(e){let t=f(),s=t.prepare("SELECT * FROM auto_collection_suggestions WHERE id = ?").get(e);if(!s)throw new Error(`suggestion not found: ${e}`);if(s.dismissed)throw new Error(`suggestion already dismissed: ${e}`);let n=Fn({name:s.suggested_name,type:s.type,pattern:s.pattern,parent_collection_id:s.suggested_parent_collection_id===null?void 0:s.suggested_parent_collection_id,created_by:"suggestion-accepted"});return t.prepare("DELETE FROM auto_collection_suggestions WHERE id = ?").run(e),Ke(),n}function _s(){let e=f(),t=new Date().toISOString(),s=pm(),n=new Set(e.prepare("SELECT decoded_path FROM projects").all().map(c=>c.decoded_path)),r=[...Nm(s,t).filter(c=>!n.has(c.pattern)),...xm(t),...Om(t)];if(e.prepare("DELETE FROM auto_collection_suggestions WHERE type = 'project-id'").run(),n.size>0){let c=Array.from(n).map(()=>"?").join(",");e.prepare(`DELETE FROM auto_collection_suggestions WHERE type = 'cwd-prefix' AND pattern IN (${c})`).run(...Array.from(n))}let o=e.prepare("SELECT type, pattern FROM auto_collection_rules").all(),a=new Set(o.map(c=>`${c.type}:${c.pattern}`));for(let c of r){let d=`${c.type}:${c.pattern}`;if(a.has(d))continue;let u=e.prepare("SELECT id FROM auto_collection_suggestions WHERE type = ? AND pattern = ?").get(c.type,c.pattern);u?e.prepare(`UPDATE auto_collection_suggestions
781
+ SET session_count = ?, detected_at = ?, suggested_name = ?, suggested_parent_collection_id = ?
782
+ WHERE id = ?`).run(c.session_count,t,c.suggested_name,c.suggested_parent_collection_id,u.id):e.prepare(`INSERT INTO auto_collection_suggestions
783
+ (id, type, pattern, suggested_name, suggested_parent_collection_id, session_count, detected_at, dismissed)
784
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0)`).run(jn(),c.type,c.pattern,c.suggested_name,c.suggested_parent_collection_id,c.session_count,t)}return Ke(),gs()}function Nm(e,t){let n=f().prepare("SELECT id, cwd FROM sessions WHERE cwd IS NOT NULL AND cwd != ''").all(),r=new Map;for(let a of n){let c=a.cwd.split("/").filter(Boolean),d="";for(let u of c){if(d=`${d}/${u}`,d===e||d==="/")continue;let g=r.get(d);g||(g=new Set,r.set(d,g)),g.add(a.id)}}let o=[];for(let[a,c]of r.entries()){if(c.size<Si)continue;let d=!1;for(let[u,g]of r.entries())if(u!==a&&u.startsWith(a+"/")&&g.size>=Si){d=!0;break}d||o.push({type:"cwd-prefix",pattern:a,suggested_name:mm(a)||a,suggested_parent_collection_id:null,session_count:c.size,detected_at:t,dismissed:!1})}return o}function xm(e){return f().prepare("SELECT tag, COUNT(*) AS n FROM session_tags GROUP BY tag HAVING n >= ?").all(hm).map(s=>({type:"tag",pattern:s.tag,suggested_name:s.tag,suggested_parent_collection_id:null,session_count:s.n,detected_at:e,dismissed:!1}))}function Om(e){let t=f().prepare(`SELECT id, first_user_message FROM sessions
785
+ WHERE first_user_message IS NOT NULL AND first_user_message != ''`).all(),s=new Map;for(let r of t)for(let o of bm){o.lastIndex=0;let a=r.first_user_message.match(o);if(a)for(let c of a){let d=s.get(c);d||(d=new Set,s.set(c,d)),d.add(r.id)}}let n=[];for(let[r,o]of s.entries())o.size<Em||n.push({type:"plan-file",pattern:r,suggested_name:r,suggested_parent_collection_id:null,session_count:o.size,detected_at:e,dismissed:!1});return n}function ms(e){return e.replace(/[\\%_]/g,"\\$&")}function Ke(){try{J(),dm(us)||um(us,{recursive:!0});let e=f(),t=e.prepare("SELECT * FROM auto_collection_rules ORDER BY created_at, id").all(),s=e.prepare("SELECT * FROM auto_collection_suggestions ORDER BY detected_at DESC, id").all();bi(gm,JSON.stringify({schema:"claude-recall.auto-rules.v1",backed_up_at:new Date().toISOString(),rules:t},null,2)),bi(_m,JSON.stringify({schema:"claude-recall.auto-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:s},null,2))}catch(e){console.error("[auto-collections] backup failed:",e)}}function xi(){let e=f().prepare("SELECT DISTINCT collection_id FROM auto_collection_rules").all();return new Set(e.map(t=>t.collection_id))}H();Z();import{randomUUID as Oi}from"node:crypto";import{writeFileSync as Li,readFileSync as bw,existsSync as Lm,mkdirSync as Cm}from"node:fs";import{join as Pn}from"node:path";var fs=Pn($,"threads"),Im=Pn(fs,"index.json");function Ci(){J(),Lm(fs)||Cm(fs,{recursive:!0})}function Un(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 Ii(e){let t=new Map;if(e.length===0)return t;let s=f(),n=e.map(()=>"?").join(","),r=s.prepare(`SELECT te.thread_id AS thread_id,
786
+ p.name AS project,
787
+ COUNT(*) AS n,
788
+ SUM(CASE WHEN te.role = 'origin' THEN 1 ELSE 0 END) AS origin_n
789
+ FROM thread_edges te
790
+ LEFT JOIN sessions s ON s.id = te.session_id
791
+ LEFT JOIN projects p ON p.id = s.project_id
792
+ WHERE te.thread_id IN (${n})
793
+ GROUP BY te.thread_id, p.name`).all(...e),o=new Map;for(let a of r){let c=o.get(a.thread_id);c||(c=[],o.set(a.thread_id,c)),c.push(a)}for(let[a,c]of o){let d=c.filter(h=>h.project!==null),u=d.length,g=null;d.length>0&&(g=[...d].sort((b,S)=>S.n-b.n||S.origin_n-b.origin_n||(b.project??"").localeCompare(S.project??""))[0].project),t.set(a,{project:g,project_count:u})}return t}function vi(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 ji(e){let s=f().prepare(`SELECT NULLIF(sa.alias, '') AS alias,
794
+ s.auto_title AS auto_title,
795
+ s.auto_title_source AS auto_title_source,
796
+ s.first_user_message AS first_user_message,
797
+ p.name AS project
798
+ FROM (SELECT ? AS sid) q
799
+ LEFT JOIN sessions s ON s.id = q.sid
800
+ LEFT JOIN session_aliases sa ON sa.session_id = q.sid
801
+ 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 $n(e){let s=f().prepare(`SELECT
802
+ COUNT(*) AS session_count,
803
+ SUM(CASE WHEN role = 'origin' THEN 1 ELSE 0 END) AS origin_count
804
+ FROM thread_edges WHERE thread_id = ?`).get(e);return{session_count:s?.session_count??0,origin_count:s?.origin_count??0}}function fe(e){let t=te(e);t&&(Ci(),Li(Pn(fs,`${e}.json`),JSON.stringify(t,null,2)),Mi())}function Mi(){Ci();let e=Bn({includeArchived:!0});Li(Im,JSON.stringify({threads:e},null,2))}function hs(e){let t=e.name.trim();if(!t)throw new Error("thread name cannot be empty");let s=f(),n=Oi(),r=new Date().toISOString();s.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(n,t,e.summary?.trim()||null,r),e.originSessionId&&s.prepare(`INSERT INTO thread_edges (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
805
+ VALUES (?, ?, NULL, 'origin', 1.0, 'manual', ?)`).run(n,e.originSessionId,r),fe(n);let o=te(n);if(!o)throw new Error("thread creation succeeded but read-back failed");return o}function Bn(e={}){let t=f(),s=e.includeArchived?"":"WHERE archived = 0",n=t.prepare(`SELECT * FROM threads ${s} ORDER BY created_at DESC`).all(),r=Ii(n.map(o=>o.id));return n.map(o=>Un(o,$n(o.id),r.get(o.id)))}function te(e){let t=f(),s=t.prepare("SELECT * FROM threads WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT e.*,
806
+ NULLIF(sa.alias, '') AS alias,
807
+ s.auto_title AS auto_title,
808
+ s.auto_title_source AS auto_title_source,
809
+ s.first_user_message AS first_user_message,
810
+ p.name AS project
811
+ FROM thread_edges e
812
+ LEFT JOIN sessions s ON s.id = e.session_id
813
+ LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
814
+ LEFT JOIN projects p ON p.id = s.project_id
815
+ WHERE e.thread_id = ?
816
+ ORDER BY e.added_at ASC`).all(e).map(vi),r=Ii([e]).get(e);return{...Un(s,$n(s.id),r),edges:n}}function Di(e){return f().prepare(`SELECT t.* FROM threads t
817
+ JOIN thread_edges e ON e.thread_id = t.id
818
+ WHERE e.session_id = ? AND t.archived = 0
819
+ ORDER BY t.created_at DESC`).all(e).map(n=>Un(n,$n(n.id)))}function Es(e){let t=f();if(!t.prepare("SELECT * FROM threads WHERE id = ?").get(e.threadId))throw new Error(`thread ${e.threadId} not found`);let n=new Date().toISOString(),r=e.parentSessionId??null,o=e.role??(r?"child":"origin"),a=e.confidence??1,c=e.source??"manual";if(a<0||a>1)throw new Error("confidence must be 0..1");t.prepare(`INSERT INTO thread_edges
820
+ (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
821
+ VALUES (?, ?, ?, ?, ?, ?, ?)
822
+ ON CONFLICT(thread_id, session_id) DO UPDATE SET
823
+ parent_session_id = excluded.parent_session_id,
824
+ role = excluded.role,
825
+ confidence = excluded.confidence,
826
+ source = excluded.source,
827
+ added_at = excluded.added_at`).run(e.threadId,e.sessionId,r,o,a,c,n),fe(e.threadId);let d=ji(e.sessionId);return{thread_id:e.threadId,session_id:e.sessionId,parent_session_id:r,role:o,confidence:a,source:c,added_at:n,alias:d.alias,auto_title:d.auto_title,auto_title_source:d.auto_title_source,alias_source:d.alias?"manual":null,first_user_message:d.first_user_message,project:d.project}}function Fi(e,t){let n=f().prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e,t);return n.changes>0&&fe(e),{removed:n.changes}}function xt(e,t,s){let n=f(),r=n.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e,t);if(!r)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 c=n.prepare("SELECT parent_session_id FROM thread_edges WHERE thread_id = ? AND session_id = ?"),d=s,u=new Set;for(;d!==null;){if(d===t)throw new Error(`cycle detected: setting parent of ${t} to ${s} would create a loop`);if(u.has(d))break;u.add(d),d=c.get(e,d)?.parent_session_id??null}}let o=s?"child":"origin";n.prepare(`UPDATE thread_edges
828
+ SET parent_session_id = ?, role = ?, added_at = ?
829
+ WHERE thread_id = ? AND session_id = ?`).run(s,o,new Date().toISOString(),e,t),fe(e);let a=ji(t);return vi({...r,parent_session_id:s,role:o,added_at:new Date().toISOString(),alias:a.alias,auto_title:a.auto_title,auto_title_source:a.auto_title_source,first_user_message:a.first_user_message,project:a.project})}function Pi(e,t){let s=t.trim();if(!s)throw new Error("name cannot be empty");f().prepare("UPDATE threads SET name = ? WHERE id = ?").run(s,e),fe(e);let r=te(e);if(!r)throw new Error(`thread ${e} not found`);return r}function Ui(e){f().prepare("UPDATE threads SET closed_at = ? WHERE id = ?").run(new Date().toISOString(),e),fe(e);let s=te(e);if(!s)throw new Error(`thread ${e} not found`);return s}function $i(e){f().prepare("UPDATE threads SET closed_at = NULL WHERE id = ?").run(e),fe(e);let s=te(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Bi(e){f().prepare("UPDATE threads SET archived = 1 WHERE id = ?").run(e),fe(e);let s=te(e);if(!s)throw new Error(`thread ${e} not found`);return s}function Hi(e,t){if(e===t)throw new Error("cannot merge a thread into itself");let s=f(),n=new Date().toISOString();s.transaction(()=>{let o=s.prepare("SELECT * FROM thread_edges WHERE thread_id = ?").all(e);for(let a of o)s.prepare(`INSERT INTO thread_edges
830
+ (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
831
+ VALUES (?, ?, ?, ?, ?, ?, ?)
832
+ ON CONFLICT(thread_id, session_id) DO UPDATE SET
833
+ parent_session_id = COALESCE(thread_edges.parent_session_id, excluded.parent_session_id),
834
+ role = CASE WHEN thread_edges.role = 'origin' OR excluded.role = 'origin' THEN 'origin' ELSE 'child' END,
835
+ confidence = MAX(thread_edges.confidence, excluded.confidence),
836
+ source = thread_edges.source`).run(t,a.session_id,a.parent_session_id,a.role,a.confidence,a.source,n);s.prepare("DELETE FROM threads WHERE id = ?").run(e)})(),fe(t),Mi();let r=te(t);if(!r)throw new Error("merge destination disappeared");return r}function Wi(e){if(e.sessionIds.length===0)throw new Error("no sessions to split off");let t=f(),s=new Date().toISOString(),n=Oi();t.transaction(()=>{t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, NULL, ?)").run(n,e.newThreadName.trim(),s);for(let o of e.sessionIds){let a=t.prepare("SELECT * FROM thread_edges WHERE thread_id = ? AND session_id = ?").get(e.threadId,o);a&&(t.prepare(`INSERT INTO thread_edges
837
+ (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
838
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(n,o,a.parent_session_id,a.role,a.confidence,a.source,s),t.prepare("DELETE FROM thread_edges WHERE thread_id = ? AND session_id = ?").run(e.threadId,o))}})(),fe(e.threadId),fe(n);let r=te(n);if(!r)throw new Error("split destination disappeared");return r}H();import{execFile as Gm}from"node:child_process";import{promisify as Ym}from"node:util";import{readlink as Km,readFile as Yi}from"node:fs/promises";import{platform as ws}from"node:os";import{readFileSync as vm,statSync as jm}from"node:fs";var Mm=200*1024*1024,Ss=.7,Ts=.5,Xi=Ts,Dm=[{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"}],Fm=["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 qi(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 Pm(e,t){let s=t-e;if(s<0)return{weight:0,label:null};for(let n of Dm)if(s<=n.maxGapMs)return{weight:n.weight,label:n.label};return{weight:0,label:null}}function Um(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 r=e[n];if(!r)continue;let o=r.toLowerCase();for(let a of Fm)if(o.includes(a))return{weight:t[n],matched:a,matchedIndex:n}}return{weight:0,matched:null,matchedIndex:-1}}function Hn(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 $m(e,t){if(e.length===0||t.length===0)return 0;let s=0;for(let n of e)for(let r of t){let o=Hn(n,r);o>s&&(s=o)}return s}function Bm(e,t){let s=Hn(e.mean_embedding,t.mean_embedding),n=Hn(e.tail_pool,t.head_pool),r=$m(e.sample_chunks,t.sample_chunks),o=0,a=null;if(s>o&&(o=s,a="mean"),n>o&&(o=n,a="asymmetric"),r>o&&(o=r,a="max_pool"),o<.65)return{weight:0,cosine:o,mode:null};if(o>=.85)return{weight:.3,cosine:o,mode:a};let c=(o-.65)/.2*.3;return{weight:Math.round(c*100)/100,cosine:o,mode:a}}function Hm(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 Wm(e,t){if(e.size===0||t.size===0)return{weight:0,count:0};let s=0;for(let r of t)e.has(r)&&s++;return s===0?{weight:0,count:0}:{weight:Math.min(.4,s*.1),count:s}}function qm(e,t){let s=qi(e),n=qi(t);return s&&n&&s===n?{weight:.1,brand:s}:{weight:0,brand:null}}function Ji(e){return e.replace(/\s+/g," ").trim().toLowerCase()}function Jm(e,t){let s=0,n=null,r=!1;if(e.authored_paths.size>0){let o=t.recent_user_messages.slice(0,3).join(`
839
+ `).toLowerCase();for(let a of e.authored_paths){let c=a.toLowerCase();if(t.touched_files.has(a)||o.includes(c)){s+=.5,n=a;break}}}if(e.authored_content.length>0&&t.recent_user_messages[0]){let o=Ji(t.recent_user_messages[0]);if(o.length>=200)for(let a of e.authored_content){let c=Ji(a);if(c.length<200)continue;let d=Math.min(c.length,240),u=c.slice(0,d);if(o.includes(u)){s+=.4,r=!0;break}}}return{weight:Math.min(.6,s),pathMatch:n,contentMatch:r}}function Xm(e,t,s=Xi){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 r=Pm(n,t.started_at_ms),o=t.recent_user_messages.length>0?t.recent_user_messages:t.first_user_message?[t.first_user_message]:[],a=Um(o),c=Wm(e.touched_files,t.touched_files),d=qm(e.auto_title,t.auto_title),u=Bm(e,t),g=Hm(e,t),h=Jm(e,t),b=r.weight+a.weight+c.weight+d.weight+u.weight+g.weight+h.weight;if(b<s)return null;let S=[];if(r.label&&S.push(`temporal ${r.label} (+${r.weight})`),a.matched){let y=a.matchedIndex===0?"opening message":`message #${a.matchedIndex+1}`;S.push(`continuation phrase "${a.matched}" in ${y} (+${a.weight})`)}if(c.count>0&&S.push(`${c.count} file${c.count===1?"":"s"} overlap (+${c.weight.toFixed(1)})`),d.brand&&S.push(`shared brand "${d.brand}" (+${d.weight})`),u.weight>0&&u.mode&&S.push(`semantic ${u.mode==="asymmetric"?"tail\u2192head":u.mode==="max_pool"?"best-chunk":"mean"} ${u.cosine.toFixed(2)} (+${u.weight.toFixed(2)})`),g.same&&S.push(`same cluster (+${g.weight})`),h.weight>0){let y=[];h.pathMatch&&y.push(`opened authored path "${h.pathMatch.split("/").pop()}"`),h.contentMatch&&y.push("verbatim-paste of authored content"),S.push(`doc-authorship: ${y.join(" + ")} (+${h.weight.toFixed(2)})`)}return{parent_id:e.id,child_id:t.id,confidence:Math.min(1,b),signals:{temporal:r.weight,continuation:a.weight,file_overlap:c.weight,same_brand:d.weight,semantic:u.weight,cluster:g.weight,doc_authorship:h.weight},reasons:S}}function ze(e,t=Xi){let s=[];for(let n=0;n<e.length;n++){let r=e[n],o=null;for(let a=0;a<n;a++){let c=e[a],d=Xm(c,r,t);d&&(!o||d.confidence>o.confidence)&&(o=d)}o&&s.push(o)}return s}function Gi(e,t){let s=new Map,n=c=>{let d=c;for(;s.get(d)!==d;)d=s.get(d);let u=c;for(;s.get(u)!==d;){let g=s.get(u);s.set(u,d),u=g}return d},r=(c,d)=>{let u=n(c),g=n(d);u!==g&&s.set(u,g)};for(let c of e)s.has(c.parent_id)||s.set(c.parent_id,c.parent_id),s.has(c.child_id)||s.set(c.child_id,c.child_id),r(c.parent_id,c.child_id);let o=new Map;for(let c of s.keys()){let d=n(c),u=o.get(d);u||(u=[],o.set(d,u)),u.push(c)}let a=new Map;for(let c of t)a.set(c.id,c.started_at_ms);return Array.from(o.values()).map(c=>(c.sort((d,u)=>(a.get(d)??0)-(a.get(u)??0)),{rootId:c[0],sessionIds:c}))}function ys(e,t={}){let s=t.maxUserMessages??5,n=t.userMessageMaxLen??2e3,r=new Set,o=[],a=new Set,c=[],d;try{if(jm(e).size>Mm)return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c};d=vm(e,"utf8")}catch{return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c}}let u=0;for(;u<d.length;){let g=d.indexOf(`
840
+ `,u),h=g===-1?d.length:g,b=d.slice(u,h);if(u=g===-1?d.length:g+1,!b.trim())continue;let S;try{S=JSON.parse(b)}catch{continue}let y=S;if(y.type==="user"&&y.message?.role==="user"&&typeof y.message.content=="string"&&o.length<s){let w=y.message.content.trim();w&&o.push(w.length>n?w.slice(0,n):w)}let k=y.message?.content;if(Array.isArray(k))for(let w of k){if(!w||typeof w!="object")continue;let D=w;if(D.type!=="tool_use")continue;let L=D.input??{},X=typeof L.file_path=="string"?L.file_path:null;if(X){let M=bs(X);M&&r.add(M)}if((D.name==="Write"||D.name==="Edit"||D.name==="MultiEdit")&&X){let M=bs(X);M&&a.add(M);let B=typeof L.content=="string"?L.content:typeof L.new_string=="string"?L.new_string:null;B&&B.length>=200&&c.push(B.length>4096?B.slice(0,4096):B)}if(D.name==="Bash"&&typeof L.command=="string")for(let M of L.command.matchAll(/(?:^|[\s'"`(=])((?:\.\.?\/|\/|src\/|test\/|docs\/|site\/)[A-Za-z0-9_./-]+)/g)){let B=bs(M[1]);B&&r.add(B)}if((D.name==="Glob"||D.name==="Grep")&&typeof L.pattern=="string"){let M=bs(L.pattern);M&&!M.includes("*")&&r.add(M)}}}return{touched_files:r,recent_user_messages:o,authored_paths:a,authored_content:c}}function bs(e){let t=e.trim().replace(/^['"]|['"]$/g,"");return!t||t.length<4||/[<>|;&\$`]/.test(t)?null:t}var qn=Ym(Gm),zm=6,Ki="Active ",zi=" sessions \u2014 ",Vm=6e4;async function Zm(){if(ws()==="win32")return[];for(let t of["/bin/ps","/usr/bin/ps"])try{let{stdout:s}=await qn(t,["-eo","pid=,comm="],{timeout:2e3}),n=[];for(let r of s.split(`
841
+ `)){let o=r.trim().match(/^(\d+)\s+(.+)$/);if(!o)continue;let a=Number(o[1]),c=o[2].trim();(c==="claude"||c.endsWith("/claude")||c.endsWith("/bin/claude"))&&Number.isFinite(a)&&n.push(a)}return n}catch{continue}return[]}async function Qm(e){let t=ws();if(t==="linux")try{return(await Km(`/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 qn(s,["-a","-p",String(e),"-d","cwd","-Fn"],{timeout:2e3});for(let r of n.split(`
842
+ `))if(r.startsWith("n"))return r.slice(1).replace(/\/+$/,"");return null}catch{continue}return null}async function eg(e){let t=ws();if(t==="linux")try{let s=await Yi(`/proc/${e}/stat`,"utf8"),n=s.lastIndexOf(")");if(n===-1)return null;let r=s.slice(n+1).trim().split(/\s+/),o=Number(r[19]);if(!Number.isFinite(o))return null;let a=await Yi("/proc/uptime","utf8"),c=Number(a.split(/\s+/)[0]);return Number.isFinite(c)?Date.now()-c*1e3+o/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 qn(s,["-o","lstart=","-p",String(e)],{timeout:2e3}),r=Date.parse(n.trim());return Number.isFinite(r)?r:null}catch{continue}return null}async function tg(e,t){let s=await Zm();if(s.length===0)return null;let n=e.replace(/\/+$/,""),r=[];for(let a of s){let c=await Qm(a);if(c&&(c===n||c.startsWith(n+"/"))){let d=await eg(a);r.push({pid:a,startMs:d})}}if(r.length===0)return new Set;let o=new Set;for(let{startMs:a}of r){if(a==null)continue;let c=null,d=Vm;for(let u of t){if(o.has(u.session_id)||!u.started_at)continue;let g=Date.parse(u.started_at);if(!Number.isFinite(g))continue;let h=Math.abs(g-a);h<d&&(d=h,c=u)}c&&o.add(c.session_id)}return o}function sg(e){let s=f().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 ng(e,t){let s=f(),n=`${Ki}${t}${zi}%`,r=s.prepare(`SELECT t.id
843
+ FROM threads t
844
+ WHERE t.archived = 0
845
+ AND t.name LIKE ?
846
+ ORDER BY t.created_at DESC`).all(n);for(let a of r){let c=te(a.id);if(c&&c.project===t)return c}let o=s.prepare(`SELECT DISTINCT te.thread_id AS id
847
+ FROM thread_edges te
848
+ JOIN sessions s ON s.id = te.session_id
849
+ JOIN threads t ON t.id = te.thread_id
850
+ WHERE s.project_id = ?
851
+ AND t.archived = 0
852
+ AND t.name LIKE ?
853
+ LIMIT 1`).get(e,n);return o?te(o.id):null}function Vi(e){let t=e?new Date(e):new Date,s=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${s}-${n}-${r}`}function Wn(e,t){let s=f(),n=t>0,r=n?Date.now()-t*60*60*1e3:0;return n?s.prepare(`SELECT s.id AS session_id,
854
+ sa.alias AS alias,
855
+ s.auto_title AS auto_title,
856
+ s.first_user_message AS first_user_message,
857
+ s.started_at AS started_at,
858
+ s.file_mtime AS file_mtime
859
+ FROM sessions s
860
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
861
+ WHERE s.project_id = ?
862
+ AND s.file_mtime IS NOT NULL
863
+ AND s.file_mtime > ?
864
+ ORDER BY s.started_at ASC`).all(e,r):s.prepare(`SELECT s.id AS session_id,
865
+ sa.alias AS alias,
866
+ s.auto_title AS auto_title,
867
+ s.first_user_message AS first_user_message,
868
+ s.started_at AS started_at,
869
+ s.file_mtime AS file_mtime
870
+ FROM sessions s
871
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
872
+ WHERE s.project_id = ?
873
+ ORDER BY s.started_at ASC`).all(e)}function rg(e){let t=f(),s=[];for(let n of e){if(!n.started_at)continue;let r=Date.parse(n.started_at);if(!Number.isFinite(r))continue;let o=t.prepare("SELECT file_path, ended_at FROM sessions WHERE id = ?").get(n.session_id);if(!o?.file_path)continue;let a=o.ended_at?Date.parse(o.ended_at):null,c=ys(o.file_path,{maxUserMessages:7});s.push({id:n.session_id,started_at_ms:r,ended_at_ms:Number.isFinite(a)?a:null,first_user_message:n.first_user_message,recent_user_messages:c.recent_user_messages,auto_title:n.auto_title,touched_files:c.touched_files,mean_embedding:null,head_pool:null,tail_pool:null,sample_chunks:[],cluster_id:null,authored_paths:c.authored_paths,authored_content:c.authored_content})}return s}function og(e){let s=f().prepare(`SELECT session_id, parent_session_id, source
874
+ FROM thread_edges
875
+ WHERE thread_id = ?`).all(e),n=new Map;for(let r of s)n.set(r.session_id,{parent_session_id:r.parent_session_id,source:r.source});return n}async function Zi(e,t={}){let s=sg(e),n=t.windowHours??zm,r=t.scoreThreshold??Ts,o=t.useLivePids??!0,a=[],c=[];if(o&&s.decoded_path){let k=Wn(e,0),w=await tg(s.decoded_path,k);if(w===null){let L=ws()==="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.";a.push(L),c=Wn(e,n)}else w.size===0?(a.push(`No active terminals open in ${s.name} (cwd=${s.decoded_path}). Open a Claude terminal in this repo and re-run.`),c=[]):c=k.filter(D=>w.has(D.session_id))}else c=Wn(e,n);c.length===0&&!a.length&&a.push(`No active sessions in ${s.name} within the last ${n}h.`);let d=ng(e,s.name),u=new Set(d?d.edges.map(k=>k.session_id):[]),g=c.filter(k=>!u.has(k.session_id)),h=rg(c);h.sort((k,w)=>k.started_at_ms-w.started_at_ms);let b=ze(h,r),S=d?d.edges.filter(k=>k.source!=="auto-active"&&(k.parent_session_id||k.role==="origin")).map(k=>({session_id:k.session_id,parent_session_id:k.parent_session_id})):[],y=d?d.name:`${Ki}${s.name}${zi}${Vi(t.todayIso)}`;return{project:s,thread:{id:d?.id??null,name:y,exists:!!d,existing_session_count:d?.edges.length??0},candidates:c,proposed_additions:g,proposed_edges:b,preserved_manual_edges:S,warnings:a}}function Qi(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=hs({name:e.thread.name,summary:`Auto-captured by sync-active on ${Vi()}. 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=og(s);for(let a of e.candidates)n.has(a.session_id)||(Es({threadId:s,sessionId:a.session_id,source:"auto-active",confidence:.5}),t.added++,n.set(a.session_id,{parent_session_id:null,source:"auto-active"}));for(let a of e.proposed_edges){let c=n.get(a.child_id);if(c&&c.source==="auto-active"&&c.parent_session_id!==a.parent_id&&n.has(a.parent_id))try{xt(s,a.child_id,a.parent_id),t.edges_set++,n.set(a.child_id,{parent_session_id:a.parent_id,source:c.source})}catch{}}let o=f().prepare(`SELECT session_id FROM thread_edges
876
+ WHERE thread_id = ?
877
+ AND source = 'auto-active'
878
+ AND parent_session_id IS NULL
879
+ AND role = 'child'`).all(s);for(let a of o)try{xt(s,a.session_id,null)}catch{}return t}H();Z();import{randomUUID as ig}from"node:crypto";import{writeFileSync as ag}from"node:fs";import{join as cg}from"node:path";var lg=cg($,"thread-folders.json");function ea(e){return{id:e.id,name:e.name,parent_folder_id:e.parent_folder_id,project_scope:e.project_scope,created_at:e.created_at,archived:e.archived===1,sort_order:e.sort_order}}function Ot(){try{J();let e=Jn({includeArchived:!0});ag(lg,JSON.stringify({folders:e},null,2))}catch{}}function Jn(e={}){let t=e.includeArchived?"":"WHERE archived = 0";return f().prepare(`SELECT * FROM thread_folders ${t} ORDER BY sort_order, name`).all().map(ea)}function $e(e){let t=f().prepare("SELECT * FROM thread_folders WHERE id = ?").get(e);return t?ea(t):null}function ta(e){let t=e.name.trim();if(!t)throw new Error("folder name cannot be empty");if(t.length>200)throw new Error("folder name too long (200 char max)");let s=e.parentFolderId??null,n=e.projectScope??null;if(s){let c=$e(s);if(!c)throw new Error(`parent folder ${s} not found`);n=c.project_scope}let r=ig(),o=new Date().toISOString(),a=sa(s,n);return f().prepare(`INSERT INTO thread_folders (id, name, parent_folder_id, project_scope, created_at, archived, sort_order)
880
+ VALUES (?, ?, ?, ?, ?, 0, ?)`).run(r,t,s,n,o,a),Ot(),{id:r,name:t,parent_folder_id:s,project_scope:n,created_at:o,archived:!1,sort_order:a}}function sa(e,t){return f().prepare(`SELECT COALESCE(MAX(sort_order), -100) + 100 AS next
881
+ FROM thread_folders
882
+ WHERE parent_folder_id IS ?
883
+ AND project_scope IS ?`).get(e,t)?.next??0}function na(e,t){let s=t.trim();if(!s)throw new Error("folder name cannot be empty");if(s.length>200)throw new Error("folder name too long (200 char max)");let n=$e(e);if(!n)throw new Error(`folder ${e} not found`);return f().prepare("UPDATE thread_folders SET name = ? WHERE id = ?").run(s,e),Ot(),{...n,name:s}}function ra(e,t){let s=$e(e);if(!s)throw new Error(`folder ${e} not found`);if(t===e)throw new Error("cannot move a folder under itself");let n=s.project_scope;if(t!==null){let o=$e(t);if(!o)throw new Error(`parent folder ${t} not found`);let a=o.parent_folder_id,c=0;for(;a!==null&&c<1024;){if(a===e)throw new Error("cannot move a folder into one of its own descendants (cycle)");let d=$e(a);if(!d)break;a=d.parent_folder_id,c++}n=o.project_scope}let r=sa(t,n);return f().prepare("UPDATE thread_folders SET parent_folder_id = ?, project_scope = ?, sort_order = ? WHERE id = ?").run(t,n,r,e),Ot(),{...s,parent_folder_id:t,project_scope:n,sort_order:r}}function oa(e,t,s){if(!Array.isArray(s)||s.length===0)throw new Error("ordered_ids must be a non-empty array");let n=new Set;for(let u of s){if(typeof u!="string"||!u)throw new Error("ordered_ids must contain non-empty strings");if(n.has(u))throw new Error(`duplicate id in ordered_ids: ${u}`);n.add(u)}let r=f(),o=r.prepare(`SELECT id FROM thread_folders
884
+ WHERE parent_folder_id IS ?
885
+ AND project_scope IS ?`).all(e,t),a=new Set(o.map(u=>u.id));if(a.size!==s.length)throw new Error(`ordered_ids length ${s.length} does not match sibling count ${a.size}`);for(let u of s)if(!a.has(u))throw new Error(`folder ${u} is not in the named sibling bucket`);let c=r.prepare("UPDATE thread_folders SET sort_order = ? WHERE id = ?");r.transaction(u=>{u.forEach((g,h)=>c.run(h*100,g))})(s),Ot()}function ia(e){if(!$e(e))throw new Error(`folder ${e} not found`);f().prepare("DELETE FROM thread_folders WHERE id = ?").run(e),Ot()}function aa(e,t){if(t!==null&&!$e(t))throw new Error(`folder ${t} not found`);if(f().prepare("UPDATE threads SET folder_id = ? WHERE id = ?").run(t,e).changes===0)throw new Error(`thread ${e} not found`)}H();Z();import{writeFileSync as ca,readFileSync as Bw,existsSync as la,mkdirSync as da,readdirSync as Hw}from"node:fs";import{join as Rs}from"node:path";var dg=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),ua=new Set(["regex","llm","embedding","manual","auto","citation","git","terminal-registry"]),ug=new Set(["pending","approved","rejected"]),pg=new Set(["L1","L2","L3","L4","user"]),Xn=Rs($,"links"),Gn=Rs($,"suggestions"),mg=Rs(Gn,"index.json");function gg(){J(),la(Xn)||da(Xn,{recursive:!0})}function _g(){J(),la(Gn)||da(Gn,{recursive:!0})}function pa(e){try{return JSON.parse(e)}catch{return e}}function ks(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:pa(e.evidence),approved:e.approved===1,created_at:e.created_at,updated_at:e.updated_at}}function Yn(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:pa(e.evidence),status:e.status,inferred_by:e.inferred_by,created_at:e.created_at,decided_at:e.decided_at}}function ma(e){if(!Number.isFinite(e)||e<0||e>1)throw new Error("confidence must be a number in [0, 1]")}function Kn(e){if(!dg.has(e))throw new Error(`invalid link_type: ${e}`)}function fg(e){if(!ua.has(e))throw new Error(`invalid source: ${e}`)}function ga(e){if(!pg.has(e))throw new Error(`invalid inferred_by: ${e}`)}function _a(e,t){if(!e||!t)throw new Error("source_session_id and target_session_id are required");if(e===t)throw new Error("a session cannot link to itself")}function fa(e){_a(e.source_session_id,e.target_session_id),Kn(e.link_type),fg(e.source),ma(e.confidence);let t=f(),s=new Date().toISOString(),n=JSON.stringify(e.evidence??null),r=e.approved?1:0;t.prepare(`INSERT INTO session_links
886
+ (source_session_id, target_session_id, link_type,
887
+ confidence, source, evidence, approved,
888
+ created_at, updated_at)
889
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
890
+ ON CONFLICT(source_session_id, target_session_id, link_type) DO UPDATE SET
891
+ confidence = excluded.confidence,
892
+ source = excluded.source,
893
+ evidence = excluded.evidence,
894
+ approved = excluded.approved,
895
+ updated_at = excluded.updated_at`).run(e.source_session_id,e.target_session_id,e.link_type,e.confidence,e.source,n,r,s,s);let o=t.prepare(`SELECT * FROM session_links
896
+ WHERE source_session_id = ?
897
+ AND target_session_id = ?
898
+ AND link_type = ?`).get(e.source_session_id,e.target_session_id,e.link_type);if(!o)throw new Error("createLink succeeded but read-back failed");return Vn(e.source_session_id),ks(o)}function As(e={}){let t=f(),s=[],n=[];e.sourceSessionId&&(s.push("source_session_id = ?"),n.push(e.sourceSessionId)),e.targetSessionId&&(s.push("target_session_id = ?"),n.push(e.targetSessionId)),e.linkType&&(Kn(e.linkType),s.push("link_type = ?"),n.push(e.linkType)),e.approvedOnly&&s.push("approved = 1");let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_links ${r}
899
+ ORDER BY confidence DESC, updated_at DESC
900
+ LIMIT ?`).all(...n,o).map(ks)}function Lt(e){return f().prepare(`SELECT * FROM session_links
901
+ WHERE source_session_id = ? OR target_session_id = ?
902
+ ORDER BY confidence DESC, updated_at DESC`).all(e,e).map(ks)}function ha(e){let t=f(),s=t.prepare("SELECT source_session_id FROM session_links WHERE id = ?").get(e);if(!s)return{removed:0,sourceSessionId:null};let n=t.prepare("DELETE FROM session_links WHERE id = ?").run(e);return n.changes>0&&Vn(s.source_session_id),{removed:n.changes,sourceSessionId:s.source_session_id}}function Ct(e){_a(e.source_session_id,e.target_session_id),Kn(e.link_type),ma(e.confidence),ga(e.inferred_by);let t=f(),s=new Date().toISOString(),n=JSON.stringify(e.evidence??null);t.prepare(`INSERT INTO session_link_suggestions
903
+ (source_session_id, target_session_id, link_type,
904
+ confidence, evidence, status, inferred_by,
905
+ created_at, decided_at)
906
+ VALUES (?, ?, ?, ?, ?, 'pending', ?, ?, NULL)
907
+ ON CONFLICT(source_session_id, target_session_id, link_type, inferred_by) DO UPDATE SET
908
+ confidence = CASE
909
+ WHEN session_link_suggestions.status = 'pending'
910
+ THEN excluded.confidence
911
+ ELSE session_link_suggestions.confidence
912
+ END,
913
+ evidence = CASE
914
+ WHEN session_link_suggestions.status = 'pending'
915
+ THEN excluded.evidence
916
+ ELSE session_link_suggestions.evidence
917
+ END,
918
+ created_at = session_link_suggestions.created_at`).run(e.source_session_id,e.target_session_id,e.link_type,e.confidence,n,e.inferred_by,s);let r=t.prepare(`SELECT * FROM session_link_suggestions
919
+ WHERE source_session_id = ?
920
+ AND target_session_id = ?
921
+ AND link_type = ?
922
+ AND inferred_by = ?`).get(e.source_session_id,e.target_session_id,e.link_type,e.inferred_by);if(!r)throw new Error("createSuggestion succeeded but read-back failed");return Ea(),Yn(r)}function Ve(e={}){let t=f(),s=[],n=[];if(e.status){if(!ug.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&&(ga(e.inferredBy),s.push("inferred_by = ?"),n.push(e.inferredBy));let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.max(1,Math.min(5e3,e.limit??1e3));return t.prepare(`SELECT * FROM session_link_suggestions ${r}
923
+ ORDER BY confidence DESC, created_at DESC
924
+ LIMIT ?`).all(...n,o).map(Yn)}function zn(e,t,s={}){if(t!=="approved"&&t!=="rejected")throw new Error(`invalid decision: ${t}`);let n=s.source??"manual";if(!ua.has(n))throw new Error(`invalid source: ${n}`);let r=f(),o=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);if(!o)throw new Error(`suggestion ${e} not found`);if(o.status!=="pending")throw new Error(`suggestion ${e} already decided as ${o.status}`);let a=new Date().toISOString(),c;r.transaction(()=>{r.prepare(`UPDATE session_link_suggestions
925
+ SET status = ?, decided_at = ?
926
+ WHERE id = ?`).run(t,a,e),t==="approved"&&(r.prepare(`INSERT INTO session_links
927
+ (source_session_id, target_session_id, link_type,
928
+ confidence, source, evidence, approved,
929
+ created_at, updated_at)
930
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)
931
+ ON CONFLICT(source_session_id, target_session_id, link_type) DO UPDATE SET
932
+ confidence = excluded.confidence,
933
+ source = excluded.source,
934
+ evidence = excluded.evidence,
935
+ approved = 1,
936
+ updated_at = excluded.updated_at`).run(o.source_session_id,o.target_session_id,o.link_type,o.confidence,n,o.evidence,a,a),c=r.prepare(`SELECT * FROM session_links
937
+ WHERE source_session_id = ?
938
+ AND target_session_id = ?
939
+ AND link_type = ?`).get(o.source_session_id,o.target_session_id,o.link_type))})(),Ea(),t==="approved"&&Vn(o.source_session_id);let d=r.prepare("SELECT * FROM session_link_suggestions WHERE id = ?").get(e);return{suggestion:Yn(d),link:c?ks(c):null}}function Vn(e){try{gg();let t=As({sourceSessionId:e}),s=Rs(Xn,`${e}.json`);if(t.length===0)return;let n={schema:"claude-recall.session-links.v1",source_session_id:e,backed_up_at:new Date().toISOString(),links:t};ca(s,JSON.stringify(n,null,2))}catch(t){console.error("[session-links] backup failed:",t)}}function Ea(){try{_g();let e=Ve({limit:5e3}),t={schema:"claude-recall.session-link-suggestions.v1",backed_up_at:new Date().toISOString(),suggestions:e};ca(mg,JSON.stringify(t,null,2))}catch(e){console.error("[session-links] suggestions backup failed:",e)}}function ba(e,t){let s=new Set,n=[];for(let r of t){if(s.has(r.id))continue;let o=null,a="";if(r.source_session_id===e)o="outbound",a=r.target_session_id;else if(r.target_session_id===e)o="inbound",a=r.source_session_id;else continue;s.add(r.id),n.push({linkId:r.id,otherSessionId:a,direction:o,updatedAt:r.updated_at,link:r})}return n.sort((r,o)=>r.updatedAt<o.updatedAt?1:r.updatedAt>o.updatedAt?-1:0),n}H();Z();import{writeFileSync as hg,readFileSync as Kw,existsSync as Eg,mkdirSync as bg,readdirSync as zw}from"node:fs";import{join as Sa}from"node:path";var Zn=Sa($,"output-index");function Sg(){J(),Eg(Zn)||bg(Zn,{recursive:!0})}function It(e){if(!e)return[];try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function Tg(e){if(!e)return null;try{return JSON.parse(e)}catch{return e}}function Ta(e){return{session_id:e.session_id,files_written:It(e.files_written),brands_mentioned:It(e.brands_mentioned),terms_introduced:It(e.terms_introduced),plan_ids_referenced:It(e.plan_ids_referenced),bug_signatures:It(e.bug_signatures),raw_extraction:Tg(e.raw_extraction),extracted_at:e.extracted_at,extractor_version:e.extractor_version}}function ya(e){if(!e.session_id)throw new Error("session_id is required");let t=f(),s=new Date().toISOString(),n=JSON.stringify(e.files_written??[]),r=JSON.stringify(e.brands_mentioned??[]),o=JSON.stringify(e.terms_introduced??[]),a=JSON.stringify(e.plan_ids_referenced??[]),c=JSON.stringify(e.bug_signatures??[]),d=e.raw_extraction===void 0?null:JSON.stringify(e.raw_extraction),u=Math.max(1,Math.floor(e.extractor_version??1));t.prepare(`INSERT INTO session_output_index
940
+ (session_id, files_written, brands_mentioned, terms_introduced,
941
+ plan_ids_referenced, bug_signatures, raw_extraction,
942
+ extracted_at, extractor_version)
943
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
944
+ ON CONFLICT(session_id) DO UPDATE SET
945
+ files_written = excluded.files_written,
946
+ brands_mentioned = excluded.brands_mentioned,
947
+ terms_introduced = excluded.terms_introduced,
948
+ plan_ids_referenced = excluded.plan_ids_referenced,
949
+ bug_signatures = excluded.bug_signatures,
950
+ raw_extraction = excluded.raw_extraction,
951
+ extracted_at = excluded.extracted_at,
952
+ extractor_version = excluded.extractor_version`).run(e.session_id,n,r,o,a,c,d,s,u);let g=t.prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e.session_id);if(!g)throw new Error("setOutputIndex succeeded but read-back failed");let h=Ta(g);return yg(e.session_id),h}function Be(e){let s=f().prepare("SELECT * FROM session_output_index WHERE session_id = ?").get(e);return s?Ta(s):null}function yg(e){try{Sg();let t=Be(e);if(!t)return;let s=Sa(Zn,`${e}.json`),n={schema:"claude-recall.session-output-index.v1",backed_up_at:new Date().toISOString(),...t};hg(s,JSON.stringify(n,null,2))}catch(t){console.error("[output-index] backup failed:",t)}}H();var wg=4e3,Rg=2,kg=30,Ag=.2,Ng={citation:1,wiki_link:.9,similar:.7,skill_track:.5,bug_pattern:.4,temporal_proximity:.3};function Ns(e){return e?Math.ceil(e.length/4):0}function wa(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/kg);return Math.max(Ag,t)}function Ra(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 ka(e){return f().prepare(`SELECT s.id,
953
+ NULLIF(sa.alias, '') AS alias,
954
+ s.auto_title,
955
+ s.auto_title_source,
956
+ s.first_user_message,
957
+ s.project_id,
958
+ p.name AS project,
959
+ s.started_at
960
+ FROM sessions s
961
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
962
+ LEFT JOIN projects p ON p.id = s.project_id
963
+ WHERE s.id = ?`).get(e)??null}function Aa(e){let s=f().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 Na(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 xg(e){let s=f().prepare(`SELECT id, auto_title, started_at
964
+ FROM sessions
965
+ WHERE project_id = ?
966
+ ORDER BY COALESCE(started_at, ''), id`).all(e),n=new Set,r=new Set,o=[];for(let b of s){if(!b.auto_title||!b.auto_title.startsWith("/")){o.push({id:b.id,brand:null,skill:null});continue}let S=b.auto_title.split(" \xB7 "),y=S[0].trim(),k=S.length>1?S.slice(1).join(" \xB7 ").trim():null;o.push({id:b.id,brand:k||null,skill:y||null}),k&&n.add(k),y&&r.add(y)}let a=[...n].sort(),c=new Map;a.forEach((b,S)=>c.set(b,S));let d=[...r].sort(),u=new Map;d.forEach((b,S)=>u.set(b,S));let g=new Map,h=new Map;for(let b of o){if(!b.brand||!b.skill)continue;let S=c.get(b.brand),y=u.get(b.skill);if(S===void 0||y===void 0)continue;let k=`${S}.${y}`,w=(g.get(k)??0)+1;g.set(k,w),h.set(b.id,`${S}.${y}.${w}`)}return{byId:h}}function Og(e){return{table:e!==null?xg(e):null,originProjectId:e,cache:new Map}}function xs(e,t){let s=e.cache.get(t);if(s)return s;let n=ka(t);if(!n)return null;let r=e.table&&n.project_id===e.originProjectId?e.table.byId.get(t)??null:null,o={session_id:n.id,title:Na(n),decimal:r,summary:Aa(n.id),project:n.project,started_at:n.started_at};return e.cache.set(t,o),o}function Lg(e,t){let n=f().prepare(`SELECT DISTINCT te.parent_session_id AS pid
967
+ FROM thread_edges te
968
+ WHERE te.session_id = ?
969
+ AND te.parent_session_id IS NOT NULL`).all(t),r=[];for(let o of n){if(!o.pid)continue;let a=xs(e,o.pid);a&&r.push(a)}return r}function Cg(e,t){let n=f().prepare(`SELECT DISTINCT te.session_id AS sid
970
+ FROM thread_edges te
971
+ WHERE te.parent_session_id = ?`).all(t),r=[];for(let o of n){if(!o.sid)continue;let a=xs(e,o.sid);a&&r.push(a)}return r}function xa(e){let t=Ng[e.linkType]??.5,s=Ze(e.confidence),n=t*s,r=wa(e.daysApart),o=e.embeddingCosine??.5,a=Ze(e.pagerank);if(e.scoring==="pagerank")return Ze(a);if(e.scoring==="embedding-rerank")return e.embeddingCosine===null?Ze(n):Ze(o);let c=.35*n+.2*r+.2*o+.25*a;return Ze(c)}function Ze(e){return!Number.isFinite(e)||e<0?0:e>1?1:e}function Ig(e,t,s,n,r){let o=new Map;function a(c,d){if(c===d)return;let u=o.get(c);u||(u=new Set,o.set(c,u)),u.add(d)}for(let c of t)a(c.source_session_id,c.target_session_id),a(c.target_session_id,c.source_session_id);for(let c of s)a(e,c.session_id);for(let c of s)a(c.session_id,e);for(let c of n)a(e,c.session_id);for(let c of n)a(c.session_id,e);if(r>1){let c=new Set([e]),d=new Set([e]);for(let u=1;u<r;u++){let g=new Set;for(let h of c){let b=o.get(h);if(b)for(let S of b){if(d.has(S))continue;let y=Lt(S).filter(k=>k.approved);for(let k of y)a(k.source_session_id,k.target_session_id),a(k.target_session_id,k.source_session_id);d.add(S),g.add(S)}}if(g.size===0)break;for(let h of g)c.add(h)}}return{edges:o}}function vg(e,t={}){let s=t.iterations??12,n=t.damping??.85,r=Array.from(e.edges.keys());if(r.length===0)return new Map;let o=1/r.length,a=new Map(r.map(u=>[u,o]));for(let u=0;u<s;u++){let g=new Map(r.map(h=>[h,(1-n)/r.length]));for(let h of r){let b=e.edges.get(h);if(!b||b.size===0)continue;let S=(a.get(h)??0)/b.size;for(let y of b)g.set(y,(g.get(y)??0)+n*S)}a=g}let c=0;for(let u of a.values())u>c&&(c=u);if(c<=0)return a;let d=new Map;for(let[u,g]of a)d.set(u,g/c);return d}var Oa=240;function La(e,t){let s=e.replace(/\s+/g," ").trim();return s.length<=t?s:`${s.slice(0,t-1).trimEnd()}\u2026`}function jg(e){let t=e.decimal?`${e.decimal} `:"",s=e.session_id.slice(0,8),n="evidence"in e&&e.evidence?` \u2014 ${e.evidence}`:"",r=`- ${t}${e.title} (${s})${n}`;if(e.summary){let o=La(e.summary,Oa);return`${r}
972
+ ${o}`}return r}function Mg(e,t,s){let n=[],r=[],o=0,a=e.decimal?`${e.decimal}: `:"",c=`# Neighborhood for ${e.session_id} (${a}${e.title})`;if(n.push(c),o+=Ns(c),e.summary){let d=La(e.summary,Oa*4);n.push(d),o+=Ns(d)}n.push("");for(let d of t){if(d.refs.length===0)continue;let u=`## ${d.heading}`,g=Ns(u),h=[],b=0;for(let S of d.refs){let y=jg(S),k=Ns(y);if(o+g+b+k>s){r.push({session_id:S.session_id,title:S.title,decimal:S.decimal,summary:S.summary,project:S.project,started_at:S.started_at});continue}h.push(y),b+=k}if(h.length>0){n.push(u);for(let S of h)n.push(S);n.push(""),o+=g+b}}for(;n.length>0&&n[n.length-1]==="";)n.pop();return{bundle:n.join(`
973
+ `)+`
974
+ `,budgetUsed:o,truncated:r}}function Dg(e,t,s,n,r,o){let a=[];for(let c of s){if(n&&!n.has(c.link_type))continue;let d=null;if(c.source_session_id===t.session_id?d=c.target_session_id:c.target_session_id===t.session_id&&(d=c.source_session_id),!d)continue;let u=xs(e,d);if(!u)continue;let g=Ra(t.started_at,u.started_at),h=xa({confidence:c.confidence,linkType:c.link_type,daysApart:g,embeddingCosine:null,pagerank:o.get(d)??0,scoring:r});a.push({...u,score:h,evidence:`(suggestion, ${c.inferred_by}) confidence=${c.confidence.toFixed(2)} ${Math.round(g)}d apart`,link_type:c.link_type})}return a}function Os(e,t={}){let s=Math.max(100,Math.floor(t.budget??wg)),n=t.scoring??"hybrid",r=Math.max(1,Math.min(5,t.maxDepth??Rg)),o=t.includeWikiLinks??!0,a=t.includeSuggestions??!1,c=t.edgeTypes?new Set(t.edgeTypes):null,d=ka(e);if(!d)throw new Error(`session not found: ${e}`);let u=Og(d.project_id),g={session_id:d.id,title:Na(d),decimal:u.table?.byId.get(d.id)??null,summary:Aa(d.id),project:d.project,started_at:d.started_at};u.cache.set(d.id,g);let h=Lg(u,e),b=Cg(u,e),S=Lt(e).filter(i=>i.approved).filter(i=>!c||c.has(i.link_type)).filter(i=>o||i.link_type!=="wiki_link"),y=Ig(e,S,h,b,r),k=vg(y),w=[],D=[],L=[],X=[];for(let i of S){let l=i.source_session_id===e?i.target_session_id:i.source_session_id,p=xs(u,l);if(!p)continue;let m=Ra(g.started_at,p.started_at),_=xa({confidence:i.confidence,linkType:i.link_type,daysApart:m,embeddingCosine:null,pagerank:k.get(l)??0,scoring:n}),E=wa(m),T=`${i.link_type} confidence=${i.confidence.toFixed(2)} recency=${E.toFixed(2)} (${Math.round(m)}d apart)`,R={...p,score:_,evidence:T,link_type:i.link_type};i.link_type==="citation"?w.push(R):i.link_type==="similar"?D.push(R):i.link_type==="wiki_link"?X.push(R):L.push(R)}if(a){let i=Ve({sourceSessionId:e,status:"pending",limit:100}),l=Ve({targetSessionId:e,status:"pending",limit:100}),p=[...i,...l],m=new Set,_=p.filter(T=>m.has(T.id)?!1:(m.add(T.id),!0)),E=Dg(u,g,_,c,n,k);for(let T of E)T.link_type==="citation"?w.push(T):T.link_type==="similar"?D.push(T):T.link_type==="wiki_link"?X.push(T):L.push(T)}let M=(i,l)=>l.score-i.score;w.sort(M),D.sort(M),L.sort(M),X.sort(M);let se=Mg(g,[{heading:"Parents",refs:h},{heading:"Children",refs:b},{heading:"Citations (approved)",refs:w},{heading:"Similar sessions",refs:D},{heading:"Cousins (skill track + temporal)",refs:L},{heading:"Wiki links (manual)",refs:X}],s);return{origin:g,parents:h,children:b,citations:w,similar:D,cousins:L,wikiLinks:X,bundle:se.bundle,budgetUsed:se.budgetUsed,budgetRemaining:Math.max(0,s-se.budgetUsed),truncated:se.truncated}}H();Z();import{writeFileSync as Fg,readFileSync as o0,existsSync as Pg,mkdirSync as Ug,readdirSync as i0,unlinkSync as a0}from"node:fs";import{join as Ca}from"node:path";import{randomUUID as Ia}from"node:crypto";var Qn=Ca($,"bug-patterns");function $g(){J(),Pg(Qn)||Ug(Qn,{recursive:!0})}function Oe(e){return{id:e.id,signature_hash:e.signature_hash,example_message:e.example_message,occurrence_count:e.occurrence_count,first_seen_at:e.first_seen_at,last_seen_at:e.last_seen_at,resolved_in_session_id:e.resolved_in_session_id,fix_summary:e.fix_summary}}function va(e){return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at}}function ja(e){if(!e.signature_hash)throw new Error("signature_hash is required");if(!e.example_message)throw new Error("example_message is required");if(!Array.isArray(e.member_session_ids)||e.member_session_ids.length===0)throw new Error("at least one member_session_id is required");let t=f(),s=new Date().toISOString(),n=e.id??Ia(),r=e.first_seen_at??s,o=e.last_seen_at??s,a=Array.from(new Set(e.member_session_ids));t.transaction(()=>{t.prepare(`INSERT INTO bug_pattern_clusters
975
+ (id, signature_hash, example_message, occurrence_count,
976
+ first_seen_at, last_seen_at, resolved_in_session_id, fix_summary)
977
+ VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(n,e.signature_hash,e.example_message,a.length,r,o);let d=t.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
978
+ VALUES (?, ?, ?)
979
+ ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let u of a)d.run(n,u,s)})();let c=Ma(n);if(!c)throw new Error("createCluster succeeded but read-back failed");return vt(n),c}function Ma(e){let t=f(),s=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!s)return null;let n=t.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ? ORDER BY matched_at ASC, session_id ASC").all(e);return{cluster:Oe(s),members:n.map(va)}}function Da(e,t){if(!e)throw new Error("clusterId is required");if(!Array.isArray(t)||t.length===0)throw new Error("sessionIds must be a non-empty array");let s=f();if(!s.prepare("SELECT 1 FROM bug_pattern_clusters WHERE id = ?").get(e))throw new Error(`cluster ${e} not found`);let r=new Date().toISOString(),o=0;s.transaction(()=>{let c=s.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
980
+ VALUES (?, ?, ?)
981
+ ON CONFLICT(cluster_id, session_id) DO NOTHING`);for(let d of Array.from(new Set(t)))c.run(e,d,r).changes>0&&(o+=1);if(o>0){let d=s.prepare("SELECT COUNT(*) AS n FROM bug_pattern_members WHERE cluster_id = ?").get(e).n;s.prepare(`UPDATE bug_pattern_clusters
982
+ SET occurrence_count = ?, last_seen_at = ?
983
+ WHERE id = ?`).run(d,r,e)}})(),o>0&&vt(e);let a=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:Oe(a),added:o}}function Fa(e={}){let t=f(),s=[],n=[];typeof e.minOccurrenceCount=="number"&&(s.push("c.occurrence_count >= ?"),n.push(Math.max(1,Math.floor(e.minOccurrenceCount)))),typeof e.hasResolved=="boolean"&&s.push(e.hasResolved?"c.resolved_in_session_id IS NOT NULL":"c.resolved_in_session_id IS NULL"),e.project&&(s.push(`EXISTS (
984
+ SELECT 1 FROM bug_pattern_members m
985
+ JOIN sessions s ON s.id = m.session_id
986
+ JOIN projects p ON p.id = s.project_id
987
+ WHERE m.cluster_id = c.id AND p.name = ?
988
+ )`),n.push(e.project));let r=s.length>0?`WHERE ${s.join(" AND ")}`:"",o=t.prepare(`SELECT COUNT(*) AS n FROM bug_pattern_clusters c ${r}`).get(...n),a=Math.max(1,Math.min(5e3,e.limit??100)),c=Math.max(0,Math.floor(e.offset??0));return{clusters:t.prepare(`SELECT c.* FROM bug_pattern_clusters c ${r}
989
+ ORDER BY c.occurrence_count DESC, c.last_seen_at DESC
990
+ LIMIT ? OFFSET ?`).all(...n,a,c).map(Oe),total:o.n}}function Bg(e){let t=e.first_user_message?e.first_user_message.slice(0,80):null,s=e.alias??e.auto_title??t??e.session_id.slice(0,8);return{cluster_id:e.cluster_id,session_id:e.session_id,matched_at:e.matched_at,title:s,alias:e.alias,auto_title:e.auto_title,project:e.project,started_at:e.started_at}}function Pa(e){let t=f(),s=t.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT m.cluster_id, m.session_id, m.matched_at,
991
+ NULLIF(sa.alias, '') AS alias,
992
+ s.auto_title,
993
+ s.first_user_message,
994
+ p.name AS project,
995
+ s.started_at
996
+ FROM bug_pattern_members m
997
+ LEFT JOIN sessions s ON s.id = m.session_id
998
+ LEFT JOIN session_aliases sa ON sa.session_id = m.session_id
999
+ LEFT JOIN projects p ON p.id = s.project_id
1000
+ WHERE m.cluster_id = ?
1001
+ ORDER BY COALESCE(s.started_at, ''), m.matched_at, m.session_id`).all(e);return{cluster:Oe(s),members:n.map(Bg)}}function Ua(e,t,s){if(!e)throw new Error("clusterId is required");if(!t)throw new Error("sessionId is required");if(typeof s!="string"||!s.trim())throw new Error("fixSummary is required");let n=f();if(!n.prepare("SELECT 1 FROM bug_pattern_clusters WHERE id = ?").get(e))throw new Error(`cluster ${e} not found`);if(!n.prepare("SELECT 1 FROM bug_pattern_members WHERE cluster_id = ? AND session_id = ?").get(e,t))throw new Error(`session ${t} is not a member of cluster ${e}`);n.prepare(`UPDATE bug_pattern_clusters
1002
+ SET resolved_in_session_id = ?, fix_summary = ?
1003
+ WHERE id = ?`).run(t,s.trim(),e),vt(e);let a=n.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);return{cluster:Oe(a)}}function $a(e,t){if(!e)throw new Error("clusterId is required");if(!Array.isArray(t)||t.length===0)throw new Error("memberSessionIds must be a non-empty array");let s=f(),n=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e);if(!n)throw new Error(`cluster ${e} not found`);let r=Array.from(new Set(t)),o=r.map(()=>"?").join(","),a=s.prepare(`SELECT session_id, matched_at FROM bug_pattern_members
1004
+ WHERE cluster_id = ? AND session_id IN (${o})`).all(e,...r);if(a.length===0)throw new Error(`none of the supplied session_ids are members of cluster ${e}`);let c=s.prepare("SELECT COUNT(*) AS n FROM bug_pattern_members WHERE cluster_id = ?").get(e).n;if(a.length>=c)throw new Error(`cannot split: would leave the original cluster empty (move ${a.length} of ${c})`);let d=Ia(),u=new Date().toISOString(),g=[];s.transaction(()=>{s.prepare(`INSERT INTO bug_pattern_clusters
1005
+ (id, signature_hash, example_message, occurrence_count,
1006
+ first_seen_at, last_seen_at, resolved_in_session_id, fix_summary)
1007
+ VALUES (?, ?, ?, ?, ?, ?, NULL, NULL)`).run(d,n.signature_hash,n.example_message,a.length,n.first_seen_at,u);let S=s.prepare(`INSERT INTO bug_pattern_members (cluster_id, session_id, matched_at)
1008
+ VALUES (?, ?, ?)`),y=s.prepare("DELETE FROM bug_pattern_members WHERE cluster_id = ? AND session_id = ?");for(let w of a)S.run(d,w.session_id,w.matched_at),y.run(e,w.session_id);let k=c-a.length;s.prepare("UPDATE bug_pattern_clusters SET occurrence_count = ? WHERE id = ?").run(k,e),g=s.prepare("SELECT * FROM bug_pattern_members WHERE cluster_id = ?").all(d)})(),vt(e),vt(d);let h=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(e),b=s.prepare("SELECT * FROM bug_pattern_clusters WHERE id = ?").get(d);return{original:Oe(h),split:Oe(b),movedMembers:g.map(va)}}function vt(e){try{$g();let t=Ma(e);if(!t)return;let s=Ca(Qn,`${e}.json`),n={schema:"claude-recall.bug-pattern-cluster.v1",cluster:t.cluster,members:t.members,backed_up_at:new Date().toISOString()};Fg(s,JSON.stringify(n,null,2))}catch(t){console.error("[bug-patterns] backup failed:",t)}}function Ba(e){return e?f().prepare(`SELECT * FROM bug_pattern_clusters
1009
+ WHERE signature_hash = ?
1010
+ ORDER BY last_seen_at DESC, id ASC`).all(e).map(Oe):[]}function Ha(e){if(!e)return new Set;let s=f().prepare(`SELECT DISTINCT m.session_id
1011
+ FROM bug_pattern_members m
1012
+ JOIN bug_pattern_clusters c ON c.id = m.cluster_id
1013
+ WHERE c.signature_hash = ?`).all(e);return new Set(s.map(n=>n.session_id))}import{randomUUID as y_}from"node:crypto";H();Z();import{writeFileSync as Gg,mkdirSync as Yg,existsSync as Kg}from"node:fs";import{join as Va}from"node:path";H();var Wa=80;function qa(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(`
1014
+ `)[0].trim();return s.length>Wa?s.slice(0,Wa)+"\u2026":s}function Hg(e){return f().prepare(`SELECT s.id AS id,
1015
+ sa.alias AS alias,
1016
+ s.auto_title AS auto_title,
1017
+ s.first_user_message AS first_user_message
1018
+ FROM sessions s
1019
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1020
+ WHERE s.id = ?`).get(e)??null}function Wg(e){let t=Hg(e);return t?qa(t):e.slice(0,8)}function Ja(e){if(!e)return null;let t=f(),s=t.prepare(`SELECT e.thread_id AS thread_id,
1021
+ t.name AS thread_name,
1022
+ e.parent_session_id AS parent_session_id,
1023
+ e.added_at AS added_at
1024
+ FROM thread_edges e
1025
+ JOIN threads t ON t.id = e.thread_id
1026
+ WHERE e.session_id = ?
1027
+ AND t.archived = 0
1028
+ ORDER BY e.added_at DESC
1029
+ LIMIT 1`).get(e);if(!s)return null;let n=s.parent_session_id?{id:s.parent_session_id,title:Wg(s.parent_session_id)}:null,r=s.parent_session_id?[e,s.parent_session_id]:[e],o=r.map(()=>"?").join(", "),c=t.prepare(`SELECT e.session_id AS session_id,
1030
+ s.id AS id,
1031
+ sa.alias AS alias,
1032
+ s.auto_title AS auto_title,
1033
+ s.first_user_message AS first_user_message
1034
+ FROM thread_edges e
1035
+ LEFT JOIN sessions s ON s.id = e.session_id
1036
+ LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
1037
+ WHERE e.thread_id = ?
1038
+ AND e.session_id NOT IN (${o})
1039
+ ORDER BY e.added_at ASC`).all(s.thread_id,...r).map(d=>({id:d.session_id,title:d.id?qa(d):d.session_id.slice(0,8)}));return{thread_id:s.thread_id,thread_name:s.thread_name,parent_session:n,siblings:c}}var Xa=[/^You will receive a sample of user messages from a Claude Cod/i,/^You will receive the first \d+ user messages from a Claude/i,/^Base directory for this skill:/i,/^You are Claude Code in a fresh terminal/i,/^You are extracting a structured Output Index from a Claude/i];function er(e){return Xa.some(t=>t.test(e))}var qg=[/^Score this person'?s relevance/i,/^You are a [^\n.]{1,60}co-pilot/i,/^Return ONLY valid JSON/i],Jg=[/^Draft (a|an|marketing) [a-z]/i,/^Read the file at /i,/^Read this and then begin/i,/^read this and then begin/i],Xg=20;function jt(e){if(e.auto_title_source==="agent"&&e.auto_title)return"agent";if(e.has_alias)return"manual_alias";if(e.auto_title&&e.auto_title.includes(" \xB7 "))return"fixed_v0.16.1";if(!e.auto_title||e.auto_title.length<Xg)return"low_signal";for(let t of Xa)if(t.test(e.auto_title))return"recursive_meta";for(let t of qg)if(t.test(e.auto_title))return"programmatic";for(let t of Jg)if(t.test(e.auto_title))return"template_pending";return"clean"}function Ls(e){if(!e)return"";let t=e.split("|")[0];return t=t.replace(/\s*\([^)]*\)\s*$/,""),t=t.replace(/\s+/g," ").trim(),t}function Ga(e){if(!e)return e;let t=e.lastIndexOf(" \xB7 ");if(t===-1)return e;let s=e.slice(0,t),n=e.slice(t+3),r=Ls(n);return!r||r===n?e:`${s} \xB7 ${r}`}var nr=Va($,"titles"),zg=80,Vg=60,Zg=100,Qg=50,Mt=5,Cs=15,e_=500;function Za(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 et(e){if(!e)return null;let t=e;if(t=t.replace(/```[\s\S]*?```/g," "),t=t.replace(/`[^`]+`/g," "),t=t.replace(/https?:\/\/\S+/g,"[url]"),t=t.replace(/\s+/g," ").trim(),!t)return null;let s=t_(t,e);if(s)return s;let n=t.match(/^[^.!?\n]{8,}?[.!?]/)?.[0]?.trim();return(n&&n.length<=zg?n:t.slice(0,Vg)).trim()||null}function t_(e,t){let s=e.match(/^\/([A-Za-z0-9][A-Za-z0-9_-]*)\s+([\s\S]*)$/);if(s){let n=`/${s[1]}`,r=t.replace(/^\s*\/[A-Za-z0-9][A-Za-z0-9_-]*\s+/,""),o=rr(r);return o?Qe(`${n} \xB7 ${o}`):n}for(let n of s_){if(!e.match(n.match))continue;let o=n.prefix,a=n.extract?n.extract(e,t):rr(t);return a?n.completeFromExtract?Qe(a):Qe(`${o} \xB7 ${a}`):o}for(let n of o_){if(!e.match(n.match))continue;let o=n.extract?n.extract(e,t):Is(t);return o?n.completeFromExtract?Qe(o):Qe(`${n.prefix} \xB7 ${o}`):n.prefix}return null}var s_=[{match:/^Draft (?:a|an) brand brief\b/i,prefix:"Draft brand brief"},{match:/^Draft (?:a|an) sales brief\b/i,prefix:"Draft sales brief"},{match:/^Draft (?:a|an) leave-behind\b/i,prefix:"Draft leave-behind"},{match:/^Draft (?:a|an) audio identity brief\b/i,prefix:"Draft audio identity brief"},{match:/^Draft marketing ideas\b/i,prefix:"Draft marketing ideas"},{match:/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i,prefix:"Draft",extract:(e,t)=>{let n=e.match(/^Draft (?:a|an) ([a-z]{3,30}(?:\s+[a-z]{3,30}){0,2})\b/i)?.[1]?.trim(),r=rr(t);return n&&r?`${n} \xB7 ${r}`:n||r}},{match:/^Read the file at /i,prefix:"Read",extract:e=>{let s=e.match(/^Read the file at\s+([^\s]+)/i)?.[1];return s?s.split("/").filter(Boolean).slice(-2).join("/")||s:null}},{match:/^(?:read|inspect) this and then begin\b/i,prefix:"Begin from preamble",completeFromExtract:!0,extract:(e,t)=>r_(t)},{match:/^Base directory for this skill:/i,prefix:"[skill]",completeFromExtract:!0,extract:(e,t)=>{let s=t.match(/\.claude\/skills\/([^/\s]+)/);return s?.[1]?`[skill] ${s[1]}`:null}},{match:/^You are extracting a structured Output Index/i,prefix:"[output-index]",completeFromExtract:!0,extract:(e,t)=>n_(t)},{match:/^You will receive a sample of user messages from a Claude Code session:/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>Ya(t,"[meta] auto-title (full-arc)")},{match:/^You will receive the first \d+ user messages from a Claude Code session/i,prefix:"[meta]",completeFromExtract:!0,extract:(e,t)=>Ya(t,"[meta] auto-title (initial)")},{match:/^You are implementing v\d+\.\d+/i,prefix:"Implementing",completeFromExtract:!0,extract:e=>{let t=e.match(/^You are implementing (v\d+\.\d+\w*)\s*(?:\(([^)]+)\))?/i);if(!t)return null;let s=t[1],n=t[2]?.trim();return n?`Implementing ${s} \xB7 ${n}`:`Implementing ${s}`}}];function n_(e){if(!e)return"[output-index] extractor";let t=e.match(/^Session:\s*([0-9a-f]{8})\b/im),s=e.match(/^Opening prompt:\s*([^\n]{1,140})/im),n=t?.[1]?.trim(),r=s?.[1]?.trim().replace(/\s+/g," "),o=r&&r.length>70?r.slice(0,67)+"\u2026":r;return n&&o?`[output-index] \xB7 ${n} \xB7 ${o}`:n?`[output-index] \xB7 ${n}`:o?`[output-index] \xB7 ${o}`:"[output-index] extractor"}function Ya(e,t){if(!e)return t;let s=e.indexOf("Messages:");if(s===-1)return t;let n=e.slice(s+9),r=/^\s*\d+\.\s*([^\n]+)/gm;for(let o of n.matchAll(r)){let a=o[1]?.trim()??"";if(!a)continue;let c=a.replace(/^<[^>]+>[\s\S]*?<\/[^>]+>\s*/g,"").replace(/^\[Pasted text[^\]]*\]\s*/i,"").trim();if(!c||/^<local-command-/.test(c))continue;let d=c.length>60?c.slice(0,57)+"\u2026":c;return`${t} \xB7 ${d}`}return t}function r_(e){if(!e)return null;let t=e.match(/([\/\w.\-]+\.(?:md|markdown|txt|json|yaml|yml|toml|ts|tsx|js|jsx|py|sql))(?=[\s,;:)\]"'`]|$)/i);if(!t)return null;let s=t[1].split("/").filter(Boolean);return(s[s.length-1]??"").replace(/\.[^.]+$/,"")||null}var o_=[{match:/^Score this person'?s relevance/i,prefix:"Score relevance",extract:(e,t)=>Is(t)},{match:/^You are a [^\n.]{1,60}co-pilot/i,prefix:"co-pilot",completeFromExtract:!0,extract:(e,t)=>{let n=e.match(/^You are a ([^\n.]{1,60}?)co-pilot/i)?.[1]?.trim(),r=n?`${n} co-pilot`:"co-pilot",o=Is(t);return o?`${r} \xB7 ${o}`:r}},{match:/^Return ONLY valid JSON/i,prefix:"JSON-only",extract:(e,t)=>Is(t)}];function Is(e){let t=/^\s*(Name|Company|Prospect|Author|Brand|Client)\s*:\s*([^\n]+)/gim,s;for(;(s=t.exec(e))!==null;){let n=s[2].trim();if(!n||/^(null|none|n\/a|—|-)$/i.test(n))continue;let r=n.replace(/\s+/g," ");return Ls(r)||r}return null}function Qe(e){return e.slice(0,Zg).trim()}var i_=[/^deep research$/i,/^reference(?: spec)?$/i,/^canonical spec/i,/^product context/i,/^services?(?: overview)?$/i,/^overview$/i,/^(?:introduction|template|instructions|context)$/i],a_=[/^(?:brand|brand brief|brand summary)$/i,/^(?:known facts?|facts)$/i,/^(?:client|prospect|customer|account)$/i,/^(?:topic|subject|brief|task)$/i,/^(?:about|for|re)$/i];function tr(e){let t=e.trim();return t.length<3?!0:i_.some(s=>s.test(t))}function c_(e){let t=e.trim().replace(/\s*\([^)]*\)\s*$/,"").trim();return a_.some(s=>s.test(t))}function rr(e){let t=l_(e);return t===null?null:Ls(t)||t}function l_(e){if(/^\s*Skill smoke test\b/i.test(e))return"smoke test";let t=/(^|\n)#{1,3}\s+([^\n]+?)\s+(?:—|–|-|:)\s+([^\n]{2,80})/g,s,n=[];for(;(s=t.exec(e))!==null;){let d=s[2].trim(),u=s[3].trim().replace(/\s+/g," ");if(!tr(u)){if(c_(d))return u;n.push(u)}}if(n.length>0)return n[0];let r=e.match(/\b(?:Topic|Subject|Brand|About|Client|For|Re)\s*:\s*([^\n]{2,80})/i);if(r?.[1]&&!tr(r[1]))return r[1].trim().replace(/\s+/g," ");let o=/===\s*([^=\n]{2,120}?)\s*===/g;for(;(s=o.exec(e))!==null;){let d=s[1].trim().replace(/\.(md|txt|json)$/i,""),u=d.replace(/\s*\([^)]*\)\s*$/,"").trim();if(u&&!tr(u)&&!/product context|reference/i.test(d))return u}let a=e.replace(/^Context for this run[^:]*:\s*/i,"");if(a!==e){let d=a.split(`
1040
+ `).map(u=>u.trim()).find(u=>u.length>=4);if(d)return d.slice(0,60)}let c=e.split(`
1041
+ `).map(d=>d.trim()).find(d=>d.length>=4);return c?c.slice(0,60):null}function or(e){let t=f(),s=t.prepare(`SELECT rowid AS rid, content_text
1042
+ FROM messages
1043
+ WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
1044
+ AND content_text IS NOT NULL AND content_text != ''
1045
+ ORDER BY COALESCE(timestamp, ''), rowid ASC
1046
+ LIMIT ?`).all(e,Mt),n=t.prepare(`SELECT rowid AS rid, content_text
1047
+ FROM messages
1048
+ WHERE session_id = ? AND role = 'user' AND is_sidechain = 0
1049
+ AND content_text IS NOT NULL AND content_text != ''
1050
+ ORDER BY COALESCE(timestamp, '') DESC, rowid DESC
1051
+ LIMIT ?`).all(e,Cs),r=new Map;for(let g of s)r.set(g.rid,g.content_text);for(let g of n)r.set(g.rid,g.content_text);if(r.size===0)throw new Error("no user messages available to summarise");let o=Array.from(r.entries()).sort((g,h)=>g[0]-h[0]).map(([,g])=>({content_text:g})),a=s.length===Mt&&n.length===Cs&&r.size===Mt+Cs,c=o.map((g,h)=>{let b=(g.content_text??"").slice(0,e_);return a&&h===Mt?`--- (middle of session omitted) ---
1052
+ ${h+1}. ${b}`:`${h+1}. ${b}`}).join(`
1053
+ `),d=null;try{d=Ja(e)}catch(g){console.error("[autoTitle] thread context resolution failed:",g),d=null}let u=[];return d&&(u.push(d_(d)),u.push("")),u.push(`You will receive a sample of user messages from a Claude Code session: the first ${Mt}`,`messages (initial intent) and the last ${Cs} 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:",c),u.join(`
1054
+ `)}var sr=5;function d_(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 r=e.siblings.slice(0,sr).map(a=>`"${a.title}"`).join(", "),o=s>sr?`, and ${s-sr} more`:"";t.push(`- Sibling sessions (${s}): ${r}${o}`)}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(`
1055
+ `)}async function Qa(e){let t=or(e),{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(ge(),Pe));if(!n())throw new Error("claude CLI not found on PATH");let r=await s(t,[],{});if(!r.success)throw new Error(`claude CLI exited ${r.exitCode}: ${r.stderr.slice(-500)}`);let o=u_(r.stdout);if(!o)throw new Error("claude CLI returned an empty title");return o.slice(0,Qg)}function u_(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 Ka(n)}}catch{}return Ka(t)}function Ka(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}function de(e,t,s){let n=t.trim();if(!n)return;let r=f(),o=r.prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
1056
+ FROM sessions WHERE id = ?`).get(e);if(!o||s==="heuristic"&&o.auto_title_source==="agent"&&o.auto_title||o.auto_title===n&&o.auto_title_source===s)return;let a=Za(o.auto_title_history),c=new Date().toISOString();o.auto_title&&o.auto_title_source&&a.push({title:o.auto_title,source:o.auto_title_source,replaced_at:c}),r.prepare(`UPDATE sessions
1057
+ SET auto_title = ?,
1058
+ auto_title_source = ?,
1059
+ auto_title_generated_at = ?,
1060
+ auto_title_history = ?
1061
+ WHERE id = ?`).run(n,s,Date.now(),JSON.stringify(a),e),__(e,n,s,c)}function Te(e){let t=f().prepare(`SELECT auto_title, auto_title_source, auto_title_generated_at, auto_title_history
1062
+ 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:Za(t.auto_title_history)}:null}function ec(){let t=f().prepare(`SELECT id, first_user_message
1063
+ FROM sessions
1064
+ WHERE auto_title IS NULL
1065
+ AND first_user_message IS NOT NULL`).all(),s=0;for(let n of t){let r=et(n.first_user_message);r&&(de(n.id,r,"heuristic"),s+=1)}return{updated:s}}function tc(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId,e.projectId]:[],r=t.prepare(`WITH dups AS (
1066
+ SELECT auto_title, project_id
1067
+ FROM sessions
1068
+ WHERE auto_title IS NOT NULL
1069
+ AND auto_title_source = 'heuristic'
1070
+ AND auto_title NOT LIKE 'You are Claude Code in a fresh terminal%'
1071
+ GROUP BY auto_title, project_id
1072
+ HAVING COUNT(*) > 1
1073
+ )
1074
+ SELECT s.id
1075
+ FROM sessions s
1076
+ WHERE s.auto_title_source = 'heuristic'${s}
1077
+ AND s.auto_title NOT LIKE 'You are Claude Code in a fresh terminal%'
1078
+ AND (
1079
+ (s.auto_title LIKE '/%' AND s.auto_title NOT LIKE '% \xB7 %')
1080
+ OR (s.auto_title LIKE 'Draft %' AND s.auto_title NOT LIKE '% \xB7 %')
1081
+ OR (s.auto_title LIKE 'Read the file at %')
1082
+ OR (s.auto_title LIKE 'read this and then begin%')
1083
+ OR (s.auto_title LIKE 'Begin from preamble%')
1084
+ OR (s.auto_title LIKE 'inspect this and then begin%')
1085
+ OR (s.auto_title LIKE '**Tool result**%')
1086
+ OR (s.auto_title LIKE 'Base directory for this skill%')
1087
+ OR (s.auto_title LIKE '[skill]%')
1088
+ OR (s.auto_title LIKE '[output-index]%')
1089
+ OR (s.auto_title LIKE '[meta]%')
1090
+ OR (s.auto_title LIKE 'You are extracting%')
1091
+ OR (s.auto_title LIKE 'You will receive%')
1092
+ OR (s.auto_title LIKE 'You are implementing%')
1093
+ OR (s.auto_title LIKE 'Implementing v%')
1094
+ OR s.auto_title = 'All right.'
1095
+ OR s.auto_title = 'Alright.'
1096
+ OR s.auto_title = 'inspect this.'
1097
+ OR s.auto_title = 'resolve this.'
1098
+ OR s.auto_title = 'do it.'
1099
+ OR s.auto_title = 'continue.'
1100
+ OR s.auto_title = '**Tool result**'
1101
+ OR (s.auto_title LIKE '[vacuous]%')
1102
+ OR (s.auto_title LIKE 'Score this person%' AND s.auto_title NOT LIKE '% \xB7 %')
1103
+ OR (s.auto_title LIKE 'You are a%co-pilot%' AND s.auto_title NOT LIKE '% \xB7 %')
1104
+ OR (s.auto_title LIKE 'Return ONLY valid JSON%' AND s.auto_title NOT LIKE '% \xB7 %')
1105
+ OR EXISTS (
1106
+ SELECT 1 FROM dups d
1107
+ WHERE d.auto_title = s.auto_title
1108
+ AND d.project_id = s.project_id
1109
+ )
1110
+ )`).all(...n),o=t.prepare(`SELECT content_text
1111
+ FROM messages
1112
+ WHERE session_id = ?
1113
+ AND role = 'user'
1114
+ AND is_sidechain = 0
1115
+ AND content_text IS NOT NULL
1116
+ AND content_text != ''
1117
+ ORDER BY COALESCE(timestamp, ''), rowid ASC
1118
+ LIMIT 8`),a=0,c=0;for(let d of r){a+=1;let u=o.all(d.id),g=null;for(let h of u){let b=h.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim();if(!b||/^<local-command-caveat>/.test(b))continue;let S=et(b);if(S&&!za(S)){g=S;break}}if(!g){let h=u.map(S=>S.content_text.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim()).find(S=>S.length>0&&!/^<local-command-caveat>/.test(S)),b=h?et(h):null;b&&za(b)&&(g=`[vacuous] ${b}`)}g&&(de(d.id,g,"heuristic"),c+=1)}return{scanned:a,updated:c}}function za(e){let t=e.trim();return!t||/^\*\*Tool result\*\*$/i.test(t)?!0:[/^all right\.?$/i,/^alright\.?$/i,/^inspect this\.?$/i,/^resolve this\.?$/i,/^do it\.?$/i,/^go\.?$/i,/^continue\.?$/i,/^proceed\.?$/i,/^keep going\.?$/i,/^ok\.?$/i,/^okay\.?$/i,/^yes\.?$/i,/^yep\.?$/i].some(n=>n.test(t))}function sc(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
1119
+ FROM sessions s
1120
+ WHERE s.auto_title_source = 'heuristic'${s}
1121
+ AND (
1122
+ s.auto_title LIKE 'You will receive %'
1123
+ OR s.auto_title LIKE 'Base directory for this skill:%'
1124
+ OR s.auto_title LIKE 'You are Claude Code in a fresh terminal%'
1125
+ )`).all(...n),o=t.prepare(`SELECT content_text
1126
+ FROM messages
1127
+ WHERE session_id = ?
1128
+ AND role = 'user'
1129
+ AND is_sidechain = 0
1130
+ AND content_text IS NOT NULL
1131
+ AND content_text != ''
1132
+ ORDER BY COALESCE(timestamp, ''), rowid ASC
1133
+ LIMIT 10`),a=0,c=0;for(let d of r){a+=1;let u=o.all(d.id),g=p_(u,d.auto_title);g&&(de(d.id,g,"heuristic"),c+=1)}return{scanned:a,updated:c}}function p_(e,t){for(let s of e){let n=m_(s.content_text);if(!n||er(n))continue;let r=et(n);if(r){if(r===t)return null;if(!er(r))return r}}return t.startsWith("[meta]")?null:Qe(`[meta] ${t}`)}function nc(e){let t=f(),s=e?.projectId?" AND s.project_id = ?":"",n=e?.projectId?[e.projectId]:[],r=t.prepare(`SELECT s.id, s.auto_title
1134
+ FROM sessions s
1135
+ WHERE s.auto_title_source = 'heuristic'${s}
1136
+ AND s.auto_title IS NOT NULL
1137
+ AND s.auto_title LIKE '% \xB7 %'
1138
+ AND (s.auto_title LIKE '%|%' OR s.auto_title LIKE '%(%')`).all(...n),o=0,a=0;for(let c of r){o+=1;let d=Ga(c.auto_title);d!==c.auto_title&&(de(c.id,d,"heuristic"),a+=1)}return{scanned:o,updated:a}}function m_(e){return e.replace(/<command-(?:name|message|args|stdout|stderr)>[\s\S]*?<\/command-(?:name|message|args|stdout|stderr)>/g,"").replace(/<local-command-(?:stdout|stderr|caveat)>[\s\S]*?<\/local-command-(?:stdout|stderr|caveat)>/g,"").trim()}function g_(){J(),Kg(nr)||Yg(nr,{recursive:!0})}function __(e,t,s,n){try{g_();let r=Va(nr,`${e}.txt`),o=`# Claude Recall auto-title \xB7 session ${e} \xB7 source ${s} \xB7 updated ${n}
1139
+ `;Gg(r,o+t+`
1140
+ `)}catch(r){console.error("[autoTitle] mirror write failed:",r)}}var f_=50;function h_(e){if(e.length===0)return[];let t=[...e].sort((d,u)=>d.added_at<u.added_at?-1:d.added_at>u.added_at?1:0),s=new Map,n=new Set,r=[];for(let d of t)if(d.parent_session_id){let u=s.get(d.parent_session_id)??[];u.push(d),s.set(d.parent_session_id,u)}let o=t.filter(d=>d.role==="origin"),c=[...o.length>0?o:t.filter(d=>!d.parent_session_id)];for(;c.length>0;){let d=c.shift();if(n.has(d.session_id))continue;n.add(d.session_id),r.push(d.session_id);let u=s.get(d.session_id)??[];for(let g of u)n.has(g.session_id)||c.push(g)}for(let d of t)n.has(d.session_id)||(n.add(d.session_id),r.push(d.session_id));return r}function E_(e){let t=or(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(`
1141
+ `);return`${t}
1142
+ ${s}`}function b_(e){return Te(e)?.auto_title_source??null}async function S_(e){let{spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(ge(),Pe));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 r=T_(n.stdout);if(!r)throw new Error("claude CLI returned an empty title");return r.slice(0,f_)}function T_(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 rc(n)}}catch{}return rc(t)}function rc(e){return e.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/\s+/g," ").replace(/[.!?]+$/g,"").trim()}async function ic(e,t={}){let s=te(e);if(!s)throw new Error(`thread not found: ${e}`);let n=h_(s.edges),r=n.length,o=t.force??!1,a=t.signal,c=[],d=[],u=[];for(let g=0;g<n.length;g++){let h=n[g],b=g+1;if(a?.aborted){let y={sessionId:h,reason:"cancelled"};d.push(y),t.onSkipped?.(y);continue}if(!o&&b_(h)==="agent"){let y={sessionId:h,reason:"already-titled"};d.push(y),t.onSkipped?.(y);continue}let S;try{if(oc)S=await oc({sessionId:h,current:b,total:r});else{let y=E_({sessionId:h,current:b,total:r});S=await S_({prompt:y,model:t.model})}}catch(y){let k=y instanceof Error?y.message:String(y??"unknown error"),w={sessionId:h,error:k};u.push(w),t.onFailed?.(w);continue}try{de(h,S,"agent")}catch(y){let k=y instanceof Error?y.message:String(y??"unknown error"),w={sessionId:h,error:`setAutoTitle failed: ${k}`};u.push(w),t.onFailed?.(w);continue}c.push(h),t.onProgress?.({current:b,total:r,sessionId:h,title:S})}return{generated:c,skipped:d,failed:u}}var oc=null;var Ft=new Map,w_=300*1e3;function Dt(e,t,s){let n=e.events.length+1;e.events.push({id:n,kind:t,data:s});for(let r of e.waiters)r.resolve();e.waiters.clear()}function R_(e){e.cleanupTimer&&clearTimeout(e.cleanupTimer),e.cleanupTimer=setTimeout(()=>{Ft.delete(e.jobId)},w_)}function k_(e){let t=0,s=0,n=0;for(let r of e.events)r.kind==="progress"&&(t+=1),r.kind==="skipped"&&(s+=1),r.kind==="error"&&(n+=1);return{jobId:e.jobId,threadId:e.threadId,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total:e.total,done:t,skipped:s,failed:n,result:e.result}}function ac(e){let t=y_(),s=new AbortController,n={jobId:t,threadId:e.threadId,status:"running",startedAt:new Date().toISOString(),endedAt:null,total:0,events:[],waiters:new Set,controller:s,result:null,cleanupTimer:null};return Ft.set(t,n),(async()=>{await Promise.resolve();try{let r=await ic(e.threadId,{force:e.force??!1,signal:s.signal,model:e.model,onProgress:o=>{n.total=o.total,Dt(n,"progress",o)},onSkipped:o=>{Dt(n,"skipped",o)},onFailed:o=>{Dt(n,"error",o)}});n.result=r,n.status=s.signal.aborted?"cancelled":"done",n.endedAt=new Date().toISOString(),Dt(n,"done",r)}catch(r){let o=r instanceof Error?r.message:String(r??"unknown error");n.result={generated:[],skipped:[],failed:[{sessionId:e.threadId,error:o}]},n.status="failed",n.endedAt=new Date().toISOString(),Dt(n,"done",n.result)}finally{R_(n)}})(),t}async function*cc(e,t=0){let s=Ft.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(n+=1,yield r,r.kind==="done")return}if(s.endedAt)return;await new Promise(r=>{s.waiters.add({resolve:r})})}}function lc(e){let t=Ft.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function ir(e){let t=Ft.get(e);return t?k_(t):null}Z();import{existsSync as A_,readFileSync as N_,writeFileSync as x_}from"node:fs";import{join as O_}from"node:path";import{z as ae}from"zod";var ar=O_($,"terminals.json"),dc=1440*60*1e3,L_=3e4,C_=6e4,I_=ae.object({shell_pid:ae.number(),tab_name:ae.string(),cwd:ae.string().nullable().optional(),opened_at:ae.string(),last_seen_at:ae.string()}),v_=ae.object({schema:ae.string().optional(),saved_at:ae.string().optional(),terminals:ae.array(I_).max(500).default([]),sessions_by_pid:ae.record(ae.string(),ae.array(ae.string()).max(50)).optional().default({})}),uc=/^[⠀-⣿✳\s]+/,pc=/^\d+(\.\d+){1,3}$/;function ne(e){let t=e.trim();return!!(!t||uc.test(t)||pc.test(t))}function tt(e){let t=e.trim();if(!t||pc.test(t))return null;let s=t.replace(uc,"").trim();return s.length>0?s:null}function cr(e,t){if(!ne(e))return e;let s=tt(e);return s||(t&&!ne(t)?t:e)}function j_(e){let t=e.now-e.withinMs,s=e.pending.filter(n=>{let r=Date.parse(n.started_at);return Number.isFinite(r)&&r>=t});if(s.length===0)return{kind:"none"};if(e.shellPid!=null){let n=s.find(r=>r.shell_pid===e.shellPid);if(n)return{kind:"pid-match",entry:n}}if(e.cwd){let n=e.cwd.replace(/\/+$/,""),r=s.filter(o=>o.cwd&&o.cwd.replace(/\/+$/,"")===n);if(r.length===1)return{kind:"singleton-cwd",entry:r[0]};if(r.length>=2)return{kind:"ambiguous",candidates:r}}return{kind:"none"}}var lr=class{entries=new Map;origins=new Map;sessionsByPid=new Map;pendingClaudeStarts=[];deferredLinks=new Map;pidOwnership=new Map;loaded=!1;ensureLoaded(){if(!this.loaded&&(this.loaded=!0,!!A_(ar)))try{let t=N_(ar,"utf8"),s=JSON.parse(t),n=v_.safeParse(s);if(!n.success){console.warn("[terminal-registry] terminals.json failed validation, starting with empty registry:",n.error.issues);return}let r=n.data;for(let o of r.terminals)this.entries.set(o.shell_pid,{shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd??null,opened_at:o.opened_at,last_seen_at:o.last_seen_at});for(let[o,a]of Object.entries(r.sessions_by_pid??{})){let c=Number(o);if(!Number.isFinite(c))continue;let d=new Set;for(let u of a)u.length>0&&d.add(u);d.size>0&&this.sessionsByPid.set(c,d)}this.gc()}catch{}}save(){try{J();let t={schema:"claude-recall.terminals.v1",saved_at:new Date().toISOString(),terminals:Array.from(this.entries.values()),sessions_by_pid:Object.fromEntries(Array.from(this.sessionsByPid.entries()).map(([s,n])=>[String(s),Array.from(n)]))};x_(ar,JSON.stringify(t,null,2))}catch{}}upsert(t){this.ensureLoaded();let s=new Date().toISOString(),n=this.entries.get(t.shell_pid),r=cr(t.tab_name,n?.tab_name),o=n?.opened_at??t.opened_at,a={...t,tab_name:r,opened_at:o,last_seen_at:s};return this.entries.set(t.shell_pid,a),this.gc(),this.save(),a}rename(t,s){this.ensureLoaded();let n=this.entries.get(t);if(!n)return null;let r=cr(s,n.tab_name),o={...n,tab_name:r,last_seen_at:new Date().toISOString()};return this.entries.set(t,o),this.save(),o}remove(t){this.ensureLoaded();let s=this.entries.delete(t),n=this.sessionsByPid.delete(t);return this.outputTails.delete(t),this.pidOwnership.delete(t),(s||n)&&this.save(),s}claimPidOwnership(t,s,n=Date.now()){if(this.ensureLoaded(),!s)return"anonymous";let r=this.pidOwnership.get(t);return r?r.instance_id===s?(r.last_claim_at=n,"refreshed"):n-r.last_claim_at>C_?(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed"):"rejected":(this.pidOwnership.set(t,{instance_id:s,last_claim_at:n}),"claimed")}pidOwnershipSnapshot(){return this.ensureLoaded(),Array.from(this.pidOwnership.entries()).map(([t,s])=>({shell_pid:t,instance_id:s.instance_id,last_claim_at:s.last_claim_at}))}sync(t){this.ensureLoaded();let s=new Date().toISOString(),n=0,r=0;for(let o of t){let a=this.entries.get(o.shell_pid),c=cr(o.tab_name,a?.tab_name),d=a?.opened_at??o.opened_at;this.entries.set(o.shell_pid,{...o,tab_name:c,opened_at:d,last_seen_at:s}),a?(a.tab_name!==c||a.cwd!==o.cwd)&&r++:n++}return this.gc(),this.save(),{added:n,updated:r,removed:0}}get(t){return this.ensureLoaded(),this.entries.get(t)??null}all(){return this.ensureLoaded(),this.gc(),Array.from(this.entries.values())}size(){return this.ensureLoaded(),this.entries.size}linkSession(t,s){this.ensureLoaded();let n=this.sessionsByPid.get(s);n||(n=new Set,this.sessionsByPid.set(s,n)),n.has(t)||(n.add(t),this.save())}sessionsFor(t){return this.ensureLoaded(),Array.from(this.sessionsByPid.get(t)??[])}isSessionAutoLinked(t){this.ensureLoaded();for(let s of this.sessionsByPid.values())if(s.has(t))return!0;return!1}unlinkSession(t){this.ensureLoaded();let s=!1;for(let[n,r]of this.sessionsByPid)r.delete(t)&&(s=!0,r.size===0&&this.sessionsByPid.delete(n));return s&&this.save(),s}pushPending(t){this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.push({...t})}takePendingMatched(t){this.ensureLoaded(),this.gcPending();let s=j_({pending:this.pendingClaudeStarts,shellPid:t.shellPid,cwd:t.cwd,withinMs:t.withinMs,now:Date.now()});if(s.kind==="pid-match"||s.kind==="singleton-cwd"){let n=this.pendingClaudeStarts.indexOf(s.entry);n>=0&&this.pendingClaudeStarts.splice(n,1)}return s}pendingSize(){return this.ensureLoaded(),this.gcPending(),this.pendingClaudeStarts.length}deferSessionLink(t,s,n,r){this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.set(t,{parent_shell_pid:s,queued_at:Date.now(),cwd:n,git_branch:r})}allDeferredLinks(){return this.ensureLoaded(),this.gcDeferredLinks(),Array.from(this.deferredLinks.entries()).map(([t,s])=>({session_id:t,...s}))}resolveDeferredLink(t){return this.deferredLinks.delete(t)}deferredLinkSize(){return this.ensureLoaded(),this.gcDeferredLinks(),this.deferredLinks.size}gcDeferredLinks(){let t=Date.now()-9e4;for(let[s,n]of this.deferredLinks)n.queued_at<t&&this.deferredLinks.delete(s)}gcPending(){let t=Date.now()-L_;this.pendingClaudeStarts.length!==0&&(this.pendingClaudeStarts=this.pendingClaudeStarts.filter(s=>{let n=Date.parse(s.started_at);return Number.isFinite(n)&&n>=t}))}outputTails=new Map;setOutputTail(t,s,n){this.ensureLoaded(),this.outputTails.set(t,{text:s,captured_at:n})}getOutputTail(t){return this.ensureLoaded(),this.outputTails.get(t)??null}allOutputTails(){return this.ensureLoaded(),new Map(this.outputTails)}removeOutputTail(t){return this.outputTails.delete(t)}setOrigin(t,s){this.ensureLoaded(),this.origins.set(t,s),this.gcOrigins()}getOrigin(t){return this.ensureLoaded(),this.origins.get(t)??null}removeOrigin(t){return this.ensureLoaded(),this.origins.delete(t)}allOrigins(){return this.ensureLoaded(),this.gcOrigins(),new Map(this.origins)}originSize(){return this.ensureLoaded(),this.origins.size}gc(){let t=Date.now()-dc;for(let[s,n]of this.entries){let r=Date.parse(n.last_seen_at);!Number.isNaN(r)&&r<t&&this.entries.delete(s)}}gcOrigins(){let t=Date.now()-dc;for(let[s,n]of this.origins)n.detectedAt<t&&this.origins.delete(s)}reapStaleLinks(t=10080*60*1e3){this.ensureLoaded();let n=Date.now()-t,r=0,o=0;for(let[a,c]of this.sessionsByPid){let d=this.entries.get(a);if(d){let u=Date.parse(d.last_seen_at);if(Number.isFinite(u)&&u>=n)continue}o+=c.size,this.sessionsByPid.delete(a),d&&(this.entries.delete(a),r++)}return(r||o)&&this.save(),{pruned_pids:r,pruned_sessions:o}}gcDeadPids(){this.ensureLoaded();let t=0,s=0;for(let n of[...this.entries.keys()]){let r=!0;try{process.kill(n,0)}catch(a){a.code==="ESRCH"&&(r=!1)}if(r)continue;this.entries.delete(n),t++;let o=this.sessionsByPid.get(n);o&&(s+=o.size,this.sessionsByPid.delete(n)),this.outputTails.delete(n),this.pidOwnership.delete(n)}return(t||s)&&this.save(),{pruned_pids:t,pruned_sessions:s}}},I=new lr;import{execFile as q_}from"node:child_process";import{promisify as J_}from"node:util";import{existsSync as X_}from"node:fs";import{basename as G_}from"node:path";H();import{execFile as M_}from"node:child_process";import{readFile as D_}from"node:fs/promises";import{promisify as F_}from"node:util";var P_=F_(M_),mc=["CURSOR_TRACE_ID","VSCODE_PID","VSCODE_INJECTION","TERM_PROGRAM","TERM_PROGRAM_VERSION","TERM","WT_SESSION","KITTY_WINDOW_ID","ALACRITTY_SOCKET","WARP_HONOR_PS1"];function U_(e){let t={};for(let s of mc){let n=e[s];typeof n=="string"&&n.length>0&&(t[s]=n)}return t}function $_(e){let t=Date.now();if(e.CURSOR_TRACE_ID)return{editor:"cursor",label:"Cursor",detectedAt:t};if(e.VSCODE_PID||e.VSCODE_INJECTION)return{editor:"vscode",label:"VS Code",detectedAt:t};let s=e.TERM_PROGRAM;return s==="WarpTerminal"?{editor:"warp",label:"Warp",detectedAt:t}:s==="iTerm.app"?{editor:"iterm",label:"iTerm",detectedAt:t}:s==="Apple_Terminal"?{editor:"apple-terminal",label:"Terminal",detectedAt:t}:s==="WezTerm"?{editor:"wezterm",label:"WezTerm",detectedAt:t}:e.WT_SESSION?{editor:"windows-terminal",label:"Windows Terminal",detectedAt:t}:e.KITTY_WINDOW_ID?{editor:"kitty",label:"Kitty",detectedAt:t}:e.TERM==="alacritty"||e.ALACRITTY_SOCKET?{editor:"alacritty",label:"Alacritty",detectedAt:t}:null}async function B_(e){if(process.platform==="linux")try{let t=await D_(`/proc/${e}/environ`,"utf8");return H_(t)}catch{return{}}try{let{stdout:t}=await P_("/bin/ps",["eww","-o","command=","-p",String(e)],{timeout:2e3,maxBuffer:1048576});return W_(t)}catch{return{}}}function H_(e){let t={};for(let s of e.split("\0")){if(!s)continue;let n=s.indexOf("=");if(n<=0)continue;let r=s.slice(0,n),o=s.slice(n+1);t[r]=o}return t}function W_(e){let t={},s=new Set(mc),n=e.replace(/\s+/g," ").trim().split(" ");for(let r of n){let o=r.indexOf("=");if(o<=0)continue;let a=r.slice(0,o);s.has(a)&&(t[a]=r.slice(o+1))}return t}async function gc(e){if(!Number.isFinite(e)||e<=0)return null;try{let t=await B_(e),s=U_(t);return $_(s)}catch{return null}}var js=J_(q_),vs;function Y_(){if(vs!==void 0)return vs;let e=["/usr/sbin/lsof","/usr/bin/lsof","/opt/homebrew/bin/lsof"];for(let t of e)if(X_(t))return vs=t,t;return vs=null,null}async function K_(e){let t=Y_();if(!t)return null;try{let{stdout:s}=await js(t,["-Fpc",e],{timeout:2e3}),n=s.split(`
1143
+ `),r=null,o=null,a=null;for(let c of n)c.startsWith("p")?(a=Number(c.slice(1)),Number.isFinite(a)&&a>0?r==null&&(r=a):a=null):c.startsWith("c")&&a!=null&&c.slice(1).trim()==="claude"&&o==null&&(o=a);return o??r}catch{return null}}async function _c(e){try{let{stdout:t}=await js("/bin/ps",["-o","ppid=","-p",String(e)],{timeout:2e3}),s=Number(t.trim());return Number.isFinite(s)&&s>0?s:null}catch{return null}}async function z_(e){try{let{stdout:t}=await js("/bin/ps",["-o","lstart=","-p",String(e)],{timeout:2e3}),s=Date.parse(t.trim());return Number.isFinite(s)?s:null}catch{return null}}var V_=new Set(["zsh","bash","fish","sh","dash","ksh","tcsh","csh","pwsh","powershell","cmd","nu","node","deno","bun","python","python3","ruby","ts-node","tsx","go","cargo","java","php","irb","pry","iex","terminal","shell","console"]);function re(e){let t=e.trim().toLowerCase();if(!t)return!0;let s=t.replace(/^[-/]+/,"").replace(/^.*\//,"").replace(/\s*\(\d+\)\s*$/,"").trim();return V_.has(s)}function st(e){let t=e.tabName?.trim();return t&&!re(t)&&!ne(t)?t:null}function Z_(e){try{return f().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(e)??null}catch{return null}}async function Ms(e){let t=G_(e,".jsonl");if(Se(t))return;let s=Z_(t),n=await K_(e),r=n?await _c(n):null,o=n?await gc(n):null;o&&I.setOrigin(t,o);let a=null,c=null,d=null,u=I.takePendingMatched({shellPid:r,cwd:s?.cwd??null,withinMs:3e4});if(u.kind==="ambiguous"){console.log(`[correlator] ambiguous pending for ${t.slice(0,8)} (${u.candidates.length} candidates in ${s?.cwd??"?"}) \u2014 refusing to guess; heuristic title will display`);return}if(u.kind==="pid-match"||u.kind==="singleton-cwd"){let y=u.entry;c=y.shell_pid,d=u.kind==="pid-match"?"pending-pid":"pending-cwd",a=I.get(y.shell_pid)??{shell_pid:y.shell_pid,tab_name:y.tab_name,cwd:y.cwd,opened_at:y.started_at,last_seen_at:y.started_at}}let g=null;if(!a&&n){let y=n;for(let k=0;k<4&&y!=null;k++){let w=await _c(y);if(!w)break;let D=I.get(w);if(D){a=D,c=w,d="ppid";break}g==null&&(g=w),y=w}}if(!a&&s?.cwd){let y=s.cwd.replace(/\/+$/,""),k=I.all().filter(w=>w.cwd&&w.cwd.replace(/\/+$/,"")===y);k.length===1?(a=k[0],c=a.shell_pid,d="cwd"):k.length>=2&&console.log(`[correlator] ${k.length} registered terminals in ${y} for ${t.slice(0,8)} \u2014 refusing to guess; heuristic title will display`)}let h=null;if(a?.tab_name&&!re(a.tab_name)&&!ne(a.tab_name))h=a.tab_name;else if(a?.tab_name&&ne(a.tab_name)){let y=tt(a.tab_name);y&&!re(y)&&(h=y)}if(!h&&!(d==="pending-pid"||d==="pending-cwd")&&a&&s?.cwd){let y=s.cwd.replace(/\/+$/,""),k=I.all().filter(w=>w.shell_pid!==c&&w.cwd&&w.cwd.replace(/\/+$/,"")===y&&!re(w.tab_name)&&!ne(w.tab_name)).sort((w,D)=>Date.parse(D.last_seen_at)-Date.parse(w.last_seen_at))[0];k&&(h=k.tab_name)}let S=st({tabName:h,origin:o,cwd:s?.cwd??null,gitBranch:s?.git_branch??null});if(g!=null&&!c&&I.deferSessionLink(t,g,s?.cwd??null,s?.git_branch??null),!!S)try{me(t,S);let y=!!h&&!re(h)&&!ne(h)&&S===h.trim();c!=null&&I.linkSession(t,c);let k=d==="pending-pid"?"pending PID-exact match":d==="pending-cwd"?"pending cwd-singleton match":d??"unknown";console.log(`[correlator] auto-aliased ${t.slice(0,8)} \u2192 "${S}"`+(y?` (tab name via ${k}, shell pid ${c})`:a?` (generic shell name "${a.tab_name}" \u2192 ${o?.editor??"origin"} fallback, shell pid ${c})`:o?` (${o.editor} origin \u2014 no terminal match)`:""))}catch{}}async function Q_(){try{let{stdout:e}=await js("/bin/ps",["-eo","pid=,ppid=,comm="],{timeout:2e3}),t=new Map;for(let s of e.split(`
1144
+ `)){let n=s.trim();if(!n)continue;let r=n.match(/^(\d+)\s+(\d+)\s+(.+)$/);if(!r)continue;let o=Number(r[1]),a=Number(r[2]),c=r[3].trim();(c==="claude"||c.endsWith("/claude")||c.endsWith("/bin/claude"))&&(!Number.isFinite(o)||!Number.isFinite(a)||t.set(o,a))}return t}catch{return new Map}}var ef=9e4;function tf(e){let t=(Date.now()-ef)/1e3,s=e.replace(/\/+$/,"");return f().prepare(`SELECT s.id, NULLIF(sa.alias, '') AS alias, s.started_at AS started_at
1145
+ FROM sessions s
1146
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1147
+ WHERE s.cwd = ? AND s.file_mtime > ?`).all(s,t).map(r=>({id:r.id,alias:r.alias,started_at_ms:r.started_at?Date.parse(r.started_at):null}))}async function sf(e){let t=await import("node:fs/promises"),s="";try{let r=await t.open(e,"r");try{let o=Buffer.alloc(32768),{bytesRead:a}=await r.read(o,0,o.length,0);s=o.toString("utf8",0,a)}finally{await r.close()}}catch{return[]}let n=[];for(let r of s.split(`
1148
+ `)){if(!r.trim())continue;let o;try{o=JSON.parse(r)}catch{continue}let a=o;if(!a||a.type!=="user"&&a.type!=="assistant")continue;let c=a.message?.content,d="";if(typeof c=="string")d=c;else if(Array.isArray(c))for(let u of c)u&&typeof u=="object"&&"text"in u&&typeof u.text=="string"&&(d+=u.text+`
1149
+ `);if(d){for(let u of d.split(/\r?\n/)){let g=u.trim();g.length>=60&&g.length<=400&&n.push(g)}if(n.length>=8)break}}return n}async function fc(e){if(Se(e))return null;let t=f().prepare("SELECT cwd, file_path FROM sessions WHERE id = ?").get(e);if(!t?.cwd||!t.file_path)return null;let s=await sf(t.file_path);if(s.length===0)return null;let n=t.cwd.replace(/\/+$/,""),r=I.allOutputTails(),o=[];for(let[a,c]of r){let d=I.get(a);if(!d||!d.cwd||d.cwd.replace(/\/+$/,"")!==n||re(d.tab_name)||ne(d.tab_name))continue;let u=0;for(let g of s)c.text.includes(g)&&u++;u>0&&o.push({shell_pid:a,tab_name:d.tab_name,matched_fingerprints:u})}return o.length===0||(o.sort((a,c)=>c.matched_fingerprints-a.matched_fingerprints),o.length>1&&o[0].matched_fingerprints===o[1].matched_fingerprints)?null:o[0]}var nf=3e4;function hc(){let e=Date.now(),t=I.all(),s=o=>{let a=Date.parse(o.last_seen_at);return!Number.isFinite(a)||e-a>nf},n=new Map;for(let o of t){if(s(o)||!o.cwd||re(o.tab_name)||ne(o.tab_name))continue;let a=`${o.cwd.replace(/\/+$/,"")}::${o.tab_name}`,c=n.get(a);c||(c=[],n.set(a,c)),c.push({shell_pid:o.shell_pid,tab_name:o.tab_name,cwd:o.cwd})}let r={rebound:0,ghosts:0,ambiguous:0};for(let o of t){if(!s(o))continue;let a=I.sessionsFor(o.shell_pid);if(a.length!==0){r.ghosts++;for(let c of a){let d=Se(c);if(!d)continue;let u=f().prepare("SELECT cwd FROM sessions WHERE id = ?").get(c);if(!u?.cwd)continue;let g=`${u.cwd.replace(/\/+$/,"")}::${d}`,h=n.get(g)??[];if(h.length===0)continue;if(h.length>1){r.ambiguous++;continue}let b=h[0];b.shell_pid!==o.shell_pid&&(I.unlinkSession(c),I.linkSession(c,b.shell_pid),r.rebound++)}}}return r}function Ec(){let e={resolved:0,expired:0},t=I.allDeferredLinks();for(let s of t){let n=I.get(s.parent_shell_pid);if(!n||re(n.tab_name)||ne(n.tab_name))continue;let r=Se(s.session_id);if(r&&!I.isSessionAutoLinked(s.session_id)){I.resolveDeferredLink(s.session_id);continue}let o=I.getOrigin(s.session_id),a=st({tabName:n.tab_name,origin:o??null,cwd:s.cwd,gitBranch:s.git_branch});if(!a){I.resolveDeferredLink(s.session_id);continue}r!==a&&me(s.session_id,a),I.linkSession(s.session_id,s.parent_shell_pid),I.resolveDeferredLink(s.session_id),e.resolved++}return e}var rf=6e4;async function Ds(){let e=await Q_(),t={scanned:e.size,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0};if(e.size===0)return t;let s=[];for(let[a,c]of e){let d=I.get(c);if(!d||!d.cwd||re(d.tab_name)||ne(d.tab_name))continue;let u=d.tab_name.trim();if(!u)continue;let g=await z_(a);s.push({claudePid:a,shellPid:c,cwd:d.cwd.replace(/\/+$/,""),target:u,startTimeMs:g})}let n=new Map,r=a=>{let c=n.get(a);if(c)return c;let d=tf(a);return n.set(a,d),d},o=new Set;for(let a of s){let c=r(a.cwd).filter(u=>!o.has(u.id));if(c.length===0)continue;let d=null;if(a.startTimeMs!=null){let u=rf;for(let g of c){if(g.started_at_ms==null)continue;let h=Math.abs(g.started_at_ms-a.startTimeMs);h<u&&(u=h,d=g)}}if(!d&&c.length===1&&(d=c[0]),!d){t.ambiguous_cwd++;continue}if(o.add(d.id),d.alias&&!I.isSessionAutoLinked(d.id)){t.skipped_manual++;continue}if(d.alias===a.target){I.linkSession(d.id,a.shellPid);continue}try{me(d.id,a.target),I.linkSession(d.id,a.shellPid),d.alias?t.renamed++:t.linked++,console.log(`[correlator] linked ${d.id.slice(0,8)} \u2192 "${a.target}" (live sweep, claude pid ${a.claudePid}, shell pid ${a.shellPid})`)}catch{}}return t}import{existsSync as bc,mkdirSync as of,readFileSync as af,writeFileSync as cf,chmodSync as lf}from"node:fs";import{homedir as df}from"node:os";import{join as Sc}from"node:path";import{z as Le}from"zod";function Tc(){return process.env.RECALL_HOME??Sc(df(),".recall")}function uf(){let e=Tc();bc(e)||of(e,{recursive:!0})}function yc(){return Sc(Tc(),"config.json")}var Ps=Le.object({enabled:Le.boolean().default(!1),backend:Le.enum(["api","mcp"]).default("api"),apiKey:Le.string().optional(),model:Le.string().default("claude-opus-4-7"),maxTagsPerSession:Le.number().int().min(1).max(10).default(4),minTagsPerSession:Le.number().int().min(1).max(10).default(2),autopilot:Le.boolean().default(!1)}),Fs={enabled:!1,backend:"api",model:"claude-opus-4-7",maxTagsPerSession:4,minTagsPerSession:2,autopilot:!1};function wc(){let e=yc();if(!bc(e))return{};try{return JSON.parse(af(e,"utf8"))}catch(t){return console.error("[auto-tag-config] failed to parse config.json, using defaults:",t),{}}}function Re(){let e=wc().autoTag;if(!e)return{...Fs};let t=Ps.safeParse({...Fs,...e});return t.success?t.data:{...Fs}}function Rc(e){uf();let t=wc(),s=Ps.parse({...Fs,...t.autoTag??{},...e}),n={...t,autoTag:s},r=yc();cf(r,JSON.stringify(n,null,2));try{lf(r,384)}catch(o){console.error("[auto-tag-config] chmod 0600 failed (continuing):",o)}return s}function dr(e){let{apiKey:t,...s}=e;return{...s,apiKey:t?"sk-ant-\u2026":null,hasApiKey:!!t}}import{existsSync as kc,mkdirSync as pf,readFileSync as mf,writeFileSync as gf}from"node:fs";import{homedir as _f}from"node:os";import{join as Ac}from"node:path";import{z as ur}from"zod";function Nc(){return process.env.RECALL_HOME??Ac(_f(),".recall")}function ff(){let e=Nc();kc(e)||pf(e,{recursive:!0})}function xc(){return Ac(Nc(),"config.json")}var $s=ur.object({heuristicEnabled:ur.boolean().default(!0),agentEnabled:ur.boolean().default(!1)}),Us={heuristicEnabled:!0,agentEnabled:!1};function Oc(){let e=xc();if(!kc(e))return{};try{return JSON.parse(mf(e,"utf8"))}catch(t){return console.error("[auto-title-config] failed to parse config.json, using defaults:",t),{}}}function He(){let e=Oc().autoTitle;if(!e)return{...Us};let t=$s.safeParse({...Us,...e});return t.success?t.data:{...Us}}function Lc(e){ff();let t=Oc(),s=$s.parse({...Us,...t.autoTitle??{},...e}),n={...t,autoTitle:s};return gf(xc(),JSON.stringify(n,null,2)),s}H();var Bs="claude-haiku-4-5-20251001",Cc=80,hf=2e3,nt=class extends Error{sessionId;constructor(t){super(`no neighborhood context available for session ${t}`),this.name="NoContextAvailableError",this.sessionId=t}},Ic=null;async function Ef(e,t){if(Ic)return Ic(e,t);let{spawnClaudePrompt:s,isClaudeCliAvailable:n}=await Promise.resolve().then(()=>(ge(),Pe));return n()?s(e,[],{model:t}):{success:!1,stdout:"",stderr:"claude CLI not found on PATH",exitCode:null}}function bf(e){let s=f().prepare(`SELECT s.id,
1150
+ s.auto_title,
1151
+ s.auto_title_source,
1152
+ CASE WHEN sa.alias IS NOT NULL AND sa.alias != ''
1153
+ THEN 1 ELSE 0 END AS has_alias_int
1154
+ FROM sessions s
1155
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1156
+ WHERE s.id = ?`).get(e);return s?{id:s.id,auto_title:s.auto_title,auto_title_source:s.auto_title_source??null,has_alias:s.has_alias_int===1}:null}function Sf(e){return e.parents.length===0&&e.children.length===0&&e.citations.length===0&&e.similar.length===0&&e.cousins.length===0&&e.wikiLinks.length===0}function Tf(e,t){if(e.has_alias)return{eligible:!1,reason:"manual_alias"};let s=jt({auto_title:e.auto_title,auto_title_source:e.auto_title_source,has_alias:e.has_alias});if(t)return{eligible:!0};switch(s){case"low_signal":case"recursive_meta":case"programmatic":return{eligible:!0};case"agent":return{eligible:!1,reason:"agent_titled"};case"manual_alias":return{eligible:!1,reason:"manual_alias"};default:return{eligible:!1,reason:"clean"}}}function yf(e){return["You are renaming a Claude Code session. Below is its NEIGHBORHOOD: parent","sessions, child sessions, related sessions, and citations \u2014 assembled by","Claude Recall's cog-graph. The session itself has a low-signal or","self-referential title that needs replacement.","","Read the neighborhood, then mint a single descriptive title that","reflects this session's role in the surrounding work. Constraints:","","- Maximum 80 characters.","- No quotes, no trailing punctuation.","- Output ONLY a JSON object with two fields:",' { "title": "<\u226480 char title>", "evidence": "<one sentence why>" }',"- No markdown, no preface, no commentary outside the JSON.","","--- NEIGHBORHOOD BUNDLE ---",e,"--- END NEIGHBORHOOD BUNDLE ---"].join(`
1157
+ `)}function wf(e){let t=e.trim();if(!t)return null;let s=t;try{let o=JSON.parse(t);if(o&&typeof o=="object"){let a=o;if(typeof a.result=="string")s=a.result.trim();else if(typeof a.title=="string")return vc(a)}}catch{}let n=s.match(/```(?:json)?\s*\n([\s\S]*?)\n?```/i);n&&(s=n[1].trim());let r=null;try{r=JSON.parse(s)}catch{let o=s.indexOf("{"),a=s.lastIndexOf("}");if(o>=0&&a>o)try{r=JSON.parse(s.slice(o,a+1))}catch{return null}else return null}return!r||typeof r!="object"?null:vc(r)}function vc(e){let t=e.title;if(typeof t!="string")return null;let s=Rf(t).trim();if(!s)return null;let n=e.evidence,r=typeof n=="string"?n.trim():"";return{title:s,evidence:r}}function Rf(e){return e.replace(/^["'`]+|["'`]+$/g,"")}function kf(e){let t=e.replace(/\s+/g," ").trim().replace(/[.!?]+$/g,"").trim();return t.length<=Cc?t:t.slice(0,Cc)}function Af(e,t){let s=e.auto_title??"";return!s&&e.has_alias&&(s=f().prepare("SELECT alias FROM session_aliases WHERE session_id = ?").get(e.id)?.alias??""),{session_id:e.id,title:s,source:e.auto_title_source,confidence:0,evidence:`skipped: ${t}`,written:!1,skipped:t}}async function pr(e,t={}){if(t.signal?.aborted)return{session_id:e,title:"",source:null,confidence:0,evidence:"aborted before start",written:!1,skipped:"aborted"};let s=bf(e);if(!s)throw new Error(`session not found: ${e}`);let n=Tf(s,t.force===!0);if(!n.eligible)return Af(s,n.reason);let r=t.budget??hf,o=Os(e,{budget:r});if(Sf(o))throw new nt(e);if(t.signal?.aborted)return{session_id:e,title:s.auto_title??"",source:s.auto_title_source,confidence:0,evidence:"aborted before CLI call",written:!1,skipped:"aborted"};let a=yf(o.bundle),c=t.model??Bs,d=await Ef(a,c);if(!d.success){let S=d.stderr.slice(-300);throw new Error(`claude CLI exited ${d.exitCode}: ${S||"no stderr captured"}`)}let u=wf(d.stdout);if(!u)throw new Error("failed to parse regeneration output: not valid JSON {title, evidence}");if(!u.title||!u.title.trim())throw new Error("regeneration produced empty title");let g=kf(u.title);if(!g)throw new Error("regeneration produced empty title after clamp");de(e,g,"agent");let b=Te(e)?.auto_title??g;return{session_id:e,title:b,source:"agent",confidence:xf(o),evidence:u.evidence||`regenerated from neighborhood (${Nf(o)})`,written:!0}}function Nf(e){let t=[];return e.parents.length&&t.push(`${e.parents.length} parents`),e.children.length&&t.push(`${e.children.length} children`),e.citations.length&&t.push(`${e.citations.length} citations`),e.similar.length&&t.push(`${e.similar.length} similar`),e.cousins.length&&t.push(`${e.cousins.length} cousins`),e.wikiLinks.length&&t.push(`${e.wikiLinks.length} wiki-links`),t.join(", ")}function xf(e){let t=e.parents.length,s=e.children.length,n=e.citations.length,r=e.similar.length,o=e.cousins.length,a=e.wikiLinks.length,c=.25*Math.min(1,t)+.15*Math.min(1,s)+.2*Math.min(1,n/2)+.15*Math.min(1,r/3)+.1*Math.min(1,o/3)+.15*Math.min(1,a);return Math.min(.95,c)}import{streamSSE as Me}from"hono/streaming";import{bodyLimit as tS}from"hono/body-limit";import{z as j}from"zod";H();ge();import{createHash as Of}from"node:crypto";var _r=1,We="claude-haiku-4-5-20251001",Lf=3,Cf=32e3,jc=2e3,If=30,vf=30,jf=30,Mf=30;function Df(e){let s=f().prepare(`SELECT s.id,
1158
+ NULLIF(sa.alias, '') AS alias,
1159
+ s.auto_title,
1160
+ s.auto_title_source,
1161
+ s.title_quality,
1162
+ s.message_count,
1163
+ s.first_user_message,
1164
+ p.name AS project
1165
+ FROM sessions s
1166
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1167
+ LEFT JOIN projects p ON p.id = s.project_id
1168
+ WHERE s.id = ?`).get(e);return s?{...s,alias_source:s.alias?"manual":null}:null}function Mc(e,t={}){if(e.message_count<Lf)return{eligible:!1,reason:"too-short"};let s=e.title_quality;if(s==="programmatic"||s==="recursive_meta")return{eligible:!1,reason:"low-signal-title"};if(e.auto_title_source==="agent"&&!e.alias)return{eligible:!1,reason:"agent-titled-no-override"};if(!t.force){let n=Be(e.id);if(n&&n.extractor_version>=_r)return{eligible:!1,reason:"already-extracted"}}return{eligible:!0}}function Ff(e){let t=Df(e);if(!t)return null;let n=f().prepare(`SELECT role, content_text
1169
+ FROM messages
1170
+ WHERE session_id = ?
1171
+ AND is_sidechain = 0
1172
+ AND content_text IS NOT NULL
1173
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of n){let c=a.role??"system",d=a.content_text.trim();if(!d)continue;let u=d.length>jc?d.slice(0,jc)+"\u2026":d,g=`${c}: ${u}`;if(o+g.length>Cf)break;r.push(g),o+=g.length}return{meta:t,excerpt:r.join(`
1174
+
1175
+ `)}}function Pf(e){let{meta:t}=e;return["You are extracting a structured Output Index from a Claude Code session for a local knowledge graph.","","Read the transcript and produce a single JSON object with EXACTLY these fields. Output JSON only \u2014 no Markdown fences, no commentary, no explanation.","","Fields (every field MUST appear; use [] for empty):","- files_written: array of strings. Relative or absolute file paths the assistant created, edited, or extensively discussed (Write/Edit tool calls or paths quoted verbatim).",'- brands_mentioned: array of strings. Distinct proper-noun company / product / brand names mentioned. Examples of valid: "Glaser Group", "Apollo", "TikTok", "Cloudflare R2". NOT generic terms like "the company" or "users".','- terms_introduced: array of objects { "term": "<lowercase phrase>", "freq": <int> }. Distinctive multi-word noun phrases the session introduced or extensively discussed. Skip generic words. Cap at 30 entries.','- plan_ids_referenced: array of strings. Planning identifiers like "v0.18.A", "Phase D", "L3", "MASTER-PLAN-cognitive-graph". Empty array if none.','- bug_signatures: array of objects { "error_type": "<class or null>", "snippet": "<first line of the error>", "file": "<path or null>" }. Errors actually encountered in the session (not hypothetical). The error_type is the exception class (e.g. "TypeError", "ReferenceError") or null if unspecified.',"","Hard constraints:","- Do NOT fabricate. If a field has no concrete content from the transcript, output an empty array.","- Output JSON ONLY. No backticks, no markdown, no preamble.","- terms_introduced \u2264 30 entries; brands_mentioned \u2264 30 distinct entries; plan_ids_referenced \u2264 30; bug_signatures \u2264 30.","",`Session: ${t.alias??t.id.slice(0,8)}`,`Project: ${t.project??"unknown"}`,t.first_user_message?`Opening prompt: ${t.first_user_message.slice(0,500)}`:"","","Transcript excerpt (may be truncated):","---",e.excerpt,"---"].filter(Boolean).join(`
1176
+ `)}function Uf(e){return Array.isArray(e)&&e.every(t=>typeof t=="string")}function mr(e,t,s=!1){if(!Array.isArray(e))return[];let n=new Set,r=[];for(let o of e){if(typeof o!="string")continue;let a=s?o.trim().toLowerCase():o.trim();if(!(!a||a.length>256)&&!n.has(a)&&(n.add(a),r.push(a),r.length>=t))break}return r}function $f(e,t){if(!Array.isArray(e))return[];let s=new Set,n=[];for(let r of e){if(!r||typeof r!="object")continue;let o=r.term,a=r.freq;if(typeof o!="string")continue;let c=o.trim().toLowerCase();if(!c||c.length>128||s.has(c))continue;s.add(c);let d=typeof a=="number"&&Number.isFinite(a)&&a>0?Math.floor(a):1;if(n.push({term:c,frequency:d}),n.length>=t)break}return n}function Bf(e,t){if(!Array.isArray(e))return[];let s=[];for(let n of e){if(!n||typeof n!="object")continue;let r=n.error_type,o=n.snippet,a=n.file,c=typeof r=="string"&&r.trim().length>0?r.trim().slice(0,64):"unknown",d=typeof o=="string"?o.trim().replace(/\s+/g," ").slice(0,256):"";if(!d)continue;let u=typeof a=="string"&&a.trim().length>0?a.trim().slice(0,256):null,g=Of("sha256").update(`${c}::${d}`).digest("hex").slice(0,12);if(s.push({error_type:c,message_hash:g,snippet:d,file:u}),s.length>=t)break}return s}function Hf(e){let t=e.trim();try{let b=JSON.parse(t);typeof b.result=="string"&&(t=b.result.trim())}catch{}t=t.replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/i,"").trim();let s=t.indexOf("{"),n=t.lastIndexOf("}");if(s===-1||n===-1||n<=s)return null;let r=t.slice(s,n+1),o;try{o=JSON.parse(r)}catch{return null}let a=mr(o.files_written,200),c=mr(o.brands_mentioned,vf),d=$f(o.terms_introduced,If),u=mr(o.plan_ids_referenced,jf),g=Bf(o.bug_signatures,Mf);return a.length===0&&c.length===0&&d.length===0&&u.length===0&&g.length===0&&!Uf(o.files_written)?null:{files_written:a,brands_mentioned:c,terms_introduced:d,plan_ids_referenced:u,bug_signatures:g}}var gr=null;async function Wf(e,t){return gr?gr(e,t):Fe(e,[],{model:t})}async function fr(e,t={}){if(t.signal?.aborted)return{session_id:e,ok:!1,failed:"aborted"};let s=Ff(e);if(!s)return{session_id:e,ok:!1,skipped:"session-not-found"};let n=Mc(s.meta,{force:t.force});if(!n.eligible)return{session_id:e,ok:!1,skipped:n.reason};if(!gr&&!oe())return{session_id:e,ok:!1,failed:"claude-cli-missing"};let r=Pf(s),o=t.model??We,a=await Wf(r,o);if(!a.success)return{session_id:e,ok:!1,failed:"claude-cli-error",exit_code:a.exitCode};let c=Hf(a.stdout);if(!c)return{session_id:e,ok:!1,failed:"parse-failed",exit_code:a.exitCode};let d=qf(a.stdout),u=ya({session_id:e,files_written:c.files_written,brands_mentioned:c.brands_mentioned,terms_introduced:c.terms_introduced,plan_ids_referenced:c.plan_ids_referenced,bug_signatures:c.bug_signatures,raw_extraction:{model:o,usage:d,raw_response_excerpt:a.stdout.slice(0,4e3)},extractor_version:_r});return{session_id:e,ok:!0,index:u,usage:d}}function qf(e){try{let t=JSON.parse(e.trim());if(t&&typeof t=="object"&&t.usage){let s=t.usage,n={};return typeof s.input_tokens=="number"&&(n.input_tokens=s.input_tokens),typeof s.output_tokens=="number"&&(n.output_tokens=s.output_tokens),n}}catch{}return null}function rt(e={}){let t=f(),s=[],n=[];typeof e.projectId=="number"&&(s.push("s.project_id = ?"),n.push(e.projectId));let r=Math.max(1,Math.min(1e4,e.limit??1e3)),o=s.length?`WHERE ${s.join(" AND ")}`:"",a=t.prepare(`SELECT s.id,
1177
+ NULLIF(sa.alias, '') AS alias,
1178
+ s.auto_title,
1179
+ s.auto_title_source,
1180
+ s.title_quality,
1181
+ s.message_count,
1182
+ s.first_user_message,
1183
+ p.name AS project
1184
+ FROM sessions s
1185
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1186
+ LEFT JOIN projects p ON p.id = s.project_id
1187
+ ${o}
1188
+ ORDER BY COALESCE(s.started_at, ''), s.id`).all(...n),c=[],d=new Map;for(let u of a){let g={...u,alias_source:u.alias?"manual":null},h=Mc(g,{force:e.force});if(!h.eligible){let b=h.reason??"session-not-found";d.set(b,(d.get(b)??0)+1);continue}if(c.push(g),c.length>=r)break}return{eligible:c,skipped:d}}async function Dc(e={}){let t=rt({projectId:e.projectId,limit:e.limit,force:e.force}),s={total:t.eligible.length,processed:0,ok:0,failed:0,skipped:0,current_session_id:null,total_input_tokens:0,total_output_tokens:0};for(let r of t.skipped.values())s.skipped+=r;e.onProgress?.({...s});let n=[];for(let r of t.eligible){if(e.signal?.aborted)break;s.current_session_id=r.id,e.onProgress?.({...s});let o=await fr(r.id,{model:e.model,force:e.force,signal:e.signal});n.push(o),e.onResult?.(o),o.ok?s.ok+=1:o.skipped?s.skipped+=1:s.failed+=1,o.usage?.input_tokens&&(s.total_input_tokens+=o.usage.input_tokens),o.usage?.output_tokens&&(s.total_output_tokens+=o.usage.output_tokens),s.processed+=1,e.onProgress?.({...s})}return s.current_session_id=null,e.onProgress?.({...s}),{progress:s,results:n}}H();H();ot();function Bt(e){return f().prepare("SELECT id, name FROM projects WHERE name = ? LIMIT 1").get(e)??null}H();function Bc(e){let t=f(),s=new Date().toISOString();return t.prepare(`INSERT INTO bug_signature_resolutions
1189
+ (message_hash, resolved_in_session_id, fix_summary, resolved_at, unresolved_at)
1190
+ VALUES (?, ?, ?, ?, NULL)
1191
+ ON CONFLICT(message_hash) DO UPDATE SET
1192
+ resolved_in_session_id = excluded.resolved_in_session_id,
1193
+ fix_summary = excluded.fix_summary,
1194
+ resolved_at = excluded.resolved_at,
1195
+ unresolved_at = NULL`).run(e.messageHash,e.resolvedInSessionId??null,e.fixSummary??null,s),Kf(e.messageHash)}function Hc(e){f().prepare(`UPDATE bug_signature_resolutions
1196
+ SET unresolved_at = ?
1197
+ WHERE message_hash = ?`).run(new Date().toISOString(),e)}function Kf(e){return f().prepare("SELECT * FROM bug_signature_resolutions WHERE message_hash = ?").get(e)??null}function Er(e){if(e.length===0)return new Map;let t=f(),s=e.map(()=>"?").join(","),n=t.prepare(`SELECT * FROM bug_signature_resolutions
1198
+ WHERE message_hash IN (${s})`).all(...e),r=new Map;for(let o of n)r.set(o.message_hash,o);return r}function br(e){return!!e&&e.unresolved_at===null}H();function Ht(){return new Date().toISOString()}function Sr(){let e=f(),t=e.prepare(`SELECT id, name, description, created_at, updated_at
1199
+ FROM macro_repos
1200
+ ORDER BY name COLLATE NOCASE`).all();if(t.length===0)return[];let s=t.map(a=>a.id),n=s.map(()=>"?").join(","),r=e.prepare(`SELECT m.macro_repo_id, m.project_id, p.name AS project_name
1201
+ FROM macro_repo_members m
1202
+ JOIN projects p ON p.id = m.project_id
1203
+ WHERE m.macro_repo_id IN (${n})
1204
+ ORDER BY p.name COLLATE NOCASE`).all(...s),o=new Map;for(let a of t)o.set(a.id,{...a,member_project_ids:[],member_project_names:[]});for(let a of r){let c=o.get(a.macro_repo_id);c&&(c.member_project_ids.push(a.project_id),c.member_project_names.push(a.project_name))}return Array.from(o.values())}function it(e){return Sr().find(s=>s.id===e)??null}function Wc(){return f().prepare(`SELECT p.id, p.name
1205
+ FROM projects p
1206
+ LEFT JOIN macro_repo_members m ON m.project_id = p.id
1207
+ WHERE m.project_id IS NULL
1208
+ ORDER BY p.name COLLATE NOCASE`).all()}function qc(e){let t=e.name.trim();if(!t)throw new Error("macro repo name is required");let s=f(),n=Ht(),r=s.prepare(`INSERT INTO macro_repos (name, description, created_at, updated_at)
1209
+ VALUES (?, ?, ?, ?)`).run(t,e.description??null,n,n),o=Number(r.lastInsertRowid);return it(o)}function Jc(e,t){let s=it(e);if(!s)throw new Error(`macro repo ${e} not found`);let n=t.name!==void 0?t.name.trim():s.name;if(!n)throw new Error("macro repo name cannot be empty");let r=t.description!==void 0?t.description:s.description;return f().prepare(`UPDATE macro_repos
1210
+ SET name = ?, description = ?, updated_at = ?
1211
+ WHERE id = ?`).run(n,r,Ht(),e),it(e)}function Xc(e){f().prepare("DELETE FROM macro_repos WHERE id = ?").run(e)}function Gc(e,t){let s=f();if(!s.prepare("SELECT 1 FROM macro_repos WHERE id = ?").get(e))throw new Error(`macro repo ${e} not found`);if(!s.prepare("SELECT 1 FROM projects WHERE id = ?").get(t))throw new Error(`project ${t} not found`);s.prepare(`INSERT OR IGNORE INTO macro_repo_members (macro_repo_id, project_id, added_at)
1212
+ VALUES (?, ?, ?)`).run(e,t,Ht()),s.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(Ht(),e)}function Yc(e,t){let s=f();s.prepare(`DELETE FROM macro_repo_members
1213
+ WHERE macro_repo_id = ? AND project_id = ?`).run(e,t),s.prepare("UPDATE macro_repos SET updated_at = ? WHERE id = ?").run(Ht(),e)}H();function Kc(e){let t=f(),s=new Date().toISOString(),n=t.prepare(`INSERT INTO bug_synthesis_results
1214
+ (scope, target_id, mode, model, output_markdown, input_tokens, output_tokens, context_summary, created_at, job_id)
1215
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.scope,e.target_id,e.mode,e.model,e.output_markdown,e.input_tokens,e.output_tokens,JSON.stringify(e.context_summary??{}),s,e.job_id??null),r=Number(n.lastInsertRowid);return Tr(r)}function zc(e){let t={};try{t=JSON.parse(e.context_summary)}catch{t={}}return{id:e.id,scope:e.scope,target_id:e.target_id,mode:e.mode,model:e.model,output_markdown:e.output_markdown,input_tokens:e.input_tokens,output_tokens:e.output_tokens,context_summary:t,created_at:e.created_at,job_id:e.job_id}}function Tr(e){let s=f().prepare("SELECT * FROM bug_synthesis_results WHERE id = ?").get(e);return s?zc(s):null}function Vc(e={}){let t=f(),s=[],n=[];e.scope&&(s.push("scope = ?"),n.push(e.scope)),e.target_id&&(s.push("target_id = ?"),n.push(e.target_id));let r=s.length?`WHERE ${s.join(" AND ")}`:"",o=Math.min(Math.max(1,e.limit??50),500);return t.prepare(`SELECT * FROM bug_synthesis_results
1216
+ ${r}
1217
+ ORDER BY created_at DESC
1218
+ LIMIT ?`).all(...n,o).map(zc)}function Zc(e){let s=f().prepare(`SELECT target_id, COUNT(*) AS n
1219
+ FROM bug_synthesis_results
1220
+ WHERE scope = ?
1221
+ GROUP BY target_id`).all(e),n=new Map;for(let r of s)n.set(r.target_id,r.n);return n}function Qc(e){f().prepare("DELETE FROM bug_synthesis_results WHERE id = ?").run(e)}import{randomBytes as zf,timingSafeEqual as Vf}from"node:crypto";var Zf=6e4,Qf=new Set(["127.0.0.1","localhost"]),Wt=new Map;function yr(){return Date.now()}function el(){let e=yr();for(let[t,s]of Wt)(s.expiresAt<=e||s.used)&&Wt.delete(t)}function ye(e){let t=e.req.header("origin")??"";if(t)try{let r=new URL(t);if(!Qf.has(r.hostname))return e.json({error:"forbidden: cross-origin launcher request rejected"},403)}catch{return e.json({error:"forbidden: invalid Origin header"},403)}let s=e.req.header("sec-fetch-site");return s&&s!=="same-origin"&&s!=="none"?e.json({error:"forbidden: Sec-Fetch-Site indicates cross-origin"},403):e.req.header("x-recall-launcher")!=="1"?e.json({error:"forbidden: missing X-Recall-Launcher header (this endpoint is callable only from the Recall web UI)"},403):null}function wr(e){el();let t=zf(32).toString("hex"),s=yr()+Zf;return Wt.set(t,{token:t,intent:e,expiresAt:s,used:!1}),{token:t,expiresAt:s}}function Rr(e){if(el(),typeof e!="string"||e.length!==64)return null;let t;try{t=Buffer.from(e,"hex")}catch{return null}if(t.length!==32)return null;for(let s of Wt.values()){if(s.used||s.expiresAt<=yr())continue;let n;try{n=Buffer.from(s.token,"hex")}catch{continue}if(n.length===t.length&&Vf(n,t))return s.used=!0,Wt.delete(s.token),s.intent}return null}import{existsSync as oh,mkdirSync as FR,readFileSync as ih,writeFileSync as PR}from"node:fs";import{homedir as ah}from"node:os";import{join as al}from"node:path";import{z as kr}from"zod";import{appendFileSync as eh,existsSync as tl,mkdirSync as th,readFileSync as sh}from"node:fs";import{homedir as nh}from"node:os";import{join as sl}from"node:path";function nl(){return process.env.RECALL_HOME??sl(nh(),".recall")}function rh(){let e=nl();tl(e)||th(e,{recursive:!0})}function rl(){return sl(nl(),"launcher-audit.log")}function ee(e){rh();let t={ts:new Date().toISOString(),...e};try{eh(rl(),JSON.stringify(t)+`
1222
+ `,"utf8")}catch(s){console.error("[launcher-audit] failed to append:",s)}}function ol(e){let t=rl();if(!tl(t))return{input_tokens:0,output_tokens:0,records_counted:0};let s=Date.now()-e,n=0,r=0,o=0,a;try{a=sh(t,"utf8")}catch{return{input_tokens:0,output_tokens:0,records_counted:0}}for(let c of a.split(`
1223
+ `)){if(!c.trim())continue;let d;try{d=JSON.parse(c)}catch{continue}if(d.kind!=="run-completed"&&d.kind!=="synth-completed")continue;let u=Date.parse(d.ts);!Number.isFinite(u)||u<s||(n+=Number(d.input_tokens??0),r+=Number(d.output_tokens??0),o+=1)}return{input_tokens:n,output_tokens:r,records_counted:o}}var ch=1440*60*1e3,lh=kr.object({dailyTokenBudget:kr.number().int().nonnegative().default(1e6),sessionCeiling:kr.number().int().positive().max(1e4).default(500)}),Ar={dailyTokenBudget:1e6,sessionCeiling:500};function dh(){return process.env.RECALL_HOME??al(ah(),".recall")}function uh(){return al(dh(),"config.json")}function ph(){let e=uh();if(!oh(e))return{};try{return JSON.parse(ih(e,"utf8"))}catch(t){return console.error("[launcher-budget] failed to parse config.json, using defaults:",t),{}}}function Nr(){let e=ph().launcher;if(!e)return{...Ar};let t=lh.safeParse({...Ar,...e});return t.success?t.data:{...Ar}}function at(){let e=Nr(),t=ol(ch),s=t.input_tokens+t.output_tokens,n=Math.max(0,e.dailyTokenBudget-s);return{daily_token_budget:e.dailyTokenBudget,session_ceiling:e.sessionCeiling,spent_input_tokens_24h:t.input_tokens,spent_output_tokens_24h:t.output_tokens,spent_total_tokens_24h:s,remaining_tokens_24h:n}}function xr(e){return{estimated_input_tokens_max:e*2e4,estimated_output_tokens_max:e*1e3}}function Or(e){let t=e.mode==="root_cause"?800:2e3;if(e.scope==="cluster")return{estimated_input_tokens_max:5e3+Math.min(8,Math.max(1,e.member_session_count??1))*3e3,estimated_output_tokens_max:t};let s=Math.max(1,e.cluster_count??1);return{estimated_input_tokens_max:Math.min(5e4,1e3*s),estimated_output_tokens_max:t}}var il={pro:45,"max-5x":225,"max-20x":900};function mh(e){let t=e.toLowerCase();return t.includes("haiku")?1:t.includes("sonnet")?5:t.includes("opus")?10:5}var gh=4e3;function cl(e){return Math.max(1,Math.ceil(e/gh))}function Ce(e,t){let s=mh(t),n=e*s,r=Object.keys(il).map(o=>{let a=n/il[o];return{plan:o,fraction:a,pct:Math.round(a*1e3)/10,would_exhaust_window:a>1}});return{model:t,model_multiplier:s,per_plan:r,caveat:"Estimates use Anthropic\u2019s public approximate caps and assume a multiplier of ~1x for Haiku, ~5x for Sonnet, ~10x for Opus. Anthropic adjusts these limits without notice; actual consumption depends on session size. Treat as planning guidance, not a contract."}}import{randomUUID as _h}from"node:crypto";var ct=new Map,qt=new Map,fh=300*1e3;function qs(e,t,s){e.events.push({id:e.events.length+1,kind:t,data:s});for(let n of e.waiters)n();e.waiters.clear()}function hh(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{ct.delete(e.jobId),qt.get(e.project)===e.jobId&&qt.delete(e.project)},fh),e.cleanupTimer.unref?.())}function Lr(e){return{jobId:e.jobId,project:e.project,model:e.model,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total:e.progress.total,processed:e.progress.processed,ok:e.progress.ok,failed:e.progress.failed,skipped:e.progress.skipped,total_input_tokens:e.progress.total_input_tokens,total_output_tokens:e.progress.total_output_tokens,current_session_id:e.progress.current_session_id,error:e.error}}function ll(e){let t=Bt(e.project);if(!t)return{error:`project "${e.project}" not found`};let s=qt.get(t.name);if(s){let g=ct.get(s);if(g&&g.status==="running")return{jobId:s,reused:!0};qt.delete(t.name)}let n=_h(),r=e.model??We,o=Math.max(1,e.limit??200),a=e.force??!1,c=new AbortController,d=new Date().toISOString(),u={jobId:n,project:t.name,projectId:t.id,model:r,limit:o,force:a,status:"running",startedAt:d,endedAt:null,events:[],waiters:new Set,controller:c,progress:{total:0,processed:0,ok:0,failed:0,skipped:0,current_session_id:null,total_input_tokens:0,total_output_tokens:0},error:null,cleanupTimer:null};return ct.set(n,u),qt.set(t.name,n),ee({kind:"run-launched",job_id:n,project:t.name,model:r,limit:o,origin:e.origin??null}),(async()=>{await Promise.resolve();try{await Dc({projectId:t.id,limit:o,force:a,model:r,signal:c.signal,onProgress:g=>{u.progress=g,qs(u,"progress",g)},onResult:g=>{!g.ok&&!g.skipped&&qs(u,"error",{session_id:g.session_id,reason:g.failed??"unknown"})}}),u.status=c.signal.aborted?"cancelled":"done",u.endedAt=new Date().toISOString(),qs(u,"done",Lr(u)),ee({kind:u.status==="cancelled"?"run-cancelled":"run-completed",job_id:n,project:t.name,model:r,limit:o,origin:e.origin??null,input_tokens:u.progress.total_input_tokens,output_tokens:u.progress.total_output_tokens,sessions_processed:u.progress.processed})}catch(g){let h=g instanceof Error?g.message:String(g??"unknown error");u.status="failed",u.endedAt=new Date().toISOString(),u.error=h,qs(u,"done",Lr(u)),ee({kind:"run-failed",job_id:n,project:t.name,model:r,limit:o,origin:e.origin??null,reason:h,input_tokens:u.progress.total_input_tokens,output_tokens:u.progress.total_output_tokens})}finally{hh(u)}})(),{jobId:n,reused:!1}}async function*dl(e,t=0){let s=ct.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(!r)break;if(n+=1,yield r,r.kind==="done")return}if(s.status!=="running")return;await new Promise(r=>s.waiters.add(r))}}function ul(e){let t=ct.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Cr(e){let t=ct.get(e);return t?Lr(t):null}H();import{randomUUID as Eh}from"node:crypto";import{spawn as bh}from"node:child_process";import{execSync as Sh}from"node:child_process";var pl="claude-haiku-4-5-20251001",dt=new Map,Gt=new Map,Th=300*1e3;function ml(e){return`${e.scope}:${e.target_id}:${e.mode}`}function Jt(e,t,s){e.events.push({id:e.events.length+1,kind:t,data:s});for(let n of e.waiters)n();e.waiters.clear()}function yh(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{dt.delete(e.jobId);let t=ml(e.intent);Gt.get(t)===e.jobId&&Gt.delete(t)},Th),e.cleanupTimer.unref?.())}function lt(e){return{jobId:e.jobId,scope:e.intent.scope,target_id:e.intent.target_id,mode:e.intent.mode,model:e.intent.model,status:e.status,output_markdown:e.output_markdown,input_tokens:e.input_tokens,output_tokens:e.output_tokens,startedAt:e.startedAt,endedAt:e.endedAt,error:e.error,context_summary:e.context_summary}}function gl(){return f().prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
1224
+ oi.bug_signatures
1225
+ FROM session_output_index oi
1226
+ JOIN sessions s ON s.id = oi.session_id
1227
+ JOIN projects p ON p.id = s.project_id
1228
+ WHERE oi.bug_signatures IS NOT NULL
1229
+ AND oi.bug_signatures != '[]'`).all()}function _l(e){try{let t=JSON.parse(e);return Array.isArray(t)?t:[]}catch{return[]}}function wh(e){let t=gl(),s=[];for(let a of t)for(let c of _l(a.bug_signatures)){let d=c.message_hash??`nohash:${(c.snippet??"").slice(0,64)}`;(c.message_hash===e||d===e)&&s.push({sig:c,session_id:a.session_id,project:a.project,auto_title:a.auto_title})}if(s.length===0)return null;let n=s[0],r=Array.from(new Set(s.map(a=>a.session_id))),o=Array.from(new Set(s.map(a=>a.project))).sort();return{message_hash:n.sig.message_hash??null,error_type:n.sig.error_type??null,snippet:(n.sig.snippet??"").slice(0,400),file:n.sig.file??null,occurrence_count:s.length,projects:o,member_session_ids:r}}function Rh(e){let t=gl().filter(o=>o.project===e),s=new Map;for(let o of t)for(let a of _l(o.bug_signatures)){let c=a.message_hash??`nohash:${(a.snippet??"").slice(0,64)}`,d=s.get(c);d?(d.sigs.push(a),d.sessions.add(o.session_id)):s.set(c,{sigs:[a],first:a,sessions:new Set([o.session_id])})}let n=new Map;try{let o=Array.from(s.keys()).filter(a=>!a.startsWith("nohash:"));if(o.length>0){let a=f(),c=o.map(()=>"?").join(","),d=a.prepare(`SELECT message_hash, fix_summary
1230
+ FROM bug_signature_resolutions
1231
+ WHERE message_hash IN (${c})
1232
+ AND unresolved_at IS NULL`).all(...o);for(let u of d)n.set(u.message_hash,{fix_summary:u.fix_summary})}}catch{}let r=[];for(let[o,a]of s){let c=a.first.message_hash??null,d=c?n.get(c)??null:null;r.push({cluster_id:c??o,error_type:a.first.error_type??null,snippet:(a.first.snippet??"").slice(0,200),file:a.first.file??null,occurrence_count:a.sessions.size,resolved:d!==null,fix_summary:d?.fix_summary??null})}return r.sort((o,a)=>a.occurrence_count-o.occurrence_count),r}function kh(e){let t=e.replace(/\s+/g," ").trim();return t.length===0?"":t.slice(0,Math.min(30,t.length))}function Ah(e,t){let s=f(),n=kh(t),r=[];for(let o of e.slice(0,8)){let a=s.prepare(`SELECT s.id, p.name AS project, s.auto_title
1233
+ FROM sessions s
1234
+ JOIN projects p ON p.id = s.project_id
1235
+ WHERE s.id = ?`).get(o);if(!a)continue;let c=[];if(n.length>0){let d=s.prepare(`SELECT rowid FROM messages
1236
+ WHERE session_id = ?
1237
+ AND is_sidechain = 0
1238
+ AND content_text LIKE ?
1239
+ ORDER BY rowid
1240
+ LIMIT 5`).all(o,`%${n}%`),u=new Set,g=0;for(let h of d){let b=s.prepare(`SELECT rowid, role, content_text FROM messages
1241
+ WHERE session_id = ?
1242
+ AND is_sidechain = 0
1243
+ AND rowid BETWEEN ? AND ?
1244
+ ORDER BY rowid`).all(o,h.rowid-2,h.rowid+2);for(let S of b){if(u.has(S.rowid))continue;u.add(S.rowid);let y=(S.content_text??"").slice(0,800);if(g+y.length>4e3)break;g+=y.length,c.push({role:S.role??"unknown",content:y})}if(g>=4e3)break}}r.push({session_id:o,short_id:o.slice(0,8),project:a.project,auto_title:a.auto_title,excerpts:c})}return r}var Nh="You are analyzing extracted bug findings from a developer's past Claude Code sessions. You are NOT being asked to fix code. You are being asked to synthesize patterns, identify likely root causes, or prioritize concerns based ONLY on the structured findings you are given. Output Markdown only, no preamble, no apologies, no questions.";function xh(e,t){let s=[];s.push("[CONTEXT]"),s.push("Bug fingerprint:"),s.push(`- error_type: ${e.error_type??"unknown"}`),s.push(`- description: ${e.snippet||"(no snippet)"}`),s.push(`- file: ${e.file??"(no file recorded)"}`),s.push(`- occurrences: ${e.occurrence_count} sessions`),s.push(`- projects: ${e.projects.join(", ")}`),s.push(`- finding_id: ${e.message_hash??"(no hash)"}`),s.push(""),s.push("Member session excerpts:");for(let n of t){let r=n.auto_title??"(untitled)";if(s.push(`=== Session ${n.short_id} | ${n.project} | "${r}" ===`),n.excerpts.length===0)s.push("(no surrounding messages found for this snippet)");else for(let o of n.excerpts)s.push(`${o.role}: ${o.content}`);s.push("")}return s.join(`
1245
+ `)}function Oh(e,t,s){let n=[];n.push("[CONTEXT]"),n.push(`Project: ${e}`),n.push(`Total clusters: ${s}`),n.push(""),n.push("Clusters (sorted by occurrence_count desc):");for(let r of t)n.push(`- cluster_id: ${r.cluster_id}`),n.push(` error_type: ${r.error_type??"unknown"}`),n.push(` snippet: ${r.snippet||"(none)"}`),r.file&&n.push(` file: ${r.file}`),n.push(` occurrence_count: ${r.occurrence_count}`),n.push(` resolved: ${r.resolved?"true":"false"}`),r.fix_summary&&n.push(` fix_summary: ${r.fix_summary}`),n.push("");return t.length<s&&n.push(`(Showing top ${t.length} of ${s} clusters by occurrence.)`),n.join(`
1246
+ `)}var Lh=`[TASK]
1247
+ Output a Markdown synopsis with these sections:
1248
+
1249
+ ## What this bug is
1250
+ 2-3 sentences in plain English describing the underlying issue.
1251
+
1252
+ ## How it manifests
1253
+ Bullet list of the distinct ways the bug shows up across sessions.
1254
+
1255
+ ## Common context
1256
+ What the sessions had in common when they hit this bug. Look for:
1257
+ - shared file paths
1258
+ - shared frameworks/libraries
1259
+ - shared workflow stages (refactor / migration / new feature)
1260
+ - shared error symptoms
1261
+
1262
+ ## Suggested fix direction
1263
+ 1-2 sentence pointer toward the most likely fix. Conservative:
1264
+ prefer "consider <pattern>" over "do <action>" unless the evidence
1265
+ is overwhelming.
1266
+
1267
+ ## Confidence
1268
+ One of: high / medium / low. With one sentence justifying the level.`,Ch=`[TASK]
1269
+ Output a Markdown response with these sections:
1270
+
1271
+ ## Most likely root cause
1272
+ 1-3 sentences naming the underlying mechanism. Be concrete.
1273
+
1274
+ ## Evidence supporting this hypothesis
1275
+ Bullets citing specific session excerpts that point to this cause.
1276
+
1277
+ ## Evidence against
1278
+ Bullets noting where the data conflicts with the hypothesis. Empty
1279
+ list is acceptable but explicitly say "(none found)".
1280
+
1281
+ ## Alternative hypotheses
1282
+ At most 2 alternatives, each with one sentence why it is less likely.
1283
+
1284
+ ## Confidence
1285
+ high / medium / low + one-sentence justification.`,Ih=`[TASK]
1286
+ Output a Markdown response with these sections:
1287
+
1288
+ ## Top 5 recurring concerns
1289
+ Numbered list. For each: cluster_id, one-sentence summary, why it is
1290
+ high priority (recurrence count, severity heuristic).
1291
+
1292
+ ## Recommended triage order
1293
+ Numbered list of cluster_ids in the order the developer should
1294
+ address them, with one sentence each on why this slot.
1295
+
1296
+ ## Quick wins
1297
+ Clusters that look small + high-confidence-fix. Empty list is fine.
1298
+
1299
+ ## Architectural concerns
1300
+ Clusters that suggest deeper issues (multiple files, repeated patterns).
1301
+
1302
+ ## Confidence
1303
+ high / medium / low.`;function vh(e,t){let s;return e.scope==="cluster"?s=e.mode==="root_cause"?Ch:Lh:s=Ih,[Nh,"",t,"",s].join(`
1304
+ `)}var jh=null;var Xt;function Mh(){if(Xt)return Xt;try{Xt=Sh("which claude",{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}catch{Xt="claude"}return Xt}function Dh(e){let t="";return s=>{t+=s.toString("utf8");let n=t.indexOf(`
1305
+ `);for(;n!==-1;){let r=t.slice(0,n);t=t.slice(n+1),r.length>0&&e(r),n=t.indexOf(`
1306
+ `)}}}function Fh(e){return new Promise(t=>{let s=["-p",e.prompt,"--output-format","stream-json","--verbose","--allowedTools","","--permission-mode","bypassPermissions","--model",e.model],n="",r=0,o=0,a=null,c=bh(Mh(),s,{stdio:["ignore","pipe","pipe"]}),d=()=>{try{c.kill("SIGTERM")}catch{}};e.signal.addEventListener("abort",d,{once:!0});let g=Dh(b=>{let S=b.trim();if(!S.startsWith("{"))return;let y;try{y=JSON.parse(S)}catch{return}if(!y||typeof y!="object")return;let k=y;if(k.type==="assistant"&&k.message?.content){for(let D of k.message.content)D?.type==="text"&&typeof D.text=="string"&&(n+=D.text,e.onPartial(D.text,n));let w=k.message?.usage;w&&(typeof w.input_tokens=="number"&&(r=Math.max(r,w.input_tokens)),typeof w.output_tokens=="number"&&(o=Math.max(o,w.output_tokens)))}});c.stdout.on("data",b=>g(b)),c.stderr.on("data",b=>{let S=b.toString("utf8");S.trim()&&(a=(a??"")+S)});let h=setTimeout(()=>{try{c.kill("SIGKILL")}catch{}},1800*1e3);c.on("close",b=>{clearTimeout(h),e.signal.removeEventListener("abort",d);let S=b===0?null:a&&a.trim()||(e.signal.aborted?null:`claude CLI exited with code ${b}`);t({output_markdown:n,input_tokens:r,output_tokens:o,error:S})}),c.on("error",b=>{clearTimeout(h),e.signal.removeEventListener("abort",d),t({output_markdown:n,input_tokens:r,output_tokens:o,error:b instanceof Error?b.message:String(b)})})})}function Js(e){if(e.scope==="cluster"){let r=wh(e.target_id);return r?{cluster:r,context_summary:{cluster_count:1,session_count:r.member_session_ids.length,findings_count:r.occurrence_count}}:null}let t=Rh(e.target_id);if(t.length===0)return null;let s=t.slice(0,30),n=t.reduce((r,o)=>r+o.occurrence_count,0);return{project_clusters:s,total_project_clusters:t.length,context_summary:{cluster_count:t.length,session_count:0,findings_count:n}}}function fl(e){let t=Js(e.intent);if(!t)return{error:e.intent.scope==="cluster"?`cluster "${e.intent.target_id}" not found in any extracted findings`:`project "${e.intent.target_id}" has no extracted findings to synthesize`};let s=ml(e.intent),n=Gt.get(s);if(n){let d=dt.get(n);if(d&&d.status==="running")return{jobId:n,reused:!0};Gt.delete(s)}let r=Eh(),o=new AbortController,a=new Date().toISOString(),c={jobId:r,intent:e.intent,status:"running",startedAt:a,endedAt:null,events:[],waiters:new Set,controller:o,output_markdown:"",input_tokens:0,output_tokens:0,error:null,context_summary:t.context_summary,cleanupTimer:null};return dt.set(r,c),Gt.set(s,r),ee({kind:"synth-launched",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,reason:`${e.intent.scope}/${e.intent.mode}/${e.intent.target_id}`}),(async()=>{await Promise.resolve();try{let d;if(e.intent.scope==="cluster"&&t.cluster){let b=Ah(t.cluster.member_session_ids,t.cluster.snippet);d=xh(t.cluster,b)}else if(e.intent.scope==="project"&&t.project_clusters)d=Oh(e.intent.target_id,t.project_clusters,t.total_project_clusters??t.project_clusters.length);else throw new Error("inconsistent prepared context");let u=vh(e.intent,d),h=await(jh??Fh)({prompt:u,model:e.intent.model,signal:o.signal,onPartial:(b,S)=>{c.output_markdown=S,Jt(c,"partial",lt(c))}});if(c.output_markdown=h.output_markdown,c.input_tokens=h.input_tokens,c.output_tokens=h.output_tokens,o.signal.aborted)c.status="cancelled",c.endedAt=new Date().toISOString(),Jt(c,"done",lt(c)),ee({kind:"synth-cancelled",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,input_tokens:c.input_tokens,output_tokens:c.output_tokens});else if(h.error)c.status="failed",c.endedAt=new Date().toISOString(),c.error=h.error,Jt(c,"done",lt(c)),ee({kind:"synth-failed",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,reason:h.error,input_tokens:c.input_tokens,output_tokens:c.output_tokens});else{c.status="done",c.endedAt=new Date().toISOString(),Jt(c,"done",lt(c)),ee({kind:"synth-completed",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,input_tokens:c.input_tokens,output_tokens:c.output_tokens});try{Kc({scope:e.intent.scope,target_id:e.intent.target_id,mode:e.intent.mode,model:e.intent.model,output_markdown:c.output_markdown,input_tokens:c.input_tokens,output_tokens:c.output_tokens,job_id:r})}catch(b){console.error("[synthesize-jobs] failed to persist synthesis result:",b)}}}catch(d){let u=d instanceof Error?d.message:String(d??"unknown error");c.status="failed",c.endedAt=new Date().toISOString(),c.error=u,Jt(c,"done",lt(c)),ee({kind:"synth-failed",job_id:r,project:e.intent.scope==="project"?e.intent.target_id:null,model:e.intent.model,limit:null,origin:e.origin??null,reason:u,input_tokens:c.input_tokens,output_tokens:c.output_tokens})}finally{yh(c)}})(),{jobId:r,reused:!1}}async function*hl(e,t=0){let s=dt.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(!r)break;if(n+=1,yield r,r.kind==="done")return}if(s.status!=="running")return;await new Promise(r=>s.waiters.add(r))}}function El(e){let t=dt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Ir(e){let t=dt.get(e);return t?lt(t):null}import{randomUUID as zh}from"node:crypto";H();import{randomUUID as Xh}from"node:crypto";H();var bl=10,Sl=20;function Ph(e){if(!e)return!1;let t=e.split(`
1307
+ `);if(t.length<4)return!1;let s=0,n=0;for(let r of t)/^\s*\d{1,5}\t/.test(r)?s++:/^\s*\/[A-Za-z0-9_./-]+/.test(r.trim())&&n++;return s/t.length>.7||n/t.length>.5}function Uh(e){let t=e.byteLength/4;if(!Number.isInteger(t)||t<=0)return null;let s=new Float32Array(t),n=new Float32Array(e.buffer,e.byteOffset,t);return s.set(n),s}function Tl(e){let t=0;for(let n=0;n<e.length;n++)t+=e[n]*e[n];if(t<=0)return!1;let s=1/Math.sqrt(t);for(let n=0;n<e.length;n++)e[n]*=s;return!0}function vr(e){if(e.length===0)return null;let t=e[0].length,s=new Float32Array(t);for(let n of e)if(n.length===t)for(let r=0;r<t;r++)s[r]+=n[r];for(let n=0;n<t;n++)s[n]/=e.length;return Tl(s)?s:null}function yl(e){let t=new Map;if(e.length===0)return t;let s=f(),n=e.map(()=>"?").join(","),r=[];try{r=s.prepare(`SELECT cm.rowid AS rowid, cm.session_id AS session_id,
1308
+ cm.text AS text, v.embedding AS embedding
1309
+ FROM chunk_meta cm
1310
+ JOIN vec_chunks v ON v.rowid = cm.rowid
1311
+ WHERE cm.stale = 0
1312
+ AND cm.session_id IN (${n})
1313
+ ORDER BY cm.session_id, cm.rowid ASC`).all(...e)}catch{return t}if(r.length===0)return t;let o=new Map;for(let a of r){let c=o.get(a.session_id);c||(c=[],o.set(a.session_id,c)),c.push(a)}for(let[a,c]of o){let d=c.filter(M=>!Ph(M.text)),u=d.length>0?d:c,g=[];for(let M of u){let B=Uh(M.embedding);B&&Tl(B)&&g.push(B)}if(g.length===0)continue;let h=Math.min(bl,g.length),b=Math.max(0,g.length-bl),S=g.slice(0,h),y=g.slice(b),k=vr(S),w=vr(y),D=vr(g),L=new Map;for(let M=0;M<S.length&&L.size<Sl;M++)L.set(M,S[M]);for(let M=0;M<y.length&&L.size<Sl;M++)L.set(b+M,y[M]);let X=Array.from(L.values());t.set(a,{session_id:a,full_mean:D,head_pool:k,tail_pool:w,sample_chunks:X})}return t}function $h(e,t){if(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 wl(e,t=.8){let s=new Map,n=[],r=[];for(let[u,g]of e)g.full_mean&&(n.push(u),r.push(g.full_mean));if(n.length===0)return s;let o=new Int32Array(n.length);for(let u=0;u<o.length;u++)o[u]=u;let a=u=>{let g=u;for(;o[g]!==g;)g=o[g];let h=u;for(;o[h]!==g;){let b=o[h];o[h]=g,h=b}return g},c=(u,g)=>{let h=a(u),b=a(g);h!==b&&(o[h]=b)};for(let u=0;u<n.length;u++)for(let g=u+1;g<n.length;g++)$h(r[u],r[g])>=t&&c(u,g);let d=new Map;for(let u=0;u<n.length;u++){let g=a(u),h=d.get(g);h===void 0&&(h=d.size,d.set(g,h)),s.set(n[u],h)}return s}var Ae={lo:.4,hi:.7},ut="claude-haiku-4-5-20251001",pt=50,Rl=1500,kl=50,Bh={same_workflow:.15,unrelated:-.2,unsure:0};function Al(e,t,s=Ae){if(s.lo>s.hi)throw new Error(`borderline band invalid: lo=${s.lo} > hi=${s.hi}`);let n=[];for(let r of e){if(r.confidence<s.lo||r.confidence>s.hi)continue;let o=t.get(r.parent_id),a=t.get(r.child_id);!o||!a||n.push({parent:o,child:a,step1:r})}return n.sort((r,o)=>r.child.started_at_ms-o.child.started_at_ms),n}function Nl(e,t=Ae){let s=0;for(let n of e)n.confidence>=t.lo&&n.confidence<=t.hi&&s++;return s}function Hh(e,t){let s=e.replace(/\s+/g," ").trim();return s.length>t?s.slice(0,t-1)+"\u2026":s}function Wh(e,t){if(e===null)return"unknown";let s=t-e;if(s<0)return"overlap";let n=Math.round(s/6e4);if(n<60)return`${n}m`;let r=Math.round(s/36e5);return r<24?`${r}h`:`${Math.round(s/864e5)}d`}function qh(e){let s=e.parent.recent_user_messages,n=e.child.recent_user_messages,r=s.slice(0,3),o=s.length>3?s.slice(-3):[],a=n.slice(0,3),c=u=>u.length===0?" (none captured)":u.map(g=>` - ${Hh(g,500)}`).join(`
1314
+ `),d=Wh(e.parent.ended_at_ms??e.parent.started_at_ms,e.child.started_at_ms);return["You are evaluating whether two Claude Code sessions belong to the same continuous workflow.","","A deterministic scorer rated this pair in the borderline band. Its signals:",e.step1.reasons.length===0?" (no signals fired strongly)":e.step1.reasons.map(u=>` - ${u}`).join(`
1315
+ `),` step1_confidence: ${e.step1.confidence.toFixed(2)}`,"","PARENT SESSION","First user messages:",c(r),"Last user messages:",c(o),"",`CHILD SESSION (gap from parent: ${d})`,"First user messages:",c(a),"","Reply with EXACTLY one JSON object on a single line, no prose, no markdown:",'{"verdict":"same_workflow"|"unrelated"|"unsure","reason":"<one short sentence>"}',"","Guidance:",'- "same_workflow" = the child is clearly continuing what the parent was doing.','- "unrelated" = different problem, different files, different intent.','- "unsure" = could go either way; do not force a verdict.'].join(`
1316
+ `)}function Jh(e){if(!e)return null;let t=e.trim();if(!t)return null;let s=t;try{let d=JSON.parse(t);typeof d.result=="string"&&(s=d.result.trim())}catch{}let n=s.match(/\{[\s\S]*\}/);if(!n)return null;let r;try{r=JSON.parse(n[0])}catch{return null}if(!r||typeof r!="object")return null;let o=r,a=typeof o.verdict=="string"?o.verdict.toLowerCase():"";if(a!=="same_workflow"&&a!=="unrelated"&&a!=="unsure")return null;let c=typeof o.reason=="string"&&o.reason.trim()?o.reason.trim():"";return{verdict:a,reason:c,delta:Bh[a]}}async function xl(e,t={}){if(t.signal?.aborted)return null;let s=t.model??ut,n=qh(e),r=t.spawn;if(!r)try{let a=await Promise.resolve().then(()=>(ge(),Pe));if(!a.isClaudeCliAvailable())return null;r=(c,d)=>a.spawnClaudePrompt(c,[],d)}catch{return null}let o;try{o=await Promise.race([r(n,{model:s}),new Promise(a=>{t.signal&&t.signal.addEventListener("abort",()=>a({success:!1,stdout:""}),{once:!0})})])}catch{return null}return!o.success||!o.stdout?null:Jh(o.stdout)}function Ol(e){let t=[];for(let s of e.proposals){let n=`${s.parent_id}::${s.child_id}`,r=e.rescored.get(n);if(!r){t.push(s);continue}let o=Math.max(0,Math.min(1,s.confidence+r.delta));if(o<e.applyThreshold)continue;let a=`LLM: ${r.verdict}${r.reason?` \u2014 ${r.reason}`:""} (${r.delta>=0?"+":""}${r.delta.toFixed(2)})`;t.push({...s,confidence:o,reasons:[...s.reasons,a]})}return t}function Ll(e){return`${e.parent_id}::${e.child_id}`}async function Gh(e){if(e.signal?.aborted)return null;let t,s;try{({spawnClaudePrompt:t,isClaudeCliAvailable:s}=await Promise.resolve().then(()=>(ge(),Pe)))}catch{return null}if(!s())return null;let n=[];for(let o of e.sessionIds){let a=e.rowById.get(o);if(!a)continue;let c=a.alias||a.auto_title||(a.first_user_message?a.first_user_message.slice(0,120).replace(/\n/g," "):"(no title)");n.push(`- ${c}`)}let r=`Below is a chronological list of related Claude Code sessions that form one continuous workflow. Generate a single short descriptive name for the workflow as a whole. Requirements:
1317
+ - 4 to 8 words
1318
+ - Title-case, no trailing punctuation
1319
+ - Capture the WORKFLOW (e.g., "Update Remotion deps + lint cleanup"), not any one session
1320
+ - No quotes around your answer
1321
+ - No markdown
1322
+
1323
+ Sessions:
1324
+ `+n.join(`
1325
+ `)+`
1326
+
1327
+ Return ONLY the workflow name on a single line.`;try{let o=t(r,[],e.model?{model:e.model}:{}),a=null,c=new Promise(h=>{e.signal&&(a=()=>h(null),e.signal.addEventListener("abort",a,{once:!0}))}),d=await Promise.race([o,c]);if(a&&e.signal&&e.signal.removeEventListener("abort",a),!d||!d.success)return null;let u=d.stdout.trim();if(!u)return null;let g;try{let h=JSON.parse(u);g=typeof h.result=="string"?h.result:u}catch{g=u}return g=g.trim().replace(/^["'`]+|["'`]+$/g,"").replace(/[.!?]+$/g,"").trim(),g?g.length>80?g.slice(0,77)+"...":g:null}catch{return null}}function jr(e){return Yh(e)}function Mr(e){let t=new Map;for(let o of e){let a=t.get(o.project);a||(a=[],t.set(o.project,a)),a.push(o)}let s=e.map(o=>o.id),n=new Map;try{n=yl(s)}catch{n=new Map}let r=new Map;for(let[o,a]of t){let c=new Map;for(let g of a){let h=n.get(g.id);h&&c.set(g.id,h)}let d=wl(c,.8),u=[];for(let g of a){let h=Kh(g,n,d);h&&u.push(h)}r.set(o,u)}return{byProject:t,scannablesByProject:r}}function Il(e){return f().prepare(`SELECT COUNT(DISTINCT t.id) AS n
1328
+ FROM threads t
1329
+ JOIN thread_edges te ON te.thread_id = t.id
1330
+ JOIN sessions s ON s.id = te.session_id
1331
+ JOIN projects p ON p.id = s.project_id
1332
+ WHERE t.id LIKE 'auto-scan-%' AND p.name = ?`).get(e)?.n??0}function Yh(e){let t=f(),s={},n="1=1 AND s.message_count > 2";return n+=" AND COALESCE(s.auto_title, '') NOT LIKE '[meta]%' AND COALESCE(s.auto_title, '') NOT LIKE '[output-index]%' AND COALESCE(s.auto_title, '') NOT LIKE '[skill]%'",e.project&&(n+=" AND p.name = @project",s.project=e.project),t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
1333
+ s.first_user_message, s.auto_title,
1334
+ NULLIF(sa.alias, '') AS alias,
1335
+ s.file_path
1336
+ FROM sessions s
1337
+ JOIN projects p ON p.id = s.project_id
1338
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1339
+ WHERE ${n}
1340
+ ORDER BY p.name ASC, s.started_at ASC`).all(s)}function Kh(e,t,s){if(!e.started_at)return null;let n=Date.parse(e.started_at);if(!Number.isFinite(n))return null;let r=e.ended_at?Date.parse(e.ended_at):null,o=ys(e.file_path,{maxUserMessages:7}),a=t?.get(e.id);return{id:e.id,started_at_ms:n,ended_at_ms:Number.isFinite(r)?r:null,first_user_message:e.first_user_message,recent_user_messages:o.recent_user_messages,auto_title:e.auto_title,touched_files:o.touched_files,mean_embedding:a?.full_mean??null,head_pool:a?.head_pool??null,tail_pool:a?.tail_pool??null,sample_chunks:a?.sample_chunks??[],cluster_id:s?.get(e.id)??null,authored_paths:o.authored_paths,authored_content:o.authored_content}}function Cl(e){let t=e.alias||e.auto_title||(e.first_user_message?e.first_user_message.slice(0,60).replace(/\n/g," ").trim():`thread from ${e.id.slice(0,8)}`);return t.length>80?t.slice(0,77)+"...":t}async function vl(e){if(!e.rescore.enabled)return{proposals:e.proposals,ran:!1,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1};let t=e.rescore.band??Ae,s=e.rescore.cap??pt,n=e.rescore.model??ut,r=new Map(e.scannables.map(b=>[b.id,b])),o=Al(e.proposals,r,t);if(o.length===0)return{proposals:e.proposals,ran:!0,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1};if(o.length>s)return{proposals:e.proposals,ran:!1,considered:o.length,promoted:0,demoted:0,unsure:0,failed:0,capped:!0};let a=new Map,c=0,d=0,u=0,g=0;for(let b=0;b<o.length&&!e.signal?.aborted;b++){let S=o[b],y=await xl(S,{model:n,signal:e.signal});y?(a.set(Ll(S.step1),y),y.verdict==="same_workflow"?c++:y.verdict==="unrelated"?d++:u++):g++,e.onProgress?.({phase:"rescoring",current:b+1,total:o.length,verdict:y?.verdict??"failed"})}return{proposals:Ol({proposals:e.proposals,rescored:a,applyThreshold:e.applyThreshold}),ran:!0,considered:o.length,promoted:c,demoted:d,unsure:u,failed:g,capped:!1}}async function jl(e){let t=f(),s=new Map(e.rows.map(u=>[u.id,u])),n=Gi(e.edges,e.scannables),r=new Date().toISOString(),o=[],a=new Map,c=0;for(let u of n){if(c++,e.signal?.aborted)break;let g=s.get(u.rootId),h=Cl(g);if(!e.llmNames){a.set(u.rootId,h),e.onProgress?.({phase:"naming",current:c,total:n.length,thread_name:h});continue}let S=await Gh({rootRow:g,sessionIds:u.sessionIds,rowById:s,signal:e.signal,model:e.model})??h;a.set(u.rootId,S),e.onProgress?.({phase:"naming",current:c,total:n.length,thread_name:S})}return t.transaction(()=>{for(let u of n){if(!a.has(u.rootId))continue;let g=s.get(u.rootId),h=`auto-scan-${Xh()}`,b=a.get(u.rootId)??Cl(g),S=`Auto-detected workflow chain (${u.sessionIds.length} sessions). Source: auto-scan-v1.`;t.prepare("INSERT INTO threads (id, name, summary, created_at) VALUES (?, ?, ?, ?)").run(h,b,S,r),t.prepare(`INSERT INTO thread_edges
1341
+ (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
1342
+ VALUES (?, ?, NULL, 'origin', 1.0, 'auto-scan-v1', ?)`).run(h,u.rootId,r);let y=new Map;for(let k of e.edges)u.sessionIds.includes(k.child_id)&&y.set(k.child_id,k);for(let k of u.sessionIds){if(k===u.rootId)continue;let w=y.get(k);w&&t.prepare(`INSERT INTO thread_edges
1343
+ (thread_id, session_id, parent_session_id, role, confidence, source, added_at)
1344
+ VALUES (?, ?, ?, 'child', ?, 'auto-scan-v1', ?)`).run(h,k,w.parent_id,w.confidence,r)}o.push({thread_id:h,name:b,session_count:u.sessionIds.length})}})(),{project:e.project,threads:o}}var gt=new Map,Yt=new Map,Vh=300*1e3;function mt(e,t,s){e.events.push({id:e.events.length+1,kind:t,data:s});for(let n of e.waiters)n();e.waiters.clear()}function Zh(e){e.cleanupTimer||(e.cleanupTimer=setTimeout(()=>{gt.delete(e.jobId),Yt.get(e.project)===e.jobId&&Yt.delete(e.project)},Vh),e.cleanupTimer.unref?.())}function Xs(e){return{jobId:e.jobId,project:e.project,threshold:e.threshold,llm_names:e.llmNames,status:e.status,startedAt:e.startedAt,endedAt:e.endedAt,total_threads:e.totalThreads,threads_named:e.threadsNamed,edges_written:e.edgesWritten,threads_created:e.threadsCreated,current_thread_name:e.currentThreadName,error:e.error,rescore:e.rescore}}var Qh=500,eE=30,Dr="claude-haiku-4-5-20251001",Ml={"claude-haiku-4-5-20251001":{in:1,out:5,label:"Haiku 4.5"},"claude-sonnet-4-6":{in:3,out:15,label:"Sonnet 4.6"},"claude-opus-4-7":{in:15,out:75,label:"Opus 4.7"}};function Dl(e){return Ml[e]??Ml[Dr]}function Fl(e){let t=typeof e.threshold=="number"?e.threshold:Ss;if(!Number.isFinite(t)||t<0||t>1)return{error:"threshold must be a number in [0, 1]"};let s=e.model??Dr,n="",r=!!e.llm_rescore?.enabled,o={lo:e.llm_rescore?.band_lo??Ae.lo,hi:e.llm_rescore?.band_hi??Ae.hi};if(r&&(!Number.isFinite(o.lo)||!Number.isFinite(o.hi)||o.lo<0||o.hi>1||o.lo>o.hi))return{error:"rescore band must satisfy 0 \u2264 lo \u2264 hi \u2264 1"};let a=e.llm_rescore?.model??ut,c=jr({project:e.project});if(c.length===0)return{eligible_sessions:0,proposed_edges:0,estimated_threads:0,estimated_llm_calls:0,estimated_input_tokens_max:0,estimated_output_tokens_max:0,estimated_cost_usd_max:0,existing_auto_scan_threads:0,plan_window_estimate:Ce(0,s),accuracy_caveat:n,model:s,llm_rescore:r?{enabled:!0,band:o,cap:pt,estimated_borderline_pairs:0,estimated_input_tokens_max:0,estimated_output_tokens_max:0,estimated_cost_usd_max:0,plan_window_estimate:Ce(0,a),exceeds_cap:!1,model:a}:null};let{byProject:d,scannablesByProject:u}=Mr(c),g=d.get(e.project)??[],h=u.get(e.project)??[],b=r?Math.min(t,o.lo):t,S=ze(h,b),y=r?S.filter(p=>p.confidence>=t):S,k=Pl(y),w=k*Qh,D=k*eE,L=Dl(s),X=w/1e6*L.in,M=D/1e6*L.out,B=X+M,se=Ce(k,s),i=Il(e.project),l=null;if(r){let p=Nl(S,o),m=Dl(a),_=p*Rl,E=p*kl,T=_/1e6*m.in+E/1e6*m.out;l={enabled:!0,band:o,cap:pt,estimated_borderline_pairs:p,estimated_input_tokens_max:_,estimated_output_tokens_max:E,estimated_cost_usd_max:Math.round(T*1e4)/1e4,plan_window_estimate:Ce(p,a),exceeds_cap:p>pt,model:a}}return{eligible_sessions:g.length,proposed_edges:y.length,estimated_threads:k,estimated_llm_calls:k,estimated_input_tokens_max:w,estimated_output_tokens_max:D,estimated_cost_usd_max:Math.round(B*1e4)/1e4,existing_auto_scan_threads:i,plan_window_estimate:se,accuracy_caveat:n,model:s,llm_rescore:l}}function Pl(e){let t=new Map,s=o=>{let a=o;for(;t.get(a)!==a;)a=t.get(a);return a},n=(o,a)=>{let c=s(o),d=s(a);c!==d&&t.set(c,d)};for(let o of e)t.has(o.parent_id)||t.set(o.parent_id,o.parent_id),t.has(o.child_id)||t.set(o.child_id,o.child_id),n(o.parent_id,o.child_id);let r=new Set;for(let o of t.keys())r.add(s(o));return r.size}function Ul(e){let t=typeof e.threshold=="number"?e.threshold:Ss;if(!Number.isFinite(t)||t<0||t>1)return{error:"threshold must be a number in [0, 1]"};let s=e.llm_names??!0,n=e.model??Dr,r=!!e.llm_rescore?.enabled,o={lo:e.llm_rescore?.band_lo??Ae.lo,hi:e.llm_rescore?.band_hi??Ae.hi};if(r&&(!Number.isFinite(o.lo)||!Number.isFinite(o.hi)||o.lo<0||o.hi>1||o.lo>o.hi))return{error:"rescore band must satisfy 0 \u2264 lo \u2264 hi \u2264 1"};let a=e.llm_rescore?.model??ut,c=Yt.get(e.project);if(c){let B=gt.get(c);if(B&&B.status==="running")return{jobId:c,reused:!0};Yt.delete(e.project)}let d=jr({project:e.project});if(d.length===0)return{error:`no eligible sessions in project "${e.project}"`};let{byProject:u,scannablesByProject:g}=Mr(d),h=u.get(e.project),b=g.get(e.project);if(!h||!b)return{error:`project "${e.project}" not found`};let S=r?Math.min(t,o.lo):t,y=ze(b,S),k=r?y.filter(B=>B.confidence>=t):y;if(y.length===0||k.length===0&&!r)return{error:"no edges above threshold; nothing to apply"};let w=zh(),D=new AbortController,L=Pl(k),X=new Date().toISOString(),M={jobId:w,project:e.project,threshold:t,llmNames:s,status:"running",startedAt:X,endedAt:null,events:[],waiters:new Set,controller:D,totalThreads:L,threadsNamed:0,edgesWritten:0,threadsCreated:0,currentThreadName:null,error:null,cleanupTimer:null,rescore:r?{enabled:!0,considered:0,promoted:0,demoted:0,unsure:0,failed:0,capped:!1}:null};return gt.set(w,M),Yt.set(e.project,w),(async()=>{await Promise.resolve();try{mt(M,"progress",{phase:"linking",edges_proposed:y.length,estimated_threads:L});let B=y;if(r){let i=await vl({proposals:y,scannables:b,applyThreshold:t,rescore:{enabled:!0,band:o,model:a},signal:D.signal,onProgress:l=>{l.phase==="rescoring"&&mt(M,"progress",{phase:"rescoring",current:l.current,total:l.total,verdict:l.verdict})}});B=i.proposals,M.rescore={enabled:!0,considered:i.considered,promoted:i.promoted,demoted:i.demoted,unsure:i.unsure,failed:i.failed,capped:i.capped}}else B=y.filter(i=>i.confidence>=t);if(B.length===0){M.status=D.signal.aborted?"cancelled":"done",M.endedAt=new Date().toISOString(),mt(M,"done",{...Xs(M),threads:[]});return}let se=await jl({project:e.project,rows:h,edges:B,scannables:b,llmNames:s,model:n,signal:D.signal,onProgress:i=>{i.phase==="naming"&&(M.threadsNamed=i.current,M.currentThreadName=i.thread_name??null,mt(M,"progress",{phase:"naming",current:i.current,total:i.total,thread_name:i.thread_name}))}});M.threadsCreated=se.threads.length,M.edgesWritten=B.length+se.threads.length,M.status=D.signal.aborted?"cancelled":"done",M.endedAt=new Date().toISOString(),mt(M,"done",{...Xs(M),threads:se.threads})}catch(B){M.status="failed",M.endedAt=new Date().toISOString(),M.error=B instanceof Error?B.message:String(B??"unknown error"),mt(M,"done",Xs(M))}finally{Zh(M)}})(),{jobId:w,reused:!1}}async function*$l(e,t=0){let s=gt.get(e);if(!s)return;let n=t;for(;;){for(;n<s.events.length;){let r=s.events[n];if(!r)break;if(n+=1,yield r,r.kind==="done")return}if(s.status!=="running")return;await new Promise(r=>s.waiters.add(r))}}function Bl(e){let t=gt.get(e);return!t||t.status!=="running"?!1:(t.controller.abort(),!0)}function Fr(e){let t=gt.get(e);return t?Xs(t):null}rs();import{randomUUID as tE}from"node:crypto";var Gs=new Map;function Hl(e){let t={id:tE(),status:"pending",createdAt:new Date().toISOString(),finishedAt:null,total:e,completed:0,results:[],error:null,controller:new AbortController,listeners:new Set};return Gs.set(t.id,t),t}function Ys(e){return Gs.get(e)}function _t(e,t){for(let s of e.listeners)s(t)}function Wl(e,t){return e.listeners.add(t),()=>{e.listeners.delete(t)}}function ql(e){let t=Gs.get(e);return t?(t.controller.abort(),t.status="cancelled",t.finishedAt=new Date().toISOString(),_t(t,{type:"status",status:"cancelled"}),!0):!1}function Jl(e){return Gs.delete(e)}Ge();function Ks(e){let{session:t,knownTags:s,minTags:n,maxTags:r}=e,o=s.slice(0,50).map(c=>c.tag).join(", "),a=t.current_tags.length>0?`already applied, do not repeat: [${t.current_tags.join(", ")}]`:"currently has no tags";return[`You are tagging a software engineering session. Produce ${n}-${r} concise, lowercase, hyphen-separated tags that describe:`," - the domain or subsystem touched (auth, db, frontend, ...)"," - the kind of work (bugfix, feature, refactor, research, ...)"," - any specific tool, library, or product if prominent","","Prefer tags from the known-tags list below when applicable \u2014 consistency over creativity. Never invent marketing-sounding labels. Never tag based on speculation; only on observed work.","",`known tags (most used first, up to 50): ${o||"(none yet)"}`,"","Session:",` project: ${t.project}`,t.alias?` alias: ${JSON.stringify(t.alias)}`:" alias: (none)",t.git_branch?` git_branch: ${t.git_branch}`:"",` ${a}`," first_user_message:",Xl(t.first_user_message," ")," message_sample:",Xl(t.message_sample," "),"","Return a single JSON object matching this schema exactly, with no prose before or after:",`{"tags": string[] length ${n}-${r}, "confidence": number 0-1, "rationale": string one short sentence}`].filter(Boolean).join(`
1345
+ `)}function Xl(e,t){return e.split(`
1346
+ `).map(s=>t+s).join(`
1347
+ `)}Ge();import{z as Kt}from"zod";var sE=Kt.object({tags:Kt.array(Kt.string()).min(1),confidence:Kt.number().min(0).max(1),rationale:Kt.string().min(1).max(500)});function nE(e){let t=e.trim();return t.startsWith("```")?t.replace(/^```(?:json)?\s*/i,"").replace(/```$/i,"").trim():t}function zs(e,t={}){let s=t.maxTags??10,n;try{n=JSON.parse(nE(e))}catch{return{ok:!1,reason:"not valid JSON"}}let r=sE.safeParse(n);if(!r.success)return{ok:!1,reason:r.error.issues.map(c=>c.message).join("; ")};let o=r.data.tags.map(c=>De(c)).filter(c=>c.length>0),a=Array.from(new Set(o)).slice(0,s);return a.length===0?{ok:!1,reason:"no usable tags after normalization"}:{ok:!0,data:{tags:a,confidence:r.data.confidence,rationale:r.data.rationale}}}import rE from"@anthropic-ai/sdk";async function Vs(e){let n=(await new rE({apiKey:e.apiKey}).messages.create({model:e.model,max_tokens:512,temperature:.2,messages:[{role:"user",content:e.prompt}]},e.signal?{signal:e.signal}:void 0)).content.find(r=>r.type==="text");if(!n||n.type!=="text")throw new Error("Anthropic response contained no text block");return n.text}function oE(e){if(!(e instanceof Error))return!1;let t=e;return t.status===429||t.status===502||t.status===503||t.status===504}async function Zs(e,t={}){let s=t.maxAttempts??3,n=t.baseDelayMs??500,r;for(let o=0;o<s;o++)try{return await e()}catch(a){if(r=a,!oE(a)||o===s-1)throw a;await new Promise(c=>setTimeout(c,n*Math.pow(2,o)))}throw r}async function Gl(e,t){e.status="running",_t(e,{type:"status",status:"running"});let s=Xe();for(let n of t.sessions){if(e.controller.signal.aborted)break;let r=Ks({session:n,knownTags:s,minTags:t.minTags,maxTags:t.maxTags}),o={sessionId:n.id,project:n.project,alias:n.alias,first_user_message:n.first_user_message,current_tags:n.current_tags,suggestion:null,error:null,applied:!1};try{let a=await Zs(()=>Vs({apiKey:t.apiKey,model:t.model,prompt:r,signal:e.controller.signal})),c=zs(a,{maxTags:t.maxTags});c.ok?o.suggestion=c.data:o.error=c.reason}catch(a){o.error=a instanceof Error?a.message:String(a)}e.results.push(o),e.completed+=1,_t(e,{type:"result",result:o}),_t(e,{type:"progress",completed:e.completed,total:e.total})}if(!e.controller.signal.aborted){e.status="completed",e.finishedAt=new Date().toISOString();let n=e.results.filter(o=>o.suggestion&&!o.error).length,r=e.results.filter(o=>o.error).length;_t(e,{type:"done",summary:{ok:n,failed:r}})}}function Yl(e,t){let s=0,n=0;for(let r of t){let o=e.results.find(a=>a.sessionId===r.sessionId);if(o){for(let a of r.tags)try{let{added:c}=Je(r.sessionId,a);c&&(s+=1)}catch(c){console.error("[scanner] apply failed:",r.sessionId,a,c),n+=1}o.applied=!0}}return{applied:s,failed:n}}Ge();rs();var Y={running:!1,status:"idle",processed:0,total:0,currentSessionId:null,lastError:null,lastRunAt:null,controller:null},Pr=new Set;function Vt(){return{status:Y.status,processed:Y.processed,total:Y.total,currentSessionId:Y.currentSessionId,lastError:Y.lastError,lastRunAt:Y.lastRunAt}}function Kl(e){return Pr.add(e),()=>{Pr.delete(e)}}function zt(){let e=Vt();for(let t of Pr)t(e)}async function Qs(){let e=Re();if(!(!e.enabled||!e.autopilot)&&!(e.backend!=="api"||!e.apiKey)&&!Y.running){Y.running=!0,Y.status="scanning",Y.processed=0,Y.total=0,Y.currentSessionId=null,Y.lastError=null,Y.controller=new AbortController,zt();try{let t=Ye({untaggedOnly:!0,limit:200});if(Y.total=t.length,zt(),t.length===0){Y.status="idle",Y.lastRunAt=new Date().toISOString();return}let s=Xe();for(let n of t){if(Y.controller.signal.aborted)break;let r=Re();if(!r.autopilot||!r.enabled||r.backend!=="api"||!r.apiKey)break;Y.currentSessionId=n.id,zt();let o=Ks({session:n,knownTags:s,minTags:r.minTagsPerSession,maxTags:r.maxTagsPerSession});try{let a=await Zs(()=>Vs({apiKey:r.apiKey,model:r.model,prompt:o,signal:Y.controller.signal})),c=zs(a,{maxTags:r.maxTagsPerSession});if(c.ok)for(let d of c.data.tags)try{Je(n.id,d)}catch(u){console.error("[autopilot] addTag failed:",n.id,d,u)}}catch(a){console.error("[autopilot] scan failed for",n.id,a)}Y.processed+=1,zt(),await new Promise(a=>setTimeout(a,1500))}Y.status="idle",Y.lastRunAt=new Date().toISOString()}catch(t){Y.status="error",Y.lastError=t instanceof Error?t.message:String(t),console.error("[autopilot] fatal:",t)}finally{Y.running=!1,Y.currentSessionId=null,Y.controller=null,zt()}}}import{execFileSync as iE}from"node:child_process";import{existsSync as zl,readFileSync as aE,writeFileSync as Vl}from"node:fs";import{homedir as cE}from"node:os";import{dirname as lE,join as dE,resolve as uE}from"node:path";import{fileURLToPath as pE}from"node:url";var ft=dE(cE(),".claude.json"),mE=lE(pE(import.meta.url)),gE=uE(mE,"..","mcp","server.js");function $r(){if(!zl(ft))return{};try{let e=aE(ft,"utf8"),t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}catch(e){return console.error("[mcp-installer] failed to parse ~/.claude.json:",e),{}}}var Ur=new Map;function _E(e){let t=Ur.get(e);if(t!==void 0)return t;try{return iE("command",["-v",e],{stdio:"ignore"}),Ur.set(e,!0),!0}catch{return Ur.set(e,!1),!1}}function fE(){return _E("claude-recall-mcp")?{command:"claude-recall-mcp",args:[]}:{command:process.execPath,args:[gE]}}function Ie(){let t=$r().mcpServers?.recall;return{configPath:ft,configExists:zl(ft),installed:!!(t&&typeof t.command=="string"),command:t?.command??null,args:t?.args??null}}function Zl(){let e=$r(),t=e.mcpServers??{},s=fE(),n=t.recall;if(n?.command===s.command&&JSON.stringify(n?.args??[])===JSON.stringify(s.args))return Ie();let o={command:s.command};s.args.length>0&&(o.args=s.args);let a={...e,mcpServers:{...t,recall:o}};return Vl(ft,JSON.stringify(a,null,2)),Ie()}function Ql(){let e=$r(),t=e.mcpServers??{};if(!t.recall)return Ie();let{recall:s,...n}=t,r={...e,mcpServers:n};return Vl(ft,JSON.stringify(r,null,2)),Ie()}import{existsSync as ed,mkdirSync as hE,readFileSync as EE,writeFileSync as td}from"node:fs";import{homedir as bE}from"node:os";import{join as sd}from"node:path";import{z as ve}from"zod";function nd(){return process.env.RECALL_HOME??sd(bE(),".recall")}function rd(){let e=nd();ed(e)||hE(e,{recursive:!0})}function Hr(){return sd(nd(),"onboarding.json")}var en=ve.object({version:ve.literal(1).default(1),completed:ve.boolean().default(!1),skipped:ve.boolean().default(!1),finishedAt:ve.string().nullable().default(null),completedSteps:ve.array(ve.string().max(100)).max(50).default([]),threadsIntroSeen:ve.boolean().default(!1)}),Br={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:[],threadsIntroSeen:!1};function tn(){let e=Hr();if(!ed(e))return{...Br};try{let t=JSON.parse(EE(e,"utf8")),s=en.safeParse(t);return s.success?s.data:{...Br}}catch(t){return console.error("[onboarding-state] failed to parse onboarding.json, using defaults:",t),{...Br}}}function od(e){rd();let t=tn(),s=en.parse({...t,...e,completedSteps:SE([...t.completedSteps??[],...e.completedSteps??[]]),version:1});return td(Hr(),JSON.stringify(s,null,2)),s}function id(){rd();let e=tn(),t={version:1,completed:!1,skipped:!1,finishedAt:null,completedSteps:e.completedSteps,threadsIntroSeen:e.threadsIntroSeen};return td(Hr(),JSON.stringify(t,null,2)),t}function SE(e){let t=new Set,s=[];for(let n of e)t.has(n)||(t.add(n),s.push(n));return s}ge();xn();Nn();import{existsSync as ad,mkdirSync as TE,readFileSync as yE,writeFileSync as wE}from"node:fs";import{homedir as RE}from"node:os";import{join as cd}from"node:path";import{z as Ne}from"zod";function ld(){return process.env.RECALL_HOME??cd(RE(),".recall")}function kE(){let e=ld();ad(e)||TE(e,{recursive:!0})}function dd(){return cd(ld(),"config.json")}var nn=Ne.object({enabled:Ne.boolean().default(!1),model:Ne.string().optional(),ratePerMinute:Ne.number().int().min(1).max(600).default(30),lastProcessedSessionId:Ne.string().nullable().default(null),backfillPaused:Ne.boolean().default(!1),autoExtractEnabled:Ne.boolean().default(!1),autoExtractIntervalMinutes:Ne.number().int().min(5).max(720).default(60),autoExtractBatchSize:Ne.number().int().min(1).max(20).default(1)}),sn={enabled:!1,ratePerMinute:30,lastProcessedSessionId:null,backfillPaused:!1,autoExtractEnabled:!1,autoExtractIntervalMinutes:60,autoExtractBatchSize:1};function ud(){let e=dd();if(!ad(e))return{};try{return JSON.parse(yE(e,"utf8"))}catch(t){return console.error("[semantic-config] failed to parse config.json, using defaults:",t),{}}}function ce(){let e=ud().semantic;if(!e)return{...sn};let t=nn.safeParse({...sn,...e});return t.success?t.data:{...sn}}function rn(e){kE();let t=ud(),s=nn.parse({...sn,...t.semantic??{},...e}),n={...t,semantic:s};return wE(dd(),JSON.stringify(n,null,2)),s}H();ge();import{existsSync as AE,mkdirSync as NE,writeFileSync as xE}from"node:fs";import{homedir as OE}from"node:os";import{join as Wr}from"node:path";var LE=1,CE=12e3,pd=3,IE=[];function vE(){return process.env.RECALL_HOME??Wr(OE(),".recall")}function md(){return Wr(vE(),"semantic")}function jE(){let e=md();AE(e)||NE(e,{recursive:!0})}function ME(e){let t=f(),s=t.prepare(`SELECT s.id, s.message_count, s.first_user_message,
1348
+ p.name AS project,
1349
+ NULLIF(sa.alias, '') AS alias
1350
+ FROM sessions s
1351
+ JOIN projects p ON p.id = s.project_id
1352
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1353
+ WHERE s.id = ?`).get(e);if(!s)return null;let n=t.prepare(`SELECT role, content_text
1354
+ FROM messages
1355
+ WHERE session_id = ? AND is_sidechain = 0
1356
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(e),r=[],o=0;for(let a of n){if(!a.content_text)continue;let c=a.role??"system",d=a.content_text.replace(/```[\s\S]*?```/g,"[code]").replace(/<[^>]+>[\s\S]*?<\/[^>]+>/g,"").trim();if(!d)continue;let u=d.length>1500?d.slice(0,1500)+"\u2026":d,g=`${c}: ${u}`;if(o+g.length>CE)break;r.push(g),o+=g.length}return{id:s.id,alias:s.alias,project:s.project,firstUserMessage:s.first_user_message,excerpt:r.join(`
1357
+
1358
+ `),messageCount:s.message_count}}function DE(e){return["You are summarizing a Claude Code session for a local semantic-search index. The summary will be stored as plain text and matched against future natural-language queries.","",`Session: ${e.alias??e.id}`,`Project: ${e.project}`,e.firstUserMessage?`Opening prompt: ${e.firstUserMessage}`:"","","Transcript excerpt (truncated):","---",e.excerpt,"---","","Output a single JSON object on one line, with no Markdown fences and no commentary:",'{"summary": "<3 sentences describing what the user was trying to do, what was built or debugged, and the outcome>", "keywords": ["<concept>", "<technology>", "<problem>", ...]}',"","Constraints:","- summary: 3 sentences, plain prose, no bullet points",'- keywords: 10\u201315 lowercase tokens, multi-word entries hyphenated (e.g. "memory-leak"); no duplicates; no generic words like "code" or "session"',"- Output JSON only. Do not echo this prompt."].filter(Boolean).join(`
1359
+ `)}function FE(e){let t=e.trim();try{let o=JSON.parse(t);typeof o.result=="string"&&(t=o.result.trim())}catch{}t=t.replace(/^```(?:json)?\s*/i,"").replace(/```\s*$/i,"").trim();let s=t.indexOf("{"),n=t.lastIndexOf("}");if(s===-1||n===-1||n<=s)return null;let r=t.slice(s,n+1);try{let o=JSON.parse(r),a=typeof o.summary=="string"?o.summary.trim():"",d=(Array.isArray(o.keywords)?o.keywords:[]).filter(u=>typeof u=="string").map(u=>u.trim().toLowerCase()).filter(u=>u.length>0&&u.length<64);return!a||d.length===0?null:{summary:a,keywords:Array.from(new Set(d)).slice(0,20)}}catch{return null}}function PE(e){let t=f(),s=e.keywords.join(",");t.prepare(`INSERT INTO session_semantic
1360
+ (session_id, summary, keywords, model, source_message_count, generated_at)
1361
+ VALUES (@session_id, @summary, @keywords, @model, @source_message_count, @generated_at)
1362
+ ON CONFLICT(session_id) DO UPDATE SET
1363
+ summary = excluded.summary,
1364
+ keywords = excluded.keywords,
1365
+ model = excluded.model,
1366
+ source_message_count = excluded.source_message_count,
1367
+ generated_at = excluded.generated_at`).run({session_id:e.sessionId,summary:e.summary,keywords:s,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt}),jE();let n=Wr(md(),`${e.sessionId}.json`);xE(n,JSON.stringify({version:LE,session_id:e.sessionId,summary:e.summary,keywords:e.keywords,model:e.model,source_message_count:e.sourceMessageCount,generated_at:e.generatedAt},null,2))}var on=null;function UE(){let t=ce().ratePerMinute,s=t/6e4;return(!on||on.capacity!==t)&&(on={tokens:t,capacity:t,refillPerMs:s,lastRefill:Date.now()}),on}function $E(e){let t=Date.now(),s=t-e.lastRefill;s>0&&(e.tokens=Math.min(e.capacity,e.tokens+s*e.refillPerMs),e.lastRefill=t)}async function BE(e){for(;;){if(e?.aborted)throw new Error("aborted");let t=UE();if($E(t),t.tokens>=1){t.tokens-=1;return}let s=1-t.tokens,n=Math.max(50,Math.ceil(s/t.refillPerMs));await new Promise(r=>setTimeout(r,Math.min(n,5e3)))}}async function an(e,t={}){let s=ce();if(!s.enabled)return{sessionId:e,ok:!1,reason:"disabled"};if(!oe())return{sessionId:e,ok:!1,reason:"claude-cli-missing"};let n=ME(e);if(!n)return{sessionId:e,ok:!1,reason:"session-not-found"};if(n.messageCount<pd)return{sessionId:e,ok:!1,reason:"too-short"};if(!n.excerpt.trim())return{sessionId:e,ok:!1,reason:"empty-excerpt"};await BE(t.signal);let r=DE(n),o=await Fe(r,IE,{model:s.model});if(!o.success)return{sessionId:e,ok:!1,reason:`claude-cli-exit-${o.exitCode??"null"}`,model:s.model??null};let a=FE(o.stdout);return a?(PE({sessionId:n.id,summary:a.summary,keywords:a.keywords,model:s.model??null,sourceMessageCount:n.messageCount,generatedAt:new Date().toISOString()}),{sessionId:e,ok:!0,model:s.model??null}):{sessionId:e,ok:!1,reason:"parse-failed",model:s.model??null}}async function cn(e={}){let t=ce();if(!t.enabled)return{total:0,processed:0,ok:0,failed:0,currentSessionId:null};let s=f(),r={limit:e.limit??1e3},o="s.message_count >= 3";e.force||(o+=" AND ss.session_id IS NULL"),typeof e.projectId=="number"&&(o+=" AND s.project_id = @projectId",r.projectId=e.projectId),t.lastProcessedSessionId&&e.force;let a=s.prepare(`SELECT s.id
1368
+ FROM sessions s
1369
+ LEFT JOIN session_semantic ss ON ss.session_id = s.id
1370
+ WHERE ${o}
1371
+ ORDER BY COALESCE(s.started_at, '') ASC, s.id ASC
1372
+ LIMIT @limit`).all(r),c={total:a.length,processed:0,ok:0,failed:0,currentSessionId:null};e.onProgress?.(c);for(let{id:d}of a){if(e.signal?.aborted||ce().backfillPaused)break;c.currentSessionId=d,e.onProgress?.({...c});try{(await an(d,{signal:e.signal})).ok?c.ok+=1:c.failed+=1}catch(g){c.failed+=1,console.error("[semantic.backfill] failed for",d,g)}c.processed+=1,rn({lastProcessedSessionId:d}),e.onProgress?.({...c})}return c.currentSessionId=null,e.onProgress?.({...c}),c}async function gd(e){if(!ce().enabled)return;let n=f().prepare(`SELECT s.message_count, s.ended_at,
1373
+ ss.generated_at, ss.source_message_count
1374
+ FROM sessions s
1375
+ LEFT JOIN session_semantic ss ON ss.session_id = s.id
1376
+ WHERE s.id = ?`).get(e);if(n&&!(n.message_count<pd)&&!(n.generated_at&&n.source_message_count!=null&&n.source_message_count>=n.message_count))try{await an(e)}catch(r){console.error("[semantic] processSession error for",e,r)}}function qr(){let e=ce(),t=f(),s=t.prepare("SELECT COUNT(*) AS n FROM sessions WHERE message_count >= 3").get().n,n=t.prepare("SELECT COUNT(*) AS n FROM session_semantic").get().n;return{enabled:e.enabled,claudeCliAvailable:oe(),ratePerMinute:e.ratePerMinute,model:e.model??null,totalSessions:s,processedSessions:n,pendingSessions:Math.max(0,s-n),lastProcessedSessionId:e.lastProcessedSessionId,backfillPaused:e.backfillPaused}}H();H();import{createReadStream as HE}from"node:fs";import{createInterface as WE}from"node:readline";function ln(e){return typeof e=="number"&&Number.isFinite(e)?e:0}function Gr(e){let t=e?.usage;if(!t||typeof t!="object")return null;let s={inputTokens:ln(t.input_tokens),outputTokens:ln(t.output_tokens),cacheCreateTokens:ln(t.cache_creation_input_tokens),cacheReadTokens:ln(t.cache_read_input_tokens)};return s.inputTokens===0&&s.outputTokens===0&&s.cacheCreateTokens===0&&s.cacheReadTokens===0?null:s}var qE=/\x1B\[[0-9;]*[a-zA-Z]/g;function Jr(e){return e.replace(qE,"")}var Xr=12e3;function _d(e,t){if(e.length<=Xr)return e;let s=e.slice(0,Xr),n=e.length-Xr;return`${s}
1377
+
1378
+ \u27E8\u2026 ${n.toLocaleString()} more chars in ${t}; see raw JSONL for full content \u27E9`}function JE(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function XE(e){if(typeof e.content=="string")return e.content;if(Array.isArray(e.content)){let t=[];for(let s of e.content)if(s&&typeof s=="object"){let n=s;n.type==="text"&&typeof n.text=="string"?t.push(n.text):n.type==="image"&&t.push("[image]")}return t.join(`
1379
+ `)}return""}function GE(e){if(!e)return{text:"",toolNames:[]};if(typeof e.content=="string")return{text:Jr(e.content),toolNames:[]};if(!Array.isArray(e.content))return{text:"",toolNames:[]};let t=[],s=[];for(let n of e.content)if(!(!n||typeof n!="object")){if(n.type==="text"&&typeof n.text=="string"){t.push(Jr(n.text));continue}if(n.type==="tool_use"&&typeof n.name=="string"){s.push(n.name);let r=n.input!=null?JE(n.input):"",o=_d(r,"tool input");t.push(`\u26A1 **Tool call \xB7 \`${n.name}\`**
1380
+
1381
+ \`\`\`json
1382
+ ${o}
1383
+ \`\`\``);continue}if(n.type==="tool_result"){let r=Jr(XE(n));if(r){let o=_d(r,"tool result");t.push(`**Tool result**
1384
+
1385
+ \`\`\`
1386
+ ${o}
1387
+ \`\`\``)}else t.push("_(tool result was empty or image-only)_");continue}if(n.type==="image"){t.push("_(image)_");continue}t.push(`_(unknown block: ${n.type})_`)}return{text:t.join(`
1388
+
1389
+ `),toolNames:s}}async function*fd(e){let t=HE(e,{encoding:"utf8"}),s=WE({input:t,crlfDelay:1/0});for await(let n of s){if(!n.trim())continue;let r;try{r=JSON.parse(n)}catch{continue}if(!r.uuid||!r.sessionId)continue;let{text:o,toolNames:a}=GE(r.message);yield{uuid:r.uuid,parentUuid:r.parentUuid??null,sessionId:r.sessionId,type:r.type??"unknown",role:r.message?.role??null,timestamp:r.timestamp??null,isSidechain:r.isSidechain===!0,cwd:r.cwd??null,gitBranch:r.gitBranch??null,version:r.version??null,contentText:o,toolNames:a,raw:n,usage:Gr(r.message),model:r.message?.model??null}}}function hd(e,t,s){e.prepare("DELETE FROM message_usage WHERE session_id = ?").run(t);let n=e.prepare(`
1390
+ INSERT INTO message_usage (
1391
+ message_uuid, session_id, model,
1392
+ input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
1393
+ timestamp
1394
+ ) VALUES (
1395
+ @uuid, @session_id, @model,
1396
+ @input, @output, @cc, @cr,
1397
+ @ts
1398
+ )
1399
+ ON CONFLICT(message_uuid) DO UPDATE SET
1400
+ model = excluded.model,
1401
+ input_tokens = excluded.input_tokens,
1402
+ output_tokens = excluded.output_tokens,
1403
+ cache_create_tokens = excluded.cache_create_tokens,
1404
+ cache_read_tokens = excluded.cache_read_tokens,
1405
+ timestamp = excluded.timestamp
1406
+ `);for(let r of s)r.usage&&r.role==="assistant"&&n.run({uuid:r.uuid,session_id:t,model:r.model,input:r.usage.inputTokens,output:r.usage.outputTokens,cc:r.usage.cacheCreateTokens,cr:r.usage.cacheReadTokens,ts:r.timestamp})}function dn(e,t){let s=e.prepare(`SELECT
1407
+ COALESCE(SUM(input_tokens), 0) AS input_tokens,
1408
+ COALESCE(SUM(output_tokens), 0) AS output_tokens,
1409
+ COALESCE(SUM(cache_create_tokens), 0) AS cache_create_tokens,
1410
+ COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens
1411
+ FROM message_usage WHERE session_id = ?`).get(t),n=e.prepare(`SELECT model, SUM(output_tokens) AS out
1412
+ FROM message_usage
1413
+ WHERE session_id = ? AND model IS NOT NULL
1414
+ GROUP BY model
1415
+ ORDER BY out DESC LIMIT 1`).get(t);e.prepare(`UPDATE sessions SET
1416
+ total_input_tokens = @input,
1417
+ total_output_tokens = @output,
1418
+ total_cache_create_tokens = @cc,
1419
+ total_cache_read_tokens = @cr,
1420
+ primary_model = @model
1421
+ WHERE id = @id`).run({id:t,input:s.input_tokens,output:s.output_tokens,cc:s.cache_create_tokens,cr:s.cache_read_tokens,model:n?.model??null})}var YE=500,un=new Set,Ed=null,pn=!1;function bd(){return un.size}function KE(){return`
1422
+ SELECT m.uuid, m.session_id, m.timestamp, m.raw_json
1423
+ FROM messages m
1424
+ LEFT JOIN message_usage mu ON mu.message_uuid = m.uuid
1425
+ WHERE m.role = 'assistant' AND mu.message_uuid IS NULL
1426
+ AND m.uuid NOT IN (SELECT value FROM json_each(?))
1427
+ LIMIT ?
1428
+ `}function Sd(e,t){let s=t.limit??Number.MAX_SAFE_INTEGER,n=Math.max(1,t.chunkSize??YE),r=e.prepare(KE()),o=e.prepare(`
1429
+ INSERT INTO message_usage (
1430
+ message_uuid, session_id, model,
1431
+ input_tokens, output_tokens, cache_create_tokens, cache_read_tokens,
1432
+ timestamp
1433
+ ) VALUES (
1434
+ @uuid, @session_id, @model,
1435
+ @input, @output, @cc, @cr, @ts
1436
+ )
1437
+ ON CONFLICT(message_uuid) DO NOTHING
1438
+ `),a=0,c=0,d=new Set;for(;a<s;){let u=Math.min(n,s-a),g=JSON.stringify([...un]),h=r.all(g,u);if(h.length===0)break;let b=new Set;if(e.transaction(()=>{for(let y of h){let k;try{k=JSON.parse(y.raw_json)}catch{un.add(y.uuid);continue}let w=Gr(k.message);if(!w){un.add(y.uuid);continue}o.run({uuid:y.uuid,session_id:y.session_id,model:k.message?.model??null,input:w.inputTokens,output:w.outputTokens,cc:w.cacheCreateTokens,cr:w.cacheReadTokens,ts:y.timestamp}),c+=1,b.add(y.session_id)}for(let y of b)dn(e,y),d.add(y)})(),a+=h.length,t.onProgress?.({scanned:a,inserted:c,sessionsTouched:d.size,done:h.length<u}),h.length<u)break}return{scanned:a,inserted:c,sessionsTouched:d.size,done:!0}}function Td(e={}){return Sd(f(),e)}function yd(e={}){return pn?!1:(pn=!0,queueMicrotask(()=>{try{let t=Sd(f(),e);Ed={scanned:t.scanned,inserted:t.inserted,sessionsTouched:t.sessionsTouched,finishedAt:new Date().toISOString()}}catch(t){console.error("[stats.backfill] failed:",t)}finally{pn=!1}}),!0)}function wd(){return pn}function Yr(){return Ed}var zE=[[/opus[-_ ]?4[-_. ]?7/i,{label:"Opus 4.7",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/opus[-_ ]?4[-_. ]?6/i,{label:"Opus 4.6",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet[-_ ]?4[-_. ]?6/i,{label:"Sonnet 4.6",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/sonnet[-_ ]?4[-_. ]?5/i,{label:"Sonnet 4.5",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/haiku[-_ ]?4[-_. ]?5/i,{label:"Haiku 4.5",inputCentsPerMtok:100,outputCentsPerMtok:500,cacheCreateCentsPerMtok:125,cacheReadCentsPerMtok:10}],[/opus[-_ ]?4(?!.*[5-9])/i,{label:"Opus 4",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet[-_ ]?4(?!.*[5-9])/i,{label:"Sonnet 4",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?7[-_ ]?sonnet|sonnet[-_ ]?3[-_. ]?7/i,{label:"Sonnet 3.7",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?5[-_ ]?sonnet|sonnet[-_ ]?3[-_. ]?5/i,{label:"Sonnet 3.5",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/3[-_. ]?5[-_ ]?haiku|haiku[-_ ]?3[-_. ]?5/i,{label:"Haiku 3.5",inputCentsPerMtok:80,outputCentsPerMtok:400,cacheCreateCentsPerMtok:100,cacheReadCentsPerMtok:8}],[/3[-_ ]?opus|opus[-_ ]?3/i,{label:"Opus 3",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/3[-_ ]?haiku|haiku(?!.*3[-_. ]?5)/i,{label:"Haiku 3",inputCentsPerMtok:25,outputCentsPerMtok:125,cacheCreateCentsPerMtok:30,cacheReadCentsPerMtok:3}],[/opus/i,{label:"Opus",inputCentsPerMtok:1500,outputCentsPerMtok:7500,cacheCreateCentsPerMtok:1875,cacheReadCentsPerMtok:150}],[/sonnet/i,{label:"Sonnet",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30}],[/haiku/i,{label:"Haiku",inputCentsPerMtok:100,outputCentsPerMtok:500,cacheCreateCentsPerMtok:125,cacheReadCentsPerMtok:10}]],Rd={label:"unknown",inputCentsPerMtok:300,outputCentsPerMtok:1500,cacheCreateCentsPerMtok:375,cacheReadCentsPerMtok:30};function je(e){if(!e)return Rd;for(let[t,s]of zE)if(t.test(e))return s;return Rd}function he(e,t){if(e.byModel&&Object.keys(e.byModel).length>0){let a={input:0,output:0,cacheCreate:0,cacheRead:0},c=0;for(let[u,g]of Object.entries(e.byModel)){let h=je(u);a.input+=g.inputTokens/1e6*h.inputCentsPerMtok,a.output+=g.outputTokens/1e6*h.outputCentsPerMtok,a.cacheCreate+=g.cacheCreateTokens/1e6*h.cacheCreateCentsPerMtok,a.cacheRead+=g.cacheReadTokens/1e6*h.cacheReadCentsPerMtok,c+=g.inputTokens+g.outputTokens+g.cacheCreateTokens+g.cacheReadTokens}let d=a.input+a.output+a.cacheCreate+a.cacheRead;return{cents:d,dollars:d/100,totalTokens:c,parts:a}}let s=je(t),n={input:e.inputTokens/1e6*s.inputCentsPerMtok,output:e.outputTokens/1e6*s.outputCentsPerMtok,cacheCreate:e.cacheCreateTokens/1e6*s.cacheCreateCentsPerMtok,cacheRead:e.cacheReadTokens/1e6*s.cacheReadCentsPerMtok},r=n.input+n.output+n.cacheCreate+n.cacheRead,o=e.inputTokens+e.outputTokens+e.cacheCreateTokens+e.cacheReadTokens;return{cents:r,dollars:r/100,totalTokens:o,parts:n}}function ht(e){let t=e/100;return t===0?"$0.00":t<.01?"<$0.01":t<1?`$${t.toFixed(2)}`:t<100?`$${t.toFixed(2)}`:t<1e4?`$${t.toFixed(0)}`:`$${(t/1e3).toFixed(1)}k`}function Et(e){return!Number.isFinite(e)||e<0?"0":e<1e3?String(Math.round(e)):e<1e6?`${(e/1e3).toFixed(1)}k`:e<1e9?`${(e/1e6).toFixed(2)}M`:e<1e12?`${(e/1e9).toFixed(2)}B`:`${(e/1e12).toFixed(2)}T`}function Kr(e){let t=new Map;for(let n of e){let r=n.model??null,o=t.get(r)??{inputTokens:0,outputTokens:0,cacheCreateTokens:0,cacheReadTokens:0,messageCount:0};o.inputTokens+=n.input_tokens,o.outputTokens+=n.output_tokens,o.cacheCreateTokens+=n.cache_create_tokens,o.cacheReadTokens+=n.cache_read_tokens,o.messageCount+=n.n,t.set(r,o)}let s=[];for(let[n,r]of t.entries()){let o=he({inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens},n);s.push({model:n,modelLabel:je(n).label,inputTokens:r.inputTokens,outputTokens:r.outputTokens,cacheCreateTokens:r.cacheCreateTokens,cacheReadTokens:r.cacheReadTokens,messageCount:r.messageCount,cost:o})}return s.sort((n,r)=>r.cost.cents-n.cost.cents)}function zr(e){let t={};for(let s of e)t[s.model??"__unknown__"]={inputTokens:s.inputTokens,outputTokens:s.outputTokens,cacheCreateTokens:s.cacheCreateTokens,cacheReadTokens:s.cacheReadTokens};return{byModel:t}}function kd(e){let t=f(),s=t.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
1439
+ s.message_count,
1440
+ s.total_input_tokens, s.total_output_tokens,
1441
+ s.total_cache_create_tokens, s.total_cache_read_tokens,
1442
+ s.primary_model
1443
+ FROM sessions s
1444
+ LEFT JOIN projects p ON p.id = s.project_id
1445
+ WHERE s.id = ?`).get(e);if(!s)return null;let n=t.prepare(`SELECT model,
1446
+ COALESCE(SUM(input_tokens), 0) AS input_tokens,
1447
+ COALESCE(SUM(output_tokens), 0) AS output_tokens,
1448
+ COALESCE(SUM(cache_create_tokens), 0) AS cache_create_tokens,
1449
+ COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
1450
+ COUNT(*) AS n
1451
+ FROM message_usage
1452
+ WHERE session_id = ?
1453
+ GROUP BY model`).all(e),r=Kr(n),o=s.total_input_tokens??0,a=s.total_output_tokens??0,c=s.total_cache_create_tokens??0,d=s.total_cache_read_tokens??0,u=he({inputTokens:o,outputTokens:a,cacheCreateTokens:c,cacheReadTokens:d,...zr(r)},s.primary_model);return{sessionId:s.id,project:s.project,startedAt:s.started_at,endedAt:s.ended_at,messageCount:s.message_count,primaryModel:s.primary_model,primaryModelLabel:je(s.primary_model).label,inputTokens:o,outputTokens:a,cacheCreateTokens:c,cacheReadTokens:d,totalTokens:u.totalTokens,cost:u,byModel:r,display:{dollars:ht(u.cents),tokens:Et(u.totalTokens),model:je(s.primary_model).label}}}function Ad(e){let t=f(),s=t.prepare("SELECT id, name FROM projects WHERE name = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT mu.model,
1454
+ COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
1455
+ COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
1456
+ COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
1457
+ COALESCE(SUM(mu.cache_read_tokens), 0) AS cache_read_tokens,
1458
+ COUNT(*) AS n
1459
+ FROM message_usage mu
1460
+ JOIN sessions s ON s.id = mu.session_id
1461
+ WHERE s.project_id = ?
1462
+ GROUP BY mu.model`).all(s.id),r=Kr(n),o=t.prepare(`SELECT COALESCE(SUM(total_input_tokens), 0) AS input_tokens,
1463
+ COALESCE(SUM(total_output_tokens), 0) AS output_tokens,
1464
+ COALESCE(SUM(total_cache_create_tokens), 0) AS cache_create_tokens,
1465
+ COALESCE(SUM(total_cache_read_tokens), 0) AS cache_read_tokens,
1466
+ COUNT(*) AS session_count
1467
+ FROM sessions WHERE project_id = ?`).get(s.id),a=he({inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,...zr(r)},null),d=t.prepare(`SELECT s.id, sa.alias, s.started_at,
1468
+ s.total_input_tokens, s.total_output_tokens,
1469
+ s.total_cache_create_tokens, s.total_cache_read_tokens,
1470
+ s.primary_model
1471
+ FROM sessions s
1472
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1473
+ WHERE s.project_id = ?
1474
+ ORDER BY (COALESCE(s.total_input_tokens,0)
1475
+ + COALESCE(s.total_output_tokens,0)
1476
+ + COALESCE(s.total_cache_create_tokens,0)
1477
+ + COALESCE(s.total_cache_read_tokens,0)) DESC
1478
+ LIMIT 10`).all(s.id).map(u=>{let g=he({inputTokens:u.total_input_tokens??0,outputTokens:u.total_output_tokens??0,cacheCreateTokens:u.total_cache_create_tokens??0,cacheReadTokens:u.total_cache_read_tokens??0},u.primary_model);return{sessionId:u.id,alias:u.alias,startedAt:u.started_at,totalTokens:g.totalTokens,cost:g}});return{project:s.name,sessionCount:o.session_count,inputTokens:o.input_tokens,outputTokens:o.output_tokens,cacheCreateTokens:o.cache_create_tokens,cacheReadTokens:o.cache_read_tokens,totalTokens:a.totalTokens,cost:a,byModel:r,topSessions:d,display:{dollars:ht(a.cents),tokens:Et(a.totalTokens)}}}function Nd(e="all"){let t=f(),s=e==="7d"?new Date(Date.now()-7*864e5).toISOString():e==="30d"?new Date(Date.now()-30*864e5).toISOString():null,n=s?"WHERE mu.timestamp >= @since OR (mu.timestamp IS NULL AND s.started_at >= @since)":"",r=s?"WHERE m.timestamp >= @since OR (m.timestamp IS NULL AND s2.started_at >= @since)":"",o=s?{since:s}:{},a=m=>s?t.prepare(m).get(o):t.prepare(m).get(),c=m=>s?t.prepare(m).all(o):t.prepare(m).all(),d=c(`SELECT mu.model,
1479
+ COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
1480
+ COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
1481
+ COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
1482
+ COALESCE(SUM(mu.cache_read_tokens), 0) AS cache_read_tokens,
1483
+ COUNT(*) AS n
1484
+ FROM message_usage mu
1485
+ JOIN sessions s ON s.id = mu.session_id
1486
+ ${n}
1487
+ GROUP BY mu.model`),u=Kr(d),g=0,h=0,b=0,S=0;for(let m of u)g+=m.inputTokens,h+=m.outputTokens,b+=m.cacheCreateTokens,S+=m.cacheReadTokens;let y=he({inputTokens:g,outputTokens:h,cacheCreateTokens:b,cacheReadTokens:S,...zr(u)},null),k=s?a(`SELECT
1488
+ (SELECT COUNT(DISTINCT m.session_id)
1489
+ FROM messages m
1490
+ JOIN sessions s2 ON s2.id = m.session_id
1491
+ ${r}) AS total_sessions,
1492
+ (SELECT COUNT(DISTINCT mu.session_id)
1493
+ FROM message_usage mu
1494
+ JOIN sessions s ON s.id = mu.session_id
1495
+ ${n}) AS sessions_with_usage`):t.prepare(`SELECT
1496
+ (SELECT COUNT(*) FROM sessions) AS total_sessions,
1497
+ (SELECT COUNT(*) FROM sessions
1498
+ WHERE (COALESCE(total_input_tokens,0)
1499
+ +COALESCE(total_output_tokens,0)
1500
+ +COALESCE(total_cache_create_tokens,0)
1501
+ +COALESCE(total_cache_read_tokens,0)) > 0) AS sessions_with_usage`).get(),w=c(`SELECT substr(datetime(COALESCE(mu.timestamp, s.started_at, ''), 'localtime'), 1, 10) AS day,
1502
+ mu.model,
1503
+ COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
1504
+ COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
1505
+ COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
1506
+ COALESCE(SUM(mu.cache_read_tokens), 0) AS cache_read_tokens
1507
+ FROM message_usage mu
1508
+ JOIN sessions s ON s.id = mu.session_id
1509
+ ${n}
1510
+ GROUP BY day, mu.model
1511
+ ORDER BY day ASC`),D=new Map;for(let m of w){if(!m.day)continue;let _=he({inputTokens:m.input_tokens,outputTokens:m.output_tokens,cacheCreateTokens:m.cache_create_tokens,cacheReadTokens:m.cache_read_tokens},m.model),E=D.get(m.day)??{tokens:0,cents:0};E.tokens+=_.totalTokens,E.cents+=_.cents,D.set(m.day,E)}let L=[...D.entries()].map(([m,_])=>({day:m,tokens:_.tokens,cents:_.cents})).sort((m,_)=>m.day.localeCompare(_.day)),M=c(`SELECT s.id, p.name AS project, sa.alias, s.started_at,
1512
+ COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
1513
+ COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
1514
+ COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
1515
+ COALESCE(SUM(mu.cache_read_tokens), 0) AS cache_read_tokens,
1516
+ s.primary_model
1517
+ FROM message_usage mu
1518
+ JOIN sessions s ON s.id = mu.session_id
1519
+ LEFT JOIN projects p ON p.id = s.project_id
1520
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1521
+ ${n}
1522
+ GROUP BY s.id
1523
+ ORDER BY (COALESCE(SUM(mu.input_tokens),0)
1524
+ + COALESCE(SUM(mu.output_tokens),0)
1525
+ + COALESCE(SUM(mu.cache_create_tokens),0)
1526
+ + COALESCE(SUM(mu.cache_read_tokens),0)) DESC
1527
+ LIMIT 10`).map(m=>{let _=he({inputTokens:m.input_tokens,outputTokens:m.output_tokens,cacheCreateTokens:m.cache_create_tokens,cacheReadTokens:m.cache_read_tokens},m.primary_model);return{sessionId:m.id,project:m.project,alias:m.alias,startedAt:m.started_at,totalTokens:_.totalTokens,cost:_}}),B=c(`SELECT p.id AS project_id,
1528
+ p.name AS project,
1529
+ mu.model,
1530
+ COALESCE(SUM(mu.input_tokens), 0) AS input_tokens,
1531
+ COALESCE(SUM(mu.output_tokens), 0) AS output_tokens,
1532
+ COALESCE(SUM(mu.cache_create_tokens), 0) AS cache_create_tokens,
1533
+ COALESCE(SUM(mu.cache_read_tokens), 0) AS cache_read_tokens,
1534
+ COUNT(DISTINCT s.id) AS sessions
1535
+ FROM message_usage mu
1536
+ JOIN sessions s ON s.id = mu.session_id
1537
+ LEFT JOIN projects p ON p.id = s.project_id
1538
+ ${n}
1539
+ GROUP BY p.id, mu.model`),se=new Map;for(let m of B){let _=m.project_id??"__none__",E=se.get(_);E||(E={project:m.project??"(no project)",sessionIds:new Set,sessionsApprox:0,byModel:{}},se.set(_,E)),m.sessions>E.sessionsApprox&&(E.sessionsApprox=m.sessions),E.byModel[m.model??"__unknown__"]={inputTokens:m.input_tokens,outputTokens:m.output_tokens,cacheCreateTokens:m.cache_create_tokens,cacheReadTokens:m.cache_read_tokens}}let i=[...se.values()].map(m=>{let _=0,E=0,T=0,R=0;for(let N of Object.values(m.byModel))_+=N.inputTokens,E+=N.outputTokens,T+=N.cacheCreateTokens,R+=N.cacheReadTokens;let A=he({inputTokens:_,outputTokens:E,cacheCreateTokens:T,cacheReadTokens:R,byModel:m.byModel},null);return{project:m.project,sessions:m.sessionsApprox,totalTokens:A.totalTokens,cost:A}});i.sort((m,_)=>_.totalTokens-m.totalTokens);let l=i.slice(0,20),p=t.prepare(`SELECT
1540
+ (SELECT COUNT(*) FROM messages WHERE role='assistant') AS assistant_messages,
1541
+ (SELECT COUNT(*) FROM message_usage) AS messages_with_usage`).get();return{range:e,totalSessions:k.total_sessions,sessionsWithUsage:k.sessions_with_usage,inputTokens:g,outputTokens:h,cacheCreateTokens:b,cacheReadTokens:S,totalTokens:y.totalTokens,cost:y,daily:L,byModel:u,topSessions:M,topRepos:l,backfill:{assistantMessages:p.assistant_messages,messagesWithUsage:p.messages_with_usage,pending:Math.max(0,p.assistant_messages-p.messages_with_usage),unrecoverable:Math.min(bd(),Math.max(0,p.assistant_messages-p.messages_with_usage))},display:{dollars:ht(y.cents),tokens:Et(y.totalTokens)}}}H();function Zt(e){return Math.max(0,Math.min(1,e))}function Vr(e){let t=f(),s=t.prepare("SELECT id, name FROM projects WHERE id = ?").get(e);if(!s)return null;let n=t.prepare(`SELECT COUNT(*) AS cnt,
1542
+ MAX(started_at) AS latest
1543
+ FROM sessions WHERE project_id = ?`).get(e),r=n.cnt;if(r===0)return{projectId:e,projectName:s.name,score:0,breakdown:{sessionCount:{raw:0,score:0,weight:.2},recency:{daysSinceLastSession:1/0,score:0,weight:.25},fragmentation:{avgMessages:0,score:0,weight:.15},searchCoverage:{ratio:0,score:0,weight:.2},tagCoverage:{ratio:0,score:0,weight:.2}}};let o=Zt(r/10),a=n.latest?(Date.now()-new Date(n.latest).getTime())/(1e3*60*60*24):90,c=Zt(1-a/90),u=t.prepare(`SELECT AVG(message_count) AS avg_msgs
1544
+ FROM sessions WHERE project_id = ?`).get(e).avg_msgs??0,g=Zt((u-2)/3),h=t.prepare(`SELECT COUNT(*) AS total,
1545
+ SUM(CASE WHEN m.content_text IS NOT NULL AND m.content_text != '' THEN 1 ELSE 0 END) AS covered
1546
+ FROM messages m
1547
+ JOIN sessions s ON s.id = m.session_id
1548
+ WHERE s.project_id = ?`).get(e),b=h.total>0?h.covered/h.total:.5,S=Zt(b),y=t.prepare(`SELECT COUNT(DISTINCT s.id) AS total,
1549
+ COUNT(DISTINCT st.session_id) AS tagged
1550
+ FROM sessions s
1551
+ LEFT JOIN session_tags st ON st.session_id = s.id
1552
+ WHERE s.project_id = ?`).get(e),k=y.total>0?y.tagged/y.total:0,w=Zt(k),D=Math.round((o*.2+c*.25+g*.15+S*.2+w*.2)*100);return{projectId:e,projectName:s.name,score:D,breakdown:{sessionCount:{raw:r,score:o,weight:.2},recency:{daysSinceLastSession:Math.round(a),score:c,weight:.25},fragmentation:{avgMessages:Math.round(u*10)/10,score:g,weight:.15},searchCoverage:{ratio:Math.round(b*100)/100,score:S,weight:.2},tagCoverage:{ratio:Math.round(k*100)/100,score:w,weight:.2}}}}function xd(){let t=f().prepare("SELECT id FROM projects ORDER BY name").all(),s=[];for(let n of t){let r=Vr(n.id);r&&s.push(r)}return s}H();import{execFile as VE}from"node:child_process";import{promisify as ZE}from"node:util";import{stat as QE}from"node:fs/promises";var Od=ZE(VE),Ld=1e4,eb="%H%x09%aI%x09%s";async function tb(e){try{let{stdout:t}=await Od("git",["rev-parse","--is-inside-work-tree"],{cwd:e,timeout:Ld});return t.trim()==="true"}catch{return!1}}async function sb(e,t,s){let n=["--no-pager","log","--all","--no-color","--since",t,"--until",s,`--pretty=format:${eb}`],{stdout:r}=await Od("git",n,{cwd:e,timeout:Ld,maxBuffer:8*1024*1024}),o=[],a=new Set;for(let c of r.split(`
1553
+ `)){if(!c)continue;let[d,u,...g]=c.split(" ");!d||a.has(d)||(a.add(d),o.push({commit_sha:d,committed_at:u??null,subject:g.join(" ")||null}))}return o}function nb(e){return f().prepare(`SELECT id, cwd, started_at, ended_at
1554
+ FROM sessions WHERE id = ?`).get(e)??null}function rb(e,t,s){if(s.length===0)return 0;let n=f(),r=new Date().toISOString(),o=n.prepare(`INSERT OR IGNORE INTO session_commits
1555
+ (session_id, commit_sha, committed_at, subject, cwd_snapshot, correlated_at)
1556
+ VALUES (?, ?, ?, ?, ?, ?)`),a=0;return n.transaction(d=>{for(let u of d)o.run(e,u.commit_sha,u.committed_at,u.subject,t,r).changes>0&&(a+=1)})(s),a}async function Zr(e){let t=nb(e);if(!t)return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:"session not found"};if(!t.cwd)return{sessionId:e,status:"no-cwd",commitsFound:0,commitsInserted:0};if(!t.started_at||!t.ended_at)return{sessionId:e,status:"no-window",commitsFound:0,commitsInserted:0};let s=t.started_at,n=t.ended_at===t.started_at?new Date(Date.parse(t.ended_at)+1e3).toISOString():t.ended_at;try{if(!(await QE(t.cwd)).isDirectory())return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}catch{return{sessionId:e,status:"cwd-missing",commitsFound:0,commitsInserted:0}}if(!await tb(t.cwd))return{sessionId:e,status:"not-a-repo",commitsFound:0,commitsInserted:0};try{let o=await sb(t.cwd,s,n),a=rb(e,t.cwd,o);return{sessionId:e,status:"ok",commitsFound:o.length,commitsInserted:a}}catch(o){return{sessionId:e,status:"error",commitsFound:0,commitsInserted:0,error:o.message}}}function mn(e){let t=f(),s=e.trim();if(!/^[0-9a-fA-F]{4,40}$/.test(s))return[];let n=`${s.toLowerCase()}%`;return t.prepare(`SELECT sc.session_id AS sessionId,
1557
+ NULLIF(sa.alias, '') AS alias,
1558
+ p.name AS project,
1559
+ s.started_at AS startedAt,
1560
+ s.ended_at AS endedAt,
1561
+ sc.commit_sha AS commitSha,
1562
+ sc.committed_at AS committedAt,
1563
+ sc.subject AS subject
1564
+ FROM session_commits sc
1565
+ JOIN sessions s ON s.id = sc.session_id
1566
+ JOIN projects p ON p.id = s.project_id
1567
+ LEFT JOIN session_aliases sa ON sa.session_id = sc.session_id
1568
+ WHERE lower(sc.commit_sha) = lower(?)
1569
+ OR lower(sc.commit_sha) LIKE ?
1570
+ ORDER BY COALESCE(sc.committed_at, s.started_at, '') DESC`).all(s,n)}function Qr(e){return f().prepare(`SELECT commit_sha, committed_at, subject, correlated_at
1571
+ FROM session_commits
1572
+ WHERE session_id = ?
1573
+ ORDER BY COALESCE(committed_at, correlated_at) ASC`).all(e)}var ob=3e4;function Cd(e){try{let s=f().prepare(`SELECT MAX(correlated_at) AS last_at
1574
+ FROM session_commits WHERE session_id = ?`).get(e),n=s?.last_at?Date.parse(s.last_at):0;if(n&&Date.now()-n<ob)return}catch{}Zr(e).catch(t=>{console.error(`[git-correlator] ${e.slice(0,8)} failed:`,t)})}H();import{execFile as ib}from"node:child_process";import{promisify as ab}from"node:util";import{stat as cb}from"node:fs/promises";var lb=ab(ib),db=60,ub=7,pb=7,mb=5e3;function gb(){let e=f(),t=s=>{try{return!!e.prepare(s).get()}catch{return!1}};return{semantic:t("SELECT 1 FROM session_semantic LIMIT 1"),cost:t(`SELECT 1 FROM sessions
1575
+ WHERE (COALESCE(total_input_tokens,0)
1576
+ + COALESCE(total_output_tokens,0)
1577
+ + COALESCE(total_cache_create_tokens,0)
1578
+ + COALESCE(total_cache_read_tokens,0)) > 0
1579
+ LIMIT 1`),git:t("SELECT 1 FROM session_commits LIMIT 1")}}function eo(e){if(!e)return[];let t=new Set,s=[];for(let n of e.split(",")){let r=n.trim().toLowerCase();!r||t.has(r)||(t.add(r),s.push(r))}return s}function Id(e){return{sessionId:e.session_id,project:e.project,alias:e.alias,startedAt:e.started_at,endedAt:e.ended_at,firstUserMessage:e.first_user_message}}function _b(){let e=f(),t=e.prepare(`SELECT ss.keywords
1580
+ FROM session_semantic ss
1581
+ JOIN sessions s ON s.id = ss.session_id
1582
+ WHERE s.started_at IS NOT NULL
1583
+ AND julianday('now') - julianday(s.started_at) <= @windowDays`).all({windowDays:ub});if(t.length===0)return null;let s=new Set;for(let o of t)for(let a of eo(o.keywords))s.add(a);if(s.size===0)return null;let n=e.prepare(`SELECT ss.session_id AS session_id,
1584
+ ss.summary AS summary,
1585
+ ss.keywords AS keywords,
1586
+ p.name AS project,
1587
+ NULLIF(sa.alias, '') AS alias,
1588
+ s.started_at AS started_at,
1589
+ s.ended_at AS ended_at,
1590
+ s.first_user_message AS first_user_message,
1591
+ julianday('now') - julianday(s.started_at) AS days_old
1592
+ FROM session_semantic ss
1593
+ JOIN sessions s ON s.id = ss.session_id
1594
+ JOIN projects p ON p.id = s.project_id
1595
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1596
+ WHERE s.started_at IS NOT NULL
1597
+ AND s.message_count > 2
1598
+ AND julianday('now') - julianday(s.started_at) >= @ageDays
1599
+ ORDER BY s.started_at ASC`).all({ageDays:db});if(n.length===0)return null;let r=null;for(let o of n){let c=eo(o.keywords).filter(d=>s.has(d));c.length!==0&&(!r||c.length>r.overlap.length)&&(r={row:o,overlap:c})}return r?{...Id(r.row),summary:r.row.summary,keywords:eo(r.row.keywords),matchedKeywords:r.overlap,daysAgo:Math.max(0,Math.round(r.row.days_old))}:null}function fb(){let t=f().prepare(`SELECT s.id AS session_id,
1600
+ p.name AS project,
1601
+ NULLIF(sa.alias, '') AS alias,
1602
+ s.started_at AS started_at,
1603
+ s.ended_at AS ended_at,
1604
+ s.first_user_message AS first_user_message,
1605
+ s.primary_model AS primary_model,
1606
+ COALESCE(s.total_input_tokens, 0) AS input_tokens,
1607
+ COALESCE(s.total_output_tokens, 0) AS output_tokens,
1608
+ COALESCE(s.total_cache_create_tokens, 0) AS cache_create_tokens,
1609
+ COALESCE(s.total_cache_read_tokens, 0) AS cache_read_tokens
1610
+ FROM sessions s
1611
+ JOIN projects p ON p.id = s.project_id
1612
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1613
+ WHERE s.started_at IS NOT NULL
1614
+ AND julianday('now') - julianday(s.started_at) <= @windowDays
1615
+ AND (COALESCE(s.total_input_tokens, 0)
1616
+ + COALESCE(s.total_output_tokens, 0)
1617
+ + COALESCE(s.total_cache_create_tokens, 0)
1618
+ + COALESCE(s.total_cache_read_tokens, 0)) > 0`).all({windowDays:pb});if(t.length===0)return null;let s=null;for(let n of t){let r=he({inputTokens:n.input_tokens,outputTokens:n.output_tokens,cacheCreateTokens:n.cache_create_tokens,cacheReadTokens:n.cache_read_tokens},n.primary_model);r.cents<=0||(!s||r.cents>s.cents)&&(s={row:n,cents:r.cents,totalTokens:r.totalTokens})}return s?{...Id(s.row),totalTokens:s.totalTokens,costCents:s.cents,costDisplay:ht(s.cents),tokensDisplay:Et(s.totalTokens),primaryModel:s.row.primary_model,primaryModelLabel:je(s.row.primary_model).label}:null}async function hb(e){try{if(!(await cb(e)).isDirectory())return null}catch{return null}try{let{stdout:t}=await lb("git",["rev-parse","HEAD"],{cwd:e,timeout:mb}),s=t.trim();return/^[0-9a-f]{40}$/.test(s)?s:null}catch{return null}}async function Eb(){let e=f(),t=e.prepare(`SELECT s.id AS id, s.cwd AS cwd
1619
+ FROM sessions s
1620
+ WHERE s.cwd IS NOT NULL AND s.started_at IS NOT NULL
1621
+ ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
1622
+ LIMIT 1`).get();if(!t?.cwd)return null;let s=await hb(t.cwd);if(!s)return null;let n=mn(s);if(n.length===0)return null;let r=n[0],o=e.prepare(`SELECT s.first_user_message AS first_user_message, s.ended_at AS ended_at
1623
+ FROM sessions s
1624
+ WHERE s.id = ?`).get(r.sessionId);return{sessionId:r.sessionId,project:r.project,alias:r.alias,startedAt:r.startedAt,endedAt:o?.ended_at??r.endedAt,firstUserMessage:o?.first_user_message??null,commitSha:r.commitSha,shortSha:r.commitSha.slice(0,7),subject:r.subject,committedAt:r.committedAt,cwd:t.cwd}}async function vd(){let e=gb(),t=e.semantic?Promise.resolve().then(()=>{try{return _b()}catch(c){return console.error("[discover.rediscovered]",c),null}}):Promise.resolve(null),s=e.cost?Promise.resolve().then(()=>{try{return fb()}catch(c){return console.error("[discover.expensive]",c),null}}):Promise.resolve(null),n=e.git?Eb().catch(c=>(console.error("[discover.authored]",c),null)):Promise.resolve(null),[r,o,a]=await Promise.all([t,s,n]);return{rediscovered:r,expensive:o,authored:a,availability:e,generatedAt:new Date().toISOString()}}ot();H();ot();async function jd(e,t=50){let s=await hr(e),n=f(),r=Buffer.from(s.buffer,s.byteOffset,s.byteLength);return n.prepare(`SELECT v.rowid, v.distance, cm.session_id, cm.text, cm.message_uuids
1625
+ FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
1626
+ WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(r,t).map(a=>({sessionId:a.session_id,chunkRowid:a.rowid,distance:a.distance,text:a.text,messageUuids:JSON.parse(a.message_uuids)}))}async function Md(e,t=10,s=.65){let n=f(),r=n.prepare("SELECT rowid FROM chunk_meta WHERE session_id = ? ORDER BY rowid LIMIT 1").get(e);if(!r)return[];let o=n.prepare("SELECT embedding FROM vec_chunks WHERE rowid = ?").get(r.rowid);if(!o)return[];let a=n.prepare(`SELECT v.rowid, v.distance, cm.session_id FROM vec_chunks v JOIN chunk_meta cm ON cm.rowid = v.rowid
1627
+ WHERE v.embedding MATCH ? AND k = ? ORDER BY v.distance`).all(o.embedding,t*5),c=new Map;for(let u of a){if(u.session_id===e)continue;let g=c.get(u.session_id);(g===void 0||u.distance<g)&&c.set(u.session_id,u.distance)}let d=[];for(let[u,g]of c){let h=1-g;h>=s&&d.push({sessionId:u,similarity:h})}return d.sort((u,g)=>g.similarity-u.similarity),d.slice(0,t)}function bb(){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 Dd(e){let t=bb(),s=new Map;for(let r of e)for(let o=0;o<r.length;o++){let a=r[o],c=1/(t+o+1),d=s.get(a.id);d?(d.score+=c,d.lanes.push(a.lane),o+1<d.bestRank&&(d.bestRank=o+1,d.bestData=a.data)):s.set(a.id,{score:c,lanes:[a.lane],bestRank:o+1,bestData:a.data})}let n=[];for(let[r,o]of s)n.push({id:r,score:o.score,lanes:o.lanes,data:o.bestData});return n.sort((r,o)=>o.score-r.score),n}H();ot();H();function Fd(e){return e.replace(/```json[\s\S]*?```/g,"[tool-call]").replace(/\{[\s\S]{200,}?\}/g,"[json-object]")}function Sb(e){let t=[],o=0;for(;o<e.length;){let a=[],c=0;for(;a.length<5&&o<e.length;){let u=e[o],g=Fd(u.content_text??"");if(c+g.length>2e3&&a.length>=3)break;a.push(u),c+=g.length,o++}if(a.length===0)break;let d=a.map(u=>{let g=u.role??"system",h=Fd(u.content_text??"");return`[${g}] ${h}`}).join(`
1628
+
1629
+ `);t.push({messageUuids:a.map(u=>u.uuid),text:d}),o<e.length&&a.length>=3&&(o=Math.max(o-1,o-1))}return t}function Pd(e){let s=f().prepare("SELECT uuid, role, content_text FROM messages WHERE session_id = ? AND is_sidechain = 0 AND content_text IS NOT NULL ORDER BY rowid").all(e);return Sb(s)}var Ud=2e3,Tb=1e4,gn=null,to=!1,$d=null;function yb(){return f().prepare("SELECT COUNT(*) AS n FROM chunk_queue").get().n}async function wb(){let e=f(),t=e.prepare("SELECT DISTINCT session_id FROM chunk_queue ORDER BY id LIMIT 1").get();if(!t)return!1;let s=t.session_id;try{e.prepare("DELETE FROM vec_chunks WHERE rowid IN (SELECT rowid FROM chunk_meta WHERE session_id = ?)").run(s),e.prepare("DELETE FROM chunk_meta WHERE session_id = ?").run(s);let n=Pd(s);if(n.length===0)return e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(s),!0;let r=n.map(u=>u.text),o=await $t(r),a=e.prepare("INSERT INTO chunk_meta(session_id, message_uuids, text, embedding_model_id, embedding_dim, stale, generated_at) VALUES (?, ?, ?, 'bge-base-en-v1.5', 768, 0, datetime('now'))"),c=e.prepare("INSERT INTO vec_chunks(rowid, embedding) VALUES (?, ?)");e.transaction(()=>{for(let u=0;u<n.length;u++){let g=a.run(s,JSON.stringify(n[u].messageUuids),n[u].text),h=Buffer.from(o[u].buffer,o[u].byteOffset,o[u].byteLength);c.run(g.lastInsertRowid,h)}e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(s)})(),$d=new Date().toISOString()}catch(n){console.error("[vector-worker] failed for session",s,n),e.prepare("DELETE FROM chunk_queue WHERE session_id = ?").run(s)}return!0}async function Bd(){if(!ke().loaded)return;let e=await wb();gn!==null&&clearTimeout(gn),gn=setTimeout(()=>{Bd()},e?Ud:Tb)}function Hd(){if(!to){if(!ke().loaded){console.error("[vector-worker] cannot start: embedder not loaded");return}to=!0,gn=setTimeout(()=>{Bd()},Ud)}}function Wd(){return{running:to,queueDepth:yb(),lastProcessedAt:$d}}Z();import{existsSync as Xd,mkdirSync as qd,rmSync as Jd,createWriteStream as Rb,statSync as kb}from"node:fs";import{join as _n}from"node:path";import{createHash as Ab}from"node:crypto";import{readFile as Nb}from"node:fs/promises";var xb="https://huggingface.co/BAAI/bge-base-en-v1.5/resolve/main/",Gd=[{path:"config.json",sha256:"bc00af31a4a31b74040d73370aa83b62da34c90b75eb77bfa7db039d90abd591"},{path:"tokenizer.json",sha256:"d241a60d5e8f04cc1b2b3e9ef7a4921b27bf526d9f6050ab90f9267a1f9e5c66"},{path:"tokenizer_config.json",sha256:"9261e7d79b44c8195c1cada2b453e55b00aeb81e907a6664974b4d7776172ab3"},{path:"onnx/model.onnx",sha256:"9bc579acdba21c253c62a9bf866891355a63ffa3442b52c8a37d75b2ccb91848"}];function Yd(){return _n($,"models","BAAI","bge-base-en-v1.5")}function so(){let e=Yd();return Gd.every(t=>Xd(_n(e,t.path)))}async function Kd(e){let t=Yd();qd(t,{recursive:!0}),qd(_n(t,"onnx"),{recursive:!0});for(let s of Gd){let n=_n(t,s.path),r=xb+s.path,o=0;Xd(n)&&(o=kb(n).size);let a={};o>0&&(a.Range=`bytes=${o}-`);let c=await fetch(r,{headers:a});if(!c.ok&&c.status!==206)throw new Error(`Failed to download ${s.path}: HTTP ${c.status}`);let d=c.headers.get("content-length"),u=d?o+Number(d):0,g=c.body;if(!g)throw new Error(`No response body for ${s.path}`);let h=Rb(n,{flags:o>0?"a":"w"}),b=g.getReader(),S=o;for(;;){let{done:w,value:D}=await b.read();if(w)break;h.write(Buffer.from(D)),S+=D.byteLength,e?.(s.path,S,u)}if(h.end(),await new Promise((w,D)=>{h.on("finish",w),h.on("error",D)}),s.sha256==="TODO_PLACEHOLDER")throw Jd(n),new Error(`Refusing to install: SHA-256 not pinned for ${s.path}. Update model-download.ts.`);let y=await Nb(n);if(Ab("sha256").update(y).digest("hex")!==s.sha256)throw Jd(n),new Error(`SHA-256 mismatch for ${s.path}`)}}H();var Ob=[/\btask\s+complete/i,/\bdone\b/i,/\bfinished\b/i,/\bimplemented\b/i,/\bcompleted\b/i,/\bshipped\b/i,/\ball\s+(?:tests?\s+)?pass/i,/\bsuccessfully\b/i],Lb=1440*60*1e3;function Cb(e){let t=f(),s=t.prepare(`SELECT content_text, tool_names FROM messages
1630
+ WHERE session_id = ? AND role = 'assistant'
1631
+ ORDER BY timestamp DESC LIMIT 5`).all(e),n=!1;for(let d of s)if(d.content_text&&Ob.some(u=>u.test(d.content_text))){n=!0;break}let r=t.prepare(`SELECT content_text, tool_names FROM messages
1632
+ WHERE session_id = ?`).all(e),o={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};for(let d of r){let u=d.tool_names??"",g=d.content_text??"";/\bWrite\b|\bEdit\b/.test(u)&&(o.fileWrites=!0),/\b(?:jest|pytest|vitest|mocha|test|spec)\b/i.test(g)&&/pass|ok|✓/i.test(g)&&(o.testRuns=!0),/\bgit\s+commit\b/i.test(g)&&(o.commits=!0),(/\bbuild\s+(?:succeeded|success|passed)\b/i.test(g)||/tsc.*(?:0 errors|no errors)/i.test(g))&&(o.buildSuccess=!0)}return n?{status:[o.fileWrites,o.testRuns,o.commits,o.buildSuccess].filter(Boolean).length>=2?"verified":"unverified",evidence:o,claimFound:n}:{status:"neutral",evidence:o,claimFound:n}}function Ib(e){let t=Cb(e);return f().prepare("UPDATE sessions SET verification_status = ?, verification_computed_at = ? WHERE id = ?").run(t.status,Date.now(),e),t}function zd(e){let s=f().prepare("SELECT verification_status, verification_computed_at FROM sessions WHERE id = ?").get(e);if(s?.verification_status&&s.verification_computed_at&&Date.now()-s.verification_computed_at<Lb){let n={fileWrites:!1,testRuns:!1,commits:!1,buildSuccess:!1};return{status:s.verification_status,evidence:n,claimFound:s.verification_status!=="neutral"}}return Ib(e)}import{readFileSync as vb,writeFileSync as jb,mkdirSync as Mb,chmodSync as Db}from"node:fs";import{join as Vd}from"node:path";import{homedir as Zd}from"node:os";function Qd(){return Vd(Zd(),".recall","config.json")}function eu(){try{return JSON.parse(vb(Qd(),"utf-8"))}catch{return{}}}function Fb(e){let t=Qd();Mb(Vd(Zd(),".recall"),{recursive:!0}),jb(t,JSON.stringify(e,null,2)+`
1633
+ `,"utf-8"),Db(t,384)}function no(){let t=eu().verification;return typeof t=="object"&&t!==null&&"enabled"in t?!!t.enabled:!1}function tu(e){let t=eu();t.verification={...typeof t.verification=="object"&&t.verification!==null?t.verification:{},enabled:e},Fb(t)}var Pb=[{name:"Anthropic API key",regex:/sk-ant-[a-zA-Z0-9_\-]{40,}/g,severity:"high"},{name:"OpenAI API key",regex:/sk-(?:proj-)?[a-zA-Z0-9]{32,}/g,severity:"high"},{name:"AWS access key ID",regex:/AKIA[0-9A-Z]{16}/g,severity:"high"},{name:"GitHub PAT",regex:/gh[pousr]_[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Stripe live/test key",regex:/(?:sk|rk|pk)_(?:live|test)_[a-zA-Z0-9]{24,}/g,severity:"high"},{name:"Slack token",regex:/xox[abprs]-[A-Za-z0-9\-]{10,}/g,severity:"high"},{name:"Google API key",regex:/AIza[0-9A-Za-z_\-]{35}/g,severity:"high"},{name:"Private key block",regex:/-----BEGIN (?:RSA |DSA |EC |OPENSSH |ENCRYPTED )?PRIVATE KEY-----/g,severity:"high"},{name:"Apify token",regex:/apify_api_[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Notion integration",regex:/(?:secret_|ntn_)[A-Za-z0-9]{40,}/g,severity:"high"},{name:"Vercel token",regex:/vercel_[A-Za-z0-9]{24,}/g,severity:"high"},{name:"Supabase service key",regex:/sbp_[A-Za-z0-9]{40,}/g,severity:"high"},{name:"SendGrid key",regex:/SG\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}/g,severity:"high"},{name:"Mailgun key",regex:/key-[a-f0-9]{32}/g,severity:"high"},{name:"Twilio SID",regex:/AC[a-f0-9]{32}/g,severity:"high"},{name:"Discord bot token",regex:/[MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27,38}/g,severity:"high"},{name:"npm token",regex:/npm_[A-Za-z0-9]{36}/g,severity:"high"},{name:"HuggingFace token",regex:/hf_[A-Za-z0-9]{30,}/g,severity:"high"},{name:"Replicate token",regex:/r8_[A-Za-z0-9]{32,}/g,severity:"high"},{name:"Figma token",regex:/figd_[A-Za-z0-9_\-]{30,}/g,severity:"high"},{name:"Linear key",regex:/lin_api_[A-Za-z0-9]{30,}/g,severity:"high"},{name:"DigitalOcean token",regex:/dop_v1_[a-f0-9]{64}/g,severity:"high"},{name:"Generic provider token",regex:/\b[a-z][a-z0-9]{2,20}_(?:api|pat|token|sk|pk|key|auth)_(?=[A-Za-z0-9_\-]*\d)[A-Za-z0-9_\-]{20,}\b/g,severity:"high"},{name:"Bearer token",regex:/\b[Bb]earer\s+[A-Za-z0-9_\-\.=]{24,}\b/g,severity:"medium"},{name:"Slack webhook URL",regex:/https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]{20,}/g,severity:"high"},{name:"Discord webhook URL",regex:/https:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[A-Za-z0-9_\-]{40,}/g,severity:"high"},{name:"Teams webhook URL",regex:/https:\/\/[a-zA-Z0-9.\-]+\.webhook\.office\.com\/webhookb2\/[A-Za-z0-9@_\-\/]{30,}/g,severity:"high"},{name:"Secret near keyword",regex:/\b(?:webhook[_\s\-]?secret|signing[_\s\-]?secret|webhook[_\s\-]?signing[_\s\-]?secret|api[_\s\-]?secret|client[_\s\-]?secret|private[_\s\-]?key|access[_\s\-]?token|auth[_\s\-]?token|api[_\s\-]?key)\b[\s\S]{0,200}?\b(?:[a-fA-F0-9]{32,}|[A-Za-z0-9+/_\-]{20,}(?:\.[A-Za-z0-9+/_\-]{10,}){1,2}|[A-Za-z0-9+/_\-]{40,}={0,2})\b/gi,severity:"high"},{name:"JWT",regex:/eyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}/g,severity:"medium"},{name:"URL with password",regex:/https?:\/\/[^:\s/@]+:[^@\s]{6,}@[^\s/]+/g,severity:"high"},{name:"Password assignment",regex:/(?<![A-Za-z])(?:password|passwd|pwd|secret|token|api[_\-]?key|access[_\-]?key|auth[_\-]?token|webhook[_\-]?secret|client[_\-]?secret|private[_\-]?key)\b\s*[:=]\s*["']?[A-Za-z0-9_\-+/=]{16,}/gi,severity:"high"}];function Ub(e){if(e.length<=8)return e.slice(0,2)+"\u2022".repeat(Math.max(0,e.length-4))+e.slice(-2);let t=e.slice(0,Math.min(6,Math.floor(e.length/3))),s=e.slice(-Math.min(4,Math.floor(e.length/4)));return`${t}${"\u2022".repeat(Math.max(3,e.length-t.length-s.length))}${s}`}function $b(e){let t=5381;for(let s=0;s<e.length;s++)t=(t<<5)+t+e.charCodeAt(s)|0;return(t>>>0).toString(36)}function qe(e){if(!e)return{redacted:e,count:0};let t=e,s=0,n=new Set;for(let r of Pb)r.regex.lastIndex=0,t=t.replace(r.regex,o=>{let a=`${r.name}::${$b(o)}`;return n.has(a)||(n.add(a),s+=1),`[REDACTED ${r.name}: ${Ub(o)}]`});return{redacted:t,count:s}}var Kb=5e3,po={scanned:0,linked:0,renamed:0,skipped_manual:0,ambiguous_cwd:0},oo=0,io=po,ao=null;async function zb(){return Date.now()-oo>=Kb&&!ao&&(ao=Ds().then(s=>(io=s,oo=Date.now(),s)).catch(()=>(oo=Date.now(),io=po,po)).finally(()=>{ao=null})),io}var Vb=2e3,Zb=6,hn=new Map;function Qt(e){return e.replace(/[\\%_]/g,t=>"\\"+t)}function Qb(e,t){let s=Date.now(),n=(hn.get(e)??[]).filter(a=>s-a.ts<Vb);return hn.set(e,n),n.length<2||n[n.length-1].name===t?!1:n.slice(0,-1).some(a=>a.name===t)}function eS(e,t){let s=hn.get(e)??[];for(s.push({name:t,ts:Date.now()});s.length>Zb;)s.shift();hn.set(e,s)}function nu(e,t){let s=t.trim();if(!s)return 0;if(ne(s)){let o=tt(s);if(!o)return 0;s=o}if(re(s))return 0;if(Qb(e,s))return console.log(`[terminal] dropping rename of pid ${e} \u2192 "${s}", flap signature (competing editor sync sources)`),0;eS(e,s);let n=I.sessionsFor(e),r=0;for(let o of n)try{if(Se(o)===s)continue;me(o,s),r++}catch{}return r>0&&console.log(`[terminal] rename of pid ${e} \u2192 "${s}" propagated to ${r} session(s)`),r}var ru="0.53.2",co=!1,lo=!1,uo=!1,sS=au(cu(import.meta.url)),mo=_o(sS,"..","web"),go=_o(mo,"index.html"),nS=Wb(go);function ou(){return f().prepare(`SELECT
1634
+ (SELECT COUNT(*) FROM projects) AS projects,
1635
+ (SELECT COUNT(*) FROM sessions) AS sessions,
1636
+ (SELECT COUNT(*) FROM messages) AS messages,
1637
+ (SELECT MIN(started_at) FROM sessions WHERE started_at IS NOT NULL) AS earliest,
1638
+ (SELECT MAX(started_at) FROM sessions WHERE started_at IS NOT NULL) AS latest`).get()}var rS=/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i,oS=/^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/i;async function es(e,t){if((await Tt()).tier!=="pro")return e.json({error:"pro_required",message:"This feature requires a Claude Recall Pro license.",upgrade_url:"https://clauderecall.com/pricing",activate_command:"recall activate <license-key>"},402);await t()}var fn=new Map,lu=0,iu=0,du=null,iS=6e4;function aS(){lu+=1;let e=Date.now();e-iu<iS||(iu=e,console.warn("[auth] /api/terminal/* request rejected without a valid X-Recall-Token. The VS Code / Cursor extension is likely outdated relative to the daemon \u2014 tab names will not flow through, and session titles will fall back to the heuristic first-message snippet. Reinstall: `code --install-extension extensions/vscode/clauderecall-vscode-*.vsix` and restart the extension host. Run `recall doctor` for a full pipeline view."))}function cS(){du=new Date().toISOString()}function lS(){return{silentTerminalRejections:lu,lastTerminalSyncAt:du}}function dS(e){let t=new Bb;if(t.use("*",tS({maxSize:1*1024*1024})),t.use("*",async(i,l)=>{let p=i.req.raw.headers.get("host")??"";if(!rS.test(p))return i.text("Forbidden: invalid Host header",403);let m=i.req.raw.headers.get("origin");if(m&&!oS.test(m))return i.text("Forbidden: cross-origin request rejected",403);await l()}),e){let i=Buffer.from(e,"utf8");t.use("/api/*",async(l,p)=>{if(l.req.method==="GET"&&l.req.path==="/api/health")return p();let m=l.req.raw.headers.get("x-recall-token")??"",_=!1;if(m.length===e.length)try{_=Gb(Buffer.from(m,"utf8"),i)}catch{_=!1}return _?p():(l.req.path.startsWith("/api/terminal/")&&aS(),l.json({error:"unauthorized"},401))})}t.use("*",async(i,l)=>{await l(),i.res.headers.set("Content-Security-Policy","default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://clauderecall.com; font-src 'self' data:; connect-src 'self' data: https://clauderecall.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"),i.res.headers.set("X-Content-Type-Options","nosniff"),i.res.headers.set("X-Frame-Options","DENY"),i.res.headers.set("Referrer-Policy","no-referrer"),i.res.headers.set("Cross-Origin-Resource-Policy","same-origin")}),t.get("/api/health",i=>i.json({status:"ok",version:ru,uptimeSeconds:Math.round(process.uptime()),pipeline:lS()})),t.get("/api/stats",i=>i.json(ou())),t.get("/api/stats/session/:id",i=>{let l=kd(i.req.param("id"));return l?i.json(l):i.json({error:"session not found"},404)}),t.get("/api/stats/project/:name",i=>{let l=Ad(i.req.param("name"));return l?i.json(l):i.json({error:"project not found"},404)}),t.get("/api/stats/overview",i=>{let l=i.req.query("range"),p=l==="7d"?"7d":l==="30d"?"30d":"all";return i.json(Nd(p))}),t.post("/api/stats/backfill",async i=>{let l=await i.req.json().catch(()=>({})),p=l.limit?Math.max(1,Math.min(1e5,Number(l.limit))):5e3;if(p>5e3){let _=yd({limit:p});return i.json({mode:"background",started:_,alreadyRunning:!_&&wd(),limit:p,lastRun:Yr()})}let m=Td({limit:p});return i.json({mode:"sync",started:!1,alreadyRunning:!1,limit:p,result:m,lastRun:Yr()})}),t.get("/api/stats/health",i=>i.json(xd())),t.get("/api/stats/health/:projectId",i=>{let l=Number(i.req.param("projectId")),p=Vr(l);return p?i.json(p):i.json({error:"project not found"},404)}),t.get("/api/config/verification",i=>i.json({enabled:no()})),t.put("/api/config/verification",async i=>{let l=await i.req.json();return typeof l.enabled=="boolean"&&tu(l.enabled),i.json({enabled:no()})}),t.get("/api/sessions/:id/verification",i=>{let l=i.req.param("id"),p=zd(l);return i.json(p)}),t.get("/api/sessions/:id/share-stats",i=>{let l=i.req.param("id"),m=f().prepare(`SELECT tool_names, raw_json FROM messages
1639
+ WHERE session_id = ? AND tool_names IS NOT NULL AND tool_names != ''`).all(l),_=m.length,E=new Set;for(let T of m){if(!/Read|Write|Edit/.test(T.tool_names))continue;let R=T.raw_json.match(/"(?:file_path|path)":\s*"([^"]+)"/g);if(R)for(let A of R){let N=A.match(/":\s*"([^"]+)"/);N&&E.add(N[1])}}return i.json({filesReferenced:E.size,toolCallCount:_})}),t.get("/api/sessions/:id/commits",async i=>{let l=i.req.param("id"),p=Qr(l);if(p.length>0||i.req.query("refresh")!=="1")return i.json({commits:p});let m=await Zr(l);return i.json({commits:Qr(l),status:m.status})}),t.get("/api/commits/:sha/session",i=>{let l=i.req.param("sha");return/^[0-9a-fA-F]{4,40}$/.test(l)?i.json({sessions:mn(l)}):i.json({error:"invalid sha format"},400)}),t.get("/api/license/status",async i=>{let l=await Tt();return i.json(l)}),t.post("/api/feedback",async i=>{let l;try{l=await i.req.json()}catch{return i.json({error:"invalid json"},400)}if(!l||typeof l!="object")return i.json({error:"invalid body"},400);let p=process.env.RECALL_FEEDBACK_API??"https://clauderecall.com/api/feedback",m=p==="https://clauderecall.com/api/feedback",_=await Tt(),E=St(),T=m&&_.tier==="pro"&&E?E.license_jwt:null,R=(()=>{try{let x=_o(au(cu(import.meta.url)),"..","..","package.json");return JSON.parse(ro(x,"utf8")).version}catch{return"unknown"}})(),A=l,N={score:A.score,comment:A.comment??null,surface:"web",version:typeof A.version=="string"?A.version:R,os:typeof A.os=="string"?A.os:process.platform,trigger_kind:typeof A.trigger_kind=="string"?A.trigger_kind:"manual",license_jwt:T};try{let x=await fetch(p,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(N)}),v=await x.json().catch(()=>({}));return i.json(v,x.status)}catch(x){let v=x instanceof Error?x.message:"network error";return i.json({error:"upstream_unreachable",detail:v},502)}}),t.get("/api/discover/today",es,async i=>{try{return i.json(await vd())}catch(l){return console.error("[discover.today]",l),i.json({rediscovered:null,expensive:null,authored:null,availability:{semantic:!1,cost:!1,git:!1},generatedAt:new Date().toISOString(),error:l.message},500)}}),t.get("/api/macro-repos",i=>i.json({macro_repos:Sr(),orphan_projects:Wc()})),t.get("/api/bug-synthesis",i=>{let l=i.req.query("scope"),p=i.req.query("target_id"),m=i.req.query("limit"),_=l==="cluster"||l==="project"?l:void 0,E=m?Math.max(1,Number(m)):50,T=Vc({scope:_,target_id:p??void 0,limit:E});return i.json({results:T})}),t.get("/api/bug-synthesis/counts",i=>{let l=i.req.query("scope");if(l!=="cluster"&&l!=="project")return i.json({error:'scope must be "cluster" or "project"'},400);let p=Zc(l);return i.json({counts:Array.from(p.entries()).map(([m,_])=>({target_id:m,count:_}))})}),t.get("/api/bug-synthesis/:id",i=>{let l=Number(i.req.param("id"));if(!Number.isFinite(l))return i.json({error:"invalid id"},400);let p=Tr(l);return p?i.json({result:p}):i.json({error:"not found"},404)}),t.delete("/api/bug-synthesis/:id",i=>{let l=Number(i.req.param("id"));return Number.isFinite(l)?(Qc(l),i.json({ok:!0})):i.json({error:"invalid id"},400)});let s=j.object({name:j.string().min(1).max(100),description:j.string().max(500).nullable().optional()});t.post("/api/macro-repos",async i=>{let l=await i.req.json().catch(()=>null),p=s.safeParse(l);if(!p.success)return i.json({error:"invalid request body",details:p.error.format()},400);try{let m=qc({name:p.data.name,description:p.data.description??null});return i.json({macro_repo:m},201)}catch(m){let _=m instanceof Error?m.message:String(m);return _.includes("UNIQUE constraint")?i.json({error:`a macro repo named "${p.data.name}" already exists`},409):i.json({error:_},400)}});let n=j.object({name:j.string().min(1).max(100).optional(),description:j.string().max(500).nullable().optional()});t.patch("/api/macro-repos/:id",async i=>{let l=Number(i.req.param("id"));if(!Number.isFinite(l))return i.json({error:"invalid id"},400);let p=await i.req.json().catch(()=>null),m=n.safeParse(p);if(!m.success)return i.json({error:"invalid request body"},400);try{let _=Jc(l,m.data);return i.json({macro_repo:_})}catch(_){let E=_ instanceof Error?_.message:String(_);return E.includes("not found")?i.json({error:E},404):E.includes("UNIQUE constraint")?i.json({error:"another macro repo already has that name"},409):i.json({error:E},400)}}),t.delete("/api/macro-repos/:id",i=>{let l=Number(i.req.param("id"));return Number.isFinite(l)?(Xc(l),i.json({ok:!0})):i.json({error:"invalid id"},400)});let r=j.object({project_id:j.number().int().positive()});t.post("/api/macro-repos/:id/members",async i=>{let l=Number(i.req.param("id"));if(!Number.isFinite(l))return i.json({error:"invalid id"},400);let p=await i.req.json().catch(()=>null),m=r.safeParse(p);if(!m.success)return i.json({error:"invalid request body"},400);try{return Gc(l,m.data.project_id),i.json({macro_repo:it(l)})}catch(_){let E=_ instanceof Error?_.message:String(_);return i.json({error:E},E.includes("not found")?404:400)}}),t.delete("/api/macro-repos/:id/members/:projectId",i=>{let l=Number(i.req.param("id")),p=Number(i.req.param("projectId"));return!Number.isFinite(l)||!Number.isFinite(p)?i.json({error:"invalid id"},400):(Yc(l,p),i.json({macro_repo:it(l)}))}),t.get("/api/projects",i=>{let l=f(),m=i.req.query("system")==="1"||i.req.query("system")==="true"?"":" AND COALESCE(s.auto_title, '') NOT LIKE '[meta]%' AND COALESCE(s.auto_title, '') NOT LIKE '[output-index]%' AND COALESCE(s.auto_title, '') NOT LIKE '[skill]%'",_=l.prepare(`SELECT p.id, p.name, p.decoded_path,
1640
+ COUNT(CASE WHEN s.id IS NOT NULL${m} THEN 1 END) AS session_count,
1641
+ COALESCE(SUM(CASE WHEN s.id IS NOT NULL${m} THEN s.message_count ELSE 0 END), 0) AS message_count,
1642
+ MAX(COALESCE(s.ended_at, s.started_at)) AS latest
1643
+ FROM projects p
1644
+ LEFT JOIN sessions s ON s.project_id = p.id
1645
+ GROUP BY p.id
1646
+ ORDER BY MAX(COALESCE(s.ended_at, s.started_at, '')) DESC`).all();return i.json(_)}),t.get("/api/graph/:project",i=>{let l=f(),p=i.req.param("project"),m=l.prepare(`SELECT id, name, decoded_path FROM projects
1647
+ WHERE name = ? LIMIT 1`).get(p);if(!m)return i.json({error:`project "${p}" not found`},404);let _=l.prepare(`SELECT s.id,
1648
+ s.auto_title,
1649
+ s.auto_title_source,
1650
+ s.first_user_message,
1651
+ s.started_at,
1652
+ s.message_count,
1653
+ s.title_quality,
1654
+ sa.alias AS alias
1655
+ FROM sessions s
1656
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1657
+ WHERE s.project_id = ?
1658
+ ORDER BY s.started_at`).all(m.id),E=_.map(A=>A.id),T=[];if(E.length>0){let A=E.map(()=>"?").join(",");T=l.prepare(`SELECT thread_id, session_id, parent_session_id, role
1659
+ FROM thread_edges
1660
+ WHERE session_id IN (${A})
1661
+ AND (parent_session_id IS NULL
1662
+ OR parent_session_id IN (${A}))`).all(...E,...E)}let R=_.map(A=>{let N=A.alias??A.auto_title??A.first_user_message??A.id.slice(0,8),x=null,v=null;if(A.auto_title?.startsWith("/")){let C=A.auto_title.split(" \xB7 ");x=C[0],v=C.length>1?C.slice(1).join(" \xB7 "):null}return{id:A.id.slice(0,8),full_id:A.id,title:N,alias:A.alias,auto_title:A.auto_title,auto_title_source:A.auto_title_source,title_quality:A.title_quality,started_at:A.started_at,msgs:A.message_count,skill:x,brand:v}});return i.json({project:m,sessions:R,thread_edges:T})});let o=new Set(["citation","similar","skill_track","bug_pattern","wiki_link","temporal_proximity"]),a=new Set(["pending","approved","rejected"]),c=new Set(["L1","L2","L3","L4","user"]);t.get("/api/links",i=>{let l=i.req.query("source_id")??void 0,p=i.req.query("target_id")??void 0,m=i.req.query("type"),_=i.req.query("approved"),E=i.req.query("limit"),T;if(m){if(!o.has(m))return i.json({error:`invalid type: ${m}`},400);T=m}let R=_==="1"||_==="true",A=E?Number(E):void 0;if(A!==void 0&&(!Number.isFinite(A)||A<1))return i.json({error:"invalid limit"},400);try{let N=As({sourceSessionId:l,targetSessionId:p,linkType:T,approvedOnly:R,limit:A});return i.json({links:N})}catch(N){return i.json({error:N.message},400)}}),t.get("/api/links/suggestions",i=>{let l=i.req.query("status"),p=i.req.query("source_id")??void 0,m=i.req.query("target_id")??void 0,_=i.req.query("inferred_by"),E=i.req.query("limit"),T;if(l){if(!a.has(l))return i.json({error:`invalid status: ${l}`},400);T=l}let R;if(_){if(!c.has(_))return i.json({error:`invalid inferred_by: ${_}`},400);R=_}let A=E?Number(E):void 0;if(A!==void 0&&(!Number.isFinite(A)||A<1))return i.json({error:"invalid limit"},400);try{let N=Ve({status:T,sourceSessionId:p,targetSessionId:m,inferredBy:R,limit:A}),x=new Set;for(let O of N)x.add(O.source_session_id),x.add(O.target_session_id);let v=new Map;if(x.size>0){let O=Array.from(x),F=O.map(()=>"?").join(","),Q=f().prepare(`SELECT s.id,
1663
+ NULLIF(sa.alias, '') AS alias,
1664
+ s.auto_title,
1665
+ s.first_user_message,
1666
+ p.name AS project
1667
+ FROM sessions s
1668
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1669
+ LEFT JOIN projects p ON p.id = s.project_id
1670
+ WHERE s.id IN (${F})`).all(...O);for(let V of Q){let W=V.first_user_message?V.first_user_message.slice(0,80):null,G=V.alias??V.auto_title??W??V.id.slice(0,8);v.set(V.id,{title:G,project:V.project})}}let C=N.map(O=>{let F=v.get(O.source_session_id),U=v.get(O.target_session_id);return{...O,source_title:F?.title??O.source_session_id.slice(0,8),source_project:F?.project??null,target_title:U?.title??O.target_session_id.slice(0,8),target_project:U?.project??null}});return i.json({suggestions:C})}catch(N){return i.json({error:N.message},400)}}),t.get("/api/output-index/:sessionId",i=>{let l=i.req.param("sessionId");if(!l)return i.json({error:"sessionId required"},400);let p=Be(l);return p?i.json(p):i.json({error:`no output index for session ${l}`},404)});let d=new Set(["pagerank","embedding-rerank","hybrid"]);t.get("/api/neighborhood/:sessionId",i=>{let l=i.req.param("sessionId");if(!l)return i.json({error:"sessionId required"},400);let p=i.req.query("budget"),m=p!==void 0?Number(p):4e3;if(!Number.isFinite(m)||m<100)return i.json({error:"budget must be a number \u2265 100"},400);let _=i.req.query("scoring")??"hybrid";if(!d.has(_))return i.json({error:`invalid scoring: ${_}; valid: pagerank, embedding-rerank, hybrid`},400);let E=_,T=i.req.query("max_depth"),R=T!==void 0?Number(T):2;if(!Number.isFinite(R)||R<1)return i.json({error:"max_depth must be a number \u2265 1"},400);let A,N=i.req.query("edge_types");if(N){let F=N.split(",").map(U=>U.trim()).filter(Boolean);for(let U of F)if(!o.has(U))return i.json({error:`invalid edge_type: ${U}`},400);A=F}let x=i.req.query("include_wiki_links"),v=x===void 0?!0:!(x==="0"||x==="false"),C=i.req.query("include_suggestions"),O=C==="1"||C==="true";try{let F=Os(l,{budget:m,scoring:E,maxDepth:R,edgeTypes:A,includeWikiLinks:v,includeSuggestions:O});return i.json(F)}catch(F){let U=F instanceof Error?F.message:"unknown error",Q=/not found/.test(U)?404:500;return i.json({error:U},Q)}}),t.get("/api/bug-patterns",i=>{let l=i.req.query("min_count"),p=i.req.query("status"),m=i.req.query("project")??void 0,_=i.req.query("limit"),E=i.req.query("offset"),T=l?Number(l):void 0;if(T!==void 0&&(!Number.isFinite(T)||T<1))return i.json({error:"min_count must be a positive integer"},400);let R;if(p==="open")R=!1;else if(p==="resolved")R=!0;else if(p&&p!=="all")return i.json({error:`invalid status: ${p}; valid: open, resolved, all`},400);let A=_?Number(_):void 0;if(A!==void 0&&(!Number.isFinite(A)||A<1))return i.json({error:"invalid limit"},400);let N=E?Number(E):void 0;if(N!==void 0&&(!Number.isFinite(N)||N<0))return i.json({error:"invalid offset"},400);try{let x=Fa({minOccurrenceCount:T,hasResolved:R,project:m,limit:A,offset:N});return i.json(x)}catch(x){return i.json({error:x.message},400)}}),t.get("/api/bug-patterns/setup-status",i=>{let l=f(),m=l.prepare(`SELECT p.name AS project,
1671
+ COUNT(s.id) AS total_sessions,
1672
+ SUM(CASE WHEN oi.session_id IS NOT NULL THEN 1 ELSE 0 END) AS extracted_sessions,
1673
+ MAX(oi.extracted_at) AS last_extracted_at
1674
+ FROM projects p
1675
+ LEFT JOIN sessions s ON s.project_id = p.id
1676
+ LEFT JOIN session_output_index oi ON oi.session_id = s.id
1677
+ GROUP BY p.id
1678
+ ORDER BY total_sessions DESC`).all().map(T=>({project:T.project,total_sessions:T.total_sessions??0,extracted_sessions:T.extracted_sessions??0,remaining_sessions:(T.total_sessions??0)-(T.extracted_sessions??0),last_extracted_at:T.last_extracted_at})),_=m.reduce((T,R)=>(T.total_sessions+=R.total_sessions,T.extracted_sessions+=R.extracted_sessions,T.remaining_sessions+=R.remaining_sessions,T),{total_sessions:0,extracted_sessions:0,remaining_sessions:0}),E=l.prepare("SELECT COUNT(*) AS n FROM bug_pattern_clusters").get();return i.json({projects:m,totals:{..._,cluster_count:E.n}})});let u=j.object({project:j.string().min(1),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),limit:j.number().int().positive().optional(),force:j.boolean().optional()});t.post("/api/extract-outputs/preflight",async i=>{let l=ye(i);if(l)return l;let p=wn();if(p>0&&p<1*1024**3)return i.json({error:"insufficient-disk-space",message:`${(p/1024**3).toFixed(2)} GB free \u2014 extract-outputs needs at least 1 GB headroom. Free up disk and retry.`,freeBytes:p},507);let m=await i.req.json().catch(()=>null),_=u.safeParse(m);if(!_.success)return i.json({error:"invalid request body",details:_.error.format()},400);let E={project:_.data.project,model:_.data.model??We,limit:_.data.limit??200,force:_.data.force??!1},T=Nr();if(E.limit>T.sessionCeiling)return ee({kind:"run-rejected",job_id:null,project:E.project,model:E.model,limit:E.limit,origin:i.req.header("origin")??null,reason:`limit ${E.limit} exceeds session ceiling ${T.sessionCeiling}`}),i.json({error:`requested limit ${E.limit} exceeds session ceiling ${T.sessionCeiling}. Lower the limit or edit launcher.sessionCeiling in ~/.recall/config.json.`},400);let R=Bt(E.project);if(!R)return i.json({error:`project "${E.project}" not found`},404);let N=rt({projectId:R.id,limit:E.limit,force:E.force}).eligible.length,x=xr(N),v=Ce(N,E.model),C=at(),O=x.estimated_input_tokens_max+x.estimated_output_tokens_max>C.remaining_tokens_24h;if(ee({kind:"preflight",job_id:null,project:E.project,model:E.model,limit:E.limit,origin:i.req.header("origin")??null,sessions_eligible:N}),N===0)return i.json({eligible_session_count:0,...x,plan_window_estimate:v,budget:C,would_exceed_budget:!1,preflight_token:null,expires_at:null,message:"No eligible sessions to extract. Pass force=true to re-extract sessions already at the current extractor version."});let{token:F,expiresAt:U}=wr(E);return i.json({preflight_token:F,expires_at:new Date(U).toISOString(),eligible_session_count:N,...x,plan_window_estimate:v,budget:C,would_exceed_budget:O})});let g=j.object({preflight_token:j.string().length(64)});t.post("/api/extract-outputs/run",async i=>{let l=ye(i);if(l)return l;let p=await i.req.json().catch(()=>null),m=g.safeParse(p);if(!m.success)return i.json({error:"invalid request body"},400);let _=Rr(m.data.preflight_token);if(!_)return ee({kind:"run-rejected",job_id:null,project:null,model:null,limit:null,origin:i.req.header("origin")??null,reason:"preflight token invalid, expired, or already used"}),i.json({error:"preflight token invalid, expired, or already used"},400);let E=at(),R=(()=>{let v=Bt(_.project);return v?rt({projectId:v.id,limit:_.limit,force:_.force}):null})()?.eligible.length??0,A=xr(R),N=A.estimated_input_tokens_max+A.estimated_output_tokens_max;if(N>E.remaining_tokens_24h)return ee({kind:"run-rejected",job_id:null,project:_.project,model:_.model,limit:_.limit,origin:i.req.header("origin")??null,reason:`projected spend ${N} exceeds remaining 24h budget ${E.remaining_tokens_24h}`}),i.json({error:"daily token budget would be exceeded. Wait for the rolling 24h window to clear, or raise launcher.dailyTokenBudget in ~/.recall/config.json.",budget:E,projected_spend:N},429);let x=ll({project:_.project,model:_.model,limit:_.limit,force:_.force,origin:i.req.header("origin")??null});return"error"in x?i.json({error:x.error},400):i.json({jobId:x.jobId,reused:x.reused},x.reused?409:200)}),t.get("/api/extract-outputs/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Cr(l))return i.json({error:"job not found"},404);let m=Number(i.req.header("Last-Event-ID")??0);return Me(i,async _=>{let E=!1,T=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let R of dl(l,m))if(E||(await _.writeSSE({id:String(R.id),event:R.kind,data:JSON.stringify(R.data)}),R.kind==="done"))break}finally{E=!0,clearInterval(T)}})}),t.get("/api/extract-outputs/jobs/:jobId",i=>{let l=Cr(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/extract-outputs/jobs/:jobId",i=>{let l=ye(i);return l||(ul(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))}),t.get("/api/extract-outputs/budget",i=>i.json(at()));let h=j.object({scope:j.enum(["cluster","project"]),target_id:j.string().min(1),mode:j.enum(["synopsis","priorities","root_cause"]),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()});t.post("/api/bug-patterns/synthesize/preflight",async i=>{{let U=wn();if(U>0&&U<1*1024**3)return i.json({error:"insufficient-disk-space",message:`${(U/1024**3).toFixed(2)} GB free \u2014 bug synthesis needs at least 1 GB headroom. Free up disk and retry.`,freeBytes:U},507)}let l=ye(i);if(l)return l;let p=await i.req.json().catch(()=>null),m=h.safeParse(p);if(!m.success)return i.json({error:"invalid request body",details:m.error.format()},400);let _={scope:m.data.scope,target_id:m.data.target_id,mode:m.data.mode,model:m.data.model??pl},E=Js(_);if(!E)return ee({kind:"synth-rejected",job_id:null,project:_.scope==="project"?_.target_id:null,model:_.model,limit:null,origin:i.req.header("origin")??null,reason:"target not found"}),i.json({error:_.scope==="cluster"?`cluster "${_.target_id}" not found in any extracted findings`:`project "${_.target_id}" has no extracted findings to synthesize`},404);let T=Or({scope:_.scope,mode:_.mode,member_session_count:E.context_summary.session_count,cluster_count:E.context_summary.cluster_count}),R=T.estimated_input_tokens_max+T.estimated_output_tokens_max,A=cl(R),N=Ce(A,_.model),x=at(),C=T.estimated_input_tokens_max+T.estimated_output_tokens_max>x.remaining_tokens_24h;ee({kind:"synth-preflight",job_id:null,project:_.scope==="project"?_.target_id:null,model:_.model,limit:null,origin:i.req.header("origin")??null,reason:`${_.scope}/${_.mode}/${_.target_id}`});let{token:O,expiresAt:F}=wr({project:_.target_id,model:_.model,limit:1,force:!1});return fn.set(O,_),setTimeout(()=>fn.delete(O),9e4).unref?.(),i.json({preflight_token:O,expires_at:new Date(F).toISOString(),estimated_input_tokens_max:T.estimated_input_tokens_max,estimated_output_tokens_max:T.estimated_output_tokens_max,plan_window_estimate:N,budget:x,would_exceed_budget:C,context_summary:E.context_summary})});let b=j.object({preflight_token:j.string().length(64)});t.post("/api/bug-patterns/synthesize/run",async i=>{let l=ye(i);if(l)return l;let p=await i.req.json().catch(()=>null),m=b.safeParse(p);if(!m.success)return i.json({error:"invalid request body"},400);let _=Rr(m.data.preflight_token),E=fn.get(m.data.preflight_token)??null;if(fn.delete(m.data.preflight_token),!E||!_)return ee({kind:"synth-rejected",job_id:null,project:null,model:null,limit:null,origin:i.req.header("origin")??null,reason:"preflight token invalid, expired, or already used"}),i.json({error:"preflight token invalid, expired, or already used"},400);let T=at(),R=Js(E);if(!R)return i.json({error:E.scope==="cluster"?`cluster "${E.target_id}" no longer exists`:`project "${E.target_id}" has no findings`},404);let A=Or({scope:E.scope,mode:E.mode,member_session_count:R.context_summary.session_count,cluster_count:R.context_summary.cluster_count}),N=A.estimated_input_tokens_max+A.estimated_output_tokens_max;if(N>T.remaining_tokens_24h)return ee({kind:"synth-rejected",job_id:null,project:E.scope==="project"?E.target_id:null,model:E.model,limit:null,origin:i.req.header("origin")??null,reason:`projected spend ${N} exceeds remaining 24h budget ${T.remaining_tokens_24h}`}),i.json({error:"daily token budget would be exceeded. Wait for the rolling 24h window to clear, or raise launcher.dailyTokenBudget in ~/.recall/config.json.",budget:T,projected_spend:N},429);let x=fl({intent:E,origin:i.req.header("origin")??null});return"error"in x?i.json({error:x.error},400):i.json({jobId:x.jobId,reused:x.reused},x.reused?409:200)}),t.get("/api/bug-patterns/synthesize/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Ir(l))return i.json({error:"job not found"},404);let m=Number(i.req.header("Last-Event-ID")??0);return Me(i,async _=>{let E=!1,T=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let R of hl(l,m))if(E||(await _.writeSSE({id:String(R.id),event:R.kind,data:JSON.stringify(R.data)}),R.kind==="done"))break}finally{E=!0,clearInterval(T)}})}),t.get("/api/bug-patterns/synthesize/jobs/:jobId",i=>{let l=Ir(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/bug-patterns/synthesize/jobs/:jobId",i=>{let l=ye(i);return l||(El(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))}),t.get("/api/bug-signatures",i=>{let l=i.req.query("project")??null,p=Math.min(Math.max(1,Number(i.req.query("limit")??100)),500),m=f(),_=["oi.bug_signatures IS NOT NULL"],E=[];l&&(_.push("p.name = ?"),E.push(l));let R=m.prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
1679
+ s.started_at, oi.extracted_at, oi.bug_signatures
1680
+ FROM session_output_index oi
1681
+ JOIN sessions s ON s.id = oi.session_id
1682
+ JOIN projects p ON p.id = s.project_id
1683
+ WHERE ${_.join(" AND ")}
1684
+ ORDER BY oi.extracted_at DESC
1685
+ LIMIT ?`).all(...E,p).map(C=>{let O=[];try{let F=JSON.parse(C.bug_signatures);Array.isArray(F)&&(O=F)}catch{O=[]}return{session_id:C.session_id,project:C.project,auto_title:C.auto_title,started_at:C.started_at,extracted_at:C.extracted_at,rawSignatures:O}}),A=R.flatMap(C=>C.rawSignatures.map(O=>O.message_hash).filter(O=>typeof O=="string")),N=Er(A),x=R.map(C=>({session_id:C.session_id,project:C.project,auto_title:C.auto_title,started_at:C.started_at,extracted_at:C.extracted_at,signatures:C.rawSignatures.map(O=>{let F=O.message_hash?N.get(O.message_hash)??null:null;return{...O,resolved:br(F),resolution:F}}),signature_count:C.rawSignatures.length})),v=x.reduce((C,O)=>(C.sessions_total+=1,O.signature_count>0?(C.sessions_with_findings+=1,C.total_findings+=O.signature_count):C.sessions_empty+=1,C),{sessions_total:0,sessions_with_findings:0,sessions_empty:0,total_findings:0});return i.json({sessions:x,totals:v})}),t.post("/api/bug-signatures/:hash/resolve",async i=>{let l=i.req.param("hash");if(!l||l.length<4)return i.json({error:"invalid message hash"},400);let p=await i.req.json().catch(()=>({})),m=Bc({messageHash:l,resolvedInSessionId:p.resolved_in_session_id??null,fixSummary:p.fix_summary??null});return i.json({resolution:m})}),t.post("/api/bug-signatures/:hash/unresolve",i=>{let l=i.req.param("hash");return!l||l.length<4?i.json({error:"invalid message hash"},400):(Hc(l),i.json({ok:!0}))}),t.get("/api/bug-patterns/graph",i=>{let l=i.req.query("project")??null,p=i.req.query("include_resolved")!=="0",m=f(),_=["oi.bug_signatures IS NOT NULL","oi.bug_signatures != '[]'"],E=[];l&&(_.push("p.name = ?"),E.push(l));let T=m.prepare(`SELECT s.id AS session_id, p.name AS project, s.auto_title,
1686
+ s.started_at, oi.extracted_at, oi.bug_signatures
1687
+ FROM session_output_index oi
1688
+ JOIN sessions s ON s.id = oi.session_id
1689
+ JOIN projects p ON p.id = s.project_id
1690
+ WHERE ${_.join(" AND ")}
1691
+ ORDER BY oi.extracted_at DESC`).all(...E),R=[];for(let W of T){let G=[];try{let K=JSON.parse(W.bug_signatures);Array.isArray(K)&&(G=K)}catch{continue}for(let K of G)R.push({sig:K,session_id:W.session_id,project:W.project,auto_title:W.auto_title})}let A=new Map;for(let W of R){let G=W.sig.message_hash??`nohash:${(W.sig.snippet??"").slice(0,64)}`,K=A.get(G);K?K.push(W):A.set(G,[W])}let N=Array.from(A.keys()).filter(W=>!W.startsWith("nohash:")),x=Er(N),v=[],C=new Map,O=[];for(let[W,G]of A){let K=G[0],P=K.sig.message_hash??null,q=P?x.get(P)??null:null,pe=br(q);if(!p&&pe)continue;let Ee=Array.from(new Set(G.map(z=>z.project))).sort(),bt=Array.from(new Set(G.map(z=>z.session_id))),ts={id:P??W,message_hash:P,error_type:K.sig.error_type??null,snippet:(K.sig.snippet??"").slice(0,200),file:K.sig.file??null,occurrence_count:G.length,projects:Ee,resolved:pe,fix_summary:q?.fix_summary??null,member_session_ids:bt};v.push(ts);for(let z of G)C.has(z.session_id)||C.set(z.session_id,{session_id:z.session_id,project:z.project,auto_title:z.auto_title}),O.push({cluster_id:ts.id,session_id:z.session_id})}let F=[],U=4,Q=new Map;function V(W){let G=Q.get(W)??0;return G>=U?!1:(Q.set(W,G+1),!0)}for(let W=0;W<v.length;W+=1)for(let G=W+1;G<v.length;G+=1){let K=v[W],P=v[G],q=null;K.error_type&&K.error_type!=="unknown"&&K.error_type===P.error_type?q="same_error_type":K.file&&P.file&&K.file===P.file&&(q="same_file"),q&&(!V(K.id)||!V(P.id)||F.push({a:K.id,b:P.id,reason:q}))}return i.json({clusters:v,sessions:Array.from(C.values()),member_edges:O,related_edges:F,totals:{cluster_count:v.length,singleton_count:v.filter(W=>W.occurrence_count===1).length,recurring_count:v.filter(W=>W.occurrence_count>1).length,session_count:C.size,resolved_count:v.filter(W=>W.resolved).length}})}),t.get("/api/bug-patterns/:clusterId",i=>{let l=i.req.param("clusterId");if(!l)return i.json({error:"clusterId required"},400);let p=Pa(l);return p?i.json(p):i.json({error:`cluster ${l} not found`},404)}),t.post("/api/bug-patterns/:clusterId/resolve",async i=>{let l=i.req.param("clusterId");if(!l)return i.json({error:"clusterId required"},400);let p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);let m=p.resolved_in_session_id,_=p.fix_summary;if(typeof m!="string"||m.length===0)return i.json({error:"resolved_in_session_id required"},400);if(typeof _!="string"||_.trim().length===0)return i.json({error:"fix_summary required"},400);try{let E=Ua(l,m,_);return i.json(E)}catch(E){let T=E instanceof Error?E.message:"unknown error",R=/not found/.test(T)?404:(/not a member/.test(T),400);return i.json({error:T},R)}}),t.post("/api/bug-patterns/:clusterId/split",async i=>{let l=i.req.param("clusterId");if(!l)return i.json({error:"clusterId required"},400);let p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);let m=p.member_session_ids;if(!Array.isArray(m)||m.length===0)return i.json({error:"member_session_ids must be a non-empty array of strings"},400);let _=[];for(let E of m){if(typeof E!="string"||E.length===0)return i.json({error:"member_session_ids must contain only non-empty strings"},400);_.push(E)}try{let E=$a(l,_);return i.json(E)}catch(E){let T=E instanceof Error?E.message:"unknown error",R=/not found/.test(T)?404:(/cannot split|none of the supplied/.test(T),400);return i.json({error:T},R)}}),t.post("/api/links",async i=>{let l=await i.req.json().catch(()=>null);if(!l)return i.json({error:"body required"},400);let p=l.source_session_id,m=l.target_session_id,_=l.link_type;if(typeof p!="string"||p.length===0)return i.json({error:"source_session_id required"},400);if(typeof m!="string"||m.length===0)return i.json({error:"target_session_id required"},400);if(typeof _!="string")return i.json({error:"link_type required"},400);if(!o.has(_))return i.json({error:`invalid link_type: ${_}`},400);if(_!=="wiki_link")return i.json({error:`link_type '${_}' is not user-writable; only wiki_link is exposed via this endpoint. Other types must go through the suggestions-queue review flow.`},403);if(p===m)return i.json({error:"a session cannot link to itself"},400);let E=f();if(!E.prepare("SELECT 1 FROM sessions WHERE id = ?").get(p))return i.json({error:`source session not found: ${p}`},404);if(!E.prepare("SELECT 1 FROM sessions WHERE id = ?").get(m))return i.json({error:`target session not found: ${m}`},404);let A=As({sourceSessionId:m,targetSessionId:p,linkType:"wiki_link"});if(A.length>0)return i.json({link:A[0]});try{let N=fa({source_session_id:p,target_session_id:m,link_type:"wiki_link",confidence:1,source:"manual",evidence:l.evidence??{created_via:"context_menu"},approved:!0});return i.json({link:N})}catch(N){return i.json({error:N.message},400)}}),t.delete("/api/links/:id",i=>{let l=i.req.param("id"),p=Number(l);if(!Number.isInteger(p)||p<=0)return i.json({error:"id must be a positive integer"},400);let m=ha(p);return m.removed===0?i.json({error:`link ${p} not found`},404):i.json(m)}),t.get("/api/sessions/:id/links",i=>{let l=i.req.param("id");if(!l)return i.json({error:"sessionId required"},400);let p=f();if(!p.prepare("SELECT 1 FROM sessions WHERE id = ?").get(l))return i.json({error:`session not found: ${l}`},404);let _=i.req.query("type")??"wiki_link";if(!o.has(_))return i.json({error:`invalid type: ${_}`},400);let E=_,T=Lt(l).filter(C=>C.link_type===E),R=ba(l,T);if(R.length===0)return i.json({links:[]});let A=R.map(C=>C.otherSessionId),N=A.map(()=>"?").join(","),x=p.prepare(`SELECT s.id,
1692
+ NULLIF(sa.alias, '') AS alias,
1693
+ s.auto_title,
1694
+ s.first_user_message,
1695
+ p.name AS project
1696
+ FROM sessions s
1697
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1698
+ LEFT JOIN projects p ON p.id = s.project_id
1699
+ WHERE s.id IN (${N})`).all(...A),v=new Map(x.map(C=>[C.id,C]));return i.json({links:R.map(C=>{let O=v.get(C.otherSessionId),F=O?.alias?.trim()||O?.auto_title?.trim()||(O?.first_user_message?O.first_user_message.slice(0,80):"")||C.otherSessionId.slice(0,8);return{linkId:C.linkId,otherSessionId:C.otherSessionId,direction:C.direction,updatedAt:C.updatedAt,title:F,project:O?.project??null}})})}),t.patch("/api/links/suggestions/:id",async i=>{let l=i.req.param("id"),p=Number(l);if(!Number.isInteger(p)||p<=0)return i.json({error:"id must be a positive integer"},400);let m=await i.req.json().catch(()=>null);if(!m||typeof m.status!="string")return i.json({error:"status required (approved|rejected)"},400);if(m.status!=="approved"&&m.status!=="rejected")return i.json({error:`invalid status: ${m.status}`},400);try{let _=zn(p,m.status);return i.json(_)}catch(_){let E=_.message,T=/already decided/.test(E)?409:/not found/.test(E)?404:400;return i.json({error:E},T)}}),t.post("/api/links/suggestions/bulk-decide",async i=>{let l=await i.req.json().catch(()=>null);if(!l)return i.json({error:"body required"},400);let p=Array.isArray(l.ids)?l.ids:null;if(!p||p.length===0)return i.json({error:"ids must be a non-empty array"},400);if(p.length>1e3)return i.json({error:"bulk-decide capped at 1000 ids per call"},400);if(l.status!=="approved"&&l.status!=="rejected")return i.json({error:`invalid status: ${l.status}`},400);let m=l.status,_=0,E=0,T=[];for(let R of p){let A=Number(R);if(!Number.isInteger(A)||A<=0){T.push({id:Number.isFinite(Number(R))?Number(R):-1,error:"invalid id"});continue}try{zn(A,m),_+=1}catch(N){let x=N.message;/already decided/.test(x)?E+=1:T.push({id:A,error:x})}}return i.json({decided:_,skipped:E,errors:T})}),t.get("/api/sessions",i=>{let l=f(),p=i.req.query("project"),m=i.req.query("since"),_=i.req.query("until"),E=i.req.queries("tag")??[],T=i.req.query("collection"),R=Math.max(1,Math.min(500,Number(i.req.query("limit")??100))),A=i.req.query("system")==="1"||i.req.query("system")==="true",N={limit:R},x="s.message_count > 2";if(A||(x+=" AND COALESCE(s.auto_title, '') NOT LIKE '[meta]%' AND COALESCE(s.auto_title, '') NOT LIKE '[output-index]%' AND COALESCE(s.auto_title, '') NOT LIKE '[skill]%'"),p&&(x+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",N.proj=`%${Qt(p)}%`),m&&(x+=" AND s.started_at >= @since",N.since=m),_&&(x+=" AND s.started_at <= @until",/^\d{4}-\d{2}-\d{2}$/.test(_)?N.until=`${_}T23:59:59.999Z`:N.until=_),E.length>0&&E.map(F=>De(F)).filter(Boolean).forEach((F,U)=>{x+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${U})`,N[`tag_${U}`]=F}),T){let O=Cn(T);if(O.length===0)return i.json([]);let F=O.map((U,Q)=>`@col_${Q}`).join(",");x+=` AND s.id IN (SELECT session_id FROM collection_sessions WHERE collection_id IN (${F}))`,O.forEach((U,Q)=>{N[`col_${Q}`]=U})}let C=l.prepare(`SELECT s.id, p.name AS project, s.started_at, s.ended_at,
1700
+ s.message_count, s.first_user_message, s.git_branch,
1701
+ s.auto_title, s.auto_title_source, s.verification_status,
1702
+ NULLIF(sa.alias, '') AS alias,
1703
+ CASE
1704
+ WHEN (sn.content IS NOT NULL AND sn.content != '')
1705
+ OR (sn.auto_synopsis IS NOT NULL AND sn.auto_synopsis != '')
1706
+ THEN 1 ELSE 0
1707
+ END AS has_notes,
1708
+ COALESCE(
1709
+ (SELECT GROUP_CONCAT(tag, ',')
1710
+ FROM (SELECT tag FROM session_tags WHERE session_id = s.id ORDER BY tag)),
1711
+ ''
1712
+ ) AS tags_csv
1713
+ FROM sessions s
1714
+ JOIN projects p ON p.id = s.project_id
1715
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1716
+ LEFT JOIN session_notes sn ON sn.session_id = s.id
1717
+ WHERE ${x}
1718
+ ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
1719
+ LIMIT @limit`).all(N).map(({tags_csv:O,...F})=>{let U=F.id,Q=I.getOrigin(U),V=F.alias,W=V==null?null:I.isSessionAutoLinked(U)?"auto":"manual",G=jt({auto_title:F.auto_title,auto_title_source:F.auto_title_source??null,has_alias:V!=null&&W==="manual"});return{...F,tags:O?O.split(","):[],origin:Q?{editor:Q.editor,label:Q.label}:null,alias_source:W,title_quality:G}});return i.json(C)}),t.get("/api/sessions/:id",i=>{let l=f(),p=i.req.param("id"),m=l.prepare(`SELECT s.*, p.name AS project_name, p.decoded_path,
1720
+ NULLIF(sa.alias, '') AS alias
1721
+ FROM sessions s
1722
+ JOIN projects p ON p.id = s.project_id
1723
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1724
+ WHERE s.id = ?`).get(p);if(!m)return i.json({error:"not found"},404);let _=wt(p),E=I.getOrigin(p),T=E?{editor:E.editor,label:E.label}:null,R=m.alias==null?null:I.isSessionAutoLinked(p)?"auto":"manual",A=l.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
1725
+ FROM messages
1726
+ WHERE session_id = ?
1727
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(p);return i.json({session:{...m,tags:_,origin:T,alias_source:R},messages:A})}),t.get("/api/tags",i=>i.json(Xe())),t.get("/api/sessions/:id/tags",i=>i.json({tags:wt(i.req.param("id"))})),t.post("/api/sessions/:id/tags",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.tag!="string")return i.json({error:"tag required"},400);try{let m=Je(l,p.tag);return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.delete("/api/sessions/:id/tags/:tag",i=>{let l=i.req.param("id"),p=i.req.param("tag");return i.json(Zo(l,p))}),t.get("/api/config/auto-tag",i=>i.json(dr(Re()))),t.put("/api/config/auto-tag",async i=>{let l=await i.req.json().catch(()=>({})),p=Ps.partial().safeParse(l);if(!p.success)return i.json({error:"invalid config",issues:p.error.issues},400);let m=p.data;m.apiKey===void 0&&delete m.apiKey;let _=Rc(m);return _.autopilot&&_.enabled&&_.backend==="api"&&_.apiKey&&Qs(),i.json(dr(_))}),t.get("/api/onboarding",i=>{let p=f().prepare(`SELECT s.id,
1728
+ p.name AS project,
1729
+ s.started_at,
1730
+ s.ended_at,
1731
+ s.message_count,
1732
+ s.first_user_message,
1733
+ NULLIF(sa.alias, '') AS alias
1734
+ FROM sessions s
1735
+ JOIN projects p ON p.id = s.project_id
1736
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1737
+ WHERE s.message_count > 2
1738
+ ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
1739
+ LIMIT 1`).get();return i.json({state:tn(),mostRecentSession:p??null})}),t.put("/api/onboarding",async i=>{let l=await i.req.json().catch(()=>({})),p=en.partial().safeParse(l);return p.success?i.json(od(p.data)):i.json({error:"invalid onboarding state",issues:p.error.issues},400)}),t.post("/api/onboarding/reset",i=>i.json(id())),t.get("/api/config/mcp-install",i=>i.json({...Ie(),claudeCliAvailable:oe()})),t.post("/api/config/mcp-install",i=>i.json({...Zl(),claudeCliAvailable:oe()})),t.delete("/api/config/mcp-install",i=>i.json({...Ql(),claudeCliAvailable:oe()}));let S=j.object({scope:j.object({untaggedOnly:j.boolean().optional(),project:j.string().optional(),collectionId:j.string().optional(),sessionIds:j.array(j.string()).optional(),limit:j.number().int().min(1).max(500).optional()}).default({}),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),scanId:j.string().min(1).max(100).optional()});t.post("/api/tags/scan/claude-cli",async i=>{if(co)return i.json({error:"a scan is already running"},409);if(!oe())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!Ie().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let p=await i.req.json().catch(()=>({})),m=S.safeParse(p);if(!m.success)return i.json({error:"invalid scope",issues:m.error.issues},400);let _=m.data.scope,E=Re(),T=m.data.model??E.model,R=f(),A=()=>R.prepare("SELECT COUNT(*) AS n FROM session_tags").get().n,N=A();co=!0;let x;try{let v=m.data.scanId;x=await On(_,{model:T,scanId:v});let C=A(),O=Math.max(0,C-N);return v&&os(v,{type:"done",result:{success:x.success,exitCode:x.exitCode,tagsAdded:O}}),i.json({success:x.success,exitCode:x.exitCode,tagsAdded:O,model:T,stdout:qe(x.stdout.slice(0,2e3)).redacted,stderrTail:qe(x.stderr.slice(-2e3)).redacted})}finally{co=!1}}),t.get("/api/claude-cli/scan/:scanId/progress",i=>{let l=i.req.param("scanId");return Me(i,async p=>{let m=[],_={resolve:()=>{}},E=new Promise(N=>{_.resolve=N}),T=ti(l,N=>{m.push(N);let x=_.resolve;E=new Promise(v=>{_.resolve=v}),x()}),R=!1,A=setInterval(()=>{R||p.writeSSE({event:"heartbeat",data:""}).catch(()=>{R=!0})},15e3);try{for(;!R;){m.length===0&&await E;let N=m.shift();if(N&&(await p.writeSSE({event:N.type,data:JSON.stringify(N)}),N.type==="done"))break}}finally{R=!0,clearInterval(A),T()}})}),t.get("/api/prompts",i=>i.json({prompts:An.map(l=>({name:l.name,title:l.title,description:l.description})),claudeCliAvailable:oe()})),t.post("/api/prompts/run",async i=>{if(!oe())return i.json({error:"claude CLI not found on PATH. Install Claude Code locally, then reload."},400);if(!Ie().installed)return i.json({error:"Recall MCP is not installed in Claude Code yet, run the one-click install first."},400);let p=await i.req.json().catch(()=>({})),_=j.object({name:j.string(),args:j.record(j.string(),j.unknown()).optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).safeParse(p);if(!_.success)return i.json({error:"invalid request",issues:_.error.issues},400);let E=ei(_.data.name);if(!E)return i.json({error:`unknown prompt: ${_.data.name}`},404);let T=E.build(_.data.args??{}),R=Re(),A=_.data.model??R.model,N=await Fe(T,E.allowedTools,{model:A});return i.json({success:N.success,exitCode:N.exitCode,promptName:E.name,model:A,stdout:N.stdout,stderrTail:N.stderr.slice(-4e3)})}),t.get("/api/autopilot/status",i=>i.json(Vt())),t.get("/api/autopilot/events",i=>Me(i,async l=>{await l.writeSSE({event:"state",data:JSON.stringify(Vt())});let p=[],m=()=>{},_=new Promise(T=>m=T),E=Kl(T=>{p.push(T);let R=m;_=new Promise(A=>m=A),R()});try{for(;;){if(p.length===0){let R=new Promise(N=>setTimeout(()=>N("tick"),3e4));if(await Promise.race([_.then(()=>"event"),R])==="tick"){await l.writeSSE({event:"heartbeat",data:"1"});continue}}let T=p.shift();T&&await l.writeSSE({event:"state",data:JSON.stringify(T)})}}finally{E()}})),t.post("/api/autopilot/kick",i=>(Qs(),i.json({ok:!0,snapshot:Vt()})));let y=j.object({scope:j.object({untaggedOnly:j.boolean().optional(),project:j.string().optional(),collectionId:j.string().optional(),sessionIds:j.array(j.string()).optional(),limit:j.number().int().min(1).max(500).optional()}).default({})});t.post("/api/tags/scan",async i=>{let l=Re();if(!l.enabled)return i.json({error:"auto-tagging is disabled"},403);if(l.backend!=="api")return i.json({error:"api-backend scan requires backend=api in config"},400);if(!l.apiKey)return i.json({error:"no api key configured"},400);let p=await i.req.json().catch(()=>({})),m=y.safeParse(p);if(!m.success)return i.json({error:"invalid scope",issues:m.error.issues},400);let _=Ye(m.data.scope);if(_.length===0)return i.json({error:"no sessions match scope"},400);let E=Hl(_.length);return Gl(E,{apiKey:l.apiKey,model:l.model,minTags:l.minTagsPerSession,maxTags:l.maxTagsPerSession,sessions:_}),i.json({scanId:E.id,total:E.total})}),t.get("/api/tags/scan/:id",i=>{let l=Ys(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let{controller:p,listeners:m,..._}=l;return i.json(_)}),t.get("/api/tags/scan/:id/events",i=>{let l=Ys(i.req.param("id"));return l?Me(i,async p=>{await p.writeSSE({event:"state",data:JSON.stringify({completed:l.completed,total:l.total,status:l.status})});for(let R of l.results)await p.writeSSE({event:"result",data:JSON.stringify(R)});let m=[],_={resolve:()=>{}},E=new Promise(R=>{_.resolve=R}),T=Wl(l,R=>{m.push(R);let A=_.resolve;E=new Promise(N=>{_.resolve=N}),A()});try{for(;l.status==="running"||l.status==="pending";){m.length===0&&await E;let R=m.shift();if(R&&(await p.writeSSE({event:R.type,data:JSON.stringify(R)}),R.type==="done"||R.type==="status"&&(R.status==="cancelled"||R.status==="failed")))break}}finally{T()}}):i.json({error:"scan not found"},404)});let k=j.object({selection:j.array(j.object({sessionId:j.string(),tags:j.array(j.string()).min(1)}))});t.post("/api/tags/scan/:id/apply",async i=>{let l=Ys(i.req.param("id"));if(!l)return i.json({error:"scan not found"},404);let p=await i.req.json().catch(()=>({})),m=k.safeParse(p);if(!m.success)return i.json({error:"invalid selection"},400);let _=Yl(l,m.data.selection);return i.json(_)}),t.delete("/api/tags/scan/:id",i=>{let l=i.req.param("id");return ql(l),Jl(l),i.json({ok:!0})}),t.put("/api/sessions/:id/alias",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.alias!="string")return i.json({error:"alias required"},400);try{let m=me(l,p.alias);if(p.pin===!0)I.unlinkSession(l);else{let _=f().prepare("SELECT cwd, started_at FROM sessions WHERE id = ?").get(l),E=_?.cwd?_.cwd.replace(/\/+$/,""):null,T=!1;if(E&&_?.started_at){let R=Date.parse(_.started_at),A=_.started_at,N=I.all().filter(x=>x.cwd&&x.cwd.replace(/\/+$/,"")===E&&yt({sessionStartedAt:A,terminalOpenedAt:x.opened_at??null}).allowed);if(Number.isFinite(R)&&N.length>0){let v=N.map(C=>({t:C,gap:R-Date.parse(C.opened_at??"")})).filter(C=>Number.isFinite(C.gap)).sort((C,O)=>C.gap-O.gap)[0];v&&(I.linkSession(l,v.t.shell_pid),T=!0)}}T||I.unlinkSession(l)}return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.delete("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return ns(l),I.unlinkSession(l),i.json({ok:!0})}),t.get("/api/sessions/:id/alias",i=>{let l=i.req.param("id");return i.json({alias:Se(l)})}),t.get("/api/config/auto-title",i=>i.json(He())),t.put("/api/config/auto-title",async i=>{let l=await i.req.json().catch(()=>({})),p=$s.partial().safeParse(l);return p.success?i.json(Lc(p.data)):i.json({error:"invalid config",issues:p.error.issues},400)}),t.get("/api/sessions/:id/auto-title",i=>{let l=i.req.param("id"),p=Te(l);return p?i.json(p):i.json({error:"session not found"},404)}),t.post("/api/sessions/:id/auto-title",async i=>{let l=i.req.param("id");if(!He().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);if(!f().prepare("SELECT 1 FROM sessions WHERE id = ?").get(l))return i.json({error:"session not found"},404);try{let E=await Qa(l);return de(l,E,"agent"),i.json(Te(l))}catch(E){return i.json({error:E.message,code:"agent-title-failed"},500)}}),t.post("/api/sessions/:id/auto-title/revert",i=>{let l=i.req.param("id"),p=Te(l);if(!p)return i.json({error:"session not found"},404);let m=p.auto_title_history;if(!m||m.length===0)return i.json({error:"no prior title to revert to",code:"no-history"},422);let _=m[m.length-1];return de(l,_.title,"agent"),i.json(Te(l))}),t.post("/api/sessions/:id/regenerate-title",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({})),m=p.model??Bs;try{let _=await pr(l,{model:m,force:p.force===!0,budget:typeof p.budget=="number"?p.budget:void 0,signal:i.req.raw.signal}),E=Te(l),T=E?.auto_title_history&&E.auto_title_history.length>0?E.auto_title_history[E.auto_title_history.length-1].title:null;return i.json({..._,previous_title:T})}catch(_){if(_ instanceof nt)return i.json({error:_.message,code:"no-context-available",session_id:_.sessionId},422);let E=_ instanceof Error?_.message:"unknown error",T=/not found|unknown/i.test(E)?404:500;return i.json({error:E,code:"regenerate-failed"},T)}}),t.post("/api/sessions/regenerate-titles-batch",async i=>{let l=await i.req.json().catch(()=>null);if(!l)return i.json({error:"body required"},400);let p=l.project;if(typeof p!="string"||p.length===0)return i.json({error:"project (string) required"},400);let m=l.quality_filter;if(!Array.isArray(m)||m.length===0)return i.json({error:"quality_filter (non-empty array) required"},400);let _=new Set(["low_signal","recursive_meta","programmatic"]),E=[];for(let O of m){if(typeof O!="string")return i.json({error:`invalid quality_filter entry: ${O}`},400);if(!_.has(O))return i.json({error:`quality_filter must be a subset of ${[..._].join(",")}; got ${O}`},400);E.push(O)}let T=typeof l.model=="string"&&l.model.length>0?l.model:Bs,R=typeof l.limit=="number"&&l.limit>0?Math.min(2e3,Math.floor(l.limit)):500,A=typeof l.budget=="number"&&l.budget>=100?Math.floor(l.budget):void 0,x=f().prepare(`SELECT s.id,
1740
+ s.auto_title,
1741
+ s.auto_title_source,
1742
+ NULLIF(sa.alias, '') AS alias
1743
+ FROM sessions s
1744
+ JOIN projects p ON p.id = s.project_id
1745
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1746
+ WHERE p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\'
1747
+ ORDER BY COALESCE(s.ended_at, s.started_at, '') DESC
1748
+ LIMIT @limit`).all({proj:`%${Qt(p)}%`,limit:R}),v=new Set(E),C=x.filter(O=>{let F=O.alias==null?null:I.isSessionAutoLinked(O.id)?"auto":"manual",U=jt({auto_title:O.auto_title,auto_title_source:O.auto_title_source??null,has_alias:O.alias!=null&&F==="manual"});return v.has(U)});return Me(i,async O=>{let F=C.length,U=[],Q=[],V=[],W=0,G=async(P,q)=>{W+=1;try{await O.writeSSE({id:String(W),event:P,data:JSON.stringify(q)})}catch{}};await G("start",{total:F,model:T});let K=0;for(let P of C){if(i.req.raw.signal.aborted)break;K+=1;try{let q=await pr(P.id,{model:T,budget:A,signal:i.req.raw.signal});q.written?(U.push(P.id),await G("progress",{sessionId:P.id,title:q.title,evidence:q.evidence,confidence:q.confidence,current:K,total:F})):(Q.push({sessionId:P.id,reason:q.skipped??"unknown"}),await G("skipped",{sessionId:P.id,reason:q.skipped??"unknown",current:K,total:F}))}catch(q){let pe=q instanceof Error?q.message:String(q),Ee=q instanceof nt?"no-context-available":"failed";V.push({sessionId:P.id,error:pe}),await G("error",{sessionId:P.id,error:pe,code:Ee,current:K,total:F})}}await G("done",{generated:U,skipped:Q,failed:V,cancelled:i.req.raw.signal.aborted})})}),t.get("/api/sessions/:id/notes",i=>{let l=i.req.param("id"),p=is(l);return p?i.json(p):i.body(null,204)}),t.put("/api/sessions/:id/notes",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.content!="string")return i.json({error:"content required (string)"},400);try{let m=ci(l,p.content);return i.json(m)}catch(m){return console.error("[notes] failed to save note for session",l,m),i.json({error:"failed to save note"},500)}}),t.post("/api/sessions/:id/generate-note",async i=>{let l=i.req.param("id");if(!He().agentEnabled)return i.json({error:"autoTitle.agentEnabled is false"},403);try{let m=await li(l),_=di(l,m);return i.json(_)}catch(m){let _=m.message,E=/no messages available/i.test(_)?404:500;return i.json({error:_},E)}}),t.get("/api/semantic/status",i=>i.json(qr())),t.put("/api/semantic/config",async i=>{let l=await i.req.json().catch(()=>({})),p=nn.partial().safeParse(l);return p.success?(rn(p.data),i.json(qr())):i.json({error:"invalid semantic config",issues:p.error.issues},400)}),t.get("/api/semantic/config",i=>i.json(ce())),t.post("/api/semantic/backfill",async i=>{if(uo)return i.json({error:"a scan is already running"},409);if(!ce().enabled)return i.json({error:"semantic search is disabled"},400);let p=await i.req.json().catch(()=>({})),m=Math.max(1,Math.min(5e3,Number(p.limit??200)));return uo=!0,cn({limit:m,force:!!p.force}).catch(_=>console.error("[semantic.backfill] error:",_)).finally(()=>{uo=!1}),i.json({ok:!0,limit:m})}),t.post("/api/semantic/sessions/:id",async i=>{if(!ce().enabled)return i.json({error:"semantic search is disabled"},400);let p=i.req.param("id"),m=await an(p);return i.json(m)}),t.get("/api/semantic/vector-status",i=>{let l=ke(),p=Wd(),m=so();return i.json({embedder:l,worker:p,modelInstalled:m})}),t.post("/api/semantic/install",es,async i=>{if(so())return i.json({ok:!0,status:"already_installed"});if(lo)return i.json({error:"a scan is already running"},409);lo=!0;try{return await Kd(),await Ws(),Hd(),i.json({ok:!0,status:"installed"})}catch(l){let p=l instanceof Error?l.message:"unknown error";return i.json({ok:!1,error:p},500)}finally{lo=!1}}),t.get("/api/sessions/:id/similar",es,async i=>{if(!ke().loaded)return i.json({error:"vector model not loaded"},503);let l=i.req.param("id"),p=Math.max(1,Math.min(50,Number(i.req.query("limit")??10)));try{let m=await Md(l,p);return i.json({sessionId:l,similar:m})}catch(m){let _=m instanceof Error?m.message:"unknown error";return i.json({error:_},500)}}),t.get("/api/search",es,async i=>{let l=f(),p=i.req.query("q")?.trim();if(!p)return i.json({query:"",hits:[],tags:[]});if(p.length>500)return i.json({error:"query too long (max 500 chars)"},400);let m=i.req.query("project"),_=p.split(/\s+/).filter(P=>P.length>0),E=_.filter(P=>P.startsWith("#")).map(P=>De(P)).filter(Boolean),T=_.filter(P=>!P.startsWith("#")),R=T.length>20,N=(R?T.slice(0,20):T).map(P=>`"${P.replace(/"/g,"")}"`),x=N.join(" "),v=Math.max(1,Math.min(200,Number(i.req.query("limit")??30)));if(N.length===0&&E.length>0){let P=`
1749
+ SELECT s.id AS session_id,
1750
+ s.id AS message_uuid,
1751
+ p.name AS project,
1752
+ s.started_at,
1753
+ COALESCE(s.first_user_message, '') AS snippet,
1754
+ CAST(NULL AS TEXT) AS role,
1755
+ CAST(NULL AS TEXT) AS timestamp,
1756
+ NULLIF(sa.alias, '') AS alias
1757
+ FROM sessions s
1758
+ JOIN projects p ON p.id = s.project_id
1759
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1760
+ WHERE 1=1
1761
+ `,q={limit:v};m&&(P+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",q.proj=`%${Qt(m)}%`),E.forEach((Ee,bt)=>{P+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${bt})`,q[`tag_${bt}`]=Ee}),P+=" ORDER BY COALESCE(s.started_at, '') DESC LIMIT @limit";let pe=l.prepare(P).all(q);return i.json({query:p,hits:pe,tags:E,truncated:R})}let C=`
1762
+ SELECT m.session_id AS session_id,
1763
+ m.uuid AS message_uuid,
1764
+ p.name AS project,
1765
+ s.started_at,
1766
+ snippet(messages_fts, 0, '<<', '>>', '\u2026', 20) AS snippet,
1767
+ m.role,
1768
+ m.timestamp,
1769
+ NULLIF(sa.alias, '') AS alias
1770
+ FROM messages_fts
1771
+ JOIN messages m ON m.rowid = messages_fts.rowid
1772
+ JOIN sessions s ON s.id = m.session_id
1773
+ JOIN projects p ON p.id = s.project_id
1774
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1775
+ WHERE messages_fts MATCH @fts
1776
+ `,O={fts:x,limit:v};m&&(C+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",O.proj=`%${Qt(m)}%`),E.forEach((P,q)=>{C+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${q})`,O[`tag_${q}`]=P}),C+=" ORDER BY bm25(messages_fts) LIMIT @limit";let U=l.prepare(C).all(O).map(P=>({...P,matched_via:"fts"}));if(i.req.query("mode")!=="semantic")return i.json({query:p,hits:U,tags:E,truncated:R});let V=[];try{let P=`
1777
+ SELECT s.id AS session_id,
1778
+ s.id AS message_uuid,
1779
+ p.name AS project,
1780
+ s.started_at,
1781
+ COALESCE(NULLIF(ss.summary, ''), s.first_user_message, '') AS snippet,
1782
+ CAST(NULL AS TEXT) AS role,
1783
+ CAST(NULL AS TEXT) AS timestamp,
1784
+ NULLIF(sa.alias, '') AS alias,
1785
+ bm25(sessions_fts) AS rank
1786
+ FROM sessions_fts
1787
+ JOIN session_semantic ss ON ss.rowid = sessions_fts.rowid
1788
+ JOIN sessions s ON s.id = ss.session_id
1789
+ JOIN projects p ON p.id = s.project_id
1790
+ LEFT JOIN session_aliases sa ON sa.session_id = s.id
1791
+ WHERE sessions_fts MATCH @fts
1792
+ `,q={fts:x,limit:v};m&&(P+=" AND (p.name LIKE @proj ESCAPE '\\' OR p.decoded_path LIKE @proj ESCAPE '\\')",q.proj=`%${Qt(m)}%`),E.forEach((pe,Ee)=>{P+=` AND s.id IN (SELECT session_id FROM session_tags WHERE tag = @tag_${Ee})`,q[`tag_${Ee}`]=pe}),P+=" ORDER BY rank LIMIT @limit",V=l.prepare(P).all(q)}catch(P){console.error("[search.semantic] failed:",P)}if(ke().loaded)try{let P=await jd(p,v),q=U.map(z=>({id:String(z.session_id),data:z,lane:"bm25"})),pe=V.map(z=>({id:String(z.session_id),data:z,lane:"summary"})),Ee=P.map(z=>({id:z.sessionId,data:{session_id:z.sessionId,snippet:z.text,matched_via:"vector"},lane:"vector"})),ts=Dd([q,pe,Ee]).slice(0,v).map(z=>({...z.data,session_id:z.id,rrf_score:z.score,lanes:z.lanes,matched_via:z.lanes.length>1?"fused":z.lanes[0]}));return i.json({query:p,hits:ts,tags:E,mode:"semantic",fusion:"rrf",truncated:R})}catch(P){console.error("[search.vector] failed, falling back:",P)}let W=new Set(U.map(P=>String(P.session_id))),G=V.filter(P=>!W.has(String(P.session_id))).map(({rank:P,...q})=>({...q,matched_via:"semantic"})),K=[...U,...G].slice(0,v);return i.json({query:p,hits:K,tags:E,mode:"semantic",truncated:R})}),t.get("/api/sessions/:id/context",es,i=>{let l=f(),p=i.req.param("id"),m=i.req.query("mode")==="full"?"full":"condensed",_=i.req.query("subagents")==="1",E=i.req.query("prelude")??null,T=l.prepare(`SELECT s.id, p.name AS project_name, p.decoded_path,
1793
+ s.started_at, s.ended_at, s.message_count, s.git_branch
1794
+ FROM sessions s JOIN projects p ON p.id = s.project_id
1795
+ WHERE s.id = ?`).get(p);if(!T)return i.json({error:"not found"},404);let R=l.prepare(`SELECT uuid, type, role, timestamp, is_sidechain, content_text, tool_names
1796
+ FROM messages
1797
+ WHERE session_id = ?
1798
+ ORDER BY COALESCE(timestamp, ''), rowid`).all(p),A=zo(T,R,{mode:m,includeSidechain:_,prelude:E});return i.text(A)}),t.get("/api/collections",i=>{let l=i.req.query("archived")==="1";return i.json({collections:pi(l)})}),t.get("/api/collections/:id",i=>{let l=i.req.param("id"),p=xe(l);if(!p)return i.json({error:"not found"},404);let m=mi(l,!0);return i.json({collection:p,members:m})}),t.post("/api/collections",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.name!="string")return i.json({error:"name required"},400);try{let p=At({name:l.name,description:l.description??null,icon:l.icon??null,color:l.color??null,parent_id:l.parent_id??null,sort_key:l.sort_key});return i.json(p,201)}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/collections/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);try{let m=_i(l,p);return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.post("/api/collections/:id/archive",i=>{let l=i.req.param("id");try{let p=fi(l);return i.json(p)}catch(p){return i.json({error:p.message},404)}}),t.post("/api/collections/:id/restore",i=>{let l=i.req.param("id");try{let p=hi(l);return i.json(p)}catch(p){return i.json({error:p.message},404)}}),t.post("/api/collections/:id/members",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p||typeof p.session_id!="string")return i.json({error:"session_id required"},400);try{let m=Nt(l,p.session_id,p.note??null);return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.delete("/api/collections/:id/members/:sid",i=>{let l=i.req.param("id"),p=i.req.param("sid");try{let m=Ei(l,p);return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.get("/api/sessions/:id/collections",i=>{let l=i.req.param("id");return i.json({collections:gi(l)})});let w=["cwd-prefix","project-id","tag","plan-file","git-branch-prefix"];t.get("/api/auto-collections/rules",i=>i.json({rules:wi()})),t.post("/api/auto-collections/rules",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.name!="string"||typeof l.pattern!="string"||!l.type||!w.includes(l.type))return i.json({error:"name, type, pattern required (type must be a known matcher)"},400);try{let p=Fn({name:l.name,type:l.type,pattern:l.pattern,collection_id:l.collection_id,parent_collection_id:l.parent_collection_id,priority:l.priority,enabled:l.enabled});return i.json(p,201)}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/auto-collections/rules/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(!p)return i.json({error:"body required"},400);try{let m=Ri(l,p);return i.json(m)}catch(m){return i.json({error:m.message},400)}}),t.delete("/api/auto-collections/rules/:id",i=>{let l=i.req.param("id");try{let p=ki(l);return i.json(p)}catch(p){return i.json({error:p.message},400)}}),t.get("/api/auto-collections/suggestions",i=>{let l=i.req.query("dismissed")==="1";return i.json({suggestions:gs({includeDismissed:l})})}),t.post("/api/auto-collections/suggestions/:id/accept",i=>{let l=i.req.param("id");try{let p=Ni(l);return i.json({rule:p})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/auto-collections/suggestions/:id/dismiss",i=>{let l=i.req.param("id");try{return Ai(l),i.json({ok:!0})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/auto-collections/detect",i=>{let l=_s();return i.json({suggestions:l})}),t.get("/api/auto-collections/suggestions/:id/preview",i=>{let l=i.req.param("id"),p=Math.max(1,Math.min(20,Number(i.req.query("limit"))||3)),_=gs({includeDismissed:!1}).find(T=>T.id===l);if(!_)return i.json({error:"suggestion not found"},404);let E=Ti(_.type,_.pattern,p);return i.json({sessions:E})}),t.get("/api/auto-collections/parents",i=>{let l=Array.from(xi());return i.json({auto_collection_ids:l})}),t.get("/api/threads",i=>{let l=i.req.query("archived")==="1";return i.json({threads:Bn({includeArchived:l})})}),t.get("/api/threads/:id",i=>{let l=i.req.param("id"),p=te(l);if(!p)return i.json({error:"thread not found"},404);let m=p.edges.map(_=>({..._,alias_source:_.alias==null?null:I.isSessionAutoLinked(_.session_id)?"auto":"manual"}));return i.json({thread:{...p,edges:m}})}),t.post("/api/threads",async i=>{let l=await i.req.json().catch(()=>({}));if(!l.name)return i.json({error:"name required"},400);try{let p=hs({name:l.name,summary:l.summary??null,originSessionId:l.originSessionId});return i.json({thread:p})}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/threads/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));try{p.name&&Pi(l,p.name),p.close&&Ui(l),p.reopen&&$i(l),p.archive&&Bi(l),"folder_id"in p&&aa(l,p.folder_id??null);let m=te(l);return m?i.json({thread:m}):i.json({error:"thread not found"},404)}catch(m){return i.json({error:m.message},400)}}),t.get("/api/thread-folders",i=>i.json({folders:Jn()})),t.post("/api/thread-folders",async i=>{let l=await i.req.json().catch(()=>({}));if(!l.name||typeof l.name!="string")return i.json({error:"name required"},400);try{let p=ta({name:l.name,parentFolderId:l.parent_folder_id??null,projectScope:l.project_scope??null});return i.json({folder:p})}catch(p){return i.json({error:p.message},400)}}),t.patch("/api/thread-folders/:id",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));try{let m;return p.name&&(m=na(l,p.name)),"parent_folder_id"in p&&(m=ra(l,p.parent_folder_id??null)),m?i.json({folder:m}):i.json({error:"no patch fields"},400)}catch(m){return i.json({error:m.message},400)}}),t.delete("/api/thread-folders/:id",i=>{let l=i.req.param("id");try{return ia(l),i.json({ok:!0})}catch(p){return i.json({error:p.message},400)}}),t.post("/api/thread-folders/reorder",async i=>{let l=await i.req.json().catch(()=>({})),p=l.ordered_ids;if(!Array.isArray(p))return i.json({error:"ordered_ids must be an array"},400);try{return oa(l.parent_folder_id??null,l.project_scope??null,p),i.json({ok:!0})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/threads/:id/sessions",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sessionId)return i.json({error:"sessionId required"},400);try{let m=Es({threadId:l,sessionId:p.sessionId,parentSessionId:p.parentSessionId??null,role:p.role});return i.json({edge:m})}catch(m){return i.json({error:m.message},400)}}),t.delete("/api/threads/:id/sessions/:sessionId",i=>{let l=i.req.param("id"),p=i.req.param("sessionId"),m=Fi(l,p);return i.json(m)}),t.patch("/api/threads/:id/sessions/:sessionId",async i=>{let l=i.req.param("id"),p=i.req.param("sessionId"),m=await i.req.json().catch(()=>({}));try{let _=xt(l,p,m.parentSessionId??null);return i.json({edge:_})}catch(_){return i.json({error:_.message},400)}}),t.post("/api/threads/:id/merge",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sourceId)return i.json({error:"sourceId required"},400);try{let m=Hi(p.sourceId,l);return i.json({thread:m})}catch(m){return i.json({error:m.message},400)}}),t.post("/api/threads/:id/split",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({}));if(!p.sessionIds?.length||!p.newThreadName)return i.json({error:"sessionIds and newThreadName required"},400);try{let m=Wi({threadId:l,sessionIds:p.sessionIds,newThreadName:p.newThreadName});return i.json({thread:m})}catch(m){return i.json({error:m.message},400)}}),t.get("/api/sessions/:id/threads",i=>{let l=i.req.param("id");return i.json({threads:Di(l)})});let D=j.object({enabled:j.boolean(),band_lo:j.number().min(0).max(1).optional(),band_hi:j.number().min(0).max(1).optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional()}).optional(),L=j.object({project:j.string().min(1),threshold:j.number().min(0).max(1).optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:D});t.post("/api/threads/scan/preflight",async i=>{let l=ye(i);if(l)return l;let p=await i.req.json().catch(()=>null),m=L.safeParse(p);if(!m.success)return i.json({error:"invalid request body",details:m.error.format()},400);let _=Fl({project:m.data.project,threshold:m.data.threshold,model:m.data.model,llm_rescore:m.data.llm_rescore});return"error"in _?i.json({error:_.error},400):i.json(_)});let X=j.object({project:j.string().min(1),threshold:j.number().min(0).max(1).optional(),llm_names:j.boolean().optional(),model:j.string().regex(/^[a-zA-Z0-9._-]{1,100}$/).optional(),llm_rescore:D});t.post("/api/threads/scan/apply",async i=>{let l=ye(i);if(l)return l;let p=await i.req.json().catch(()=>null),m=X.safeParse(p);if(!m.success)return i.json({error:"invalid request body",details:m.error.format()},400);let _=Ul({project:m.data.project,threshold:m.data.threshold,llm_names:m.data.llm_names,model:m.data.model,llm_rescore:m.data.llm_rescore});return"error"in _?i.json({error:_.error},400):i.json({jobId:_.jobId,reused:_.reused},_.reused?409:200)}),t.get("/api/threads/scan/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!Fr(l))return i.json({error:"job not found"},404);let m=Number(i.req.header("Last-Event-ID")??0);return Me(i,async _=>{let E=!1,T=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let R of $l(l,m))if(E||(await _.writeSSE({id:String(R.id),event:R.kind,data:JSON.stringify(R.data)}),R.kind==="done"))break}finally{E=!0,clearInterval(T)}})}),t.get("/api/threads/scan/jobs/:jobId",i=>{let l=Fr(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/threads/scan/jobs/:jobId",i=>{let l=ye(i);return l||(Bl(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404))});let M=j.object({project_id:j.number().int().positive(),mode:j.enum(["preflight","apply"]),window_hours:j.number().min(.5).max(168).optional(),score_threshold:j.number().min(0).max(1).optional(),use_live_pids:j.boolean().optional()});t.post("/api/threads/sync-active",async i=>{let l=await i.req.json().catch(()=>null),p=M.safeParse(l);if(!p.success)return i.json({error:"invalid request body",details:p.error.format()},400);try{let m=await Zi(p.data.project_id,{windowHours:p.data.window_hours,scoreThreshold:p.data.score_threshold,useLivePids:p.data.use_live_pids});if(p.data.mode==="preflight")return i.json({plan:m});let _=Qi(m);return i.json({plan:m,result:_})}catch(m){return i.json({error:m.message},400)}}),t.get("/api/threads/:id/titles/preflight",i=>{let l=i.req.param("id"),p=te(l);if(!p)return i.json({error:"thread not found"},404);let m=f(),_=0;for(let E of p.edges)m.prepare("SELECT auto_title_source FROM sessions WHERE id = ?").get(E.session_id)?.auto_title_source==="agent"&&(_+=1);return i.json({total:p.edges.length,alreadyTitled:_,untitled:p.edges.length-_})}),t.post("/api/threads/:id/titles/generate",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>({})),m=te(l);if(!m)return i.json({error:"thread not found"},404);if(m.edges.length===0)return i.json({error:"thread has no sessions"},400);let _=Re(),E=p.model??_.model,T=ac({threadId:l,force:p.force??!1,model:E});return i.json({jobId:T})}),t.get("/api/jobs/:jobId/stream",i=>{let l=i.req.param("jobId");if(!ir(l))return i.json({error:"job not found"},404);let m=Number(i.req.header("Last-Event-ID")??0);return Me(i,async _=>{let E=!1,T=setInterval(()=>{E||_.writeSSE({event:"heartbeat",data:""}).catch(()=>{E=!0})},15e3);try{for await(let R of cc(l,m))if(E||(await _.writeSSE({id:String(R.id),event:R.kind,data:JSON.stringify(R.data)}),R.kind==="done"))break}finally{E=!0,clearInterval(T)}})}),t.get("/api/jobs/:jobId",i=>{let l=ir(i.req.param("jobId"));return l?i.json(l):i.json({error:"job not found"},404)}),t.delete("/api/jobs/:jobId",i=>lc(i.req.param("jobId"))?i.json({ok:!0}):i.json({error:"job not found or already done"},404)),t.post("/api/terminal/opened",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.tab_name!="string")return i.json({error:"shell_pid and tab_name required"},400);let p=I.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(p==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance",count:I.size()});let m=I.upsert({shell_pid:l.shell_pid,tab_name:l.tab_name,cwd:l.cwd??null,opened_at:l.opened_at??new Date().toISOString()});return i.json({ok:!0,ownership:p,count:I.size(),entry:m})}),t.post("/api/terminal/renamed",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.tab_name!="string")return i.json({error:"shell_pid and tab_name required"},400);let p=I.claimPidOwnership(l.shell_pid,l.extension_instance_id??null);if(p==="rejected")return i.json({ok:!0,rejected:!0,reason:"pid-owned-by-other-extension-instance"});let m=I.rename(l.shell_pid,l.tab_name);if(!m)return i.json({error:"unknown shell_pid"},404);let _=nu(l.shell_pid,l.tab_name);return i.json({ok:!0,ownership:p,entry:m,propagated:_})}),t.post("/api/terminal/closed",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number")return i.json({error:"shell_pid required"},400);let p=I.remove(l.shell_pid);return i.json({ok:!0,removed:p,count:I.size()})}),t.post("/api/terminal/claude-started",async i=>{let l=await i.req.json().catch(()=>null);return!l||typeof l.shell_pid!="number"?i.json({error:"shell_pid required"},400):(I.pushPending({shell_pid:l.shell_pid,tab_name:typeof l.tab_name=="string"?l.tab_name:"",cwd:typeof l.cwd=="string"?l.cwd:null,started_at:typeof l.started_at=="string"?l.started_at:new Date().toISOString()}),i.json({ok:!0,pending:I.pendingSize()}))}),t.post("/api/terminal/output",async i=>{let l=await i.req.json().catch(()=>null);if(!l||typeof l.shell_pid!="number"||typeof l.text!="string")return i.json({error:"shell_pid and text required"},400);let p=l.text.length>8192?l.text.slice(-8192):l.text,m=typeof l.captured_at=="string"?l.captured_at:new Date().toISOString();return I.setOutputTail(l.shell_pid,p,m),i.json({ok:!0})}),t.post("/api/terminal/sync",async i=>{let l=await i.req.json().catch(()=>null);if(!l||!Array.isArray(l.terminals))return i.json({error:"terminals array required"},400);let p=new Map;for(let O of I.all())p.set(O.shell_pid,O.tab_name);let m=l.terminals.filter(O=>!!O&&typeof O.shell_pid=="number"&&typeof O.tab_name=="string").map(O=>({shell_pid:O.shell_pid,tab_name:O.tab_name,cwd:O.cwd??null,opened_at:O.opened_at??new Date().toISOString()})),_=l.extension_instance_id??null,E=[],T=m.filter(O=>{let F=I.claimPidOwnership(O.shell_pid,_);return E.push({shell_pid:O.shell_pid,ownership:F}),F!=="rejected"}),R=I.sync(T),A=0;for(let O of T){let F=p.get(O.shell_pid),U=I.get(O.shell_pid)?.tab_name??O.tab_name,V=!!U&&!re(U)&&!ne(U)?U:O.tab_name;F!==void 0&&F!==V&&(A+=nu(O.shell_pid,V))}let N=E.filter(O=>O.ownership==="rejected").length;N>0&&console.log(`[terminal/sync] dropped ${N} tab_name update(s), pid(s) owned by a different extension instance`);let x=await zb(),v={resolved:0,expired:0};try{v=Ec()}catch{}let C={rebound:0,ghosts:0,ambiguous:0};try{C=hc()}catch{}return cS(),i.json({ok:!0,count:I.size(),diff:R,propagated:A,live_sweep:x,deferred_resolved:v,rebound:C})}),t.get("/api/terminal/registry",i=>i.json({terminals:I.all(),count:I.size()})),t.get("/api/terminal/sessions/:shellPid",i=>{let l=i.req.param("shellPid"),p=Number(l);if(!Number.isInteger(p)||p<=0)return i.json({error:"shellPid must be a positive integer"},400);let m=I.sessionsFor(p);return i.json({shell_pid:p,sessions:m})}),t.get("/api/sessions/:id/linked-terminal",i=>{let l=i.req.param("id"),p=I.all().find(_=>I.sessionsFor(_.shell_pid).includes(l)),m=[];if(!p){let _=f().prepare("SELECT cwd, started_at FROM sessions WHERE id = ?").get(l);if(_?.cwd&&_.started_at){let E=Date.parse(_.started_at);if(Number.isFinite(E)){let T=_.cwd.replace(/\/+$/,""),R=300*1e3;for(let A of I.all()){if(!A.cwd||A.cwd.replace(/\/+$/,"")!==T||re(A.tab_name))continue;let N=Date.parse(A.opened_at),x=Date.parse(A.last_seen_at);!Number.isFinite(N)||!Number.isFinite(x)||N>E||x+R<E||m.push({shell_pid:A.shell_pid,tab_name:A.tab_name,cwd:A.cwd,opened_at:A.opened_at,last_seen_at:A.last_seen_at,reason:"time-overlap"})}m.sort((A,N)=>Date.parse(N.last_seen_at)-Date.parse(A.last_seen_at))}}}return p?i.json({linked:{shell_pid:p.shell_pid,tab_name:p.tab_name,cwd:p.cwd},suggested:[]}):i.json({linked:null,suggested:m})}),t.post("/api/sessions/:id/auto-relink",async i=>{let l=i.req.param("id");if(Se(l))return i.json({applied:!1,reason:"has-alias"});if(I.all().some(T=>I.sessionsFor(T.shell_pid).includes(l)))return i.json({applied:!1,reason:"already-linked"});let m=f().prepare("SELECT cwd, git_branch, started_at FROM sessions WHERE id = ?").get(l);if(!m?.cwd)return i.json({applied:!1,reason:"no-cwd"});let _=m.cwd.replace(/\/+$/,""),E=I.all().filter(T=>T.cwd&&T.cwd.replace(/\/+$/,"")===_&&!re(T.tab_name));if(E.length===1){let T=E[0],R=yt({sessionStartedAt:m.started_at??null,terminalOpenedAt:T.opened_at??null});if(!R.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:R.reason});let A=I.getOrigin(l),N=st({tabName:T.tab_name,origin:A??null,cwd:m.cwd??null,gitBranch:m.git_branch??null});return N?(me(l,N),I.linkSession(l,T.shell_pid),i.json({applied:!0,alias:N,linked_pid:T.shell_pid,linked_tab_name:T.tab_name,method:"cwd-singleton"})):i.json({applied:!1,reason:"no-usable-name"})}if(E.length>1){let T=await fc(l);if(T){let A=I.get(T.shell_pid),N=yt({sessionStartedAt:m.started_at??null,terminalOpenedAt:A?.opened_at??null});if(!N.allowed)return i.json({applied:!1,reason:"temporal-guard",guard_reason:N.reason});let x=I.getOrigin(l),v=st({tabName:T.tab_name,origin:x??null,cwd:m.cwd??null,gitBranch:m.git_branch??null});if(v)return me(l,v),I.linkSession(l,T.shell_pid),i.json({applied:!0,alias:v,linked_pid:T.shell_pid,linked_tab_name:T.tab_name,matched_fingerprints:T.matched_fingerprints,method:"content-match"})}let R=6e4;if(m.started_at){let A=Date.parse(m.started_at);if(Number.isFinite(A)){let N=E.filter(v=>yt({sessionStartedAt:m.started_at,terminalOpenedAt:v.opened_at??null}).allowed).map(v=>({t:v,gap:A-Date.parse(v.opened_at??"")})).filter(v=>Number.isFinite(v.gap)&&v.gap>=0&&v.gap<=R);if(N.length>=2)return i.json({applied:!1,reason:"ambiguous-temporal",candidate_count:N.length});let x=N[0];if(x){let v=I.getOrigin(l),C=st({tabName:x.t.tab_name,origin:v??null,cwd:m.cwd??null,gitBranch:m.git_branch??null});if(C)return me(l,C),I.linkSession(l,x.t.shell_pid),i.json({applied:!0,alias:C,linked_pid:x.t.shell_pid,linked_tab_name:x.t.tab_name,method:"closest-before-temporal",gap_ms:x.gap})}}}return i.json({applied:!1,reason:"ambiguous",candidate_count:E.length})}return i.json({applied:!1,reason:"no-candidates"})}),t.post("/api/sessions/:id/relink",async i=>{let l=i.req.param("id"),p=await i.req.json().catch(()=>null);if(p?.clear)return I.unlinkSession(l),ns(l),i.json({ok:!0,alias:null,linked_pid:null});if(!p||typeof p.shell_pid!="number")return i.json({error:"shell_pid required"},400);let m=I.get(p.shell_pid);if(!m)return i.json({error:"terminal not registered"},404);let _=I.getOrigin(l),E=f().prepare("SELECT cwd, git_branch FROM sessions WHERE id = ?").get(l),T=null,R=m.tab_name?.trim()??"";if(R&&!re(R)&&!ne(R))T=R;else if(R&&ne(R)){let A=tt(R);A&&!re(A)&&(T=A)}return T?(I.unlinkSession(l),me(l,T),i.json({ok:!0,alias:T,linked_pid:p.shell_pid,linked_tab_name:m.tab_name})):i.json({error:"terminal has no usable name, name the tab in your editor first, then retry the relink"},422)}),t.post("/api/sessions/:id/recorrelate",async i=>{let l=i.req.param("id"),p=f().prepare("SELECT file_path FROM sessions WHERE id = ?").get(l);if(!p?.file_path)return i.json({error:"session not found"},404);I.unlinkSession(l),ns(l),await Ms(p.file_path);let m=Se(l);return i.json({ok:!0,alias:m,linked_pid:I.all().find(_=>I.sessionsFor(_.shell_pid).includes(l))?.shell_pid??null})}),t.get("/api/paste-expand",async i=>{let l=i.req.query("session"),p=i.req.query("message"),m=i.req.query("path");if(!l||!p||!m)return i.json({error:"session, message and path are required"},400);let _=f(),E=_.prepare("SELECT rowid, content_text FROM messages WHERE uuid = ? AND session_id = ?").get(p,l);if(!E)return i.json({error:"message not found in session"},404);let T=m.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");if(!new RegExp(`\\[Pasted text #\\d+ \\+\\d+ lines\\]\\s*${T}`).test(E.content_text??""))return i.json({error:"path not referenced by this message"},403);let A=_.prepare(`SELECT content_text FROM messages
1799
+ WHERE session_id = ? AND rowid > ?
1800
+ ORDER BY rowid ASC LIMIT 10`).all(l,E.rowid);for(let N of A){let x=N.content_text??"";if(x.includes("**Tool result**")&&x.includes(m))return i.json({source:"tool-result",content:x});if(/^\s*1\t/.test(x)&&x.length>200)return i.json({source:"tool-result",content:x})}try{let N=await Xb(m),x=Yb();if(!N.startsWith(x+"/")&&!N.startsWith(x+"\\"))return i.json({error:"path outside allowed root"},403);let v=[".ssh",".gnupg",".gpg",".aws",".kube",".docker",".password-store"],O=N.slice(x.length+1).split("/")[0].split("\\")[0];if(v.includes(O))return i.json({error:"path inside sensitive directory"},403);let F=await qb(N),U=2*1024*1024;if(F.size>U)return i.json({error:"file too large",size:F.size,max:U},413);let Q=await Jb(N,"utf8");return i.json({source:"disk",content:Q})}catch(N){return i.json({source:"missing",error:N.message})}}),t.get("/api/projects/:name/stats",i=>{let l=f(),p=i.req.param("name"),m=l.prepare(`SELECT
1801
+ (SELECT COUNT(*) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=? AND s.message_count > 2) AS sessions,
1802
+ (SELECT COALESCE(SUM(s.message_count), 0) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=?) AS messages,
1803
+ (SELECT MIN(s.started_at) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE p.name=? AND s.started_at IS NOT NULL) AS earliest,
1804
+ (SELECT MAX(COALESCE(s.ended_at, s.started_at)) FROM sessions s JOIN projects p ON p.id=s.project_id WHERE (s.ended_at IS NOT NULL OR s.started_at IS NOT NULL) AND p.name=?) AS latest`).get(p,p,p,p),_=l.prepare(`SELECT DISTINCT s.git_branch FROM sessions s
1805
+ JOIN projects p ON p.id = s.project_id
1806
+ WHERE p.name = ? AND s.git_branch IS NOT NULL
1807
+ ORDER BY s.git_branch
1808
+ LIMIT 20`).all(p).map(E=>E.git_branch);return i.json({...m,branches:_})});function B(i){return i.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function se(i){if(!e)return i;let l=`<meta name="recall-token" content="${B(e)}" />`,p=i.indexOf("</head>");return p!==-1?i.slice(0,p)+l+i.slice(p):l+i}return nS?(t.use("/assets/*",su({root:mo})),t.get("/favicon.svg",su({root:mo})),t.get("/",i=>(i.header("cache-control","no-cache, no-store, must-revalidate"),i.header("pragma","no-cache"),i.header("expires","0"),i.html(se(ro(go,"utf8"))))),t.get("*",i=>i.req.path.startsWith("/api/")?i.notFound():(i.header("cache-control","no-cache, no-store, must-revalidate"),i.header("pragma","no-cache"),i.header("expires","0"),i.html(se(ro(go,"utf8")))))):t.get("/",i=>{let l=ou();return i.html(Ko({projects:l.projects,sessions:l.sessions,messages:l.messages,port:Number(i.req.raw.headers.get("host")?.split(":")[1]??0),version:ru}))}),t}function uS(){if(Qs(),!!He().heuristicEnabled){try{let{updated:e}=ec();e>0&&console.log(`[auto-title] backfilled heuristic title on ${e} sessions`)}catch(e){console.error("[auto-title] backfill failed:",e)}try{let{scanned:e,updated:t}=tc();t>0&&console.log(`[auto-title] refreshed templated heuristic title on ${t}/${e} sessions`)}catch(e){console.error("[auto-title] templated-title refresh failed:",e)}try{let{scanned:e,updated:t}=sc();t>0&&console.log(`[auto-title] refreshed recursive_meta title on ${t}/${e} sessions`)}catch(e){console.error("[auto-title] recursive_meta refresh failed:",e)}try{let{scanned:e,updated:t}=nc();t>0&&console.log(`[auto-title] canonicalized brand on ${t}/${e} session titles`)}catch(e){console.error("[auto-title] brand canonicalization failed:",e)}}}async function uu(e,t){let s=dS(t);return new Promise((n,r)=>{try{let o=Hb({fetch:s.fetch,port:e,hostname:"127.0.0.1"},()=>{n(o),setImmediate(()=>{try{uS()}catch(a){console.error("[daemon] startup maintenance crashed:",a)}})})}catch(o){r(o)}})}Z();H();import{watch as pS}from"chokidar";import{statSync as mu}from"node:fs";import{basename as mS}from"node:path";var gS=1500,fo=new Map;function _S(e){let t=e.split("/"),s=t.findIndex(n=>n==="projects");return s===-1||s+1>=t.length?null:t[s+1]??null}async function fS(e){let t=0;try{t=mu(e).mtimeMs}catch{return}let s=_S(e);if(!s)return;let n=f(),r=n.prepare("SELECT id, file_mtime FROM sessions WHERE file_path = ? LIMIT 1").get(e);if(r&&r.file_mtime>=t)return;let o=new Map,a=null;for await(let k of fd(e)){let w=o.get(k.sessionId);if(w||(w={sessionId:k.sessionId,entries:[],earliest:null,latest:null,firstUser:null,users:0,assistants:0,cwd:null,branch:null,version:null},o.set(k.sessionId,w)),w.entries.push(k),k.timestamp&&((!w.earliest||k.timestamp<w.earliest)&&(w.earliest=k.timestamp),(!w.latest||k.timestamp>w.latest)&&(w.latest=k.timestamp)),k.role==="user"&&!k.isSidechain){if(w.users+=1,!w.firstUser&&k.contentText){let D=k.contentText.replace(/^(?:<[^>]+>[\s\S]*?<\/[^>]+>\s*)+/,"").trim();D&&!/^<local-command-caveat>/.test(D)&&(w.firstUser=qe(D).redacted.slice(0,2e3))}}else k.role==="assistant"&&!k.isSidechain&&(w.assistants+=1);!w.cwd&&k.cwd&&(w.cwd=k.cwd),!w.branch&&k.gitBranch&&(w.branch=k.gitBranch),!w.version&&k.version&&(w.version=k.version),!a&&k.cwd&&(a=k.cwd)}let c=a?mS(a)||a:s,d=a??s.replace(/^-/,"/").replace(/-/g,"/"),u=n.prepare(`INSERT INTO projects (encoded_path, decoded_path, name)
1809
+ VALUES (?, ?, ?)
1810
+ ON CONFLICT(encoded_path) DO UPDATE SET decoded_path = excluded.decoded_path, name = excluded.name
1811
+ RETURNING id`),g=n.prepare(`
1812
+ INSERT INTO sessions (
1813
+ id, project_id, file_path, file_mtime,
1814
+ started_at, ended_at, message_count,
1815
+ user_message_count, assistant_message_count,
1816
+ first_user_message, cwd, git_branch, version, indexed_at
1817
+ ) VALUES (@id, @project_id, @file_path, @file_mtime, @started_at, @ended_at, @message_count,
1818
+ @user_message_count, @assistant_message_count, @first_user_message,
1819
+ @cwd, @git_branch, @version, @indexed_at)
1820
+ ON CONFLICT(id) DO UPDATE SET
1821
+ file_path = excluded.file_path,
1822
+ file_mtime = excluded.file_mtime,
1823
+ started_at = excluded.started_at,
1824
+ ended_at = excluded.ended_at,
1825
+ message_count = excluded.message_count,
1826
+ user_message_count = excluded.user_message_count,
1827
+ assistant_message_count = excluded.assistant_message_count,
1828
+ first_user_message = excluded.first_user_message,
1829
+ cwd = excluded.cwd,
1830
+ git_branch = excluded.git_branch,
1831
+ version = excluded.version,
1832
+ indexed_at = excluded.indexed_at
1833
+ `),h=n.prepare(`
1834
+ INSERT INTO messages (uuid, session_id, parent_uuid, type, role, timestamp,
1835
+ is_sidechain, content_text, tool_names, raw_json)
1836
+ VALUES (@uuid, @session_id, @parent_uuid, @type, @role, @timestamp,
1837
+ @is_sidechain, @content_text, @tool_names, @raw_json)
1838
+ ON CONFLICT(uuid) DO NOTHING
1839
+ `),b=n.prepare("DELETE FROM messages WHERE session_id = ?"),S=new Date().toISOString();if(n.transaction(()=>{let{id:k}=u.get(s,d,c);for(let w of o.values()){g.run({id:w.sessionId,project_id:k,file_path:e,file_mtime:t,started_at:w.earliest,ended_at:w.latest,message_count:w.entries.length,user_message_count:w.users,assistant_message_count:w.assistants,first_user_message:w.firstUser,cwd:w.cwd,git_branch:w.branch,version:w.version,indexed_at:S}),b.run(w.sessionId);for(let D of w.entries)h.run(hS(D));hd(n,w.sessionId,w.entries),dn(n,w.sessionId)}})(),He().heuristicEnabled)for(let k of o.values()){let w=et(k.firstUser);w&&de(k.sessionId,w,"heuristic")}}function hS(e){let t=qe(e.contentText).redacted,s=qe(e.raw).redacted;return{uuid:e.uuid,session_id:e.sessionId,parent_uuid:e.parentUuid,type:e.type,role:e.role,timestamp:e.timestamp,is_sidechain:e.isSidechain?1:0,content_text:t,tool_names:e.toolNames.join(","),raw_json:s}}function pu(e){let t=fo.get(e);t?.timer&&clearTimeout(t.timer);let s={timer:null};s.timer=setTimeout(()=>{fo.delete(e),fS(e).then(async()=>{Ms(e);try{let r=f().prepare("SELECT id FROM sessions WHERE file_path = ?").all(e);for(let o of r){gd(o.id),Cd(o.id);try{yi(o.id)}catch(a){console.error("[watcher] auto-collections apply failed:",a)}}}catch(n){console.error("[watcher] semantic dispatch failed:",n)}}).catch(n=>{console.error(`[watcher] reindex failed for ${e}:`,n)})},gS),fo.set(e,s)}function gu(){let e=pS(Co,{depth:4,ignoreInitial:!0,persistent:!0,awaitWriteFinish:{stabilityThreshold:500,pollInterval:200},ignored:t=>{if(t.endsWith(".jsonl"))return!1;try{if(mu(t).isDirectory())return!1}catch{}return!0}});return e.on("add",t=>t.endsWith(".jsonl")&&pu(t)),e.on("change",t=>t.endsWith(".jsonl")&&pu(t)),e}import{createServer as fu}from"node:net";function _u(e){return new Promise(t=>{let s=fu();s.once("error",()=>t(!1)),s.once("listening",()=>{s.close(()=>t(!0))}),s.listen(e,"127.0.0.1")})}async function hu(){let e=new Set([3e3,3001,4200,5e3,5173,8e3,8080,8888,9e3]),t=51370;if(!e.has(t)&&await _u(t))return t;for(let s=0;s<50;s++){let n=49152+Math.floor(Math.random()*16383);if(!e.has(n)&&await _u(n))return n}return new Promise((s,n)=>{let r=fu();r.once("error",n),r.listen(0,"127.0.0.1",()=>{let o=r.address();if(o&&typeof o=="object"){let a=o.port;r.close(()=>s(a))}else r.close(),n(new Error("could not determine a free port"))})})}Z();import{existsSync as TS,readFileSync as Qx,writeFileSync as bu,unlinkSync as yS}from"node:fs";import{join as Eo}from"node:path";Z();import{randomBytes as ES}from"node:crypto";import{writeFileSync as bS,readFileSync as Gx,existsSync as Yx}from"node:fs";import{join as SS}from"node:path";var ho=SS($,"daemon.token");function Eu(){J();let e=ES(32).toString("hex");return bS(ho,e,{encoding:"utf8",mode:384}),e}var Su=Eo($,"daemon.pid"),Tu=Eo($,"daemon.port"),nO=Eo($,"daemon.log");function yu(e){J(),bu(Su,JSON.stringify(e),{encoding:"utf8",mode:384}),bu(Tu,String(e.port),{encoding:"utf8",mode:384})}function bo(){for(let e of[Su,Tu,ho])if(TS(e))try{yS(e)}catch{}}H();H();import{createHash as wS}from"node:crypto";var RS=/\b0x[0-9a-fA-F]+\b/g,kS=/\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/g,AS=/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?\b/g,NS=/:\d+:\d+/g,xS=/\bline\s+\d+\b/gi,OS=/\bcolumn\s+\d+\b/gi,LS=/\b(?:pid|PID|process(?:\s+id)?)\s*[:=]?\s*\d+\b/gi,CS=/\b(?:port|:)\s*[:=]?\s*\d{2,5}\b/gi,IS=/\b\d{4,}\b/g,vS=/(['"`])[^'"`\n]{1,128}\1/g;function jS(e){if(!e)return"";let t=String(e);return t=t.replace(RS,"<hex>"),t=t.replace(kS,"<uuid>"),t=t.replace(AS,"<ts>"),t=t.replace(NS,":<line>:<col>"),t=t.replace(xS,"line <n>"),t=t.replace(OS,"column <n>"),t=t.replace(LS,"pid <n>"),t=t.replace(CS,"port <n>"),t=t.replace(IS,"<num>"),t=t.replace(vS,"<arg>"),t=t.replace(/\s+/g," ").trim(),t.toLowerCase()}function MS(e){let t=(e.error_type??"unknown").toLowerCase().trim(),s=jS(e.snippet??e.message_hash??""),n=`${t}|${s}`;return wS("sha256").update(n).digest("hex").slice(0,16)}function DS(e){let t=f(),s=["oi.bug_signatures IS NOT NULL"],n=[];e&&(s.push("p.name = ?"),n.push(e));let r=`WHERE ${s.join(" AND ")}`,o=t.prepare(`SELECT oi.session_id AS session_id,
1840
+ p.name AS project,
1841
+ s.started_at AS started_at,
1842
+ oi.bug_signatures AS bug_signatures
1843
+ FROM session_output_index oi
1844
+ LEFT JOIN sessions s ON s.id = oi.session_id
1845
+ LEFT JOIN projects p ON p.id = s.project_id
1846
+ ${r}`).all(...n),a=[];for(let c of o){if(!c.bug_signatures)continue;let d=[];try{let u=JSON.parse(c.bug_signatures);Array.isArray(u)&&(d=u)}catch{continue}for(let u of d){if(!u||typeof u!="object"||!(u.snippet??"").trim())continue;let h=MS(u);a.push({session_id:c.session_id,project:c.project,started_at:c.started_at,signature:u,fingerprint:h})}}return a}function FS(e){let t=new Map;for(let n of e){let r=t.get(n.fingerprint);r||(r=[],t.set(n.fingerprint,r)),r.push(n)}let s=[];for(let[n,r]of t){let o=new Set,a=[];for(let g of r)o.has(g.session_id)||(o.add(g.session_id),a.push(g));let c=[...a].sort((g,h)=>{let b=g.started_at??"",S=h.started_at??"";return b&&S?b<S?-1:b>S?1:0:b?-1:S?1:0}),d=c.find(g=>g.started_at),u=[...c].reverse().find(g=>g.started_at);s.push({fingerprint:n,example_message:a[0].signature.snippet??a[0].signature.message_hash??"",members:a,first_seen_at:d?.started_at??new Date().toISOString(),last_seen_at:u?.started_at??new Date().toISOString()})}return s}function PS(e,t){if(e.length!==t.length)return 0;let s=0,n=0,r=0;for(let a=0;a<e.length;a++)s+=e[a]*t[a],n+=e[a]*e[a],r+=t[a]*t[a];let o=Math.sqrt(n)*Math.sqrt(r);return o>0?s/o:0}function US(e){let{records:t,vectors:s,epsilon:n,minPts:r}=e,o=t.length;if(o===0)return[];let a=[];for(let g=0;g<o;g++){let h=[];for(let b=0;b<o;b++){if(g===b)continue;1-PS(s[g],s[b])<=n&&h.push(b)}a.push(h)}let c=new Array(o).fill(!1),d=new Array(o).fill(-1),u=[];for(let g=0;g<o;g++){if(c[g])continue;c[g]=!0;let h=a[g];if(h.length<r)continue;let b=u.length;u.push({members:[t[g]]}),d[g]=b;let S=[...h];for(;S.length>0;){let y=S.shift();if(!c[y]&&(c[y]=!0,a[y].length>=r))for(let k of a[y])(!c[k]||d[k]===-1)&&S.push(k);d[y]===-1&&(d[y]=b,u[b].members.push(t[y]))}}return u}async function $S(e,t,s,n){if(e.length===0)return[];let r=e.map(d=>{let u=d.signature.snippet??d.signature.message_hash??"";return`${d.signature.error_type??""}: ${u}`.trim()}),o=await t(r);if(o.length!==e.length)throw new Error(`embedder returned ${o.length} vectors for ${e.length} inputs`);let a=US({records:e,vectors:o,epsilon:s,minPts:n}),c=[];for(let d of a){if(d.members.length===0)continue;let u=new Set,g=[];for(let w of d.members)u.has(w.session_id)||(u.add(w.session_id),g.push(w));if(g.length===0)continue;let b=`sem:${[...g.map(w=>w.fingerprint)].sort()[0]}`,S=[...g].sort((w,D)=>{let L=w.started_at??"",X=D.started_at??"";return L<X?-1:L>X?1:0}),y=S.find(w=>w.started_at),k=[...S].reverse().find(w=>w.started_at);c.push({fingerprint:b,example_message:g[0].signature.snippet??g[0].signature.message_hash??"",members:g,first_seen_at:y?.started_at??new Date().toISOString(),last_seen_at:k?.started_at??new Date().toISOString()})}return c}function wu(e,t){let s={clusters_created:0,clusters_merged:0,members_added:0,cluster_ids:[]};for(let n of e){if(n.members.length<t)continue;let r=Ba(n.fingerprint),o=Ha(n.fingerprint),a=n.members.map(u=>u.session_id).filter(u=>!o.has(u));if(r.length===0){let u=ja({signature_hash:n.fingerprint,example_message:n.example_message.slice(0,256),member_session_ids:n.members.map(g=>g.session_id),first_seen_at:n.first_seen_at,last_seen_at:n.last_seen_at});s.clusters_created+=1,s.members_added+=u.members.length,s.cluster_ids.push(u.cluster.id);continue}if(a.length===0){s.cluster_ids.push(r[0].id);continue}let c=r[0],d=Da(c.id,a);d.added>0&&(s.clusters_merged+=1,s.members_added+=d.added),s.cluster_ids.push(c.id)}return s}async function Ru(e={}){let t=Math.max(2,Math.floor(e.minClusterSize??3)),s=Math.max(1,Math.min(5e3,Math.floor(e.limit??1e3))),n=e.semanticEpsilon??.15,r=Math.max(1,Math.floor(e.semanticMinPts??1)),o=DS(e.project),a=new Set(o.map(L=>L.session_id)),c=FS(o),d=[],u=!1;if(e.semantic){let L=e.embedder??null;if(!L)try{L=await WS()}catch(X){let B=(X instanceof Error?X.message:String(X)).split(`
1847
+ `)[0];console.warn(`[bug-pattern] --semantic requested but the embedder is unavailable: ${B}
1848
+ Falling back to exact-match-only clustering. Run \`recall semantic install\` to enable the semantic pass.`),u=!0}if(L){let X=[];for(let M of c)M.members.length===1&&X.push(M.members[0]);X.length>=2&&(d=await $S(X,L,n,r))}}let g=c.filter(L=>L.members.length>=t),h=d.filter(L=>L.members.length>=t),b=wu(g,t),S=wu(h,t),y=[...b.cluster_ids,...S.cluster_ids],k=Array.from(new Set(y)),w=[];if(k.length>0){let L=f(),X=k.map(()=>"?").join(","),M=L.prepare(`SELECT * FROM bug_pattern_clusters
1849
+ WHERE id IN (${X})
1850
+ ORDER BY occurrence_count DESC, last_seen_at DESC
1851
+ LIMIT ?`).all(...k,s);for(let B of M)w.push({id:B.id,signature_hash:B.signature_hash,example_message:B.example_message,occurrence_count:B.occurrence_count,first_seen_at:B.first_seen_at,last_seen_at:B.last_seen_at,resolved_in_session_id:B.resolved_in_session_id,fix_summary:B.fix_summary})}return{progress:{total_sessions:a.size,total_signatures:o.length,exact_match_groups:g.length,semantic_groups:h.length,clusters_created:b.clusters_created+S.clusters_created,clusters_merged:b.clusters_merged+S.clusters_merged,members_added:b.members_added+S.members_added,semantic_skipped:u},clusters:w}}var BS=async()=>{let{embed:e,loadEmbedder:t,getEmbedderStatus:s}=await Promise.resolve().then(()=>(ot(),$c));return s().loaded||await t(),e},HS=BS;async function WS(){return HS()}H();var So={citation:"same-project",similar:"same-project",skill_track:"same-project",bug_pattern:"cross-project",wiki_link:"cross-project",temporal_proximity:"same-project"},qS=2,JS=.25,XS=5,GS=60,YS=25;function En(e){return e.trim().toLowerCase()}function KS(e){let t=new Set;for(let s of e.files_written)t.add(`file:${En(s)}`);for(let s of e.brands_mentioned)t.add(`brand:${En(s)}`);for(let s of e.terms_introduced)t.add(`term:${En(s)}`);for(let s of e.plan_ids_referenced)t.add(`plan:${En(s)}`);return t}function zS(e){if(!Number.isFinite(e)||e<0)return 1;let t=Math.exp(-e/GS);return Math.max(.2,t)}function VS(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 ZS(e){let t=Be(e);return t?{session_id:t.session_id,files_written:t.files_written,brands_mentioned:t.brands_mentioned,terms_introduced:t.terms_introduced.map(s=>s.term),plan_ids_referenced:t.plan_ids_referenced}:null}function QS(e){return f().prepare(`SELECT s.id AS id, s.project_id AS project_id, s.started_at AS started_at
1852
+ FROM sessions s
1853
+ JOIN session_output_index oi ON oi.session_id = s.id
1854
+ WHERE s.project_id = ?
1855
+ ORDER BY COALESCE(s.started_at, ''), s.id`).all(e)}function eT(e,t){let s=new Map,n=new Map,r=new Map;for(let o of e){r.set(o.id,o.started_at);let a=t.get(o.id);if(!a)continue;let c=KS(a);if(c.size!==0){n.set(o.id,c);for(let d of c){let u=s.get(d);u?u.push(o.id):s.set(d,[o.id])}}}return{posting:s,vocab:n,startedAt:r}}function tT(e,t){let s=t.vocab.get(e),n=t.startedAt.get(e)??null;if(!s||!n)return[];let r=new Map;for(let a of s){let c=t.posting.get(a);if(c)for(let d of c){if(d===e)continue;let u=t.startedAt.get(d);if(!u||u>=n)continue;let g=r.get(d);g?g.push(a):r.set(d,[a])}}let o=[];for(let[a,c]of r){let d=c.length;if(d<qS)continue;let u=t.startedAt.get(a)??null,g=VS(n,u),h=zS(g),b=Math.min(1,d/XS*h);if(b<JS)continue;let S=c.slice(0,12);o.push({target_session_id:a,matched_terms:S,overlap:d,days_apart:Math.round(g*10)/10,recency:Math.round(h*1e3)/1e3,confidence:Math.round(b*1e3)/1e3})}return o.sort((a,c)=>c.confidence-a.confidence),o.slice(0,YS)}async function ku(e){if(So.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project \u2014 refusing to run inference");let t=QS(e.projectId),s=new Map;for(let a of t){let c=ZS(a.id);c&&s.set(a.id,c)}let n=eT(t,s),r={total_sessions:t.length,processed_sessions:0,suggestions_created:0,suggestions_skipped_existing:0,current_session_id:null};e.onProgress?.({...r});let o=[];for(let a of t){if(e.signal?.aborted)break;r.current_session_id=a.id,e.onProgress?.({...r});let c=tT(a.id,n);for(let d of c)try{let u=Ct({source_session_id:a.id,target_session_id:d.target_session_id,link_type:"citation",confidence:d.confidence,evidence:{matched_terms:d.matched_terms,overlap_count:d.overlap,recency:d.recency,days_apart:d.days_apart},inferred_by:"L2"});o.push(u.id),r.suggestions_created+=1}catch(u){console.error("[citation-inference] createSuggestion failed:",u)}r.processed_sessions+=1,e.onProgress?.({...r})}return r.current_session_id=null,e.onProgress?.({...r}),{progress:r,suggestion_ids:o}}H();var sT=/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,nT=[/\bv\d+\.\d+(?:\.[A-Za-z]+)?\b/g,/\bPhase\s+[A-Ha-h]\b/g,/\bL\d+\b/g,/MASTER-PLAN-[A-Za-z-]+/g],rT=.95,oT=.85;var To=50;function iT(e){if(!e)return[];let t=new Set,s=[],n=e.match(sT);if(!n)return s;for(let r of n){let o=r.toLowerCase();if(!t.has(o)&&(t.add(o),s.push(o),s.length>=To))break}return s}function aT(e){if(!e)return[];let t=new Set,s=[];for(let n of nT){n.lastIndex=0;let r=e.match(n);if(r){for(let o of r){let a=o.trim().toLowerCase().replace(/\s+/g," ");if(!(!a||a.length>64)&&!t.has(a)&&(t.add(a),s.push(a),s.length>=To))break}if(s.length>=To)break}}return s}function Au(e){let t=f();return typeof e=="number"?t.prepare("SELECT id, project_id FROM sessions WHERE project_id = ?").all(e):t.prepare("SELECT id, project_id FROM sessions").all()}function Nu(e){let t=f();return typeof e=="number"?t.prepare(`SELECT m.uuid AS message_uuid, m.session_id, m.content_text,
1856
+ s.project_id
1857
+ FROM messages m
1858
+ JOIN sessions s ON s.id = m.session_id
1859
+ WHERE s.project_id = ?
1860
+ AND m.is_sidechain = 0
1861
+ AND m.content_text IS NOT NULL
1862
+ AND length(m.content_text) > 0`).all(e):t.prepare(`SELECT m.uuid AS message_uuid, m.session_id, m.content_text,
1863
+ s.project_id
1864
+ FROM messages m
1865
+ JOIN sessions s ON s.id = m.session_id
1866
+ WHERE m.is_sidechain = 0
1867
+ AND m.content_text IS NOT NULL
1868
+ AND length(m.content_text) > 0`).all()}function cT(e){let t=f(),s=typeof e=="number"?"WHERE s.project_id = ?":"",n=typeof e=="number"?[e]:[];return t.prepare(`SELECT oi.session_id AS session_id,
1869
+ s.project_id AS project_id,
1870
+ oi.plan_ids_referenced AS plan_ids_json
1871
+ FROM session_output_index oi
1872
+ JOIN sessions s ON s.id = oi.session_id
1873
+ ${s}`).all(...n)}function lT(e){if(!e)return[];try{let t=JSON.parse(e);if(Array.isArray(t))return t.filter(s=>typeof s=="string")}catch{}return[]}function xu(e={}){if(So.citation!=="same-project")throw new Error("citation policy unexpectedly not same-project");let t=Au(e.projectId),s=new Set(t.map(o=>o.id)),n=new Map;for(let o of t)n.set(o.id,o.project_id);let r=0;for(let o of Nu(e.projectId)){if(e.signal?.aborted)break;let a=iT(o.content_text);if(a.length===0)continue;let c=n.get(o.session_id);if(c!==void 0)for(let d of a){if(d===o.session_id)continue;let u=n.get(d);if(!(u===void 0&&!s.has(d))&&!(u!==void 0&&c!==void 0&&u!==c))try{Ct({source_session_id:o.session_id,target_session_id:d,link_type:"citation",confidence:rT,evidence:{matched_uuid:d,source_message_uuid:o.message_uuid,scanner:"uuid-ref"},inferred_by:"L1"}),r+=1}catch{}}}return{created:r}}function Ou(e={}){let t=cT(e.projectId);if(t.length===0)return{created:0};let s=new Map;for(let c of t){let d=lT(c.plan_ids_json);for(let u of d){let g=u.trim().toLowerCase();if(!g)continue;let h=s.get(g);h?h.push({id:c.session_id,project_id:c.project_id}):s.set(g,[{id:c.session_id,project_id:c.project_id}])}}if(s.size===0)return{created:0};let n=Au(e.projectId),r=new Map;for(let c of n)r.set(c.id,c.project_id);let o=0,a=new Map;for(let c of Nu(e.projectId)){if(e.signal?.aborted)break;let d=aT(c.content_text);if(d.length===0)continue;let u=r.get(c.session_id);if(u!==void 0)for(let g of d){let h=s.get(g);if(h)for(let b of h){if(b.id===c.session_id||b.project_id!==u)continue;let S=a.get(c.session_id);S||(S=new Map,a.set(c.session_id,S));let y=S.get(b.id);y||(y=new Set,S.set(b.id,y)),y.add(g)}}}for(let[c,d]of a)for(let[u,g]of d)try{Ct({source_session_id:c,target_session_id:u,link_type:"citation",confidence:oT,evidence:{matched_plan_ids:Array.from(g).slice(0,12),scanner:"plan-ref"},inferred_by:"L1"}),o+=1}catch{}return{created:o}}ge();var le="[daemon:inference]",yo=!1,wo=!1,Ro=!1,ko=!1,Ao=!1,Lu=0,No=!1,xo=!1;function dT(){return f().prepare("SELECT id, name FROM projects").all()}async function Cu(){if(yo)return;yo=!0;let e=Date.now();try{let s=(await Ru({minClusterSize:2})).progress;(s.clusters_created||s.clusters_merged||s.members_added)&&console.log(`${le} bug-patterns: created=${s.clusters_created} merged=${s.clusters_merged} members_added=${s.members_added} (${Date.now()-e}ms)`)}catch(t){console.error(`${le} bug-patterns failed:`,t)}finally{yo=!1}}async function Iu(){if(wo)return;wo=!0;let e=Date.now();try{let t=0,s=0;for(let n of dT())try{let r=await ku({projectId:n.id});t+=r.progress.suggestions_created,s+=1}catch(r){console.error(`${le} citations failed for project "${n.name}":`,r)}t>0&&console.log(`${le} citations: ${t} suggestion(s) across ${s} project(s) (${Date.now()-e}ms)`)}catch(t){console.error(`${le} citations failed:`,t)}finally{wo=!1}}function vu(){if(Ro)return;Ro=!0;let e=Date.now();try{let t=xu({}),s=Ou({});t.created+s.created>0&&console.log(`${le} l1: uuid=${t.created} plan=${s.created} (${Date.now()-e}ms)`)}catch(t){console.error(`${le} l1 failed:`,t)}finally{Ro=!1}}async function ju(){if(ko)return;let e=ce();if(!e.enabled||e.backfillPaused)return;ko=!0;let t=Date.now();try{let s=await cn({limit:25});s.processed>0&&console.log(`${le} backfill: processed=${s.processed} ok=${s.ok} failed=${s.failed} (${Date.now()-t}ms)`)}catch(s){console.error(`${le} backfill failed:`,s)}finally{ko=!1}}async function Mu(){if(Ao)return;let e=ce();if(!e.autoExtractEnabled)return;let t=e.autoExtractIntervalMinutes*60*1e3;if(Date.now()-Lu<t)return;if(!oe()){No||(console.log(`${le} auto-extract: claude CLI not on PATH \u2014 pausing nibbler (will retry; install Claude Code or run \`recall semantic auto-extract off\` to silence)`),No=!0);return}if(No=!1,!await Wo().catch(()=>!1)){xo||(console.log(`${le} auto-extract: Pro license required \u2014 pausing nibbler (run \`recall semantic auto-extract off\` to silence; upgrade unlocks)`),xo=!0);return}xo=!1,Ao=!0,Lu=Date.now();let n=Date.now();try{let r=e.autoExtractBatchSize,{eligible:o}=rt({limit:r});if(o.length===0)return;let a=0,c=0,d=0,u=0;for(let g of o){let h=await fr(g.id,{model:We});h.ok?a+=1:h.skipped||(c+=1),h.usage?.input_tokens&&(d+=h.usage.input_tokens),h.usage?.output_tokens&&(u+=h.usage.output_tokens)}console.log(`${le} auto-extract: processed=${o.length} ok=${a} failed=${c} tokens=${d}+${u} (${Date.now()-n}ms)`)}catch(r){console.error(`${le} auto-extract failed:`,r)}finally{Ao=!1}}function Du(){let g=[],h=[];return g.push(setTimeout(()=>{Cu()},9e4)),h.push(setInterval(()=>{Cu()},18e5)),g.push(setTimeout(()=>{Iu()},18e4)),h.push(setInterval(()=>{Iu()},36e5)),g.push(setTimeout(()=>vu(),12e4)),h.push(setInterval(()=>vu(),18e5)),g.push(setTimeout(()=>{ju()},24e4)),h.push(setInterval(()=>{ju()},9e5)),g.push(setTimeout(()=>{Mu()},3e5)),h.push(setInterval(()=>{Mu()},9e5)),console.log(`${le} scheduled: bug-patterns (30m), citations (60m), l1 (30m), backfill (15m, when enabled), auto-extract (60m, when enabled)`),{startupTimers:g,intervalTimers:h,stop:()=>{for(let b of g)clearTimeout(b);for(let b of h)clearInterval(b)}}}var uT=360*60*1e3,pT=60*1e3,mT=1440*60*1e3,gT=300*1e3,_T=300*1e3,fT=10*1e3,hT=1440*60*1e3,ET=30*1e3,bT=500;async function ST(){let e=await hu(),t=Eu(),s=await uu(e,t);yu({pid:process.pid,port:e,startedAt:new Date().toISOString()});let n=gu(),r=()=>{try{_s()}catch(L){console.error("[daemon] suggestion scan failed:",L)}},o=setTimeout(r,pT),a=setInterval(r,uT),c=()=>{try{let L=I.reapStaleLinks();(L.pruned_pids||L.pruned_sessions)&&console.log(`[daemon] reaper: pruned ${L.pruned_pids} pid${L.pruned_pids===1?"":"s"}, ${L.pruned_sessions} session link${L.pruned_sessions===1?"":"s"}`)}catch(L){console.error("[daemon] stale-link reaper failed:",L)}},d=setTimeout(c,gT),u=setInterval(c,mT),g=()=>{try{let L=I.gcDeadPids();(L.pruned_pids||L.pruned_sessions)&&console.log(`[daemon] dead-pid gc: pruned ${L.pruned_pids} pid${L.pruned_pids===1?"":"s"}, ${L.pruned_sessions} session link${L.pruned_sessions===1?"":"s"}`)}catch(L){console.error("[daemon] dead-pid gc failed:",L)}},h=setTimeout(g,fT),b=setInterval(g,_T),S=()=>{Ho().then(L=>{L.ran&&L.revoked&&console.log(`[daemon] license check: REVOKED${L.reason?` (${L.reason})`:""}`)}).catch(L=>{console.error("[daemon] license check failed:",L)})},y=setTimeout(S,ET),k=setInterval(S,hT),w=Du(),D=L=>{console.log(`[daemon] received ${L}, shutting down`),clearTimeout(o),clearInterval(a),clearTimeout(d),clearInterval(u),clearTimeout(h),clearInterval(b),clearTimeout(y),clearInterval(k),w.stop(),n.close(),s.close(),bo(),process.exit(0)};process.on("SIGTERM",()=>D("SIGTERM")),process.on("SIGINT",()=>D("SIGINT")),process.on("SIGHUP",()=>D("SIGHUP")),console.log(`[daemon] ready on http://127.0.0.1:${e} pid=${process.pid}`),setTimeout(()=>{Ds().then(L=>{console.log(`[daemon] boot sweep: scanned ${L.scanned} live claude(s), linked ${L.linked}, renamed ${L.renamed}, ambiguous_cwd ${L.ambiguous_cwd}`)}).catch(L=>{console.error("[daemon] boot sweep failed:",L)})},bT)}ST().catch(e=>{console.error("[daemon] fatal:",e),bo(),process.exit(1)});