@stackmemoryai/stackmemory 0.3.17 → 0.3.18

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 (212) hide show
  1. package/dist/cli/commands/skills.js +15 -2
  2. package/dist/cli/commands/skills.js.map +2 -2
  3. package/dist/cli/index.js +113 -834
  4. package/dist/cli/index.js.map +3 -3
  5. package/dist/core/context/dual-stack-manager.js +1 -1
  6. package/dist/core/context/dual-stack-manager.js.map +1 -1
  7. package/dist/core/context/frame-manager.js +3 -0
  8. package/dist/core/context/frame-manager.js.map +2 -2
  9. package/dist/integrations/claude-code/subagent-client.js +106 -3
  10. package/dist/integrations/claude-code/subagent-client.js.map +2 -2
  11. package/dist/servers/railway/config.js +51 -0
  12. package/dist/servers/railway/config.js.map +7 -0
  13. package/dist/servers/railway/index-enhanced.js +156 -0
  14. package/dist/servers/railway/index-enhanced.js.map +7 -0
  15. package/dist/servers/railway/minimal.js +48 -3
  16. package/dist/servers/railway/minimal.js.map +2 -2
  17. package/dist/servers/railway/storage-test.js +455 -0
  18. package/dist/servers/railway/storage-test.js.map +7 -0
  19. package/dist/skills/claude-skills.js +13 -12
  20. package/dist/skills/claude-skills.js.map +2 -2
  21. package/dist/skills/recursive-agent-orchestrator.js +27 -18
  22. package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
  23. package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
  24. package/package.json +6 -18
  25. package/scripts/README-TESTING.md +186 -0
  26. package/scripts/analyze-cli-security.js +288 -0
  27. package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
  28. package/scripts/archive/analyze-linear-duplicates.js +214 -0
  29. package/scripts/archive/analyze-remaining-duplicates.js +230 -0
  30. package/scripts/archive/analyze-sta-duplicates.js +292 -0
  31. package/scripts/archive/analyze-sta-graphql.js +399 -0
  32. package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
  33. package/scripts/archive/check-all-duplicates.ts +419 -0
  34. package/scripts/archive/clean-duplicate-tasks.js +114 -0
  35. package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
  36. package/scripts/archive/create-phase-tasks.js +387 -0
  37. package/scripts/archive/delete-linear-duplicates.js +182 -0
  38. package/scripts/archive/delete-remaining-duplicates.js +158 -0
  39. package/scripts/archive/delete-sta-duplicates.js +201 -0
  40. package/scripts/archive/delete-sta-oauth.js +201 -0
  41. package/scripts/archive/export-sta-tasks.js +62 -0
  42. package/scripts/archive/install-auto-sync.js +266 -0
  43. package/scripts/archive/install-chromadb-hooks.sh +133 -0
  44. package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
  45. package/scripts/archive/install-post-task-hooks.sh +289 -0
  46. package/scripts/archive/install-stackmemory-hooks.sh +420 -0
  47. package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
  48. package/scripts/archive/merge-linear-duplicates.ts +180 -0
  49. package/scripts/archive/remove-sta-tasks.js +70 -0
  50. package/scripts/archive/setup-background-sync.sh +168 -0
  51. package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
  52. package/scripts/archive/setup-claude-autostart.sh +305 -0
  53. package/scripts/archive/setup-git-hooks.sh +25 -0
  54. package/scripts/archive/setup-linear-oauth.sh +46 -0
  55. package/scripts/archive/setup-mcp.sh +113 -0
  56. package/scripts/archive/setup-railway-deployment.sh +81 -0
  57. package/scripts/auto-handoff.sh +262 -0
  58. package/scripts/background-sync-manager.js +416 -0
  59. package/scripts/benchmark-performance.ts +57 -0
  60. package/scripts/check-redis.ts +48 -0
  61. package/scripts/chromadb-auto-loader.sh +128 -0
  62. package/scripts/chromadb-context-loader.js +479 -0
  63. package/scripts/claude-chromadb-hook.js +460 -0
  64. package/scripts/claude-code-wrapper.sh +66 -0
  65. package/scripts/claude-linear-skill.js +455 -0
  66. package/scripts/claude-pre-commit.sh +302 -0
  67. package/scripts/claude-sm-autostart.js +532 -0
  68. package/scripts/claude-sm-setup.sh +367 -0
  69. package/scripts/claude-with-chromadb.sh +69 -0
  70. package/scripts/claude-worktree-manager.sh +323 -0
  71. package/scripts/claude-worktree-monitor.sh +371 -0
  72. package/scripts/claude-worktree-setup.sh +327 -0
  73. package/scripts/clean-linear-backlog.js +273 -0
  74. package/scripts/cleanup-old-sessions.sh +57 -0
  75. package/scripts/codex-wrapper.sh +88 -0
  76. package/scripts/create-sandbox.sh +269 -0
  77. package/scripts/debug-linear-update.js +174 -0
  78. package/scripts/delete-linear-tasks.js +167 -0
  79. package/scripts/deploy.sh +89 -0
  80. package/scripts/deployment/railway.sh +352 -0
  81. package/scripts/deployment/test-deployment.js +194 -0
  82. package/scripts/detect-and-rehydrate.js +162 -0
  83. package/scripts/detect-and-rehydrate.mjs +165 -0
  84. package/scripts/development/create-demo-tasks.js +143 -0
  85. package/scripts/development/debug-frame-test.js +16 -0
  86. package/scripts/development/demo-auto-sync.js +128 -0
  87. package/scripts/development/fix-all-imports.js +213 -0
  88. package/scripts/development/fix-imports.js +229 -0
  89. package/scripts/development/fix-lint-loop.cjs +103 -0
  90. package/scripts/development/fix-project-id.ts +161 -0
  91. package/scripts/development/fix-strict-mode-issues.ts +291 -0
  92. package/scripts/development/reorganize-structure.sh +228 -0
  93. package/scripts/development/test-persistence-direct.js +148 -0
  94. package/scripts/development/test-persistence.js +114 -0
  95. package/scripts/development/test-tasks.js +93 -0
  96. package/scripts/development/update-imports.js +212 -0
  97. package/scripts/fetch-linear-status.js +125 -0
  98. package/scripts/git-hooks/README.md +310 -0
  99. package/scripts/git-hooks/branch-context-manager.sh +342 -0
  100. package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
  101. package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
  102. package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
  103. package/scripts/hooks/cleanup-shell.sh +130 -0
  104. package/scripts/hooks/task-complete.sh +114 -0
  105. package/scripts/initialize.ts +129 -0
  106. package/scripts/install-claude-hooks-auto.js +104 -0
  107. package/scripts/install-claude-hooks.sh +133 -0
  108. package/scripts/install-global.sh +296 -0
  109. package/scripts/install.sh +235 -0
  110. package/scripts/linear-auto-sync.js +262 -0
  111. package/scripts/linear-auto-sync.sh +161 -0
  112. package/scripts/linear-sync-daemon.js +150 -0
  113. package/scripts/linear-task-review.js +237 -0
  114. package/scripts/list-linear-tasks.ts +178 -0
  115. package/scripts/mcp-proxy.js +66 -0
  116. package/scripts/opencode-wrapper.sh +85 -0
  117. package/scripts/publish-local.js +74 -0
  118. package/scripts/query-chromadb.ts +201 -0
  119. package/scripts/railway-env-setup.sh +39 -0
  120. package/scripts/reconcile-local-tasks.js +170 -0
  121. package/scripts/recreate-frames-db.js +89 -0
  122. package/scripts/setup/claude-integration.js +138 -0
  123. package/scripts/setup/configure-alias.js +125 -0
  124. package/scripts/setup/configure-codex-alias.js +161 -0
  125. package/scripts/setup/configure-opencode-alias.js +175 -0
  126. package/scripts/setup-claude-integration.js +204 -0
  127. package/scripts/setup-claude-integration.sh +183 -0
  128. package/scripts/setup.sh +31 -0
  129. package/scripts/show-linear-summary.ts +172 -0
  130. package/scripts/stackmemory-auto-handoff.sh +231 -0
  131. package/scripts/stackmemory-daemon.sh +40 -0
  132. package/scripts/start-linear-sync-daemon.sh +141 -0
  133. package/scripts/start-temporal-paradox.sh +214 -0
  134. package/scripts/status.ts +159 -0
  135. package/scripts/sync-and-clean-tasks.js +258 -0
  136. package/scripts/sync-frames-from-railway.js +228 -0
  137. package/scripts/sync-linear-graphql.js +303 -0
  138. package/scripts/sync-linear-tasks.js +186 -0
  139. package/scripts/test-auto-triggers.sh +57 -0
  140. package/scripts/test-browser-mcp.js +74 -0
  141. package/scripts/test-chromadb-full.js +115 -0
  142. package/scripts/test-chromadb-hooks.sh +28 -0
  143. package/scripts/test-chromadb-sync.ts +245 -0
  144. package/scripts/test-cli-security.js +293 -0
  145. package/scripts/test-hooks-persistence.sh +220 -0
  146. package/scripts/test-installation-scenarios.sh +359 -0
  147. package/scripts/test-installation.sh +224 -0
  148. package/scripts/test-mcp.js +163 -0
  149. package/scripts/test-pre-publish-quick.sh +75 -0
  150. package/scripts/test-quality-gates.sh +263 -0
  151. package/scripts/test-railway-db.js +222 -0
  152. package/scripts/test-redis-storage.ts +490 -0
  153. package/scripts/test-rlm-basic.sh +122 -0
  154. package/scripts/test-rlm-comprehensive.sh +260 -0
  155. package/scripts/test-rlm-e2e.sh +268 -0
  156. package/scripts/test-rlm-simple.js +90 -0
  157. package/scripts/test-rlm.js +110 -0
  158. package/scripts/test-session-handoff.sh +165 -0
  159. package/scripts/test-shell-integration.sh +275 -0
  160. package/scripts/testing/ab-test-runner.ts +508 -0
  161. package/scripts/testing/collect-metrics.ts +457 -0
  162. package/scripts/testing/quick-effectiveness-demo.js +187 -0
  163. package/scripts/testing/real-performance-test.js +422 -0
  164. package/scripts/testing/run-effectiveness-tests.sh +176 -0
  165. package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
  166. package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
  167. package/scripts/testing/simple-effectiveness-test.js +310 -0
  168. package/scripts/testing/src/core/context/context-bridge.js +253 -0
  169. package/scripts/testing/src/core/context/frame-manager.js +746 -0
  170. package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
  171. package/scripts/testing/src/core/database/database-adapter.js +54 -0
  172. package/scripts/testing/src/core/errors/index.js +291 -0
  173. package/scripts/testing/src/core/errors/recovery.js +268 -0
  174. package/scripts/testing/src/core/monitoring/logger.js +145 -0
  175. package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
  176. package/scripts/testing/src/core/session/index.js +1 -0
  177. package/scripts/testing/src/core/session/session-manager.js +323 -0
  178. package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
  179. package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
  180. package/scripts/testing/src/core/trace/debug-trace.js +398 -0
  181. package/scripts/testing/src/core/trace/index.js +120 -0
  182. package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
  183. package/scripts/update-linear-status.js +268 -0
  184. package/scripts/update-linear-tasks-fixed.js +284 -0
  185. package/templates/claude-hooks/hooks.json +5 -0
  186. package/templates/claude-hooks/on-clear.js +56 -0
  187. package/templates/claude-hooks/on-startup.js +56 -0
  188. package/templates/claude-hooks/tool-use-trace.js +67 -0
  189. package/dist/features/tui/components/analytics-panel.js +0 -157
  190. package/dist/features/tui/components/analytics-panel.js.map +0 -7
  191. package/dist/features/tui/components/frame-visualizer.js +0 -377
  192. package/dist/features/tui/components/frame-visualizer.js.map +0 -7
  193. package/dist/features/tui/components/pr-tracker.js +0 -135
  194. package/dist/features/tui/components/pr-tracker.js.map +0 -7
  195. package/dist/features/tui/components/session-monitor.js +0 -299
  196. package/dist/features/tui/components/session-monitor.js.map +0 -7
  197. package/dist/features/tui/components/subagent-fleet.js +0 -395
  198. package/dist/features/tui/components/subagent-fleet.js.map +0 -7
  199. package/dist/features/tui/components/task-board.js +0 -1139
  200. package/dist/features/tui/components/task-board.js.map +0 -7
  201. package/dist/features/tui/index.js +0 -408
  202. package/dist/features/tui/index.js.map +0 -7
  203. package/dist/features/tui/services/data-service.js +0 -641
  204. package/dist/features/tui/services/data-service.js.map +0 -7
  205. package/dist/features/tui/services/linear-task-reader.js +0 -102
  206. package/dist/features/tui/services/linear-task-reader.js.map +0 -7
  207. package/dist/features/tui/services/websocket-client.js +0 -162
  208. package/dist/features/tui/services/websocket-client.js.map +0 -7
  209. package/dist/features/tui/terminal-compat.js +0 -220
  210. package/dist/features/tui/terminal-compat.js.map +0 -7
  211. package/dist/features/tui/types.js +0 -1
  212. package/dist/features/tui/types.js.map +0 -7
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Custom error classes for StackMemory
3
+ * Provides a hierarchy of error types for better error handling and debugging
4
+ */
5
+ export var ErrorCode;
6
+ (function (ErrorCode) {
7
+ // Database errors (1000-1999)
8
+ ErrorCode["DB_CONNECTION_FAILED"] = "DB_001";
9
+ ErrorCode["DB_QUERY_FAILED"] = "DB_002";
10
+ ErrorCode["DB_TRANSACTION_FAILED"] = "DB_003";
11
+ ErrorCode["DB_MIGRATION_FAILED"] = "DB_004";
12
+ ErrorCode["DB_CONSTRAINT_VIOLATION"] = "DB_005";
13
+ ErrorCode["DB_SCHEMA_ERROR"] = "DB_006";
14
+ ErrorCode["DB_INSERT_FAILED"] = "DB_007";
15
+ ErrorCode["DB_UPDATE_FAILED"] = "DB_008";
16
+ ErrorCode["DB_DELETE_FAILED"] = "DB_009";
17
+ // Frame errors (2000-2999)
18
+ ErrorCode["FRAME_NOT_FOUND"] = "FRAME_001";
19
+ ErrorCode["FRAME_INVALID_STATE"] = "FRAME_002";
20
+ ErrorCode["FRAME_PARENT_NOT_FOUND"] = "FRAME_003";
21
+ ErrorCode["FRAME_CYCLE_DETECTED"] = "FRAME_004";
22
+ ErrorCode["FRAME_ALREADY_CLOSED"] = "FRAME_005";
23
+ ErrorCode["FRAME_INIT_FAILED"] = "FRAME_006";
24
+ ErrorCode["FRAME_INVALID_INPUT"] = "FRAME_007";
25
+ ErrorCode["FRAME_STACK_OVERFLOW"] = "FRAME_008";
26
+ // Task errors (3000-3999)
27
+ ErrorCode["TASK_NOT_FOUND"] = "TASK_001";
28
+ ErrorCode["TASK_INVALID_STATE"] = "TASK_002";
29
+ ErrorCode["TASK_DEPENDENCY_CONFLICT"] = "TASK_003";
30
+ ErrorCode["TASK_CIRCULAR_DEPENDENCY"] = "TASK_004";
31
+ // Integration errors (4000-4999)
32
+ ErrorCode["LINEAR_AUTH_FAILED"] = "LINEAR_001";
33
+ ErrorCode["LINEAR_API_ERROR"] = "LINEAR_002";
34
+ ErrorCode["LINEAR_SYNC_FAILED"] = "LINEAR_003";
35
+ ErrorCode["LINEAR_WEBHOOK_FAILED"] = "LINEAR_004";
36
+ // MCP errors (5000-5999)
37
+ ErrorCode["MCP_TOOL_NOT_FOUND"] = "MCP_001";
38
+ ErrorCode["MCP_INVALID_PARAMS"] = "MCP_002";
39
+ ErrorCode["MCP_EXECUTION_FAILED"] = "MCP_003";
40
+ ErrorCode["MCP_RATE_LIMITED"] = "MCP_004";
41
+ // Project errors (6000-6999)
42
+ ErrorCode["PROJECT_NOT_FOUND"] = "PROJECT_001";
43
+ ErrorCode["PROJECT_INVALID_PATH"] = "PROJECT_002";
44
+ ErrorCode["PROJECT_GIT_ERROR"] = "PROJECT_003";
45
+ // Validation errors (7000-7999)
46
+ ErrorCode["VALIDATION_FAILED"] = "VAL_001";
47
+ ErrorCode["INVALID_INPUT"] = "VAL_002";
48
+ ErrorCode["MISSING_REQUIRED_FIELD"] = "VAL_003";
49
+ ErrorCode["TYPE_MISMATCH"] = "VAL_004";
50
+ // System errors (8000-8999)
51
+ ErrorCode["INITIALIZATION_ERROR"] = "SYS_001";
52
+ ErrorCode["NOT_FOUND"] = "SYS_002";
53
+ ErrorCode["INTERNAL_ERROR"] = "SYS_003";
54
+ ErrorCode["CONFIGURATION_ERROR"] = "SYS_004";
55
+ ErrorCode["PERMISSION_DENIED"] = "SYS_005";
56
+ ErrorCode["RESOURCE_EXHAUSTED"] = "SYS_006";
57
+ ErrorCode["SERVICE_UNAVAILABLE"] = "SYS_007";
58
+ ErrorCode["SYSTEM_INIT_FAILED"] = "SYS_008";
59
+ // Collaboration errors (9000-9999)
60
+ ErrorCode["STACK_CONTEXT_NOT_FOUND"] = "COLLAB_001";
61
+ ErrorCode["HANDOFF_REQUEST_EXPIRED"] = "COLLAB_002";
62
+ ErrorCode["MERGE_CONFLICT_UNRESOLVABLE"] = "COLLAB_003";
63
+ ErrorCode["PERMISSION_VIOLATION"] = "COLLAB_004";
64
+ ErrorCode["OPERATION_FAILED"] = "COLLAB_005";
65
+ ErrorCode["OPERATION_EXPIRED"] = "COLLAB_006";
66
+ ErrorCode["INVALID_STATE"] = "COLLAB_007";
67
+ ErrorCode["RESOURCE_NOT_FOUND"] = "COLLAB_008";
68
+ ErrorCode["HANDOFF_ALREADY_EXISTS"] = "COLLAB_009";
69
+ ErrorCode["MERGE_SESSION_INVALID"] = "COLLAB_010";
70
+ ErrorCode["STACK_SWITCH_FAILED"] = "COLLAB_011";
71
+ ErrorCode["APPROVAL_TIMEOUT"] = "COLLAB_012";
72
+ ErrorCode["CONFLICT_RESOLUTION_FAILED"] = "COLLAB_013";
73
+ ErrorCode["TEAM_ACCESS_DENIED"] = "COLLAB_014";
74
+ ErrorCode["STACK_LIMIT_EXCEEDED"] = "COLLAB_015";
75
+ })(ErrorCode || (ErrorCode = {}));
76
+ /**
77
+ * Base error class for all StackMemory errors
78
+ */
79
+ export class StackMemoryError extends Error {
80
+ constructor(options) {
81
+ super(options.message);
82
+ this.name = this.constructor.name;
83
+ this.code = options.code;
84
+ this.context = options.context;
85
+ this.cause = options.cause;
86
+ this.isRetryable = options.isRetryable ?? false;
87
+ this.httpStatus = options.httpStatus ?? 500;
88
+ this.timestamp = new Date();
89
+ // Maintains proper stack trace for where our error was thrown
90
+ if (Error.captureStackTrace) {
91
+ Error.captureStackTrace(this, this.constructor);
92
+ }
93
+ }
94
+ toJSON() {
95
+ return {
96
+ name: this.name,
97
+ code: this.code,
98
+ message: this.message,
99
+ context: this.context,
100
+ isRetryable: this.isRetryable,
101
+ httpStatus: this.httpStatus,
102
+ timestamp: this.timestamp.toISOString(),
103
+ stack: this.stack,
104
+ cause: this.cause?.message,
105
+ };
106
+ }
107
+ }
108
+ /**
109
+ * Database-related errors
110
+ */
111
+ export class DatabaseError extends StackMemoryError {
112
+ constructor(message, code = ErrorCode.DB_QUERY_FAILED, context, cause) {
113
+ super({
114
+ code,
115
+ message,
116
+ context,
117
+ cause,
118
+ isRetryable: code === ErrorCode.DB_CONNECTION_FAILED,
119
+ httpStatus: 503,
120
+ });
121
+ }
122
+ }
123
+ /**
124
+ * Frame-related errors
125
+ */
126
+ export class FrameError extends StackMemoryError {
127
+ constructor(message, code = ErrorCode.FRAME_INVALID_STATE, context) {
128
+ super({
129
+ code,
130
+ message,
131
+ context,
132
+ isRetryable: false,
133
+ httpStatus: 400,
134
+ });
135
+ }
136
+ }
137
+ /**
138
+ * Task-related errors
139
+ */
140
+ export class TaskError extends StackMemoryError {
141
+ constructor(message, code = ErrorCode.TASK_INVALID_STATE, context) {
142
+ super({
143
+ code,
144
+ message,
145
+ context,
146
+ isRetryable: false,
147
+ httpStatus: 400,
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * Integration errors (Linear, etc.)
153
+ */
154
+ export class IntegrationError extends StackMemoryError {
155
+ constructor(message, code = ErrorCode.LINEAR_API_ERROR, context, cause) {
156
+ super({
157
+ code,
158
+ message,
159
+ context,
160
+ cause,
161
+ isRetryable: true,
162
+ httpStatus: 502,
163
+ });
164
+ }
165
+ }
166
+ /**
167
+ * MCP-related errors
168
+ */
169
+ export class MCPError extends StackMemoryError {
170
+ constructor(message, code = ErrorCode.MCP_EXECUTION_FAILED, context) {
171
+ super({
172
+ code,
173
+ message,
174
+ context,
175
+ isRetryable: code === ErrorCode.MCP_RATE_LIMITED,
176
+ httpStatus: code === ErrorCode.MCP_RATE_LIMITED ? 429 : 400,
177
+ });
178
+ }
179
+ }
180
+ /**
181
+ * Validation errors
182
+ */
183
+ export class ValidationError extends StackMemoryError {
184
+ constructor(message, code = ErrorCode.VALIDATION_FAILED, context) {
185
+ super({
186
+ code,
187
+ message,
188
+ context,
189
+ isRetryable: false,
190
+ httpStatus: 400,
191
+ });
192
+ }
193
+ }
194
+ /**
195
+ * Project-related errors
196
+ */
197
+ export class ProjectError extends StackMemoryError {
198
+ constructor(message, code = ErrorCode.PROJECT_NOT_FOUND, context) {
199
+ super({
200
+ code,
201
+ message,
202
+ context,
203
+ isRetryable: false,
204
+ httpStatus: 404,
205
+ });
206
+ }
207
+ }
208
+ /**
209
+ * System/Internal errors
210
+ */
211
+ export class SystemError extends StackMemoryError {
212
+ constructor(message, code = ErrorCode.INTERNAL_ERROR, context, cause) {
213
+ super({
214
+ code,
215
+ message,
216
+ context,
217
+ cause,
218
+ isRetryable: code === ErrorCode.SERVICE_UNAVAILABLE,
219
+ httpStatus: 500,
220
+ });
221
+ }
222
+ }
223
+ /**
224
+ * Helper function to determine if an error is retryable
225
+ */
226
+ export function isRetryableError(error) {
227
+ if (error instanceof StackMemoryError) {
228
+ return error.isRetryable;
229
+ }
230
+ // Check for common retryable error patterns
231
+ if (error instanceof Error) {
232
+ const message = error.message.toLowerCase();
233
+ return (message.includes('econnrefused') ||
234
+ message.includes('timeout') ||
235
+ message.includes('enotfound') ||
236
+ message.includes('socket hang up'));
237
+ }
238
+ return false;
239
+ }
240
+ /**
241
+ * Helper function to safely extract error message
242
+ */
243
+ export function getErrorMessage(error) {
244
+ if (error instanceof Error) {
245
+ return error.message;
246
+ }
247
+ if (typeof error === 'string') {
248
+ return error;
249
+ }
250
+ if (error && typeof error === 'object' && 'message' in error) {
251
+ return String(error.message);
252
+ }
253
+ return 'An unknown error occurred';
254
+ }
255
+ /**
256
+ * Helper function to wrap unknown errors in StackMemoryError
257
+ */
258
+ export function wrapError(error, defaultMessage, code = ErrorCode.INTERNAL_ERROR, context) {
259
+ if (error instanceof StackMemoryError) {
260
+ return error;
261
+ }
262
+ const cause = error instanceof Error ? error : undefined;
263
+ const message = error instanceof Error ? error.message : defaultMessage;
264
+ return new SystemError(message, code, context, cause);
265
+ }
266
+ /**
267
+ * Type guard to check if error is a StackMemoryError
268
+ */
269
+ export function isStackMemoryError(error) {
270
+ return error instanceof StackMemoryError;
271
+ }
272
+ /**
273
+ * Create context-aware error handler
274
+ */
275
+ export function createErrorHandler(defaultContext) {
276
+ return (error, additionalContext) => {
277
+ const context = { ...defaultContext, ...additionalContext };
278
+ if (error instanceof StackMemoryError) {
279
+ // Create a new error with merged context since context is readonly
280
+ return new StackMemoryError({
281
+ code: error.code,
282
+ message: error.message,
283
+ context: { ...error.context, ...context },
284
+ cause: error.cause,
285
+ isRetryable: error.isRetryable,
286
+ httpStatus: error.httpStatus,
287
+ });
288
+ }
289
+ return wrapError(error, getErrorMessage(error), ErrorCode.INTERNAL_ERROR, context);
290
+ };
291
+ }
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Error recovery utilities for StackMemory
3
+ * Provides retry logic, circuit breakers, and fallback mechanisms
4
+ */
5
+ import { logger } from '../monitoring/logger.js';
6
+ import { isRetryableError, getErrorMessage, } from './index.js';
7
+ export var CircuitState;
8
+ (function (CircuitState) {
9
+ CircuitState["CLOSED"] = "closed";
10
+ CircuitState["OPEN"] = "open";
11
+ CircuitState["HALF_OPEN"] = "half_open";
12
+ })(CircuitState || (CircuitState = {}));
13
+ /**
14
+ * Exponential backoff with jitter
15
+ */
16
+ export function calculateBackoff(attempt, initialDelay, maxDelay, factor) {
17
+ const exponentialDelay = Math.min(initialDelay * Math.pow(factor, attempt - 1), maxDelay);
18
+ // Add jitter (0-25% of delay)
19
+ const jitter = exponentialDelay * Math.random() * 0.25;
20
+ return Math.floor(exponentialDelay + jitter);
21
+ }
22
+ /**
23
+ * Retry with exponential backoff
24
+ */
25
+ export async function retry(fn, options = {}) {
26
+ const { maxAttempts = 3, initialDelay = 1000, maxDelay = 30000, backoffFactor = 2, timeout, onRetry, } = options;
27
+ let lastError;
28
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
29
+ try {
30
+ // Add timeout if specified
31
+ if (timeout) {
32
+ return await Promise.race([
33
+ fn(),
34
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout)),
35
+ ]);
36
+ }
37
+ return await fn();
38
+ }
39
+ catch (error) {
40
+ lastError = error;
41
+ // Don't retry if not retryable or last attempt
42
+ if (!isRetryableError(error) || attempt === maxAttempts) {
43
+ throw error;
44
+ }
45
+ const delay = calculateBackoff(attempt, initialDelay, maxDelay, backoffFactor);
46
+ logger.warn(`Retry attempt ${attempt}/${maxAttempts} after ${delay}ms`, {
47
+ error: getErrorMessage(error),
48
+ attempt,
49
+ delay,
50
+ });
51
+ if (onRetry) {
52
+ onRetry(attempt, error);
53
+ }
54
+ await new Promise((resolve) => setTimeout(resolve, delay));
55
+ }
56
+ }
57
+ throw lastError;
58
+ }
59
+ /**
60
+ * Circuit breaker implementation
61
+ */
62
+ export class CircuitBreaker {
63
+ constructor(name, options = {}) {
64
+ this.name = name;
65
+ this.state = CircuitState.CLOSED;
66
+ this.failures = 0;
67
+ this.successCount = 0;
68
+ this.options = {
69
+ failureThreshold: options.failureThreshold ?? 5,
70
+ resetTimeout: options.resetTimeout ?? 60000,
71
+ halfOpenRequests: options.halfOpenRequests ?? 3,
72
+ };
73
+ }
74
+ async execute(fn) {
75
+ // Check if circuit should transition from OPEN to HALF_OPEN
76
+ if (this.state === CircuitState.OPEN) {
77
+ const timeSinceLastFailure = this.lastFailTime
78
+ ? Date.now() - this.lastFailTime.getTime()
79
+ : 0;
80
+ if (timeSinceLastFailure >= this.options.resetTimeout) {
81
+ this.state = CircuitState.HALF_OPEN;
82
+ this.successCount = 0;
83
+ logger.info(`Circuit breaker ${this.name} entering half-open state`);
84
+ }
85
+ else {
86
+ throw new Error(`Circuit breaker ${this.name} is OPEN. Retry after ${this.options.resetTimeout - timeSinceLastFailure}ms`);
87
+ }
88
+ }
89
+ try {
90
+ const result = await fn();
91
+ // Handle success
92
+ if (this.state === CircuitState.HALF_OPEN) {
93
+ this.successCount++;
94
+ if (this.successCount >= this.options.halfOpenRequests) {
95
+ this.state = CircuitState.CLOSED;
96
+ this.failures = 0;
97
+ logger.info(`Circuit breaker ${this.name} is now CLOSED`);
98
+ }
99
+ }
100
+ else {
101
+ this.failures = 0;
102
+ }
103
+ return result;
104
+ }
105
+ catch (error) {
106
+ this.handleFailure(error);
107
+ throw error;
108
+ }
109
+ }
110
+ handleFailure(error) {
111
+ this.failures++;
112
+ this.lastFailTime = new Date();
113
+ if (this.state === CircuitState.HALF_OPEN) {
114
+ this.state = CircuitState.OPEN;
115
+ logger.error(`Circuit breaker ${this.name} reopened due to failure in half-open state`);
116
+ }
117
+ else if (this.state === CircuitState.CLOSED &&
118
+ this.failures >= this.options.failureThreshold) {
119
+ this.state = CircuitState.OPEN;
120
+ logger.error(`Circuit breaker ${this.name} opened after ${this.failures} failures`);
121
+ }
122
+ }
123
+ getState() {
124
+ return this.state;
125
+ }
126
+ reset() {
127
+ this.state = CircuitState.CLOSED;
128
+ this.failures = 0;
129
+ this.successCount = 0;
130
+ this.lastFailTime = undefined;
131
+ logger.info(`Circuit breaker ${this.name} manually reset`);
132
+ }
133
+ }
134
+ /**
135
+ * Fallback with multiple strategies
136
+ */
137
+ export async function withFallback(primary, fallbacks, context) {
138
+ const errors = [];
139
+ // Try primary
140
+ try {
141
+ return await primary();
142
+ }
143
+ catch (error) {
144
+ errors.push(error);
145
+ logger.warn('Primary operation failed, trying fallbacks', {
146
+ error: getErrorMessage(error),
147
+ context,
148
+ });
149
+ }
150
+ // Try fallbacks in order
151
+ for (let i = 0; i < fallbacks.length; i++) {
152
+ try {
153
+ const result = await fallbacks[i]();
154
+ logger.info(`Fallback ${i + 1} succeeded`, { context });
155
+ return result;
156
+ }
157
+ catch (error) {
158
+ errors.push(error);
159
+ if (i < fallbacks.length - 1) {
160
+ logger.warn(`Fallback ${i + 1} failed, trying next`, {
161
+ error: getErrorMessage(error),
162
+ context,
163
+ });
164
+ }
165
+ }
166
+ }
167
+ // All attempts failed
168
+ throw new Error(`All attempts failed. Errors: ${errors.map(getErrorMessage).join(', ')}`);
169
+ }
170
+ /**
171
+ * Bulkhead pattern - limit concurrent operations
172
+ */
173
+ export class Bulkhead {
174
+ constructor(name, maxConcurrent) {
175
+ this.name = name;
176
+ this.maxConcurrent = maxConcurrent;
177
+ this.running = 0;
178
+ this.queue = [];
179
+ }
180
+ async execute(fn) {
181
+ if (this.running >= this.maxConcurrent) {
182
+ await new Promise((resolve) => {
183
+ this.queue.push(resolve);
184
+ });
185
+ }
186
+ this.running++;
187
+ try {
188
+ return await fn();
189
+ }
190
+ finally {
191
+ this.running--;
192
+ const next = this.queue.shift();
193
+ if (next) {
194
+ next();
195
+ }
196
+ }
197
+ }
198
+ getStats() {
199
+ return {
200
+ running: this.running,
201
+ queued: this.queue.length,
202
+ maxConcurrent: this.maxConcurrent,
203
+ };
204
+ }
205
+ }
206
+ /**
207
+ * Timeout wrapper with proper cleanup
208
+ */
209
+ export async function withTimeout(fn, timeoutMs, timeoutMessage) {
210
+ return Promise.race([
211
+ fn(),
212
+ new Promise((_, reject) => setTimeout(() => reject(new Error(timeoutMessage ?? `Operation timed out after ${timeoutMs}ms`)), timeoutMs)),
213
+ ]);
214
+ }
215
+ /**
216
+ * Graceful degradation helper
217
+ */
218
+ export async function gracefulDegrade(fn, defaultValue, logContext) {
219
+ try {
220
+ return await fn();
221
+ }
222
+ catch (error) {
223
+ logger.warn('Operation failed, using default value', {
224
+ error: getErrorMessage(error),
225
+ ...logContext,
226
+ });
227
+ return defaultValue;
228
+ }
229
+ }
230
+ /**
231
+ * Create a resilient operation with multiple recovery strategies
232
+ */
233
+ export function createResilientOperation(name, options = {}) {
234
+ const circuitBreaker = options.circuitBreaker
235
+ ? new CircuitBreaker(name, options.circuitBreaker)
236
+ : null;
237
+ const bulkhead = options.bulkhead
238
+ ? new Bulkhead(name, options.bulkhead)
239
+ : null;
240
+ return async (fn) => {
241
+ let currentFn = fn;
242
+ // Wrap with bulkhead if configured
243
+ if (bulkhead) {
244
+ const wrapped = currentFn;
245
+ currentFn = () => bulkhead.execute(wrapped);
246
+ }
247
+ // Wrap with timeout if configured
248
+ if (options.timeout) {
249
+ const wrapped = currentFn;
250
+ currentFn = () => withTimeout(wrapped, options.timeout);
251
+ }
252
+ // Wrap with retry if configured
253
+ if (options.retry) {
254
+ const wrapped = currentFn;
255
+ currentFn = () => retry(wrapped, options.retry);
256
+ }
257
+ // Wrap with circuit breaker if configured
258
+ if (circuitBreaker) {
259
+ const wrapped = currentFn;
260
+ currentFn = () => circuitBreaker.execute(wrapped);
261
+ }
262
+ // Execute with fallback if configured
263
+ if (options.fallback) {
264
+ return withFallback(currentFn, [options.fallback]);
265
+ }
266
+ return currentFn();
267
+ };
268
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Structured logging utility for StackMemory CLI
3
+ */
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ export var LogLevel;
7
+ (function (LogLevel) {
8
+ LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
9
+ LogLevel[LogLevel["WARN"] = 1] = "WARN";
10
+ LogLevel[LogLevel["INFO"] = 2] = "INFO";
11
+ LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
12
+ })(LogLevel || (LogLevel = {}));
13
+ export class Logger {
14
+ constructor() {
15
+ this.logLevel = LogLevel.INFO;
16
+ this.fileLoggingDisabledNotified = false;
17
+ // Set log level from environment
18
+ const envLevel = process.env.STACKMEMORY_LOG_LEVEL?.toUpperCase();
19
+ switch (envLevel) {
20
+ case 'ERROR':
21
+ this.logLevel = LogLevel.ERROR;
22
+ break;
23
+ case 'WARN':
24
+ this.logLevel = LogLevel.WARN;
25
+ break;
26
+ case 'DEBUG':
27
+ this.logLevel = LogLevel.DEBUG;
28
+ break;
29
+ default:
30
+ this.logLevel = LogLevel.INFO;
31
+ }
32
+ // Set up log file if in debug mode or if specified
33
+ if (this.logLevel === LogLevel.DEBUG || process.env.STACKMEMORY_LOG_FILE) {
34
+ this.logFile =
35
+ process.env.STACKMEMORY_LOG_FILE ||
36
+ path.join(process.env.HOME || '.', '.stackmemory', 'logs', 'cli.log');
37
+ this.ensureLogDirectory();
38
+ }
39
+ }
40
+ static getInstance() {
41
+ if (!Logger.instance) {
42
+ Logger.instance = new Logger();
43
+ }
44
+ return Logger.instance;
45
+ }
46
+ ensureLogDirectory() {
47
+ if (!this.logFile)
48
+ return;
49
+ const logDir = path.dirname(this.logFile);
50
+ try {
51
+ if (!fs.existsSync(logDir)) {
52
+ fs.mkdirSync(logDir, { recursive: true });
53
+ }
54
+ }
55
+ catch (err) {
56
+ // Disable file logging if we cannot create the directory (e.g., ENOSPC)
57
+ this.logFile = undefined;
58
+ if (!this.fileLoggingDisabledNotified) {
59
+ this.fileLoggingDisabledNotified = true;
60
+ // Emit a single warning to console so we don't spam output
61
+ const msg = '[Logger] File logging disabled (failed to create log directory). Falling back to console only.';
62
+ // Use console directly to avoid recursion
63
+ // eslint-disable-next-line no-console
64
+ console.warn(msg);
65
+ }
66
+ }
67
+ }
68
+ writeLog(entry) {
69
+ const logLine = JSON.stringify(entry) + '\n';
70
+ // Always write to file if configured
71
+ if (this.logFile) {
72
+ try {
73
+ fs.appendFileSync(this.logFile, logLine);
74
+ }
75
+ catch (err) {
76
+ // Disable file logging on error (e.g., ENOSPC) to avoid repeated failures
77
+ this.logFile = undefined;
78
+ if (!this.fileLoggingDisabledNotified) {
79
+ this.fileLoggingDisabledNotified = true;
80
+ const msg = '[Logger] File logging disabled (write failed). Falling back to console only.';
81
+ // eslint-disable-next-line no-console
82
+ console.warn(msg);
83
+ }
84
+ }
85
+ }
86
+ // Console output based on level
87
+ if (entry.level <= this.logLevel) {
88
+ const levelNames = ['ERROR', 'WARN', 'INFO', 'DEBUG'];
89
+ const levelName = levelNames[entry.level] || 'UNKNOWN';
90
+ const consoleMessage = `[${entry.timestamp}] ${levelName}: ${entry.message}`;
91
+ if (entry.level === LogLevel.ERROR) {
92
+ console.error(consoleMessage);
93
+ if (entry.error) {
94
+ console.error(entry.error.stack);
95
+ }
96
+ }
97
+ else if (entry.level === LogLevel.WARN) {
98
+ console.warn(consoleMessage);
99
+ }
100
+ else {
101
+ console.log(consoleMessage);
102
+ }
103
+ }
104
+ }
105
+ error(message, errorOrContext, context) {
106
+ const isError = errorOrContext instanceof Error;
107
+ this.writeLog({
108
+ timestamp: new Date().toISOString(),
109
+ level: LogLevel.ERROR,
110
+ message,
111
+ context: isError ? context : errorOrContext,
112
+ error: isError ? errorOrContext : undefined,
113
+ });
114
+ }
115
+ warn(message, errorOrContext) {
116
+ const isError = errorOrContext instanceof Error;
117
+ this.writeLog({
118
+ timestamp: new Date().toISOString(),
119
+ level: LogLevel.WARN,
120
+ message,
121
+ context: isError
122
+ ? undefined
123
+ : errorOrContext,
124
+ error: isError ? errorOrContext : undefined,
125
+ });
126
+ }
127
+ info(message, context) {
128
+ this.writeLog({
129
+ timestamp: new Date().toISOString(),
130
+ level: LogLevel.INFO,
131
+ message,
132
+ context,
133
+ });
134
+ }
135
+ debug(message, context) {
136
+ this.writeLog({
137
+ timestamp: new Date().toISOString(),
138
+ level: LogLevel.DEBUG,
139
+ message,
140
+ context,
141
+ });
142
+ }
143
+ }
144
+ // Export singleton instance
145
+ export const logger = Logger.getInstance();