@fml-inc/panopticon 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/api/client.d.ts +2 -2
  2. package/dist/api/client.js +1 -1
  3. package/dist/{chunk-KLXRBD4N.js → chunk-2PMFNSGA.js} +6 -5
  4. package/dist/{chunk-KLXRBD4N.js.map → chunk-2PMFNSGA.js.map} +1 -1
  5. package/dist/{chunk-3BUJ7URA.js → chunk-3ILOOWUF.js} +66 -2
  6. package/dist/chunk-3ILOOWUF.js.map +1 -0
  7. package/dist/{chunk-NXH7AONS.js → chunk-5FRYHDPJ.js} +8 -6
  8. package/dist/chunk-5FRYHDPJ.js.map +1 -0
  9. package/dist/{chunk-TCKL7E4K.js → chunk-5IPC5XXZ.js} +4 -4
  10. package/dist/{chunk-YZBYEULL.js → chunk-75IXBXBO.js} +5 -5
  11. package/dist/{chunk-HRCEIYKU.js → chunk-7NBZ4NMJ.js} +2 -2
  12. package/dist/{chunk-4SM2H22C.js → chunk-HO443ZQM.js} +1 -1
  13. package/dist/{chunk-4SM2H22C.js.map → chunk-HO443ZQM.js.map} +1 -1
  14. package/dist/{chunk-3TZAKV3M.js → chunk-OSWBZKK5.js} +2 -2
  15. package/dist/{chunk-SEXU2WYG.js → chunk-P5KAIE3O.js} +5 -4
  16. package/dist/chunk-P5KAIE3O.js.map +1 -0
  17. package/dist/{chunk-XO5NQRTD.js → chunk-RNICEX6N.js} +2 -2
  18. package/dist/{chunk-L7G27XWF.js → chunk-RRHBTXHB.js} +3 -3
  19. package/dist/{chunk-BVOE7A2Z.js → chunk-UD6H6EP6.js} +8 -6
  20. package/dist/chunk-UD6H6EP6.js.map +1 -0
  21. package/dist/{chunk-LWXF7YRG.js → chunk-UDUBRSIG.js} +2 -2
  22. package/dist/{chunk-SUGSQ4YI.js → chunk-UEEWG7FD.js} +4 -4
  23. package/dist/{chunk-DZ5HJFB4.js → chunk-UM5U6FPH.js} +60 -2
  24. package/dist/chunk-UM5U6FPH.js.map +1 -0
  25. package/dist/{chunk-VXQ33OYT.js → chunk-YV2AN73J.js} +47 -19
  26. package/dist/chunk-YV2AN73J.js.map +1 -0
  27. package/dist/cli.js +6 -6
  28. package/dist/cli.js.map +1 -1
  29. package/dist/db.js +1 -1
  30. package/dist/doctor.js +4 -4
  31. package/dist/hooks/handler.js +4 -4
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +15 -15
  34. package/dist/mcp/server.js +1 -1
  35. package/dist/otlp/server.js +5 -5
  36. package/dist/pricing.js +2 -2
  37. package/dist/proxy/server.js +5 -5
  38. package/dist/prune.js +2 -2
  39. package/dist/query.js +2 -2
  40. package/dist/{reparse-636YZCE3.js → reparse-TFYD67WP.js} +5 -5
  41. package/dist/scanner.d.ts +7 -1
  42. package/dist/scanner.js +1 -1
  43. package/dist/server.js +13 -13
  44. package/dist/setup.js +3 -3
  45. package/dist/sync/index.d.ts +2 -2
  46. package/dist/sync/index.js +4 -4
  47. package/dist/{types-D-MYCBol.d.ts → types-DrhrWbWe.d.ts} +1 -0
  48. package/package.json +1 -1
  49. package/dist/chunk-3BUJ7URA.js.map +0 -1
  50. package/dist/chunk-BVOE7A2Z.js.map +0 -1
  51. package/dist/chunk-DZ5HJFB4.js.map +0 -1
  52. package/dist/chunk-NXH7AONS.js.map +0 -1
  53. package/dist/chunk-SEXU2WYG.js.map +0 -1
  54. package/dist/chunk-VXQ33OYT.js.map +0 -1
  55. /package/dist/{chunk-TCKL7E4K.js.map → chunk-5IPC5XXZ.js.map} +0 -0
  56. /package/dist/{chunk-YZBYEULL.js.map → chunk-75IXBXBO.js.map} +0 -0
  57. /package/dist/{chunk-HRCEIYKU.js.map → chunk-7NBZ4NMJ.js.map} +0 -0
  58. /package/dist/{chunk-3TZAKV3M.js.map → chunk-OSWBZKK5.js.map} +0 -0
  59. /package/dist/{chunk-XO5NQRTD.js.map → chunk-RNICEX6N.js.map} +0 -0
  60. /package/dist/{chunk-L7G27XWF.js.map → chunk-RRHBTXHB.js.map} +0 -0
  61. /package/dist/{chunk-LWXF7YRG.js.map → chunk-UDUBRSIG.js.map} +0 -0
  62. /package/dist/{chunk-SUGSQ4YI.js.map → chunk-UEEWG7FD.js.map} +0 -0
  63. /package/dist/{reparse-636YZCE3.js.map → reparse-TFYD67WP.js.map} +0 -0
package/dist/index.js CHANGED
@@ -5,12 +5,12 @@ import {
5
5
  import {
6
6
  createUnifiedServer,
7
7
  syncAwarePrune
8
- } from "./chunk-VXQ33OYT.js";
9
- import "./chunk-TCKL7E4K.js";
10
- import "./chunk-KLXRBD4N.js";
11
- import "./chunk-YZBYEULL.js";
12
- import "./chunk-NXH7AONS.js";
13
- import "./chunk-BVOE7A2Z.js";
8
+ } from "./chunk-YV2AN73J.js";
9
+ import "./chunk-5IPC5XXZ.js";
10
+ import "./chunk-2PMFNSGA.js";
11
+ import "./chunk-75IXBXBO.js";
12
+ import "./chunk-5FRYHDPJ.js";
13
+ import "./chunk-UD6H6EP6.js";
14
14
  import {
15
15
  resolveRepoFromCwd
16
16
  } from "./chunk-YVRWVDIA.js";
@@ -18,26 +18,26 @@ import {
18
18
  autoPrune,
19
19
  pruneEstimate,
20
20
  pruneExecute
21
- } from "./chunk-HRCEIYKU.js";
21
+ } from "./chunk-7NBZ4NMJ.js";
22
22
  import {
23
23
  readConfig,
24
24
  writeFile,
25
25
  writeSettings
26
- } from "./chunk-3BUJ7URA.js";
27
- import "./chunk-XO5NQRTD.js";
26
+ } from "./chunk-3ILOOWUF.js";
27
+ import "./chunk-RNICEX6N.js";
28
28
  import "./chunk-7Q3BJMLG.js";
29
29
  import {
30
30
  configureShellEnv,
31
31
  fetchPricing,
32
32
  initDb
33
- } from "./chunk-L7G27XWF.js";
33
+ } from "./chunk-RRHBTXHB.js";
34
34
  import {
35
35
  refreshPricing
36
- } from "./chunk-3TZAKV3M.js";
36
+ } from "./chunk-OSWBZKK5.js";
37
37
  import {
38
38
  doctor
39
- } from "./chunk-SUGSQ4YI.js";
40
- import "./chunk-SEXU2WYG.js";
39
+ } from "./chunk-UEEWG7FD.js";
40
+ import "./chunk-P5KAIE3O.js";
41
41
  import {
42
42
  loadRetentionConfig,
43
43
  loadUnifiedConfig,
@@ -53,13 +53,13 @@ import {
53
53
  rawQuery,
54
54
  search,
55
55
  sessionTimeline
56
- } from "./chunk-LWXF7YRG.js";
56
+ } from "./chunk-UDUBRSIG.js";
57
57
  import "./chunk-ZEC4LRKS.js";
58
58
  import "./chunk-QVK6VGCV.js";
59
59
  import {
60
60
  closeDb,
61
61
  getDb
62
- } from "./chunk-DZ5HJFB4.js";
62
+ } from "./chunk-UM5U6FPH.js";
63
63
  import {
64
64
  config,
65
65
  ensureDataDir
@@ -9,7 +9,7 @@ import {
9
9
  rawQuery,
10
10
  search,
11
11
  sessionTimeline
12
- } from "../chunk-4SM2H22C.js";
12
+ } from "../chunk-HO443ZQM.js";
13
13
  import {
14
14
  log,
15
15
  logPaths
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  createOtlpServer,
3
3
  handleOtlpRequest
4
- } from "../chunk-TCKL7E4K.js";
5
- import "../chunk-BVOE7A2Z.js";
6
- import "../chunk-XO5NQRTD.js";
4
+ } from "../chunk-5IPC5XXZ.js";
5
+ import "../chunk-UD6H6EP6.js";
6
+ import "../chunk-RNICEX6N.js";
7
7
  import "../chunk-7Q3BJMLG.js";
8
- import "../chunk-3TZAKV3M.js";
8
+ import "../chunk-OSWBZKK5.js";
9
9
  import "../chunk-ZEC4LRKS.js";
10
10
  import "../chunk-QVK6VGCV.js";
11
- import "../chunk-DZ5HJFB4.js";
11
+ import "../chunk-UM5U6FPH.js";
12
12
  import "../chunk-K7YUPLES.js";
13
13
  export {
14
14
  createOtlpServer,
package/dist/pricing.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  COST_EXPR,
3
3
  refreshIfStale,
4
4
  refreshPricing
5
- } from "./chunk-3TZAKV3M.js";
6
- import "./chunk-DZ5HJFB4.js";
5
+ } from "./chunk-OSWBZKK5.js";
6
+ import "./chunk-UM5U6FPH.js";
7
7
  import "./chunk-K7YUPLES.js";
8
8
  export {
9
9
  COST_EXPR,
@@ -2,15 +2,15 @@ import {
2
2
  createProxyServer,
3
3
  handleProxyRequest,
4
4
  tunnelWebSocket
5
- } from "../chunk-KLXRBD4N.js";
6
- import "../chunk-BVOE7A2Z.js";
5
+ } from "../chunk-2PMFNSGA.js";
6
+ import "../chunk-UD6H6EP6.js";
7
7
  import "../chunk-YVRWVDIA.js";
8
- import "../chunk-3BUJ7URA.js";
9
- import "../chunk-XO5NQRTD.js";
8
+ import "../chunk-3ILOOWUF.js";
9
+ import "../chunk-RNICEX6N.js";
10
10
  import "../chunk-7Q3BJMLG.js";
11
11
  import "../chunk-ZEC4LRKS.js";
12
12
  import "../chunk-QVK6VGCV.js";
13
- import "../chunk-DZ5HJFB4.js";
13
+ import "../chunk-UM5U6FPH.js";
14
14
  import "../chunk-K7YUPLES.js";
15
15
  export {
16
16
  createProxyServer,
package/dist/prune.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  autoPrune,
3
3
  pruneEstimate,
4
4
  pruneExecute
5
- } from "./chunk-HRCEIYKU.js";
6
- import "./chunk-DZ5HJFB4.js";
5
+ } from "./chunk-7NBZ4NMJ.js";
6
+ import "./chunk-UM5U6FPH.js";
7
7
  import "./chunk-K7YUPLES.js";
8
8
  export {
9
9
  autoPrune,
package/dist/query.js CHANGED
@@ -8,10 +8,10 @@ import {
8
8
  rawQuery,
9
9
  search,
10
10
  sessionTimeline
11
- } from "./chunk-LWXF7YRG.js";
11
+ } from "./chunk-UDUBRSIG.js";
12
12
  import "./chunk-ZEC4LRKS.js";
13
13
  import "./chunk-QVK6VGCV.js";
14
- import "./chunk-DZ5HJFB4.js";
14
+ import "./chunk-UM5U6FPH.js";
15
15
  import "./chunk-K7YUPLES.js";
16
16
  export {
17
17
  activitySummary,
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  reparseAll
3
- } from "./chunk-NXH7AONS.js";
4
- import "./chunk-BVOE7A2Z.js";
3
+ } from "./chunk-5FRYHDPJ.js";
4
+ import "./chunk-UD6H6EP6.js";
5
5
  import "./chunk-YVRWVDIA.js";
6
6
  import "./chunk-7Q3BJMLG.js";
7
- import "./chunk-3TZAKV3M.js";
7
+ import "./chunk-OSWBZKK5.js";
8
8
  import "./chunk-QVK6VGCV.js";
9
- import "./chunk-DZ5HJFB4.js";
9
+ import "./chunk-UM5U6FPH.js";
10
10
  import "./chunk-K7YUPLES.js";
11
11
  export {
12
12
  reparseAll
13
13
  };
14
- //# sourceMappingURL=reparse-636YZCE3.js.map
14
+ //# sourceMappingURL=reparse-TFYD67WP.js.map
package/dist/scanner.d.ts CHANGED
@@ -31,6 +31,11 @@ interface ConfigLayer {
31
31
  deny: string[];
32
32
  };
33
33
  }
34
+ interface PluginHooksSummary {
35
+ pluginName: string;
36
+ marketplace: string;
37
+ hooks: ConfigLayer["hooks"];
38
+ }
34
39
  interface ClaudeCodeConfig {
35
40
  managed: ConfigLayer | null;
36
41
  user: ConfigLayer;
@@ -45,6 +50,7 @@ interface ClaudeCodeConfig {
45
50
  pluginName: string;
46
51
  marketplace: string;
47
52
  }>;
53
+ pluginHooks: PluginHooksSummary[];
48
54
  }
49
55
  /**
50
56
  * Resolve the git repository root for a directory.
@@ -70,4 +76,4 @@ declare function writeSettings(level: "project" | "projectLocal" | "user", patch
70
76
  */
71
77
  declare function writeFile(level: "project" | "user", type: "command" | "agent" | "rule" | "skill", name: string, content: string, cwd?: string): void;
72
78
 
73
- export { type ClaudeCodeConfig, type ConfigLayer, isGitignored, readConfig, resolveGitRoot, writeFile, writeSettings };
79
+ export { type ClaudeCodeConfig, type ConfigLayer, type PluginHooksSummary, isGitignored, readConfig, resolveGitRoot, writeFile, writeSettings };
package/dist/scanner.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  resolveGitRoot,
5
5
  writeFile,
6
6
  writeSettings
7
- } from "./chunk-3BUJ7URA.js";
7
+ } from "./chunk-3ILOOWUF.js";
8
8
  export {
9
9
  isGitignored,
10
10
  readConfig,
package/dist/server.js CHANGED
@@ -1,23 +1,23 @@
1
1
  import {
2
2
  createUnifiedServer
3
- } from "./chunk-VXQ33OYT.js";
4
- import "./chunk-TCKL7E4K.js";
5
- import "./chunk-KLXRBD4N.js";
6
- import "./chunk-YZBYEULL.js";
7
- import "./chunk-NXH7AONS.js";
8
- import "./chunk-BVOE7A2Z.js";
3
+ } from "./chunk-YV2AN73J.js";
4
+ import "./chunk-5IPC5XXZ.js";
5
+ import "./chunk-2PMFNSGA.js";
6
+ import "./chunk-75IXBXBO.js";
7
+ import "./chunk-5FRYHDPJ.js";
8
+ import "./chunk-UD6H6EP6.js";
9
9
  import "./chunk-YVRWVDIA.js";
10
- import "./chunk-HRCEIYKU.js";
11
- import "./chunk-3BUJ7URA.js";
12
- import "./chunk-XO5NQRTD.js";
10
+ import "./chunk-7NBZ4NMJ.js";
11
+ import "./chunk-3ILOOWUF.js";
12
+ import "./chunk-RNICEX6N.js";
13
13
  import "./chunk-7Q3BJMLG.js";
14
- import "./chunk-3TZAKV3M.js";
15
- import "./chunk-SEXU2WYG.js";
14
+ import "./chunk-OSWBZKK5.js";
15
+ import "./chunk-P5KAIE3O.js";
16
16
  import "./chunk-QK5442ZP.js";
17
- import "./chunk-LWXF7YRG.js";
17
+ import "./chunk-UDUBRSIG.js";
18
18
  import "./chunk-ZEC4LRKS.js";
19
19
  import "./chunk-QVK6VGCV.js";
20
- import "./chunk-DZ5HJFB4.js";
20
+ import "./chunk-UM5U6FPH.js";
21
21
  import "./chunk-K7YUPLES.js";
22
22
  export {
23
23
  createUnifiedServer
package/dist/setup.js CHANGED
@@ -2,11 +2,11 @@ import {
2
2
  configureShellEnv,
3
3
  fetchPricing,
4
4
  initDb
5
- } from "./chunk-L7G27XWF.js";
6
- import "./chunk-3TZAKV3M.js";
5
+ } from "./chunk-RRHBTXHB.js";
6
+ import "./chunk-OSWBZKK5.js";
7
7
  import "./chunk-ZEC4LRKS.js";
8
8
  import "./chunk-QVK6VGCV.js";
9
- import "./chunk-DZ5HJFB4.js";
9
+ import "./chunk-UM5U6FPH.js";
10
10
  import {
11
11
  config
12
12
  } from "./chunk-K7YUPLES.js";
@@ -1,5 +1,5 @@
1
- import { S as SyncTarget, a as SyncFilter, b as SyncOptions, c as SyncHandle, T as TableSyncDescriptor } from '../types-D-MYCBol.js';
2
- export { R as RepoConfigSnapshotRecord, d as SessionSyncRecord, U as UserConfigSnapshotRecord } from '../types-D-MYCBol.js';
1
+ import { S as SyncTarget, a as SyncFilter, b as SyncOptions, c as SyncHandle, T as TableSyncDescriptor } from '../types-DrhrWbWe.js';
2
+ export { R as RepoConfigSnapshotRecord, d as SessionSyncRecord, U as UserConfigSnapshotRecord } from '../types-DrhrWbWe.js';
3
3
 
4
4
  interface SyncConfig {
5
5
  targets: SyncTarget[];
@@ -5,17 +5,17 @@ import {
5
5
  loadSyncConfig,
6
6
  removeTarget,
7
7
  saveSyncConfig
8
- } from "../chunk-YZBYEULL.js";
9
- import "../chunk-XO5NQRTD.js";
8
+ } from "../chunk-75IXBXBO.js";
9
+ import "../chunk-RNICEX6N.js";
10
10
  import "../chunk-7Q3BJMLG.js";
11
11
  import {
12
12
  TABLE_SYNC_REGISTRY,
13
13
  readWatermark,
14
14
  resetWatermarks,
15
15
  watermarkKey
16
- } from "../chunk-SEXU2WYG.js";
16
+ } from "../chunk-P5KAIE3O.js";
17
17
  import "../chunk-QK5442ZP.js";
18
- import "../chunk-DZ5HJFB4.js";
18
+ import "../chunk-UM5U6FPH.js";
19
19
  import "../chunk-K7YUPLES.js";
20
20
  export {
21
21
  TABLE_SYNC_REGISTRY,
@@ -47,6 +47,7 @@ interface UserConfigSnapshotRecord {
47
47
  commands: unknown[];
48
48
  rules: unknown[];
49
49
  skills: unknown[];
50
+ pluginHooks: unknown[];
50
51
  }
51
52
  interface RepoConfigSnapshotRecord {
52
53
  id: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fml-inc/panopticon",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Observability for Claude Code — captures OTel signals and hook events, queryable via MCP",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/scanner.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface ConfigLayer {\n settings: Record<string, unknown> | null;\n hooks: Array<{ event: string; matcher: string | null; type: string }>;\n mcpServers: Array<{ name: string; command: string }>;\n commands: Array<{ name: string; content: string }>;\n agents: Array<{ name: string; content: string }>;\n rules: Array<{ name: string; content: string }>;\n skills: Array<{ name: string; content: string }>;\n permissions: { allow: string[]; ask: string[]; deny: string[] };\n}\n\nexport interface ClaudeCodeConfig {\n managed: ConfigLayer | null;\n user: ConfigLayer;\n project: ConfigLayer | null;\n projectLocal: ConfigLayer | null;\n instructions: Array<{ path: string; content: string; lineCount: number }>;\n enabledPlugins: Array<{ pluginName: string; marketplace: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// File-system helpers\n// ---------------------------------------------------------------------------\n\nfunction readFileOrNull(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nfunction readJsonOrNull(filePath: string): Record<string, unknown> | null {\n const raw = readFileOrNull(filePath);\n if (raw === null) return null;\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction readMdFiles(dir: string): Array<{ name: string; content: string }> {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const results: Array<{ name: string; content: string }> = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue;\n const content = readFileOrNull(path.join(dir, entry.name));\n if (content === null) continue;\n results.push({ name: entry.name.replace(/\\.md$/, \"\"), content });\n }\n return results;\n}\n\nfunction readSkills(dir: string): Array<{ name: string; content: string }> {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const results: Array<{ name: string; content: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const content = readFileOrNull(path.join(dir, entry.name, \"SKILL.md\"));\n if (content !== null) {\n results.push({ name: entry.name, content });\n }\n }\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Settings parsers\n// ---------------------------------------------------------------------------\n\nfunction parseHooks(json: Record<string, unknown>): ConfigLayer[\"hooks\"] {\n const hooks: ConfigLayer[\"hooks\"] = [];\n const raw = json.hooks;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return hooks;\n for (const [event, entries] of Object.entries(\n raw as Record<string, unknown>,\n )) {\n if (!Array.isArray(entries)) continue;\n for (const entry of entries) {\n if (typeof entry !== \"object\" || entry === null) continue;\n const e = entry as Record<string, unknown>;\n const matcher = typeof e.matcher === \"string\" ? e.matcher : null;\n\n // Current format: entry has a nested `hooks` array\n if (Array.isArray(e.hooks)) {\n for (const hook of e.hooks) {\n if (typeof hook !== \"object\" || hook === null) continue;\n const h = hook as Record<string, unknown>;\n hooks.push({\n event,\n matcher,\n type: h.type === \"command\" ? \"command\" : \"script\",\n });\n }\n } else {\n // Legacy format: entry IS a hook directly\n hooks.push({\n event,\n matcher,\n type: e.command ? \"command\" : \"script\",\n });\n }\n }\n }\n return hooks;\n}\n\nfunction parseMcpServers(\n json: Record<string, unknown>,\n): ConfigLayer[\"mcpServers\"] {\n const servers: ConfigLayer[\"mcpServers\"] = [];\n const raw = json.mcpServers;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return servers;\n for (const [name, def] of Object.entries(raw as Record<string, unknown>)) {\n if (typeof def !== \"object\" || def === null) continue;\n const s = def as Record<string, unknown>;\n servers.push({\n name,\n command: typeof s.command === \"string\" ? s.command : \"\",\n });\n }\n return servers;\n}\n\nfunction parsePermissions(\n json: Record<string, unknown>,\n): ConfigLayer[\"permissions\"] {\n const allow: string[] = [];\n const ask: string[] = [];\n const deny: string[] = [];\n\n function collectStrings(arr: unknown): string[] {\n if (!Array.isArray(arr)) return [];\n return arr.filter((v): v is string => typeof v === \"string\");\n }\n\n // Current format: permissions.allow / permissions.ask / permissions.deny\n const perms = json.permissions;\n if (perms && typeof perms === \"object\" && !Array.isArray(perms)) {\n const p = perms as Record<string, unknown>;\n allow.push(...collectStrings(p.allow));\n ask.push(...collectStrings(p.ask));\n deny.push(...collectStrings(p.deny));\n }\n\n // Legacy format: allowedTools / deniedTools (fall back if permissions empty)\n if (allow.length === 0 && deny.length === 0) {\n allow.push(...collectStrings(json.allowedTools));\n deny.push(...collectStrings(json.deniedTools));\n }\n\n return { allow, ask, deny };\n}\n\nfunction parseEnabledPlugins(\n settings: Record<string, unknown> | null,\n): Array<{ pluginName: string; marketplace: string }> {\n if (!settings) return [];\n const raw = settings.enabledPlugins;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return [];\n const result: Array<{ pluginName: string; marketplace: string }> = [];\n for (const [key, enabled] of Object.entries(raw as Record<string, unknown>)) {\n if (!enabled) continue;\n const atIndex = key.indexOf(\"@\");\n if (atIndex > 0) {\n result.push({\n pluginName: key.slice(0, atIndex),\n marketplace: key.slice(atIndex + 1),\n });\n } else {\n result.push({ pluginName: key, marketplace: \"\" });\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Layer builder\n// ---------------------------------------------------------------------------\n\nfunction buildLayer(\n settingsPath: string,\n dirs: {\n commands?: string;\n agents?: string;\n rules?: string;\n skills?: string;\n },\n mcpJsonPath?: string,\n): ConfigLayer {\n const settings = readJsonOrNull(settingsPath);\n\n // MCP servers: merge settings.json and .mcp.json (settings wins on dupes)\n const mcpFromSettings = settings ? parseMcpServers(settings) : [];\n const mcpFromFile = mcpJsonPath\n ? parseMcpServers(readJsonOrNull(mcpJsonPath) ?? {})\n : [];\n const mcpNames = new Set(mcpFromSettings.map((s) => s.name));\n const mcpServers = [\n ...mcpFromSettings,\n ...mcpFromFile.filter((s) => !mcpNames.has(s.name)),\n ];\n\n return {\n settings,\n hooks: settings ? parseHooks(settings) : [],\n mcpServers,\n permissions: settings\n ? parsePermissions(settings)\n : { allow: [], ask: [], deny: [] },\n commands: dirs.commands ? readMdFiles(dirs.commands) : [],\n agents: dirs.agents ? readMdFiles(dirs.agents) : [],\n rules: dirs.rules ? readMdFiles(dirs.rules) : [],\n skills: dirs.skills ? readSkills(dirs.skills) : [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Git helpers\n// ---------------------------------------------------------------------------\n\nconst gitRootCache = new Map<string, string | null>();\n\n/**\n * Resolve the git repository root for a directory.\n * Cached per cwd for the lifetime of the process.\n */\nexport function resolveGitRoot(cwd: string): string | null {\n if (gitRootCache.has(cwd)) return gitRootCache.get(cwd)!;\n let root: string | null = null;\n try {\n root = execFileSync(\"git\", [\"-C\", cwd, \"rev-parse\", \"--show-toplevel\"], {\n encoding: \"utf-8\",\n timeout: 5000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n // Not a git repo\n }\n gitRootCache.set(cwd, root);\n return root;\n}\n\n/**\n * Check if a file path is gitignored.\n */\nexport function isGitignored(filePath: string, cwd: string): boolean {\n try {\n execFileSync(\"git\", [\"-C\", cwd, \"check-ignore\", \"-q\", filePath], {\n timeout: 3000,\n stdio: [\"ignore\", \"ignore\", \"ignore\"],\n });\n return true; // exit code 0 = ignored\n } catch {\n return false; // exit code 1 = not ignored, or not a git repo\n }\n}\n\n// ---------------------------------------------------------------------------\n// Instruction discovery\n// ---------------------------------------------------------------------------\n\nfunction findPerDirectoryClaudeMd(\n root: string,\n excludePaths: Set<string>,\n): string[] {\n // Try git ls-files first (fast, respects .gitignore)\n const gitRoot = resolveGitRoot(root);\n if (gitRoot) {\n try {\n const output = execFileSync(\n \"git\",\n [\"-C\", gitRoot, \"ls-files\", \"--full-name\", \"*/CLAUDE.md\", \"CLAUDE.md\"],\n {\n encoding: \"utf-8\",\n timeout: 5000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n },\n ).trim();\n if (output) {\n return output\n .split(\"\\n\")\n .map((rel) => path.resolve(gitRoot, rel))\n .filter((p) => !excludePaths.has(p));\n }\n return [];\n } catch {\n // Fall through to find\n }\n }\n\n // Fallback: use find command\n try {\n const output = execFileSync(\n \"find\",\n [\n root,\n \"-name\",\n \"CLAUDE.md\",\n \"-not\",\n \"-path\",\n \"*/node_modules/*\",\n \"-not\",\n \"-path\",\n \"*/.git/*\",\n ],\n {\n encoding: \"utf-8\",\n timeout: 10000,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n },\n ).trim();\n if (output) {\n return output\n .split(\"\\n\")\n .map((p) => path.resolve(p))\n .filter((p) => !excludePaths.has(p));\n }\n } catch {\n // Fall through\n }\n\n return [];\n}\n\nfunction buildInstruction(\n filePath: string,\n): { path: string; content: string; lineCount: number } | null {\n const content = readFileOrNull(filePath);\n if (content === null) return null;\n return { path: filePath, content, lineCount: content.split(\"\\n\").length };\n}\n\nfunction getManagedDir(): string {\n if (process.platform === \"darwin\") {\n return \"/Library/Application Support/ClaudeCode\";\n }\n // Linux, WSL\n return \"/etc/claude-code\";\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Read all Claude Code config from the filesystem.\n * Returns structured layers (managed, user, project, projectLocal) plus\n * instruction files.\n */\nexport function readConfig(cwd?: string): ClaudeCodeConfig {\n const rawCwd = cwd ? path.resolve(cwd) : process.cwd();\n // Use git root if available so project config is found even from subdirs\n const root = resolveGitRoot(rawCwd) ?? rawCwd;\n const home = os.homedir();\n const claudeHome = path.join(home, \".claude\");\n const dotClaude = path.join(root, \".claude\");\n const managedDir = getManagedDir();\n\n // Managed (enterprise) layer\n let managed: ConfigLayer | null = null;\n const managedSettings = readJsonOrNull(\n path.join(managedDir, \"managed-settings.json\"),\n );\n if (managedSettings) {\n managed = {\n settings: managedSettings,\n hooks: parseHooks(managedSettings),\n mcpServers: parseMcpServers(managedSettings),\n permissions: parsePermissions(managedSettings),\n commands: [],\n agents: [],\n rules: [],\n skills: [],\n };\n }\n\n // User layer\n const user = buildLayer(\n path.join(claudeHome, \"settings.json\"),\n {\n commands: path.join(claudeHome, \"commands\"),\n rules: path.join(claudeHome, \"rules\"),\n skills: path.join(claudeHome, \"skills\"),\n },\n path.join(claudeHome, \".mcp.json\"),\n );\n\n // Project layer — null if no .claude directory\n let project: ConfigLayer | null = null;\n try {\n if (fs.statSync(dotClaude).isDirectory()) {\n project = buildLayer(\n path.join(dotClaude, \"settings.json\"),\n {\n commands: path.join(dotClaude, \"commands\"),\n agents: path.join(dotClaude, \"agents\"),\n rules: path.join(dotClaude, \"rules\"),\n skills: path.join(dotClaude, \"skills\"),\n },\n path.join(dotClaude, \".mcp.json\"),\n );\n }\n } catch {\n // no .claude directory\n }\n\n // Project local layer — null if no settings.local.json\n let projectLocal: ConfigLayer | null = null;\n const localSettings = readJsonOrNull(\n path.join(dotClaude, \"settings.local.json\"),\n );\n if (localSettings) {\n projectLocal = {\n settings: localSettings,\n hooks: parseHooks(localSettings),\n mcpServers: parseMcpServers(localSettings),\n permissions: parsePermissions(localSettings),\n commands: [],\n agents: [],\n rules: [],\n skills: [],\n };\n }\n\n // Instructions — fixed candidates + per-directory discovery\n const instructions: ClaudeCodeConfig[\"instructions\"] = [];\n const knownPaths = new Set<string>();\n\n const instructionCandidates = [\n path.join(managedDir, \"CLAUDE.md\"),\n path.join(claudeHome, \"CLAUDE.md\"),\n path.join(root, \"CLAUDE.md\"),\n path.join(dotClaude, \"CLAUDE.md\"),\n path.join(root, \"AGENTS.md\"),\n ];\n\n for (const p of instructionCandidates) {\n const resolved = path.resolve(p);\n const inst = buildInstruction(resolved);\n if (inst) {\n instructions.push(inst);\n knownPaths.add(resolved);\n }\n }\n\n for (const p of findPerDirectoryClaudeMd(root, knownPaths)) {\n const inst = buildInstruction(p);\n if (inst) instructions.push(inst);\n }\n\n // Enabled plugins — merge user + project, deduplicate\n const pluginSet = new Set<string>();\n const enabledPlugins: ClaudeCodeConfig[\"enabledPlugins\"] = [];\n for (const layer of [project, user]) {\n for (const p of parseEnabledPlugins(layer?.settings ?? null)) {\n const key = `${p.pluginName}@${p.marketplace}`;\n if (!pluginSet.has(key)) {\n pluginSet.add(key);\n enabledPlugins.push(p);\n }\n }\n }\n\n return { managed, user, project, projectLocal, instructions, enabledPlugins };\n}\n\n/**\n * Merge-write a settings patch into the given layer's settings.json.\n */\nexport function writeSettings(\n level: \"project\" | \"projectLocal\" | \"user\",\n patch: Record<string, unknown>,\n cwd?: string,\n): void {\n const root = cwd ? path.resolve(cwd) : process.cwd();\n const home = os.homedir();\n\n let filePath: string;\n switch (level) {\n case \"project\":\n filePath = path.join(root, \".claude\", \"settings.json\");\n break;\n case \"projectLocal\":\n filePath = path.join(root, \".claude\", \"settings.local.json\");\n break;\n case \"user\":\n filePath = path.join(home, \".claude\", \"settings.json\");\n break;\n }\n\n const existing = readJsonOrNull(filePath) ?? {};\n const merged = { ...existing, ...patch };\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, `${JSON.stringify(merged, null, 2)}\\n`);\n}\n\n/**\n * Write a config file (command, agent, rule, or skill) to the given layer.\n */\nexport function writeFile(\n level: \"project\" | \"user\",\n type: \"command\" | \"agent\" | \"rule\" | \"skill\",\n name: string,\n content: string,\n cwd?: string,\n): void {\n const root = cwd ? path.resolve(cwd) : process.cwd();\n const home = os.homedir();\n\n const base =\n level === \"project\"\n ? path.join(root, \".claude\")\n : path.join(home, \".claude\");\n\n if (type === \"skill\") {\n const dir = path.join(base, \"skills\", name);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(path.join(dir, \"SKILL.md\"), content);\n } else {\n const dir = path.join(base, `${type}s`);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(path.join(dir, `${name}.md`), content);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/db/store.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { gzipSync } from \"node:zlib\";\n\nimport { getDb } from \"./schema.js\";\n\nexport interface OtelLogRow {\n timestamp_ns: number;\n observed_timestamp_ns?: number;\n severity_number?: number;\n severity_text?: string;\n body?: string;\n attributes?: Record<string, unknown>;\n resource_attributes?: Record<string, unknown>;\n session_id?: string;\n prompt_id?: string;\n trace_id?: string;\n span_id?: string;\n}\n\nexport interface OtelMetricRow {\n timestamp_ns: number;\n name: string;\n value: number;\n metric_type?: string;\n unit?: string;\n attributes?: Record<string, unknown>;\n resource_attributes?: Record<string, unknown>;\n session_id?: string;\n}\n\nexport interface OtelSpanRow {\n trace_id: string;\n span_id: string;\n parent_span_id?: string;\n name: string;\n kind?: number;\n start_time_ns: number;\n end_time_ns: number;\n status_code?: number;\n status_message?: string;\n attributes?: Record<string, unknown>;\n resource_attributes?: Record<string, unknown>;\n session_id?: string;\n}\n\nexport interface HookEventRow {\n session_id: string;\n event_type: string;\n timestamp_ms: number;\n cwd?: string;\n repository?: string;\n tool_name?: string;\n target?: string;\n user_prompt?: string;\n file_path?: string;\n command?: string;\n plan?: string;\n allowed_prompts?: string;\n payload: unknown;\n}\n\nconst INSERT_LOG_SQL = `\n INSERT INTO otel_logs (timestamp_ns, observed_timestamp_ns, severity_number, severity_text, body, attributes, resource_attributes, session_id, prompt_id, trace_id, span_id)\n VALUES (@timestamp_ns, @observed_timestamp_ns, @severity_number, @severity_text, @body, @attributes, @resource_attributes, @session_id, @prompt_id, @trace_id, @span_id)\n`;\n\nconst INSERT_METRIC_SQL = `\n INSERT INTO otel_metrics (timestamp_ns, name, value, metric_type, unit, attributes, resource_attributes, session_id)\n VALUES (@timestamp_ns, @name, @value, @metric_type, @unit, @attributes, @resource_attributes, @session_id)\n`;\n\nconst INSERT_SPAN_SQL = `\n INSERT OR IGNORE INTO otel_spans (trace_id, span_id, parent_span_id, name, kind, start_time_ns, end_time_ns, status_code, status_message, attributes, resource_attributes, session_id)\n VALUES (@trace_id, @span_id, @parent_span_id, @name, @kind, @start_time_ns, @end_time_ns, @status_code, @status_message, @attributes, @resource_attributes, @session_id)\n`;\n\nconst INSERT_HOOK_SQL = `\n INSERT INTO hook_events (session_id, event_type, timestamp_ms, cwd, repository, tool_name,\n target, user_prompt, file_path, command, tool_result, plan, allowed_prompts, payload)\n VALUES (@session_id, @event_type, @timestamp_ms, @cwd, @repository, @tool_name,\n @target, @user_prompt, @file_path, @command, @tool_result, @plan, @allowed_prompts, @payload)\n`;\n\nexport function insertOtelLogs(rows: OtelLogRow[]): void {\n const db = getDb();\n const stmt = db.prepare(INSERT_LOG_SQL);\n const insertMany = db.transaction((rows: OtelLogRow[]) => {\n for (const row of rows) {\n stmt.run({\n timestamp_ns: row.timestamp_ns,\n observed_timestamp_ns: row.observed_timestamp_ns ?? null,\n severity_number: row.severity_number ?? null,\n severity_text: row.severity_text ?? null,\n body: row.body ?? null,\n attributes: row.attributes ? JSON.stringify(row.attributes) : null,\n resource_attributes: row.resource_attributes\n ? JSON.stringify(row.resource_attributes)\n : null,\n session_id: row.session_id ?? null,\n prompt_id: row.prompt_id ?? null,\n trace_id: row.trace_id ?? null,\n span_id: row.span_id ?? null,\n });\n }\n });\n insertMany(rows);\n}\n\nexport function insertOtelMetrics(rows: OtelMetricRow[]): void {\n const db = getDb();\n const stmt = db.prepare(INSERT_METRIC_SQL);\n\n const insertMany = db.transaction((rows: OtelMetricRow[]) => {\n for (const row of rows) {\n const sessionId = row.session_id ?? null;\n\n stmt.run({\n timestamp_ns: row.timestamp_ns,\n name: row.name,\n value: row.value,\n metric_type: row.metric_type ?? null,\n unit: row.unit ?? null,\n attributes: row.attributes ? JSON.stringify(row.attributes) : null,\n resource_attributes: row.resource_attributes\n ? JSON.stringify(row.resource_attributes)\n : null,\n session_id: sessionId,\n });\n }\n });\n insertMany(rows);\n}\n\nexport function insertOtelSpans(rows: OtelSpanRow[]): void {\n const db = getDb();\n const stmt = db.prepare(INSERT_SPAN_SQL);\n\n const insertMany = db.transaction((rows: OtelSpanRow[]) => {\n for (const row of rows) {\n stmt.run({\n trace_id: row.trace_id,\n span_id: row.span_id,\n parent_span_id: row.parent_span_id ?? null,\n name: row.name,\n kind: row.kind ?? null,\n start_time_ns: row.start_time_ns,\n end_time_ns: row.end_time_ns,\n status_code: row.status_code ?? null,\n status_message: row.status_message ?? null,\n attributes: row.attributes ? JSON.stringify(row.attributes) : null,\n resource_attributes: row.resource_attributes\n ? JSON.stringify(row.resource_attributes)\n : null,\n session_id: row.session_id ?? null,\n });\n }\n });\n insertMany(rows);\n}\n\nexport function upsertSessionRepository(\n sessionId: string,\n repository: string,\n timestampMs: number,\n gitIdentity?: { name: string | null; email: string | null },\n branch?: string | null,\n): void {\n const db = getDb();\n const existing = db\n .prepare(\n `SELECT 1 FROM session_repositories WHERE session_id = ? AND repository = ?`,\n )\n .get(sessionId, repository);\n\n db.prepare(\n `INSERT INTO session_repositories (session_id, repository, first_seen_ms, git_user_name, git_user_email, branch)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(session_id, repository) DO UPDATE SET\n git_user_name = COALESCE(session_repositories.git_user_name, excluded.git_user_name),\n git_user_email = COALESCE(session_repositories.git_user_email, excluded.git_user_email),\n branch = COALESCE(excluded.branch, session_repositories.branch)`,\n ).run(\n sessionId,\n repository,\n timestampMs,\n gitIdentity?.name ?? null,\n gitIdentity?.email ?? null,\n branch ?? null,\n );\n\n // When a NEW repo is associated, bump sync_seq so the session re-syncs\n // with updated repository info (avoids backend repo-filter rejections)\n if (!existing) {\n db.prepare(\n `UPDATE sessions SET sync_seq = COALESCE(sync_seq, 0) + 1 WHERE session_id = ?`,\n ).run(sessionId);\n }\n}\n\nexport function upsertSessionCwd(\n sessionId: string,\n cwd: string,\n timestampMs: number,\n): void {\n const db = getDb();\n db.prepare(\n \"INSERT INTO session_cwds (session_id, cwd, first_seen_ms) VALUES (?, ?, ?) ON CONFLICT DO NOTHING\",\n ).run(sessionId, cwd, timestampMs);\n}\n\n// ---------------------------------------------------------------------------\n// Config snapshots\n// ---------------------------------------------------------------------------\n\nfunction contentHash(obj: Record<string, unknown>): string {\n return createHash(\"sha256\")\n .update(JSON.stringify(obj, Object.keys(obj).sort()))\n .digest(\"hex\");\n}\n\nexport interface UserConfigSnapshot {\n deviceName: string;\n permissions: unknown;\n enabledPlugins: unknown;\n hooks: unknown;\n commands: unknown;\n rules: unknown;\n skills: unknown;\n}\n\n/**\n * Insert a user config snapshot if the content has changed since the last one\n * for this device. Returns true if a new row was inserted.\n */\nexport function insertUserConfigSnapshot(snap: UserConfigSnapshot): boolean {\n const db = getDb();\n const hash = contentHash({\n permissions: snap.permissions,\n enabledPlugins: snap.enabledPlugins,\n hooks: snap.hooks,\n commands: snap.commands,\n rules: snap.rules,\n skills: snap.skills,\n });\n\n // Check if latest snapshot for this device has the same hash\n const existing = db\n .prepare(\n \"SELECT content_hash FROM user_config_snapshots WHERE device_name = ? ORDER BY snapshot_at_ms DESC LIMIT 1\",\n )\n .get(snap.deviceName) as { content_hash: string } | undefined;\n\n if (existing?.content_hash === hash) return false;\n\n db.prepare(\n `INSERT INTO user_config_snapshots\n (device_name, snapshot_at_ms, content_hash, permissions, enabled_plugins, hooks, commands, rules, skills)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n snap.deviceName,\n Date.now(),\n hash,\n JSON.stringify(snap.permissions),\n JSON.stringify(snap.enabledPlugins),\n JSON.stringify(snap.hooks),\n JSON.stringify(snap.commands),\n JSON.stringify(snap.rules),\n JSON.stringify(snap.skills),\n );\n return true;\n}\n\nexport interface RepoConfigSnapshot {\n repository: string;\n cwd: string;\n sessionId?: string;\n // project layer\n hooks: unknown;\n mcpServers: unknown;\n commands: unknown;\n agents: unknown;\n rules: unknown;\n // project local layer\n localHooks: unknown;\n localMcpServers: unknown;\n localPermissions: unknown;\n localIsGitignored: boolean;\n // instructions\n instructions: unknown;\n}\n\n/**\n * Insert a repo config snapshot if the content has changed since the last one\n * for this repository. Returns true if a new row was inserted.\n */\nexport function insertRepoConfigSnapshot(snap: RepoConfigSnapshot): boolean {\n const db = getDb();\n const hash = contentHash({\n hooks: snap.hooks,\n mcpServers: snap.mcpServers,\n commands: snap.commands,\n agents: snap.agents,\n rules: snap.rules,\n localHooks: snap.localHooks,\n localMcpServers: snap.localMcpServers,\n localPermissions: snap.localPermissions,\n localIsGitignored: snap.localIsGitignored,\n instructions: snap.instructions,\n });\n\n const existing = db\n .prepare(\n \"SELECT content_hash FROM repo_config_snapshots WHERE repository = ? ORDER BY snapshot_at_ms DESC LIMIT 1\",\n )\n .get(snap.repository) as { content_hash: string } | undefined;\n\n if (existing?.content_hash === hash) return false;\n\n db.prepare(\n `INSERT INTO repo_config_snapshots\n (repository, cwd, session_id, snapshot_at_ms, content_hash,\n hooks, mcp_servers, commands, agents, rules,\n local_hooks, local_mcp_servers, local_permissions, local_is_gitignored,\n instructions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n snap.repository,\n snap.cwd,\n snap.sessionId ?? null,\n Date.now(),\n hash,\n JSON.stringify(snap.hooks),\n JSON.stringify(snap.mcpServers),\n JSON.stringify(snap.commands),\n JSON.stringify(snap.agents),\n JSON.stringify(snap.rules),\n JSON.stringify(snap.localHooks),\n JSON.stringify(snap.localMcpServers),\n JSON.stringify(snap.localPermissions),\n snap.localIsGitignored ? 1 : 0,\n JSON.stringify(snap.instructions),\n );\n return true;\n}\n\nexport interface SessionUpsert {\n session_id: string;\n target?: string;\n started_at_ms?: number;\n ended_at_ms?: number;\n first_prompt?: string;\n permission_mode?: string;\n agent_version?: string;\n // Scanner-sourced fields\n model?: string;\n cli_version?: string;\n scanner_file_path?: string;\n total_input_tokens?: number;\n total_output_tokens?: number;\n total_cache_read_tokens?: number;\n total_cache_creation_tokens?: number;\n total_reasoning_tokens?: number;\n turn_count?: number;\n // OTLP-sourced tokens\n otel_input_tokens?: number;\n otel_output_tokens?: number;\n otel_cache_read_tokens?: number;\n otel_cache_creation_tokens?: number;\n // Metadata\n project?: string;\n created_at?: number;\n has_hooks?: number;\n has_otel?: number;\n has_scanner?: number;\n parent_session_id?: string;\n relationship_type?: string;\n is_automated?: number;\n}\n\nexport function upsertSession(row: SessionUpsert): void {\n const db = getDb();\n db.prepare(\n `INSERT INTO sessions (session_id, target, started_at_ms, ended_at_ms, first_prompt,\n permission_mode, agent_version, model, cli_version, scanner_file_path,\n total_input_tokens, total_output_tokens, total_cache_read_tokens,\n total_cache_creation_tokens, total_reasoning_tokens, turn_count,\n otel_input_tokens, otel_output_tokens, otel_cache_read_tokens, otel_cache_creation_tokens,\n models, project, created_at, parent_session_id, relationship_type, is_automated,\n has_hooks, has_otel, has_scanner)\n VALUES (@session_id, @target, @started_at_ms, @ended_at_ms, @first_prompt,\n @permission_mode, @agent_version, @model, @cli_version, @scanner_file_path,\n @total_input_tokens, @total_output_tokens, @total_cache_read_tokens,\n @total_cache_creation_tokens, @total_reasoning_tokens, @turn_count,\n @otel_input_tokens, @otel_output_tokens, @otel_cache_read_tokens, @otel_cache_creation_tokens,\n @model, @project, @created_at, @parent_session_id, @relationship_type, @is_automated,\n @has_hooks, @has_otel, @has_scanner)\n ON CONFLICT(session_id) DO UPDATE SET\n target = COALESCE(excluded.target, sessions.target),\n started_at_ms = COALESCE(excluded.started_at_ms, sessions.started_at_ms),\n ended_at_ms = COALESCE(excluded.ended_at_ms, sessions.ended_at_ms),\n first_prompt = COALESCE(sessions.first_prompt, excluded.first_prompt),\n permission_mode = COALESCE(excluded.permission_mode, sessions.permission_mode),\n agent_version = COALESCE(excluded.agent_version, sessions.agent_version),\n model = COALESCE(excluded.model, sessions.model),\n cli_version = COALESCE(excluded.cli_version, sessions.cli_version),\n scanner_file_path = COALESCE(excluded.scanner_file_path, sessions.scanner_file_path),\n total_input_tokens = COALESCE(excluded.total_input_tokens, sessions.total_input_tokens),\n total_output_tokens = COALESCE(excluded.total_output_tokens, sessions.total_output_tokens),\n total_cache_read_tokens = COALESCE(excluded.total_cache_read_tokens, sessions.total_cache_read_tokens),\n total_cache_creation_tokens = COALESCE(excluded.total_cache_creation_tokens, sessions.total_cache_creation_tokens),\n total_reasoning_tokens = COALESCE(excluded.total_reasoning_tokens, sessions.total_reasoning_tokens),\n turn_count = COALESCE(excluded.turn_count, sessions.turn_count),\n otel_input_tokens = COALESCE(excluded.otel_input_tokens, sessions.otel_input_tokens),\n otel_output_tokens = COALESCE(excluded.otel_output_tokens, sessions.otel_output_tokens),\n otel_cache_read_tokens = COALESCE(excluded.otel_cache_read_tokens, sessions.otel_cache_read_tokens),\n otel_cache_creation_tokens = COALESCE(excluded.otel_cache_creation_tokens, sessions.otel_cache_creation_tokens),\n models = CASE\n WHEN excluded.model IS NULL THEN sessions.models\n WHEN sessions.models IS NULL THEN excluded.model\n WHEN sessions.models LIKE '%' || excluded.model || '%' THEN sessions.models\n ELSE sessions.models || ',' || excluded.model\n END,\n project = COALESCE(sessions.project, excluded.project),\n parent_session_id = COALESCE(excluded.parent_session_id, sessions.parent_session_id),\n relationship_type = COALESCE(excluded.relationship_type, sessions.relationship_type),\n is_automated = COALESCE(excluded.is_automated, sessions.is_automated),\n has_hooks = MAX(COALESCE(sessions.has_hooks, 0), COALESCE(excluded.has_hooks, 0)),\n has_otel = MAX(COALESCE(sessions.has_otel, 0), COALESCE(excluded.has_otel, 0)),\n has_scanner = MAX(COALESCE(sessions.has_scanner, 0), COALESCE(excluded.has_scanner, 0)),\n sync_dirty = 1,\n sync_seq = COALESCE(sessions.sync_seq, 0) + 1`,\n ).run({\n session_id: row.session_id,\n target: row.target ?? null,\n started_at_ms: row.started_at_ms ?? null,\n ended_at_ms: row.ended_at_ms ?? null,\n first_prompt: row.first_prompt ?? null,\n permission_mode: row.permission_mode ?? null,\n agent_version: row.agent_version ?? null,\n model: row.model ?? null,\n cli_version: row.cli_version ?? null,\n scanner_file_path: row.scanner_file_path ?? null,\n total_input_tokens: row.total_input_tokens ?? null,\n total_output_tokens: row.total_output_tokens ?? null,\n total_cache_read_tokens: row.total_cache_read_tokens ?? null,\n total_cache_creation_tokens: row.total_cache_creation_tokens ?? null,\n total_reasoning_tokens: row.total_reasoning_tokens ?? null,\n turn_count: row.turn_count ?? null,\n otel_input_tokens: row.otel_input_tokens ?? null,\n otel_output_tokens: row.otel_output_tokens ?? null,\n otel_cache_read_tokens: row.otel_cache_read_tokens ?? null,\n otel_cache_creation_tokens: row.otel_cache_creation_tokens ?? null,\n project: row.project ?? null,\n created_at: row.created_at ?? null,\n parent_session_id: row.parent_session_id ?? null,\n relationship_type: row.relationship_type ?? null,\n is_automated: row.is_automated ?? null,\n has_hooks: row.has_hooks ?? null,\n has_otel: row.has_otel ?? null,\n has_scanner: row.has_scanner ?? null,\n });\n}\n\n/** Prefixes/substrings that identify automated (non-interactive) sessions. */\nconst AUTOMATED_PREFIXES = [\n \"You are a code reviewer.\",\n \"You are a security code reviewer.\",\n \"You are a design reviewer.\",\n \"You are a code assistant. Your task is to address\",\n \"You are a code review insights analyst.\",\n \"You are reviewing whether an implementation matches\",\n \"You are a plan document reviewer.\",\n \"You are a spec document reviewer.\",\n \"You are summarizing a day of AI agent activity.\",\n \"You are analyzing AI agent sessions.\",\n \"## Analysis Request\",\n \"# Fix Request\",\n];\nconst AUTOMATED_SUBSTRINGS = [\"invoked by roborev to perform this review\"];\n\nfunction isAutomatedPrompt(firstPrompt: string): boolean {\n for (const p of AUTOMATED_PREFIXES) {\n if (firstPrompt.startsWith(p)) return true;\n }\n for (const s of AUTOMATED_SUBSTRINGS) {\n if (firstPrompt.includes(s)) return true;\n }\n return false;\n}\n\n/**\n * Recompute message_count, user_message_count, and is_automated\n * from the messages table. is_automated is set when user_message_count <= 1\n * and first_prompt matches a known automated pattern.\n */\nexport function updateSessionMessageCounts(sessionId: string): void {\n const db = getDb();\n\n // Count non-system user messages\n const counts = db\n .prepare(\n `SELECT\n (SELECT COUNT(*) FROM messages WHERE session_id = ?) as msg_count,\n (SELECT COUNT(*) FROM messages WHERE session_id = ? AND role = 'user' AND is_system = 0) as user_count,\n (SELECT first_prompt FROM sessions WHERE session_id = ?) as first_prompt`,\n )\n .get(sessionId, sessionId, sessionId) as {\n msg_count: number;\n user_count: number;\n first_prompt: string | null;\n };\n\n const isAutomated =\n counts.user_count <= 1 &&\n counts.first_prompt != null &&\n isAutomatedPrompt(counts.first_prompt)\n ? 1\n : 0;\n\n db.prepare(\n `UPDATE sessions SET\n message_count = ?,\n user_message_count = ?,\n is_automated = CASE WHEN ? > 1 THEN 0 ELSE ? END,\n sync_seq = COALESCE(sync_seq, 0) + 1\n WHERE session_id = ?`,\n ).run(\n counts.msg_count,\n counts.user_count,\n counts.user_count,\n isAutomated,\n sessionId,\n );\n}\n\n/**\n * Increment a hook tool count for a session. Uses JSON_SET to atomically\n * update the hook_tool_counts JSON object.\n * Does NOT bump sync_seq — the scanner drives sync_seq via updateSessionTotals.\n * Hook data syncs via Phase 2 (hook_events table), not the session row.\n */\nexport function incrementToolCount(sessionId: string, toolName: string): void {\n const db = getDb();\n db.prepare(\n `UPDATE sessions\n SET hook_tool_counts = JSON_SET(\n COALESCE(hook_tool_counts, '{}'),\n '$.' || @tool,\n COALESCE(JSON_EXTRACT(hook_tool_counts, '$.' || @tool), 0) + 1\n )\n WHERE session_id = @session_id`,\n ).run({ session_id: sessionId, tool: toolName });\n}\n\n/**\n * Increment a hook event type count for a session.\n * Does NOT bump sync_seq — same rationale as incrementToolCount.\n */\nexport function incrementEventTypeCount(\n sessionId: string,\n eventType: string,\n): void {\n const db = getDb();\n db.prepare(\n `UPDATE sessions\n SET hook_event_type_counts = JSON_SET(\n COALESCE(hook_event_type_counts, '{}'),\n '$.' || @event_type,\n COALESCE(JSON_EXTRACT(hook_event_type_counts, '$.' || @event_type), 0) + 1\n )\n WHERE session_id = @session_id`,\n ).run({ session_id: sessionId, event_type: eventType });\n}\n\nfunction extractStr(\n obj: Record<string, unknown> | undefined,\n key: string,\n): string | undefined {\n const v = obj?.[key];\n return typeof v === \"string\" ? v : undefined;\n}\n\nexport function insertHookEvent(row: HookEventRow): void {\n const db = getDb();\n const data = row.payload as Record<string, unknown>;\n const toolInput = data.tool_input as Record<string, unknown> | undefined;\n\n // Extract high-value fields into columns for indexed queries\n const userPrompt =\n extractStr(data, \"prompt\") ?? extractStr(data, \"user_prompt\");\n const filePath = extractStr(toolInput, \"file_path\");\n const command = extractStr(toolInput, \"command\");\n const plan = extractStr(toolInput, \"plan\");\n const toolResultRaw = data.tool_result ?? data.tool_response;\n const toolResult = toolResultRaw\n ? typeof toolResultRaw === \"string\"\n ? toolResultRaw\n : JSON.stringify(toolResultRaw)\n : undefined;\n const allowedPrompts = toolInput?.allowedPrompts\n ? JSON.stringify(toolInput.allowedPrompts)\n : undefined;\n\n const fullJson = JSON.stringify(data);\n\n const insertWithFts = db.transaction(() => {\n db.prepare(INSERT_HOOK_SQL).run({\n session_id: row.session_id,\n event_type: row.event_type,\n timestamp_ms: row.timestamp_ms,\n cwd: row.cwd ?? null,\n repository: row.repository ?? null,\n tool_name: row.tool_name ?? null,\n target: row.target ?? null,\n user_prompt: userPrompt ?? null,\n file_path: filePath ?? null,\n command: command ?? null,\n tool_result: toolResult ?? null,\n plan: plan ?? null,\n allowed_prompts: allowedPrompts ?? null,\n payload: gzipSync(Buffer.from(fullJson)),\n });\n const { id } = db.prepare(\"SELECT last_insert_rowid() as id\").get() as {\n id: number;\n };\n db.prepare(\"INSERT INTO hook_events_fts(rowid, payload) VALUES (?, ?)\").run(\n id,\n fullJson,\n );\n });\n insertWithFts();\n}\n"],"mappings":"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","names":["rows"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/db/schema.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport { gunzipSync } from \"node:zlib\";\nimport Database from \"better-sqlite3\";\nimport { config } from \"../config.js\";\n\nexport const SCHEMA_SQL = `\n\n-- ── OTLP tables ─────────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS otel_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n timestamp_ns INTEGER NOT NULL,\n observed_timestamp_ns INTEGER,\n severity_number INTEGER,\n severity_text TEXT,\n body TEXT,\n attributes JSON,\n resource_attributes JSON,\n session_id TEXT,\n prompt_id TEXT,\n trace_id TEXT,\n span_id TEXT,\n sync_id TEXT DEFAULT (hex(randomblob(8)))\n);\n\nCREATE TABLE IF NOT EXISTS otel_metrics (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n timestamp_ns INTEGER NOT NULL,\n name TEXT NOT NULL,\n value REAL NOT NULL,\n metric_type TEXT,\n unit TEXT,\n attributes JSON,\n resource_attributes JSON,\n session_id TEXT,\n sync_id TEXT DEFAULT (hex(randomblob(8)))\n);\n\nCREATE TABLE IF NOT EXISTS otel_spans (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n trace_id TEXT NOT NULL,\n span_id TEXT NOT NULL,\n parent_span_id TEXT,\n name TEXT NOT NULL,\n kind INTEGER,\n start_time_ns INTEGER NOT NULL,\n end_time_ns INTEGER NOT NULL,\n status_code INTEGER,\n status_message TEXT,\n attributes JSON,\n resource_attributes JSON,\n session_id TEXT,\n UNIQUE(trace_id, span_id)\n);\n\n-- ── Hook events ─────────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS hook_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n event_type TEXT NOT NULL,\n timestamp_ms INTEGER NOT NULL,\n cwd TEXT,\n repository TEXT,\n tool_name TEXT,\n payload BLOB NOT NULL,\n user_prompt TEXT,\n file_path TEXT,\n command TEXT,\n plan TEXT,\n allowed_prompts TEXT,\n tool_result TEXT,\n target TEXT,\n sync_id TEXT DEFAULT (hex(randomblob(8)))\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS hook_events_fts USING fts5(\n payload,\n content='',\n contentless_delete=1,\n tokenize='trigram'\n);\n\n-- ── Sessions ────────────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS sessions (\n session_id TEXT PRIMARY KEY,\n target TEXT,\n started_at_ms INTEGER,\n ended_at_ms INTEGER,\n cwd TEXT,\n first_prompt TEXT,\n permission_mode TEXT,\n agent_version TEXT,\n model TEXT,\n cli_version TEXT,\n scanner_file_path TEXT,\n total_input_tokens INTEGER DEFAULT 0,\n total_output_tokens INTEGER DEFAULT 0,\n total_cache_read_tokens INTEGER DEFAULT 0,\n total_cache_creation_tokens INTEGER DEFAULT 0,\n total_reasoning_tokens INTEGER DEFAULT 0,\n turn_count INTEGER DEFAULT 0,\n otel_input_tokens INTEGER DEFAULT 0,\n otel_output_tokens INTEGER DEFAULT 0,\n otel_cache_read_tokens INTEGER DEFAULT 0,\n otel_cache_creation_tokens INTEGER DEFAULT 0,\n models TEXT,\n has_hooks INTEGER DEFAULT 0,\n has_otel INTEGER DEFAULT 0,\n has_scanner INTEGER DEFAULT 0,\n summary TEXT,\n summary_version INTEGER DEFAULT 0,\n sync_dirty INTEGER DEFAULT 0,\n sync_seq INTEGER DEFAULT 0,\n tool_counts JSON DEFAULT '{}',\n hook_tool_counts JSON DEFAULT '{}',\n event_type_counts JSON DEFAULT '{}',\n hook_event_type_counts JSON DEFAULT '{}',\n project TEXT,\n machine TEXT NOT NULL DEFAULT 'local',\n message_count INTEGER DEFAULT 0,\n user_message_count INTEGER DEFAULT 0,\n parent_session_id TEXT,\n relationship_type TEXT DEFAULT '',\n is_automated INTEGER DEFAULT 0,\n created_at INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS session_repositories (\n session_id TEXT NOT NULL,\n repository TEXT NOT NULL,\n first_seen_ms INTEGER NOT NULL,\n git_user_name TEXT,\n git_user_email TEXT,\n branch TEXT,\n UNIQUE(session_id, repository)\n);\n\nCREATE TABLE IF NOT EXISTS session_cwds (\n session_id TEXT NOT NULL,\n cwd TEXT NOT NULL,\n first_seen_ms INTEGER NOT NULL,\n UNIQUE(session_id, cwd)\n);\n\n-- ── Messages ────────────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS messages (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n ordinal INTEGER NOT NULL,\n role TEXT NOT NULL,\n content TEXT NOT NULL,\n timestamp_ms INTEGER,\n has_thinking INTEGER NOT NULL DEFAULT 0,\n has_tool_use INTEGER NOT NULL DEFAULT 0,\n content_length INTEGER NOT NULL DEFAULT 0,\n is_system INTEGER NOT NULL DEFAULT 0,\n model TEXT NOT NULL DEFAULT '',\n token_usage TEXT NOT NULL DEFAULT '',\n context_tokens INTEGER NOT NULL DEFAULT 0,\n output_tokens INTEGER NOT NULL DEFAULT 0,\n has_context_tokens INTEGER NOT NULL DEFAULT 0,\n has_output_tokens INTEGER NOT NULL DEFAULT 0,\n uuid TEXT,\n parent_uuid TEXT,\n UNIQUE(session_id, ordinal)\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(\n content,\n content='',\n contentless_delete=1,\n tokenize='trigram'\n);\n\n-- ── Tool calls ──────────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS tool_calls (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n message_id INTEGER NOT NULL,\n session_id TEXT NOT NULL,\n tool_name TEXT NOT NULL,\n category TEXT NOT NULL,\n tool_use_id TEXT,\n input_json TEXT,\n skill_name TEXT,\n result_content_length INTEGER,\n result_content TEXT,\n duration_ms INTEGER,\n subagent_session_id TEXT,\n sync_id TEXT DEFAULT (hex(randomblob(8)))\n);\n\n-- ── Scanner tables ──────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS scanner_turns (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n source TEXT NOT NULL,\n turn_index INTEGER NOT NULL,\n timestamp_ms INTEGER NOT NULL,\n model TEXT,\n role TEXT,\n content_preview TEXT,\n input_tokens INTEGER DEFAULT 0,\n output_tokens INTEGER DEFAULT 0,\n cache_read_tokens INTEGER DEFAULT 0,\n cache_creation_tokens INTEGER DEFAULT 0,\n reasoning_tokens INTEGER DEFAULT 0,\n sync_id TEXT DEFAULT (hex(randomblob(8))),\n UNIQUE(session_id, source, turn_index)\n);\n\nCREATE TABLE IF NOT EXISTS scanner_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n source TEXT NOT NULL,\n event_type TEXT NOT NULL,\n timestamp_ms INTEGER NOT NULL,\n tool_name TEXT,\n tool_input TEXT,\n tool_output TEXT,\n content TEXT,\n metadata JSON,\n sync_id TEXT DEFAULT (hex(randomblob(8))),\n UNIQUE(session_id, source, event_type, timestamp_ms, tool_name)\n);\n\nCREATE TABLE IF NOT EXISTS scanner_file_watermarks (\n file_path TEXT PRIMARY KEY,\n byte_offset INTEGER NOT NULL DEFAULT 0,\n last_scanned_ms INTEGER NOT NULL,\n archived_size INTEGER DEFAULT 0\n);\n\n-- ── Session summaries ───────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS session_summary_deltas (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n delta_index INTEGER NOT NULL,\n created_at_ms INTEGER NOT NULL,\n from_turn INTEGER NOT NULL,\n to_turn INTEGER NOT NULL,\n content TEXT NOT NULL,\n method TEXT NOT NULL DEFAULT 'deterministic',\n UNIQUE(session_id, delta_index)\n);\n\n-- ── Model pricing ───────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS model_pricing (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n model_id TEXT NOT NULL,\n input_per_m REAL NOT NULL,\n output_per_m REAL NOT NULL,\n cache_read_per_m REAL NOT NULL DEFAULT 0,\n cache_write_per_m REAL NOT NULL DEFAULT 0,\n updated_ms INTEGER NOT NULL\n);\n\n-- ── Config snapshots ────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS user_config_snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n device_name TEXT NOT NULL,\n snapshot_at_ms INTEGER NOT NULL,\n content_hash TEXT NOT NULL,\n permissions JSON NOT NULL DEFAULT '{}',\n enabled_plugins JSON NOT NULL DEFAULT '[]',\n hooks JSON NOT NULL DEFAULT '[]',\n commands JSON NOT NULL DEFAULT '[]',\n rules JSON NOT NULL DEFAULT '[]',\n skills JSON NOT NULL DEFAULT '[]'\n);\n\nCREATE TABLE IF NOT EXISTS repo_config_snapshots (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n repository TEXT NOT NULL,\n cwd TEXT NOT NULL,\n session_id TEXT,\n snapshot_at_ms INTEGER NOT NULL,\n content_hash TEXT NOT NULL,\n hooks JSON NOT NULL DEFAULT '[]',\n mcp_servers JSON NOT NULL DEFAULT '[]',\n commands JSON NOT NULL DEFAULT '[]',\n agents JSON NOT NULL DEFAULT '[]',\n rules JSON NOT NULL DEFAULT '[]',\n local_hooks JSON NOT NULL DEFAULT '[]',\n local_mcp_servers JSON NOT NULL DEFAULT '[]',\n local_permissions JSON NOT NULL DEFAULT '{}',\n local_is_gitignored INTEGER NOT NULL DEFAULT 1,\n instructions JSON NOT NULL DEFAULT '[]'\n);\n\n-- ── Sync watermarks ─────────────────────────────────────────────────────────\n\nCREATE TABLE IF NOT EXISTS watermarks (\n key TEXT PRIMARY KEY,\n value INTEGER NOT NULL\n);\n\n-- ── Per-session sync state ─────────────────────────────────────────────────\n\nDROP TABLE IF EXISTS pending_session_sync;\n\nCREATE TABLE IF NOT EXISTS target_session_sync (\n session_id TEXT NOT NULL,\n target TEXT NOT NULL,\n confirmed INTEGER DEFAULT 0,\n sync_seq INTEGER DEFAULT 0,\n synced_seq INTEGER DEFAULT 0,\n wm_messages INTEGER DEFAULT 0,\n wm_tool_calls INTEGER DEFAULT 0,\n wm_scanner_turns INTEGER DEFAULT 0,\n wm_scanner_events INTEGER DEFAULT 0,\n wm_hook_events INTEGER DEFAULT 0,\n wm_otel_logs INTEGER DEFAULT 0,\n wm_otel_metrics INTEGER DEFAULT 0,\n wm_otel_spans INTEGER DEFAULT 0,\n PRIMARY KEY (session_id, target)\n);\n\n-- ── Indexes ─────────────────────────────────────────────────────────────────\n\n-- otel_logs\nCREATE INDEX IF NOT EXISTS idx_logs_session ON otel_logs(session_id);\nCREATE INDEX IF NOT EXISTS idx_logs_body ON otel_logs(body);\nCREATE INDEX IF NOT EXISTS idx_logs_ts ON otel_logs(timestamp_ns);\nCREATE INDEX IF NOT EXISTS idx_logs_prompt ON otel_logs(prompt_id);\n\n-- otel_metrics\nCREATE INDEX IF NOT EXISTS idx_metrics_session ON otel_metrics(session_id);\nCREATE INDEX IF NOT EXISTS idx_metrics_name ON otel_metrics(name);\nCREATE INDEX IF NOT EXISTS idx_metrics_ts ON otel_metrics(timestamp_ns);\n\n-- otel_spans\nCREATE INDEX IF NOT EXISTS idx_spans_session ON otel_spans(session_id);\nCREATE INDEX IF NOT EXISTS idx_spans_trace ON otel_spans(trace_id);\nCREATE INDEX IF NOT EXISTS idx_spans_start ON otel_spans(start_time_ns);\n\n-- hook_events\nCREATE INDEX IF NOT EXISTS idx_hooks_session ON hook_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_hooks_type ON hook_events(event_type);\nCREATE INDEX IF NOT EXISTS idx_hooks_tool ON hook_events(tool_name);\nCREATE INDEX IF NOT EXISTS idx_hooks_ts ON hook_events(timestamp_ms);\nCREATE INDEX IF NOT EXISTS idx_hooks_file_path ON hook_events(file_path);\nCREATE INDEX IF NOT EXISTS idx_hooks_target ON hook_events(target);\n\n-- sessions\nCREATE INDEX IF NOT EXISTS idx_sessions_target ON sessions(target);\nCREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at_ms);\nCREATE INDEX IF NOT EXISTS idx_sessions_sync_seq ON sessions(sync_seq);\nCREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project);\nCREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine);\nCREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id)\n WHERE parent_session_id IS NOT NULL;\n\n-- session_repositories\nCREATE INDEX IF NOT EXISTS idx_session_repos_session ON session_repositories(session_id);\nCREATE INDEX IF NOT EXISTS idx_session_repos_repo ON session_repositories(repository);\n\n-- session_cwds\nCREATE INDEX IF NOT EXISTS idx_session_cwds_session ON session_cwds(session_id);\n\n-- messages\nCREATE INDEX IF NOT EXISTS idx_messages_session_ordinal ON messages(session_id, ordinal);\nCREATE INDEX IF NOT EXISTS idx_messages_session_role ON messages(session_id, role);\n\n-- tool_calls\nCREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_category ON tool_calls(category);\nCREATE INDEX IF NOT EXISTS idx_tool_calls_skill ON tool_calls(skill_name)\n WHERE skill_name IS NOT NULL;\nCREATE INDEX IF NOT EXISTS idx_tool_calls_subagent ON tool_calls(subagent_session_id)\n WHERE subagent_session_id IS NOT NULL;\n\n-- scanner_turns\nCREATE INDEX IF NOT EXISTS idx_scanner_turns_session ON scanner_turns(session_id);\nCREATE INDEX IF NOT EXISTS idx_scanner_turns_ts ON scanner_turns(timestamp_ms);\n\n-- scanner_events\nCREATE INDEX IF NOT EXISTS idx_scanner_events_session ON scanner_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_scanner_events_type ON scanner_events(event_type);\n\n-- session_summary_deltas\nCREATE INDEX IF NOT EXISTS idx_summary_deltas_session ON session_summary_deltas(session_id);\n\n-- model_pricing\nCREATE INDEX IF NOT EXISTS idx_model_pricing_model ON model_pricing(model_id, updated_ms);\n\n-- user_config_snapshots\nCREATE INDEX IF NOT EXISTS idx_user_config_ts ON user_config_snapshots(snapshot_at_ms);\nCREATE INDEX IF NOT EXISTS idx_user_config_device_hash ON user_config_snapshots(device_name, content_hash);\n\n-- repo_config_snapshots\nCREATE INDEX IF NOT EXISTS idx_repo_config_repo_ts ON repo_config_snapshots(repository, snapshot_at_ms);\nCREATE INDEX IF NOT EXISTS idx_repo_config_session ON repo_config_snapshots(session_id);\nCREATE INDEX IF NOT EXISTS idx_repo_config_repo_hash ON repo_config_snapshots(repository, content_hash);\n\n`;\n\n/**\n * Scanner data version. Increment when parser logic changes in ways that\n * affect stored data (new fields extracted, content formatting changes,\n * fork detection improvements, etc.). On startup, if the DB's user_version\n * is lower than this, a full resync is triggered automatically.\n */\nexport const SCANNER_DATA_VERSION = 1;\n\nlet _db: Database.Database | null = null;\nlet _needsResync = false;\n\nfunction registerCompressionFunctions(db: Database.Database): void {\n db.function(\"decompress\", (blob: Buffer | null) =>\n blob ? gunzipSync(blob).toString() : null,\n );\n}\n\nexport function getDb(): Database.Database {\n // If the db file was deleted (e.g. uninstall --purge) while this process\n // still holds a stale connection, drop it so we don't serve old data.\n if (_db && !fs.existsSync(config.dbPath)) {\n try {\n _db.close();\n } catch {}\n _db = null;\n }\n if (_db) return _db;\n\n // Don't auto-create the data directory — callers that need to bootstrap\n // the DB (install, initDb, hook handler) call ensureDataDir() first.\n if (!fs.existsSync(config.dataDir)) {\n throw new Error(\n `Panopticon data directory not found: ${config.dataDir}. Run \"panopticon install\" to set up.`,\n );\n }\n\n _db = new Database(config.dbPath);\n _db.pragma(\"auto_vacuum = INCREMENTAL\");\n _db.pragma(\"journal_mode = WAL\");\n _db.pragma(\"busy_timeout = 5000\");\n\n registerCompressionFunctions(_db);\n _db.exec(SCHEMA_SQL);\n\n // Check data version for resync\n const currentVersion = (_db.pragma(\"user_version\", { simple: true }) ??\n 0) as number;\n if (currentVersion < SCANNER_DATA_VERSION) {\n _needsResync = true;\n } else {\n _needsResync = false;\n }\n\n return _db;\n}\n\n/** True when the DB was opened with a stale data version. */\nexport function needsResync(): boolean {\n return _needsResync;\n}\n\n/** Mark resync as complete and stamp the current data version. */\nexport function markResyncComplete(): void {\n _needsResync = false;\n const db = getDb();\n db.pragma(`user_version = ${SCANNER_DATA_VERSION}`);\n}\n\nexport function closeDb(): void {\n if (_db) {\n _db.close();\n _db = null;\n }\n}\n"],"mappings":"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","names":[]}