@jacques-ai/core 0.0.7-alpha.1

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 (341) hide show
  1. package/dist/archive/archive-store.d.ts +166 -0
  2. package/dist/archive/archive-store.d.ts.map +1 -0
  3. package/dist/archive/archive-store.js +612 -0
  4. package/dist/archive/archive-store.js.map +1 -0
  5. package/dist/archive/bulk-archive.d.ts +63 -0
  6. package/dist/archive/bulk-archive.d.ts.map +1 -0
  7. package/dist/archive/bulk-archive.js +315 -0
  8. package/dist/archive/bulk-archive.js.map +1 -0
  9. package/dist/archive/filename-utils.d.ts +39 -0
  10. package/dist/archive/filename-utils.d.ts.map +1 -0
  11. package/dist/archive/filename-utils.js +78 -0
  12. package/dist/archive/filename-utils.js.map +1 -0
  13. package/dist/archive/index.d.ts +21 -0
  14. package/dist/archive/index.d.ts.map +1 -0
  15. package/dist/archive/index.js +45 -0
  16. package/dist/archive/index.js.map +1 -0
  17. package/dist/archive/manifest-extractor.d.ts +40 -0
  18. package/dist/archive/manifest-extractor.d.ts.map +1 -0
  19. package/dist/archive/manifest-extractor.js +456 -0
  20. package/dist/archive/manifest-extractor.js.map +1 -0
  21. package/dist/archive/migration.d.ts +59 -0
  22. package/dist/archive/migration.d.ts.map +1 -0
  23. package/dist/archive/migration.js +172 -0
  24. package/dist/archive/migration.js.map +1 -0
  25. package/dist/archive/plan-cataloger.d.ts +24 -0
  26. package/dist/archive/plan-cataloger.d.ts.map +1 -0
  27. package/dist/archive/plan-cataloger.js +100 -0
  28. package/dist/archive/plan-cataloger.js.map +1 -0
  29. package/dist/archive/plan-extractor.d.ts +84 -0
  30. package/dist/archive/plan-extractor.d.ts.map +1 -0
  31. package/dist/archive/plan-extractor.js +371 -0
  32. package/dist/archive/plan-extractor.js.map +1 -0
  33. package/dist/archive/search-indexer.d.ts +50 -0
  34. package/dist/archive/search-indexer.d.ts.map +1 -0
  35. package/dist/archive/search-indexer.js +294 -0
  36. package/dist/archive/search-indexer.js.map +1 -0
  37. package/dist/archive/subagent-store.d.ts +113 -0
  38. package/dist/archive/subagent-store.d.ts.map +1 -0
  39. package/dist/archive/subagent-store.js +173 -0
  40. package/dist/archive/subagent-store.js.map +1 -0
  41. package/dist/archive/types.d.ts +236 -0
  42. package/dist/archive/types.d.ts.map +1 -0
  43. package/dist/archive/types.js +30 -0
  44. package/dist/archive/types.js.map +1 -0
  45. package/dist/branding.d.ts +9 -0
  46. package/dist/branding.d.ts.map +1 -0
  47. package/dist/branding.js +50 -0
  48. package/dist/branding.js.map +1 -0
  49. package/dist/cache/git-utils.d.ts +36 -0
  50. package/dist/cache/git-utils.d.ts.map +1 -0
  51. package/dist/cache/git-utils.js +160 -0
  52. package/dist/cache/git-utils.js.map +1 -0
  53. package/dist/cache/hidden-projects.d.ts +19 -0
  54. package/dist/cache/hidden-projects.d.ts.map +1 -0
  55. package/dist/cache/hidden-projects.js +48 -0
  56. package/dist/cache/hidden-projects.js.map +1 -0
  57. package/dist/cache/index.d.ts +15 -0
  58. package/dist/cache/index.d.ts.map +1 -0
  59. package/dist/cache/index.js +20 -0
  60. package/dist/cache/index.js.map +1 -0
  61. package/dist/cache/metadata-extractor.d.ts +62 -0
  62. package/dist/cache/metadata-extractor.d.ts.map +1 -0
  63. package/dist/cache/metadata-extractor.js +574 -0
  64. package/dist/cache/metadata-extractor.js.map +1 -0
  65. package/dist/cache/mode-detector.d.ts +19 -0
  66. package/dist/cache/mode-detector.d.ts.map +1 -0
  67. package/dist/cache/mode-detector.js +161 -0
  68. package/dist/cache/mode-detector.js.map +1 -0
  69. package/dist/cache/persistence.d.ts +39 -0
  70. package/dist/cache/persistence.d.ts.map +1 -0
  71. package/dist/cache/persistence.js +98 -0
  72. package/dist/cache/persistence.js.map +1 -0
  73. package/dist/cache/project-discovery.d.ts +41 -0
  74. package/dist/cache/project-discovery.d.ts.map +1 -0
  75. package/dist/cache/project-discovery.js +212 -0
  76. package/dist/cache/project-discovery.js.map +1 -0
  77. package/dist/cache/session-index.d.ts +258 -0
  78. package/dist/cache/session-index.d.ts.map +1 -0
  79. package/dist/cache/session-index.js +1030 -0
  80. package/dist/cache/session-index.js.map +1 -0
  81. package/dist/cache/types.d.ts +159 -0
  82. package/dist/cache/types.d.ts.map +1 -0
  83. package/dist/cache/types.js +29 -0
  84. package/dist/cache/types.js.map +1 -0
  85. package/dist/catalog/bulk-extractor.d.ts +18 -0
  86. package/dist/catalog/bulk-extractor.d.ts.map +1 -0
  87. package/dist/catalog/bulk-extractor.js +150 -0
  88. package/dist/catalog/bulk-extractor.js.map +1 -0
  89. package/dist/catalog/extractor.d.ts +53 -0
  90. package/dist/catalog/extractor.d.ts.map +1 -0
  91. package/dist/catalog/extractor.js +522 -0
  92. package/dist/catalog/extractor.js.map +1 -0
  93. package/dist/catalog/index.d.ts +10 -0
  94. package/dist/catalog/index.d.ts.map +1 -0
  95. package/dist/catalog/index.js +11 -0
  96. package/dist/catalog/index.js.map +1 -0
  97. package/dist/catalog/types.d.ts +134 -0
  98. package/dist/catalog/types.d.ts.map +1 -0
  99. package/dist/catalog/types.js +8 -0
  100. package/dist/catalog/types.js.map +1 -0
  101. package/dist/client/index.d.ts +6 -0
  102. package/dist/client/index.d.ts.map +1 -0
  103. package/dist/client/index.js +5 -0
  104. package/dist/client/index.js.map +1 -0
  105. package/dist/client/websocket-client.d.ts +96 -0
  106. package/dist/client/websocket-client.d.ts.map +1 -0
  107. package/dist/client/websocket-client.js +222 -0
  108. package/dist/client/websocket-client.js.map +1 -0
  109. package/dist/context/index.d.ts +13 -0
  110. package/dist/context/index.d.ts.map +1 -0
  111. package/dist/context/index.js +26 -0
  112. package/dist/context/index.js.map +1 -0
  113. package/dist/context/indexer.d.ts +73 -0
  114. package/dist/context/indexer.d.ts.map +1 -0
  115. package/dist/context/indexer.js +233 -0
  116. package/dist/context/indexer.js.map +1 -0
  117. package/dist/context/manager.d.ts +66 -0
  118. package/dist/context/manager.d.ts.map +1 -0
  119. package/dist/context/manager.js +310 -0
  120. package/dist/context/manager.js.map +1 -0
  121. package/dist/context/types.d.ts +149 -0
  122. package/dist/context/types.d.ts.map +1 -0
  123. package/dist/context/types.js +36 -0
  124. package/dist/context/types.js.map +1 -0
  125. package/dist/handoff/catalog.d.ts +54 -0
  126. package/dist/handoff/catalog.d.ts.map +1 -0
  127. package/dist/handoff/catalog.js +121 -0
  128. package/dist/handoff/catalog.js.map +1 -0
  129. package/dist/handoff/generator.d.ts +107 -0
  130. package/dist/handoff/generator.d.ts.map +1 -0
  131. package/dist/handoff/generator.js +603 -0
  132. package/dist/handoff/generator.js.map +1 -0
  133. package/dist/handoff/index.d.ts +13 -0
  134. package/dist/handoff/index.d.ts.map +1 -0
  135. package/dist/handoff/index.js +12 -0
  136. package/dist/handoff/index.js.map +1 -0
  137. package/dist/handoff/llm-generator.d.ts +77 -0
  138. package/dist/handoff/llm-generator.d.ts.map +1 -0
  139. package/dist/handoff/llm-generator.js +513 -0
  140. package/dist/handoff/llm-generator.js.map +1 -0
  141. package/dist/handoff/prompts.d.ts +18 -0
  142. package/dist/handoff/prompts.d.ts.map +1 -0
  143. package/dist/handoff/prompts.js +22 -0
  144. package/dist/handoff/prompts.js.map +1 -0
  145. package/dist/handoff/types.d.ts +28 -0
  146. package/dist/handoff/types.d.ts.map +1 -0
  147. package/dist/handoff/types.js +7 -0
  148. package/dist/handoff/types.js.map +1 -0
  149. package/dist/index.d.ts +56 -0
  150. package/dist/index.d.ts.map +1 -0
  151. package/dist/index.js +132 -0
  152. package/dist/index.js.map +1 -0
  153. package/dist/logging/claude-operations.d.ts +111 -0
  154. package/dist/logging/claude-operations.d.ts.map +1 -0
  155. package/dist/logging/claude-operations.js +132 -0
  156. package/dist/logging/claude-operations.js.map +1 -0
  157. package/dist/logging/error-utils.d.ts +18 -0
  158. package/dist/logging/error-utils.d.ts.map +1 -0
  159. package/dist/logging/error-utils.js +37 -0
  160. package/dist/logging/error-utils.js.map +1 -0
  161. package/dist/logging/index.d.ts +11 -0
  162. package/dist/logging/index.d.ts.map +1 -0
  163. package/dist/logging/index.js +10 -0
  164. package/dist/logging/index.js.map +1 -0
  165. package/dist/logging/logger.d.ts +25 -0
  166. package/dist/logging/logger.d.ts.map +1 -0
  167. package/dist/logging/logger.js +39 -0
  168. package/dist/logging/logger.js.map +1 -0
  169. package/dist/notifications/constants.d.ts +16 -0
  170. package/dist/notifications/constants.d.ts.map +1 -0
  171. package/dist/notifications/constants.js +49 -0
  172. package/dist/notifications/constants.js.map +1 -0
  173. package/dist/notifications/index.d.ts +9 -0
  174. package/dist/notifications/index.d.ts.map +1 -0
  175. package/dist/notifications/index.js +8 -0
  176. package/dist/notifications/index.js.map +1 -0
  177. package/dist/notifications/types.d.ts +28 -0
  178. package/dist/notifications/types.d.ts.map +1 -0
  179. package/dist/notifications/types.js +7 -0
  180. package/dist/notifications/types.js.map +1 -0
  181. package/dist/notifications/utils.d.ts +20 -0
  182. package/dist/notifications/utils.d.ts.map +1 -0
  183. package/dist/notifications/utils.js +37 -0
  184. package/dist/notifications/utils.js.map +1 -0
  185. package/dist/plan/index.d.ts +12 -0
  186. package/dist/plan/index.d.ts.map +1 -0
  187. package/dist/plan/index.js +15 -0
  188. package/dist/plan/index.js.map +1 -0
  189. package/dist/plan/plan-parser.d.ts +33 -0
  190. package/dist/plan/plan-parser.d.ts.map +1 -0
  191. package/dist/plan/plan-parser.js +189 -0
  192. package/dist/plan/plan-parser.js.map +1 -0
  193. package/dist/plan/progress-computer.d.ts +34 -0
  194. package/dist/plan/progress-computer.d.ts.map +1 -0
  195. package/dist/plan/progress-computer.js +211 -0
  196. package/dist/plan/progress-computer.js.map +1 -0
  197. package/dist/plan/progress-matcher.d.ts +34 -0
  198. package/dist/plan/progress-matcher.d.ts.map +1 -0
  199. package/dist/plan/progress-matcher.js +297 -0
  200. package/dist/plan/progress-matcher.js.map +1 -0
  201. package/dist/plan/task-extractor.d.ts +30 -0
  202. package/dist/plan/task-extractor.d.ts.map +1 -0
  203. package/dist/plan/task-extractor.js +435 -0
  204. package/dist/plan/task-extractor.js.map +1 -0
  205. package/dist/plan/types.d.ts +131 -0
  206. package/dist/plan/types.d.ts.map +1 -0
  207. package/dist/plan/types.js +8 -0
  208. package/dist/plan/types.js.map +1 -0
  209. package/dist/project/aggregator.d.ts +43 -0
  210. package/dist/project/aggregator.d.ts.map +1 -0
  211. package/dist/project/aggregator.js +218 -0
  212. package/dist/project/aggregator.js.map +1 -0
  213. package/dist/project/index.d.ts +9 -0
  214. package/dist/project/index.d.ts.map +1 -0
  215. package/dist/project/index.js +9 -0
  216. package/dist/project/index.js.map +1 -0
  217. package/dist/project/types.d.ts +65 -0
  218. package/dist/project/types.d.ts.map +1 -0
  219. package/dist/project/types.js +27 -0
  220. package/dist/project/types.js.map +1 -0
  221. package/dist/session/detector.d.ts +113 -0
  222. package/dist/session/detector.d.ts.map +1 -0
  223. package/dist/session/detector.js +333 -0
  224. package/dist/session/detector.js.map +1 -0
  225. package/dist/session/filters.d.ts +32 -0
  226. package/dist/session/filters.d.ts.map +1 -0
  227. package/dist/session/filters.js +100 -0
  228. package/dist/session/filters.js.map +1 -0
  229. package/dist/session/format-title.d.ts +16 -0
  230. package/dist/session/format-title.d.ts.map +1 -0
  231. package/dist/session/format-title.js +54 -0
  232. package/dist/session/format-title.js.map +1 -0
  233. package/dist/session/index.d.ts +16 -0
  234. package/dist/session/index.d.ts.map +1 -0
  235. package/dist/session/index.js +10 -0
  236. package/dist/session/index.js.map +1 -0
  237. package/dist/session/parser.d.ts +264 -0
  238. package/dist/session/parser.d.ts.map +1 -0
  239. package/dist/session/parser.js +588 -0
  240. package/dist/session/parser.js.map +1 -0
  241. package/dist/session/token-estimator.d.ts +32 -0
  242. package/dist/session/token-estimator.d.ts.map +1 -0
  243. package/dist/session/token-estimator.js +139 -0
  244. package/dist/session/token-estimator.js.map +1 -0
  245. package/dist/session/transformer.d.ts +126 -0
  246. package/dist/session/transformer.d.ts.map +1 -0
  247. package/dist/session/transformer.js +158 -0
  248. package/dist/session/transformer.js.map +1 -0
  249. package/dist/setup/hooks-config.d.ts +35 -0
  250. package/dist/setup/hooks-config.d.ts.map +1 -0
  251. package/dist/setup/hooks-config.js +107 -0
  252. package/dist/setup/hooks-config.js.map +1 -0
  253. package/dist/setup/hooks-symlink.d.ts +17 -0
  254. package/dist/setup/hooks-symlink.d.ts.map +1 -0
  255. package/dist/setup/hooks-symlink.js +89 -0
  256. package/dist/setup/hooks-symlink.js.map +1 -0
  257. package/dist/setup/index.d.ts +13 -0
  258. package/dist/setup/index.d.ts.map +1 -0
  259. package/dist/setup/index.js +12 -0
  260. package/dist/setup/index.js.map +1 -0
  261. package/dist/setup/prerequisites.d.ts +11 -0
  262. package/dist/setup/prerequisites.d.ts.map +1 -0
  263. package/dist/setup/prerequisites.js +72 -0
  264. package/dist/setup/prerequisites.js.map +1 -0
  265. package/dist/setup/settings-merge.d.ts +33 -0
  266. package/dist/setup/settings-merge.d.ts.map +1 -0
  267. package/dist/setup/settings-merge.js +131 -0
  268. package/dist/setup/settings-merge.js.map +1 -0
  269. package/dist/setup/skills-install.d.ts +17 -0
  270. package/dist/setup/skills-install.d.ts.map +1 -0
  271. package/dist/setup/skills-install.js +60 -0
  272. package/dist/setup/skills-install.js.map +1 -0
  273. package/dist/setup/types.d.ts +39 -0
  274. package/dist/setup/types.d.ts.map +1 -0
  275. package/dist/setup/types.js +7 -0
  276. package/dist/setup/types.js.map +1 -0
  277. package/dist/setup/verification.d.ts +9 -0
  278. package/dist/setup/verification.d.ts.map +1 -0
  279. package/dist/setup/verification.js +91 -0
  280. package/dist/setup/verification.js.map +1 -0
  281. package/dist/shortcuts/index.d.ts +8 -0
  282. package/dist/shortcuts/index.d.ts.map +1 -0
  283. package/dist/shortcuts/index.js +6 -0
  284. package/dist/shortcuts/index.js.map +1 -0
  285. package/dist/shortcuts/key-utils.d.ts +54 -0
  286. package/dist/shortcuts/key-utils.d.ts.map +1 -0
  287. package/dist/shortcuts/key-utils.js +129 -0
  288. package/dist/shortcuts/key-utils.js.map +1 -0
  289. package/dist/shortcuts/shortcut-registry.d.ts +37 -0
  290. package/dist/shortcuts/shortcut-registry.d.ts.map +1 -0
  291. package/dist/shortcuts/shortcut-registry.js +322 -0
  292. package/dist/shortcuts/shortcut-registry.js.map +1 -0
  293. package/dist/sources/config.d.ts +91 -0
  294. package/dist/sources/config.d.ts.map +1 -0
  295. package/dist/sources/config.js +229 -0
  296. package/dist/sources/config.js.map +1 -0
  297. package/dist/sources/googledocs.d.ts +43 -0
  298. package/dist/sources/googledocs.d.ts.map +1 -0
  299. package/dist/sources/googledocs.js +298 -0
  300. package/dist/sources/googledocs.js.map +1 -0
  301. package/dist/sources/index.d.ts +14 -0
  302. package/dist/sources/index.d.ts.map +1 -0
  303. package/dist/sources/index.js +19 -0
  304. package/dist/sources/index.js.map +1 -0
  305. package/dist/sources/notion.d.ts +35 -0
  306. package/dist/sources/notion.d.ts.map +1 -0
  307. package/dist/sources/notion.js +352 -0
  308. package/dist/sources/notion.js.map +1 -0
  309. package/dist/sources/obsidian.d.ts +38 -0
  310. package/dist/sources/obsidian.d.ts.map +1 -0
  311. package/dist/sources/obsidian.js +228 -0
  312. package/dist/sources/obsidian.js.map +1 -0
  313. package/dist/sources/types.d.ts +133 -0
  314. package/dist/sources/types.d.ts.map +1 -0
  315. package/dist/sources/types.js +19 -0
  316. package/dist/sources/types.js.map +1 -0
  317. package/dist/storage/index.d.ts +6 -0
  318. package/dist/storage/index.d.ts.map +1 -0
  319. package/dist/storage/index.js +5 -0
  320. package/dist/storage/index.js.map +1 -0
  321. package/dist/storage/writer.d.ts +86 -0
  322. package/dist/storage/writer.d.ts.map +1 -0
  323. package/dist/storage/writer.js +137 -0
  324. package/dist/storage/writer.js.map +1 -0
  325. package/dist/types.d.ts +203 -0
  326. package/dist/types.d.ts.map +1 -0
  327. package/dist/types.js +8 -0
  328. package/dist/types.js.map +1 -0
  329. package/dist/utils/claude-token.d.ts +49 -0
  330. package/dist/utils/claude-token.d.ts.map +1 -0
  331. package/dist/utils/claude-token.js +169 -0
  332. package/dist/utils/claude-token.js.map +1 -0
  333. package/dist/utils/index.d.ts +7 -0
  334. package/dist/utils/index.d.ts.map +1 -0
  335. package/dist/utils/index.js +13 -0
  336. package/dist/utils/index.js.map +1 -0
  337. package/dist/utils/settings.d.ts +100 -0
  338. package/dist/utils/settings.d.ts.map +1 -0
  339. package/dist/utils/settings.js +206 -0
  340. package/dist/utils/settings.js.map +1 -0
  341. package/package.json +54 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Cache Module
3
+ *
4
+ * Lightweight session indexing without content duplication.
5
+ * Reads directly from Claude Code JSONL files.
6
+ */
7
+ export { getDefaultSessionIndex, decodeProjectPath, } from "./types.js";
8
+ // Persistence
9
+ export { getCacheDir, getIndexPath, ensureCacheDir, readSessionIndex, writeSessionIndex, getSessionIndex, invalidateIndex, } from "./persistence.js";
10
+ // Mode detection
11
+ export { detectModeAndPlans } from "./mode-detector.js";
12
+ // Git utilities
13
+ export { detectGitInfo, readGitBranchFromJsonl, readWorktreeRepoRoot, computeBranchDivergence, checkDirtyStatus } from "./git-utils.js";
14
+ // Metadata extraction & index building
15
+ export { extractTitle, extractTimestamps, extractSessionMetadata, extractContinueTitleFromHandoff, listAllProjects, buildSessionIndex, } from "./metadata-extractor.js";
16
+ // Project discovery & query
17
+ export { getProjectGroupKey, discoverProjects, getSessionEntry, getSessionsByProject, getIndexStats, } from "./project-discovery.js";
18
+ // Hidden projects
19
+ export { getHiddenProjects, hideProject, unhideProject, } from "./hidden-projects.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAcH,OAAO,EACL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAEpB,cAAc;AACd,OAAO,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAE1B,iBAAiB;AACjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,gBAAgB;AAChB,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAExI,uCAAuC;AACvC,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,+BAA+B,EAC/B,eAAe,EACf,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,4BAA4B;AAC5B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,aAAa,GACd,MAAM,wBAAwB,CAAC;AAEhC,kBAAkB;AAClB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,aAAa,GACd,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Metadata Extractor
3
+ *
4
+ * Extracts session metadata from JSONL files and catalog data.
5
+ * Builds the session index using a catalog-first strategy.
6
+ */
7
+ import type { SessionEntry, SessionIndex } from "./types.js";
8
+ /**
9
+ * Extract "## Current Task" from the most recent handoff created before sessionStartedAt.
10
+ * Returns "Cont: <task>" or null if no matching handoff found.
11
+ */
12
+ export declare function extractContinueTitleFromHandoff(projectPath: string, sessionStartedAt: string): Promise<string | null>;
13
+ /**
14
+ * Extract session title from parsed JSONL entries.
15
+ * Priority:
16
+ * 1. Summary entry (Claude's auto-generated title)
17
+ * 2. First real user message (skips internal command messages)
18
+ */
19
+ export declare function extractTitle(entries: Array<{
20
+ type: string;
21
+ content: {
22
+ summary?: string;
23
+ text?: string;
24
+ };
25
+ }>): string;
26
+ /**
27
+ * Extract timestamps from entries
28
+ */
29
+ export declare function extractTimestamps(entries: Array<{
30
+ timestamp: string;
31
+ }>): {
32
+ startedAt: string;
33
+ endedAt: string;
34
+ };
35
+ /**
36
+ * Extract metadata from a single JSONL file
37
+ */
38
+ export declare function extractSessionMetadata(jsonlPath: string, projectPath: string, projectSlug: string): Promise<SessionEntry | null>;
39
+ /**
40
+ * List all project directories in ~/.claude/projects/
41
+ */
42
+ export declare function listAllProjects(): Promise<Array<{
43
+ encodedPath: string;
44
+ projectPath: string;
45
+ projectSlug: string;
46
+ }>>;
47
+ /**
48
+ * Scan all sessions and build the index.
49
+ *
50
+ * Uses catalog-first loading: reads pre-extracted metadata from .jacques/index.json
51
+ * for each project, only falling back to JSONL parsing for new/uncataloged sessions.
52
+ */
53
+ export declare function buildSessionIndex(options?: {
54
+ /** Progress callback - called for each session scanned */
55
+ onProgress?: (progress: {
56
+ phase: "scanning" | "processing";
57
+ total: number;
58
+ completed: number;
59
+ current: string;
60
+ }) => void;
61
+ }): Promise<SessionIndex>;
62
+ //# sourceMappingURL=metadata-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-extractor.d.ts","sourceRoot":"","sources":["../../src/cache/metadata-extractor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAA0C,MAAM,YAAY,CAAC;AAWrG;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,GAC7E,MAAM,CA6BR;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,KAAK,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GACpC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoBxC;AAkID;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAyG9B;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAC9C,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CACzE,CAmCA;AA0MD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE;IAChD,0DAA0D;IAC1D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE;QACtB,KAAK,EAAE,UAAU,GAAG,YAAY,CAAC;QACjC,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,KAAK,IAAI,CAAC;CACZ,GAAG,OAAO,CAAC,YAAY,CAAC,CAyExB"}
@@ -0,0 +1,574 @@
1
+ /**
2
+ * Metadata Extractor
3
+ *
4
+ * Extracts session metadata from JSONL files and catalog data.
5
+ * Builds the session index using a catalog-first strategy.
6
+ */
7
+ import { promises as fs } from "fs";
8
+ import * as path from "path";
9
+ import { parseJSONL, getEntryStatistics } from "../session/parser.js";
10
+ import { listSubagentFiles, decodeProjectPath } from "../session/detector.js";
11
+ import { readProjectIndex } from "../context/indexer.js";
12
+ import { CLAUDE_PROJECTS_PATH } from "./types.js";
13
+ import { detectGitInfo, readGitBranchFromJsonl } from "./git-utils.js";
14
+ import { detectModeAndPlans } from "./mode-detector.js";
15
+ import { writeSessionIndex } from "./persistence.js";
16
+ import { isContinueSession } from "../session/format-title.js";
17
+ import { isNotFoundError, getErrorMessage } from "../logging/error-utils.js";
18
+ import { createLogger } from "../logging/logger.js";
19
+ const logger = createLogger({ prefix: "[Metadata]" });
20
+ /**
21
+ * Extract "## Current Task" from the most recent handoff created before sessionStartedAt.
22
+ * Returns "Cont: <task>" or null if no matching handoff found.
23
+ */
24
+ export async function extractContinueTitleFromHandoff(projectPath, sessionStartedAt) {
25
+ const handoffsDir = path.join(projectPath, ".jacques", "handoffs");
26
+ try {
27
+ const files = await fs.readdir(handoffsDir);
28
+ const handoffFiles = files
29
+ .filter((f) => f.endsWith("-handoff.md"))
30
+ .sort()
31
+ .reverse(); // newest first (filenames are ISO-ish timestamps)
32
+ for (const file of handoffFiles) {
33
+ // Filename: YYYY-MM-DDTHH-mm-ss-handoff.md → stat for reliable mtime comparison
34
+ const filePath = path.join(handoffsDir, file);
35
+ const stat = await fs.stat(filePath);
36
+ if (stat.mtime.getTime() > new Date(sessionStartedAt).getTime()) {
37
+ continue; // handoff created after session started — skip
38
+ }
39
+ const content = await fs.readFile(filePath, "utf-8");
40
+ const taskMatch = content.match(/## Current Task\n+(.+?)(?=\n## |\n*$)/s);
41
+ if (taskMatch) {
42
+ const task = taskMatch[1].trim();
43
+ const firstLine = task.split("\n")[0].trim();
44
+ if (firstLine) {
45
+ return `Cont: ${firstLine}`;
46
+ }
47
+ }
48
+ }
49
+ }
50
+ catch (err) {
51
+ if (!isNotFoundError(err)) {
52
+ logger.warn("Failed to read handoffs for continue title:", getErrorMessage(err));
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ /**
58
+ * Extract session title from parsed JSONL entries.
59
+ * Priority:
60
+ * 1. Summary entry (Claude's auto-generated title)
61
+ * 2. First real user message (skips internal command messages)
62
+ */
63
+ export function extractTitle(entries) {
64
+ // Try summary first
65
+ const summaryEntry = entries.find((e) => e.type === "summary" && e.content.summary);
66
+ if (summaryEntry?.content.summary) {
67
+ return summaryEntry.content.summary;
68
+ }
69
+ // Fallback to first real user message (skip internal command messages)
70
+ const userMessage = entries.find((e) => {
71
+ if (e.type !== "user_message" || !e.content.text)
72
+ return false;
73
+ const text = e.content.text.trim();
74
+ // Skip internal Claude Code messages
75
+ if (text.startsWith("<local-command"))
76
+ return false;
77
+ if (text.startsWith("<command-"))
78
+ return false;
79
+ if (text.length === 0)
80
+ return false;
81
+ return true;
82
+ });
83
+ if (userMessage?.content.text) {
84
+ // Truncate long messages
85
+ const text = userMessage.content.text.trim();
86
+ if (text.length > 100) {
87
+ return text.slice(0, 97) + "...";
88
+ }
89
+ return text;
90
+ }
91
+ return "Untitled Session";
92
+ }
93
+ /**
94
+ * Extract timestamps from entries
95
+ */
96
+ export function extractTimestamps(entries) {
97
+ if (entries.length === 0) {
98
+ const now = new Date().toISOString();
99
+ return { startedAt: now, endedAt: now };
100
+ }
101
+ // Find earliest and latest timestamps
102
+ let startedAt = entries[0].timestamp;
103
+ let endedAt = entries[0].timestamp;
104
+ for (const entry of entries) {
105
+ if (entry.timestamp < startedAt) {
106
+ startedAt = entry.timestamp;
107
+ }
108
+ if (entry.timestamp > endedAt) {
109
+ endedAt = entry.timestamp;
110
+ }
111
+ }
112
+ return { startedAt, endedAt };
113
+ }
114
+ /**
115
+ * Extract explore agents and web searches from entries.
116
+ * For explore agents, computes token cost from their subagent JSONL files.
117
+ */
118
+ async function extractAgentsAndSearches(entries, subagentFiles) {
119
+ const exploreAgents = [];
120
+ const webSearches = [];
121
+ const seenAgentIds = new Set();
122
+ const seenQueries = new Set();
123
+ // Build a map of agentId -> subagent file for quick lookup
124
+ const subagentFileMap = new Map();
125
+ for (const f of subagentFiles) {
126
+ subagentFileMap.set(f.agentId, f);
127
+ }
128
+ for (const entry of entries) {
129
+ // Extract explore agents from agent_progress entries
130
+ if (entry.type === 'agent_progress' && entry.content.agentType === 'Explore') {
131
+ const agentId = entry.content.agentId;
132
+ if (agentId && !seenAgentIds.has(agentId)) {
133
+ seenAgentIds.add(agentId);
134
+ exploreAgents.push({
135
+ id: agentId,
136
+ description: entry.content.agentDescription || 'Explore codebase',
137
+ timestamp: entry.timestamp,
138
+ });
139
+ }
140
+ }
141
+ // Extract web searches from web_search entries with results
142
+ if (entry.type === 'web_search' && entry.content.searchType === 'results') {
143
+ const query = entry.content.searchQuery;
144
+ if (query && !seenQueries.has(query)) {
145
+ seenQueries.add(query);
146
+ webSearches.push({
147
+ query,
148
+ resultCount: entry.content.searchResultCount || 0,
149
+ timestamp: entry.timestamp,
150
+ });
151
+ }
152
+ }
153
+ }
154
+ // Compute token costs for explore agents from their subagent JSONL files
155
+ for (const agent of exploreAgents) {
156
+ const subagentFile = subagentFileMap.get(agent.id);
157
+ if (subagentFile) {
158
+ try {
159
+ const subEntries = await parseJSONL(subagentFile.filePath);
160
+ if (subEntries.length > 0) {
161
+ const subStats = getEntryStatistics(subEntries);
162
+ // Total cost = last turn's context window size + estimated output
163
+ const inputCost = subStats.lastInputTokens + subStats.lastCacheRead;
164
+ const outputCost = subStats.totalOutputTokensEstimated;
165
+ agent.tokenCost = inputCost + outputCost;
166
+ }
167
+ }
168
+ catch (err) {
169
+ logger.warn(`Failed to parse subagent ${agent.id}:`, getErrorMessage(err));
170
+ }
171
+ }
172
+ }
173
+ return { exploreAgents, webSearches };
174
+ }
175
+ /**
176
+ * Convert a catalog SubagentEntry (type=exploration) to an ExploreAgentRef.
177
+ */
178
+ function catalogSubagentToExploreRef(entry) {
179
+ return {
180
+ id: entry.id,
181
+ description: entry.title,
182
+ timestamp: entry.timestamp,
183
+ tokenCost: entry.tokenCost,
184
+ };
185
+ }
186
+ /**
187
+ * Convert a catalog SubagentEntry (type=search) to a WebSearchRef.
188
+ */
189
+ function catalogSubagentToSearchRef(entry) {
190
+ return {
191
+ query: entry.title,
192
+ resultCount: entry.resultCount || 0,
193
+ timestamp: entry.timestamp,
194
+ };
195
+ }
196
+ /**
197
+ * Convert a catalog PlanEntry to a partial PlanRef.
198
+ * messageIndex is set to 0 since catalog doesn't track this.
199
+ */
200
+ function catalogPlanToPlanRef(plan) {
201
+ return {
202
+ title: plan.title,
203
+ source: "embedded",
204
+ messageIndex: 0,
205
+ catalogId: plan.id,
206
+ };
207
+ }
208
+ /**
209
+ * Read the session manifest JSON from .jacques/sessions/{id}.json.
210
+ * Returns null if file doesn't exist or is unreadable.
211
+ */
212
+ async function readSessionManifest(projectPath, sessionId) {
213
+ try {
214
+ const manifestPath = path.join(projectPath, ".jacques", "sessions", `${sessionId}.json`);
215
+ const content = await fs.readFile(manifestPath, "utf-8");
216
+ return JSON.parse(content);
217
+ }
218
+ catch (err) {
219
+ if (!isNotFoundError(err)) {
220
+ logger.warn(`Failed to read session manifest ${sessionId}:`, getErrorMessage(err));
221
+ }
222
+ return null;
223
+ }
224
+ }
225
+ /**
226
+ * Extract metadata from a single JSONL file
227
+ */
228
+ export async function extractSessionMetadata(jsonlPath, projectPath, projectSlug) {
229
+ try {
230
+ // Get file stats
231
+ const stats = await fs.stat(jsonlPath);
232
+ const sessionId = path.basename(jsonlPath, ".jsonl");
233
+ // Parse JSONL to get metadata
234
+ const entries = await parseJSONL(jsonlPath);
235
+ if (entries.length === 0) {
236
+ return null;
237
+ }
238
+ // Get statistics
239
+ const entryStats = getEntryStatistics(entries);
240
+ // Get timestamps
241
+ const { startedAt, endedAt } = extractTimestamps(entries);
242
+ // Get title — resolve continue sessions from handoff data
243
+ let title = extractTitle(entries);
244
+ if (isContinueSession(title)) {
245
+ const continueTitle = await extractContinueTitleFromHandoff(projectPath, startedAt);
246
+ if (continueTitle)
247
+ title = continueTitle;
248
+ }
249
+ // Check for subagents
250
+ const subagentFiles = await listSubagentFiles(jsonlPath);
251
+ // Filter out internal agents (prompt_suggestion, acompact) from user-visible count
252
+ // These are system agents that shouldn't appear in the subagent count
253
+ const userVisibleSubagents = subagentFiles.filter((f) => !f.agentId.startsWith('aprompt_suggestion-') &&
254
+ !f.agentId.startsWith('acompact-'));
255
+ // Track if auto-compact occurred (for showing indicator in UI)
256
+ const autoCompactFile = subagentFiles.find((f) => f.agentId.startsWith('acompact-'));
257
+ const hadAutoCompact = !!autoCompactFile;
258
+ const autoCompactAt = autoCompactFile?.modifiedAt.toISOString();
259
+ const hasSubagents = userVisibleSubagents.length > 0;
260
+ // Detect mode and plans
261
+ const { mode, planRefs } = detectModeAndPlans(entries);
262
+ // Extract explore agents and web searches (with token costs from subagent files)
263
+ const { exploreAgents, webSearches } = await extractAgentsAndSearches(entries, subagentFiles);
264
+ // Detect git info from project path
265
+ const gitInfo = await detectGitInfo(projectPath);
266
+ // If detectGitInfo failed (e.g., deleted worktree), read gitBranch from raw JSONL
267
+ if (!gitInfo.branch) {
268
+ gitInfo.branch = await readGitBranchFromJsonl(jsonlPath) || undefined;
269
+ }
270
+ // Use LAST turn's input tokens for context window size
271
+ // Each turn reports the FULL context, so summing would overcount
272
+ // Total context = fresh input + cache read (cache_creation is subset of fresh, not additional)
273
+ const totalInput = entryStats.lastInputTokens + entryStats.lastCacheRead;
274
+ // Use tiktoken-estimated output tokens (cumulative - each turn generates NEW output)
275
+ const totalOutput = entryStats.totalOutputTokensEstimated;
276
+ const hasTokens = totalInput > 0 || totalOutput > 0;
277
+ return {
278
+ id: sessionId,
279
+ jsonlPath,
280
+ projectPath,
281
+ projectSlug,
282
+ title,
283
+ startedAt,
284
+ endedAt,
285
+ messageCount: entryStats.userMessages + entryStats.assistantMessages,
286
+ toolCallCount: entryStats.toolCalls,
287
+ hasSubagents,
288
+ subagentIds: hasSubagents
289
+ ? userVisibleSubagents.map((f) => f.agentId)
290
+ : undefined,
291
+ hadAutoCompact: hadAutoCompact || undefined,
292
+ autoCompactAt: autoCompactAt || undefined,
293
+ tokens: hasTokens ? {
294
+ input: totalInput,
295
+ output: totalOutput,
296
+ cacheCreation: entryStats.lastCacheCreation,
297
+ cacheRead: entryStats.lastCacheRead,
298
+ } : undefined,
299
+ fileSizeBytes: stats.size,
300
+ modifiedAt: stats.mtime.toISOString(),
301
+ mode: mode || undefined,
302
+ planCount: planRefs.length > 0 ? planRefs.length : undefined,
303
+ planRefs: planRefs.length > 0 ? planRefs : undefined,
304
+ gitRepoRoot: gitInfo.repoRoot || undefined,
305
+ gitBranch: gitInfo.branch || undefined,
306
+ gitWorktree: gitInfo.worktree || undefined,
307
+ exploreAgents: exploreAgents.length > 0 ? exploreAgents : undefined,
308
+ webSearches: webSearches.length > 0 ? webSearches : undefined,
309
+ };
310
+ }
311
+ catch (err) {
312
+ const sessionId = path.basename(jsonlPath, ".jsonl");
313
+ logger.error(`Failed to extract metadata for ${sessionId}:`, getErrorMessage(err));
314
+ return null;
315
+ }
316
+ }
317
+ /**
318
+ * List all project directories in ~/.claude/projects/
319
+ */
320
+ export async function listAllProjects() {
321
+ const projects = [];
322
+ try {
323
+ const entries = await fs.readdir(CLAUDE_PROJECTS_PATH, {
324
+ withFileTypes: true,
325
+ });
326
+ const dirs = entries.filter((e) => e.isDirectory());
327
+ // Decode all project paths in parallel (each reads sessions-index.json or first JSONL)
328
+ const results = await Promise.all(dirs.map(async (entry) => {
329
+ const projectPath = await decodeProjectPath(entry.name);
330
+ const projectSlug = path.basename(projectPath);
331
+ return {
332
+ encodedPath: path.join(CLAUDE_PROJECTS_PATH, entry.name),
333
+ projectPath,
334
+ projectSlug,
335
+ };
336
+ }));
337
+ projects.push(...results);
338
+ }
339
+ catch (err) {
340
+ if (!isNotFoundError(err)) {
341
+ logger.warn("Failed to list projects:", getErrorMessage(err));
342
+ }
343
+ }
344
+ return projects;
345
+ }
346
+ /**
347
+ * Build session entries from catalog data (fast path).
348
+ *
349
+ * For each project:
350
+ * 1. Read .jacques/index.json for catalog metadata
351
+ * 2. List JSONL files in the encoded project dir
352
+ * 3. Stat JSONL files for size/mtime (in parallel)
353
+ * 4. Convert catalog entries to SessionEntry
354
+ * 5. Identify uncataloged JSONL files for fallback parsing
355
+ */
356
+ async function buildFromCatalog(projects) {
357
+ const catalogSessions = [];
358
+ const uncatalogedFiles = [];
359
+ for (const project of projects) {
360
+ // Read catalog index (returns empty default if missing)
361
+ const index = await readProjectIndex(project.projectPath);
362
+ // List JSONL files in the encoded project directory
363
+ let jsonlFilenames = [];
364
+ try {
365
+ const dirEntries = await fs.readdir(project.encodedPath, { withFileTypes: true });
366
+ jsonlFilenames = dirEntries
367
+ .filter((e) => e.isFile() && e.name.endsWith(".jsonl"))
368
+ .map((e) => e.name);
369
+ }
370
+ catch (err) {
371
+ if (!isNotFoundError(err)) {
372
+ logger.warn(`Skipping unreadable project dir ${project.projectSlug}:`, getErrorMessage(err));
373
+ }
374
+ continue;
375
+ }
376
+ // Build set of cataloged session IDs
377
+ const catalogedSessionIds = new Set(index.sessions.map((s) => s.id));
378
+ // Stat all JSONL files in parallel
379
+ const statResults = await Promise.all(jsonlFilenames.map(async (filename) => {
380
+ const jsonlPath = path.join(project.encodedPath, filename);
381
+ const sessionId = path.basename(filename, ".jsonl");
382
+ try {
383
+ const stats = await fs.stat(jsonlPath);
384
+ return { sessionId, jsonlPath, stats, filename };
385
+ }
386
+ catch {
387
+ return null; // File disappeared between readdir and stat (race condition)
388
+ }
389
+ }));
390
+ // Detect git info ONCE per project (not per session) — avoids spawning
391
+ // a git subprocess for every JSONL file in the same project directory.
392
+ const projectGitInfo = await detectGitInfo(project.projectPath);
393
+ if (!projectGitInfo.branch && jsonlFilenames.length > 0) {
394
+ projectGitInfo.branch = await readGitBranchFromJsonl(path.join(project.encodedPath, jsonlFilenames[0])) || undefined;
395
+ }
396
+ // If detectGitInfo failed (e.g., deleted worktree), try recovering
397
+ // gitRepoRoot from a previously-extracted session manifest.
398
+ if (!projectGitInfo.repoRoot) {
399
+ for (const result of statResults) {
400
+ if (!result)
401
+ continue;
402
+ const manifest = await readSessionManifest(project.projectPath, result.sessionId);
403
+ if (manifest?.gitRepoRoot) {
404
+ projectGitInfo.repoRoot = manifest.gitRepoRoot;
405
+ if (!projectGitInfo.branch && manifest.gitBranch) {
406
+ projectGitInfo.branch = manifest.gitBranch;
407
+ }
408
+ if (!projectGitInfo.worktree && manifest.gitWorktree) {
409
+ projectGitInfo.worktree = manifest.gitWorktree;
410
+ }
411
+ break;
412
+ }
413
+ }
414
+ }
415
+ for (const result of statResults) {
416
+ if (!result)
417
+ continue;
418
+ const { sessionId, jsonlPath, stats } = result;
419
+ if (!catalogedSessionIds.has(sessionId)) {
420
+ // Not in catalog - needs JSONL parsing
421
+ uncatalogedFiles.push({
422
+ filePath: jsonlPath,
423
+ projectPath: project.projectPath,
424
+ projectSlug: project.projectSlug,
425
+ });
426
+ continue;
427
+ }
428
+ // Find catalog session entry
429
+ const catalogSession = index.sessions.find((s) => s.id === sessionId);
430
+ if (!catalogSession)
431
+ continue;
432
+ // Staleness check: if JSONL is newer than catalog savedAt, re-parse
433
+ const jsonlMtime = stats.mtime.toISOString();
434
+ // Read the session manifest for planRefs and precise mtime check
435
+ const manifest = await readSessionManifest(project.projectPath, sessionId);
436
+ if (catalogSession.savedAt && jsonlMtime > catalogSession.savedAt) {
437
+ if (!manifest || jsonlMtime > manifest.jsonlModifiedAt) {
438
+ uncatalogedFiles.push({
439
+ filePath: jsonlPath,
440
+ projectPath: project.projectPath,
441
+ projectSlug: project.projectSlug,
442
+ });
443
+ continue;
444
+ }
445
+ }
446
+ // Map subagents from index
447
+ const exploreSubagents = index.subagents.filter((s) => s.sessionId === sessionId && s.type === "exploration");
448
+ const searchSubagents = index.subagents.filter((s) => s.sessionId === sessionId && s.type === "search");
449
+ // Use planRefs from manifest (preserves source: embedded/write/agent)
450
+ // Fall back to reconstructing from PlanEntry if manifest lacks planRefs
451
+ let planRefs = [];
452
+ if (manifest?.planRefs && manifest.planRefs.length > 0) {
453
+ // Manifest has full planRefs with correct source types
454
+ planRefs = manifest.planRefs.map((ref) => {
455
+ // Find matching catalogId from planIds
456
+ const catalogId = catalogSession.planIds?.find((pid) => index.plans.some((p) => p.id === pid));
457
+ return {
458
+ title: ref.title,
459
+ source: ref.source,
460
+ messageIndex: ref.messageIndex,
461
+ filePath: ref.filePath,
462
+ agentId: ref.agentId,
463
+ catalogId: ref.catalogId || catalogId,
464
+ };
465
+ });
466
+ }
467
+ else if (catalogSession.planIds) {
468
+ // Fallback: reconstruct from PlanEntry (older manifests without planRefs)
469
+ for (const planId of catalogSession.planIds) {
470
+ const plan = index.plans.find((p) => p.id === planId);
471
+ if (plan) {
472
+ planRefs.push(catalogPlanToPlanRef(plan));
473
+ }
474
+ }
475
+ }
476
+ const exploreAgents = exploreSubagents.map(catalogSubagentToExploreRef);
477
+ const webSearches = searchSubagents.map(catalogSubagentToSearchRef);
478
+ // Resolve continue session titles from handoff data
479
+ let resolvedTitle = catalogSession.title;
480
+ if (resolvedTitle && isContinueSession(resolvedTitle)) {
481
+ const continueTitle = await extractContinueTitleFromHandoff(project.projectPath, catalogSession.startedAt);
482
+ if (continueTitle)
483
+ resolvedTitle = continueTitle;
484
+ }
485
+ // Build SessionEntry from catalog data + file stats
486
+ const entry = {
487
+ id: sessionId,
488
+ jsonlPath,
489
+ projectPath: project.projectPath,
490
+ projectSlug: project.projectSlug,
491
+ title: resolvedTitle,
492
+ startedAt: catalogSession.startedAt,
493
+ endedAt: catalogSession.endedAt,
494
+ messageCount: catalogSession.messageCount,
495
+ toolCallCount: catalogSession.toolCallCount,
496
+ hasSubagents: catalogSession.hasSubagents ?? false,
497
+ subagentIds: catalogSession.subagentIds,
498
+ hadAutoCompact: catalogSession.hadAutoCompact || undefined,
499
+ tokens: catalogSession.tokens,
500
+ fileSizeBytes: stats.size,
501
+ modifiedAt: stats.mtime.toISOString(),
502
+ mode: catalogSession.mode || undefined,
503
+ planCount: planRefs.length > 0 ? planRefs.length : (catalogSession.planCount || undefined),
504
+ planRefs: planRefs.length > 0 ? planRefs : undefined,
505
+ gitRepoRoot: projectGitInfo.repoRoot || undefined,
506
+ gitBranch: projectGitInfo.branch || undefined,
507
+ gitWorktree: projectGitInfo.worktree || undefined,
508
+ exploreAgents: exploreAgents.length > 0 ? exploreAgents : undefined,
509
+ webSearches: webSearches.length > 0 ? webSearches : undefined,
510
+ };
511
+ catalogSessions.push(entry);
512
+ }
513
+ }
514
+ return { catalogSessions, uncatalogedFiles };
515
+ }
516
+ /**
517
+ * Scan all sessions and build the index.
518
+ *
519
+ * Uses catalog-first loading: reads pre-extracted metadata from .jacques/index.json
520
+ * for each project, only falling back to JSONL parsing for new/uncataloged sessions.
521
+ */
522
+ export async function buildSessionIndex(options) {
523
+ const { onProgress } = options || {};
524
+ onProgress?.({
525
+ phase: "scanning",
526
+ total: 0,
527
+ completed: 0,
528
+ current: "Scanning projects...",
529
+ });
530
+ // Get all projects
531
+ const projects = await listAllProjects();
532
+ // Phase 1: Read catalog data (fast - reads .jacques/index.json + stats JSONL files)
533
+ const { catalogSessions, uncatalogedFiles } = await buildFromCatalog(projects);
534
+ const totalFiles = catalogSessions.length + uncatalogedFiles.length;
535
+ onProgress?.({
536
+ phase: "processing",
537
+ total: totalFiles,
538
+ completed: catalogSessions.length,
539
+ current: `${catalogSessions.length} from catalog, ${uncatalogedFiles.length} to parse...`,
540
+ });
541
+ // Phase 2: Parse only uncataloged/stale sessions (slow path - only for new sessions)
542
+ const sessions = [...catalogSessions];
543
+ for (let i = 0; i < uncatalogedFiles.length; i++) {
544
+ const file = uncatalogedFiles[i];
545
+ const sessionId = path.basename(file.filePath, ".jsonl");
546
+ onProgress?.({
547
+ phase: "processing",
548
+ total: totalFiles,
549
+ completed: catalogSessions.length + i,
550
+ current: `${file.projectSlug}/${sessionId.substring(0, 8)}...`,
551
+ });
552
+ const metadata = await extractSessionMetadata(file.filePath, file.projectPath, file.projectSlug);
553
+ if (metadata) {
554
+ sessions.push(metadata);
555
+ }
556
+ }
557
+ // Sort by modification time (newest first)
558
+ sessions.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime());
559
+ const index = {
560
+ version: "2.0.0",
561
+ lastScanned: new Date().toISOString(),
562
+ sessions,
563
+ };
564
+ // Save to disk
565
+ await writeSessionIndex(index);
566
+ onProgress?.({
567
+ phase: "processing",
568
+ total: totalFiles,
569
+ completed: totalFiles,
570
+ current: "Complete",
571
+ });
572
+ return index;
573
+ }
574
+ //# sourceMappingURL=metadata-extractor.js.map