@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.
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +113 -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-manager.js +3 -0
- package/dist/core/context/frame-manager.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/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 +6 -18
- 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.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/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,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Management for StackMemory
|
|
3
|
+
* Provides session persistence and recovery across CLI invocations
|
|
4
|
+
*/
|
|
5
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { logger } from '../monitoring/logger.js';
|
|
9
|
+
import { SystemError, ErrorCode } from '../errors/index.js';
|
|
10
|
+
export var FrameQueryMode;
|
|
11
|
+
(function (FrameQueryMode) {
|
|
12
|
+
FrameQueryMode["CURRENT_SESSION"] = "current";
|
|
13
|
+
FrameQueryMode["PROJECT_ACTIVE"] = "project";
|
|
14
|
+
FrameQueryMode["ALL_ACTIVE"] = "all";
|
|
15
|
+
FrameQueryMode["HISTORICAL"] = "historical";
|
|
16
|
+
})(FrameQueryMode || (FrameQueryMode = {}));
|
|
17
|
+
export class SessionManager {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.currentSession = null;
|
|
20
|
+
this.STALE_THRESHOLD = 24 * 60 * 60 * 1000; // 24 hours
|
|
21
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
22
|
+
this.sessionsDir = path.join(homeDir, '.stackmemory', 'sessions');
|
|
23
|
+
}
|
|
24
|
+
static getInstance() {
|
|
25
|
+
if (!SessionManager.instance) {
|
|
26
|
+
SessionManager.instance = new SessionManager();
|
|
27
|
+
}
|
|
28
|
+
return SessionManager.instance;
|
|
29
|
+
}
|
|
30
|
+
async initialize() {
|
|
31
|
+
try {
|
|
32
|
+
await fs.mkdir(this.sessionsDir, { recursive: true });
|
|
33
|
+
await fs.mkdir(path.join(this.sessionsDir, 'projects'), {
|
|
34
|
+
recursive: true,
|
|
35
|
+
});
|
|
36
|
+
await fs.mkdir(path.join(this.sessionsDir, 'history'), {
|
|
37
|
+
recursive: true,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new SystemError('Failed to initialize session directories', ErrorCode.INITIALIZATION_ERROR, { error, sessionsDir: this.sessionsDir });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async getOrCreateSession(options) {
|
|
45
|
+
// 1. Check explicit session ID
|
|
46
|
+
if (options?.sessionId) {
|
|
47
|
+
const session = await this.loadSession(options.sessionId);
|
|
48
|
+
if (session) {
|
|
49
|
+
this.currentSession = session;
|
|
50
|
+
return session;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// 2. Check environment variable
|
|
54
|
+
const envSessionId = process.env.STACKMEMORY_SESSION;
|
|
55
|
+
if (envSessionId) {
|
|
56
|
+
const session = await this.loadSession(envSessionId);
|
|
57
|
+
if (session) {
|
|
58
|
+
this.currentSession = session;
|
|
59
|
+
return session;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 3. Check project + branch context
|
|
63
|
+
const projectHash = await this.getProjectHash(options?.projectPath);
|
|
64
|
+
const branch = options?.branch || (await this.getGitBranch(options?.projectPath));
|
|
65
|
+
if (projectHash) {
|
|
66
|
+
// Try project+branch session
|
|
67
|
+
const branchSession = await this.findProjectBranchSession(projectHash, branch);
|
|
68
|
+
if (branchSession && this.isSessionRecent(branchSession)) {
|
|
69
|
+
await this.touchSession(branchSession);
|
|
70
|
+
this.currentSession = branchSession;
|
|
71
|
+
return branchSession;
|
|
72
|
+
}
|
|
73
|
+
// Try last active for project
|
|
74
|
+
const lastActive = await this.findLastActiveSession(projectHash);
|
|
75
|
+
if (lastActive && this.isSessionRecent(lastActive)) {
|
|
76
|
+
await this.touchSession(lastActive);
|
|
77
|
+
this.currentSession = lastActive;
|
|
78
|
+
return lastActive;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 4. Create new session
|
|
82
|
+
const newSession = await this.createSession({
|
|
83
|
+
projectId: projectHash || 'global',
|
|
84
|
+
branch,
|
|
85
|
+
metadata: options?.metadata,
|
|
86
|
+
});
|
|
87
|
+
this.currentSession = newSession;
|
|
88
|
+
return newSession;
|
|
89
|
+
}
|
|
90
|
+
async createSession(params) {
|
|
91
|
+
const session = {
|
|
92
|
+
sessionId: uuidv4(),
|
|
93
|
+
runId: uuidv4(),
|
|
94
|
+
projectId: params.projectId,
|
|
95
|
+
branch: params.branch,
|
|
96
|
+
startedAt: Date.now(),
|
|
97
|
+
lastActiveAt: Date.now(),
|
|
98
|
+
metadata: {
|
|
99
|
+
...params.metadata,
|
|
100
|
+
user: process.env.USER,
|
|
101
|
+
environment: process.env.NODE_ENV || 'development',
|
|
102
|
+
cliVersion: process.env.npm_package_version,
|
|
103
|
+
},
|
|
104
|
+
state: 'active',
|
|
105
|
+
};
|
|
106
|
+
await this.saveSession(session);
|
|
107
|
+
await this.setProjectActiveSession(params.projectId, session.sessionId);
|
|
108
|
+
// Set as current session
|
|
109
|
+
this.currentSession = session;
|
|
110
|
+
logger.info('Created new session', {
|
|
111
|
+
sessionId: session.sessionId,
|
|
112
|
+
projectId: session.projectId,
|
|
113
|
+
branch: session.branch,
|
|
114
|
+
});
|
|
115
|
+
return session;
|
|
116
|
+
}
|
|
117
|
+
async loadSession(sessionId) {
|
|
118
|
+
try {
|
|
119
|
+
const sessionPath = path.join(this.sessionsDir, `${sessionId}.json`);
|
|
120
|
+
const data = await fs.readFile(sessionPath, 'utf-8');
|
|
121
|
+
return JSON.parse(data);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// Check history
|
|
125
|
+
try {
|
|
126
|
+
const historyPath = path.join(this.sessionsDir, 'history', `${sessionId}.json`);
|
|
127
|
+
const data = await fs.readFile(historyPath, 'utf-8');
|
|
128
|
+
return JSON.parse(data);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async saveSession(session) {
|
|
136
|
+
const sessionPath = path.join(this.sessionsDir, `${session.sessionId}.json`);
|
|
137
|
+
await fs.writeFile(sessionPath, JSON.stringify(session, null, 2));
|
|
138
|
+
}
|
|
139
|
+
async suspendSession(sessionId) {
|
|
140
|
+
const id = sessionId || this.currentSession?.sessionId;
|
|
141
|
+
if (!id)
|
|
142
|
+
return;
|
|
143
|
+
const session = await this.loadSession(id);
|
|
144
|
+
if (session) {
|
|
145
|
+
session.state = 'suspended';
|
|
146
|
+
session.lastActiveAt = Date.now();
|
|
147
|
+
await this.saveSession(session);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async resumeSession(sessionId) {
|
|
151
|
+
const session = await this.loadSession(sessionId);
|
|
152
|
+
if (!session) {
|
|
153
|
+
throw new SystemError('Session not found', ErrorCode.NOT_FOUND, {
|
|
154
|
+
sessionId,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
session.state = 'active';
|
|
158
|
+
session.lastActiveAt = Date.now();
|
|
159
|
+
await this.saveSession(session);
|
|
160
|
+
this.currentSession = session;
|
|
161
|
+
return session;
|
|
162
|
+
}
|
|
163
|
+
async closeSession(sessionId) {
|
|
164
|
+
const id = sessionId || this.currentSession?.sessionId;
|
|
165
|
+
if (!id)
|
|
166
|
+
return;
|
|
167
|
+
const session = await this.loadSession(id);
|
|
168
|
+
if (session) {
|
|
169
|
+
session.state = 'closed';
|
|
170
|
+
session.lastActiveAt = Date.now();
|
|
171
|
+
// Move to history
|
|
172
|
+
const sessionPath = path.join(this.sessionsDir, `${session.sessionId}.json`);
|
|
173
|
+
const historyPath = path.join(this.sessionsDir, 'history', `${session.sessionId}.json`);
|
|
174
|
+
await fs.rename(sessionPath, historyPath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async listSessions(filter) {
|
|
178
|
+
const sessions = [];
|
|
179
|
+
// Load active sessions
|
|
180
|
+
const files = await fs.readdir(this.sessionsDir);
|
|
181
|
+
for (const file of files) {
|
|
182
|
+
if (file.endsWith('.json')) {
|
|
183
|
+
const session = await this.loadSession(file.replace('.json', ''));
|
|
184
|
+
if (session) {
|
|
185
|
+
sessions.push(session);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Apply filters
|
|
190
|
+
return sessions.filter((s) => {
|
|
191
|
+
if (filter?.projectId && s.projectId !== filter.projectId)
|
|
192
|
+
return false;
|
|
193
|
+
if (filter?.state && s.state !== filter.state)
|
|
194
|
+
return false;
|
|
195
|
+
if (filter?.branch && s.branch !== filter.branch)
|
|
196
|
+
return false;
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async mergeSessions(sourceId, targetId) {
|
|
201
|
+
const source = await this.loadSession(sourceId);
|
|
202
|
+
const target = await this.loadSession(targetId);
|
|
203
|
+
if (!source || !target) {
|
|
204
|
+
throw new SystemError('Session not found for merge', ErrorCode.NOT_FOUND, { sourceId, targetId });
|
|
205
|
+
}
|
|
206
|
+
// Merge metadata
|
|
207
|
+
target.metadata = {
|
|
208
|
+
...target.metadata,
|
|
209
|
+
...source.metadata,
|
|
210
|
+
tags: [...(target.metadata.tags || []), ...(source.metadata.tags || [])],
|
|
211
|
+
};
|
|
212
|
+
// Update timestamps
|
|
213
|
+
target.lastActiveAt = Date.now();
|
|
214
|
+
// Close source session
|
|
215
|
+
await this.closeSession(sourceId);
|
|
216
|
+
await this.saveSession(target);
|
|
217
|
+
logger.info('Merged sessions', {
|
|
218
|
+
source: sourceId,
|
|
219
|
+
target: targetId,
|
|
220
|
+
});
|
|
221
|
+
return target;
|
|
222
|
+
}
|
|
223
|
+
async cleanupStaleSessions(maxAge = 30 * 24 * 60 * 60 * 1000) {
|
|
224
|
+
const historyDir = path.join(this.sessionsDir, 'history');
|
|
225
|
+
const files = await fs.readdir(historyDir);
|
|
226
|
+
const cutoff = Date.now() - maxAge;
|
|
227
|
+
let cleaned = 0;
|
|
228
|
+
for (const file of files) {
|
|
229
|
+
if (file.endsWith('.json')) {
|
|
230
|
+
const filePath = path.join(historyDir, file);
|
|
231
|
+
const stats = await fs.stat(filePath);
|
|
232
|
+
if (stats.mtimeMs < cutoff) {
|
|
233
|
+
await fs.unlink(filePath);
|
|
234
|
+
cleaned++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
logger.info(`Cleaned up ${cleaned} stale sessions`);
|
|
239
|
+
return cleaned;
|
|
240
|
+
}
|
|
241
|
+
getCurrentSession() {
|
|
242
|
+
return this.currentSession;
|
|
243
|
+
}
|
|
244
|
+
getSessionRunId() {
|
|
245
|
+
return this.currentSession?.runId || uuidv4();
|
|
246
|
+
}
|
|
247
|
+
async getProjectHash(projectPath) {
|
|
248
|
+
try {
|
|
249
|
+
const cwd = projectPath || process.cwd();
|
|
250
|
+
const pathModule = await import('path');
|
|
251
|
+
// Try to get git remote first (consistent with project-manager)
|
|
252
|
+
let identifier;
|
|
253
|
+
try {
|
|
254
|
+
const { execSync } = await import('child_process');
|
|
255
|
+
identifier = execSync('git config --get remote.origin.url', {
|
|
256
|
+
cwd,
|
|
257
|
+
encoding: 'utf-8',
|
|
258
|
+
timeout: 5000,
|
|
259
|
+
}).trim();
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Fall back to directory path
|
|
263
|
+
identifier = cwd;
|
|
264
|
+
}
|
|
265
|
+
// Use same algorithm as project-manager.generateProjectId
|
|
266
|
+
const cleaned = identifier
|
|
267
|
+
.replace(/\.git$/, '')
|
|
268
|
+
.replace(/[^a-zA-Z0-9-]/g, '-')
|
|
269
|
+
.toLowerCase();
|
|
270
|
+
return cleaned.substring(cleaned.length - 50);
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async getGitBranch(projectPath) {
|
|
277
|
+
try {
|
|
278
|
+
const { execSync } = await import('child_process');
|
|
279
|
+
const cwd = projectPath || process.cwd();
|
|
280
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
281
|
+
cwd,
|
|
282
|
+
encoding: 'utf-8',
|
|
283
|
+
}).trim();
|
|
284
|
+
return branch;
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async findProjectBranchSession(projectHash, branch) {
|
|
291
|
+
if (!branch)
|
|
292
|
+
return null;
|
|
293
|
+
const sessions = await this.listSessions({
|
|
294
|
+
projectId: projectHash,
|
|
295
|
+
state: 'active',
|
|
296
|
+
branch,
|
|
297
|
+
});
|
|
298
|
+
return sessions.sort((a, b) => b.lastActiveAt - a.lastActiveAt)[0] || null;
|
|
299
|
+
}
|
|
300
|
+
async findLastActiveSession(projectHash) {
|
|
301
|
+
const sessions = await this.listSessions({
|
|
302
|
+
projectId: projectHash,
|
|
303
|
+
state: 'active',
|
|
304
|
+
});
|
|
305
|
+
return sessions.sort((a, b) => b.lastActiveAt - a.lastActiveAt)[0] || null;
|
|
306
|
+
}
|
|
307
|
+
async setProjectActiveSession(projectId, sessionId) {
|
|
308
|
+
const projectFile = path.join(this.sessionsDir, 'projects', `${projectId}.json`);
|
|
309
|
+
await fs.writeFile(projectFile, JSON.stringify({
|
|
310
|
+
projectId,
|
|
311
|
+
activeSessionId: sessionId,
|
|
312
|
+
updatedAt: Date.now(),
|
|
313
|
+
}, null, 2));
|
|
314
|
+
}
|
|
315
|
+
isSessionRecent(session) {
|
|
316
|
+
return Date.now() - session.lastActiveAt < this.STALE_THRESHOLD;
|
|
317
|
+
}
|
|
318
|
+
async touchSession(session) {
|
|
319
|
+
session.lastActiveAt = Date.now();
|
|
320
|
+
await this.saveSession(session);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
export const sessionManager = SessionManager.getInstance();
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Command Trace Wrapper
|
|
3
|
+
* Automatically wraps Commander.js commands with comprehensive tracing
|
|
4
|
+
*/
|
|
5
|
+
import { trace } from './debug-trace.js';
|
|
6
|
+
import { logger } from '../monitoring/logger.js';
|
|
7
|
+
export function wrapCommand(command) {
|
|
8
|
+
const originalAction = command.action.bind(command);
|
|
9
|
+
command.action(async function (...args) {
|
|
10
|
+
// Extract command path and options
|
|
11
|
+
const commandPath = getCommandPath(command);
|
|
12
|
+
const options = args[args.length - 1];
|
|
13
|
+
const commandArgs = args.slice(0, -1);
|
|
14
|
+
// Build comprehensive context
|
|
15
|
+
const context = {
|
|
16
|
+
command: commandPath,
|
|
17
|
+
args: commandArgs,
|
|
18
|
+
options: typeof options === 'object' ? options : {},
|
|
19
|
+
cwd: process.cwd(),
|
|
20
|
+
env: {
|
|
21
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
22
|
+
DEBUG_TRACE: process.env.DEBUG_TRACE,
|
|
23
|
+
LINEAR_API_KEY: process.env.LINEAR_API_KEY ? '[SET]' : '[NOT SET]',
|
|
24
|
+
},
|
|
25
|
+
timestamp: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
// Log command start
|
|
28
|
+
logger.info(`CLI Command: ${commandPath}`, context);
|
|
29
|
+
// Wrap the actual action with tracing
|
|
30
|
+
await trace.command(commandPath, context, async () => {
|
|
31
|
+
try {
|
|
32
|
+
// Call the original action with wrapped handler
|
|
33
|
+
const result = await originalAction.apply(null, args);
|
|
34
|
+
// Log successful completion
|
|
35
|
+
logger.info(`CLI Command Completed: ${commandPath}`, {
|
|
36
|
+
duration: trace.exportTraces().find(t => t.name === commandPath)?.duration,
|
|
37
|
+
});
|
|
38
|
+
// Show execution summary if verbose
|
|
39
|
+
if (process.env.DEBUG_TRACE === 'true') {
|
|
40
|
+
console.log(trace.getExecutionSummary());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// Enhanced error logging for CLI commands
|
|
45
|
+
logger.error(`CLI Command Failed: ${commandPath}`, error, context);
|
|
46
|
+
// Get the last error trace for debugging
|
|
47
|
+
const lastError = trace.getLastError();
|
|
48
|
+
if (lastError) {
|
|
49
|
+
console.error('\nš Error occurred at:');
|
|
50
|
+
console.error(` ${lastError.name}`);
|
|
51
|
+
if (lastError.params) {
|
|
52
|
+
console.error(' With params:', JSON.stringify(lastError.params, null, 2));
|
|
53
|
+
}
|
|
54
|
+
console.error(' Error details:', lastError.error);
|
|
55
|
+
}
|
|
56
|
+
// Re-throw to maintain original error handling
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
// Recursively wrap subcommands
|
|
62
|
+
command.commands.forEach(subcommand => {
|
|
63
|
+
wrapCommand(subcommand);
|
|
64
|
+
});
|
|
65
|
+
return command;
|
|
66
|
+
}
|
|
67
|
+
function getCommandPath(command) {
|
|
68
|
+
const parts = [];
|
|
69
|
+
let current = command;
|
|
70
|
+
while (current) {
|
|
71
|
+
if (current.name()) {
|
|
72
|
+
parts.unshift(current.name());
|
|
73
|
+
}
|
|
74
|
+
current = current.parent;
|
|
75
|
+
}
|
|
76
|
+
return parts.join(' ');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Wrap the main program with comprehensive tracing
|
|
80
|
+
*/
|
|
81
|
+
export function wrapProgram(program) {
|
|
82
|
+
// Add global error handler with tracing
|
|
83
|
+
program.exitOverride((err) => {
|
|
84
|
+
if (err.code === 'commander.help' || err.code === 'commander.version') {
|
|
85
|
+
// Normal help/version display, not an error
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
// Log the error with full context
|
|
89
|
+
logger.error('CLI Error', err, {
|
|
90
|
+
code: err.code,
|
|
91
|
+
exitCode: err.exitCode,
|
|
92
|
+
command: process.argv.slice(2).join(' '),
|
|
93
|
+
});
|
|
94
|
+
// Show trace summary on error
|
|
95
|
+
if (process.env.DEBUG_TRACE === 'true') {
|
|
96
|
+
console.error('\n' + trace.getExecutionSummary());
|
|
97
|
+
}
|
|
98
|
+
process.exit(err.exitCode || 1);
|
|
99
|
+
});
|
|
100
|
+
// Add pre-action hook for setup
|
|
101
|
+
program.hook('preAction', (thisCommand) => {
|
|
102
|
+
// Initialize trace context for this command
|
|
103
|
+
trace.reset();
|
|
104
|
+
// Log command invocation
|
|
105
|
+
const commandPath = getCommandPath(thisCommand);
|
|
106
|
+
logger.debug(`Preparing to execute: ${commandPath}`, {
|
|
107
|
+
args: thisCommand.args,
|
|
108
|
+
opts: thisCommand.opts(),
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
// Add post-action hook for cleanup
|
|
112
|
+
program.hook('postAction', (thisCommand) => {
|
|
113
|
+
// Log completion
|
|
114
|
+
const commandPath = getCommandPath(thisCommand);
|
|
115
|
+
logger.debug(`Completed execution: ${commandPath}`);
|
|
116
|
+
});
|
|
117
|
+
// Wrap all existing commands
|
|
118
|
+
program.commands.forEach(command => {
|
|
119
|
+
wrapCommand(command);
|
|
120
|
+
});
|
|
121
|
+
return program;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Helper to wrap async functions with step tracing
|
|
125
|
+
*/
|
|
126
|
+
export function traceStep(name, fn) {
|
|
127
|
+
return trace.step(name, fn);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Helper to wrap database queries
|
|
131
|
+
*/
|
|
132
|
+
export function traceQuery(sql, params, fn) {
|
|
133
|
+
return trace.traceSync('query', sql.substring(0, 100), params, fn);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Helper to wrap API calls
|
|
137
|
+
*/
|
|
138
|
+
export function traceAPI(method, url, body, fn) {
|
|
139
|
+
return trace.api(method, url, body, fn);
|
|
140
|
+
}
|