@planckspace/cli 0.1.1 → 0.2.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 (190) hide show
  1. package/README.md +24 -1
  2. package/dist/anomaly/constants.d.ts +13 -0
  3. package/dist/anomaly/constants.d.ts.map +1 -0
  4. package/dist/anomaly/constants.js +13 -0
  5. package/dist/anomaly/constants.js.map +1 -0
  6. package/dist/anomaly/detector.d.ts +12 -0
  7. package/dist/anomaly/detector.d.ts.map +1 -0
  8. package/dist/anomaly/detector.js +98 -0
  9. package/dist/anomaly/detector.js.map +1 -0
  10. package/dist/anomaly/types.d.ts +19 -0
  11. package/dist/anomaly/types.d.ts.map +1 -0
  12. package/dist/anomaly/types.js +2 -0
  13. package/dist/anomaly/types.js.map +1 -0
  14. package/dist/commands/budget.d.ts +26 -0
  15. package/dist/commands/budget.d.ts.map +1 -0
  16. package/dist/commands/budget.js +91 -0
  17. package/dist/commands/budget.js.map +1 -0
  18. package/dist/commands/config.d.ts +4 -0
  19. package/dist/commands/config.d.ts.map +1 -0
  20. package/dist/commands/config.js +45 -0
  21. package/dist/commands/config.js.map +1 -0
  22. package/dist/commands/connect.d.ts +17 -0
  23. package/dist/commands/connect.d.ts.map +1 -0
  24. package/dist/commands/connect.js +191 -0
  25. package/dist/commands/connect.js.map +1 -0
  26. package/dist/commands/daemon.d.ts +8 -0
  27. package/dist/commands/daemon.d.ts.map +1 -0
  28. package/dist/commands/daemon.js +310 -0
  29. package/dist/commands/daemon.js.map +1 -0
  30. package/dist/commands/diagnose.d.ts +2 -0
  31. package/dist/commands/diagnose.d.ts.map +1 -0
  32. package/dist/commands/diagnose.js +81 -0
  33. package/dist/commands/diagnose.js.map +1 -0
  34. package/dist/commands/doctor.d.ts +2 -0
  35. package/dist/commands/doctor.d.ts.map +1 -0
  36. package/dist/commands/doctor.js +67 -0
  37. package/dist/commands/doctor.js.map +1 -0
  38. package/dist/commands/export.d.ts +40 -0
  39. package/dist/commands/export.d.ts.map +1 -0
  40. package/dist/commands/export.js +184 -0
  41. package/dist/commands/export.js.map +1 -0
  42. package/dist/commands/init.d.ts +3 -1
  43. package/dist/commands/init.d.ts.map +1 -1
  44. package/dist/commands/init.js +14 -1
  45. package/dist/commands/init.js.map +1 -1
  46. package/dist/commands/insights.d.ts +2 -0
  47. package/dist/commands/insights.d.ts.map +1 -0
  48. package/dist/commands/insights.js +71 -0
  49. package/dist/commands/insights.js.map +1 -0
  50. package/dist/commands/inspect.d.ts +2 -0
  51. package/dist/commands/inspect.d.ts.map +1 -0
  52. package/dist/commands/inspect.js +81 -0
  53. package/dist/commands/inspect.js.map +1 -0
  54. package/dist/commands/integrations.d.ts +2 -0
  55. package/dist/commands/integrations.d.ts.map +1 -0
  56. package/dist/commands/integrations.js +46 -0
  57. package/dist/commands/integrations.js.map +1 -0
  58. package/dist/commands/login.d.ts +1 -1
  59. package/dist/commands/login.d.ts.map +1 -1
  60. package/dist/commands/login.js +45 -6
  61. package/dist/commands/login.js.map +1 -1
  62. package/dist/commands/metrics.d.ts +6 -0
  63. package/dist/commands/metrics.d.ts.map +1 -0
  64. package/dist/commands/metrics.js +85 -0
  65. package/dist/commands/metrics.js.map +1 -0
  66. package/dist/commands/reconcile.d.ts +2 -0
  67. package/dist/commands/reconcile.d.ts.map +1 -0
  68. package/dist/commands/reconcile.js +63 -0
  69. package/dist/commands/reconcile.js.map +1 -0
  70. package/dist/commands/scan.d.ts.map +1 -1
  71. package/dist/commands/scan.js +53 -10
  72. package/dist/commands/scan.js.map +1 -1
  73. package/dist/commands/status.d.ts +1 -1
  74. package/dist/commands/status.d.ts.map +1 -1
  75. package/dist/commands/status.js +84 -14
  76. package/dist/commands/status.js.map +1 -1
  77. package/dist/commands/subscription.d.ts +15 -0
  78. package/dist/commands/subscription.d.ts.map +1 -0
  79. package/dist/commands/subscription.js +62 -0
  80. package/dist/commands/subscription.js.map +1 -0
  81. package/dist/commands/sync.d.ts +1 -1
  82. package/dist/commands/sync.d.ts.map +1 -1
  83. package/dist/commands/sync.js +77 -10
  84. package/dist/commands/sync.js.map +1 -1
  85. package/dist/commands/value.d.ts +5 -0
  86. package/dist/commands/value.d.ts.map +1 -0
  87. package/dist/commands/value.js +95 -0
  88. package/dist/commands/value.js.map +1 -0
  89. package/dist/config.d.ts +28 -3
  90. package/dist/config.d.ts.map +1 -1
  91. package/dist/config.js +9 -1
  92. package/dist/config.js.map +1 -1
  93. package/dist/correlate.d.ts +7 -0
  94. package/dist/correlate.d.ts.map +1 -1
  95. package/dist/correlate.js +102 -15
  96. package/dist/correlate.js.map +1 -1
  97. package/dist/daemon/daemon.d.ts +2 -0
  98. package/dist/daemon/daemon.d.ts.map +1 -0
  99. package/dist/daemon/daemon.js +188 -0
  100. package/dist/daemon/daemon.js.map +1 -0
  101. package/dist/daemon/daemonState.d.ts +25 -0
  102. package/dist/daemon/daemonState.d.ts.map +1 -0
  103. package/dist/daemon/daemonState.js +82 -0
  104. package/dist/daemon/daemonState.js.map +1 -0
  105. package/dist/daemon/logger.d.ts +7 -0
  106. package/dist/daemon/logger.d.ts.map +1 -0
  107. package/dist/daemon/logger.js +61 -0
  108. package/dist/daemon/logger.js.map +1 -0
  109. package/dist/daemon/syncLoop.d.ts +38 -0
  110. package/dist/daemon/syncLoop.d.ts.map +1 -0
  111. package/dist/daemon/syncLoop.js +119 -0
  112. package/dist/daemon/syncLoop.js.map +1 -0
  113. package/dist/daemon/watcher.d.ts +26 -0
  114. package/dist/daemon/watcher.d.ts.map +1 -0
  115. package/dist/daemon/watcher.js +187 -0
  116. package/dist/daemon/watcher.js.map +1 -0
  117. package/dist/db/store.d.ts +123 -2
  118. package/dist/db/store.d.ts.map +1 -1
  119. package/dist/db/store.js +397 -11
  120. package/dist/db/store.js.map +1 -1
  121. package/dist/detectors/cache-gap.d.ts +3 -0
  122. package/dist/detectors/cache-gap.d.ts.map +1 -0
  123. package/dist/detectors/cache-gap.js +70 -0
  124. package/dist/detectors/cache-gap.js.map +1 -0
  125. package/dist/detectors/context-bloat.d.ts +3 -0
  126. package/dist/detectors/context-bloat.d.ts.map +1 -0
  127. package/dist/detectors/context-bloat.js +68 -0
  128. package/dist/detectors/context-bloat.js.map +1 -0
  129. package/dist/detectors/fileTokens.d.ts +3 -0
  130. package/dist/detectors/fileTokens.d.ts.map +1 -0
  131. package/dist/detectors/fileTokens.js +12 -0
  132. package/dist/detectors/fileTokens.js.map +1 -0
  133. package/dist/detectors/index.d.ts +20 -0
  134. package/dist/detectors/index.d.ts.map +1 -0
  135. package/dist/detectors/index.js +41 -0
  136. package/dist/detectors/index.js.map +1 -0
  137. package/dist/detectors/model-routing.d.ts +3 -0
  138. package/dist/detectors/model-routing.d.ts.map +1 -0
  139. package/dist/detectors/model-routing.js +71 -0
  140. package/dist/detectors/model-routing.js.map +1 -0
  141. package/dist/detectors/repeat-read.d.ts +3 -0
  142. package/dist/detectors/repeat-read.d.ts.map +1 -0
  143. package/dist/detectors/repeat-read.js +69 -0
  144. package/dist/detectors/repeat-read.js.map +1 -0
  145. package/dist/detectors/seat-efficiency.d.ts +4 -0
  146. package/dist/detectors/seat-efficiency.d.ts.map +1 -0
  147. package/dist/detectors/seat-efficiency.js +86 -0
  148. package/dist/detectors/seat-efficiency.js.map +1 -0
  149. package/dist/detectors/types.d.ts +46 -0
  150. package/dist/detectors/types.d.ts.map +1 -0
  151. package/dist/detectors/types.js +2 -0
  152. package/dist/detectors/types.js.map +1 -0
  153. package/dist/health.d.ts +59 -0
  154. package/dist/health.d.ts.map +1 -0
  155. package/dist/health.js +106 -0
  156. package/dist/health.js.map +1 -0
  157. package/dist/index.js +389 -5
  158. package/dist/index.js.map +1 -1
  159. package/dist/metrics.d.ts +29 -0
  160. package/dist/metrics.d.ts.map +1 -0
  161. package/dist/metrics.js +205 -0
  162. package/dist/metrics.js.map +1 -0
  163. package/dist/scrapers/claudeCode.d.ts +1 -0
  164. package/dist/scrapers/claudeCode.d.ts.map +1 -1
  165. package/dist/scrapers/claudeCode.js +43 -13
  166. package/dist/scrapers/claudeCode.js.map +1 -1
  167. package/dist/scrapers/cursor.d.ts +3 -2
  168. package/dist/scrapers/cursor.d.ts.map +1 -1
  169. package/dist/scrapers/cursor.js +56 -16
  170. package/dist/scrapers/cursor.js.map +1 -1
  171. package/dist/scrapers/jetbrains.d.ts +15 -0
  172. package/dist/scrapers/jetbrains.d.ts.map +1 -0
  173. package/dist/scrapers/jetbrains.js +232 -0
  174. package/dist/scrapers/jetbrains.js.map +1 -0
  175. package/dist/scrapers/types.d.ts +4 -1
  176. package/dist/scrapers/types.d.ts.map +1 -1
  177. package/dist/scrapers/windsurf.d.ts +3 -2
  178. package/dist/scrapers/windsurf.d.ts.map +1 -1
  179. package/dist/scrapers/windsurf.js +25 -9
  180. package/dist/scrapers/windsurf.js.map +1 -1
  181. package/dist/sync/payload.d.ts +4 -5
  182. package/dist/sync/payload.d.ts.map +1 -1
  183. package/dist/sync/payload.js +88 -7
  184. package/dist/sync/payload.js.map +1 -1
  185. package/dist/sync/syncEngine.d.ts +19 -3
  186. package/dist/sync/syncEngine.d.ts.map +1 -1
  187. package/dist/sync/syncEngine.js +116 -10
  188. package/dist/sync/syncEngine.js.map +1 -1
  189. package/install.sh +27 -10
  190. package/package.json +43 -42
package/dist/db/store.js CHANGED
@@ -29,6 +29,9 @@ export function initDb(dbPath = DB_PATH) {
29
29
  cacheReadTokens INTEGER NOT NULL DEFAULT 0,
30
30
  cacheWriteTokens INTEGER NOT NULL DEFAULT 0,
31
31
  costUsd REAL NOT NULL DEFAULT 0,
32
+ costType TEXT NOT NULL DEFAULT 'imputed',
33
+ subscriptionPlan TEXT,
34
+ unmatchedTurns INTEGER NOT NULL DEFAULT 0,
32
35
  repoName TEXT,
33
36
  gitBranch TEXT,
34
37
  gitAuthorEmail TEXT,
@@ -37,7 +40,10 @@ export function initDb(dbPath = DB_PATH) {
37
40
  filesTouchedCount INTEGER NOT NULL DEFAULT 0,
38
41
  outcome TEXT NOT NULL DEFAULT 'unknown',
39
42
  turnsJson TEXT NOT NULL DEFAULT '[]',
40
- scrapedAt TEXT NOT NULL
43
+ scrapedAt TEXT NOT NULL,
44
+ mergedPrNumber INTEGER,
45
+ mergedAt TEXT,
46
+ timeToMergeMs INTEGER
41
47
  );
42
48
 
43
49
  CREATE TABLE IF NOT EXISTS sync_state (
@@ -50,14 +56,63 @@ export function initDb(dbPath = DB_PATH) {
50
56
  key TEXT PRIMARY KEY,
51
57
  value TEXT NOT NULL
52
58
  );
59
+
60
+ CREATE TABLE IF NOT EXISTS insights (
61
+ id TEXT PRIMARY KEY,
62
+ detectorId TEXT NOT NULL,
63
+ repoName TEXT,
64
+ workspaceScope TEXT NOT NULL,
65
+ titleShort TEXT NOT NULL,
66
+ estimatedMonthlySavingsUsd REAL NOT NULL DEFAULT 0,
67
+ confidence TEXT NOT NULL,
68
+ evidenceJson TEXT NOT NULL,
69
+ manualFix TEXT NOT NULL,
70
+ createdAt TEXT NOT NULL,
71
+ syncedAt TEXT
72
+ );
73
+
74
+ CREATE TABLE IF NOT EXISTS file_offsets (
75
+ filePath TEXT PRIMARY KEY,
76
+ byteOffset INTEGER NOT NULL DEFAULT 0,
77
+ updatedAt TEXT NOT NULL
78
+ );
79
+
80
+ CREATE TABLE IF NOT EXISTS anomalies (
81
+ id TEXT PRIMARY KEY,
82
+ type TEXT NOT NULL,
83
+ repoName TEXT,
84
+ sessionId TEXT,
85
+ costUsd REAL NOT NULL,
86
+ baselineUsd REAL NOT NULL,
87
+ ratio REAL NOT NULL,
88
+ detectedAt TEXT NOT NULL,
89
+ syncedAt TEXT
90
+ );
91
+
92
+ CREATE TABLE IF NOT EXISTS connection_state (
93
+ key TEXT PRIMARY KEY,
94
+ value TEXT NOT NULL
95
+ );
53
96
  `);
54
- // Migration: databases created before git correlation lack gitAuthorEmail.
97
+ // Idempotent migrations for columns added after initial schema.
55
98
  const columns = db
56
99
  .prepare("PRAGMA table_info(sessions)")
57
100
  .all();
58
- if (!columns.some((c) => c.name === "gitAuthorEmail")) {
101
+ const has = (col) => columns.some((c) => c.name === col);
102
+ if (!has("gitAuthorEmail"))
59
103
  db.exec("ALTER TABLE sessions ADD COLUMN gitAuthorEmail TEXT");
60
- }
104
+ if (!has("costType"))
105
+ db.exec("ALTER TABLE sessions ADD COLUMN costType TEXT NOT NULL DEFAULT 'imputed'");
106
+ if (!has("subscriptionPlan"))
107
+ db.exec("ALTER TABLE sessions ADD COLUMN subscriptionPlan TEXT");
108
+ if (!has("unmatchedTurns"))
109
+ db.exec("ALTER TABLE sessions ADD COLUMN unmatchedTurns INTEGER NOT NULL DEFAULT 0");
110
+ if (!has("mergedPrNumber"))
111
+ db.exec("ALTER TABLE sessions ADD COLUMN mergedPrNumber INTEGER");
112
+ if (!has("mergedAt"))
113
+ db.exec("ALTER TABLE sessions ADD COLUMN mergedAt TEXT");
114
+ if (!has("timeToMergeMs"))
115
+ db.exec("ALTER TABLE sessions ADD COLUMN timeToMergeMs INTEGER");
61
116
  _db = db;
62
117
  }
63
118
  /** Close the database handle (mainly for tests; releases the file lock on Windows). */
@@ -72,16 +127,18 @@ export function upsertSessions(sessions) {
72
127
  let added = 0;
73
128
  let updated = 0;
74
129
  const now = new Date().toISOString();
75
- const checkStmt = db.prepare("SELECT id FROM sessions WHERE id = ?");
130
+ const checkStmt = db.prepare("SELECT id, turnCount FROM sessions WHERE id = ?");
76
131
  const upsertStmt = db.prepare(`
77
132
  INSERT INTO sessions (
78
133
  id, tool, model, startedAt, endedAt,
79
134
  inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, costUsd,
135
+ costType, subscriptionPlan, unmatchedTurns,
80
136
  repoName, gitBranch, workingDir, turnCount, filesTouchedCount,
81
137
  outcome, turnsJson, scrapedAt
82
138
  ) VALUES (
83
139
  ?, ?, ?, ?, ?,
84
140
  ?, ?, ?, ?, ?,
141
+ ?, ?, ?,
85
142
  ?, ?, ?, ?, ?,
86
143
  'unknown', ?, ?
87
144
  )
@@ -95,6 +152,9 @@ export function upsertSessions(sessions) {
95
152
  cacheReadTokens = excluded.cacheReadTokens,
96
153
  cacheWriteTokens = excluded.cacheWriteTokens,
97
154
  costUsd = excluded.costUsd,
155
+ costType = excluded.costType,
156
+ subscriptionPlan = excluded.subscriptionPlan,
157
+ unmatchedTurns = excluded.unmatchedTurns,
98
158
  repoName = excluded.repoName,
99
159
  gitBranch = excluded.gitBranch,
100
160
  workingDir = excluded.workingDir,
@@ -103,9 +163,14 @@ export function upsertSessions(sessions) {
103
163
  turnsJson = excluded.turnsJson,
104
164
  scrapedAt = excluded.scrapedAt
105
165
  `);
106
- const syncStmt = db.prepare(`
166
+ // Insert sync_state for new sessions; for updated sessions with more turns,
167
+ // clear syncedAt so the fresh data is re-queued on the next sync cycle.
168
+ const syncInsertStmt = db.prepare(`
107
169
  INSERT OR IGNORE INTO sync_state (sessionId, syncedAt, lastError)
108
170
  VALUES (?, NULL, NULL)
171
+ `);
172
+ const syncResetStmt = db.prepare(`
173
+ UPDATE sync_state SET syncedAt = NULL WHERE sessionId = ?
109
174
  `);
110
175
  const run = db.transaction(() => {
111
176
  for (const session of sessions) {
@@ -116,8 +181,14 @@ export function upsertSessions(sessions) {
116
181
  else {
117
182
  added++;
118
183
  }
119
- upsertStmt.run(session.sessionId, session.tool, session.model, session.startedAt, session.endedAt, session.inputTokens, session.outputTokens, session.cacheReadTokens, session.cacheWriteTokens, session.costUsd, session.repoName, session.gitBranch, session.workingDir, session.turnCount, session.filesTouchedCount, JSON.stringify(session.turns), now);
120
- syncStmt.run(session.sessionId);
184
+ upsertStmt.run(session.sessionId, session.tool, session.model, session.startedAt, session.endedAt, session.inputTokens, session.outputTokens, session.cacheReadTokens, session.cacheWriteTokens, session.costUsd, session.costType, session.subscriptionPlan ?? null, session.unmatchedTurns, session.repoName, session.gitBranch, session.workingDir, session.turnCount, session.filesTouchedCount, JSON.stringify(session.turns), now);
185
+ if (!existing) {
186
+ syncInsertStmt.run(session.sessionId);
187
+ }
188
+ else if (session.turnCount > existing.turnCount) {
189
+ // Session got new turns — re-queue for sync so the backend sees the update.
190
+ syncResetStmt.run(session.sessionId);
191
+ }
121
192
  }
122
193
  });
123
194
  run();
@@ -128,13 +199,20 @@ export function getSessions() {
128
199
  .prepare("SELECT * FROM sessions ORDER BY startedAt DESC")
129
200
  .all();
130
201
  }
202
+ /**
203
+ * Sessions that have not yet been synced, including turnsJson and merge metadata.
204
+ * turnsJson is included so buildTelemetryPayload can extract per-turn metadata;
205
+ * the privacy invariant is enforced there (explicit field mapping, never forwarded raw).
206
+ */
131
207
  export function getUnsyncedSessions() {
132
208
  return getDb()
133
209
  .prepare(`
134
210
  SELECT s.id, s.tool, s.model, s.startedAt, s.endedAt,
135
211
  s.inputTokens, s.outputTokens, s.cacheReadTokens, s.cacheWriteTokens,
136
- s.costUsd, s.repoName, s.gitBranch, s.gitAuthorEmail, s.workingDir,
137
- s.turnCount, s.filesTouchedCount, s.outcome, s.scrapedAt
212
+ s.costUsd, s.costType, s.subscriptionPlan, s.unmatchedTurns,
213
+ s.repoName, s.gitBranch, s.gitAuthorEmail, s.workingDir,
214
+ s.turnCount, s.filesTouchedCount, s.outcome, s.turnsJson, s.scrapedAt,
215
+ s.mergedPrNumber, s.mergedAt, s.timeToMergeMs
138
216
  FROM sessions s
139
217
  LEFT JOIN sync_state ss ON s.id = ss.sessionId
140
218
  WHERE ss.syncedAt IS NULL
@@ -142,11 +220,27 @@ export function getUnsyncedSessions() {
142
220
  `)
143
221
  .all();
144
222
  }
223
+ /** Full session row by ID — used by `planck inspect`. Returns null if not found. */
224
+ export function getSessionById(id) {
225
+ return (getDb()
226
+ .prepare("SELECT * FROM sessions WHERE id = ?")
227
+ .get(id) ?? null);
228
+ }
229
+ export function getSessionsForValue(tool, sinceIso) {
230
+ return getDb()
231
+ .prepare(`
232
+ SELECT tool, costUsd, costType, gitAuthorEmail
233
+ FROM sessions
234
+ WHERE tool = ? AND startedAt >= ?
235
+ ORDER BY startedAt DESC
236
+ `)
237
+ .all(tool, sinceIso);
238
+ }
145
239
  /** Sessions to feed the git correlator — only the fields it needs to classify outcomes. */
146
240
  export function getSessionsForCorrelation() {
147
241
  return getDb()
148
242
  .prepare(`
149
- SELECT id, repoName, gitBranch, workingDir, startedAt, endedAt, outcome
243
+ SELECT id, repoName, gitBranch, workingDir, startedAt, endedAt, outcome, gitAuthorEmail
150
244
  FROM sessions
151
245
  `)
152
246
  .all();
@@ -157,6 +251,37 @@ export function setSessionCorrelation(id, outcome, gitAuthorEmail) {
157
251
  .prepare("UPDATE sessions SET outcome = ?, gitAuthorEmail = ? WHERE id = ?")
158
252
  .run(outcome, gitAuthorEmail, id);
159
253
  }
254
+ /**
255
+ * Persist merge metadata for a shipped session.
256
+ * Called after correlation determines outcome = "shipped".
257
+ * Clears these fields for non-shipped sessions when called with nulls.
258
+ */
259
+ export function setSessionMergeInfo(id, mergedPrNumber, mergedAt, timeToMergeMs) {
260
+ getDb()
261
+ .prepare("UPDATE sessions SET mergedPrNumber = ?, mergedAt = ?, timeToMergeMs = ? WHERE id = ?")
262
+ .run(mergedPrNumber, mergedAt, timeToMergeMs, id);
263
+ }
264
+ export function getSessionsForMetrics(sinceIso) {
265
+ if (sinceIso) {
266
+ return getDb()
267
+ .prepare(`
268
+ SELECT id, costUsd, outcome, gitBranch, repoName, workingDir, startedAt,
269
+ gitAuthorEmail, mergedPrNumber, mergedAt, timeToMergeMs
270
+ FROM sessions
271
+ WHERE startedAt >= ?
272
+ ORDER BY startedAt DESC
273
+ `)
274
+ .all(sinceIso);
275
+ }
276
+ return getDb()
277
+ .prepare(`
278
+ SELECT id, costUsd, outcome, gitBranch, repoName, workingDir, startedAt,
279
+ gitAuthorEmail, mergedPrNumber, mergedAt, timeToMergeMs
280
+ FROM sessions
281
+ ORDER BY startedAt DESC
282
+ `)
283
+ .all();
284
+ }
160
285
  /** Mark sessions as successfully synced (sets syncedAt, clears lastError). */
161
286
  export function markSessionsSynced(ids) {
162
287
  if (ids.length === 0)
@@ -176,6 +301,16 @@ export function markSessionsSynced(ids) {
176
301
  });
177
302
  run();
178
303
  }
304
+ /**
305
+ * Reset sync state for all sessions, forcing a full re-sync on next run.
306
+ * Called when switching to a different workspace so previous sessions are
307
+ * sent to the new workspace rather than silently skipped as already-synced.
308
+ */
309
+ export function resetSyncState() {
310
+ getDb()
311
+ .prepare("UPDATE sync_state SET syncedAt = NULL, lastError = NULL")
312
+ .run();
313
+ }
179
314
  /** Record a sync failure; leaves syncedAt NULL so the rows retry next run. */
180
315
  export function recordSyncError(ids, error) {
181
316
  if (ids.length === 0)
@@ -207,4 +342,255 @@ export function getMeta(key) {
207
342
  .get(key);
208
343
  return row?.value ?? null;
209
344
  }
345
+ // ── connection state ──────────────────────────────────────────────────────────
346
+ export function getConnectionState(key) {
347
+ const row = getDb()
348
+ .prepare("SELECT value FROM connection_state WHERE key = ?")
349
+ .get(key);
350
+ return row?.value ?? null;
351
+ }
352
+ export function setConnectionState(key, value) {
353
+ getDb()
354
+ .prepare(`
355
+ INSERT INTO connection_state (key, value) VALUES (?, ?)
356
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
357
+ `)
358
+ .run(key, value);
359
+ }
360
+ /** Session IDs that have been successfully synced (syncedAt IS NOT NULL). */
361
+ export function getSyncedSessionIds() {
362
+ return getDb()
363
+ .prepare("SELECT sessionId FROM sync_state WHERE syncedAt IS NOT NULL")
364
+ .all().map((r) => r.sessionId);
365
+ }
366
+ /**
367
+ * Reset sync state for specific sessions so they are re-sent on the next sync.
368
+ * Used by reconcileWithManifest when sessions are missing from the backend.
369
+ */
370
+ export function resetSyncStateForIds(ids, reason) {
371
+ if (ids.length === 0)
372
+ return;
373
+ const db = getDb();
374
+ const stmt = db.prepare("UPDATE sync_state SET syncedAt = NULL, lastError = ? WHERE sessionId = ?");
375
+ const run = db.transaction(() => {
376
+ for (const id of ids)
377
+ stmt.run(reason, id);
378
+ });
379
+ run();
380
+ }
381
+ /** Total number of sessions in the local DB (cheap COUNT, no row loading). */
382
+ export function getSessionCount() {
383
+ const row = getDb()
384
+ .prepare("SELECT COUNT(*) as count FROM sessions")
385
+ .get();
386
+ return row?.count ?? 0;
387
+ }
388
+ /**
389
+ * Stable ID for an insight: detectorId + repo + short title slug.
390
+ * Stable across runs for the same finding so upserts overwrite rather than accumulate.
391
+ */
392
+ function insightId(insight) {
393
+ const repo = (insight.repoName ?? "workspace")
394
+ .replace(/[^a-zA-Z0-9_-]/g, "_")
395
+ .slice(0, 40);
396
+ const slug = insight.titleShort
397
+ .split(" ")
398
+ .slice(0, 5)
399
+ .join("-")
400
+ .replace(/[^a-zA-Z0-9-]/g, "")
401
+ .toLowerCase()
402
+ .slice(0, 30);
403
+ return `${insight.detectorId}:${repo}:${slug}`;
404
+ }
405
+ /** Upsert a batch of insights; resets syncedAt so updated findings re-sync. */
406
+ export function upsertInsights(insights) {
407
+ if (insights.length === 0)
408
+ return;
409
+ const db = getDb();
410
+ const now = new Date().toISOString();
411
+ const stmt = db.prepare(`
412
+ INSERT INTO insights (
413
+ id, detectorId, repoName, workspaceScope, titleShort,
414
+ estimatedMonthlySavingsUsd, confidence, evidenceJson, manualFix, createdAt, syncedAt
415
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
416
+ ON CONFLICT(id) DO UPDATE SET
417
+ titleShort = excluded.titleShort,
418
+ estimatedMonthlySavingsUsd = excluded.estimatedMonthlySavingsUsd,
419
+ confidence = excluded.confidence,
420
+ evidenceJson = excluded.evidenceJson,
421
+ manualFix = excluded.manualFix,
422
+ createdAt = excluded.createdAt,
423
+ syncedAt = NULL
424
+ `);
425
+ const run = db.transaction(() => {
426
+ for (const insight of insights) {
427
+ stmt.run(insightId(insight), insight.detectorId, insight.repoName, insight.workspaceScope, insight.titleShort, insight.estimatedMonthlySavingsUsd, insight.confidence, JSON.stringify(insight.evidence), insight.manualFix, now);
428
+ }
429
+ });
430
+ run();
431
+ }
432
+ export function getUnsyncedInsights() {
433
+ return getDb()
434
+ .prepare("SELECT * FROM insights WHERE syncedAt IS NULL ORDER BY createdAt DESC")
435
+ .all();
436
+ }
437
+ export function markInsightsSynced(ids) {
438
+ if (ids.length === 0)
439
+ return;
440
+ const db = getDb();
441
+ const now = new Date().toISOString();
442
+ const stmt = db.prepare("UPDATE insights SET syncedAt = ? WHERE id = ?");
443
+ const run = db.transaction(() => {
444
+ for (const id of ids)
445
+ stmt.run(now, id);
446
+ });
447
+ run();
448
+ }
449
+ // ── file offsets (daemon incremental scraping) ────────────────────────────────
450
+ /** Returns the last processed byte offset for a file path, or 0 if unseen. */
451
+ export function getFileOffset(filePath) {
452
+ const row = getDb()
453
+ .prepare("SELECT byteOffset FROM file_offsets WHERE filePath = ?")
454
+ .get(filePath);
455
+ return row?.byteOffset ?? 0;
456
+ }
457
+ /** Record that a file has been fully processed up to the given byte offset. */
458
+ export function setFileOffset(filePath, byteOffset) {
459
+ getDb()
460
+ .prepare(`
461
+ INSERT INTO file_offsets (filePath, byteOffset, updatedAt)
462
+ VALUES (?, ?, ?)
463
+ ON CONFLICT(filePath) DO UPDATE SET
464
+ byteOffset = excluded.byteOffset,
465
+ updatedAt = excluded.updatedAt
466
+ `)
467
+ .run(filePath, byteOffset, new Date().toISOString());
468
+ }
469
+ /** Return all file paths whose stored offset is less than the given current size map. */
470
+ export function getFilesNeedingScrape(currentSizes) {
471
+ const db = getDb();
472
+ const rows = db
473
+ .prepare("SELECT filePath, byteOffset FROM file_offsets")
474
+ .all();
475
+ const knownOffsets = new Map(rows.map((r) => [r.filePath, r.byteOffset]));
476
+ const needsScrape = [];
477
+ for (const [fp, size] of currentSizes) {
478
+ const offset = knownOffsets.get(fp) ?? 0;
479
+ if (size > offset)
480
+ needsScrape.push(fp);
481
+ }
482
+ return needsScrape;
483
+ }
484
+ /**
485
+ * Insert anomaly events that don't already exist (deduplication by stable id).
486
+ * Returns only the newly inserted events so callers can report them to the user.
487
+ */
488
+ export function upsertAnomalies(events) {
489
+ if (events.length === 0)
490
+ return [];
491
+ const db = getDb();
492
+ // Find which IDs already exist to return only the truly new ones.
493
+ const ids = events.map((e) => e.id);
494
+ const placeholders = ids.map(() => "?").join(",");
495
+ const existingIds = new Set(db
496
+ .prepare(`SELECT id FROM anomalies WHERE id IN (${placeholders})`)
497
+ .all(...ids).map((r) => r.id));
498
+ const newEvents = events.filter((e) => !existingIds.has(e.id));
499
+ if (newEvents.length === 0)
500
+ return [];
501
+ const stmt = db.prepare(`
502
+ INSERT OR IGNORE INTO anomalies
503
+ (id, type, repoName, sessionId, costUsd, baselineUsd, ratio, detectedAt, syncedAt)
504
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)
505
+ `);
506
+ const run = db.transaction(() => {
507
+ for (const e of newEvents) {
508
+ stmt.run(e.id, e.type, e.repoName, e.sessionId, e.costUsd, e.baselineUsd, e.ratio, e.detectedAt);
509
+ }
510
+ });
511
+ run();
512
+ return newEvents;
513
+ }
514
+ export function getUnsyncedAnomalies() {
515
+ return getDb()
516
+ .prepare("SELECT * FROM anomalies WHERE syncedAt IS NULL ORDER BY detectedAt DESC")
517
+ .all();
518
+ }
519
+ export function markAnomaliesSynced(ids) {
520
+ if (ids.length === 0)
521
+ return;
522
+ const db = getDb();
523
+ const now = new Date().toISOString();
524
+ const stmt = db.prepare("UPDATE anomalies SET syncedAt = ? WHERE id = ?");
525
+ const run = db.transaction(() => {
526
+ for (const id of ids)
527
+ stmt.run(now, id);
528
+ });
529
+ run();
530
+ }
531
+ // ── anomaly detector queries ──────────────────────────────────────────────────
532
+ /** Daily cost totals per repo for the given time window; ordered by repoName, day ASC. */
533
+ export function getDailySessionCosts(since) {
534
+ return getDb()
535
+ .prepare(`
536
+ SELECT repoName, date(startedAt) as day, SUM(costUsd) as dailyCost
537
+ FROM sessions
538
+ WHERE startedAt >= ?
539
+ GROUP BY repoName, date(startedAt)
540
+ ORDER BY repoName, day ASC
541
+ `)
542
+ .all(since);
543
+ }
544
+ /** Sessions with a known developer email; ordered by gitAuthorEmail, costUsd ASC. */
545
+ export function getSessionsForAnomalyDetection(since) {
546
+ return getDb()
547
+ .prepare(`
548
+ SELECT id, gitAuthorEmail, costUsd, repoName
549
+ FROM sessions
550
+ WHERE startedAt >= ?
551
+ AND costUsd > 0
552
+ AND gitAuthorEmail IS NOT NULL
553
+ ORDER BY gitAuthorEmail, costUsd ASC
554
+ `)
555
+ .all(since);
556
+ }
557
+ export function getSessionsForExport(monthStart, monthEnd) {
558
+ return getDb()
559
+ .prepare(`
560
+ SELECT id, tool, model, startedAt, endedAt,
561
+ inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens,
562
+ costUsd, costType, subscriptionPlan, unmatchedTurns,
563
+ repoName, gitBranch, gitAuthorEmail, workingDir,
564
+ turnCount, filesTouchedCount, outcome, scrapedAt,
565
+ mergedPrNumber, mergedAt, timeToMergeMs
566
+ FROM sessions
567
+ WHERE startedAt >= ? AND startedAt < ?
568
+ ORDER BY startedAt ASC
569
+ `)
570
+ .all(monthStart, monthEnd);
571
+ }
572
+ // ── budget queries ────────────────────────────────────────────────────────────
573
+ /**
574
+ * Total session cost since `monthStart`, broken down by repoName.
575
+ * Used to compute burn-rate for configured budgets.
576
+ */
577
+ export function getMonthlyCosts(monthStart) {
578
+ const rows = getDb()
579
+ .prepare(`
580
+ SELECT repoName, SUM(costUsd) as totalCost
581
+ FROM sessions
582
+ WHERE startedAt >= ?
583
+ GROUP BY repoName
584
+ `)
585
+ .all(monthStart);
586
+ const byRepo = new Map();
587
+ let total = 0;
588
+ for (const r of rows) {
589
+ total += r.totalCost;
590
+ if (r.repoName) {
591
+ byRepo.set(r.repoName, (byRepo.get(r.repoName) ?? 0) + r.totalCost);
592
+ }
593
+ }
594
+ return { byRepo, total };
595
+ }
210
596
  //# sourceMappingURL=store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/db/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/db/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Detector } from "./types.js";
2
+ export declare const cacheGapDetector: Detector;
3
+ //# sourceMappingURL=cache-gap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-gap.d.ts","sourceRoot":"","sources":["../../src/detectors/cache-gap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAU7D,eAAO,MAAM,gBAAgB,EAAE,QA6E9B,CAAC"}
@@ -0,0 +1,70 @@
1
+ const DAYS_30_MS = 30 * 24 * 60 * 60 * 1000;
2
+ const CACHE_RATIO_THRESHOLD = 0.30; // < 30% cache-hit ratio triggers
3
+ const TOKEN_VOLUME_THRESHOLD = 1_000_000; // > 1M tokens/month in that repo
4
+ const INPUT_RATE_PER_M = 3.00; // Sonnet 4 baseline, $/M input tokens
5
+ const CACHEABLE_FRACTION = 0.50; // conservative: ~50% of input could be cached
6
+ const CACHE_READ_FRACTION = 0.10; // cache reads cost 0.1× the input rate
7
+ const MIN_SESSIONS = 5;
8
+ export const cacheGapDetector = {
9
+ id: "cache-gap",
10
+ description: "Repo has high token volume but low cache-hit ratio — prompt caching not configured",
11
+ minSessionsRequired: MIN_SESSIONS,
12
+ detect(sessions) {
13
+ if (sessions.length < MIN_SESSIONS)
14
+ return [];
15
+ const since = new Date(Date.now() - DAYS_30_MS).toISOString();
16
+ const recent = sessions.filter((s) => s.startedAt >= since);
17
+ const repoMap = new Map();
18
+ for (const session of recent) {
19
+ const key = session.repoName ?? "_unattributed";
20
+ if (!repoMap.has(key)) {
21
+ repoMap.set(key, { sessions: [], inputTokens: 0, cacheReadTokens: 0 });
22
+ }
23
+ const entry = repoMap.get(key);
24
+ entry.sessions.push(session);
25
+ entry.inputTokens += session.inputTokens;
26
+ entry.cacheReadTokens += session.cacheReadTokens;
27
+ }
28
+ const insights = [];
29
+ for (const [repoKey, { sessions: repoSessions, inputTokens, cacheReadTokens }] of repoMap) {
30
+ const totalVolume = inputTokens + cacheReadTokens;
31
+ if (totalVolume < TOKEN_VOLUME_THRESHOLD)
32
+ continue;
33
+ const cacheRatio = totalVolume > 0 ? cacheReadTokens / totalVolume : 0;
34
+ if (cacheRatio >= CACHE_RATIO_THRESHOLD)
35
+ continue;
36
+ // Savings = cacheable tokens × (input rate - cache read rate)
37
+ const monthlySavingsUsd = (inputTokens * CACHEABLE_FRACTION * (1 - CACHE_READ_FRACTION) * INPUT_RATE_PER_M) /
38
+ 1_000_000;
39
+ const repoName = repoKey === "_unattributed" ? null : repoKey;
40
+ const cacheRatioPct = (cacheRatio * 100).toFixed(1);
41
+ const volumeMStr = (totalVolume / 1_000_000).toFixed(1);
42
+ insights.push({
43
+ detectorId: "cache-gap",
44
+ repoName,
45
+ workspaceScope: "repo",
46
+ titleShort: `${repoKey}: ${cacheRatioPct}% cache-hit ratio on ${volumeMStr}M tokens/month`,
47
+ estimatedMonthlySavingsUsd: monthlySavingsUsd,
48
+ confidence: "medium",
49
+ evidence: {
50
+ sessionIds: repoSessions.map((s) => s.id),
51
+ metrics: {
52
+ inputTokens,
53
+ cacheReadTokens,
54
+ cacheRatioPct: parseFloat(cacheRatioPct),
55
+ totalVolumeM: parseFloat(volumeMStr),
56
+ },
57
+ },
58
+ manualFix: `Prompt caching is not used effectively in \`${repoKey}\`. Cache-hit ratio is` +
59
+ ` ${cacheRatioPct}%, well below the 30% threshold, on ${volumeMStr}M tokens/month.\n\n` +
60
+ `To enable caching: ensure the stable prefix of each prompt (CLAUDE.md, project context,` +
61
+ ` system instructions) comes before the dynamic user turn and exceeds ~2048 tokens.` +
62
+ ` Claude Code caches this prefix automatically with a 5-minute TTL. If sessions are very` +
63
+ ` short, group related tasks into longer sessions so the cache-write cost amortises across` +
64
+ ` more turns.`,
65
+ });
66
+ }
67
+ return insights;
68
+ },
69
+ };
70
+ //# sourceMappingURL=cache-gap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-gap.js","sourceRoot":"","sources":["../../src/detectors/cache-gap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Detector } from "./types.js";
2
+ export declare const contextBloatDetector: Detector;
3
+ //# sourceMappingURL=context-bloat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-bloat.d.ts","sourceRoot":"","sources":["../../src/detectors/context-bloat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,YAAY,CAAC;AAc7D,eAAO,MAAM,oBAAoB,EAAE,QAiElC,CAAC"}