@ai-studio-3d/vyasa 0.1.0

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 (237) hide show
  1. package/README.md +69 -0
  2. package/data/common-kit.json +34 -0
  3. package/data/pricing.json +119 -0
  4. package/data/schema/AnalyticsData.json +214 -0
  5. package/data/schema/FoundationData.json +42 -0
  6. package/data/schema/KitData.json +321 -0
  7. package/data/schema/SearchData.json +173 -0
  8. package/data/schema/SessionDetailData.json +231 -0
  9. package/data/schema/SessionsListData.json +115 -0
  10. package/data/schema/WasteData.json +95 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +343 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/core/billing/index.d.ts +4 -0
  16. package/dist/core/billing/index.d.ts.map +1 -0
  17. package/dist/core/billing/index.js +43 -0
  18. package/dist/core/billing/index.js.map +1 -0
  19. package/dist/core/budget/index.d.ts +30 -0
  20. package/dist/core/budget/index.d.ts.map +1 -0
  21. package/dist/core/budget/index.js +61 -0
  22. package/dist/core/budget/index.js.map +1 -0
  23. package/dist/core/codex/index.d.ts +3 -0
  24. package/dist/core/codex/index.d.ts.map +1 -0
  25. package/dist/core/codex/index.js +212 -0
  26. package/dist/core/codex/index.js.map +1 -0
  27. package/dist/core/cursor/index.d.ts +3 -0
  28. package/dist/core/cursor/index.d.ts.map +1 -0
  29. package/dist/core/cursor/index.js +304 -0
  30. package/dist/core/cursor/index.js.map +1 -0
  31. package/dist/core/cursor/scan.d.ts +28 -0
  32. package/dist/core/cursor/scan.d.ts.map +1 -0
  33. package/dist/core/cursor/scan.js +79 -0
  34. package/dist/core/cursor/scan.js.map +1 -0
  35. package/dist/core/decisions/index.d.ts +21 -0
  36. package/dist/core/decisions/index.d.ts.map +1 -0
  37. package/dist/core/decisions/index.js +133 -0
  38. package/dist/core/decisions/index.js.map +1 -0
  39. package/dist/core/dlp/index.d.ts +7 -0
  40. package/dist/core/dlp/index.d.ts.map +1 -0
  41. package/dist/core/dlp/index.js +115 -0
  42. package/dist/core/dlp/index.js.map +1 -0
  43. package/dist/core/health/index.d.ts +10 -0
  44. package/dist/core/health/index.d.ts.map +1 -0
  45. package/dist/core/health/index.js +21 -0
  46. package/dist/core/health/index.js.map +1 -0
  47. package/dist/core/index/index.d.ts +9 -0
  48. package/dist/core/index/index.d.ts.map +1 -0
  49. package/dist/core/index/index.js +103 -0
  50. package/dist/core/index/index.js.map +1 -0
  51. package/dist/core/index/ingest.d.ts +16 -0
  52. package/dist/core/index/ingest.d.ts.map +1 -0
  53. package/dist/core/index/ingest.js +184 -0
  54. package/dist/core/index/ingest.js.map +1 -0
  55. package/dist/core/index/platforms.d.ts +21 -0
  56. package/dist/core/index/platforms.d.ts.map +1 -0
  57. package/dist/core/index/platforms.js +44 -0
  58. package/dist/core/index/platforms.js.map +1 -0
  59. package/dist/core/index/pricing-map.d.ts +10 -0
  60. package/dist/core/index/pricing-map.d.ts.map +1 -0
  61. package/dist/core/index/pricing-map.js +16 -0
  62. package/dist/core/index/pricing-map.js.map +1 -0
  63. package/dist/core/index/progress.d.ts +26 -0
  64. package/dist/core/index/progress.d.ts.map +1 -0
  65. package/dist/core/index/progress.js +59 -0
  66. package/dist/core/index/progress.js.map +1 -0
  67. package/dist/core/index/scan.d.ts +9 -0
  68. package/dist/core/index/scan.d.ts.map +1 -0
  69. package/dist/core/index/scan.js +42 -0
  70. package/dist/core/index/scan.js.map +1 -0
  71. package/dist/core/index/statements.d.ts +18 -0
  72. package/dist/core/index/statements.d.ts.map +1 -0
  73. package/dist/core/index/statements.js +82 -0
  74. package/dist/core/index/statements.js.map +1 -0
  75. package/dist/core/index/tail.d.ts +42 -0
  76. package/dist/core/index/tail.d.ts.map +1 -0
  77. package/dist/core/index/tail.js +123 -0
  78. package/dist/core/index/tail.js.map +1 -0
  79. package/dist/core/jsonl/index.d.ts +3 -0
  80. package/dist/core/jsonl/index.d.ts.map +1 -0
  81. package/dist/core/jsonl/index.js +106 -0
  82. package/dist/core/jsonl/index.js.map +1 -0
  83. package/dist/core/jsonl-validate/index.d.ts +10 -0
  84. package/dist/core/jsonl-validate/index.d.ts.map +1 -0
  85. package/dist/core/jsonl-validate/index.js +40 -0
  86. package/dist/core/jsonl-validate/index.js.map +1 -0
  87. package/dist/core/kit/detection.d.ts +94 -0
  88. package/dist/core/kit/detection.d.ts.map +1 -0
  89. package/dist/core/kit/detection.js +312 -0
  90. package/dist/core/kit/detection.js.map +1 -0
  91. package/dist/core/kit/index.d.ts +12 -0
  92. package/dist/core/kit/index.d.ts.map +1 -0
  93. package/dist/core/kit/index.js +131 -0
  94. package/dist/core/kit/index.js.map +1 -0
  95. package/dist/core/kit/scan.d.ts +28 -0
  96. package/dist/core/kit/scan.d.ts.map +1 -0
  97. package/dist/core/kit/scan.js +432 -0
  98. package/dist/core/kit/scan.js.map +1 -0
  99. package/dist/core/recap/index.d.ts +18 -0
  100. package/dist/core/recap/index.d.ts.map +1 -0
  101. package/dist/core/recap/index.js +88 -0
  102. package/dist/core/recap/index.js.map +1 -0
  103. package/dist/core/repetition/index.d.ts +5 -0
  104. package/dist/core/repetition/index.d.ts.map +1 -0
  105. package/dist/core/repetition/index.js +0 -0
  106. package/dist/core/repetition/index.js.map +1 -0
  107. package/dist/core/schema/index.d.ts +514 -0
  108. package/dist/core/schema/index.d.ts.map +1 -0
  109. package/dist/core/schema/index.js +2 -0
  110. package/dist/core/schema/index.js.map +1 -0
  111. package/dist/core/schema-watch/index.d.ts +34 -0
  112. package/dist/core/schema-watch/index.d.ts.map +1 -0
  113. package/dist/core/schema-watch/index.js +87 -0
  114. package/dist/core/schema-watch/index.js.map +1 -0
  115. package/dist/core/search/index.d.ts +44 -0
  116. package/dist/core/search/index.d.ts.map +1 -0
  117. package/dist/core/search/index.js +641 -0
  118. package/dist/core/search/index.js.map +1 -0
  119. package/dist/core/settings/index.d.ts +24 -0
  120. package/dist/core/settings/index.d.ts.map +1 -0
  121. package/dist/core/settings/index.js +76 -0
  122. package/dist/core/settings/index.js.map +1 -0
  123. package/dist/core/share/bundle.d.ts +40 -0
  124. package/dist/core/share/bundle.d.ts.map +1 -0
  125. package/dist/core/share/bundle.js +158 -0
  126. package/dist/core/share/bundle.js.map +1 -0
  127. package/dist/core/share/canonical/from-claude.d.ts +13 -0
  128. package/dist/core/share/canonical/from-claude.d.ts.map +1 -0
  129. package/dist/core/share/canonical/from-claude.js +129 -0
  130. package/dist/core/share/canonical/from-claude.js.map +1 -0
  131. package/dist/core/share/canonical/from-codex.d.ts +18 -0
  132. package/dist/core/share/canonical/from-codex.d.ts.map +1 -0
  133. package/dist/core/share/canonical/from-codex.js +183 -0
  134. package/dist/core/share/canonical/from-codex.js.map +1 -0
  135. package/dist/core/share/canonical/to-claude.d.ts +15 -0
  136. package/dist/core/share/canonical/to-claude.d.ts.map +1 -0
  137. package/dist/core/share/canonical/to-claude.js +146 -0
  138. package/dist/core/share/canonical/to-claude.js.map +1 -0
  139. package/dist/core/share/canonical/to-codex.d.ts +21 -0
  140. package/dist/core/share/canonical/to-codex.d.ts.map +1 -0
  141. package/dist/core/share/canonical/to-codex.js +124 -0
  142. package/dist/core/share/canonical/to-codex.js.map +1 -0
  143. package/dist/core/share/canonical/tool-map.d.ts +61 -0
  144. package/dist/core/share/canonical/tool-map.d.ts.map +1 -0
  145. package/dist/core/share/canonical/tool-map.js +299 -0
  146. package/dist/core/share/canonical/tool-map.js.map +1 -0
  147. package/dist/core/share/canonical/types.d.ts +57 -0
  148. package/dist/core/share/canonical/types.d.ts.map +1 -0
  149. package/dist/core/share/canonical/types.js +9 -0
  150. package/dist/core/share/canonical/types.js.map +1 -0
  151. package/dist/core/share/import.d.ts +28 -0
  152. package/dist/core/share/import.d.ts.map +1 -0
  153. package/dist/core/share/import.js +174 -0
  154. package/dist/core/share/import.js.map +1 -0
  155. package/dist/core/share/manifest.d.ts +37 -0
  156. package/dist/core/share/manifest.d.ts.map +1 -0
  157. package/dist/core/share/manifest.js +31 -0
  158. package/dist/core/share/manifest.js.map +1 -0
  159. package/dist/core/share/preview.d.ts +4 -0
  160. package/dist/core/share/preview.d.ts.map +1 -0
  161. package/dist/core/share/preview.js +153 -0
  162. package/dist/core/share/preview.js.map +1 -0
  163. package/dist/core/share/primer.d.ts +19 -0
  164. package/dist/core/share/primer.d.ts.map +1 -0
  165. package/dist/core/share/primer.js +196 -0
  166. package/dist/core/share/primer.js.map +1 -0
  167. package/dist/core/share/resume.d.ts +10 -0
  168. package/dist/core/share/resume.d.ts.map +1 -0
  169. package/dist/core/share/resume.js +58 -0
  170. package/dist/core/share/resume.js.map +1 -0
  171. package/dist/core/share/scrub.d.ts +10 -0
  172. package/dist/core/share/scrub.d.ts.map +1 -0
  173. package/dist/core/share/scrub.js +198 -0
  174. package/dist/core/share/scrub.js.map +1 -0
  175. package/dist/core/share/tar.d.ts +12 -0
  176. package/dist/core/share/tar.d.ts.map +1 -0
  177. package/dist/core/share/tar.js +78 -0
  178. package/dist/core/share/tar.js.map +1 -0
  179. package/dist/core/tags/index.d.ts +48 -0
  180. package/dist/core/tags/index.d.ts.map +1 -0
  181. package/dist/core/tags/index.js +113 -0
  182. package/dist/core/tags/index.js.map +1 -0
  183. package/dist/core/today/index.d.ts +25 -0
  184. package/dist/core/today/index.d.ts.map +1 -0
  185. package/dist/core/today/index.js +42 -0
  186. package/dist/core/today/index.js.map +1 -0
  187. package/dist/core/waste/abandoned.d.ts +12 -0
  188. package/dist/core/waste/abandoned.d.ts.map +1 -0
  189. package/dist/core/waste/abandoned.js +127 -0
  190. package/dist/core/waste/abandoned.js.map +1 -0
  191. package/dist/core/waste/cache-miss.d.ts +9 -0
  192. package/dist/core/waste/cache-miss.d.ts.map +1 -0
  193. package/dist/core/waste/cache-miss.js +84 -0
  194. package/dist/core/waste/cache-miss.js.map +1 -0
  195. package/dist/core/waste/index.d.ts +12 -0
  196. package/dist/core/waste/index.d.ts.map +1 -0
  197. package/dist/core/waste/index.js +45 -0
  198. package/dist/core/waste/index.js.map +1 -0
  199. package/dist/core/waste/wrong-model.d.ts +11 -0
  200. package/dist/core/waste/wrong-model.d.ts.map +1 -0
  201. package/dist/core/waste/wrong-model.js +104 -0
  202. package/dist/core/waste/wrong-model.js.map +1 -0
  203. package/dist/db/index.d.ts +10 -0
  204. package/dist/db/index.d.ts.map +1 -0
  205. package/dist/db/index.js +29 -0
  206. package/dist/db/index.js.map +1 -0
  207. package/dist/db/migrate.d.ts +8 -0
  208. package/dist/db/migrate.d.ts.map +1 -0
  209. package/dist/db/migrate.js +282 -0
  210. package/dist/db/migrate.js.map +1 -0
  211. package/dist/server/events.d.ts +4 -0
  212. package/dist/server/events.d.ts.map +1 -0
  213. package/dist/server/events.js +54 -0
  214. package/dist/server/events.js.map +1 -0
  215. package/dist/server/index.d.ts +7 -0
  216. package/dist/server/index.d.ts.map +1 -0
  217. package/dist/server/index.js +238 -0
  218. package/dist/server/index.js.map +1 -0
  219. package/dist/server/routes/foundation.d.ts +2 -0
  220. package/dist/server/routes/foundation.d.ts.map +1 -0
  221. package/dist/server/routes/foundation.js +2 -0
  222. package/dist/server/routes/foundation.js.map +1 -0
  223. package/dist/server/routes/search.d.ts +2 -0
  224. package/dist/server/routes/search.d.ts.map +1 -0
  225. package/dist/server/routes/search.js +2 -0
  226. package/dist/server/routes/search.js.map +1 -0
  227. package/dist/server/routes/sessions.d.ts +2 -0
  228. package/dist/server/routes/sessions.d.ts.map +1 -0
  229. package/dist/server/routes/sessions.js +2 -0
  230. package/dist/server/routes/sessions.js.map +1 -0
  231. package/dist/server/routes/waste.d.ts +2 -0
  232. package/dist/server/routes/waste.d.ts.map +1 -0
  233. package/dist/server/routes/waste.js +2 -0
  234. package/dist/server/routes/waste.js.map +1 -0
  235. package/dist/web/assets/index-Ba1VvTj0.js +37 -0
  236. package/dist/web/index.html +12 -0
  237. package/package.json +76 -0
@@ -0,0 +1,79 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import Database from 'better-sqlite3';
5
+ import { indexProgress } from '../index/progress.js';
6
+ /**
7
+ * Cursor stores chat data in TWO places (current schema, late-2025/early-2026):
8
+ *
9
+ * 1. ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb
10
+ * • cursorDiskKV rows keyed `composerData:<id>` and `bubbleId:<composerId>:<bubbleId>`
11
+ * • This is where the actual conversation data lives.
12
+ *
13
+ * 2. ~/Library/Application Support/Cursor/User/workspaceStorage/<hash>/state.vscdb
14
+ * • workspace.json sibling holds the `folder` URI (project mapping)
15
+ * • ItemTable['composer.composerData'] lists composer IDs for that workspace
16
+ * • cursorDiskKV in workspace storage is empty in this version
17
+ *
18
+ * scan() returns one entry per composer using synthetic path encoding
19
+ * `<global-vscdb-path>#<composerId>`. The parser splits on '#' and queries
20
+ * the global DB for that one composer. mtime is the global vscdb mtime —
21
+ * coarse (any Cursor write marks all composers dirty) but acceptable: a single
22
+ * composer parses in milliseconds and the indexer's mtime-skip keeps unchanged
23
+ * sessions out of re-ingest.
24
+ *
25
+ * `rootDir` is the workspaceStorage directory (kept for API symmetry with
26
+ * other adapters); the global DB is resolved as its sibling at
27
+ * `../globalStorage/state.vscdb`.
28
+ */
29
+ export async function scanCursorWorkspaces(rootDir) {
30
+ const results = [];
31
+ // Global storage path: sibling of workspaceStorage under User/
32
+ const userDir = dirname(rootDir);
33
+ const globalDbPath = join(userDir, 'globalStorage', 'state.vscdb');
34
+ if (!existsSync(globalDbPath)) {
35
+ return results;
36
+ }
37
+ let mtimeMs;
38
+ try {
39
+ const s = await stat(globalDbPath);
40
+ mtimeMs = s.mtimeMs;
41
+ }
42
+ catch {
43
+ return results;
44
+ }
45
+ // Read all composerData:<id> keys from the global DB
46
+ let composerIds = [];
47
+ try {
48
+ const db = new Database(globalDbPath, { readonly: true, fileMustExist: true });
49
+ try {
50
+ // Verify table exists
51
+ const tables = db
52
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='cursorDiskKV'")
53
+ .all();
54
+ if (tables.length === 0) {
55
+ db.close();
56
+ return results;
57
+ }
58
+ const rows = db
59
+ .prepare("SELECT key FROM cursorDiskKV WHERE key LIKE 'composerData:%'")
60
+ .all();
61
+ composerIds = rows.map((r) => r.key.slice('composerData:'.length));
62
+ }
63
+ finally {
64
+ db.close();
65
+ }
66
+ }
67
+ catch (err) {
68
+ console.error(`[vyasa] cursor scan failed for ${globalDbPath}:`, err);
69
+ indexProgress.recordError();
70
+ return results;
71
+ }
72
+ // List everything from global; the parser will skip empties. The
73
+ // workspaceStorage walk happens at parse-time for project-name mapping.
74
+ for (const composerId of composerIds) {
75
+ results.push({ path: `${globalDbPath}#${composerId}`, mtimeMs });
76
+ }
77
+ return results;
78
+ }
79
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/core/cursor/scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe;IAEf,MAAM,OAAO,GAA6C,EAAE,CAAA;IAE5D,+DAA+D;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAChC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,eAAe,EAAE,aAAa,CAAC,CAAA;IAElE,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,IAAI,OAAe,CAAA;IACnB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,CAAA;QAClC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAA;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,qDAAqD;IACrD,IAAI,WAAW,GAAa,EAAE,CAAA;IAC9B,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9E,IAAI,CAAC;YACH,sBAAsB;YACtB,MAAM,MAAM,GAAG,EAAE;iBACd,OAAO,CAAC,2EAA2E,CAAC;iBACpF,GAAG,EAA6B,CAAA;YACnC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,EAAE,CAAC,KAAK,EAAE,CAAA;gBACV,OAAO,OAAO,CAAA;YAChB,CAAC;YACD,MAAM,IAAI,GAAG,EAAE;iBACZ,OAAO,CAAC,8DAA8D,CAAC;iBACvE,GAAG,EAA4B,CAAA;YAClC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAA;QACpE,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,KAAK,EAAE,CAAA;QACZ,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,kCAAkC,YAAY,GAAG,EAAE,GAAG,CAAC,CAAA;QACrE,aAAa,CAAC,WAAW,EAAE,CAAA;QAC3B,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,iEAAiE;IACjE,wEAAwE;IACxE,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,YAAY,IAAI,UAAU,EAAE,EAAE,OAAO,EAAE,CAAC,CAAA;IAClE,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { PlanEvent } from '../schema/index.js';
3
+ /**
4
+ * Derive plan/decision events for one session from the messages it already has.
5
+ *
6
+ * Signals (all read from persisted data — no re-parsing of JSONL):
7
+ * - plan_finalized — an assistant ExitPlanMode tool_use (first occurrence)
8
+ * - plan_reopened — a later ExitPlanMode after a prior finalize (plan was re-entered + re-finalized)
9
+ * - model_change — the messages.model column changes between consecutive real assistant turns
10
+ * - permission_change — the messages.permission_mode column changes between consecutive records
11
+ * that carry a mode (e.g. default → acceptEdits). The first observed mode
12
+ * sets the baseline and emits nothing (null → value boot is not a change).
13
+ * permission_mode is persisted only on user records; other roles store null
14
+ * and are skipped for this signal.
15
+ *
16
+ * tokensInSpan = input+output tokens accumulated from this event (inclusive of its own row)
17
+ * up to — but not including — the next event's row (or session end). Empty array is the
18
+ * common, correct result: only ~4% of sessions carry any of these signals.
19
+ */
20
+ export declare function getDecisions(db: Database.Database, sessionId: string): PlanEvent[];
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/decisions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,oBAAoB,CAAA;AA6ClE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CA4FlF"}
@@ -0,0 +1,133 @@
1
+ /** Synthetic assistant turns (stop/error placeholders) carry model '<synthetic>'.
2
+ * They are not real model selections, so they must never trigger a model_change. */
3
+ function isRealModel(m) {
4
+ return !!m && m !== '<synthetic>';
5
+ }
6
+ /** True if a stored assistant message envelope contains an ExitPlanMode tool_use.
7
+ * content_json is JSON.stringify(message) → { ..., content: ContentBlock[] }. */
8
+ function hasExitPlanMode(contentJson) {
9
+ // Cheap string pre-filter before paying for JSON.parse on every assistant row.
10
+ if (contentJson.indexOf('ExitPlanMode') === -1)
11
+ return false;
12
+ let parsed;
13
+ try {
14
+ parsed = JSON.parse(contentJson);
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ const content = parsed?.content;
20
+ if (!Array.isArray(content))
21
+ return false;
22
+ return content.some((b) => b && typeof b === 'object' &&
23
+ b.type === 'tool_use' &&
24
+ b.name === 'ExitPlanMode');
25
+ }
26
+ function shortModel(m) {
27
+ // claude-opus-4-7 → opus-4-7; leave bare aliases (sonnet, etc.) as-is.
28
+ return m.replace(/^claude-/, '');
29
+ }
30
+ /**
31
+ * Derive plan/decision events for one session from the messages it already has.
32
+ *
33
+ * Signals (all read from persisted data — no re-parsing of JSONL):
34
+ * - plan_finalized — an assistant ExitPlanMode tool_use (first occurrence)
35
+ * - plan_reopened — a later ExitPlanMode after a prior finalize (plan was re-entered + re-finalized)
36
+ * - model_change — the messages.model column changes between consecutive real assistant turns
37
+ * - permission_change — the messages.permission_mode column changes between consecutive records
38
+ * that carry a mode (e.g. default → acceptEdits). The first observed mode
39
+ * sets the baseline and emits nothing (null → value boot is not a change).
40
+ * permission_mode is persisted only on user records; other roles store null
41
+ * and are skipped for this signal.
42
+ *
43
+ * tokensInSpan = input+output tokens accumulated from this event (inclusive of its own row)
44
+ * up to — but not including — the next event's row (or session end). Empty array is the
45
+ * common, correct result: only ~4% of sessions carry any of these signals.
46
+ */
47
+ export function getDecisions(db, sessionId) {
48
+ // All roles, in idx order: assistant rows carry model/ExitPlanMode signals; user rows
49
+ // carry the persisted permission_mode. Token usage only ever joins on assistant rows.
50
+ const rows = db
51
+ .prepare(`SELECT m.idx AS idx,
52
+ m.ts AS ts,
53
+ m.role AS role,
54
+ m.model AS model,
55
+ m.permission_mode AS permission_mode,
56
+ m.content_json AS content_json,
57
+ u.input_tokens AS input_tokens,
58
+ u.output_tokens AS output_tokens
59
+ FROM messages m
60
+ LEFT JOIN usage u ON u.message_id = m.id
61
+ WHERE m.session_id = ?
62
+ ORDER BY m.idx ASC`)
63
+ .all(sessionId);
64
+ if (rows.length === 0)
65
+ return [];
66
+ const events = [];
67
+ let lastRealModel = null;
68
+ let lastPermMode = null;
69
+ let seenFinalize = false;
70
+ for (const r of rows) {
71
+ // permission_change — fire only between two records that both carry a mode.
72
+ // The first observed mode sets the baseline (null → value boot emits nothing).
73
+ if (r.permission_mode) {
74
+ if (lastPermMode !== null && r.permission_mode !== lastPermMode) {
75
+ events.push({
76
+ msgIdx: r.idx,
77
+ ts: r.ts,
78
+ kind: 'permission_change',
79
+ detail: `${lastPermMode} → ${r.permission_mode}`,
80
+ tokensInSpan: null,
81
+ });
82
+ }
83
+ lastPermMode = r.permission_mode;
84
+ }
85
+ if (r.role !== 'assistant')
86
+ continue;
87
+ // model_change — fire only between two real (non-synthetic) models.
88
+ if (isRealModel(r.model)) {
89
+ if (lastRealModel !== null && r.model !== lastRealModel) {
90
+ events.push({
91
+ msgIdx: r.idx,
92
+ ts: r.ts,
93
+ kind: 'model_change',
94
+ detail: `${shortModel(lastRealModel)} → ${shortModel(r.model)}`,
95
+ tokensInSpan: null,
96
+ });
97
+ }
98
+ lastRealModel = r.model;
99
+ }
100
+ // plan_finalized / plan_reopened — an ExitPlanMode tool_use in this assistant turn.
101
+ if (hasExitPlanMode(r.content_json)) {
102
+ const kind = seenFinalize ? 'plan_reopened' : 'plan_finalized';
103
+ events.push({
104
+ msgIdx: r.idx,
105
+ ts: r.ts,
106
+ kind,
107
+ detail: seenFinalize ? 'plan revised' : 'plan accepted',
108
+ tokensInSpan: null,
109
+ });
110
+ seenFinalize = true;
111
+ }
112
+ }
113
+ if (events.length === 0)
114
+ return events;
115
+ // Order by msgIdx (model_change and an ExitPlanMode can land on the same row; stable enough).
116
+ events.sort((a, b) => a.msgIdx - b.msgIdx);
117
+ // tokensInSpan: walk the assistant rows once, bucketing token totals into the span that
118
+ // starts at each event. A row belongs to the most recent event whose msgIdx <= row.idx.
119
+ let cursor = 0;
120
+ for (const r of rows) {
121
+ // advance cursor to the last event that has started by this row
122
+ while (cursor + 1 < events.length && events[cursor + 1].msgIdx <= r.idx)
123
+ cursor++;
124
+ if (events[cursor].msgIdx > r.idx)
125
+ continue; // row precedes the first event — no span yet
126
+ const tok = (r.input_tokens ?? 0) + (r.output_tokens ?? 0);
127
+ if (tok > 0) {
128
+ events[cursor].tokensInSpan = (events[cursor].tokensInSpan ?? 0) + tok;
129
+ }
130
+ }
131
+ return events;
132
+ }
133
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/decisions/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { DlpFinding } from '../schema/index.js';
3
+ /** Scan one session's stored content for secrets/PII (for the nudge). */
4
+ export declare function scanSession(db: Database.Database, sessionId: string): DlpFinding[];
5
+ /** Scan sessions active since `sinceMs` (for the recap). Bounded to recently-touched sessions. */
6
+ export declare function scanRecent(db: Database.Database, sinceMs: number): DlpFinding[];
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/dlp/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAA;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAgIpD,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,EAAE,CAKlF;AAED,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAe/E"}
@@ -0,0 +1,115 @@
1
+ import { getSessionMeta } from '../tags/index.js';
2
+ // Structured, high-entropy credential shapes. Anchored on vendor prefixes + length so random
3
+ // prose doesn't trip them. `g` flag so we can scan each text once for multiple hits.
4
+ const PATTERNS = [
5
+ { re: /\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{20,}/g, kind: 'secret', reason: 'possible API key (sk-…)' },
6
+ { re: /\bgh[pousr]_[A-Za-z0-9]{30,}/g, kind: 'secret', reason: 'possible GitHub token' },
7
+ { re: /\bAKIA[0-9A-Z]{16}\b/g, kind: 'secret', reason: 'possible AWS access key' },
8
+ { re: /\bASIA[0-9A-Z]{16}\b/g, kind: 'secret', reason: 'possible AWS temporary key' },
9
+ { re: /\bxox[baprs]-[A-Za-z0-9-]{20,}/g, kind: 'secret', reason: 'possible Slack token' },
10
+ { re: /\bAIza[0-9A-Za-z_-]{35}\b/g, kind: 'secret', reason: 'possible Google API key' },
11
+ { re: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, kind: 'secret', reason: 'possible JWT' },
12
+ { re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g, kind: 'secret', reason: 'possible private key' },
13
+ // Gated email: only when it sits in a credential assignment (password/secret/token = …),
14
+ // not the bare-email match the scrubber uses (which flooded on signatures + docs).
15
+ {
16
+ re: /\b(?:password|passwd|secret|token|api[_-]?key|credential)["'\s]*[:=]\s*["']?[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/gi,
17
+ kind: 'pii',
18
+ reason: 'possible credential with email',
19
+ },
20
+ ];
21
+ // Placeholder / example / redacted tokens — never worth a nudge. If a match contains any of
22
+ // these (case-insensitive) we drop it.
23
+ const PLACEHOLDER_RE = /xxxx|yyyy|zzzz|your[_-]?key|your[_-]?token|example|placeholder|redacted|dummy|sample|test[_-]?key|sk-xxx|\.\.\.|<[a-z_]+>|\bfake\b|\bchangeme\b|\binsert\b/i;
24
+ function isPlaceholder(match) {
25
+ return PLACEHOLDER_RE.test(match);
26
+ }
27
+ /** Mask the middle of a secret so the preview proves the hit without leaking it. */
28
+ function maskSecret(secret) {
29
+ if (secret.length <= 8)
30
+ return '•'.repeat(secret.length);
31
+ const head = secret.slice(0, 4);
32
+ const tail = secret.slice(-2);
33
+ return `${head}${'•'.repeat(Math.min(secret.length - 6, 12))}${tail}`;
34
+ }
35
+ /** Build a short REDACTED context snippet around the match, with the secret masked. */
36
+ function redactedPreview(text, match, index) {
37
+ const start = Math.max(0, index - 24);
38
+ const end = Math.min(text.length, index + match.length + 24);
39
+ const before = text.slice(start, index);
40
+ const after = text.slice(index + match.length, end);
41
+ const snippet = `${start > 0 ? '…' : ''}${before}${maskSecret(match)}${after}${end < text.length ? '…' : ''}`;
42
+ return snippet.replace(/\s+/g, ' ').trim().slice(0, 120);
43
+ }
44
+ /** Scan the joined message/tool text for one session id; emit at most one finding per (msgIdx, reason). */
45
+ function scanRows(rows, sessionId, name) {
46
+ const findings = [];
47
+ const seen = new Set(); // msgIdx + reason — keeps a noisy session to one line per kind
48
+ for (const row of rows) {
49
+ // Concatenate the row's scannable surfaces. content_json/*.json are stored JSON; we scan
50
+ // the raw text so a key embedded anywhere in a structured value is still caught.
51
+ const haystack = [row.content_json, row.input_json, row.output_json].filter(Boolean).join('\n');
52
+ if (!haystack)
53
+ continue;
54
+ for (const { re, kind, reason } of PATTERNS) {
55
+ re.lastIndex = 0;
56
+ let m;
57
+ while ((m = re.exec(haystack)) !== null) {
58
+ const raw = m[0];
59
+ if (raw.length === 0) {
60
+ re.lastIndex++; // guard against zero-width loops
61
+ continue;
62
+ }
63
+ if (isPlaceholder(raw))
64
+ continue;
65
+ const key = `${row.idx}:${reason}`;
66
+ if (seen.has(key))
67
+ continue;
68
+ seen.add(key);
69
+ findings.push({
70
+ sessionId,
71
+ name,
72
+ msgIdx: row.idx,
73
+ kind,
74
+ reason,
75
+ preview: redactedPreview(haystack, raw, m.index),
76
+ });
77
+ }
78
+ }
79
+ }
80
+ return findings;
81
+ }
82
+ const ROW_QUERY = `
83
+ SELECT m.idx AS idx, m.content_json AS content_json,
84
+ tc.input_json AS input_json, tc.output_json AS output_json
85
+ FROM messages m
86
+ LEFT JOIN tool_calls tc ON tc.message_id = m.id
87
+ WHERE m.session_id = ?
88
+ ORDER BY m.idx ASC
89
+ `;
90
+ /** Scan one session's stored content for secrets/PII (for the nudge). */
91
+ export function scanSession(db, sessionId) {
92
+ const rows = db.prepare(ROW_QUERY).all(sessionId);
93
+ if (rows.length === 0)
94
+ return [];
95
+ const name = getSessionMeta(db, sessionId).name;
96
+ return scanRows(rows, sessionId, name);
97
+ }
98
+ /** Scan sessions active since `sinceMs` (for the recap). Bounded to recently-touched sessions. */
99
+ export function scanRecent(db, sinceMs) {
100
+ const sinceIso = new Date(sinceMs).toISOString();
101
+ // Activity = the session's last touch (ended_at, falling back to started_at). ISO timestamps
102
+ // compare lexicographically, so a string >= filter is correct here.
103
+ const sessions = db.prepare(`
104
+ SELECT id FROM sessions
105
+ WHERE COALESCE(ended_at, started_at) >= ?
106
+ ORDER BY COALESCE(ended_at, started_at) DESC
107
+ `).all(sinceIso);
108
+ const out = [];
109
+ for (const { id } of sessions) {
110
+ for (const f of scanSession(db, id))
111
+ out.push(f);
112
+ }
113
+ return out;
114
+ }
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/dlp/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { SessionListRow, SessionHealth } from '../schema/index.js';
2
+ /**
3
+ * Classify a session's run-health from its raw error count + last outcome.
4
+ * Pure — no db access. `error` wins over `warn`; `clean` is the default.
5
+ * - error: at least one tool call errored
6
+ * - warn: no errors but the run was abandoned
7
+ * - clean: otherwise
8
+ */
9
+ export declare function classifyHealth(row: Pick<SessionListRow, 'errorCount' | 'lastOutcome'>): SessionHealth;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/health/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,GAAG,aAAa,CAAC,GAAG,aAAa,CAerG"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Classify a session's run-health from its raw error count + last outcome.
3
+ * Pure — no db access. `error` wins over `warn`; `clean` is the default.
4
+ * - error: at least one tool call errored
5
+ * - warn: no errors but the run was abandoned
6
+ * - clean: otherwise
7
+ */
8
+ export function classifyHealth(row) {
9
+ const errorCount = row.errorCount ?? 0;
10
+ const reasons = [];
11
+ if (errorCount > 0) {
12
+ reasons.push(errorCount === 1 ? '1 tool error' : `${errorCount} tool errors`);
13
+ return { status: 'error', errorCount, reasons };
14
+ }
15
+ if (row.lastOutcome === 'abandoned') {
16
+ reasons.push('abandoned');
17
+ return { status: 'warn', errorCount, reasons };
18
+ }
19
+ return { status: 'clean', errorCount, reasons };
20
+ }
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/health/index.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,GAAuD;IACpF,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,CAAC,CAAA;IACtC,MAAM,OAAO,GAAa,EAAE,CAAA;IAE5B,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,UAAU,cAAc,CAAC,CAAA;QAC7E,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAA;IACjD,CAAC;IAED,IAAI,GAAG,CAAC,WAAW,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACzB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAA;IAChD,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAA;AACjD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { PlatformAdapter } from './platforms.js';
3
+ export interface IndexStats {
4
+ filesScanned: number;
5
+ filesIngested: number;
6
+ durationMs: number;
7
+ }
8
+ export declare function buildIndex(db: Database.Database, adapters: PlatformAdapter[]): Promise<IndexStats>;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/index/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAIrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAUrD,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,QAAQ,EAAE,eAAe,EAAE,GAC1B,OAAO,CAAC,UAAU,CAAC,CAgHrB"}
@@ -0,0 +1,103 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { compareToGolden } from '../schema-watch/index.js';
5
+ import { prepareIngestStatements } from './statements.js';
6
+ import { loadPricingMap } from './pricing-map.js';
7
+ import { ingestFromParsed } from './ingest.js';
8
+ import { computeAllWaste } from '../waste/index.js';
9
+ import { indexProgress } from './progress.js';
10
+ export async function buildIndex(db, adapters) {
11
+ const start = Date.now();
12
+ let filesScanned = 0;
13
+ let filesIngested = 0;
14
+ const stmtGetMtime = db.prepare('SELECT mtime_ms FROM _file_index WHERE path = ?');
15
+ const stmts = prepareIngestStatements(db);
16
+ const pricingMap = loadPricingMap(db);
17
+ const allFiles = [];
18
+ for (const adapter of adapters) {
19
+ const files = await adapter.scan();
20
+ filesScanned += files.length;
21
+ for (const f of files) {
22
+ const row = stmtGetMtime.get(f.path);
23
+ if (row !== undefined && row.mtime_ms === f.mtimeMs)
24
+ continue; // mtime-skip
25
+ allFiles.push({ path: f.path, mtimeMs: f.mtimeMs, adapter });
26
+ }
27
+ }
28
+ // 2. Sort most-recent first — critical for onboarding UX
29
+ allFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
30
+ // 3. Update progress state
31
+ indexProgress.begin(allFiles.length);
32
+ indexProgress.setPhase('indexing');
33
+ // 4. Batch-sequential ingest: parse N files, flush in one db.transaction (minimises FTS5 segments)
34
+ const BATCH_SIZE = 50;
35
+ for (let i = 0; i < allFiles.length; i += BATCH_SIZE) {
36
+ const batch = allFiles.slice(i, i + BATCH_SIZE);
37
+ const parsedBatch = [];
38
+ for (const { path, mtimeMs, adapter } of batch) {
39
+ try {
40
+ const parsed = await adapter.parse(path);
41
+ if (parsed.sessionId)
42
+ parsedBatch.push({ filePath: path, mtimeMs, platform: adapter.id, parsed });
43
+ }
44
+ catch (err) {
45
+ // Log the actual error — bare `failed to parse <path>` masked a real
46
+ // null-toString bug in the Cursor parser for weeks (May 2026).
47
+ indexProgress.recordError();
48
+ console.error(`[vyasa] failed to parse ${path}:`, err);
49
+ }
50
+ }
51
+ db.transaction(() => {
52
+ for (const item of parsedBatch) {
53
+ try {
54
+ ingestFromParsed(db, stmts, pricingMap, item.filePath, item.mtimeMs, item.platform, item.parsed);
55
+ }
56
+ catch (err) {
57
+ indexProgress.recordError();
58
+ console.error(`[vyasa] failed to ingest ${item.filePath}:`, err);
59
+ }
60
+ }
61
+ })();
62
+ filesIngested += parsedBatch.length;
63
+ indexProgress.filesCompleted(parsedBatch.length);
64
+ }
65
+ indexProgress.finish();
66
+ // Recompute waste attributions over the freshly-indexed data. The detectors
67
+ // are whole-DB SQL aggregations (not per-file), so this runs once after
68
+ // ingestion. MUST stay above the schema-drift early-return below — that
69
+ // return fires on every install without the golden file (i.e. all real users).
70
+ computeAllWaste(db);
71
+ // Schema drift check — only runs when the golden file is present (dev/maintainer only; not shipped in npm package)
72
+ try {
73
+ const goldenPath = join(fileURLToPath(import.meta.url), '../../../../data/schema-fingerprints.json');
74
+ if (!existsSync(goldenPath))
75
+ return { filesScanned, filesIngested, durationMs: Date.now() - start };
76
+ const observed = new Map();
77
+ const obsRows = db.prepare('SELECT platform, fingerprint, first_seen_file, first_seen_line, last_seen, count FROM _schema_observations').all();
78
+ for (const row of obsRows) {
79
+ observed.set(row.fingerprint, {
80
+ firstSeenFile: row.first_seen_file,
81
+ firstSeenLine: row.first_seen_line,
82
+ lastSeen: row.last_seen,
83
+ count: row.count,
84
+ });
85
+ }
86
+ const { unknown } = compareToGolden(observed, goldenPath);
87
+ const capped = unknown.slice(0, 20);
88
+ for (const fp of capped) {
89
+ const meta = observed.get(fp);
90
+ const loc = meta ? ` (first seen at ${meta.firstSeenFile}:${meta.firstSeenLine})` : '';
91
+ console.error(`[schema-drift] new fingerprint: ${fp}${loc}`);
92
+ }
93
+ }
94
+ catch {
95
+ // Schema drift check is non-critical — never block indexing
96
+ }
97
+ return {
98
+ filesScanned,
99
+ filesIngested,
100
+ durationMs: Date.now() - start,
101
+ };
102
+ }
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/index/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { ParsedSession } from '../schema/index.js';
3
+ import type { IngestStatements } from './statements.js';
4
+ import type { PricingRow } from './pricing-map.js';
5
+ /**
6
+ * Synchronous ingest of a pre-parsed session.
7
+ * Must be called INSIDE a caller-supplied db.transaction() — does NOT wrap itself.
8
+ * Throws on any SQLite error so the caller's transaction rolls back cleanly.
9
+ */
10
+ export declare function ingestFromParsed(db: Database.Database, stmts: IngestStatements, pricingMap: Map<string, PricingRow>, filePath: string, mtimeMs: number, platform: string, parsed: ParsedSession): void;
11
+ /**
12
+ * Async wrapper — parses and ingests a single file inside its own transaction.
13
+ * Preserved for backward compatibility with any tests calling ingestFile directly.
14
+ */
15
+ export declare function ingestFile(db: Database.Database, filePath: string, mtimeMs: number, platform: string | undefined, stmts: IngestStatements, pricingMap: Map<string, PricingRow>): Promise<void>;
16
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../../src/core/index/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAIrC,OAAO,KAAK,EAA6C,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,aAAa,GACpB,IAAI,CAmNN;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,YAAgB,EAChC,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAOf"}