tracelattice 1.2.5

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 (294) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +112 -0
  3. package/dist/ServerConfig.d.ts +229 -0
  4. package/dist/ServerConfig.d.ts.map +1 -0
  5. package/dist/ServerConfig.js +121 -0
  6. package/dist/ServerConfig.js.map +1 -0
  7. package/dist/__tests__/base-registry.test.d.ts +2 -0
  8. package/dist/__tests__/base-registry.test.d.ts.map +1 -0
  9. package/dist/__tests__/base-transport-cov.test.d.ts +2 -0
  10. package/dist/__tests__/base-transport-cov.test.d.ts.map +1 -0
  11. package/dist/__tests__/base-transport.test.d.ts +2 -0
  12. package/dist/__tests__/base-transport.test.d.ts.map +1 -0
  13. package/dist/__tests__/config-loader.test.d.ts +2 -0
  14. package/dist/__tests__/config-loader.test.d.ts.map +1 -0
  15. package/dist/__tests__/connection-pool-cov.test.d.ts +2 -0
  16. package/dist/__tests__/connection-pool-cov.test.d.ts.map +1 -0
  17. package/dist/__tests__/connection-pool.test.d.ts +2 -0
  18. package/dist/__tests__/connection-pool.test.d.ts.map +1 -0
  19. package/dist/__tests__/container.test.d.ts +2 -0
  20. package/dist/__tests__/container.test.d.ts.map +1 -0
  21. package/dist/__tests__/crud.test.d.ts +2 -0
  22. package/dist/__tests__/crud.test.d.ts.map +1 -0
  23. package/dist/__tests__/discovery-cache.test.d.ts +2 -0
  24. package/dist/__tests__/discovery-cache.test.d.ts.map +1 -0
  25. package/dist/__tests__/errors.test.d.ts +2 -0
  26. package/dist/__tests__/errors.test.d.ts.map +1 -0
  27. package/dist/__tests__/factories.test.d.ts +2 -0
  28. package/dist/__tests__/factories.test.d.ts.map +1 -0
  29. package/dist/__tests__/health-checker-cov.test.d.ts +2 -0
  30. package/dist/__tests__/health-checker-cov.test.d.ts.map +1 -0
  31. package/dist/__tests__/health-checker.test.d.ts +2 -0
  32. package/dist/__tests__/health-checker.test.d.ts.map +1 -0
  33. package/dist/__tests__/helpers/factories.d.ts +36 -0
  34. package/dist/__tests__/helpers/factories.d.ts.map +1 -0
  35. package/dist/__tests__/helpers/index.d.ts +3 -0
  36. package/dist/__tests__/helpers/index.d.ts.map +1 -0
  37. package/dist/__tests__/helpers/timers.d.ts +4 -0
  38. package/dist/__tests__/helpers/timers.d.ts.map +1 -0
  39. package/dist/__tests__/history-manager.test.d.ts +2 -0
  40. package/dist/__tests__/history-manager.test.d.ts.map +1 -0
  41. package/dist/__tests__/http-helpers-cov.test.d.ts +2 -0
  42. package/dist/__tests__/http-helpers-cov.test.d.ts.map +1 -0
  43. package/dist/__tests__/http-transport-cov.test.d.ts +2 -0
  44. package/dist/__tests__/http-transport-cov.test.d.ts.map +1 -0
  45. package/dist/__tests__/http-transport.test.d.ts +2 -0
  46. package/dist/__tests__/http-transport.test.d.ts.map +1 -0
  47. package/dist/__tests__/input-normalizer.test.d.ts +8 -0
  48. package/dist/__tests__/input-normalizer.test.d.ts.map +1 -0
  49. package/dist/__tests__/integration.test.d.ts +2 -0
  50. package/dist/__tests__/integration.test.d.ts.map +1 -0
  51. package/dist/__tests__/lib-server.test.d.ts +2 -0
  52. package/dist/__tests__/lib-server.test.d.ts.map +1 -0
  53. package/dist/__tests__/memory-persistence.test.d.ts +2 -0
  54. package/dist/__tests__/memory-persistence.test.d.ts.map +1 -0
  55. package/dist/__tests__/metrics-integration.test.d.ts +2 -0
  56. package/dist/__tests__/metrics-integration.test.d.ts.map +1 -0
  57. package/dist/__tests__/persistence.test.d.ts +2 -0
  58. package/dist/__tests__/persistence.test.d.ts.map +1 -0
  59. package/dist/__tests__/reasoning-integration.test.d.ts +11 -0
  60. package/dist/__tests__/reasoning-integration.test.d.ts.map +1 -0
  61. package/dist/__tests__/reasoning-types.test.d.ts +2 -0
  62. package/dist/__tests__/reasoning-types.test.d.ts.map +1 -0
  63. package/dist/__tests__/request-context.test.d.ts +2 -0
  64. package/dist/__tests__/request-context.test.d.ts.map +1 -0
  65. package/dist/__tests__/sanitize.test.d.ts +2 -0
  66. package/dist/__tests__/sanitize.test.d.ts.map +1 -0
  67. package/dist/__tests__/schema.test.d.ts +2 -0
  68. package/dist/__tests__/schema.test.d.ts.map +1 -0
  69. package/dist/__tests__/sequentialthinking-tools.test.d.ts +2 -0
  70. package/dist/__tests__/sequentialthinking-tools.test.d.ts.map +1 -0
  71. package/dist/__tests__/server-config.test.d.ts +2 -0
  72. package/dist/__tests__/server-config.test.d.ts.map +1 -0
  73. package/dist/__tests__/skill-discovery.test.d.ts +2 -0
  74. package/dist/__tests__/skill-discovery.test.d.ts.map +1 -0
  75. package/dist/__tests__/skill-registry.test.d.ts +2 -0
  76. package/dist/__tests__/skill-registry.test.d.ts.map +1 -0
  77. package/dist/__tests__/skill-watcher.test.d.ts +2 -0
  78. package/dist/__tests__/skill-watcher.test.d.ts.map +1 -0
  79. package/dist/__tests__/sqlite-persistence.test.d.ts +2 -0
  80. package/dist/__tests__/sqlite-persistence.test.d.ts.map +1 -0
  81. package/dist/__tests__/sse-transport-cov.test.d.ts +2 -0
  82. package/dist/__tests__/sse-transport-cov.test.d.ts.map +1 -0
  83. package/dist/__tests__/sse-transport.test.d.ts +2 -0
  84. package/dist/__tests__/sse-transport.test.d.ts.map +1 -0
  85. package/dist/__tests__/streamable-http-cov.test.d.ts +2 -0
  86. package/dist/__tests__/streamable-http-cov.test.d.ts.map +1 -0
  87. package/dist/__tests__/streamable-http-transport.test.d.ts +2 -0
  88. package/dist/__tests__/streamable-http-transport.test.d.ts.map +1 -0
  89. package/dist/__tests__/structured-logger.test.d.ts +2 -0
  90. package/dist/__tests__/structured-logger.test.d.ts.map +1 -0
  91. package/dist/__tests__/thought-evaluator.test.d.ts +2 -0
  92. package/dist/__tests__/thought-evaluator.test.d.ts.map +1 -0
  93. package/dist/__tests__/thought-formatter.test.d.ts +2 -0
  94. package/dist/__tests__/thought-formatter.test.d.ts.map +1 -0
  95. package/dist/__tests__/thought-processor.test.d.ts +8 -0
  96. package/dist/__tests__/thought-processor.test.d.ts.map +1 -0
  97. package/dist/__tests__/tool-registry-cov.test.d.ts +2 -0
  98. package/dist/__tests__/tool-registry-cov.test.d.ts.map +1 -0
  99. package/dist/__tests__/tool-registry.test.d.ts +2 -0
  100. package/dist/__tests__/tool-registry.test.d.ts.map +1 -0
  101. package/dist/__tests__/tool-watcher.test.d.ts +2 -0
  102. package/dist/__tests__/tool-watcher.test.d.ts.map +1 -0
  103. package/dist/__tests__/worker-manager-cov.test.d.ts +2 -0
  104. package/dist/__tests__/worker-manager-cov.test.d.ts.map +1 -0
  105. package/dist/__tests__/worker-manager.test.d.ts +2 -0
  106. package/dist/__tests__/worker-manager.test.d.ts.map +1 -0
  107. package/dist/cache/DiscoveryCache.d.ts +269 -0
  108. package/dist/cache/DiscoveryCache.d.ts.map +1 -0
  109. package/dist/cache/DiscoveryCache.js +100 -0
  110. package/dist/cache/DiscoveryCache.js.map +1 -0
  111. package/dist/cli.d.ts +3 -0
  112. package/dist/cli.d.ts.map +1 -0
  113. package/dist/cli.js +114 -0
  114. package/dist/cli.js.map +1 -0
  115. package/dist/cluster/WorkerManager.d.ts +166 -0
  116. package/dist/cluster/WorkerManager.d.ts.map +1 -0
  117. package/dist/cluster/WorkerManager.js +202 -0
  118. package/dist/cluster/WorkerManager.js.map +1 -0
  119. package/dist/cluster/worker.d.ts +11 -0
  120. package/dist/cluster/worker.d.ts.map +1 -0
  121. package/dist/cluster/worker.js +36 -0
  122. package/dist/cluster/worker.js.map +1 -0
  123. package/dist/config/ConfigLoader.d.ts +224 -0
  124. package/dist/config/ConfigLoader.d.ts.map +1 -0
  125. package/dist/config/ConfigLoader.js +85 -0
  126. package/dist/config/ConfigLoader.js.map +1 -0
  127. package/dist/context/RequestContext.d.ts +61 -0
  128. package/dist/context/RequestContext.d.ts.map +1 -0
  129. package/dist/context/RequestContext.js +17 -0
  130. package/dist/context/RequestContext.js.map +1 -0
  131. package/dist/contracts/index.d.ts +10 -0
  132. package/dist/contracts/index.d.ts.map +1 -0
  133. package/dist/contracts/index.js +1 -0
  134. package/dist/contracts/interfaces.d.ts +107 -0
  135. package/dist/contracts/interfaces.d.ts.map +1 -0
  136. package/dist/contracts/interfaces.js +1 -0
  137. package/dist/core/HistoryManager.d.ts +514 -0
  138. package/dist/core/HistoryManager.d.ts.map +1 -0
  139. package/dist/core/HistoryManager.js +331 -0
  140. package/dist/core/HistoryManager.js.map +1 -0
  141. package/dist/core/IHistoryManager.d.ts +100 -0
  142. package/dist/core/IHistoryManager.d.ts.map +1 -0
  143. package/dist/core/IHistoryManager.js +1 -0
  144. package/dist/core/InputNormalizer.d.ts +139 -0
  145. package/dist/core/InputNormalizer.d.ts.map +1 -0
  146. package/dist/core/InputNormalizer.js +101 -0
  147. package/dist/core/InputNormalizer.js.map +1 -0
  148. package/dist/core/ThoughtEvaluator.d.ts +127 -0
  149. package/dist/core/ThoughtEvaluator.d.ts.map +1 -0
  150. package/dist/core/ThoughtEvaluator.js +346 -0
  151. package/dist/core/ThoughtEvaluator.js.map +1 -0
  152. package/dist/core/ThoughtFormatter.d.ts +133 -0
  153. package/dist/core/ThoughtFormatter.d.ts.map +1 -0
  154. package/dist/core/ThoughtFormatter.js +70 -0
  155. package/dist/core/ThoughtFormatter.js.map +1 -0
  156. package/dist/core/ThoughtProcessor.d.ts +218 -0
  157. package/dist/core/ThoughtProcessor.d.ts.map +1 -0
  158. package/dist/core/ThoughtProcessor.js +205 -0
  159. package/dist/core/ThoughtProcessor.js.map +1 -0
  160. package/dist/core/reasoning.d.ts +169 -0
  161. package/dist/core/reasoning.d.ts.map +1 -0
  162. package/dist/core/reasoning.js +1 -0
  163. package/dist/core/step.d.ts +45 -0
  164. package/dist/core/step.d.ts.map +1 -0
  165. package/dist/core/step.js +1 -0
  166. package/dist/core/thought.d.ts +190 -0
  167. package/dist/core/thought.d.ts.map +1 -0
  168. package/dist/core/thought.js +1 -0
  169. package/dist/di/Container.d.ts +226 -0
  170. package/dist/di/Container.d.ts.map +1 -0
  171. package/dist/di/Container.js +96 -0
  172. package/dist/di/Container.js.map +1 -0
  173. package/dist/di/ServiceRegistry.d.ts +32 -0
  174. package/dist/di/ServiceRegistry.d.ts.map +1 -0
  175. package/dist/di/ServiceRegistry.js +1 -0
  176. package/dist/errors.d.ts +482 -0
  177. package/dist/errors.d.ts.map +1 -0
  178. package/dist/errors.js +108 -0
  179. package/dist/errors.js.map +1 -0
  180. package/dist/health/HealthChecker.d.ts +73 -0
  181. package/dist/health/HealthChecker.d.ts.map +1 -0
  182. package/dist/health/HealthChecker.js +69 -0
  183. package/dist/health/HealthChecker.js.map +1 -0
  184. package/dist/index.d.ts +2 -0
  185. package/dist/index.d.ts.map +1 -0
  186. package/dist/index.js +1 -0
  187. package/dist/lib.d.ts +205 -0
  188. package/dist/lib.d.ts.map +1 -0
  189. package/dist/lib.js +219 -0
  190. package/dist/lib.js.map +1 -0
  191. package/dist/logger/NullLogger.d.ts +154 -0
  192. package/dist/logger/NullLogger.d.ts.map +1 -0
  193. package/dist/logger/NullLogger.js +24 -0
  194. package/dist/logger/NullLogger.js.map +1 -0
  195. package/dist/logger/StructuredLogger.d.ts +327 -0
  196. package/dist/logger/StructuredLogger.d.ts.map +1 -0
  197. package/dist/logger/StructuredLogger.js +72 -0
  198. package/dist/logger/StructuredLogger.js.map +1 -0
  199. package/dist/metrics/__tests__/metrics.test.d.ts +2 -0
  200. package/dist/metrics/__tests__/metrics.test.d.ts.map +1 -0
  201. package/dist/metrics/metrics.impl.d.ts +252 -0
  202. package/dist/metrics/metrics.impl.d.ts.map +1 -0
  203. package/dist/metrics/metrics.impl.js +197 -0
  204. package/dist/metrics/metrics.impl.js.map +1 -0
  205. package/dist/persistence/FilePersistence.d.ts +66 -0
  206. package/dist/persistence/FilePersistence.d.ts.map +1 -0
  207. package/dist/persistence/FilePersistence.js +132 -0
  208. package/dist/persistence/FilePersistence.js.map +1 -0
  209. package/dist/persistence/MemoryPersistence.d.ts +68 -0
  210. package/dist/persistence/MemoryPersistence.d.ts.map +1 -0
  211. package/dist/persistence/MemoryPersistence.js +51 -0
  212. package/dist/persistence/MemoryPersistence.js.map +1 -0
  213. package/dist/persistence/PersistenceBackend.d.ts +69 -0
  214. package/dist/persistence/PersistenceBackend.d.ts.map +1 -0
  215. package/dist/persistence/PersistenceBackend.js +1 -0
  216. package/dist/persistence/PersistenceFactory.d.ts +21 -0
  217. package/dist/persistence/PersistenceFactory.d.ts.map +1 -0
  218. package/dist/persistence/PersistenceFactory.js +25 -0
  219. package/dist/persistence/PersistenceFactory.js.map +1 -0
  220. package/dist/persistence/SqlitePersistence.d.ts +60 -0
  221. package/dist/persistence/SqlitePersistence.d.ts.map +1 -0
  222. package/dist/persistence/SqlitePersistence.js +136 -0
  223. package/dist/persistence/SqlitePersistence.js.map +1 -0
  224. package/dist/pool/ConnectionPool.d.ts +215 -0
  225. package/dist/pool/ConnectionPool.d.ts.map +1 -0
  226. package/dist/pool/ConnectionPool.js +187 -0
  227. package/dist/pool/ConnectionPool.js.map +1 -0
  228. package/dist/registry/BaseRegistry.d.ts +203 -0
  229. package/dist/registry/BaseRegistry.d.ts.map +1 -0
  230. package/dist/registry/BaseRegistry.js +165 -0
  231. package/dist/registry/BaseRegistry.js.map +1 -0
  232. package/dist/registry/SkillRegistry.d.ts +69 -0
  233. package/dist/registry/SkillRegistry.d.ts.map +1 -0
  234. package/dist/registry/SkillRegistry.js +88 -0
  235. package/dist/registry/SkillRegistry.js.map +1 -0
  236. package/dist/registry/ToolRegistry.d.ts +69 -0
  237. package/dist/registry/ToolRegistry.d.ts.map +1 -0
  238. package/dist/registry/ToolRegistry.js +93 -0
  239. package/dist/registry/ToolRegistry.js.map +1 -0
  240. package/dist/sanitize.d.ts +63 -0
  241. package/dist/sanitize.d.ts.map +1 -0
  242. package/dist/sanitize.js +14 -0
  243. package/dist/sanitize.js.map +1 -0
  244. package/dist/schema.d.ts +531 -0
  245. package/dist/schema.d.ts.map +1 -0
  246. package/dist/schema.js +204 -0
  247. package/dist/schema.js.map +1 -0
  248. package/dist/telemetry/Telemetry.d.ts +36 -0
  249. package/dist/telemetry/Telemetry.d.ts.map +1 -0
  250. package/dist/telemetry/Telemetry.js +68 -0
  251. package/dist/telemetry/Telemetry.js.map +1 -0
  252. package/dist/telemetry/__tests__/Telemetry.test.d.ts +2 -0
  253. package/dist/telemetry/__tests__/Telemetry.test.d.ts.map +1 -0
  254. package/dist/transport/BaseTransport.d.ts +184 -0
  255. package/dist/transport/BaseTransport.d.ts.map +1 -0
  256. package/dist/transport/BaseTransport.js +200 -0
  257. package/dist/transport/BaseTransport.js.map +1 -0
  258. package/dist/transport/HttpHelpers.d.ts +60 -0
  259. package/dist/transport/HttpHelpers.d.ts.map +1 -0
  260. package/dist/transport/HttpHelpers.js +50 -0
  261. package/dist/transport/HttpHelpers.js.map +1 -0
  262. package/dist/transport/HttpTransport.d.ts +134 -0
  263. package/dist/transport/HttpTransport.d.ts.map +1 -0
  264. package/dist/transport/HttpTransport.js +175 -0
  265. package/dist/transport/HttpTransport.js.map +1 -0
  266. package/dist/transport/SseTransport.d.ts +133 -0
  267. package/dist/transport/SseTransport.d.ts.map +1 -0
  268. package/dist/transport/SseTransport.js +318 -0
  269. package/dist/transport/SseTransport.js.map +1 -0
  270. package/dist/transport/StreamableHttpTransport.d.ts +224 -0
  271. package/dist/transport/StreamableHttpTransport.d.ts.map +1 -0
  272. package/dist/transport/StreamableHttpTransport.js +407 -0
  273. package/dist/transport/StreamableHttpTransport.js.map +1 -0
  274. package/dist/types/disposable.d.ts +22 -0
  275. package/dist/types/disposable.d.ts.map +1 -0
  276. package/dist/types/disposable.js +1 -0
  277. package/dist/types/server-config.d.ts +32 -0
  278. package/dist/types/server-config.d.ts.map +1 -0
  279. package/dist/types/server-config.js +1 -0
  280. package/dist/types/skill.d.ts +69 -0
  281. package/dist/types/skill.d.ts.map +1 -0
  282. package/dist/types/skill.js +1 -0
  283. package/dist/types/tool.d.ts +68 -0
  284. package/dist/types/tool.d.ts.map +1 -0
  285. package/dist/types/tool.js +1 -0
  286. package/dist/watchers/SkillWatcher.d.ts +132 -0
  287. package/dist/watchers/SkillWatcher.d.ts.map +1 -0
  288. package/dist/watchers/SkillWatcher.js +73 -0
  289. package/dist/watchers/SkillWatcher.js.map +1 -0
  290. package/dist/watchers/ToolWatcher.d.ts +109 -0
  291. package/dist/watchers/ToolWatcher.d.ts.map +1 -0
  292. package/dist/watchers/ToolWatcher.js +71 -0
  293. package/dist/watchers/ToolWatcher.js.map +1 -0
  294. package/package.json +95 -0
@@ -0,0 +1,132 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join, resolve, sep } from "node:path";
5
+ class FilePersistence {
6
+ _dataDir;
7
+ _historyPath;
8
+ _branchesDir;
9
+ _maxHistorySize;
10
+ _persistBranches;
11
+ _metrics;
12
+ constructor(options){
13
+ const defaultDataDir = existsSync('.claude/data') ? '.claude/data' : join(homedir(), '.claude/data');
14
+ this._dataDir = options?.dataDir ?? defaultDataDir;
15
+ this._historyPath = join(this._dataDir, 'history.json');
16
+ this._branchesDir = join(this._dataDir, 'branches');
17
+ this._maxHistorySize = options?.maxHistorySize ?? 10000;
18
+ this._persistBranches = options?.persistBranches ?? true;
19
+ this._metrics = options?.metrics;
20
+ }
21
+ _recordOperationDuration(operation, startTime) {
22
+ const durationSeconds = (Date.now() - startTime) / 1000;
23
+ this._metrics?.histogram('persistence_op_duration_seconds', durationSeconds, {
24
+ operation
25
+ });
26
+ }
27
+ async _ensureDirectories() {
28
+ if (!existsSync(this._dataDir)) await mkdir(this._dataDir, {
29
+ recursive: true
30
+ });
31
+ if (this._persistBranches && !existsSync(this._branchesDir)) await mkdir(this._branchesDir, {
32
+ recursive: true
33
+ });
34
+ }
35
+ _safeBranchPath(branchId) {
36
+ const validBranchIdPattern = /^[a-zA-Z0-9_-]{1,64}$/;
37
+ if (!validBranchIdPattern.test(branchId)) throw new Error("Invalid branch ID: must be 1-64 alphanumeric characters, hyphens, or underscores only");
38
+ const resolved = resolve(this._branchesDir, `${branchId}.json`);
39
+ const normalizedBranchesDir = resolve(this._branchesDir);
40
+ if (!resolved.startsWith(normalizedBranchesDir + sep)) throw new Error("Invalid branch ID: path traversal detected");
41
+ return resolved;
42
+ }
43
+ async saveThought(thought) {
44
+ const startTime = Date.now();
45
+ try {
46
+ await this._ensureDirectories();
47
+ const history = await this.loadHistory();
48
+ history.push(thought);
49
+ if (history.length > this._maxHistorySize) history.splice(0, history.length - this._maxHistorySize);
50
+ await writeFile(this._historyPath, JSON.stringify(history, null, 2), 'utf-8');
51
+ } finally{
52
+ this._recordOperationDuration('save_thought', startTime);
53
+ }
54
+ }
55
+ async loadHistory() {
56
+ const startTime = Date.now();
57
+ try {
58
+ if (!existsSync(this._historyPath)) return [];
59
+ const content = await readFile(this._historyPath, 'utf-8');
60
+ const data = JSON.parse(content);
61
+ return Array.isArray(data) ? data : [];
62
+ } catch {
63
+ return [];
64
+ } finally{
65
+ this._recordOperationDuration('load_history', startTime);
66
+ }
67
+ }
68
+ async saveBranch(branchId, thoughts) {
69
+ const startTime = Date.now();
70
+ try {
71
+ if (!this._persistBranches) return;
72
+ await this._ensureDirectories();
73
+ const branchPath = this._safeBranchPath(branchId);
74
+ await writeFile(branchPath, JSON.stringify(thoughts, null, 2), 'utf-8');
75
+ } finally{
76
+ this._recordOperationDuration('save_branch', startTime);
77
+ }
78
+ }
79
+ async loadBranch(branchId) {
80
+ const startTime = Date.now();
81
+ try {
82
+ if (!this._persistBranches) return;
83
+ const branchPath = this._safeBranchPath(branchId);
84
+ try {
85
+ if (!existsSync(branchPath)) return;
86
+ const content = await readFile(branchPath, 'utf-8');
87
+ const data = JSON.parse(content);
88
+ return Array.isArray(data) ? data : void 0;
89
+ } catch {
90
+ return;
91
+ }
92
+ } finally{
93
+ this._recordOperationDuration('load_branch', startTime);
94
+ }
95
+ }
96
+ async listBranches() {
97
+ return this.getBranchIds();
98
+ }
99
+ async clear() {
100
+ try {
101
+ if (existsSync(this._historyPath)) await unlink(this._historyPath);
102
+ if (this._persistBranches && existsSync(this._branchesDir)) {
103
+ const files = await readdir(this._branchesDir);
104
+ for (const file of files)if (file.endsWith('.json')) await unlink(join(this._branchesDir, file));
105
+ }
106
+ } catch {}
107
+ }
108
+ async healthy() {
109
+ try {
110
+ await this._ensureDirectories();
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+ getDataDir() {
117
+ return this._dataDir;
118
+ }
119
+ async getBranchIds() {
120
+ if (!this._persistBranches || !existsSync(this._branchesDir)) return [];
121
+ try {
122
+ const files = await readdir(this._branchesDir);
123
+ return files.filter((f)=>f.endsWith('.json')).map((f)=>f.replace('.json', ''));
124
+ } catch {
125
+ return [];
126
+ }
127
+ }
128
+ async close() {}
129
+ }
130
+ export { FilePersistence };
131
+
132
+ //# sourceMappingURL=FilePersistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence/FilePersistence.js","sources":["../../src/persistence/FilePersistence.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { mkdir, readFile, readdir, unlink, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join, resolve, sep } from 'node:path';\nimport type { IMetrics } from '../contracts/index.js';\nimport type { ThoughtData } from '../core/thought.js';\nimport type { PersistenceBackend, PersistenceConfig } from './PersistenceBackend.js';\n\n/**\n * File-based persistence backend using JSON files.\n *\n * Stores thoughts and branches as JSON files in a configured directory.\n * Simple and reliable, with no external dependencies.\n *\n * File structure:\n * ```\n * dataDir/\n * history.json # Main thought history\n * branches/\n * <branch-id>.json # Individual branch files\n * ```\n */\nexport class FilePersistence implements PersistenceBackend {\n\tprivate _dataDir: string;\n\tprivate _historyPath: string;\n\tprivate _branchesDir: string;\n\tprivate _maxHistorySize: number;\n\tprivate _persistBranches: boolean;\n\tprivate _metrics?: IMetrics;\n\n\tconstructor(options?: PersistenceConfig['options'] & { metrics?: IMetrics }) {\n\t\t// Default to .claude/data in current directory or home directory\n\t\tconst defaultDataDir = existsSync('.claude/data')\n\t\t\t? '.claude/data'\n\t\t\t: join(homedir(), '.claude/data');\n\t\tthis._dataDir = options?.dataDir ?? defaultDataDir;\n\t\tthis._historyPath = join(this._dataDir, 'history.json');\n\t\tthis._branchesDir = join(this._dataDir, 'branches');\n\t\tthis._maxHistorySize = options?.maxHistorySize ?? 10000;\n\t\tthis._persistBranches = options?.persistBranches ?? true;\n\t\tthis._metrics = options?.metrics;\n\t}\n\n\tprivate _recordOperationDuration(operation: string, startTime: number): void {\n\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\tthis._metrics?.histogram('persistence_op_duration_seconds', durationSeconds, { operation });\n\t}\n\n\t/**\n\t * Initialize the persistence directory structure.\n\t */\n\tprivate async _ensureDirectories(): Promise<void> {\n\t\tif (!existsSync(this._dataDir)) {\n\t\t\tawait mkdir(this._dataDir, { recursive: true });\n\t\t}\n\t\tif (this._persistBranches && !existsSync(this._branchesDir)) {\n\t\t\tawait mkdir(this._branchesDir, { recursive: true });\n\t\t}\n\t}\n\n\t/**\n\t * Validates branch ID format and resolves the path safely.\n\t *\n\t * This method provides defense-in-depth security by:\n\t * 1. Validating the branch ID format (alphanumeric, hyphens, underscores only)\n\t * 2. Preventing path traversal attacks\n\t *\n\t * @param branchId - The branch ID to validate and resolve\n\t * @returns The safe, resolved branch file path\n\t * @throws Error if branch ID is invalid or path traversal is detected\n\t */\n\tprivate _safeBranchPath(branchId: string): string {\n\t\t// Validate format first (must be alphanumeric with hyphens/underscores, 1-64 chars)\n\t\tconst validBranchIdPattern = /^[a-zA-Z0-9_-]{1,64}$/;\n\t\tif (!validBranchIdPattern.test(branchId)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid branch ID: must be 1-64 alphanumeric characters, hyphens, or underscores only`\n\t\t\t);\n\t\t}\n\n\t\tconst resolved = resolve(this._branchesDir, `${branchId}.json`);\n\t\tconst normalizedBranchesDir = resolve(this._branchesDir);\n\n\t\t// Ensure the resolved path is still within branches directory\n\t\tif (!resolved.startsWith(normalizedBranchesDir + sep)) {\n\t\t\tthrow new Error(`Invalid branch ID: path traversal detected`);\n\t\t}\n\n\t\treturn resolved;\n\t}\n\n\tpublic async saveThought(thought: ThoughtData): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\ttry {\n\t\t\tawait this._ensureDirectories();\n\n\t\t\tconst history = await this.loadHistory();\n\t\t\thistory.push(thought);\n\n\t\t\tif (history.length > this._maxHistorySize) {\n\t\t\t\thistory.splice(0, history.length - this._maxHistorySize);\n\t\t\t}\n\n\t\t\tawait writeFile(this._historyPath, JSON.stringify(history, null, 2), 'utf-8');\n\t\t} finally {\n\t\t\tthis._recordOperationDuration('save_thought', startTime);\n\t\t}\n\t}\n\n\tpublic async loadHistory(): Promise<ThoughtData[]> {\n\t\tconst startTime = Date.now();\n\t\ttry {\n\t\t\tif (!existsSync(this._historyPath)) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tconst content = await readFile(this._historyPath, 'utf-8');\n\t\t\tconst data = JSON.parse(content) as ThoughtData[];\n\n\t\t\t// Validate and filter\n\t\t\treturn Array.isArray(data) ? data : [];\n\t\t} catch {\n\t\t\t// If file is corrupted, start fresh\n\t\t\treturn [];\n\t\t} finally {\n\t\t\tthis._recordOperationDuration('load_history', startTime);\n\t\t}\n\t}\n\n\tpublic async saveBranch(branchId: string, thoughts: ThoughtData[]): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\ttry {\n\t\t\tif (!this._persistBranches) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait this._ensureDirectories();\n\n\t\t\tconst branchPath = this._safeBranchPath(branchId);\n\t\t\tawait writeFile(branchPath, JSON.stringify(thoughts, null, 2), 'utf-8');\n\t\t} finally {\n\t\t\tthis._recordOperationDuration('save_branch', startTime);\n\t\t}\n\t}\n\n\tpublic async loadBranch(branchId: string): Promise<ThoughtData[] | undefined> {\n\t\tconst startTime = Date.now();\n\t\ttry {\n\t\t\tif (!this._persistBranches) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\tconst branchPath = this._safeBranchPath(branchId);\n\n\t\t\ttry {\n\t\t\t\tif (!existsSync(branchPath)) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\n\t\t\t\tconst content = await readFile(branchPath, 'utf-8');\n\t\t\t\tconst data = JSON.parse(content) as ThoughtData[];\n\n\t\t\t\treturn Array.isArray(data) ? data : undefined;\n\t\t\t} catch {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t} finally {\n\t\t\tthis._recordOperationDuration('load_branch', startTime);\n\t\t}\n\t}\n\n\tpublic async listBranches(): Promise<string[]> {\n\t\treturn this.getBranchIds();\n\t}\n\n\tpublic async clear(): Promise<void> {\n\t\ttry {\n\t\t\t// Clear history\n\t\t\tif (existsSync(this._historyPath)) {\n\t\t\t\tawait unlink(this._historyPath);\n\t\t\t}\n\n\t\t\t// Clear all branches\n\t\t\tif (this._persistBranches && existsSync(this._branchesDir)) {\n\t\t\t\tconst files = await readdir(this._branchesDir);\n\t\t\t\tfor (const file of files) {\n\t\t\t\t\tif (file.endsWith('.json')) {\n\t\t\t\t\t\tawait unlink(join(this._branchesDir, file));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors during clear\n\t\t}\n\t}\n\n\tpublic async healthy(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this._ensureDirectories();\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Get the data directory path.\n\t */\n\tpublic getDataDir(): string {\n\t\treturn this._dataDir;\n\t}\n\n\t/**\n\t * Get all branch IDs that are persisted.\n\t */\n\tpublic async getBranchIds(): Promise<string[]> {\n\t\tif (!this._persistBranches || !existsSync(this._branchesDir)) {\n\t\t\treturn [];\n\t\t}\n\n\t\ttry {\n\t\t\tconst files = await readdir(this._branchesDir);\n\t\t\treturn files.filter((f) => f.endsWith('.json')).map((f) => f.replace('.json', ''));\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Close the backend and release resources.\n\t * No resources to release for file backend.\n\t */\n\tpublic async close(): Promise<void> {\n\t\t// No-op for file backend (files are already flushed on write)\n\t}\n}\n"],"names":["FilePersistence","options","defaultDataDir","existsSync","join","homedir","operation","startTime","durationSeconds","Date","mkdir","branchId","validBranchIdPattern","Error","resolved","resolve","normalizedBranchesDir","sep","thought","history","writeFile","JSON","content","readFile","data","Array","thoughts","branchPath","undefined","unlink","files","readdir","file","f"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { ThoughtData } from '../core/thought.js';
2
+ import type { PersistenceBackend } from './PersistenceBackend.js';
3
+ /**
4
+ * Configuration options for MemoryPersistence.
5
+ */
6
+ export interface MemoryPersistenceOptions {
7
+ /**
8
+ * Maximum number of thoughts to keep in memory.
9
+ * Older thoughts are trimmed when limit is exceeded.
10
+ * Set to 0 or undefined for unlimited.
11
+ * @default undefined (unlimited)
12
+ */
13
+ maxSize?: number;
14
+ }
15
+ /**
16
+ * In-memory persistence backend for testing purposes.
17
+ *
18
+ * This backend stores all data in memory and provides no durability.
19
+ * It's useful for testing and development where persistence is not needed.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Unlimited history
24
+ * const backend = new MemoryPersistence();
25
+ *
26
+ * // Limited to 1000 thoughts
27
+ * const backend = new MemoryPersistence({ maxSize: 1000 });
28
+ *
29
+ * await backend.saveThought(thought);
30
+ * const history = await backend.loadHistory();
31
+ * ```
32
+ */
33
+ export declare class MemoryPersistence implements PersistenceBackend {
34
+ private _history;
35
+ private _branches;
36
+ private _maxSize?;
37
+ constructor(options?: MemoryPersistenceOptions);
38
+ saveThought(thought: ThoughtData): Promise<void>;
39
+ loadHistory(): Promise<ThoughtData[]>;
40
+ saveBranch(branchId: string, thoughts: ThoughtData[]): Promise<void>;
41
+ loadBranch(branchId: string): Promise<ThoughtData[] | undefined>;
42
+ listBranches(): Promise<string[]>;
43
+ /**
44
+ * In-memory backend is always healthy.
45
+ */
46
+ healthy(): Promise<boolean>;
47
+ /**
48
+ * Clear all data from memory.
49
+ */
50
+ clear(): Promise<void>;
51
+ /**
52
+ * No resources to release for in-memory backend.
53
+ */
54
+ close(): Promise<void>;
55
+ /**
56
+ * Get the current number of thoughts in memory.
57
+ */
58
+ getHistorySize(): number;
59
+ /**
60
+ * Get the current number of branches in memory.
61
+ */
62
+ getBranchCount(): number;
63
+ /**
64
+ * Get all branch IDs.
65
+ */
66
+ getBranchIds(): string[];
67
+ }
68
+ //# sourceMappingURL=MemoryPersistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemoryPersistence.d.ts","sourceRoot":"","sources":["../../src/persistence/MemoryPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,iBAAkB,YAAW,kBAAkB;IAC3D,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAS;gBAEd,OAAO,GAAE,wBAA6B;IAIrC,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAShD,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAIrC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC;IAKhE,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI9C;;OAEG;IACU,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIxC;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKnC;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC;;OAEG;IACI,cAAc,IAAI,MAAM;IAI/B;;OAEG;IACI,cAAc,IAAI,MAAM;IAI/B;;OAEG;IACI,YAAY,IAAI,MAAM,EAAE;CAG/B"}
@@ -0,0 +1,51 @@
1
+ class MemoryPersistence {
2
+ _history = [];
3
+ _branches = new Map();
4
+ _maxSize;
5
+ constructor(options = {}){
6
+ this._maxSize = options.maxSize && options.maxSize > 0 ? options.maxSize : void 0;
7
+ }
8
+ async saveThought(thought) {
9
+ this._history.push(thought);
10
+ if (void 0 !== this._maxSize && this._history.length > this._maxSize) this._history = this._history.slice(-this._maxSize);
11
+ }
12
+ async loadHistory() {
13
+ return [
14
+ ...this._history
15
+ ];
16
+ }
17
+ async saveBranch(branchId, thoughts) {
18
+ this._branches.set(branchId, [
19
+ ...thoughts
20
+ ]);
21
+ }
22
+ async loadBranch(branchId) {
23
+ const branch = this._branches.get(branchId);
24
+ return branch ? [
25
+ ...branch
26
+ ] : void 0;
27
+ }
28
+ async listBranches() {
29
+ return this.getBranchIds();
30
+ }
31
+ async healthy() {
32
+ return true;
33
+ }
34
+ async clear() {
35
+ this._history = [];
36
+ this._branches.clear();
37
+ }
38
+ async close() {}
39
+ getHistorySize() {
40
+ return this._history.length;
41
+ }
42
+ getBranchCount() {
43
+ return this._branches.size;
44
+ }
45
+ getBranchIds() {
46
+ return Array.from(this._branches.keys());
47
+ }
48
+ }
49
+ export { MemoryPersistence };
50
+
51
+ //# sourceMappingURL=MemoryPersistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence/MemoryPersistence.js","sources":["../../src/persistence/MemoryPersistence.ts"],"sourcesContent":["import type { ThoughtData } from '../core/thought.js';\nimport type { PersistenceBackend } from './PersistenceBackend.js';\n\n/**\n * Configuration options for MemoryPersistence.\n */\nexport interface MemoryPersistenceOptions {\n\t/**\n\t * Maximum number of thoughts to keep in memory.\n\t * Older thoughts are trimmed when limit is exceeded.\n\t * Set to 0 or undefined for unlimited.\n\t * @default undefined (unlimited)\n\t */\n\tmaxSize?: number;\n}\n\n/**\n * In-memory persistence backend for testing purposes.\n *\n * This backend stores all data in memory and provides no durability.\n * It's useful for testing and development where persistence is not needed.\n *\n * @example\n * ```typescript\n * // Unlimited history\n * const backend = new MemoryPersistence();\n *\n * // Limited to 1000 thoughts\n * const backend = new MemoryPersistence({ maxSize: 1000 });\n *\n * await backend.saveThought(thought);\n * const history = await backend.loadHistory();\n * ```\n */\nexport class MemoryPersistence implements PersistenceBackend {\n\tprivate _history: ThoughtData[] = [];\n\tprivate _branches: Map<string, ThoughtData[]> = new Map();\n\tprivate _maxSize?: number;\n\n\tconstructor(options: MemoryPersistenceOptions = {}) {\n\t\tthis._maxSize = options.maxSize && options.maxSize > 0 ? options.maxSize : undefined;\n\t}\n\n\tpublic async saveThought(thought: ThoughtData): Promise<void> {\n\t\tthis._history.push(thought);\n\n\t\t// Trim if maxSize is set and exceeded\n\t\tif (this._maxSize !== undefined && this._history.length > this._maxSize) {\n\t\t\tthis._history = this._history.slice(-this._maxSize);\n\t\t}\n\t}\n\n\tpublic async loadHistory(): Promise<ThoughtData[]> {\n\t\treturn [...this._history];\n\t}\n\n\tpublic async saveBranch(branchId: string, thoughts: ThoughtData[]): Promise<void> {\n\t\tthis._branches.set(branchId, [...thoughts]);\n\t}\n\n\tpublic async loadBranch(branchId: string): Promise<ThoughtData[] | undefined> {\n\t\tconst branch = this._branches.get(branchId);\n\t\treturn branch ? [...branch] : undefined;\n\t}\n\n\tpublic async listBranches(): Promise<string[]> {\n\t\treturn this.getBranchIds();\n\t}\n\n\t/**\n\t * In-memory backend is always healthy.\n\t */\n\tpublic async healthy(): Promise<boolean> {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Clear all data from memory.\n\t */\n\tpublic async clear(): Promise<void> {\n\t\tthis._history = [];\n\t\tthis._branches.clear();\n\t}\n\n\t/**\n\t * No resources to release for in-memory backend.\n\t */\n\tpublic async close(): Promise<void> {\n\t\t// No-op for in-memory backend\n\t}\n\n\t/**\n\t * Get the current number of thoughts in memory.\n\t */\n\tpublic getHistorySize(): number {\n\t\treturn this._history.length;\n\t}\n\n\t/**\n\t * Get the current number of branches in memory.\n\t */\n\tpublic getBranchCount(): number {\n\t\treturn this._branches.size;\n\t}\n\n\t/**\n\t * Get all branch IDs.\n\t */\n\tpublic getBranchIds(): string[] {\n\t\treturn Array.from(this._branches.keys());\n\t}\n}\n"],"names":["MemoryPersistence","Map","options","undefined","thought","branchId","thoughts","branch","Array"],"mappings":"AAkCO,MAAMA;IACJ,WAA0B,EAAE,CAAC;IAC7B,YAAwC,IAAIC,MAAM;IAClD,SAAkB;IAE1B,YAAYC,UAAoC,CAAC,CAAC,CAAE;QACnD,IAAI,CAAC,QAAQ,GAAGA,QAAQ,OAAO,IAAIA,QAAQ,OAAO,GAAG,IAAIA,QAAQ,OAAO,GAAGC;IAC5E;IAEA,MAAa,YAAYC,OAAoB,EAAiB;QAC7D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAACA;QAGnB,IAAI,AAAkBD,WAAlB,IAAI,CAAC,QAAQ,IAAkB,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,EACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ;IAEpD;IAEA,MAAa,cAAsC;QAClD,OAAO;eAAI,IAAI,CAAC,QAAQ;SAAC;IAC1B;IAEA,MAAa,WAAWE,QAAgB,EAAEC,QAAuB,EAAiB;QACjF,IAAI,CAAC,SAAS,CAAC,GAAG,CAACD,UAAU;eAAIC;SAAS;IAC3C;IAEA,MAAa,WAAWD,QAAgB,EAAsC;QAC7E,MAAME,SAAS,IAAI,CAAC,SAAS,CAAC,GAAG,CAACF;QAClC,OAAOE,SAAS;eAAIA;SAAO,GAAGJ;IAC/B;IAEA,MAAa,eAAkC;QAC9C,OAAO,IAAI,CAAC,YAAY;IACzB;IAKA,MAAa,UAA4B;QACxC,OAAO;IACR;IAKA,MAAa,QAAuB;QACnC,IAAI,CAAC,QAAQ,GAAG,EAAE;QAClB,IAAI,CAAC,SAAS,CAAC,KAAK;IACrB;IAKA,MAAa,QAAuB,CAEpC;IAKO,iBAAyB;QAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM;IAC5B;IAKO,iBAAyB;QAC/B,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI;IAC3B;IAKO,eAAyB;QAC/B,OAAOK,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI;IACtC;AACD"}
@@ -0,0 +1,69 @@
1
+ import type { ThoughtData } from '../core/thought.js';
2
+ /**
3
+ * Persistence backend interface for storing thought history and branches.
4
+ *
5
+ * Implementations can store data in various formats (JSON files, SQLite, etc.)
6
+ * while providing a unified API for HistoryManager.
7
+ */
8
+ export interface PersistenceBackend {
9
+ /**
10
+ * Save a single thought to persistent storage.
11
+ *
12
+ * @param thought - The thought data to persist
13
+ */
14
+ saveThought(thought: ThoughtData): Promise<void>;
15
+ /**
16
+ * Load all thoughts from persistent storage.
17
+ * Returns thoughts in chronological order (oldest first).
18
+ *
19
+ * @returns Array of all persisted thoughts
20
+ */
21
+ loadHistory(): Promise<ThoughtData[]>;
22
+ /**
23
+ * Save all thoughts in a branch to persistent storage.
24
+ *
25
+ * @param branchId - The unique identifier for the branch
26
+ * @param thoughts - Array of thoughts in branch
27
+ */
28
+ saveBranch(branchId: string, thoughts: ThoughtData[]): Promise<void>;
29
+ /**
30
+ * Load all thoughts for a specific branch.
31
+ *
32
+ * @param branchId - The unique identifier for the branch
33
+ * @returns Array of thoughts in branch, or undefined if branch doesn't exist
34
+ */
35
+ loadBranch(branchId: string): Promise<ThoughtData[] | undefined>;
36
+ /**
37
+ * List all branch IDs that are persisted.
38
+ *
39
+ * @returns Array of branch identifiers
40
+ */
41
+ listBranches(): Promise<string[]>;
42
+ /**
43
+ * Check if backend is healthy.
44
+ * @returns Promise that resolves to true if healthy, false otherwise
45
+ */
46
+ healthy(): Promise<boolean>;
47
+ /**
48
+ * Clear all persisted data (history and branches).
49
+ * Use with caution - this cannot be undone.
50
+ */
51
+ clear(): Promise<void>;
52
+ /**
53
+ * Close the backend and release resources.
54
+ * Should be called during graceful shutdown to ensure data is flushed.
55
+ */
56
+ close(): Promise<void>;
57
+ }
58
+ export interface PersistenceConfig {
59
+ enabled?: boolean;
60
+ backend?: 'file' | 'sqlite' | 'memory';
61
+ options?: {
62
+ dataDir?: string;
63
+ dbPath?: string;
64
+ enableWAL?: boolean;
65
+ maxHistorySize?: number;
66
+ persistBranches?: boolean;
67
+ };
68
+ }
69
+ //# sourceMappingURL=PersistenceBackend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PersistenceBackend.d.ts","sourceRoot":"","sources":["../../src/persistence/PersistenceBackend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjD;;;;;OAKG;IACH,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErE;;;;;OAKG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC,CAAC;IAEjE;;;;OAIG;IACH,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAElC;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5B;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvC,OAAO,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,OAAO,CAAC;KAC1B,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,21 @@
1
+ import type { PersistenceBackend, PersistenceConfig } from './PersistenceBackend.js';
2
+ /**
3
+ * Create a persistence backend based on the provided configuration.
4
+ *
5
+ * Uses dynamic imports to avoid loading unused backends,
6
+ * keeping the factory decoupled from concrete implementations.
7
+ *
8
+ * @param config - Persistence configuration
9
+ * @returns A configured persistence backend, or null if disabled
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const backend = createPersistenceBackend({
14
+ * enabled: true,
15
+ * backend: 'file',
16
+ * options: { dataDir: './data' }
17
+ * });
18
+ * ```
19
+ */
20
+ export declare function createPersistenceBackend(config: PersistenceConfig): Promise<PersistenceBackend | null>;
21
+ //# sourceMappingURL=PersistenceFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PersistenceFactory.d.ts","sourceRoot":"","sources":["../../src/persistence/PersistenceFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAErF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,wBAAwB,CAC7C,MAAM,EAAE,iBAAiB,GACvB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAwBpC"}
@@ -0,0 +1,25 @@
1
+ async function createPersistenceBackend(config) {
2
+ if (!config.enabled) return null;
3
+ switch(config.backend){
4
+ case 'file':
5
+ {
6
+ const { FilePersistence } = await import("./FilePersistence.js");
7
+ return new FilePersistence(config.options);
8
+ }
9
+ case 'sqlite':
10
+ {
11
+ const { SqlitePersistence } = await import("./SqlitePersistence.js");
12
+ return await SqlitePersistence.create(config.options);
13
+ }
14
+ case 'memory':
15
+ {
16
+ const { MemoryPersistence } = await import("./MemoryPersistence.js");
17
+ return new MemoryPersistence();
18
+ }
19
+ default:
20
+ throw new Error(`Unknown persistence backend: ${config.backend}`);
21
+ }
22
+ }
23
+ export { createPersistenceBackend };
24
+
25
+ //# sourceMappingURL=PersistenceFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence/PersistenceFactory.js","sources":["../../src/persistence/PersistenceFactory.ts"],"sourcesContent":["import type { PersistenceBackend, PersistenceConfig } from './PersistenceBackend.js';\n\n/**\n * Create a persistence backend based on the provided configuration.\n *\n * Uses dynamic imports to avoid loading unused backends,\n * keeping the factory decoupled from concrete implementations.\n *\n * @param config - Persistence configuration\n * @returns A configured persistence backend, or null if disabled\n *\n * @example\n * ```typescript\n * const backend = createPersistenceBackend({\n * enabled: true,\n * backend: 'file',\n * options: { dataDir: './data' }\n * });\n * ```\n */\nexport async function createPersistenceBackend(\n\tconfig: PersistenceConfig\n): Promise<PersistenceBackend | null> {\n\tif (!config.enabled) {\n\t\treturn null;\n\t}\n\n\tswitch (config.backend) {\n\t\tcase 'file': {\n\t\t\tconst { FilePersistence } = await import('./FilePersistence.js');\n\t\t\treturn new FilePersistence(config.options);\n\t\t}\n\n\t\tcase 'sqlite': {\n\t\t\tconst { SqlitePersistence } = await import('./SqlitePersistence.js');\n\t\t\treturn await SqlitePersistence.create(config.options);\n\t\t}\n\n\t\tcase 'memory': {\n\t\t\tconst { MemoryPersistence } = await import('./MemoryPersistence.js');\n\t\t\treturn new MemoryPersistence();\n\t\t}\n\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown persistence backend: ${config.backend}`);\n\t}\n}\n"],"names":["createPersistenceBackend","config","FilePersistence","SqlitePersistence","MemoryPersistence","Error"],"mappings":"AAoBO,eAAeA,yBACrBC,MAAyB;IAEzB,IAAI,CAACA,OAAO,OAAO,EAClB,OAAO;IAGR,OAAQA,OAAO,OAAO;QACrB,KAAK;YAAQ;gBACZ,MAAM,EAAEC,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC;gBACzC,OAAO,IAAIA,gBAAgBD,OAAO,OAAO;YAC1C;QAEA,KAAK;YAAU;gBACd,MAAM,EAAEE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC;gBAC3C,OAAO,MAAMA,kBAAkB,MAAM,CAACF,OAAO,OAAO;YACrD;QAEA,KAAK;YAAU;gBACd,MAAM,EAAEG,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC;gBAC3C,OAAO,IAAIA;YACZ;QAEA;YACC,MAAM,IAAIC,MAAM,CAAC,6BAA6B,EAAEJ,OAAO,OAAO,EAAE;IAClE;AACD"}
@@ -0,0 +1,60 @@
1
+ import type { ThoughtData } from '../core/thought.js';
2
+ import type { PersistenceBackend, PersistenceConfig } from './PersistenceBackend.js';
3
+ /**
4
+ * SQLite-based persistence backend.
5
+ *
6
+ * Provides efficient, transactional persistence using SQLite.
7
+ * Requires the 'better-sqlite3' package to be installed.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const backend = await SqlitePersistence.create({
12
+ * dbPath: './data/history.db',
13
+ * enableWAL: true
14
+ * });
15
+ * ```
16
+ */
17
+ export declare class SqlitePersistence implements PersistenceBackend {
18
+ private _db;
19
+ private _maxHistorySize;
20
+ private _persistBranches;
21
+ private constructor();
22
+ /**
23
+ * Creates a new SqlitePersistence instance with dynamic import of better-sqlite3.
24
+ *
25
+ * @param options - Configuration options
26
+ * @returns A Promise that resolves to a SqlitePersistence instance
27
+ * @throws Error if better-sqlite3 is not installed
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const backend = await SqlitePersistence.create({
32
+ * dbPath: './data/history.db',
33
+ * enableWAL: true
34
+ * });
35
+ * ```
36
+ */
37
+ static create(options?: PersistenceConfig['options']): Promise<SqlitePersistence>;
38
+ private _initializeSchema;
39
+ saveThought(thought: ThoughtData): Promise<void>;
40
+ loadHistory(): Promise<ThoughtData[]>;
41
+ saveBranch(branchId: string, thoughts: ThoughtData[]): Promise<void>;
42
+ loadBranch(branchId: string): Promise<ThoughtData[] | undefined>;
43
+ listBranches(): Promise<string[]>;
44
+ clear(): Promise<void>;
45
+ healthy(): Promise<boolean>;
46
+ /**
47
+ * Close the database connection with proper cleanup.
48
+ * Runs WAL checkpoint before closing to ensure all data is persisted.
49
+ */
50
+ close(): Promise<void>;
51
+ /**
52
+ * Get statistics about the persisted data.
53
+ */
54
+ getStats(): {
55
+ thoughtCount: number;
56
+ branchCount: number;
57
+ dbSize: number;
58
+ };
59
+ }
60
+ //# sourceMappingURL=SqlitePersistence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SqlitePersistence.d.ts","sourceRoot":"","sources":["../../src/persistence/SqlitePersistence.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAwBrF;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAkB,YAAW,kBAAkB;IAC3D,OAAO,CAAC,GAAG,CAAW;IACtB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAAU;IAElC,OAAO;IAOP;;;;;;;;;;;;;;OAcG;WACU,MAAM,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAmCvF,OAAO,CAAC,iBAAiB;IA2BZ,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBhD,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAerC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,SAAS,CAAC;IAoBhE,YAAY,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAUjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAUxC;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYnC;;OAEG;IACI,QAAQ,IAAI;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;KACf;CAiBD"}
@@ -0,0 +1,136 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ class SqlitePersistence {
5
+ _db;
6
+ _maxHistorySize;
7
+ _persistBranches;
8
+ constructor(db, options){
9
+ this._db = db;
10
+ this._maxHistorySize = options?.maxHistorySize ?? 10000;
11
+ this._persistBranches = options?.persistBranches ?? true;
12
+ this._initializeSchema();
13
+ }
14
+ static async create(options) {
15
+ const defaultDataDir = existsSync('.claude/data') ? '.claude/data' : join(homedir(), '.claude/data');
16
+ const dbPath = options?.dbPath ?? join(defaultDataDir, 'history.db');
17
+ let Database;
18
+ try {
19
+ const module = await import("better-sqlite3");
20
+ Database = module.default;
21
+ } catch {
22
+ throw new Error("SQLite persistence requires 'better-sqlite3' package. Install it with: npm install better-sqlite3");
23
+ }
24
+ const db = new Database(dbPath);
25
+ if (options?.enableWAL !== false) db.pragma('journal_mode = WAL');
26
+ db.pragma('synchronous = NORMAL');
27
+ db.pragma('foreign_keys = ON');
28
+ db.pragma('busy_timeout = 5000');
29
+ db.pragma('cache_size = -64000');
30
+ db.pragma('temp_store = MEMORY');
31
+ return new SqlitePersistence(db, options);
32
+ }
33
+ _initializeSchema() {
34
+ this._db.exec(`
35
+ CREATE TABLE IF NOT EXISTS thoughts (
36
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37
+ data TEXT NOT NULL,
38
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
39
+ )
40
+ `);
41
+ if (this._persistBranches) this._db.exec(`
42
+ CREATE TABLE IF NOT EXISTS branches (
43
+ branch_id TEXT PRIMARY KEY,
44
+ data TEXT NOT NULL,
45
+ updated_at INTEGER DEFAULT (strftime('%s', 'now'))
46
+ )
47
+ `);
48
+ this._db.exec(`
49
+ CREATE INDEX IF NOT EXISTS idx_thoughts_created_at ON thoughts(created_at)
50
+ `);
51
+ }
52
+ async saveThought(thought) {
53
+ const stmt = this._db.prepare('INSERT INTO thoughts (data) VALUES (?)');
54
+ stmt.run(JSON.stringify(thought));
55
+ const countStmt = this._db.prepare('SELECT COUNT(*) as count FROM thoughts');
56
+ const { count } = countStmt.get();
57
+ if (count > this._maxHistorySize) {
58
+ const deleteStmt = this._db.prepare(`DELETE FROM thoughts WHERE id IN (
59
+ SELECT id FROM thoughts ORDER BY id ASC LIMIT ?
60
+ )`);
61
+ deleteStmt.run(count - this._maxHistorySize);
62
+ }
63
+ }
64
+ async loadHistory() {
65
+ const stmt = this._db.prepare('SELECT data FROM thoughts ORDER BY id ASC');
66
+ const rows = stmt.all();
67
+ return rows.map((row)=>{
68
+ try {
69
+ return JSON.parse(row.data);
70
+ } catch {
71
+ return null;
72
+ }
73
+ }).filter((t)=>null !== t);
74
+ }
75
+ async saveBranch(branchId, thoughts) {
76
+ if (!this._persistBranches) return;
77
+ const stmt = this._db.prepare('INSERT OR REPLACE INTO branches (branch_id, data, updated_at) VALUES (?, ?, strftime("%s", "now"))');
78
+ stmt.run(branchId, JSON.stringify(thoughts));
79
+ }
80
+ async loadBranch(branchId) {
81
+ if (!this._persistBranches) return;
82
+ const stmt = this._db.prepare('SELECT data FROM branches WHERE branch_id = ?');
83
+ const row = stmt.get(branchId);
84
+ if (!row) return;
85
+ try {
86
+ const data = JSON.parse(row.data);
87
+ return Array.isArray(data) ? data : void 0;
88
+ } catch {
89
+ return;
90
+ }
91
+ }
92
+ async listBranches() {
93
+ if (!this._persistBranches) return [];
94
+ const stmt = this._db.prepare('SELECT branch_id FROM branches ORDER BY branch_id ASC');
95
+ const rows = stmt.all();
96
+ return rows.map((row)=>row.branch_id);
97
+ }
98
+ async clear() {
99
+ this._db.exec('DELETE FROM thoughts');
100
+ if (this._persistBranches) this._db.exec('DELETE FROM branches');
101
+ }
102
+ async healthy() {
103
+ try {
104
+ this._db.prepare('SELECT 1').get();
105
+ return true;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+ async close() {
111
+ if (this._db) {
112
+ try {
113
+ this._db.pragma('wal_checkpoint(TRUNCATE)');
114
+ } catch {}
115
+ this._db.close();
116
+ }
117
+ }
118
+ getStats() {
119
+ const thoughtStmt = this._db.prepare('SELECT COUNT(*) as count FROM thoughts');
120
+ const { count: thoughtCount } = thoughtStmt.get();
121
+ let branchCount = 0;
122
+ if (this._persistBranches) {
123
+ const branchStmt = this._db.prepare('SELECT COUNT(*) as count FROM branches');
124
+ const result = branchStmt.get();
125
+ branchCount = result?.count ?? 0;
126
+ }
127
+ return {
128
+ thoughtCount,
129
+ branchCount,
130
+ dbSize: 0
131
+ };
132
+ }
133
+ }
134
+ export { SqlitePersistence };
135
+
136
+ //# sourceMappingURL=SqlitePersistence.js.map