@stackmemoryai/stackmemory 0.3.17 → 0.3.19

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 (234) hide show
  1. package/dist/cli/claude-sm.js +51 -5
  2. package/dist/cli/claude-sm.js.map +2 -2
  3. package/dist/cli/codex-sm.js +52 -19
  4. package/dist/cli/codex-sm.js.map +2 -2
  5. package/dist/cli/commands/db.js +143 -0
  6. package/dist/cli/commands/db.js.map +7 -0
  7. package/dist/cli/commands/login.js +50 -0
  8. package/dist/cli/commands/login.js.map +7 -0
  9. package/dist/cli/commands/migrate.js +178 -0
  10. package/dist/cli/commands/migrate.js.map +7 -0
  11. package/dist/cli/commands/onboard.js +158 -2
  12. package/dist/cli/commands/onboard.js.map +2 -2
  13. package/dist/cli/commands/skills.js +15 -2
  14. package/dist/cli/commands/skills.js.map +2 -2
  15. package/dist/cli/index.js +118 -834
  16. package/dist/cli/index.js.map +3 -3
  17. package/dist/core/context/dual-stack-manager.js +1 -1
  18. package/dist/core/context/dual-stack-manager.js.map +1 -1
  19. package/dist/core/context/frame-database.js +1 -0
  20. package/dist/core/context/frame-database.js.map +2 -2
  21. package/dist/core/context/frame-manager.js +59 -2
  22. package/dist/core/context/frame-manager.js.map +2 -2
  23. package/dist/core/database/database-adapter.js +6 -1
  24. package/dist/core/database/database-adapter.js.map +2 -2
  25. package/dist/core/database/sqlite-adapter.js +60 -2
  26. package/dist/core/database/sqlite-adapter.js.map +2 -2
  27. package/dist/integrations/claude-code/subagent-client.js +106 -3
  28. package/dist/integrations/claude-code/subagent-client.js.map +2 -2
  29. package/dist/servers/railway/config.js +51 -0
  30. package/dist/servers/railway/config.js.map +7 -0
  31. package/dist/servers/railway/index-enhanced.js +156 -0
  32. package/dist/servers/railway/index-enhanced.js.map +7 -0
  33. package/dist/servers/railway/index.js +843 -82
  34. package/dist/servers/railway/index.js.map +3 -3
  35. package/dist/servers/railway/minimal.js +48 -3
  36. package/dist/servers/railway/minimal.js.map +2 -2
  37. package/dist/servers/railway/storage-test.js +455 -0
  38. package/dist/servers/railway/storage-test.js.map +7 -0
  39. package/dist/skills/claude-skills.js +13 -12
  40. package/dist/skills/claude-skills.js.map +2 -2
  41. package/dist/skills/recursive-agent-orchestrator.js +27 -18
  42. package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
  43. package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
  44. package/package.json +13 -21
  45. package/scripts/README-TESTING.md +186 -0
  46. package/scripts/analyze-cli-security.js +288 -0
  47. package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
  48. package/scripts/archive/analyze-linear-duplicates.js +214 -0
  49. package/scripts/archive/analyze-remaining-duplicates.js +230 -0
  50. package/scripts/archive/analyze-sta-duplicates.js +292 -0
  51. package/scripts/archive/analyze-sta-graphql.js +399 -0
  52. package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
  53. package/scripts/archive/check-all-duplicates.ts +419 -0
  54. package/scripts/archive/clean-duplicate-tasks.js +114 -0
  55. package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
  56. package/scripts/archive/create-phase-tasks.js +387 -0
  57. package/scripts/archive/delete-linear-duplicates.js +182 -0
  58. package/scripts/archive/delete-remaining-duplicates.js +158 -0
  59. package/scripts/archive/delete-sta-duplicates.js +201 -0
  60. package/scripts/archive/delete-sta-oauth.js +201 -0
  61. package/scripts/archive/export-sta-tasks.js +62 -0
  62. package/scripts/archive/install-auto-sync.js +266 -0
  63. package/scripts/archive/install-chromadb-hooks.sh +133 -0
  64. package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
  65. package/scripts/archive/install-post-task-hooks.sh +289 -0
  66. package/scripts/archive/install-stackmemory-hooks.sh +420 -0
  67. package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
  68. package/scripts/archive/merge-linear-duplicates.ts +180 -0
  69. package/scripts/archive/remove-sta-tasks.js +70 -0
  70. package/scripts/archive/setup-background-sync.sh +168 -0
  71. package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
  72. package/scripts/archive/setup-claude-autostart.sh +305 -0
  73. package/scripts/archive/setup-git-hooks.sh +25 -0
  74. package/scripts/archive/setup-linear-oauth.sh +46 -0
  75. package/scripts/archive/setup-mcp.sh +113 -0
  76. package/scripts/archive/setup-railway-deployment.sh +81 -0
  77. package/scripts/auto-handoff.sh +262 -0
  78. package/scripts/background-sync-manager.js +416 -0
  79. package/scripts/benchmark-performance.ts +57 -0
  80. package/scripts/check-redis.ts +48 -0
  81. package/scripts/chromadb-auto-loader.sh +128 -0
  82. package/scripts/chromadb-context-loader.js +479 -0
  83. package/scripts/claude-chromadb-hook.js +460 -0
  84. package/scripts/claude-code-wrapper.sh +66 -0
  85. package/scripts/claude-linear-skill.js +455 -0
  86. package/scripts/claude-pre-commit.sh +302 -0
  87. package/scripts/claude-sm-autostart.js +532 -0
  88. package/scripts/claude-sm-setup.sh +367 -0
  89. package/scripts/claude-with-chromadb.sh +69 -0
  90. package/scripts/claude-worktree-manager.sh +323 -0
  91. package/scripts/claude-worktree-monitor.sh +371 -0
  92. package/scripts/claude-worktree-setup.sh +327 -0
  93. package/scripts/clean-linear-backlog.js +273 -0
  94. package/scripts/cleanup-old-sessions.sh +57 -0
  95. package/scripts/codex-wrapper.sh +88 -0
  96. package/scripts/create-sandbox.sh +269 -0
  97. package/scripts/debug-linear-update.js +174 -0
  98. package/scripts/delete-linear-tasks.js +167 -0
  99. package/scripts/deploy.sh +89 -0
  100. package/scripts/deployment/railway.sh +352 -0
  101. package/scripts/deployment/test-deployment.js +194 -0
  102. package/scripts/detect-and-rehydrate.js +162 -0
  103. package/scripts/detect-and-rehydrate.mjs +165 -0
  104. package/scripts/development/create-demo-tasks.js +143 -0
  105. package/scripts/development/debug-frame-test.js +16 -0
  106. package/scripts/development/demo-auto-sync.js +128 -0
  107. package/scripts/development/fix-all-imports.js +213 -0
  108. package/scripts/development/fix-imports.js +229 -0
  109. package/scripts/development/fix-lint-loop.cjs +103 -0
  110. package/scripts/development/fix-project-id.ts +161 -0
  111. package/scripts/development/fix-strict-mode-issues.ts +291 -0
  112. package/scripts/development/reorganize-structure.sh +228 -0
  113. package/scripts/development/test-persistence-direct.js +148 -0
  114. package/scripts/development/test-persistence.js +114 -0
  115. package/scripts/development/test-tasks.js +93 -0
  116. package/scripts/development/update-imports.js +212 -0
  117. package/scripts/fetch-linear-status.js +125 -0
  118. package/scripts/git-hooks/README.md +310 -0
  119. package/scripts/git-hooks/branch-context-manager.sh +342 -0
  120. package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
  121. package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
  122. package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
  123. package/scripts/hooks/cleanup-shell.sh +130 -0
  124. package/scripts/hooks/task-complete.sh +114 -0
  125. package/scripts/initialize.ts +129 -0
  126. package/scripts/install-claude-hooks-auto.js +104 -0
  127. package/scripts/install-claude-hooks.sh +133 -0
  128. package/scripts/install-global.sh +296 -0
  129. package/scripts/install.sh +235 -0
  130. package/scripts/linear-auto-sync.js +262 -0
  131. package/scripts/linear-auto-sync.sh +161 -0
  132. package/scripts/linear-sync-daemon.js +150 -0
  133. package/scripts/linear-task-review.js +237 -0
  134. package/scripts/list-linear-tasks.ts +178 -0
  135. package/scripts/mcp-proxy.js +66 -0
  136. package/scripts/opencode-wrapper.sh +85 -0
  137. package/scripts/publish-local.js +74 -0
  138. package/scripts/query-chromadb.ts +201 -0
  139. package/scripts/railway-env-setup.sh +39 -0
  140. package/scripts/reconcile-local-tasks.js +170 -0
  141. package/scripts/recreate-frames-db.js +89 -0
  142. package/scripts/setup/claude-integration.js +138 -0
  143. package/scripts/setup/configure-alias.js +125 -0
  144. package/scripts/setup/configure-codex-alias.js +161 -0
  145. package/scripts/setup/configure-opencode-alias.js +175 -0
  146. package/scripts/setup-claude-integration.js +204 -0
  147. package/scripts/setup-claude-integration.sh +183 -0
  148. package/scripts/setup-railway-deployment.sh +37 -0
  149. package/scripts/setup.sh +31 -0
  150. package/scripts/show-linear-summary.ts +172 -0
  151. package/scripts/stackmemory-auto-handoff.sh +231 -0
  152. package/scripts/stackmemory-daemon.sh +40 -0
  153. package/scripts/start-linear-sync-daemon.sh +141 -0
  154. package/scripts/start-temporal-paradox.sh +214 -0
  155. package/scripts/status.ts +159 -0
  156. package/scripts/sync-and-clean-tasks.js +258 -0
  157. package/scripts/sync-frames-from-railway.js +228 -0
  158. package/scripts/sync-linear-graphql.js +303 -0
  159. package/scripts/sync-linear-tasks.js +186 -0
  160. package/scripts/test-auto-triggers.sh +57 -0
  161. package/scripts/test-browser-mcp.js +74 -0
  162. package/scripts/test-chromadb-full.js +115 -0
  163. package/scripts/test-chromadb-hooks.sh +28 -0
  164. package/scripts/test-chromadb-sync.ts +245 -0
  165. package/scripts/test-cli-security.js +293 -0
  166. package/scripts/test-hooks-persistence.sh +220 -0
  167. package/scripts/test-installation-scenarios.sh +359 -0
  168. package/scripts/test-installation.sh +224 -0
  169. package/scripts/test-mcp.js +163 -0
  170. package/scripts/test-pre-publish-quick.sh +75 -0
  171. package/scripts/test-quality-gates.sh +263 -0
  172. package/scripts/test-railway-db.js +222 -0
  173. package/scripts/test-redis-storage.ts +490 -0
  174. package/scripts/test-rlm-basic.sh +122 -0
  175. package/scripts/test-rlm-comprehensive.sh +260 -0
  176. package/scripts/test-rlm-e2e.sh +268 -0
  177. package/scripts/test-rlm-simple.js +90 -0
  178. package/scripts/test-rlm.js +110 -0
  179. package/scripts/test-session-handoff.sh +165 -0
  180. package/scripts/test-shell-integration.sh +275 -0
  181. package/scripts/testing/ab-test-runner.ts +508 -0
  182. package/scripts/testing/collect-metrics.ts +457 -0
  183. package/scripts/testing/quick-effectiveness-demo.js +187 -0
  184. package/scripts/testing/real-performance-test.js +422 -0
  185. package/scripts/testing/run-effectiveness-tests.sh +176 -0
  186. package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
  187. package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
  188. package/scripts/testing/simple-effectiveness-test.js +310 -0
  189. package/scripts/testing/src/core/context/context-bridge.js +253 -0
  190. package/scripts/testing/src/core/context/frame-manager.js +746 -0
  191. package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
  192. package/scripts/testing/src/core/database/database-adapter.js +54 -0
  193. package/scripts/testing/src/core/errors/index.js +291 -0
  194. package/scripts/testing/src/core/errors/recovery.js +268 -0
  195. package/scripts/testing/src/core/monitoring/logger.js +145 -0
  196. package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
  197. package/scripts/testing/src/core/session/index.js +1 -0
  198. package/scripts/testing/src/core/session/session-manager.js +323 -0
  199. package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
  200. package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
  201. package/scripts/testing/src/core/trace/debug-trace.js +398 -0
  202. package/scripts/testing/src/core/trace/index.js +120 -0
  203. package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
  204. package/scripts/update-linear-status.js +268 -0
  205. package/scripts/update-linear-tasks-fixed.js +284 -0
  206. package/scripts/verify-railway-schema.ts +35 -0
  207. package/templates/claude-hooks/hooks.json +5 -0
  208. package/templates/claude-hooks/on-clear.js +56 -0
  209. package/templates/claude-hooks/on-startup.js +56 -0
  210. package/templates/claude-hooks/tool-use-trace.js +67 -0
  211. package/dist/features/tui/components/analytics-panel.js +0 -157
  212. package/dist/features/tui/components/analytics-panel.js.map +0 -7
  213. package/dist/features/tui/components/frame-visualizer.js +0 -377
  214. package/dist/features/tui/components/frame-visualizer.js.map +0 -7
  215. package/dist/features/tui/components/pr-tracker.js +0 -135
  216. package/dist/features/tui/components/pr-tracker.js.map +0 -7
  217. package/dist/features/tui/components/session-monitor.js +0 -299
  218. package/dist/features/tui/components/session-monitor.js.map +0 -7
  219. package/dist/features/tui/components/subagent-fleet.js +0 -395
  220. package/dist/features/tui/components/subagent-fleet.js.map +0 -7
  221. package/dist/features/tui/components/task-board.js +0 -1139
  222. package/dist/features/tui/components/task-board.js.map +0 -7
  223. package/dist/features/tui/index.js +0 -408
  224. package/dist/features/tui/index.js.map +0 -7
  225. package/dist/features/tui/services/data-service.js +0 -641
  226. package/dist/features/tui/services/data-service.js.map +0 -7
  227. package/dist/features/tui/services/linear-task-reader.js +0 -102
  228. package/dist/features/tui/services/linear-task-reader.js.map +0 -7
  229. package/dist/features/tui/services/websocket-client.js +0 -162
  230. package/dist/features/tui/services/websocket-client.js.map +0 -7
  231. package/dist/features/tui/terminal-compat.js +0 -220
  232. package/dist/features/tui/terminal-compat.js.map +0 -7
  233. package/dist/features/tui/types.js +0 -1
  234. package/dist/features/tui/types.js.map +0 -7
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Database Operations Trace Wrapper
3
+ * Wraps SQLite operations with comprehensive tracing for debugging
4
+ */
5
+ import Database from 'better-sqlite3';
6
+ import { trace } from './debug-trace.js';
7
+ import { logger } from '../monitoring/logger.js';
8
+ /**
9
+ * Create a traced database instance
10
+ */
11
+ export function createTracedDatabase(filename, options) {
12
+ const db = new Database(filename, options);
13
+ if (options?.traceEnabled !== false) {
14
+ return wrapDatabase(db, options?.slowQueryThreshold);
15
+ }
16
+ return db;
17
+ }
18
+ /**
19
+ * Wrap an existing database with tracing
20
+ */
21
+ export function wrapDatabase(db, slowQueryThreshold = 100) {
22
+ // Wrap prepare method to trace all queries
23
+ const originalPrepare = db.prepare.bind(db);
24
+ db.prepare = function (source) {
25
+ const statement = originalPrepare(source);
26
+ return wrapStatement(statement, source, slowQueryThreshold);
27
+ };
28
+ // Wrap exec for direct SQL execution
29
+ const originalExec = db.exec.bind(db);
30
+ db.exec = function (source) {
31
+ return trace.traceSync('query', `EXEC: ${source.substring(0, 50)}...`, {}, () => {
32
+ const startTime = performance.now();
33
+ const result = originalExec(source);
34
+ const duration = performance.now() - startTime;
35
+ if (duration > slowQueryThreshold) {
36
+ logger.warn(`Slow query detected: ${duration.toFixed(0)}ms`, {
37
+ query: source.substring(0, 200),
38
+ duration,
39
+ });
40
+ }
41
+ return result;
42
+ });
43
+ };
44
+ // Wrap transaction for transaction tracking
45
+ const originalTransaction = db.transaction.bind(db);
46
+ db.transaction = function (fn) {
47
+ return originalTransaction(function (...args) {
48
+ return trace.traceSync('query', 'TRANSACTION', { args: args.length }, () => {
49
+ return fn.apply(this, args);
50
+ });
51
+ });
52
+ };
53
+ // Add query statistics tracking
54
+ db.__queryStats = {
55
+ totalQueries: 0,
56
+ slowQueries: 0,
57
+ totalDuration: 0,
58
+ queryTypes: {},
59
+ };
60
+ return db;
61
+ }
62
+ /**
63
+ * Wrap a statement with tracing
64
+ */
65
+ function wrapStatement(statement, source, slowQueryThreshold) {
66
+ const queryType = source.trim().split(/\s+/)[0].toUpperCase();
67
+ const shortQuery = source.substring(0, 100).replace(/\s+/g, ' ');
68
+ // Wrap run method
69
+ const originalRun = statement.run.bind(statement);
70
+ statement.run = function (...params) {
71
+ return trace.traceSync('query', `${queryType}: ${shortQuery}`, params, () => {
72
+ const startTime = performance.now();
73
+ const result = originalRun(...params);
74
+ const duration = performance.now() - startTime;
75
+ // Track statistics
76
+ updateQueryStats(statement, queryType, duration, slowQueryThreshold);
77
+ // Log slow queries
78
+ if (duration > slowQueryThreshold) {
79
+ logger.warn(`Slow ${queryType} query: ${duration.toFixed(0)}ms`, {
80
+ query: shortQuery,
81
+ params,
82
+ duration,
83
+ changes: result.changes,
84
+ });
85
+ }
86
+ return result;
87
+ });
88
+ };
89
+ // Wrap get method
90
+ const originalGet = statement.get.bind(statement);
91
+ statement.get = function (...params) {
92
+ return trace.traceSync('query', `${queryType} (get): ${shortQuery}`, params, () => {
93
+ const startTime = performance.now();
94
+ const result = originalGet(...params);
95
+ const duration = performance.now() - startTime;
96
+ updateQueryStats(statement, queryType, duration, slowQueryThreshold);
97
+ if (duration > slowQueryThreshold) {
98
+ logger.warn(`Slow ${queryType} query: ${duration.toFixed(0)}ms`, {
99
+ query: shortQuery,
100
+ params,
101
+ duration,
102
+ found: result != null,
103
+ });
104
+ }
105
+ return result;
106
+ });
107
+ };
108
+ // Wrap all method
109
+ const originalAll = statement.all.bind(statement);
110
+ statement.all = function (...params) {
111
+ return trace.traceSync('query', `${queryType} (all): ${shortQuery}`, params, () => {
112
+ const startTime = performance.now();
113
+ const result = originalAll(...params);
114
+ const duration = performance.now() - startTime;
115
+ updateQueryStats(statement, queryType, duration, slowQueryThreshold);
116
+ if (duration > slowQueryThreshold) {
117
+ logger.warn(`Slow ${queryType} query: ${duration.toFixed(0)}ms`, {
118
+ query: shortQuery,
119
+ params,
120
+ duration,
121
+ rows: result.length,
122
+ });
123
+ }
124
+ // Warn about potential N+1 queries
125
+ if (result.length > 100 && queryType === 'SELECT') {
126
+ logger.warn(`Large result set: ${result.length} rows`, {
127
+ query: shortQuery,
128
+ suggestion: 'Consider pagination or more specific queries',
129
+ });
130
+ }
131
+ return result;
132
+ });
133
+ };
134
+ // Wrap iterate method for cursor operations
135
+ const originalIterate = statement.iterate.bind(statement);
136
+ statement.iterate = function (...params) {
137
+ const startTime = performance.now();
138
+ let rowCount = 0;
139
+ const iterator = originalIterate(...params);
140
+ const wrappedIterator = {
141
+ [Symbol.iterator]() {
142
+ return this;
143
+ },
144
+ next() {
145
+ const result = iterator.next();
146
+ if (!result.done) {
147
+ rowCount++;
148
+ }
149
+ else {
150
+ const duration = performance.now() - startTime;
151
+ updateQueryStats(statement, queryType, duration, slowQueryThreshold);
152
+ if (duration > slowQueryThreshold) {
153
+ logger.warn(`Slow ${queryType} iteration: ${duration.toFixed(0)}ms`, {
154
+ query: shortQuery,
155
+ params,
156
+ duration,
157
+ rows: rowCount,
158
+ });
159
+ }
160
+ }
161
+ return result;
162
+ },
163
+ };
164
+ return wrappedIterator;
165
+ };
166
+ return statement;
167
+ }
168
+ /**
169
+ * Update query statistics
170
+ */
171
+ function updateQueryStats(statement, queryType, duration, slowQueryThreshold) {
172
+ const db = statement.database;
173
+ if (db.__queryStats) {
174
+ db.__queryStats.totalQueries++;
175
+ db.__queryStats.totalDuration += duration;
176
+ if (duration > slowQueryThreshold) {
177
+ db.__queryStats.slowQueries++;
178
+ }
179
+ if (!db.__queryStats.queryTypes[queryType]) {
180
+ db.__queryStats.queryTypes[queryType] = 0;
181
+ }
182
+ db.__queryStats.queryTypes[queryType]++;
183
+ }
184
+ }
185
+ /**
186
+ * Get query statistics from a traced database
187
+ */
188
+ export function getQueryStatistics(db) {
189
+ const stats = db.__queryStats;
190
+ if (!stats)
191
+ return null;
192
+ return {
193
+ ...stats,
194
+ averageDuration: stats.totalQueries > 0
195
+ ? stats.totalDuration / stats.totalQueries
196
+ : 0,
197
+ };
198
+ }
199
+ /**
200
+ * Helper to trace a specific query with context
201
+ */
202
+ export async function traceQuery(db, queryName, query, params, fn) {
203
+ return trace.traceAsync('query', queryName, { query, params }, async () => {
204
+ try {
205
+ const result = fn();
206
+ // Log successful complex queries for debugging
207
+ if (query.includes('JOIN') || query.includes('GROUP BY')) {
208
+ logger.debug(`Complex query executed: ${queryName}`, {
209
+ query: query.substring(0, 200),
210
+ params,
211
+ });
212
+ }
213
+ return result;
214
+ }
215
+ catch (error) {
216
+ // Enhanced error logging for database errors
217
+ logger.error(`Database query failed: ${queryName}`, error, {
218
+ query,
219
+ params,
220
+ errorCode: error.code,
221
+ });
222
+ throw error;
223
+ }
224
+ });
225
+ }
226
+ /**
227
+ * Create a traced transaction with automatic rollback on error
228
+ */
229
+ export function createTracedTransaction(db, name, fn) {
230
+ return trace.traceSync('query', `TRANSACTION: ${name}`, {}, () => {
231
+ const startTime = performance.now();
232
+ try {
233
+ const tx = db.transaction(fn);
234
+ const result = tx.deferred();
235
+ const duration = performance.now() - startTime;
236
+ logger.info(`Transaction completed: ${name}`, {
237
+ duration,
238
+ success: true,
239
+ });
240
+ return result;
241
+ }
242
+ catch (error) {
243
+ const duration = performance.now() - startTime;
244
+ logger.error(`Transaction failed: ${name}`, error, {
245
+ duration,
246
+ success: false,
247
+ });
248
+ throw error;
249
+ }
250
+ });
251
+ }
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Debug Trace Module - Comprehensive execution tracing for LLM debugging
3
+ *
4
+ * This module provides detailed execution tracing to help LLMs understand
5
+ * exactly what happened during code execution, making debugging much easier.
6
+ */
7
+ import { performance } from 'perf_hooks';
8
+ import { logger } from '../monitoring/logger.js';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { v4 as uuidv4 } from 'uuid';
12
+ export class TraceContext {
13
+ constructor() {
14
+ this.currentTrace = null;
15
+ this.traceStack = [];
16
+ this.allTraces = [];
17
+ this.startTime = Date.now();
18
+ this.sensitivePatterns = [
19
+ /api[_-]?key/i,
20
+ /token/i,
21
+ /secret/i,
22
+ /password/i,
23
+ /bearer/i,
24
+ /authorization/i,
25
+ /client[_-]?id/i,
26
+ /client[_-]?secret/i,
27
+ ];
28
+ this.config = this.loadConfig();
29
+ if (this.config.output === 'file' || this.config.output === 'both') {
30
+ this.initializeOutputFile();
31
+ }
32
+ }
33
+ static getInstance() {
34
+ if (!TraceContext.instance) {
35
+ TraceContext.instance = new TraceContext();
36
+ }
37
+ return TraceContext.instance;
38
+ }
39
+ loadConfig() {
40
+ return {
41
+ enabled: process.env.DEBUG_TRACE === 'true' || process.env.STACKMEMORY_DEBUG === 'true',
42
+ verbosity: process.env.TRACE_VERBOSITY || 'full',
43
+ output: process.env.TRACE_OUTPUT || 'console',
44
+ includeParams: process.env.TRACE_PARAMS !== 'false',
45
+ includeResults: process.env.TRACE_RESULTS !== 'false',
46
+ maskSensitive: process.env.TRACE_MASK_SENSITIVE !== 'false',
47
+ performanceThreshold: parseInt(process.env.TRACE_PERF_THRESHOLD || '100'),
48
+ maxDepth: parseInt(process.env.TRACE_MAX_DEPTH || '20'),
49
+ captureMemory: process.env.TRACE_MEMORY === 'true',
50
+ };
51
+ }
52
+ initializeOutputFile() {
53
+ const traceDir = path.join(process.env.HOME || '.', '.stackmemory', 'traces');
54
+ if (!fs.existsSync(traceDir)) {
55
+ fs.mkdirSync(traceDir, { recursive: true });
56
+ }
57
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
58
+ this.outputFile = path.join(traceDir, `trace-${timestamp}.jsonl`);
59
+ }
60
+ maskSensitiveData(obj) {
61
+ if (!this.config.maskSensitive)
62
+ return obj;
63
+ if (typeof obj !== 'object' || obj === null)
64
+ return obj;
65
+ const masked = Array.isArray(obj) ? [...obj] : { ...obj };
66
+ for (const key in masked) {
67
+ if (typeof key === 'string') {
68
+ // Check if key matches sensitive patterns
69
+ const isSensitive = this.sensitivePatterns.some(pattern => pattern.test(key));
70
+ if (isSensitive) {
71
+ masked[key] = '[MASKED]';
72
+ }
73
+ else if (typeof masked[key] === 'object') {
74
+ masked[key] = this.maskSensitiveData(masked[key]);
75
+ }
76
+ else if (typeof masked[key] === 'string' && masked[key].length > 20) {
77
+ // Check if value looks like a token/key
78
+ if (/^[a-zA-Z0-9_-]{20,}$/.test(masked[key])) {
79
+ masked[key] = masked[key].substring(0, 8) + '...[MASKED]';
80
+ }
81
+ }
82
+ }
83
+ }
84
+ return masked;
85
+ }
86
+ captureMemory() {
87
+ if (!this.config.captureMemory)
88
+ return undefined;
89
+ return process.memoryUsage();
90
+ }
91
+ formatDuration(ms) {
92
+ if (ms < 1000)
93
+ return `${ms.toFixed(0)}ms`;
94
+ if (ms < 60000)
95
+ return `${(ms / 1000).toFixed(2)}s`;
96
+ return `${(ms / 60000).toFixed(2)}m`;
97
+ }
98
+ formatMemory(bytes) {
99
+ const mb = bytes / 1024 / 1024;
100
+ return `${mb.toFixed(2)}MB`;
101
+ }
102
+ getIndent(depth) {
103
+ return ' '.repeat(depth);
104
+ }
105
+ formatTraceEntry(entry, includeChildren = true) {
106
+ const indent = this.getIndent(entry.depth);
107
+ const duration = entry.duration ? ` [${this.formatDuration(entry.duration)}]` : '';
108
+ const memory = entry.memory?.delta
109
+ ? ` (Δmem: ${this.formatMemory(entry.memory.delta.heapUsed)})`
110
+ : '';
111
+ let output = `${indent}→ [${entry.type.toUpperCase()}:${entry.id.substring(0, 8)}] ${entry.name}${duration}${memory}`;
112
+ if (entry.error) {
113
+ output += `\n${indent} ✗ ERROR: ${entry.error.message || entry.error}`;
114
+ if (entry.error.stack && this.config.verbosity === 'full') {
115
+ output += `\n${indent} Stack: ${entry.error.stack.split('\n')[1]?.trim()}`;
116
+ }
117
+ }
118
+ if (this.config.includeParams && entry.params && Object.keys(entry.params).length > 0) {
119
+ const maskedParams = this.maskSensitiveData(entry.params);
120
+ output += `\n${indent} ▸ Params: ${JSON.stringify(maskedParams, null, 2).replace(/\n/g, '\n' + indent + ' ')}`;
121
+ }
122
+ if (this.config.includeResults && entry.result !== undefined && !entry.error) {
123
+ const maskedResult = this.maskSensitiveData(entry.result);
124
+ const resultStr = JSON.stringify(maskedResult, null, 2);
125
+ if (resultStr.length < 200) {
126
+ output += `\n${indent} ◂ Result: ${resultStr.replace(/\n/g, '\n' + indent + ' ')}`;
127
+ }
128
+ else {
129
+ output += `\n${indent} ◂ Result: [${typeof maskedResult}] ${resultStr.substring(0, 100)}...`;
130
+ }
131
+ }
132
+ if (entry.duration && entry.duration > this.config.performanceThreshold) {
133
+ output += `\n${indent} ⚠ SLOW: Exceeded ${this.config.performanceThreshold}ms threshold`;
134
+ }
135
+ if (includeChildren && entry.children.length > 0) {
136
+ for (const child of entry.children) {
137
+ output += '\n' + this.formatTraceEntry(child, true);
138
+ }
139
+ }
140
+ if (entry.endTime && entry.depth > 0) {
141
+ output += `\n${indent}← [${entry.type.toUpperCase()}:${entry.id.substring(0, 8)}] completed`;
142
+ }
143
+ return output;
144
+ }
145
+ outputTrace(entry) {
146
+ if (!this.config.enabled)
147
+ return;
148
+ const formatted = this.formatTraceEntry(entry, false);
149
+ if (this.config.output === 'console' || this.config.output === 'both') {
150
+ console.log(formatted);
151
+ }
152
+ if ((this.config.output === 'file' || this.config.output === 'both') && this.outputFile) {
153
+ const jsonLine = JSON.stringify({
154
+ ...entry,
155
+ formatted,
156
+ timestamp: new Date().toISOString(),
157
+ }) + '\n';
158
+ fs.appendFileSync(this.outputFile, jsonLine);
159
+ }
160
+ }
161
+ startTrace(type, name, params, metadata) {
162
+ if (!this.config.enabled)
163
+ return '';
164
+ const id = uuidv4();
165
+ const parentId = this.currentTrace?.id;
166
+ const depth = this.traceStack.length;
167
+ if (depth > this.config.maxDepth) {
168
+ return id; // Prevent infinite recursion
169
+ }
170
+ const entry = {
171
+ id,
172
+ parentId,
173
+ type,
174
+ name,
175
+ startTime: performance.now(),
176
+ depth,
177
+ params: this.config.includeParams ? params : undefined,
178
+ metadata,
179
+ children: [],
180
+ memory: this.captureMemory() ? { before: this.captureMemory() } : undefined,
181
+ };
182
+ if (this.currentTrace) {
183
+ this.currentTrace.children.push(entry);
184
+ }
185
+ else {
186
+ this.allTraces.push(entry);
187
+ }
188
+ this.traceStack.push(entry);
189
+ this.currentTrace = entry;
190
+ this.outputTrace(entry);
191
+ return id;
192
+ }
193
+ endTrace(id, result, error) {
194
+ if (!this.config.enabled)
195
+ return;
196
+ const index = this.traceStack.findIndex(t => t.id === id);
197
+ if (index === -1)
198
+ return;
199
+ const entry = this.traceStack[index];
200
+ entry.endTime = performance.now();
201
+ entry.duration = entry.endTime - entry.startTime;
202
+ entry.result = this.config.includeResults && !error ? result : undefined;
203
+ entry.error = error;
204
+ if (entry.memory?.before) {
205
+ entry.memory.after = this.captureMemory();
206
+ if (entry.memory.after) {
207
+ entry.memory.delta = {
208
+ rss: entry.memory.after.rss - entry.memory.before.rss,
209
+ heapUsed: entry.memory.after.heapUsed - entry.memory.before.heapUsed,
210
+ };
211
+ }
212
+ }
213
+ this.outputTrace(entry);
214
+ // Remove from stack and update current
215
+ this.traceStack.splice(index);
216
+ this.currentTrace = this.traceStack[this.traceStack.length - 1] || null;
217
+ }
218
+ async traceAsync(type, name, params, fn) {
219
+ const id = this.startTrace(type, name, params);
220
+ try {
221
+ const result = await fn();
222
+ this.endTrace(id, result);
223
+ return result;
224
+ }
225
+ catch (error) {
226
+ this.endTrace(id, undefined, error);
227
+ throw error;
228
+ }
229
+ }
230
+ traceSync(type, name, params, fn) {
231
+ const id = this.startTrace(type, name, params);
232
+ try {
233
+ const result = fn();
234
+ this.endTrace(id, result);
235
+ return result;
236
+ }
237
+ catch (error) {
238
+ this.endTrace(id, undefined, error);
239
+ throw error;
240
+ }
241
+ }
242
+ async command(name, options, fn) {
243
+ return this.traceAsync('command', name, options, fn);
244
+ }
245
+ async step(name, fn) {
246
+ return this.traceAsync('step', name, undefined, fn);
247
+ }
248
+ async query(sql, params, fn) {
249
+ return this.traceAsync('query', sql.substring(0, 50), params, fn);
250
+ }
251
+ async api(method, url, body, fn) {
252
+ return this.traceAsync('api', `${method} ${url}`, { body }, fn);
253
+ }
254
+ getExecutionSummary() {
255
+ if (!this.config.enabled)
256
+ return 'Tracing disabled';
257
+ const totalDuration = Date.now() - this.startTime;
258
+ const errorCount = this.countErrors(this.allTraces);
259
+ const slowCount = this.countSlowOperations(this.allTraces);
260
+ let summary = `\n${'='.repeat(80)}\n`;
261
+ summary += `EXECUTION SUMMARY\n`;
262
+ summary += `${'='.repeat(80)}\n`;
263
+ summary += `Total Duration: ${this.formatDuration(totalDuration)}\n`;
264
+ summary += `Total Operations: ${this.countOperations(this.allTraces)}\n`;
265
+ summary += `Errors: ${errorCount}\n`;
266
+ summary += `Slow Operations (>${this.config.performanceThreshold}ms): ${slowCount}\n`;
267
+ if (this.config.captureMemory) {
268
+ const memUsage = process.memoryUsage();
269
+ summary += `Final Memory: RSS=${this.formatMemory(memUsage.rss)}, Heap=${this.formatMemory(memUsage.heapUsed)}\n`;
270
+ }
271
+ if (this.outputFile) {
272
+ summary += `Trace Log: ${this.outputFile}\n`;
273
+ }
274
+ summary += `${'='.repeat(80)}`;
275
+ return summary;
276
+ }
277
+ countOperations(traces) {
278
+ let count = traces.length;
279
+ for (const trace of traces) {
280
+ count += this.countOperations(trace.children);
281
+ }
282
+ return count;
283
+ }
284
+ countErrors(traces) {
285
+ let count = 0;
286
+ for (const trace of traces) {
287
+ if (trace.error)
288
+ count++;
289
+ count += this.countErrors(trace.children);
290
+ }
291
+ return count;
292
+ }
293
+ countSlowOperations(traces) {
294
+ let count = 0;
295
+ for (const trace of traces) {
296
+ if (trace.duration && trace.duration > this.config.performanceThreshold)
297
+ count++;
298
+ count += this.countSlowOperations(trace.children);
299
+ }
300
+ return count;
301
+ }
302
+ getLastError() {
303
+ const findLastError = (traces) => {
304
+ for (let i = traces.length - 1; i >= 0; i--) {
305
+ const trace = traces[i];
306
+ if (trace.error)
307
+ return trace;
308
+ const childError = findLastError(trace.children);
309
+ if (childError)
310
+ return childError;
311
+ }
312
+ return null;
313
+ };
314
+ return findLastError(this.allTraces);
315
+ }
316
+ exportTraces() {
317
+ return this.allTraces;
318
+ }
319
+ reset() {
320
+ this.currentTrace = null;
321
+ this.traceStack = [];
322
+ this.allTraces = [];
323
+ this.startTime = Date.now();
324
+ }
325
+ }
326
+ // Singleton instance
327
+ export const trace = TraceContext.getInstance();
328
+ // Decorator for tracing class methods
329
+ export function Trace(type = 'function') {
330
+ return function (target, propertyKey, descriptor) {
331
+ const originalMethod = descriptor.value;
332
+ const isAsync = originalMethod.constructor.name === 'AsyncFunction';
333
+ if (isAsync) {
334
+ descriptor.value = async function (...args) {
335
+ const className = target.constructor.name;
336
+ const methodName = `${className}.${propertyKey}`;
337
+ return trace.traceAsync(type, methodName, args, async () => {
338
+ return originalMethod.apply(this, args);
339
+ });
340
+ };
341
+ }
342
+ else {
343
+ descriptor.value = function (...args) {
344
+ const className = target.constructor.name;
345
+ const methodName = `${className}.${propertyKey}`;
346
+ return trace.traceSync(type, methodName, args, () => {
347
+ return originalMethod.apply(this, args);
348
+ });
349
+ };
350
+ }
351
+ return descriptor;
352
+ };
353
+ }
354
+ // Decorator for tracing entire classes
355
+ export function TraceClass(type = 'function') {
356
+ return function (constructor) {
357
+ const prototype = constructor.prototype;
358
+ const propertyNames = Object.getOwnPropertyNames(prototype);
359
+ for (const propertyName of propertyNames) {
360
+ if (propertyName === 'constructor')
361
+ continue;
362
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);
363
+ if (!descriptor || typeof descriptor.value !== 'function')
364
+ continue;
365
+ Trace(type)(prototype, propertyName, descriptor);
366
+ Object.defineProperty(prototype, propertyName, descriptor);
367
+ }
368
+ return constructor;
369
+ };
370
+ }
371
+ // Helper for critical operations
372
+ export function TraceCritical(target, propertyKey, descriptor) {
373
+ const originalMethod = descriptor.value;
374
+ descriptor.value = async function (...args) {
375
+ const className = target.constructor.name;
376
+ const methodName = `${className}.${propertyKey} [CRITICAL]`;
377
+ // Store context before execution
378
+ const contextBefore = {
379
+ memory: process.memoryUsage(),
380
+ timestamp: new Date().toISOString(),
381
+ args: trace['maskSensitiveData'](args),
382
+ };
383
+ try {
384
+ return await trace.traceAsync('function', methodName, contextBefore, async () => {
385
+ return originalMethod.apply(this, args);
386
+ });
387
+ }
388
+ catch (error) {
389
+ // Enhanced error logging for critical operations
390
+ logger.error(`Critical operation failed: ${methodName}`, error, {
391
+ context: contextBefore,
392
+ stack: error.stack,
393
+ });
394
+ throw error;
395
+ }
396
+ };
397
+ return descriptor;
398
+ }