@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.
- package/dist/cli/claude-sm.js +51 -5
- package/dist/cli/claude-sm.js.map +2 -2
- package/dist/cli/codex-sm.js +52 -19
- package/dist/cli/codex-sm.js.map +2 -2
- package/dist/cli/commands/db.js +143 -0
- package/dist/cli/commands/db.js.map +7 -0
- package/dist/cli/commands/login.js +50 -0
- package/dist/cli/commands/login.js.map +7 -0
- package/dist/cli/commands/migrate.js +178 -0
- package/dist/cli/commands/migrate.js.map +7 -0
- package/dist/cli/commands/onboard.js +158 -2
- package/dist/cli/commands/onboard.js.map +2 -2
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +118 -834
- package/dist/cli/index.js.map +3 -3
- package/dist/core/context/dual-stack-manager.js +1 -1
- package/dist/core/context/dual-stack-manager.js.map +1 -1
- package/dist/core/context/frame-database.js +1 -0
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-manager.js +59 -2
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/core/database/database-adapter.js +6 -1
- package/dist/core/database/database-adapter.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js +60 -2
- package/dist/core/database/sqlite-adapter.js.map +2 -2
- package/dist/integrations/claude-code/subagent-client.js +106 -3
- package/dist/integrations/claude-code/subagent-client.js.map +2 -2
- package/dist/servers/railway/config.js +51 -0
- package/dist/servers/railway/config.js.map +7 -0
- package/dist/servers/railway/index-enhanced.js +156 -0
- package/dist/servers/railway/index-enhanced.js.map +7 -0
- package/dist/servers/railway/index.js +843 -82
- package/dist/servers/railway/index.js.map +3 -3
- package/dist/servers/railway/minimal.js +48 -3
- package/dist/servers/railway/minimal.js.map +2 -2
- package/dist/servers/railway/storage-test.js +455 -0
- package/dist/servers/railway/storage-test.js.map +7 -0
- package/dist/skills/claude-skills.js +13 -12
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +27 -18
- package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
- package/package.json +13 -21
- package/scripts/README-TESTING.md +186 -0
- package/scripts/analyze-cli-security.js +288 -0
- package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
- package/scripts/archive/analyze-linear-duplicates.js +214 -0
- package/scripts/archive/analyze-remaining-duplicates.js +230 -0
- package/scripts/archive/analyze-sta-duplicates.js +292 -0
- package/scripts/archive/analyze-sta-graphql.js +399 -0
- package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
- package/scripts/archive/check-all-duplicates.ts +419 -0
- package/scripts/archive/clean-duplicate-tasks.js +114 -0
- package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
- package/scripts/archive/create-phase-tasks.js +387 -0
- package/scripts/archive/delete-linear-duplicates.js +182 -0
- package/scripts/archive/delete-remaining-duplicates.js +158 -0
- package/scripts/archive/delete-sta-duplicates.js +201 -0
- package/scripts/archive/delete-sta-oauth.js +201 -0
- package/scripts/archive/export-sta-tasks.js +62 -0
- package/scripts/archive/install-auto-sync.js +266 -0
- package/scripts/archive/install-chromadb-hooks.sh +133 -0
- package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
- package/scripts/archive/install-post-task-hooks.sh +289 -0
- package/scripts/archive/install-stackmemory-hooks.sh +420 -0
- package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
- package/scripts/archive/merge-linear-duplicates.ts +180 -0
- package/scripts/archive/remove-sta-tasks.js +70 -0
- package/scripts/archive/setup-background-sync.sh +168 -0
- package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
- package/scripts/archive/setup-claude-autostart.sh +305 -0
- package/scripts/archive/setup-git-hooks.sh +25 -0
- package/scripts/archive/setup-linear-oauth.sh +46 -0
- package/scripts/archive/setup-mcp.sh +113 -0
- package/scripts/archive/setup-railway-deployment.sh +81 -0
- package/scripts/auto-handoff.sh +262 -0
- package/scripts/background-sync-manager.js +416 -0
- package/scripts/benchmark-performance.ts +57 -0
- package/scripts/check-redis.ts +48 -0
- package/scripts/chromadb-auto-loader.sh +128 -0
- package/scripts/chromadb-context-loader.js +479 -0
- package/scripts/claude-chromadb-hook.js +460 -0
- package/scripts/claude-code-wrapper.sh +66 -0
- package/scripts/claude-linear-skill.js +455 -0
- package/scripts/claude-pre-commit.sh +302 -0
- package/scripts/claude-sm-autostart.js +532 -0
- package/scripts/claude-sm-setup.sh +367 -0
- package/scripts/claude-with-chromadb.sh +69 -0
- package/scripts/claude-worktree-manager.sh +323 -0
- package/scripts/claude-worktree-monitor.sh +371 -0
- package/scripts/claude-worktree-setup.sh +327 -0
- package/scripts/clean-linear-backlog.js +273 -0
- package/scripts/cleanup-old-sessions.sh +57 -0
- package/scripts/codex-wrapper.sh +88 -0
- package/scripts/create-sandbox.sh +269 -0
- package/scripts/debug-linear-update.js +174 -0
- package/scripts/delete-linear-tasks.js +167 -0
- package/scripts/deploy.sh +89 -0
- package/scripts/deployment/railway.sh +352 -0
- package/scripts/deployment/test-deployment.js +194 -0
- package/scripts/detect-and-rehydrate.js +162 -0
- package/scripts/detect-and-rehydrate.mjs +165 -0
- package/scripts/development/create-demo-tasks.js +143 -0
- package/scripts/development/debug-frame-test.js +16 -0
- package/scripts/development/demo-auto-sync.js +128 -0
- package/scripts/development/fix-all-imports.js +213 -0
- package/scripts/development/fix-imports.js +229 -0
- package/scripts/development/fix-lint-loop.cjs +103 -0
- package/scripts/development/fix-project-id.ts +161 -0
- package/scripts/development/fix-strict-mode-issues.ts +291 -0
- package/scripts/development/reorganize-structure.sh +228 -0
- package/scripts/development/test-persistence-direct.js +148 -0
- package/scripts/development/test-persistence.js +114 -0
- package/scripts/development/test-tasks.js +93 -0
- package/scripts/development/update-imports.js +212 -0
- package/scripts/fetch-linear-status.js +125 -0
- package/scripts/git-hooks/README.md +310 -0
- package/scripts/git-hooks/branch-context-manager.sh +342 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
- package/scripts/hooks/cleanup-shell.sh +130 -0
- package/scripts/hooks/task-complete.sh +114 -0
- package/scripts/initialize.ts +129 -0
- package/scripts/install-claude-hooks-auto.js +104 -0
- package/scripts/install-claude-hooks.sh +133 -0
- package/scripts/install-global.sh +296 -0
- package/scripts/install.sh +235 -0
- package/scripts/linear-auto-sync.js +262 -0
- package/scripts/linear-auto-sync.sh +161 -0
- package/scripts/linear-sync-daemon.js +150 -0
- package/scripts/linear-task-review.js +237 -0
- package/scripts/list-linear-tasks.ts +178 -0
- package/scripts/mcp-proxy.js +66 -0
- package/scripts/opencode-wrapper.sh +85 -0
- package/scripts/publish-local.js +74 -0
- package/scripts/query-chromadb.ts +201 -0
- package/scripts/railway-env-setup.sh +39 -0
- package/scripts/reconcile-local-tasks.js +170 -0
- package/scripts/recreate-frames-db.js +89 -0
- package/scripts/setup/claude-integration.js +138 -0
- package/scripts/setup/configure-alias.js +125 -0
- package/scripts/setup/configure-codex-alias.js +161 -0
- package/scripts/setup/configure-opencode-alias.js +175 -0
- package/scripts/setup-claude-integration.js +204 -0
- package/scripts/setup-claude-integration.sh +183 -0
- package/scripts/setup-railway-deployment.sh +37 -0
- package/scripts/setup.sh +31 -0
- package/scripts/show-linear-summary.ts +172 -0
- package/scripts/stackmemory-auto-handoff.sh +231 -0
- package/scripts/stackmemory-daemon.sh +40 -0
- package/scripts/start-linear-sync-daemon.sh +141 -0
- package/scripts/start-temporal-paradox.sh +214 -0
- package/scripts/status.ts +159 -0
- package/scripts/sync-and-clean-tasks.js +258 -0
- package/scripts/sync-frames-from-railway.js +228 -0
- package/scripts/sync-linear-graphql.js +303 -0
- package/scripts/sync-linear-tasks.js +186 -0
- package/scripts/test-auto-triggers.sh +57 -0
- package/scripts/test-browser-mcp.js +74 -0
- package/scripts/test-chromadb-full.js +115 -0
- package/scripts/test-chromadb-hooks.sh +28 -0
- package/scripts/test-chromadb-sync.ts +245 -0
- package/scripts/test-cli-security.js +293 -0
- package/scripts/test-hooks-persistence.sh +220 -0
- package/scripts/test-installation-scenarios.sh +359 -0
- package/scripts/test-installation.sh +224 -0
- package/scripts/test-mcp.js +163 -0
- package/scripts/test-pre-publish-quick.sh +75 -0
- package/scripts/test-quality-gates.sh +263 -0
- package/scripts/test-railway-db.js +222 -0
- package/scripts/test-redis-storage.ts +490 -0
- package/scripts/test-rlm-basic.sh +122 -0
- package/scripts/test-rlm-comprehensive.sh +260 -0
- package/scripts/test-rlm-e2e.sh +268 -0
- package/scripts/test-rlm-simple.js +90 -0
- package/scripts/test-rlm.js +110 -0
- package/scripts/test-session-handoff.sh +165 -0
- package/scripts/test-shell-integration.sh +275 -0
- package/scripts/testing/ab-test-runner.ts +508 -0
- package/scripts/testing/collect-metrics.ts +457 -0
- package/scripts/testing/quick-effectiveness-demo.js +187 -0
- package/scripts/testing/real-performance-test.js +422 -0
- package/scripts/testing/run-effectiveness-tests.sh +176 -0
- package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
- package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
- package/scripts/testing/simple-effectiveness-test.js +310 -0
- package/scripts/testing/src/core/context/context-bridge.js +253 -0
- package/scripts/testing/src/core/context/frame-manager.js +746 -0
- package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
- package/scripts/testing/src/core/database/database-adapter.js +54 -0
- package/scripts/testing/src/core/errors/index.js +291 -0
- package/scripts/testing/src/core/errors/recovery.js +268 -0
- package/scripts/testing/src/core/monitoring/logger.js +145 -0
- package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
- package/scripts/testing/src/core/session/index.js +1 -0
- package/scripts/testing/src/core/session/session-manager.js +323 -0
- package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
- package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
- package/scripts/testing/src/core/trace/debug-trace.js +398 -0
- package/scripts/testing/src/core/trace/index.js +120 -0
- package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
- package/scripts/update-linear-status.js +268 -0
- package/scripts/update-linear-tasks-fixed.js +284 -0
- package/scripts/verify-railway-schema.ts +35 -0
- package/templates/claude-hooks/hooks.json +5 -0
- package/templates/claude-hooks/on-clear.js +56 -0
- package/templates/claude-hooks/on-startup.js +56 -0
- package/templates/claude-hooks/tool-use-trace.js +67 -0
- package/dist/features/tui/components/analytics-panel.js +0 -157
- package/dist/features/tui/components/analytics-panel.js.map +0 -7
- package/dist/features/tui/components/frame-visualizer.js +0 -377
- package/dist/features/tui/components/frame-visualizer.js.map +0 -7
- package/dist/features/tui/components/pr-tracker.js +0 -135
- package/dist/features/tui/components/pr-tracker.js.map +0 -7
- package/dist/features/tui/components/session-monitor.js +0 -299
- package/dist/features/tui/components/session-monitor.js.map +0 -7
- package/dist/features/tui/components/subagent-fleet.js +0 -395
- package/dist/features/tui/components/subagent-fleet.js.map +0 -7
- package/dist/features/tui/components/task-board.js +0 -1139
- package/dist/features/tui/components/task-board.js.map +0 -7
- package/dist/features/tui/index.js +0 -408
- package/dist/features/tui/index.js.map +0 -7
- package/dist/features/tui/services/data-service.js +0 -641
- package/dist/features/tui/services/data-service.js.map +0 -7
- package/dist/features/tui/services/linear-task-reader.js +0 -102
- package/dist/features/tui/services/linear-task-reader.js.map +0 -7
- package/dist/features/tui/services/websocket-client.js +0 -162
- package/dist/features/tui/services/websocket-client.js.map +0 -7
- package/dist/features/tui/terminal-compat.js +0 -220
- package/dist/features/tui/terminal-compat.js.map +0 -7
- package/dist/features/tui/types.js +0 -1
- 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
|
+
}
|