@stackmemoryai/stackmemory 0.5.0 → 0.5.2
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/config.js +81 -0
- package/dist/cli/commands/config.js.map +2 -2
- package/dist/cli/commands/decision.js +262 -0
- package/dist/cli/commands/decision.js.map +7 -0
- package/dist/cli/commands/handoff.js +87 -24
- package/dist/cli/commands/handoff.js.map +3 -3
- package/dist/cli/commands/service.js +684 -0
- package/dist/cli/commands/service.js.map +7 -0
- package/dist/cli/commands/sweep.js +311 -0
- package/dist/cli/commands/sweep.js.map +7 -0
- package/dist/cli/index.js +98 -4
- package/dist/cli/index.js.map +2 -2
- package/dist/cli/streamlined-cli.js +144 -0
- package/dist/cli/streamlined-cli.js.map +7 -0
- package/dist/core/config/storage-config.js +111 -0
- package/dist/core/config/storage-config.js.map +7 -0
- package/dist/core/events/event-bus.js +110 -0
- package/dist/core/events/event-bus.js.map +7 -0
- package/dist/core/plugins/plugin-interface.js +87 -0
- package/dist/core/plugins/plugin-interface.js.map +7 -0
- package/dist/core/session/enhanced-handoff.js +654 -0
- package/dist/core/session/enhanced-handoff.js.map +7 -0
- package/dist/core/storage/simplified-storage.js +328 -0
- package/dist/core/storage/simplified-storage.js.map +7 -0
- package/dist/daemon/session-daemon.js +308 -0
- package/dist/daemon/session-daemon.js.map +7 -0
- package/dist/plugins/linear/index.js +166 -0
- package/dist/plugins/linear/index.js.map +7 -0
- package/dist/plugins/loader.js +57 -0
- package/dist/plugins/loader.js.map +7 -0
- package/dist/plugins/plugin-interface.js +67 -0
- package/dist/plugins/plugin-interface.js.map +7 -0
- package/dist/plugins/ralph/simple-ralph-plugin.js +305 -0
- package/dist/plugins/ralph/simple-ralph-plugin.js.map +7 -0
- package/dist/plugins/ralph/use-cases/code-generator.js +151 -0
- package/dist/plugins/ralph/use-cases/code-generator.js.map +7 -0
- package/dist/plugins/ralph/use-cases/test-generator.js +201 -0
- package/dist/plugins/ralph/use-cases/test-generator.js.map +7 -0
- package/dist/skills/repo-ingestion-skill.js +54 -10
- package/dist/skills/repo-ingestion-skill.js.map +2 -2
- package/package.json +4 -8
- package/scripts/archive/check-all-duplicates.ts +2 -2
- package/scripts/archive/merge-linear-duplicates.ts +6 -4
- package/scripts/install-claude-hooks-auto.js +72 -15
- package/scripts/measure-handoff-impact.mjs +395 -0
- package/scripts/measure-handoff-impact.ts +450 -0
- package/templates/claude-hooks/on-startup.js +200 -19
- package/templates/services/com.stackmemory.guardian.plist +59 -0
- package/templates/services/stackmemory-guardian.service +41 -0
- package/scripts/testing/results/real-performance-results.json +0 -90
- package/scripts/testing/test-tier-migration.js +0 -100
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
class SessionDaemon {
|
|
6
|
+
config;
|
|
7
|
+
state;
|
|
8
|
+
stackmemoryDir;
|
|
9
|
+
sessionsDir;
|
|
10
|
+
logsDir;
|
|
11
|
+
pidFile;
|
|
12
|
+
heartbeatFile;
|
|
13
|
+
logFile;
|
|
14
|
+
saveInterval = null;
|
|
15
|
+
heartbeatInterval = null;
|
|
16
|
+
activityCheckInterval = null;
|
|
17
|
+
isShuttingDown = false;
|
|
18
|
+
constructor(sessionId2, options2) {
|
|
19
|
+
const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "";
|
|
20
|
+
this.stackmemoryDir = path.join(homeDir, ".stackmemory");
|
|
21
|
+
this.sessionsDir = path.join(this.stackmemoryDir, "sessions");
|
|
22
|
+
this.logsDir = path.join(this.stackmemoryDir, "logs");
|
|
23
|
+
this.config = {
|
|
24
|
+
sessionId: sessionId2,
|
|
25
|
+
saveIntervalMs: options2?.saveIntervalMs ?? 15 * 60 * 1e3,
|
|
26
|
+
inactivityTimeoutMs: options2?.inactivityTimeoutMs ?? 30 * 60 * 1e3,
|
|
27
|
+
heartbeatIntervalMs: options2?.heartbeatIntervalMs ?? 60 * 1e3
|
|
28
|
+
};
|
|
29
|
+
this.pidFile = path.join(this.sessionsDir, `${sessionId2}.pid`);
|
|
30
|
+
this.heartbeatFile = path.join(this.sessionsDir, `${sessionId2}.heartbeat`);
|
|
31
|
+
this.logFile = path.join(this.logsDir, "daemon.log");
|
|
32
|
+
this.state = {
|
|
33
|
+
startTime: Date.now(),
|
|
34
|
+
lastSaveTime: Date.now(),
|
|
35
|
+
lastActivityTime: Date.now(),
|
|
36
|
+
saveCount: 0,
|
|
37
|
+
errors: []
|
|
38
|
+
};
|
|
39
|
+
this.ensureDirectories();
|
|
40
|
+
}
|
|
41
|
+
ensureDirectories() {
|
|
42
|
+
[this.sessionsDir, this.logsDir].forEach((dir) => {
|
|
43
|
+
if (!fs.existsSync(dir)) {
|
|
44
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
log(level, message, data) {
|
|
49
|
+
const entry = {
|
|
50
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51
|
+
level,
|
|
52
|
+
sessionId: this.config.sessionId,
|
|
53
|
+
message,
|
|
54
|
+
data
|
|
55
|
+
};
|
|
56
|
+
const logLine = JSON.stringify(entry) + "\n";
|
|
57
|
+
try {
|
|
58
|
+
fs.appendFileSync(this.logFile, logLine);
|
|
59
|
+
} catch {
|
|
60
|
+
console.error(`[${entry.timestamp}] ${level}: ${message}`, data);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
checkIdempotency() {
|
|
64
|
+
if (fs.existsSync(this.pidFile)) {
|
|
65
|
+
try {
|
|
66
|
+
const existingPid = fs.readFileSync(this.pidFile, "utf8").trim();
|
|
67
|
+
const pid = parseInt(existingPid, 10);
|
|
68
|
+
try {
|
|
69
|
+
process.kill(pid, 0);
|
|
70
|
+
this.log("WARN", "Daemon already running for this session", {
|
|
71
|
+
existingPid: pid
|
|
72
|
+
});
|
|
73
|
+
return false;
|
|
74
|
+
} catch {
|
|
75
|
+
this.log("INFO", "Cleaning up stale PID file", { stalePid: pid });
|
|
76
|
+
fs.unlinkSync(this.pidFile);
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
try {
|
|
80
|
+
fs.unlinkSync(this.pidFile);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
writePidFile() {
|
|
88
|
+
fs.writeFileSync(this.pidFile, process.pid.toString());
|
|
89
|
+
this.log("INFO", "PID file created", {
|
|
90
|
+
pid: process.pid,
|
|
91
|
+
file: this.pidFile
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
updateHeartbeat() {
|
|
95
|
+
const heartbeatData = {
|
|
96
|
+
pid: process.pid,
|
|
97
|
+
sessionId: this.config.sessionId,
|
|
98
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
99
|
+
uptime: Date.now() - this.state.startTime,
|
|
100
|
+
saveCount: this.state.saveCount,
|
|
101
|
+
lastSaveTime: new Date(this.state.lastSaveTime).toISOString()
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
fs.writeFileSync(
|
|
105
|
+
this.heartbeatFile,
|
|
106
|
+
JSON.stringify(heartbeatData, null, 2)
|
|
107
|
+
);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
this.log("ERROR", "Failed to update heartbeat file", {
|
|
110
|
+
error: String(err)
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
saveContext() {
|
|
115
|
+
if (this.isShuttingDown) return;
|
|
116
|
+
try {
|
|
117
|
+
const stackmemoryBin = path.join(
|
|
118
|
+
this.stackmemoryDir,
|
|
119
|
+
"bin",
|
|
120
|
+
"stackmemory"
|
|
121
|
+
);
|
|
122
|
+
if (!fs.existsSync(stackmemoryBin)) {
|
|
123
|
+
this.log("WARN", "StackMemory binary not found", {
|
|
124
|
+
path: stackmemoryBin
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const message = `Auto-checkpoint #${this.state.saveCount + 1} at ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
129
|
+
execSync(`"${stackmemoryBin}" context add observation "${message}"`, {
|
|
130
|
+
timeout: 3e4,
|
|
131
|
+
encoding: "utf8",
|
|
132
|
+
stdio: "pipe"
|
|
133
|
+
});
|
|
134
|
+
this.state.saveCount++;
|
|
135
|
+
this.state.lastSaveTime = Date.now();
|
|
136
|
+
this.log("INFO", "Context saved successfully", {
|
|
137
|
+
saveCount: this.state.saveCount,
|
|
138
|
+
intervalMs: this.config.saveIntervalMs
|
|
139
|
+
});
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
142
|
+
if (!errorMsg.includes("EBUSY") && !errorMsg.includes("EAGAIN")) {
|
|
143
|
+
this.state.errors.push(errorMsg);
|
|
144
|
+
this.log("WARN", "Failed to save context", { error: errorMsg });
|
|
145
|
+
}
|
|
146
|
+
if (this.state.errors.length > 50) {
|
|
147
|
+
this.log("ERROR", "Too many errors, initiating shutdown");
|
|
148
|
+
this.shutdown("too_many_errors");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
checkActivity() {
|
|
153
|
+
if (this.isShuttingDown) return;
|
|
154
|
+
const sessionFile = path.join(
|
|
155
|
+
this.stackmemoryDir,
|
|
156
|
+
"traces",
|
|
157
|
+
"current-session.json"
|
|
158
|
+
);
|
|
159
|
+
try {
|
|
160
|
+
if (fs.existsSync(sessionFile)) {
|
|
161
|
+
const stats = fs.statSync(sessionFile);
|
|
162
|
+
const lastModified = stats.mtimeMs;
|
|
163
|
+
if (lastModified > this.state.lastActivityTime) {
|
|
164
|
+
this.state.lastActivityTime = lastModified;
|
|
165
|
+
this.log("DEBUG", "Activity detected", {
|
|
166
|
+
lastModified: new Date(lastModified).toISOString()
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
const inactiveTime = Date.now() - this.state.lastActivityTime;
|
|
173
|
+
if (inactiveTime > this.config.inactivityTimeoutMs) {
|
|
174
|
+
this.log("INFO", "Inactivity timeout reached", {
|
|
175
|
+
inactiveTimeMs: inactiveTime,
|
|
176
|
+
timeoutMs: this.config.inactivityTimeoutMs
|
|
177
|
+
});
|
|
178
|
+
this.shutdown("inactivity_timeout");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
setupSignalHandlers() {
|
|
182
|
+
const handleSignal = (signal) => {
|
|
183
|
+
this.log("INFO", `Received ${signal}, shutting down gracefully`);
|
|
184
|
+
this.shutdown(signal.toLowerCase());
|
|
185
|
+
};
|
|
186
|
+
process.on("SIGTERM", () => handleSignal("SIGTERM"));
|
|
187
|
+
process.on("SIGINT", () => handleSignal("SIGINT"));
|
|
188
|
+
process.on("SIGHUP", () => handleSignal("SIGHUP"));
|
|
189
|
+
process.on("uncaughtException", (err) => {
|
|
190
|
+
this.log("ERROR", "Uncaught exception", {
|
|
191
|
+
error: err.message,
|
|
192
|
+
stack: err.stack
|
|
193
|
+
});
|
|
194
|
+
this.shutdown("uncaught_exception");
|
|
195
|
+
});
|
|
196
|
+
process.on("unhandledRejection", (reason) => {
|
|
197
|
+
this.log("ERROR", "Unhandled rejection", { reason: String(reason) });
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
cleanup() {
|
|
201
|
+
try {
|
|
202
|
+
if (fs.existsSync(this.pidFile)) {
|
|
203
|
+
fs.unlinkSync(this.pidFile);
|
|
204
|
+
this.log("INFO", "PID file removed");
|
|
205
|
+
}
|
|
206
|
+
} catch (e) {
|
|
207
|
+
this.log("WARN", "Failed to remove PID file", { error: String(e) });
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const finalHeartbeat = {
|
|
211
|
+
pid: process.pid,
|
|
212
|
+
sessionId: this.config.sessionId,
|
|
213
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
214
|
+
status: "shutdown",
|
|
215
|
+
uptime: Date.now() - this.state.startTime,
|
|
216
|
+
totalSaves: this.state.saveCount
|
|
217
|
+
};
|
|
218
|
+
fs.writeFileSync(
|
|
219
|
+
this.heartbeatFile,
|
|
220
|
+
JSON.stringify(finalHeartbeat, null, 2)
|
|
221
|
+
);
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
shutdown(reason) {
|
|
226
|
+
if (this.isShuttingDown) return;
|
|
227
|
+
this.isShuttingDown = true;
|
|
228
|
+
this.log("INFO", "Daemon shutting down", {
|
|
229
|
+
reason,
|
|
230
|
+
uptime: Date.now() - this.state.startTime,
|
|
231
|
+
totalSaves: this.state.saveCount,
|
|
232
|
+
errors: this.state.errors.length
|
|
233
|
+
});
|
|
234
|
+
if (this.saveInterval) {
|
|
235
|
+
clearInterval(this.saveInterval);
|
|
236
|
+
this.saveInterval = null;
|
|
237
|
+
}
|
|
238
|
+
if (this.heartbeatInterval) {
|
|
239
|
+
clearInterval(this.heartbeatInterval);
|
|
240
|
+
this.heartbeatInterval = null;
|
|
241
|
+
}
|
|
242
|
+
if (this.activityCheckInterval) {
|
|
243
|
+
clearInterval(this.activityCheckInterval);
|
|
244
|
+
this.activityCheckInterval = null;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
this.saveContext();
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
this.cleanup();
|
|
251
|
+
process.exit(
|
|
252
|
+
reason === "inactivity_timeout" || reason === "sigterm" ? 0 : 1
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
start() {
|
|
256
|
+
if (!this.checkIdempotency()) {
|
|
257
|
+
this.log("INFO", "Exiting - daemon already running");
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
this.writePidFile();
|
|
261
|
+
this.setupSignalHandlers();
|
|
262
|
+
this.log("INFO", "Session daemon started", {
|
|
263
|
+
sessionId: this.config.sessionId,
|
|
264
|
+
pid: process.pid,
|
|
265
|
+
saveIntervalMs: this.config.saveIntervalMs,
|
|
266
|
+
inactivityTimeoutMs: this.config.inactivityTimeoutMs
|
|
267
|
+
});
|
|
268
|
+
this.updateHeartbeat();
|
|
269
|
+
this.heartbeatInterval = setInterval(() => {
|
|
270
|
+
this.updateHeartbeat();
|
|
271
|
+
}, this.config.heartbeatIntervalMs);
|
|
272
|
+
this.saveInterval = setInterval(() => {
|
|
273
|
+
this.saveContext();
|
|
274
|
+
}, this.config.saveIntervalMs);
|
|
275
|
+
this.activityCheckInterval = setInterval(() => {
|
|
276
|
+
this.checkActivity();
|
|
277
|
+
}, 60 * 1e3);
|
|
278
|
+
this.saveContext();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function parseArgs() {
|
|
282
|
+
const args = process.argv.slice(2);
|
|
283
|
+
let sessionId2 = `session-${Date.now()}`;
|
|
284
|
+
const options2 = {};
|
|
285
|
+
for (let i = 0; i < args.length; i++) {
|
|
286
|
+
const arg = args[i];
|
|
287
|
+
if (arg === "--session-id" && args[i + 1]) {
|
|
288
|
+
sessionId2 = args[i + 1];
|
|
289
|
+
i++;
|
|
290
|
+
} else if (arg === "--save-interval" && args[i + 1]) {
|
|
291
|
+
options2.saveIntervalMs = parseInt(args[i + 1], 10) * 1e3;
|
|
292
|
+
i++;
|
|
293
|
+
} else if (arg === "--inactivity-timeout" && args[i + 1]) {
|
|
294
|
+
options2.inactivityTimeoutMs = parseInt(args[i + 1], 10) * 1e3;
|
|
295
|
+
i++;
|
|
296
|
+
} else if (arg === "--heartbeat-interval" && args[i + 1]) {
|
|
297
|
+
options2.heartbeatIntervalMs = parseInt(args[i + 1], 10) * 1e3;
|
|
298
|
+
i++;
|
|
299
|
+
} else if (!arg.startsWith("--")) {
|
|
300
|
+
sessionId2 = arg;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return { sessionId: sessionId2, options: options2 };
|
|
304
|
+
}
|
|
305
|
+
const { sessionId, options } = parseArgs();
|
|
306
|
+
const daemon = new SessionDaemon(sessionId, options);
|
|
307
|
+
daemon.start();
|
|
308
|
+
//# sourceMappingURL=session-daemon.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/daemon/session-daemon.ts"],
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Session Daemon for StackMemory\n *\n * Lightweight background daemon that:\n * - Saves context periodically (default: every 15 minutes)\n * - Auto-exits after 30 minutes of no Claude Code activity\n * - Updates heartbeat file to indicate liveness\n * - Logs to JSON structured log file\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { execSync } from 'child_process';\n\ninterface DaemonConfig {\n sessionId: string;\n saveIntervalMs: number;\n inactivityTimeoutMs: number;\n heartbeatIntervalMs: number;\n}\n\ninterface DaemonState {\n startTime: number;\n lastSaveTime: number;\n lastActivityTime: number;\n saveCount: number;\n errors: string[];\n}\n\ninterface LogEntry {\n timestamp: string;\n level: 'INFO' | 'WARN' | 'ERROR' | 'DEBUG';\n sessionId: string;\n message: string;\n data?: Record<string, unknown>;\n}\n\nclass SessionDaemon {\n private config: DaemonConfig;\n private state: DaemonState;\n private stackmemoryDir: string;\n private sessionsDir: string;\n private logsDir: string;\n private pidFile: string;\n private heartbeatFile: string;\n private logFile: string;\n\n private saveInterval: NodeJS.Timeout | null = null;\n private heartbeatInterval: NodeJS.Timeout | null = null;\n private activityCheckInterval: NodeJS.Timeout | null = null;\n private isShuttingDown = false;\n\n constructor(sessionId: string, options?: Partial<DaemonConfig>) {\n const homeDir = process.env['HOME'] || process.env['USERPROFILE'] || '';\n this.stackmemoryDir = path.join(homeDir, '.stackmemory');\n this.sessionsDir = path.join(this.stackmemoryDir, 'sessions');\n this.logsDir = path.join(this.stackmemoryDir, 'logs');\n\n this.config = {\n sessionId,\n saveIntervalMs: options?.saveIntervalMs ?? 15 * 60 * 1000,\n inactivityTimeoutMs: options?.inactivityTimeoutMs ?? 30 * 60 * 1000,\n heartbeatIntervalMs: options?.heartbeatIntervalMs ?? 60 * 1000,\n };\n\n this.pidFile = path.join(this.sessionsDir, `${sessionId}.pid`);\n this.heartbeatFile = path.join(this.sessionsDir, `${sessionId}.heartbeat`);\n this.logFile = path.join(this.logsDir, 'daemon.log');\n\n this.state = {\n startTime: Date.now(),\n lastSaveTime: Date.now(),\n lastActivityTime: Date.now(),\n saveCount: 0,\n errors: [],\n };\n\n this.ensureDirectories();\n }\n\n private ensureDirectories(): void {\n [this.sessionsDir, this.logsDir].forEach((dir) => {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n });\n }\n\n private log(\n level: LogEntry['level'],\n message: string,\n data?: Record<string, unknown>\n ): void {\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n sessionId: this.config.sessionId,\n message,\n data,\n };\n\n const logLine = JSON.stringify(entry) + '\\n';\n\n try {\n fs.appendFileSync(this.logFile, logLine);\n } catch {\n console.error(`[${entry.timestamp}] ${level}: ${message}`, data);\n }\n }\n\n private checkIdempotency(): boolean {\n if (fs.existsSync(this.pidFile)) {\n try {\n const existingPid = fs.readFileSync(this.pidFile, 'utf8').trim();\n const pid = parseInt(existingPid, 10);\n\n // Check if process is still running\n try {\n process.kill(pid, 0);\n // Process exists, daemon already running\n this.log('WARN', 'Daemon already running for this session', {\n existingPid: pid,\n });\n return false;\n } catch {\n // Process not running, stale PID file\n this.log('INFO', 'Cleaning up stale PID file', { stalePid: pid });\n fs.unlinkSync(this.pidFile);\n }\n } catch {\n try {\n fs.unlinkSync(this.pidFile);\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n return true;\n }\n\n private writePidFile(): void {\n fs.writeFileSync(this.pidFile, process.pid.toString());\n this.log('INFO', 'PID file created', {\n pid: process.pid,\n file: this.pidFile,\n });\n }\n\n private updateHeartbeat(): void {\n const heartbeatData = {\n pid: process.pid,\n sessionId: this.config.sessionId,\n timestamp: new Date().toISOString(),\n uptime: Date.now() - this.state.startTime,\n saveCount: this.state.saveCount,\n lastSaveTime: new Date(this.state.lastSaveTime).toISOString(),\n };\n\n try {\n fs.writeFileSync(\n this.heartbeatFile,\n JSON.stringify(heartbeatData, null, 2)\n );\n } catch (err) {\n this.log('ERROR', 'Failed to update heartbeat file', {\n error: String(err),\n });\n }\n }\n\n private saveContext(): void {\n if (this.isShuttingDown) return;\n\n try {\n const stackmemoryBin = path.join(\n this.stackmemoryDir,\n 'bin',\n 'stackmemory'\n );\n\n if (!fs.existsSync(stackmemoryBin)) {\n this.log('WARN', 'StackMemory binary not found', {\n path: stackmemoryBin,\n });\n return;\n }\n\n // Save context checkpoint using the context add command\n const message = `Auto-checkpoint #${this.state.saveCount + 1} at ${new Date().toISOString()}`;\n\n execSync(`\"${stackmemoryBin}\" context add observation \"${message}\"`, {\n timeout: 30000,\n encoding: 'utf8',\n stdio: 'pipe',\n });\n\n this.state.saveCount++;\n this.state.lastSaveTime = Date.now();\n\n this.log('INFO', 'Context saved successfully', {\n saveCount: this.state.saveCount,\n intervalMs: this.config.saveIntervalMs,\n });\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n\n // Only log if not a transient error - many save errors are expected when CLI is busy\n if (!errorMsg.includes('EBUSY') && !errorMsg.includes('EAGAIN')) {\n this.state.errors.push(errorMsg);\n this.log('WARN', 'Failed to save context', { error: errorMsg });\n }\n\n // If we have too many consecutive errors, consider shutting down\n if (this.state.errors.length > 50) {\n this.log('ERROR', 'Too many errors, initiating shutdown');\n this.shutdown('too_many_errors');\n }\n }\n }\n\n private checkActivity(): void {\n if (this.isShuttingDown) return;\n\n // Check for Claude Code activity by looking at the session file or heartbeat\n const sessionFile = path.join(\n this.stackmemoryDir,\n 'traces',\n 'current-session.json'\n );\n\n try {\n if (fs.existsSync(sessionFile)) {\n const stats = fs.statSync(sessionFile);\n const lastModified = stats.mtimeMs;\n\n // If session file was modified recently, update activity time\n if (lastModified > this.state.lastActivityTime) {\n this.state.lastActivityTime = lastModified;\n this.log('DEBUG', 'Activity detected', {\n lastModified: new Date(lastModified).toISOString(),\n });\n }\n }\n } catch {\n // Ignore errors checking activity\n }\n\n // Check if we've exceeded the inactivity timeout\n const inactiveTime = Date.now() - this.state.lastActivityTime;\n if (inactiveTime > this.config.inactivityTimeoutMs) {\n this.log('INFO', 'Inactivity timeout reached', {\n inactiveTimeMs: inactiveTime,\n timeoutMs: this.config.inactivityTimeoutMs,\n });\n this.shutdown('inactivity_timeout');\n }\n }\n\n private setupSignalHandlers(): void {\n const handleSignal = (signal: string) => {\n this.log('INFO', `Received ${signal}, shutting down gracefully`);\n this.shutdown(signal.toLowerCase());\n };\n\n process.on('SIGTERM', () => handleSignal('SIGTERM'));\n process.on('SIGINT', () => handleSignal('SIGINT'));\n process.on('SIGHUP', () => handleSignal('SIGHUP'));\n\n // Handle uncaught exceptions\n process.on('uncaughtException', (err) => {\n this.log('ERROR', 'Uncaught exception', {\n error: err.message,\n stack: err.stack,\n });\n this.shutdown('uncaught_exception');\n });\n\n process.on('unhandledRejection', (reason) => {\n this.log('ERROR', 'Unhandled rejection', { reason: String(reason) });\n });\n }\n\n private cleanup(): void {\n // Remove PID file\n try {\n if (fs.existsSync(this.pidFile)) {\n fs.unlinkSync(this.pidFile);\n this.log('INFO', 'PID file removed');\n }\n } catch (e) {\n this.log('WARN', 'Failed to remove PID file', { error: String(e) });\n }\n\n // Update heartbeat with shutdown status\n try {\n const finalHeartbeat = {\n pid: process.pid,\n sessionId: this.config.sessionId,\n timestamp: new Date().toISOString(),\n status: 'shutdown',\n uptime: Date.now() - this.state.startTime,\n totalSaves: this.state.saveCount,\n };\n fs.writeFileSync(\n this.heartbeatFile,\n JSON.stringify(finalHeartbeat, null, 2)\n );\n } catch {\n // Ignore errors updating final heartbeat\n }\n }\n\n private shutdown(reason: string): void {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n this.log('INFO', 'Daemon shutting down', {\n reason,\n uptime: Date.now() - this.state.startTime,\n totalSaves: this.state.saveCount,\n errors: this.state.errors.length,\n });\n\n // Clear all intervals\n if (this.saveInterval) {\n clearInterval(this.saveInterval);\n this.saveInterval = null;\n }\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = null;\n }\n if (this.activityCheckInterval) {\n clearInterval(this.activityCheckInterval);\n this.activityCheckInterval = null;\n }\n\n // Final context save before shutdown\n try {\n this.saveContext();\n } catch {\n // Ignore errors during final save\n }\n\n this.cleanup();\n\n // Exit with appropriate code\n process.exit(\n reason === 'inactivity_timeout' || reason === 'sigterm' ? 0 : 1\n );\n }\n\n public start(): void {\n // Check idempotency first\n if (!this.checkIdempotency()) {\n this.log('INFO', 'Exiting - daemon already running');\n process.exit(0);\n }\n\n // Write PID file\n this.writePidFile();\n\n // Setup signal handlers\n this.setupSignalHandlers();\n\n // Log startup\n this.log('INFO', 'Session daemon started', {\n sessionId: this.config.sessionId,\n pid: process.pid,\n saveIntervalMs: this.config.saveIntervalMs,\n inactivityTimeoutMs: this.config.inactivityTimeoutMs,\n });\n\n // Initial heartbeat\n this.updateHeartbeat();\n\n // Setup periodic tasks\n this.heartbeatInterval = setInterval(() => {\n this.updateHeartbeat();\n }, this.config.heartbeatIntervalMs);\n\n this.saveInterval = setInterval(() => {\n this.saveContext();\n }, this.config.saveIntervalMs);\n\n // Check activity every minute\n this.activityCheckInterval = setInterval(() => {\n this.checkActivity();\n }, 60 * 1000);\n\n // Initial context save\n this.saveContext();\n }\n}\n\n// Parse command line arguments\nfunction parseArgs(): { sessionId: string; options: Partial<DaemonConfig> } {\n const args = process.argv.slice(2);\n let sessionId = `session-${Date.now()}`;\n const options: Partial<DaemonConfig> = {};\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--session-id' && args[i + 1]) {\n sessionId = args[i + 1];\n i++;\n } else if (arg === '--save-interval' && args[i + 1]) {\n options.saveIntervalMs = parseInt(args[i + 1], 10) * 1000;\n i++;\n } else if (arg === '--inactivity-timeout' && args[i + 1]) {\n options.inactivityTimeoutMs = parseInt(args[i + 1], 10) * 1000;\n i++;\n } else if (arg === '--heartbeat-interval' && args[i + 1]) {\n options.heartbeatIntervalMs = parseInt(args[i + 1], 10) * 1000;\n i++;\n } else if (!arg.startsWith('--')) {\n sessionId = arg;\n }\n }\n\n return { sessionId, options };\n}\n\n// Main entry point\nconst { sessionId, options } = parseArgs();\nconst daemon = new SessionDaemon(sessionId, options);\ndaemon.start();\n"],
|
|
5
|
+
"mappings": ";AAYA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,SAAS,gBAAgB;AAyBzB,MAAM,cAAc;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,eAAsC;AAAA,EACtC,oBAA2C;AAAA,EAC3C,wBAA+C;AAAA,EAC/C,iBAAiB;AAAA,EAEzB,YAAYA,YAAmBC,UAAiC;AAC9D,UAAM,UAAU,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,aAAa,KAAK;AACrE,SAAK,iBAAiB,KAAK,KAAK,SAAS,cAAc;AACvD,SAAK,cAAc,KAAK,KAAK,KAAK,gBAAgB,UAAU;AAC5D,SAAK,UAAU,KAAK,KAAK,KAAK,gBAAgB,MAAM;AAEpD,SAAK,SAAS;AAAA,MACZ,WAAAD;AAAA,MACA,gBAAgBC,UAAS,kBAAkB,KAAK,KAAK;AAAA,MACrD,qBAAqBA,UAAS,uBAAuB,KAAK,KAAK;AAAA,MAC/D,qBAAqBA,UAAS,uBAAuB,KAAK;AAAA,IAC5D;AAEA,SAAK,UAAU,KAAK,KAAK,KAAK,aAAa,GAAGD,UAAS,MAAM;AAC7D,SAAK,gBAAgB,KAAK,KAAK,KAAK,aAAa,GAAGA,UAAS,YAAY;AACzE,SAAK,UAAU,KAAK,KAAK,KAAK,SAAS,YAAY;AAEnD,SAAK,QAAQ;AAAA,MACX,WAAW,KAAK,IAAI;AAAA,MACpB,cAAc,KAAK,IAAI;AAAA,MACvB,kBAAkB,KAAK,IAAI;AAAA,MAC3B,WAAW;AAAA,MACX,QAAQ,CAAC;AAAA,IACX;AAEA,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEQ,oBAA0B;AAChC,KAAC,KAAK,aAAa,KAAK,OAAO,EAAE,QAAQ,CAAC,QAAQ;AAChD,UAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AACvB,WAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,IACN,OACA,SACA,MACM;AACN,UAAM,QAAkB;AAAA,MACtB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA,WAAW,KAAK,OAAO;AAAA,MACvB;AAAA,MACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,UAAU,KAAK,IAAI;AAExC,QAAI;AACF,SAAG,eAAe,KAAK,SAAS,OAAO;AAAA,IACzC,QAAQ;AACN,cAAQ,MAAM,IAAI,MAAM,SAAS,KAAK,KAAK,KAAK,OAAO,IAAI,IAAI;AAAA,IACjE;AAAA,EACF;AAAA,EAEQ,mBAA4B;AAClC,QAAI,GAAG,WAAW,KAAK,OAAO,GAAG;AAC/B,UAAI;AACF,cAAM,cAAc,GAAG,aAAa,KAAK,SAAS,MAAM,EAAE,KAAK;AAC/D,cAAM,MAAM,SAAS,aAAa,EAAE;AAGpC,YAAI;AACF,kBAAQ,KAAK,KAAK,CAAC;AAEnB,eAAK,IAAI,QAAQ,2CAA2C;AAAA,YAC1D,aAAa;AAAA,UACf,CAAC;AACD,iBAAO;AAAA,QACT,QAAQ;AAEN,eAAK,IAAI,QAAQ,8BAA8B,EAAE,UAAU,IAAI,CAAC;AAChE,aAAG,WAAW,KAAK,OAAO;AAAA,QAC5B;AAAA,MACF,QAAQ;AACN,YAAI;AACF,aAAG,WAAW,KAAK,OAAO;AAAA,QAC5B,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAqB;AAC3B,OAAG,cAAc,KAAK,SAAS,QAAQ,IAAI,SAAS,CAAC;AACrD,SAAK,IAAI,QAAQ,oBAAoB;AAAA,MACnC,KAAK,QAAQ;AAAA,MACb,MAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEQ,kBAAwB;AAC9B,UAAM,gBAAgB;AAAA,MACpB,KAAK,QAAQ;AAAA,MACb,WAAW,KAAK,OAAO;AAAA,MACvB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,QAAQ,KAAK,IAAI,IAAI,KAAK,MAAM;AAAA,MAChC,WAAW,KAAK,MAAM;AAAA,MACtB,cAAc,IAAI,KAAK,KAAK,MAAM,YAAY,EAAE,YAAY;AAAA,IAC9D;AAEA,QAAI;AACF,SAAG;AAAA,QACD,KAAK;AAAA,QACL,KAAK,UAAU,eAAe,MAAM,CAAC;AAAA,MACvC;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,mCAAmC;AAAA,QACnD,OAAO,OAAO,GAAG;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,cAAoB;AAC1B,QAAI,KAAK,eAAgB;AAEzB,QAAI;AACF,YAAM,iBAAiB,KAAK;AAAA,QAC1B,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAEA,UAAI,CAAC,GAAG,WAAW,cAAc,GAAG;AAClC,aAAK,IAAI,QAAQ,gCAAgC;AAAA,UAC/C,MAAM;AAAA,QACR,CAAC;AACD;AAAA,MACF;AAGA,YAAM,UAAU,oBAAoB,KAAK,MAAM,YAAY,CAAC,QAAO,oBAAI,KAAK,GAAE,YAAY,CAAC;AAE3F,eAAS,IAAI,cAAc,8BAA8B,OAAO,KAAK;AAAA,QACnE,SAAS;AAAA,QACT,UAAU;AAAA,QACV,OAAO;AAAA,MACT,CAAC;AAED,WAAK,MAAM;AACX,WAAK,MAAM,eAAe,KAAK,IAAI;AAEnC,WAAK,IAAI,QAAQ,8BAA8B;AAAA,QAC7C,WAAW,KAAK,MAAM;AAAA,QACtB,YAAY,KAAK,OAAO;AAAA,MAC1B,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAGhE,UAAI,CAAC,SAAS,SAAS,OAAO,KAAK,CAAC,SAAS,SAAS,QAAQ,GAAG;AAC/D,aAAK,MAAM,OAAO,KAAK,QAAQ;AAC/B,aAAK,IAAI,QAAQ,0BAA0B,EAAE,OAAO,SAAS,CAAC;AAAA,MAChE;AAGA,UAAI,KAAK,MAAM,OAAO,SAAS,IAAI;AACjC,aAAK,IAAI,SAAS,sCAAsC;AACxD,aAAK,SAAS,iBAAiB;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,KAAK,eAAgB;AAGzB,UAAM,cAAc,KAAK;AAAA,MACvB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAEA,QAAI;AACF,UAAI,GAAG,WAAW,WAAW,GAAG;AAC9B,cAAM,QAAQ,GAAG,SAAS,WAAW;AACrC,cAAM,eAAe,MAAM;AAG3B,YAAI,eAAe,KAAK,MAAM,kBAAkB;AAC9C,eAAK,MAAM,mBAAmB;AAC9B,eAAK,IAAI,SAAS,qBAAqB;AAAA,YACrC,cAAc,IAAI,KAAK,YAAY,EAAE,YAAY;AAAA,UACnD,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAGA,UAAM,eAAe,KAAK,IAAI,IAAI,KAAK,MAAM;AAC7C,QAAI,eAAe,KAAK,OAAO,qBAAqB;AAClD,WAAK,IAAI,QAAQ,8BAA8B;AAAA,QAC7C,gBAAgB;AAAA,QAChB,WAAW,KAAK,OAAO;AAAA,MACzB,CAAC;AACD,WAAK,SAAS,oBAAoB;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,sBAA4B;AAClC,UAAM,eAAe,CAAC,WAAmB;AACvC,WAAK,IAAI,QAAQ,YAAY,MAAM,4BAA4B;AAC/D,WAAK,SAAS,OAAO,YAAY,CAAC;AAAA,IACpC;AAEA,YAAQ,GAAG,WAAW,MAAM,aAAa,SAAS,CAAC;AACnD,YAAQ,GAAG,UAAU,MAAM,aAAa,QAAQ,CAAC;AACjD,YAAQ,GAAG,UAAU,MAAM,aAAa,QAAQ,CAAC;AAGjD,YAAQ,GAAG,qBAAqB,CAAC,QAAQ;AACvC,WAAK,IAAI,SAAS,sBAAsB;AAAA,QACtC,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,MACb,CAAC;AACD,WAAK,SAAS,oBAAoB;AAAA,IACpC,CAAC;AAED,YAAQ,GAAG,sBAAsB,CAAC,WAAW;AAC3C,WAAK,IAAI,SAAS,uBAAuB,EAAE,QAAQ,OAAO,MAAM,EAAE,CAAC;AAAA,IACrE,CAAC;AAAA,EACH;AAAA,EAEQ,UAAgB;AAEtB,QAAI;AACF,UAAI,GAAG,WAAW,KAAK,OAAO,GAAG;AAC/B,WAAG,WAAW,KAAK,OAAO;AAC1B,aAAK,IAAI,QAAQ,kBAAkB;AAAA,MACrC;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,QAAQ,6BAA6B,EAAE,OAAO,OAAO,CAAC,EAAE,CAAC;AAAA,IACpE;AAGA,QAAI;AACF,YAAM,iBAAiB;AAAA,QACrB,KAAK,QAAQ;AAAA,QACb,WAAW,KAAK,OAAO;AAAA,QACvB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,QAAQ;AAAA,QACR,QAAQ,KAAK,IAAI,IAAI,KAAK,MAAM;AAAA,QAChC,YAAY,KAAK,MAAM;AAAA,MACzB;AACA,SAAG;AAAA,QACD,KAAK;AAAA,QACL,KAAK,UAAU,gBAAgB,MAAM,CAAC;AAAA,MACxC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,SAAS,QAAsB;AACrC,QAAI,KAAK,eAAgB;AACzB,SAAK,iBAAiB;AAEtB,SAAK,IAAI,QAAQ,wBAAwB;AAAA,MACvC;AAAA,MACA,QAAQ,KAAK,IAAI,IAAI,KAAK,MAAM;AAAA,MAChC,YAAY,KAAK,MAAM;AAAA,MACvB,QAAQ,KAAK,MAAM,OAAO;AAAA,IAC5B,CAAC;AAGD,QAAI,KAAK,cAAc;AACrB,oBAAc,KAAK,YAAY;AAC/B,WAAK,eAAe;AAAA,IACtB;AACA,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AACA,QAAI,KAAK,uBAAuB;AAC9B,oBAAc,KAAK,qBAAqB;AACxC,WAAK,wBAAwB;AAAA,IAC/B;AAGA,QAAI;AACF,WAAK,YAAY;AAAA,IACnB,QAAQ;AAAA,IAER;AAEA,SAAK,QAAQ;AAGb,YAAQ;AAAA,MACN,WAAW,wBAAwB,WAAW,YAAY,IAAI;AAAA,IAChE;AAAA,EACF;AAAA,EAEO,QAAc;AAEnB,QAAI,CAAC,KAAK,iBAAiB,GAAG;AAC5B,WAAK,IAAI,QAAQ,kCAAkC;AACnD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,SAAK,aAAa;AAGlB,SAAK,oBAAoB;AAGzB,SAAK,IAAI,QAAQ,0BAA0B;AAAA,MACzC,WAAW,KAAK,OAAO;AAAA,MACvB,KAAK,QAAQ;AAAA,MACb,gBAAgB,KAAK,OAAO;AAAA,MAC5B,qBAAqB,KAAK,OAAO;AAAA,IACnC,CAAC;AAGD,SAAK,gBAAgB;AAGrB,SAAK,oBAAoB,YAAY,MAAM;AACzC,WAAK,gBAAgB;AAAA,IACvB,GAAG,KAAK,OAAO,mBAAmB;AAElC,SAAK,eAAe,YAAY,MAAM;AACpC,WAAK,YAAY;AAAA,IACnB,GAAG,KAAK,OAAO,cAAc;AAG7B,SAAK,wBAAwB,YAAY,MAAM;AAC7C,WAAK,cAAc;AAAA,IACrB,GAAG,KAAK,GAAI;AAGZ,SAAK,YAAY;AAAA,EACnB;AACF;AAGA,SAAS,YAAmE;AAC1E,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAIA,aAAY,WAAW,KAAK,IAAI,CAAC;AACrC,QAAMC,WAAiC,CAAC;AAExC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,kBAAkB,KAAK,IAAI,CAAC,GAAG;AACzC,MAAAD,aAAY,KAAK,IAAI,CAAC;AACtB;AAAA,IACF,WAAW,QAAQ,qBAAqB,KAAK,IAAI,CAAC,GAAG;AACnD,MAAAC,SAAQ,iBAAiB,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE,IAAI;AACrD;AAAA,IACF,WAAW,QAAQ,0BAA0B,KAAK,IAAI,CAAC,GAAG;AACxD,MAAAA,SAAQ,sBAAsB,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE,IAAI;AAC1D;AAAA,IACF,WAAW,QAAQ,0BAA0B,KAAK,IAAI,CAAC,GAAG;AACxD,MAAAA,SAAQ,sBAAsB,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE,IAAI;AAC1D;AAAA,IACF,WAAW,CAAC,IAAI,WAAW,IAAI,GAAG;AAChC,MAAAD,aAAY;AAAA,IACd;AAAA,EACF;AAEA,SAAO,EAAE,WAAAA,YAAW,SAAAC,SAAQ;AAC9B;AAGA,MAAM,EAAE,WAAW,QAAQ,IAAI,UAAU;AACzC,MAAM,SAAS,IAAI,cAAc,WAAW,OAAO;AACnD,OAAO,MAAM;",
|
|
6
|
+
"names": ["sessionId", "options"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { LinearClient } from "@linear/sdk";
|
|
2
|
+
import { logger } from "../../core/monitoring/logger.js";
|
|
3
|
+
class LinearPlugin {
|
|
4
|
+
name = "linear-integration";
|
|
5
|
+
version = "1.0.0";
|
|
6
|
+
description = "Linear task synchronization for StackMemory";
|
|
7
|
+
client = null;
|
|
8
|
+
apiKey = null;
|
|
9
|
+
async initialize() {
|
|
10
|
+
this.apiKey = process.env["LINEAR_API_KEY"] || null;
|
|
11
|
+
if (!this.apiKey) {
|
|
12
|
+
logger.info("Linear plugin: No API key found, running in offline mode");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
this.client = new LinearClient({ apiKey: this.apiKey });
|
|
17
|
+
await this.client.me;
|
|
18
|
+
logger.info("Linear plugin initialized successfully");
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logger.warn("Linear plugin: Failed to connect", error);
|
|
21
|
+
this.client = null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async shutdown() {
|
|
25
|
+
this.client = null;
|
|
26
|
+
logger.info("Linear plugin shut down");
|
|
27
|
+
}
|
|
28
|
+
registerCommands() {
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
name: "linear-sync",
|
|
32
|
+
description: "Sync tasks with Linear",
|
|
33
|
+
action: this.syncTasks.bind(this),
|
|
34
|
+
options: [
|
|
35
|
+
{
|
|
36
|
+
flag: "--team <id>",
|
|
37
|
+
description: "Linear team ID"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
flag: "--project <id>",
|
|
41
|
+
description: "Linear project ID"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "linear-create",
|
|
47
|
+
description: "Create a Linear issue from current context",
|
|
48
|
+
action: this.createIssue.bind(this),
|
|
49
|
+
options: [
|
|
50
|
+
{
|
|
51
|
+
flag: "--title <title>",
|
|
52
|
+
description: "Issue title"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
flag: "--description <desc>",
|
|
56
|
+
description: "Issue description"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
registerTools() {
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
name: "linear_sync",
|
|
66
|
+
description: "Sync current context with Linear tasks",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
teamId: { type: "string", description: "Linear team ID" },
|
|
71
|
+
projectId: { type: "string", description: "Linear project ID" }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
handler: async (input) => {
|
|
75
|
+
return await this.syncTasks(input);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "linear_create_issue",
|
|
80
|
+
description: "Create a Linear issue",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
title: { type: "string" },
|
|
85
|
+
description: { type: "string" },
|
|
86
|
+
teamId: { type: "string" }
|
|
87
|
+
},
|
|
88
|
+
required: ["title"]
|
|
89
|
+
},
|
|
90
|
+
handler: async (input) => {
|
|
91
|
+
return await this.createIssue(input);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
onFrameCreated(frame) {
|
|
97
|
+
if (frame.type === "task" && this.client) {
|
|
98
|
+
this.syncFrameToLinear(frame).catch((error) => {
|
|
99
|
+
logger.debug("Failed to sync frame to Linear", error);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async syncTasks(options) {
|
|
104
|
+
if (!this.client) {
|
|
105
|
+
return { success: false, message: "Linear not connected" };
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const issues = await this.client.issues({
|
|
109
|
+
filter: {
|
|
110
|
+
team: { id: { eq: options.teamId } }
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
synced: issues.nodes.length,
|
|
116
|
+
message: `Synced ${issues.nodes.length} issues from Linear`
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.error("Linear sync failed", error);
|
|
120
|
+
return { success: false, error: error.message };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async createIssue(options) {
|
|
124
|
+
if (!this.client) {
|
|
125
|
+
return { success: false, message: "Linear not connected" };
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const issue = await this.client.createIssue({
|
|
129
|
+
title: options.title,
|
|
130
|
+
description: options.description,
|
|
131
|
+
teamId: options.teamId || await this.getDefaultTeamId()
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
issueId: issue.issue?.id,
|
|
136
|
+
url: issue.issue?.url,
|
|
137
|
+
message: `Created Linear issue: ${issue.issue?.identifier}`
|
|
138
|
+
};
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logger.error("Failed to create Linear issue", error);
|
|
141
|
+
return { success: false, error: error.message };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async syncFrameToLinear(frame) {
|
|
145
|
+
if (frame.name && frame.content) {
|
|
146
|
+
await this.createIssue({
|
|
147
|
+
title: frame.name,
|
|
148
|
+
description: frame.content
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async getDefaultTeamId() {
|
|
153
|
+
if (!this.client) throw new Error("Linear not connected");
|
|
154
|
+
const teams = await this.client.teams();
|
|
155
|
+
if (teams.nodes.length > 0) {
|
|
156
|
+
return teams.nodes[0].id;
|
|
157
|
+
}
|
|
158
|
+
throw new Error("No Linear teams found");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
var linear_default = LinearPlugin;
|
|
162
|
+
export {
|
|
163
|
+
LinearPlugin,
|
|
164
|
+
linear_default as default
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/plugins/linear/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Linear Integration Plugin for StackMemory\n * Provides task synchronization with Linear as an optional feature\n */\n\nimport { StackMemoryPlugin, PluginCommand, PluginTool } from '../plugin-interface.js';\nimport { LinearClient } from '@linear/sdk';\nimport { logger } from '../../core/monitoring/logger.js';\n\nexport class LinearPlugin implements StackMemoryPlugin {\n name = 'linear-integration';\n version = '1.0.0';\n description = 'Linear task synchronization for StackMemory';\n \n private client: LinearClient | null = null;\n private apiKey: string | null = null;\n \n async initialize(): Promise<void> {\n this.apiKey = process.env['LINEAR_API_KEY'] || null;\n \n if (!this.apiKey) {\n logger.info('Linear plugin: No API key found, running in offline mode');\n return;\n }\n \n try {\n this.client = new LinearClient({ apiKey: this.apiKey });\n await this.client.me; // Test connection\n logger.info('Linear plugin initialized successfully');\n } catch (error) {\n logger.warn('Linear plugin: Failed to connect', error);\n this.client = null;\n }\n }\n \n async shutdown(): Promise<void> {\n this.client = null;\n logger.info('Linear plugin shut down');\n }\n \n registerCommands(): PluginCommand[] {\n return [\n {\n name: 'linear-sync',\n description: 'Sync tasks with Linear',\n action: this.syncTasks.bind(this),\n options: [\n {\n flag: '--team <id>',\n description: 'Linear team ID',\n },\n {\n flag: '--project <id>',\n description: 'Linear project ID',\n }\n ]\n },\n {\n name: 'linear-create',\n description: 'Create a Linear issue from current context',\n action: this.createIssue.bind(this),\n options: [\n {\n flag: '--title <title>',\n description: 'Issue title',\n },\n {\n flag: '--description <desc>',\n description: 'Issue description',\n }\n ]\n }\n ];\n }\n \n registerTools(): PluginTool[] {\n return [\n {\n name: 'linear_sync',\n description: 'Sync current context with Linear tasks',\n inputSchema: {\n type: 'object',\n properties: {\n teamId: { type: 'string', description: 'Linear team ID' },\n projectId: { type: 'string', description: 'Linear project ID' }\n }\n },\n handler: async (input) => {\n return await this.syncTasks(input);\n }\n },\n {\n name: 'linear_create_issue',\n description: 'Create a Linear issue',\n inputSchema: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n description: { type: 'string' },\n teamId: { type: 'string' }\n },\n required: ['title']\n },\n handler: async (input) => {\n return await this.createIssue(input);\n }\n }\n ];\n }\n \n onFrameCreated(frame: any): void {\n // Auto-sync with Linear when a task frame is created\n if (frame.type === 'task' && this.client) {\n this.syncFrameToLinear(frame).catch(error => {\n logger.debug('Failed to sync frame to Linear', error);\n });\n }\n }\n \n private async syncTasks(options: any): Promise<any> {\n if (!this.client) {\n return { success: false, message: 'Linear not connected' };\n }\n \n try {\n // Basic sync implementation\n const issues = await this.client.issues({\n filter: {\n team: { id: { eq: options.teamId } }\n }\n });\n \n return {\n success: true,\n synced: issues.nodes.length,\n message: `Synced ${issues.nodes.length} issues from Linear`\n };\n } catch (error) {\n logger.error('Linear sync failed', error);\n return { success: false, error: (error as Error).message };\n }\n }\n \n private async createIssue(options: any): Promise<any> {\n if (!this.client) {\n return { success: false, message: 'Linear not connected' };\n }\n \n try {\n const issue = await this.client.createIssue({\n title: options.title,\n description: options.description,\n teamId: options.teamId || (await this.getDefaultTeamId())\n });\n \n return {\n success: true,\n issueId: issue.issue?.id,\n url: issue.issue?.url,\n message: `Created Linear issue: ${issue.issue?.identifier}`\n };\n } catch (error) {\n logger.error('Failed to create Linear issue', error);\n return { success: false, error: (error as Error).message };\n }\n }\n \n private async syncFrameToLinear(frame: any): Promise<void> {\n // Simple frame to Linear sync\n if (frame.name && frame.content) {\n await this.createIssue({\n title: frame.name,\n description: frame.content\n });\n }\n }\n \n private async getDefaultTeamId(): Promise<string> {\n if (!this.client) throw new Error('Linear not connected');\n \n const teams = await this.client.teams();\n if (teams.nodes.length > 0) {\n return teams.nodes[0].id;\n }\n throw new Error('No Linear teams found');\n }\n}\n\n// Export for plugin registration\nexport default LinearPlugin;"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { pluginManager } from "./plugin-interface.js";
|
|
2
|
+
import { logger } from "../core/monitoring/logger.js";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
async function loadPlugins() {
|
|
6
|
+
const configPath = join(process.cwd(), ".stackmemory", "plugins.json");
|
|
7
|
+
let enabledPlugins = [];
|
|
8
|
+
if (existsSync(configPath)) {
|
|
9
|
+
try {
|
|
10
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
11
|
+
enabledPlugins = config.enabled || [];
|
|
12
|
+
} catch (error) {
|
|
13
|
+
logger.warn("Failed to load plugin configuration", error);
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
enabledPlugins = [];
|
|
17
|
+
if (process.env["ENABLE_LINEAR_PLUGIN"] === "true") {
|
|
18
|
+
enabledPlugins.push("linear");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const pluginName of enabledPlugins) {
|
|
22
|
+
try {
|
|
23
|
+
await loadPlugin(pluginName);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.warn(`Failed to load plugin ${pluginName}`, error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const loadedPlugins = pluginManager.getAllPlugins();
|
|
29
|
+
if (loadedPlugins.length > 0) {
|
|
30
|
+
logger.info(
|
|
31
|
+
`Loaded ${loadedPlugins.length} plugins: ${loadedPlugins.map((p) => p.name).join(", ")}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function loadPlugin(name) {
|
|
36
|
+
switch (name) {
|
|
37
|
+
case "linear": {
|
|
38
|
+
const { LinearPlugin } = await import("./linear/index.js");
|
|
39
|
+
const plugin = new LinearPlugin();
|
|
40
|
+
await pluginManager.register(plugin);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
// Future plugins can be added here
|
|
44
|
+
// case 'github': {
|
|
45
|
+
// const { GitHubPlugin } = await import('./github/index.js');
|
|
46
|
+
// await pluginManager.register(new GitHubPlugin());
|
|
47
|
+
// break;
|
|
48
|
+
// }
|
|
49
|
+
default:
|
|
50
|
+
logger.warn(`Unknown plugin: ${name}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
loadPlugins,
|
|
55
|
+
pluginManager
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/plugins/loader.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Plugin Loader for StackMemory\n * Automatically loads plugins based on configuration\n */\n\nimport { pluginManager } from './plugin-interface.js';\nimport { logger } from '../core/monitoring/logger.js';\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\n\nexport async function loadPlugins(): Promise<void> {\n // Check for plugin configuration\n const configPath = join(process.cwd(), '.stackmemory', 'plugins.json');\n\n let enabledPlugins: string[] = [];\n\n if (existsSync(configPath)) {\n try {\n const config = JSON.parse(readFileSync(configPath, 'utf-8'));\n enabledPlugins = config.enabled || [];\n } catch (error) {\n logger.warn('Failed to load plugin configuration', error);\n }\n } else {\n // Default plugins (none by default, all optional)\n enabledPlugins = [];\n\n // Check environment variables for opt-in plugins\n if (process.env['ENABLE_LINEAR_PLUGIN'] === 'true') {\n enabledPlugins.push('linear');\n }\n }\n\n // Load enabled plugins\n for (const pluginName of enabledPlugins) {\n try {\n await loadPlugin(pluginName);\n } catch (error) {\n logger.warn(`Failed to load plugin ${pluginName}`, error);\n }\n }\n\n const loadedPlugins = pluginManager.getAllPlugins();\n if (loadedPlugins.length > 0) {\n logger.info(\n `Loaded ${loadedPlugins.length} plugins: ${loadedPlugins.map((p) => p.name).join(', ')}`\n );\n }\n}\n\nasync function loadPlugin(name: string): Promise<void> {\n switch (name) {\n case 'linear': {\n const { LinearPlugin } = await import('./linear/index.js');\n const plugin = new LinearPlugin();\n await pluginManager.register(plugin);\n break;\n }\n\n // Future plugins can be added here\n // case 'github': {\n // const { GitHubPlugin } = await import('./github/index.js');\n // await pluginManager.register(new GitHubPlugin());\n // break;\n // }\n\n default:\n logger.warn(`Unknown plugin: ${name}`);\n }\n}\n\nexport { pluginManager };\n"],
|
|
5
|
+
"mappings": "AAKA,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AACvB,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AAErB,eAAsB,cAA6B;AAEjD,QAAM,aAAa,KAAK,QAAQ,IAAI,GAAG,gBAAgB,cAAc;AAErE,MAAI,iBAA2B,CAAC;AAEhC,MAAI,WAAW,UAAU,GAAG;AAC1B,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAC3D,uBAAiB,OAAO,WAAW,CAAC;AAAA,IACtC,SAAS,OAAO;AACd,aAAO,KAAK,uCAAuC,KAAK;AAAA,IAC1D;AAAA,EACF,OAAO;AAEL,qBAAiB,CAAC;AAGlB,QAAI,QAAQ,IAAI,sBAAsB,MAAM,QAAQ;AAClD,qBAAe,KAAK,QAAQ;AAAA,IAC9B;AAAA,EACF;AAGA,aAAW,cAAc,gBAAgB;AACvC,QAAI;AACF,YAAM,WAAW,UAAU;AAAA,IAC7B,SAAS,OAAO;AACd,aAAO,KAAK,yBAAyB,UAAU,IAAI,KAAK;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM,gBAAgB,cAAc,cAAc;AAClD,MAAI,cAAc,SAAS,GAAG;AAC5B,WAAO;AAAA,MACL,UAAU,cAAc,MAAM,aAAa,cAAc,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACxF;AAAA,EACF;AACF;AAEA,eAAe,WAAW,MAA6B;AACrD,UAAQ,MAAM;AAAA,IACZ,KAAK,UAAU;AACb,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,mBAAmB;AACzD,YAAM,SAAS,IAAI,aAAa;AAChC,YAAM,cAAc,SAAS,MAAM;AACnC;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA;AACE,aAAO,KAAK,mBAAmB,IAAI,EAAE;AAAA,EACzC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|