@stackmemoryai/stackmemory 0.7.0 → 0.8.1
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/README.md +39 -1
- package/dist/src/cli/claude-sm.js +69 -372
- package/dist/src/cli/claude-sm.js.map +2 -2
- package/dist/src/cli/codex-sm.js +28 -0
- package/dist/src/cli/codex-sm.js.map +2 -2
- package/dist/src/cli/commands/clear.js +10 -15
- package/dist/src/cli/commands/clear.js.map +2 -2
- package/dist/src/cli/commands/daemon.js +31 -0
- package/dist/src/cli/commands/daemon.js.map +2 -2
- package/dist/src/cli/commands/onboard.js +22 -6
- package/dist/src/cli/commands/onboard.js.map +2 -2
- package/dist/src/cli/commands/setup.js +1 -2
- package/dist/src/cli/commands/setup.js.map +2 -2
- package/dist/src/cli/index.js +1 -71
- package/dist/src/cli/index.js.map +3 -3
- package/dist/src/cli/opencode-sm.js +28 -0
- package/dist/src/cli/opencode-sm.js.map +2 -2
- package/dist/src/core/config/feature-flags.js +1 -13
- package/dist/src/core/config/feature-flags.js.map +2 -2
- package/dist/src/core/context/refactored-frame-manager.js +7 -0
- package/dist/src/core/context/refactored-frame-manager.js.map +2 -2
- package/dist/src/core/database/sqlite-adapter.js +1 -1
- package/dist/src/core/database/sqlite-adapter.js.map +2 -2
- package/dist/src/core/session/enhanced-handoff.js +1 -1
- package/dist/src/core/session/enhanced-handoff.js.map +2 -2
- package/dist/src/daemon/daemon-config.js +11 -0
- package/dist/src/daemon/daemon-config.js.map +2 -2
- package/dist/src/daemon/services/memory-service.js +190 -0
- package/dist/src/daemon/services/memory-service.js.map +7 -0
- package/dist/src/daemon/unified-daemon.js +33 -3
- package/dist/src/daemon/unified-daemon.js.map +2 -2
- package/dist/src/features/sweep/pty-wrapper.js +11 -1
- package/dist/src/features/sweep/pty-wrapper.js.map +2 -2
- package/dist/src/hooks/index.js +0 -3
- package/dist/src/hooks/index.js.map +2 -2
- package/dist/src/hooks/schemas.js +0 -117
- package/dist/src/hooks/schemas.js.map +2 -2
- package/dist/src/hooks/session-summary.js.map +1 -1
- package/package.json +2 -2
- package/scripts/smoke-init-db.sh +77 -0
- package/scripts/test-pre-publish-quick.sh +26 -34
- package/scripts/install-notify-hook.sh +0 -84
- package/scripts/setup-notify-webhook.sh +0 -103
- package/templates/claude-hooks/notify-review-hook.js +0 -360
- package/templates/claude-hooks/sms-response-handler.js +0 -174
|
@@ -18,12 +18,14 @@ import {
|
|
|
18
18
|
import { DaemonContextService } from "./services/context-service.js";
|
|
19
19
|
import { DaemonLinearService } from "./services/linear-service.js";
|
|
20
20
|
import { DaemonMaintenanceService } from "./services/maintenance-service.js";
|
|
21
|
+
import { DaemonMemoryService } from "./services/memory-service.js";
|
|
21
22
|
class UnifiedDaemon {
|
|
22
23
|
config;
|
|
23
24
|
paths;
|
|
24
25
|
contextService;
|
|
25
26
|
linearService;
|
|
26
27
|
maintenanceService;
|
|
28
|
+
memoryService;
|
|
27
29
|
heartbeatInterval;
|
|
28
30
|
isShuttingDown = false;
|
|
29
31
|
startTime = 0;
|
|
@@ -42,6 +44,10 @@ class UnifiedDaemon {
|
|
|
42
44
|
this.config.maintenance,
|
|
43
45
|
(level, msg, data) => this.log(level, "maintenance", msg, data)
|
|
44
46
|
);
|
|
47
|
+
this.memoryService = new DaemonMemoryService(
|
|
48
|
+
this.config.memory,
|
|
49
|
+
(level, msg, data) => this.log(level, "memory", msg, data)
|
|
50
|
+
);
|
|
45
51
|
}
|
|
46
52
|
log(level, service, message, data) {
|
|
47
53
|
const logLevel = level.toUpperCase();
|
|
@@ -95,6 +101,7 @@ class UnifiedDaemon {
|
|
|
95
101
|
}
|
|
96
102
|
updateStatus() {
|
|
97
103
|
const maintenanceState = this.maintenanceService.getState();
|
|
104
|
+
const memoryState = this.memoryService.getState();
|
|
98
105
|
const status = {
|
|
99
106
|
running: true,
|
|
100
107
|
pid: process.pid,
|
|
@@ -120,6 +127,12 @@ class UnifiedDaemon {
|
|
|
120
127
|
embeddingsTotal: maintenanceState.embeddingsTotal,
|
|
121
128
|
embeddingsRemaining: maintenanceState.embeddingsRemaining
|
|
122
129
|
},
|
|
130
|
+
memory: {
|
|
131
|
+
enabled: this.config.memory.enabled,
|
|
132
|
+
lastTrigger: memoryState.lastTriggerTime || void 0,
|
|
133
|
+
triggerCount: memoryState.triggerCount,
|
|
134
|
+
currentRamPercent: memoryState.currentRamPercent
|
|
135
|
+
},
|
|
123
136
|
fileWatch: {
|
|
124
137
|
enabled: this.config.fileWatch.enabled
|
|
125
138
|
}
|
|
@@ -127,7 +140,8 @@ class UnifiedDaemon {
|
|
|
127
140
|
errors: [
|
|
128
141
|
...this.contextService.getState().errors.slice(-5),
|
|
129
142
|
...this.linearService.getState().errors.slice(-5),
|
|
130
|
-
...maintenanceState.errors.slice(-5)
|
|
143
|
+
...maintenanceState.errors.slice(-5),
|
|
144
|
+
...memoryState.errors.slice(-5)
|
|
131
145
|
]
|
|
132
146
|
};
|
|
133
147
|
writeDaemonStatus(status);
|
|
@@ -182,6 +196,10 @@ class UnifiedDaemon {
|
|
|
182
196
|
staleFramesCleaned: this.maintenanceService.getState().staleFramesCleaned,
|
|
183
197
|
ftsRebuilds: this.maintenanceService.getState().ftsRebuilds
|
|
184
198
|
},
|
|
199
|
+
memory: {
|
|
200
|
+
enabled: false,
|
|
201
|
+
triggerCount: this.memoryService.getState().triggerCount
|
|
202
|
+
},
|
|
185
203
|
fileWatch: { enabled: false }
|
|
186
204
|
},
|
|
187
205
|
errors: []
|
|
@@ -196,7 +214,8 @@ class UnifiedDaemon {
|
|
|
196
214
|
uptime: Date.now() - this.startTime,
|
|
197
215
|
contextSaves: this.contextService.getState().saveCount,
|
|
198
216
|
linearSyncs: this.linearService.getState().syncCount,
|
|
199
|
-
maintenanceRuns: this.maintenanceService.getState().ftsRebuilds
|
|
217
|
+
maintenanceRuns: this.maintenanceService.getState().ftsRebuilds,
|
|
218
|
+
memoryTriggers: this.memoryService.getState().triggerCount
|
|
200
219
|
});
|
|
201
220
|
if (this.heartbeatInterval) {
|
|
202
221
|
clearInterval(this.heartbeatInterval);
|
|
@@ -205,6 +224,7 @@ class UnifiedDaemon {
|
|
|
205
224
|
this.contextService.stop();
|
|
206
225
|
this.linearService.stop();
|
|
207
226
|
this.maintenanceService.stop();
|
|
227
|
+
this.memoryService.stop();
|
|
208
228
|
this.cleanup();
|
|
209
229
|
const exitCode = reason === "sigterm" || reason === "sigint" || reason === "sighup" ? 0 : 1;
|
|
210
230
|
process.exit(exitCode);
|
|
@@ -223,12 +243,14 @@ class UnifiedDaemon {
|
|
|
223
243
|
context: this.config.context.enabled,
|
|
224
244
|
linear: this.config.linear.enabled,
|
|
225
245
|
maintenance: this.config.maintenance.enabled,
|
|
246
|
+
memory: this.config.memory.enabled,
|
|
226
247
|
fileWatch: this.config.fileWatch.enabled
|
|
227
248
|
}
|
|
228
249
|
});
|
|
229
250
|
this.contextService.start();
|
|
230
251
|
await this.linearService.start();
|
|
231
252
|
this.maintenanceService.start();
|
|
253
|
+
this.memoryService.start();
|
|
232
254
|
this.heartbeatInterval = setInterval(() => {
|
|
233
255
|
this.updateStatus();
|
|
234
256
|
}, this.config.heartbeatInterval * 1e3);
|
|
@@ -236,6 +258,7 @@ class UnifiedDaemon {
|
|
|
236
258
|
}
|
|
237
259
|
getStatus() {
|
|
238
260
|
const maintenanceState = this.maintenanceService.getState();
|
|
261
|
+
const memoryState = this.memoryService.getState();
|
|
239
262
|
return {
|
|
240
263
|
running: !this.isShuttingDown,
|
|
241
264
|
pid: process.pid,
|
|
@@ -261,6 +284,12 @@ class UnifiedDaemon {
|
|
|
261
284
|
embeddingsTotal: maintenanceState.embeddingsTotal,
|
|
262
285
|
embeddingsRemaining: maintenanceState.embeddingsRemaining
|
|
263
286
|
},
|
|
287
|
+
memory: {
|
|
288
|
+
enabled: this.config.memory.enabled,
|
|
289
|
+
lastTrigger: memoryState.lastTriggerTime || void 0,
|
|
290
|
+
triggerCount: memoryState.triggerCount,
|
|
291
|
+
currentRamPercent: memoryState.currentRamPercent
|
|
292
|
+
},
|
|
264
293
|
fileWatch: {
|
|
265
294
|
enabled: this.config.fileWatch.enabled
|
|
266
295
|
}
|
|
@@ -268,7 +297,8 @@ class UnifiedDaemon {
|
|
|
268
297
|
errors: [
|
|
269
298
|
...this.contextService.getState().errors,
|
|
270
299
|
...this.linearService.getState().errors,
|
|
271
|
-
...maintenanceState.errors
|
|
300
|
+
...maintenanceState.errors,
|
|
301
|
+
...memoryState.errors
|
|
272
302
|
]
|
|
273
303
|
};
|
|
274
304
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/daemon/unified-daemon.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Unified Daemon for StackMemory\n *\n * Single background process managing multiple services:\n * - Context auto-save\n * - Linear sync\n * - File watch (future)\n */\n\nimport {\n existsSync,\n writeFileSync,\n unlinkSync,\n appendFileSync,\n readFileSync,\n} from 'fs';\nimport {\n loadDaemonConfig,\n getDaemonPaths,\n writeDaemonStatus,\n type DaemonConfig,\n type DaemonStatus,\n} from './daemon-config.js';\nimport { DaemonContextService } from './services/context-service.js';\nimport { DaemonLinearService } from './services/linear-service.js';\nimport { DaemonMaintenanceService } from './services/maintenance-service.js';\n\ninterface LogEntry {\n timestamp: string;\n level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';\n service: string;\n message: string;\n data?: unknown;\n}\n\nexport class UnifiedDaemon {\n private config: DaemonConfig;\n private paths: ReturnType<typeof getDaemonPaths>;\n private contextService: DaemonContextService;\n private linearService: DaemonLinearService;\n private maintenanceService: DaemonMaintenanceService;\n private heartbeatInterval?: NodeJS.Timeout;\n private isShuttingDown = false;\n private startTime: number = 0;\n\n constructor(config?: Partial<DaemonConfig>) {\n this.paths = getDaemonPaths();\n this.config = { ...loadDaemonConfig(), ...config };\n\n // Initialize services\n this.contextService = new DaemonContextService(\n this.config.context,\n (level, msg, data) => this.log(level, 'context', msg, data)\n );\n\n this.linearService = new DaemonLinearService(\n this.config.linear,\n (level, msg, data) => this.log(level, 'linear', msg, data)\n );\n\n this.maintenanceService = new DaemonMaintenanceService(\n this.config.maintenance,\n (level, msg, data) => this.log(level, 'maintenance', msg, data)\n );\n }\n\n private log(\n level: string,\n service: string,\n message: string,\n data?: unknown\n ): void {\n const logLevel = level.toUpperCase() as LogEntry['level'];\n\n // Check log level\n const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];\n const configLevel = this.config.logLevel.toUpperCase();\n if (levels.indexOf(logLevel) < levels.indexOf(configLevel)) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level: logLevel,\n service,\n message,\n data,\n };\n\n const logLine = JSON.stringify(entry) + '\\n';\n\n try {\n appendFileSync(this.paths.logFile, logLine);\n } catch {\n console.error(`[${entry.timestamp}] ${level} [${service}]: ${message}`);\n }\n }\n\n private checkIdempotency(): boolean {\n if (existsSync(this.paths.pidFile)) {\n try {\n const existingPid = readFileSync(this.paths.pidFile, 'utf8').trim();\n const pid = parseInt(existingPid, 10);\n\n // Check if process is running\n try {\n process.kill(pid, 0);\n this.log('WARN', 'daemon', 'Daemon already running', { pid });\n return false;\n } catch {\n // Process not running, stale PID\n this.log('INFO', 'daemon', 'Cleaning stale PID file', { pid });\n unlinkSync(this.paths.pidFile);\n }\n } catch {\n try {\n unlinkSync(this.paths.pidFile);\n } catch {\n // Ignore\n }\n }\n }\n return true;\n }\n\n private writePidFile(): void {\n writeFileSync(this.paths.pidFile, process.pid.toString());\n this.log('INFO', 'daemon', 'PID file created', {\n pid: process.pid,\n file: this.paths.pidFile,\n });\n }\n\n private updateStatus(): void {\n const maintenanceState = this.maintenanceService.getState();\n const status: DaemonStatus = {\n running: true,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors.slice(-5),\n ...this.linearService.getState().errors.slice(-5),\n ...maintenanceState.errors.slice(-5),\n ],\n };\n\n writeDaemonStatus(status);\n }\n\n private setupSignalHandlers(): void {\n const handleSignal = (signal: string) => {\n this.log('INFO', 'daemon', `Received ${signal}, shutting down`);\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 process.on('uncaughtException', (err) => {\n this.log('ERROR', 'daemon', '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', 'daemon', 'Unhandled rejection', {\n reason: String(reason),\n });\n });\n }\n\n private cleanup(): void {\n // Remove PID file\n try {\n if (existsSync(this.paths.pidFile)) {\n unlinkSync(this.paths.pidFile);\n this.log('INFO', 'daemon', 'PID file removed');\n }\n } catch (e) {\n this.log('WARN', 'daemon', 'Failed to remove PID file', {\n error: String(e),\n });\n }\n\n // Update status\n const finalStatus: DaemonStatus = {\n running: false,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: false,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: false,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: false,\n staleFramesCleaned:\n this.maintenanceService.getState().staleFramesCleaned,\n ftsRebuilds: this.maintenanceService.getState().ftsRebuilds,\n },\n fileWatch: { enabled: false },\n },\n errors: [],\n };\n writeDaemonStatus(finalStatus);\n }\n\n private shutdown(reason: string): void {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n this.log('INFO', 'daemon', 'Daemon shutting down', {\n reason,\n uptime: Date.now() - this.startTime,\n contextSaves: this.contextService.getState().saveCount,\n linearSyncs: this.linearService.getState().syncCount,\n maintenanceRuns: this.maintenanceService.getState().ftsRebuilds,\n });\n\n // Stop heartbeat\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = undefined;\n }\n\n // Stop services\n this.contextService.stop();\n this.linearService.stop();\n this.maintenanceService.stop();\n\n // Cleanup\n this.cleanup();\n\n const exitCode =\n reason === 'sigterm' || reason === 'sigint' || reason === 'sighup'\n ? 0\n : 1;\n process.exit(exitCode);\n }\n\n async start(): Promise<void> {\n // Check idempotency\n if (!this.checkIdempotency()) {\n this.log('INFO', 'daemon', 'Exiting - daemon already running');\n process.exit(0);\n }\n\n this.startTime = Date.now();\n\n // Write PID file\n this.writePidFile();\n\n // Setup signal handlers\n this.setupSignalHandlers();\n\n this.log('INFO', 'daemon', 'Unified daemon started', {\n pid: process.pid,\n config: {\n context: this.config.context.enabled,\n linear: this.config.linear.enabled,\n maintenance: this.config.maintenance.enabled,\n fileWatch: this.config.fileWatch.enabled,\n },\n });\n\n // Start services\n this.contextService.start();\n await this.linearService.start();\n this.maintenanceService.start();\n\n // Start heartbeat\n this.heartbeatInterval = setInterval(() => {\n this.updateStatus();\n }, this.config.heartbeatInterval * 1000);\n\n // Initial status update\n this.updateStatus();\n }\n\n getStatus(): DaemonStatus {\n const maintenanceState = this.maintenanceService.getState();\n return {\n running: !this.isShuttingDown,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors,\n ...this.linearService.getState().errors,\n ...maintenanceState.errors,\n ],\n };\n }\n}\n\n// CLI entry point\nif (\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('unified-daemon.js')\n) {\n const args = process.argv.slice(2);\n const config: Partial<DaemonConfig> = {};\n\n // Parse command line args\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--save-interval' && args[i + 1]) {\n config.context = { enabled: true, interval: parseInt(args[i + 1], 10) };\n i++;\n } else if (arg === '--linear-interval' && args[i + 1]) {\n config.linear = {\n enabled: true,\n interval: parseInt(args[i + 1], 10),\n retryAttempts: 3,\n retryDelay: 30000,\n };\n i++;\n } else if (arg === '--no-linear') {\n config.linear = {\n enabled: false,\n interval: 60,\n retryAttempts: 3,\n retryDelay: 30000,\n };\n } else if (arg === '--log-level' && args[i + 1]) {\n config.logLevel = args[i + 1] as DaemonConfig['logLevel'];\n i++;\n }\n }\n\n const daemon = new UnifiedDaemon(config);\n daemon.start().catch((err) => {\n console.error('Failed to start daemon:', err);\n process.exit(1);\n });\n}\n"],
|
|
5
|
-
"mappings": ";;;;;AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\n/**\n * Unified Daemon for StackMemory\n *\n * Single background process managing multiple services:\n * - Context auto-save\n * - Linear sync\n * - File watch (future)\n */\n\nimport {\n existsSync,\n writeFileSync,\n unlinkSync,\n appendFileSync,\n readFileSync,\n} from 'fs';\nimport {\n loadDaemonConfig,\n getDaemonPaths,\n writeDaemonStatus,\n type DaemonConfig,\n type DaemonStatus,\n} from './daemon-config.js';\nimport { DaemonContextService } from './services/context-service.js';\nimport { DaemonLinearService } from './services/linear-service.js';\nimport { DaemonMaintenanceService } from './services/maintenance-service.js';\nimport { DaemonMemoryService } from './services/memory-service.js';\n\ninterface LogEntry {\n timestamp: string;\n level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';\n service: string;\n message: string;\n data?: unknown;\n}\n\nexport class UnifiedDaemon {\n private config: DaemonConfig;\n private paths: ReturnType<typeof getDaemonPaths>;\n private contextService: DaemonContextService;\n private linearService: DaemonLinearService;\n private maintenanceService: DaemonMaintenanceService;\n private memoryService: DaemonMemoryService;\n private heartbeatInterval?: NodeJS.Timeout;\n private isShuttingDown = false;\n private startTime: number = 0;\n\n constructor(config?: Partial<DaemonConfig>) {\n this.paths = getDaemonPaths();\n this.config = { ...loadDaemonConfig(), ...config };\n\n // Initialize services\n this.contextService = new DaemonContextService(\n this.config.context,\n (level, msg, data) => this.log(level, 'context', msg, data)\n );\n\n this.linearService = new DaemonLinearService(\n this.config.linear,\n (level, msg, data) => this.log(level, 'linear', msg, data)\n );\n\n this.maintenanceService = new DaemonMaintenanceService(\n this.config.maintenance,\n (level, msg, data) => this.log(level, 'maintenance', msg, data)\n );\n\n this.memoryService = new DaemonMemoryService(\n this.config.memory,\n (level, msg, data) => this.log(level, 'memory', msg, data)\n );\n }\n\n private log(\n level: string,\n service: string,\n message: string,\n data?: unknown\n ): void {\n const logLevel = level.toUpperCase() as LogEntry['level'];\n\n // Check log level\n const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];\n const configLevel = this.config.logLevel.toUpperCase();\n if (levels.indexOf(logLevel) < levels.indexOf(configLevel)) {\n return;\n }\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level: logLevel,\n service,\n message,\n data,\n };\n\n const logLine = JSON.stringify(entry) + '\\n';\n\n try {\n appendFileSync(this.paths.logFile, logLine);\n } catch {\n console.error(`[${entry.timestamp}] ${level} [${service}]: ${message}`);\n }\n }\n\n private checkIdempotency(): boolean {\n if (existsSync(this.paths.pidFile)) {\n try {\n const existingPid = readFileSync(this.paths.pidFile, 'utf8').trim();\n const pid = parseInt(existingPid, 10);\n\n // Check if process is running\n try {\n process.kill(pid, 0);\n this.log('WARN', 'daemon', 'Daemon already running', { pid });\n return false;\n } catch {\n // Process not running, stale PID\n this.log('INFO', 'daemon', 'Cleaning stale PID file', { pid });\n unlinkSync(this.paths.pidFile);\n }\n } catch {\n try {\n unlinkSync(this.paths.pidFile);\n } catch {\n // Ignore\n }\n }\n }\n return true;\n }\n\n private writePidFile(): void {\n writeFileSync(this.paths.pidFile, process.pid.toString());\n this.log('INFO', 'daemon', 'PID file created', {\n pid: process.pid,\n file: this.paths.pidFile,\n });\n }\n\n private updateStatus(): void {\n const maintenanceState = this.maintenanceService.getState();\n const memoryState = this.memoryService.getState();\n const status: DaemonStatus = {\n running: true,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n memory: {\n enabled: this.config.memory.enabled,\n lastTrigger: memoryState.lastTriggerTime || undefined,\n triggerCount: memoryState.triggerCount,\n currentRamPercent: memoryState.currentRamPercent,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors.slice(-5),\n ...this.linearService.getState().errors.slice(-5),\n ...maintenanceState.errors.slice(-5),\n ...memoryState.errors.slice(-5),\n ],\n };\n\n writeDaemonStatus(status);\n }\n\n private setupSignalHandlers(): void {\n const handleSignal = (signal: string) => {\n this.log('INFO', 'daemon', `Received ${signal}, shutting down`);\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 process.on('uncaughtException', (err) => {\n this.log('ERROR', 'daemon', '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', 'daemon', 'Unhandled rejection', {\n reason: String(reason),\n });\n });\n }\n\n private cleanup(): void {\n // Remove PID file\n try {\n if (existsSync(this.paths.pidFile)) {\n unlinkSync(this.paths.pidFile);\n this.log('INFO', 'daemon', 'PID file removed');\n }\n } catch (e) {\n this.log('WARN', 'daemon', 'Failed to remove PID file', {\n error: String(e),\n });\n }\n\n // Update status\n const finalStatus: DaemonStatus = {\n running: false,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: false,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: false,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: false,\n staleFramesCleaned:\n this.maintenanceService.getState().staleFramesCleaned,\n ftsRebuilds: this.maintenanceService.getState().ftsRebuilds,\n },\n memory: {\n enabled: false,\n triggerCount: this.memoryService.getState().triggerCount,\n },\n fileWatch: { enabled: false },\n },\n errors: [],\n };\n writeDaemonStatus(finalStatus);\n }\n\n private shutdown(reason: string): void {\n if (this.isShuttingDown) return;\n this.isShuttingDown = true;\n\n this.log('INFO', 'daemon', 'Daemon shutting down', {\n reason,\n uptime: Date.now() - this.startTime,\n contextSaves: this.contextService.getState().saveCount,\n linearSyncs: this.linearService.getState().syncCount,\n maintenanceRuns: this.maintenanceService.getState().ftsRebuilds,\n memoryTriggers: this.memoryService.getState().triggerCount,\n });\n\n // Stop heartbeat\n if (this.heartbeatInterval) {\n clearInterval(this.heartbeatInterval);\n this.heartbeatInterval = undefined;\n }\n\n // Stop services\n this.contextService.stop();\n this.linearService.stop();\n this.maintenanceService.stop();\n this.memoryService.stop();\n\n // Cleanup\n this.cleanup();\n\n const exitCode =\n reason === 'sigterm' || reason === 'sigint' || reason === 'sighup'\n ? 0\n : 1;\n process.exit(exitCode);\n }\n\n async start(): Promise<void> {\n // Check idempotency\n if (!this.checkIdempotency()) {\n this.log('INFO', 'daemon', 'Exiting - daemon already running');\n process.exit(0);\n }\n\n this.startTime = Date.now();\n\n // Write PID file\n this.writePidFile();\n\n // Setup signal handlers\n this.setupSignalHandlers();\n\n this.log('INFO', 'daemon', 'Unified daemon started', {\n pid: process.pid,\n config: {\n context: this.config.context.enabled,\n linear: this.config.linear.enabled,\n maintenance: this.config.maintenance.enabled,\n memory: this.config.memory.enabled,\n fileWatch: this.config.fileWatch.enabled,\n },\n });\n\n // Start services\n this.contextService.start();\n await this.linearService.start();\n this.maintenanceService.start();\n this.memoryService.start();\n\n // Start heartbeat\n this.heartbeatInterval = setInterval(() => {\n this.updateStatus();\n }, this.config.heartbeatInterval * 1000);\n\n // Initial status update\n this.updateStatus();\n }\n\n getStatus(): DaemonStatus {\n const maintenanceState = this.maintenanceService.getState();\n const memoryState = this.memoryService.getState();\n return {\n running: !this.isShuttingDown,\n pid: process.pid,\n startTime: this.startTime,\n uptime: Date.now() - this.startTime,\n services: {\n context: {\n enabled: this.config.context.enabled,\n lastRun: this.contextService.getState().lastSaveTime || undefined,\n saveCount: this.contextService.getState().saveCount,\n },\n linear: {\n enabled: this.config.linear.enabled,\n lastRun: this.linearService.getState().lastSyncTime || undefined,\n syncCount: this.linearService.getState().syncCount,\n },\n maintenance: {\n enabled: this.config.maintenance.enabled,\n lastRun: maintenanceState.lastRunTime || undefined,\n staleFramesCleaned: maintenanceState.staleFramesCleaned,\n ftsRebuilds: maintenanceState.ftsRebuilds,\n embeddingsGenerated: maintenanceState.embeddingsGenerated,\n embeddingsTotal: maintenanceState.embeddingsTotal,\n embeddingsRemaining: maintenanceState.embeddingsRemaining,\n },\n memory: {\n enabled: this.config.memory.enabled,\n lastTrigger: memoryState.lastTriggerTime || undefined,\n triggerCount: memoryState.triggerCount,\n currentRamPercent: memoryState.currentRamPercent,\n },\n fileWatch: {\n enabled: this.config.fileWatch.enabled,\n },\n },\n errors: [\n ...this.contextService.getState().errors,\n ...this.linearService.getState().errors,\n ...maintenanceState.errors,\n ...memoryState.errors,\n ],\n };\n }\n}\n\n// CLI entry point\nif (\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('unified-daemon.js')\n) {\n const args = process.argv.slice(2);\n const config: Partial<DaemonConfig> = {};\n\n // Parse command line args\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === '--save-interval' && args[i + 1]) {\n config.context = { enabled: true, interval: parseInt(args[i + 1], 10) };\n i++;\n } else if (arg === '--linear-interval' && args[i + 1]) {\n config.linear = {\n enabled: true,\n interval: parseInt(args[i + 1], 10),\n retryAttempts: 3,\n retryDelay: 30000,\n };\n i++;\n } else if (arg === '--no-linear') {\n config.linear = {\n enabled: false,\n interval: 60,\n retryAttempts: 3,\n retryDelay: 30000,\n };\n } else if (arg === '--log-level' && args[i + 1]) {\n config.logLevel = args[i + 1] as DaemonConfig['logLevel'];\n i++;\n }\n }\n\n const daemon = new UnifiedDaemon(config);\n daemon.start().catch((err) => {\n console.error('Failed to start daemon:', err);\n process.exit(1);\n });\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;AACzC,SAAS,2BAA2B;AAU7B,MAAM,cAAc;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,YAAoB;AAAA,EAE5B,YAAY,QAAgC;AAC1C,SAAK,QAAQ,eAAe;AAC5B,SAAK,SAAS,EAAE,GAAG,iBAAiB,GAAG,GAAG,OAAO;AAGjD,SAAK,iBAAiB,IAAI;AAAA,MACxB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,WAAW,KAAK,IAAI;AAAA,IAC5D;AAEA,SAAK,gBAAgB,IAAI;AAAA,MACvB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,UAAU,KAAK,IAAI;AAAA,IAC3D;AAEA,SAAK,qBAAqB,IAAI;AAAA,MAC5B,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,eAAe,KAAK,IAAI;AAAA,IAChE;AAEA,SAAK,gBAAgB,IAAI;AAAA,MACvB,KAAK,OAAO;AAAA,MACZ,CAAC,OAAO,KAAK,SAAS,KAAK,IAAI,OAAO,UAAU,KAAK,IAAI;AAAA,IAC3D;AAAA,EACF;AAAA,EAEQ,IACN,OACA,SACA,SACA,MACM;AACN,UAAM,WAAW,MAAM,YAAY;AAGnC,UAAM,SAAS,CAAC,SAAS,QAAQ,QAAQ,OAAO;AAChD,UAAM,cAAc,KAAK,OAAO,SAAS,YAAY;AACrD,QAAI,OAAO,QAAQ,QAAQ,IAAI,OAAO,QAAQ,WAAW,GAAG;AAC1D;AAAA,IACF;AAEA,UAAM,QAAkB;AAAA,MACtB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,UAAU,KAAK,IAAI;AAExC,QAAI;AACF,qBAAe,KAAK,MAAM,SAAS,OAAO;AAAA,IAC5C,QAAQ;AACN,cAAQ,MAAM,IAAI,MAAM,SAAS,KAAK,KAAK,KAAK,OAAO,MAAM,OAAO,EAAE;AAAA,IACxE;AAAA,EACF;AAAA,EAEQ,mBAA4B;AAClC,QAAI,WAAW,KAAK,MAAM,OAAO,GAAG;AAClC,UAAI;AACF,cAAM,cAAc,aAAa,KAAK,MAAM,SAAS,MAAM,EAAE,KAAK;AAClE,cAAM,MAAM,SAAS,aAAa,EAAE;AAGpC,YAAI;AACF,kBAAQ,KAAK,KAAK,CAAC;AACnB,eAAK,IAAI,QAAQ,UAAU,0BAA0B,EAAE,IAAI,CAAC;AAC5D,iBAAO;AAAA,QACT,QAAQ;AAEN,eAAK,IAAI,QAAQ,UAAU,2BAA2B,EAAE,IAAI,CAAC;AAC7D,qBAAW,KAAK,MAAM,OAAO;AAAA,QAC/B;AAAA,MACF,QAAQ;AACN,YAAI;AACF,qBAAW,KAAK,MAAM,OAAO;AAAA,QAC/B,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAqB;AAC3B,kBAAc,KAAK,MAAM,SAAS,QAAQ,IAAI,SAAS,CAAC;AACxD,SAAK,IAAI,QAAQ,UAAU,oBAAoB;AAAA,MAC7C,KAAK,QAAQ;AAAA,MACb,MAAM,KAAK,MAAM;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EAEQ,eAAqB;AAC3B,UAAM,mBAAmB,KAAK,mBAAmB,SAAS;AAC1D,UAAM,cAAc,KAAK,cAAc,SAAS;AAChD,UAAM,SAAuB;AAAA,MAC3B,SAAS;AAAA,MACT,KAAK,QAAQ;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS,KAAK,OAAO,QAAQ;AAAA,UAC7B,SAAS,KAAK,eAAe,SAAS,EAAE,gBAAgB;AAAA,UACxD,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,SAAS,KAAK,cAAc,SAAS,EAAE,gBAAgB;AAAA,UACvD,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS,KAAK,OAAO,YAAY;AAAA,UACjC,SAAS,iBAAiB,eAAe;AAAA,UACzC,oBAAoB,iBAAiB;AAAA,UACrC,aAAa,iBAAiB;AAAA,UAC9B,qBAAqB,iBAAiB;AAAA,UACtC,iBAAiB,iBAAiB;AAAA,UAClC,qBAAqB,iBAAiB;AAAA,QACxC;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,aAAa,YAAY,mBAAmB;AAAA,UAC5C,cAAc,YAAY;AAAA,UAC1B,mBAAmB,YAAY;AAAA,QACjC;AAAA,QACA,WAAW;AAAA,UACT,SAAS,KAAK,OAAO,UAAU;AAAA,QACjC;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,KAAK,eAAe,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,QACjD,GAAG,KAAK,cAAc,SAAS,EAAE,OAAO,MAAM,EAAE;AAAA,QAChD,GAAG,iBAAiB,OAAO,MAAM,EAAE;AAAA,QACnC,GAAG,YAAY,OAAO,MAAM,EAAE;AAAA,MAChC;AAAA,IACF;AAEA,sBAAkB,MAAM;AAAA,EAC1B;AAAA,EAEQ,sBAA4B;AAClC,UAAM,eAAe,CAAC,WAAmB;AACvC,WAAK,IAAI,QAAQ,UAAU,YAAY,MAAM,iBAAiB;AAC9D,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;AAEjD,YAAQ,GAAG,qBAAqB,CAAC,QAAQ;AACvC,WAAK,IAAI,SAAS,UAAU,sBAAsB;AAAA,QAChD,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,UAAU,uBAAuB;AAAA,QACjD,QAAQ,OAAO,MAAM;AAAA,MACvB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,UAAgB;AAEtB,QAAI;AACF,UAAI,WAAW,KAAK,MAAM,OAAO,GAAG;AAClC,mBAAW,KAAK,MAAM,OAAO;AAC7B,aAAK,IAAI,QAAQ,UAAU,kBAAkB;AAAA,MAC/C;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,QAAQ,UAAU,6BAA6B;AAAA,QACtD,OAAO,OAAO,CAAC;AAAA,MACjB,CAAC;AAAA,IACH;AAGA,UAAM,cAA4B;AAAA,MAChC,SAAS;AAAA,MACT,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS;AAAA,UACT,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS;AAAA,UACT,oBACE,KAAK,mBAAmB,SAAS,EAAE;AAAA,UACrC,aAAa,KAAK,mBAAmB,SAAS,EAAE;AAAA,QAClD;AAAA,QACA,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,cAAc,KAAK,cAAc,SAAS,EAAE;AAAA,QAC9C;AAAA,QACA,WAAW,EAAE,SAAS,MAAM;AAAA,MAC9B;AAAA,MACA,QAAQ,CAAC;AAAA,IACX;AACA,sBAAkB,WAAW;AAAA,EAC/B;AAAA,EAEQ,SAAS,QAAsB;AACrC,QAAI,KAAK,eAAgB;AACzB,SAAK,iBAAiB;AAEtB,SAAK,IAAI,QAAQ,UAAU,wBAAwB;AAAA,MACjD;AAAA,MACA,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,cAAc,KAAK,eAAe,SAAS,EAAE;AAAA,MAC7C,aAAa,KAAK,cAAc,SAAS,EAAE;AAAA,MAC3C,iBAAiB,KAAK,mBAAmB,SAAS,EAAE;AAAA,MACpD,gBAAgB,KAAK,cAAc,SAAS,EAAE;AAAA,IAChD,CAAC;AAGD,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AAGA,SAAK,eAAe,KAAK;AACzB,SAAK,cAAc,KAAK;AACxB,SAAK,mBAAmB,KAAK;AAC7B,SAAK,cAAc,KAAK;AAGxB,SAAK,QAAQ;AAEb,UAAM,WACJ,WAAW,aAAa,WAAW,YAAY,WAAW,WACtD,IACA;AACN,YAAQ,KAAK,QAAQ;AAAA,EACvB;AAAA,EAEA,MAAM,QAAuB;AAE3B,QAAI,CAAC,KAAK,iBAAiB,GAAG;AAC5B,WAAK,IAAI,QAAQ,UAAU,kCAAkC;AAC7D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,SAAK,YAAY,KAAK,IAAI;AAG1B,SAAK,aAAa;AAGlB,SAAK,oBAAoB;AAEzB,SAAK,IAAI,QAAQ,UAAU,0BAA0B;AAAA,MACnD,KAAK,QAAQ;AAAA,MACb,QAAQ;AAAA,QACN,SAAS,KAAK,OAAO,QAAQ;AAAA,QAC7B,QAAQ,KAAK,OAAO,OAAO;AAAA,QAC3B,aAAa,KAAK,OAAO,YAAY;AAAA,QACrC,QAAQ,KAAK,OAAO,OAAO;AAAA,QAC3B,WAAW,KAAK,OAAO,UAAU;AAAA,MACnC;AAAA,IACF,CAAC;AAGD,SAAK,eAAe,MAAM;AAC1B,UAAM,KAAK,cAAc,MAAM;AAC/B,SAAK,mBAAmB,MAAM;AAC9B,SAAK,cAAc,MAAM;AAGzB,SAAK,oBAAoB,YAAY,MAAM;AACzC,WAAK,aAAa;AAAA,IACpB,GAAG,KAAK,OAAO,oBAAoB,GAAI;AAGvC,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,YAA0B;AACxB,UAAM,mBAAmB,KAAK,mBAAmB,SAAS;AAC1D,UAAM,cAAc,KAAK,cAAc,SAAS;AAChD,WAAO;AAAA,MACL,SAAS,CAAC,KAAK;AAAA,MACf,KAAK,QAAQ;AAAA,MACb,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,MAC1B,UAAU;AAAA,QACR,SAAS;AAAA,UACP,SAAS,KAAK,OAAO,QAAQ;AAAA,UAC7B,SAAS,KAAK,eAAe,SAAS,EAAE,gBAAgB;AAAA,UACxD,WAAW,KAAK,eAAe,SAAS,EAAE;AAAA,QAC5C;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,SAAS,KAAK,cAAc,SAAS,EAAE,gBAAgB;AAAA,UACvD,WAAW,KAAK,cAAc,SAAS,EAAE;AAAA,QAC3C;AAAA,QACA,aAAa;AAAA,UACX,SAAS,KAAK,OAAO,YAAY;AAAA,UACjC,SAAS,iBAAiB,eAAe;AAAA,UACzC,oBAAoB,iBAAiB;AAAA,UACrC,aAAa,iBAAiB;AAAA,UAC9B,qBAAqB,iBAAiB;AAAA,UACtC,iBAAiB,iBAAiB;AAAA,UAClC,qBAAqB,iBAAiB;AAAA,QACxC;AAAA,QACA,QAAQ;AAAA,UACN,SAAS,KAAK,OAAO,OAAO;AAAA,UAC5B,aAAa,YAAY,mBAAmB;AAAA,UAC5C,cAAc,YAAY;AAAA,UAC1B,mBAAmB,YAAY;AAAA,QACjC;AAAA,QACA,WAAW;AAAA,UACT,SAAS,KAAK,OAAO,UAAU;AAAA,QACjC;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,GAAG,KAAK,eAAe,SAAS,EAAE;AAAA,QAClC,GAAG,KAAK,cAAc,SAAS,EAAE;AAAA,QACjC,GAAG,iBAAiB;AAAA,QACpB,GAAG,YAAY;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;AAGA,IACE,YAAY,QAAQ,UAAU,QAAQ,KAAK,CAAC,CAAC,MAC7C,QAAQ,KAAK,CAAC,GAAG,SAAS,mBAAmB,GAC7C;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,SAAgC,CAAC;AAGvC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,qBAAqB,KAAK,IAAI,CAAC,GAAG;AAC5C,aAAO,UAAU,EAAE,SAAS,MAAM,UAAU,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE;AACtE;AAAA,IACF,WAAW,QAAQ,uBAAuB,KAAK,IAAI,CAAC,GAAG;AACrD,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,UAAU,SAAS,KAAK,IAAI,CAAC,GAAG,EAAE;AAAA,QAClC,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AACA;AAAA,IACF,WAAW,QAAQ,eAAe;AAChC,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,UAAU;AAAA,QACV,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF,WAAW,QAAQ,iBAAiB,KAAK,IAAI,CAAC,GAAG;AAC/C,aAAO,WAAW,KAAK,IAAI,CAAC;AAC5B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,cAAc,MAAM;AACvC,SAAO,MAAM,EAAE,MAAM,CAAC,QAAQ;AAC5B,YAAQ,MAAM,2BAA2B,GAAG;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -29,7 +29,8 @@ class PtyWrapper {
|
|
|
29
29
|
this.config = {
|
|
30
30
|
claudeBin: config.claudeBin || this.findClaude(),
|
|
31
31
|
claudeArgs: config.claudeArgs || [],
|
|
32
|
-
stateFile: config.stateFile || getSweepPath("sweep-state.json")
|
|
32
|
+
stateFile: config.stateFile || getSweepPath("sweep-state.json"),
|
|
33
|
+
initialInput: config.initialInput || ""
|
|
33
34
|
};
|
|
34
35
|
this.stateWatcher = new SweepStateWatcher(this.config.stateFile);
|
|
35
36
|
this.statusBar = new StatusBar();
|
|
@@ -69,10 +70,19 @@ class PtyWrapper {
|
|
|
69
70
|
process.stdin.setRawMode(true);
|
|
70
71
|
}
|
|
71
72
|
process.stdin.resume();
|
|
73
|
+
let inputInjected = false;
|
|
74
|
+
const initialInput = this.config.initialInput;
|
|
75
|
+
const ptyRef = this.ptyProcess;
|
|
72
76
|
this.ptyProcess.onData((data) => {
|
|
73
77
|
if (data.includes(ALT_SCREEN_ENTER)) {
|
|
74
78
|
this.inAltScreen = true;
|
|
75
79
|
this.statusBar.hide();
|
|
80
|
+
if (!inputInjected && initialInput && ptyRef) {
|
|
81
|
+
inputInjected = true;
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
ptyRef.write("\x1B[200~" + initialInput + "\x1B[201~");
|
|
84
|
+
}, 800);
|
|
85
|
+
}
|
|
76
86
|
}
|
|
77
87
|
if (data.includes(ALT_SCREEN_EXIT)) {
|
|
78
88
|
this.inAltScreen = false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/features/sweep/pty-wrapper.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Sweep PTY Wrapper\n *\n * Wraps Claude Code in a pseudo-terminal to add a Sweep prediction\n * status bar at the bottom of the terminal. Predictions from the\n * PostToolUse hook are displayed via the status bar. Tab to accept,\n * Esc to dismiss.\n */\n\nimport { join } from 'path';\nimport { writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { execSync } from 'child_process';\nimport { SweepStateWatcher, type PredictionEvent } from './state-watcher.js';\nimport { StatusBar } from './status-bar.js';\nimport { TabInterceptor } from './tab-interceptor.js';\n\nconst HOME = process.env['HOME'] || '/tmp';\n\nfunction getSweepDir(): string {\n return process.env['SWEEP_STATE_DIR'] || join(HOME, '.stackmemory');\n}\n\nfunction getSweepPath(filename: string): string {\n return join(getSweepDir(), filename);\n}\n\n// Alt screen buffer detection\nconst ALT_SCREEN_ENTER = '\\x1b[?1049h';\nconst ALT_SCREEN_EXIT = '\\x1b[?1049l';\n\nexport interface PtyWrapperConfig {\n claudeBin?: string;\n claudeArgs?: string[];\n stateFile?: string;\n}\n\n// Minimal interface for node-pty process to avoid compile-time dep\ninterface PtyProcess {\n write(data: string): void;\n resize(cols: number, rows: number): void;\n onData(cb: (data: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n kill(): void;\n}\n\nexport class PtyWrapper {\n private config: Required<PtyWrapperConfig>;\n private stateWatcher: SweepStateWatcher;\n private statusBar: StatusBar;\n private tabInterceptor: TabInterceptor;\n private currentPrediction: PredictionEvent | null = null;\n private inAltScreen = false;\n private ptyProcess: PtyProcess | null = null;\n\n constructor(config: PtyWrapperConfig = {}) {\n this.config = {\n claudeBin: config.claudeBin || this.findClaude(),\n claudeArgs: config.claudeArgs || [],\n stateFile: config.stateFile || getSweepPath('sweep-state.json'),\n };\n\n this.stateWatcher = new SweepStateWatcher(this.config.stateFile);\n this.statusBar = new StatusBar();\n this.tabInterceptor = new TabInterceptor({\n onAccept: () => this.acceptPrediction(),\n onDismiss: () => this.dismissPrediction(),\n onPassthrough: (data) => this.ptyProcess?.write(data.toString('utf-8')),\n });\n }\n\n async start(): Promise<void> {\n // Ensure the sweep state directory exists\n const sweepDir = getSweepDir();\n if (!existsSync(sweepDir)) {\n mkdirSync(sweepDir, { recursive: true });\n }\n\n // Dynamic import for optional dependency\n let pty: typeof import('node-pty');\n try {\n pty = await import('node-pty');\n } catch {\n throw new Error(\n 'node-pty is required for the PTY wrapper.\\n' +\n 'Install with: npm install node-pty'\n );\n }\n\n const cols = process.stdout.columns || 80;\n const rows = process.stdout.rows || 24;\n\n // Filter undefined values from env\n const env: Record<string, string> = {};\n for (const [k, v] of Object.entries(process.env)) {\n if (v !== undefined) env[k] = v;\n }\n\n // Spawn Claude Code in a PTY with 1 row reserved for status bar\n this.ptyProcess = pty.spawn(this.config.claudeBin, this.config.claudeArgs, {\n name: process.env['TERM'] || 'xterm-256color',\n cols,\n rows: rows - 1,\n cwd: process.cwd(),\n env,\n }) as PtyProcess;\n\n // Set raw mode on stdin\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true);\n }\n process.stdin.resume();\n\n // PTY stdout -> parent stdout (transparent passthrough)\n this.ptyProcess.onData((data: string) => {\n // Detect alt screen buffer transitions\n if (data.includes(ALT_SCREEN_ENTER)) {\n this.inAltScreen = true;\n this.statusBar.hide();\n }\n if (data.includes(ALT_SCREEN_EXIT)) {\n this.inAltScreen = false;\n }\n\n process.stdout.write(data);\n });\n\n // Parent stdin -> tab interceptor -> PTY\n process.stdin.on('data', (data: Buffer) => {\n this.tabInterceptor.process(data);\n });\n\n // State watcher -> status bar\n this.stateWatcher.on('loading', () => {\n if (!this.inAltScreen) {\n this.statusBar.showLoading();\n }\n });\n\n this.stateWatcher.on('prediction', (event: PredictionEvent) => {\n this.currentPrediction = event;\n this.tabInterceptor.setPredictionActive(true);\n if (!this.inAltScreen) {\n this.statusBar.show(\n event.prediction,\n event.file_path,\n event.latency_ms\n );\n }\n });\n\n this.stateWatcher.start();\n\n // Handle terminal resize\n process.stdout.on('resize', () => {\n const newCols = process.stdout.columns || 80;\n const newRows = process.stdout.rows || 24;\n this.ptyProcess?.resize(newCols, newRows - 1);\n this.statusBar.resize(newRows, newCols);\n });\n\n // Handle PTY exit\n this.ptyProcess.onExit(({ exitCode }) => {\n this.cleanup();\n process.exit(exitCode);\n });\n\n // Handle signals\n const onSignal = () => {\n this.cleanup();\n process.exit(0);\n };\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n }\n\n private acceptPrediction(): void {\n if (!this.currentPrediction || !this.ptyProcess) return;\n\n // Write prediction to pending file for Claude to read\n const dir = getSweepDir();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const pendingFile = getSweepPath('sweep-pending.json');\n writeFileSync(\n pendingFile,\n JSON.stringify(\n {\n file_path: this.currentPrediction.file_path,\n predicted_content: this.currentPrediction.prediction,\n timestamp: Date.now(),\n },\n null,\n 2\n )\n );\n\n // Inject acceptance prompt into PTY stdin.\n // SAFETY: pendingFile is derived from env or a constant path, not\n // arbitrary user input. The prompt is written to Claude Code's input,\n // which interprets it as a user message, not as a shell command.\n const prompt = `Apply the Sweep prediction from ${pendingFile}\\n`;\n this.ptyProcess.write(prompt);\n\n this.dismissPrediction();\n }\n\n private dismissPrediction(): void {\n this.currentPrediction = null;\n this.tabInterceptor.setPredictionActive(false);\n this.statusBar.hide();\n }\n\n private cleanup(): void {\n this.stateWatcher.stop();\n this.statusBar.hide();\n\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n }\n\n private findClaude(): string {\n // Check PATH first via which\n try {\n const resolved = execSync('which claude', { encoding: 'utf-8' }).trim();\n if (resolved) return resolved;\n } catch {\n // Not on PATH\n }\n\n // Check known locations\n const candidates = [\n join(HOME, '.bun', 'bin', 'claude'),\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n\n return 'claude';\n }\n}\n\n/**\n * Launch the PTY wrapper\n */\nexport async function launchWrapper(config?: PtyWrapperConfig): Promise<void> {\n const wrapper = new PtyWrapper(config);\n await wrapper.start();\n}\n"],
|
|
5
|
-
"mappings": ";;;;AASA,SAAS,YAAY;AACrB,SAAS,eAAe,YAAY,iBAAiB;AACrD,SAAS,gBAAgB;AACzB,SAAS,yBAA+C;AACxD,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAE/B,MAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAEpC,SAAS,cAAsB;AAC7B,SAAO,QAAQ,IAAI,iBAAiB,KAAK,KAAK,MAAM,cAAc;AACpE;AAEA,SAAS,aAAa,UAA0B;AAC9C,SAAO,KAAK,YAAY,GAAG,QAAQ;AACrC;AAGA,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;
|
|
4
|
+
"sourcesContent": ["/**\n * Sweep PTY Wrapper\n *\n * Wraps Claude Code in a pseudo-terminal to add a Sweep prediction\n * status bar at the bottom of the terminal. Predictions from the\n * PostToolUse hook are displayed via the status bar. Tab to accept,\n * Esc to dismiss.\n */\n\nimport { join } from 'path';\nimport { writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { execSync } from 'child_process';\nimport { SweepStateWatcher, type PredictionEvent } from './state-watcher.js';\nimport { StatusBar } from './status-bar.js';\nimport { TabInterceptor } from './tab-interceptor.js';\n\nconst HOME = process.env['HOME'] || '/tmp';\n\nfunction getSweepDir(): string {\n return process.env['SWEEP_STATE_DIR'] || join(HOME, '.stackmemory');\n}\n\nfunction getSweepPath(filename: string): string {\n return join(getSweepDir(), filename);\n}\n\n// Alt screen buffer detection\nconst ALT_SCREEN_ENTER = '\\x1b[?1049h';\nconst ALT_SCREEN_EXIT = '\\x1b[?1049l';\n\nexport interface PtyWrapperConfig {\n claudeBin?: string;\n claudeArgs?: string[];\n stateFile?: string;\n initialInput?: string;\n}\n\n// Minimal interface for node-pty process to avoid compile-time dep\ninterface PtyProcess {\n write(data: string): void;\n resize(cols: number, rows: number): void;\n onData(cb: (data: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n kill(): void;\n}\n\nexport class PtyWrapper {\n private config: Required<PtyWrapperConfig>;\n private stateWatcher: SweepStateWatcher;\n private statusBar: StatusBar;\n private tabInterceptor: TabInterceptor;\n private currentPrediction: PredictionEvent | null = null;\n private inAltScreen = false;\n private ptyProcess: PtyProcess | null = null;\n\n constructor(config: PtyWrapperConfig = {}) {\n this.config = {\n claudeBin: config.claudeBin || this.findClaude(),\n claudeArgs: config.claudeArgs || [],\n stateFile: config.stateFile || getSweepPath('sweep-state.json'),\n initialInput: config.initialInput || '',\n };\n\n this.stateWatcher = new SweepStateWatcher(this.config.stateFile);\n this.statusBar = new StatusBar();\n this.tabInterceptor = new TabInterceptor({\n onAccept: () => this.acceptPrediction(),\n onDismiss: () => this.dismissPrediction(),\n onPassthrough: (data) => this.ptyProcess?.write(data.toString('utf-8')),\n });\n }\n\n async start(): Promise<void> {\n // Ensure the sweep state directory exists\n const sweepDir = getSweepDir();\n if (!existsSync(sweepDir)) {\n mkdirSync(sweepDir, { recursive: true });\n }\n\n // Dynamic import for optional dependency\n let pty: typeof import('node-pty');\n try {\n pty = await import('node-pty');\n } catch {\n throw new Error(\n 'node-pty is required for the PTY wrapper.\\n' +\n 'Install with: npm install node-pty'\n );\n }\n\n const cols = process.stdout.columns || 80;\n const rows = process.stdout.rows || 24;\n\n // Filter undefined values from env\n const env: Record<string, string> = {};\n for (const [k, v] of Object.entries(process.env)) {\n if (v !== undefined) env[k] = v;\n }\n\n // Spawn Claude Code in a PTY with 1 row reserved for status bar\n this.ptyProcess = pty.spawn(this.config.claudeBin, this.config.claudeArgs, {\n name: process.env['TERM'] || 'xterm-256color',\n cols,\n rows: rows - 1,\n cwd: process.cwd(),\n env,\n }) as PtyProcess;\n\n // Set raw mode on stdin\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true);\n }\n process.stdin.resume();\n\n // PTY stdout -> parent stdout (transparent passthrough)\n let inputInjected = false;\n const initialInput = this.config.initialInput;\n const ptyRef = this.ptyProcess;\n\n this.ptyProcess.onData((data: string) => {\n // Detect alt screen buffer transitions\n if (data.includes(ALT_SCREEN_ENTER)) {\n this.inAltScreen = true;\n this.statusBar.hide();\n\n // Inject initial input as bracketed paste after Claude UI enters alt screen\n if (!inputInjected && initialInput && ptyRef) {\n inputInjected = true;\n setTimeout(() => {\n // Bracketed paste mode: text appears in input area without auto-sending\n ptyRef.write('\\x1b[200~' + initialInput + '\\x1b[201~');\n }, 800);\n }\n }\n if (data.includes(ALT_SCREEN_EXIT)) {\n this.inAltScreen = false;\n }\n\n process.stdout.write(data);\n });\n\n // Parent stdin -> tab interceptor -> PTY\n process.stdin.on('data', (data: Buffer) => {\n this.tabInterceptor.process(data);\n });\n\n // State watcher -> status bar\n this.stateWatcher.on('loading', () => {\n if (!this.inAltScreen) {\n this.statusBar.showLoading();\n }\n });\n\n this.stateWatcher.on('prediction', (event: PredictionEvent) => {\n this.currentPrediction = event;\n this.tabInterceptor.setPredictionActive(true);\n if (!this.inAltScreen) {\n this.statusBar.show(\n event.prediction,\n event.file_path,\n event.latency_ms\n );\n }\n });\n\n this.stateWatcher.start();\n\n // Handle terminal resize\n process.stdout.on('resize', () => {\n const newCols = process.stdout.columns || 80;\n const newRows = process.stdout.rows || 24;\n this.ptyProcess?.resize(newCols, newRows - 1);\n this.statusBar.resize(newRows, newCols);\n });\n\n // Handle PTY exit\n this.ptyProcess.onExit(({ exitCode }) => {\n this.cleanup();\n process.exit(exitCode);\n });\n\n // Handle signals\n const onSignal = () => {\n this.cleanup();\n process.exit(0);\n };\n process.on('SIGINT', onSignal);\n process.on('SIGTERM', onSignal);\n }\n\n private acceptPrediction(): void {\n if (!this.currentPrediction || !this.ptyProcess) return;\n\n // Write prediction to pending file for Claude to read\n const dir = getSweepDir();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n const pendingFile = getSweepPath('sweep-pending.json');\n writeFileSync(\n pendingFile,\n JSON.stringify(\n {\n file_path: this.currentPrediction.file_path,\n predicted_content: this.currentPrediction.prediction,\n timestamp: Date.now(),\n },\n null,\n 2\n )\n );\n\n // Inject acceptance prompt into PTY stdin.\n // SAFETY: pendingFile is derived from env or a constant path, not\n // arbitrary user input. The prompt is written to Claude Code's input,\n // which interprets it as a user message, not as a shell command.\n const prompt = `Apply the Sweep prediction from ${pendingFile}\\n`;\n this.ptyProcess.write(prompt);\n\n this.dismissPrediction();\n }\n\n private dismissPrediction(): void {\n this.currentPrediction = null;\n this.tabInterceptor.setPredictionActive(false);\n this.statusBar.hide();\n }\n\n private cleanup(): void {\n this.stateWatcher.stop();\n this.statusBar.hide();\n\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n }\n\n private findClaude(): string {\n // Check PATH first via which\n try {\n const resolved = execSync('which claude', { encoding: 'utf-8' }).trim();\n if (resolved) return resolved;\n } catch {\n // Not on PATH\n }\n\n // Check known locations\n const candidates = [\n join(HOME, '.bun', 'bin', 'claude'),\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\n for (const c of candidates) {\n if (existsSync(c)) return c;\n }\n\n return 'claude';\n }\n}\n\n/**\n * Launch the PTY wrapper\n */\nexport async function launchWrapper(config?: PtyWrapperConfig): Promise<void> {\n const wrapper = new PtyWrapper(config);\n await wrapper.start();\n}\n"],
|
|
5
|
+
"mappings": ";;;;AASA,SAAS,YAAY;AACrB,SAAS,eAAe,YAAY,iBAAiB;AACrD,SAAS,gBAAgB;AACzB,SAAS,yBAA+C;AACxD,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAE/B,MAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAEpC,SAAS,cAAsB;AAC7B,SAAO,QAAQ,IAAI,iBAAiB,KAAK,KAAK,MAAM,cAAc;AACpE;AAEA,SAAS,aAAa,UAA0B;AAC9C,SAAO,KAAK,YAAY,GAAG,QAAQ;AACrC;AAGA,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AAkBjB,MAAM,WAAW;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAA4C;AAAA,EAC5C,cAAc;AAAA,EACd,aAAgC;AAAA,EAExC,YAAY,SAA2B,CAAC,GAAG;AACzC,SAAK,SAAS;AAAA,MACZ,WAAW,OAAO,aAAa,KAAK,WAAW;AAAA,MAC/C,YAAY,OAAO,cAAc,CAAC;AAAA,MAClC,WAAW,OAAO,aAAa,aAAa,kBAAkB;AAAA,MAC9D,cAAc,OAAO,gBAAgB;AAAA,IACvC;AAEA,SAAK,eAAe,IAAI,kBAAkB,KAAK,OAAO,SAAS;AAC/D,SAAK,YAAY,IAAI,UAAU;AAC/B,SAAK,iBAAiB,IAAI,eAAe;AAAA,MACvC,UAAU,MAAM,KAAK,iBAAiB;AAAA,MACtC,WAAW,MAAM,KAAK,kBAAkB;AAAA,MACxC,eAAe,CAAC,SAAS,KAAK,YAAY,MAAM,KAAK,SAAS,OAAO,CAAC;AAAA,IACxE,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAE3B,UAAM,WAAW,YAAY;AAC7B,QAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,gBAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,IACzC;AAGA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,OAAO,UAAU;AAAA,IAC/B,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AAEA,UAAM,OAAO,QAAQ,OAAO,WAAW;AACvC,UAAM,OAAO,QAAQ,OAAO,QAAQ;AAGpC,UAAM,MAA8B,CAAC;AACrC,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,GAAG,GAAG;AAChD,UAAI,MAAM,OAAW,KAAI,CAAC,IAAI;AAAA,IAChC;AAGA,SAAK,aAAa,IAAI,MAAM,KAAK,OAAO,WAAW,KAAK,OAAO,YAAY;AAAA,MACzE,MAAM,QAAQ,IAAI,MAAM,KAAK;AAAA,MAC7B;AAAA,MACA,MAAM,OAAO;AAAA,MACb,KAAK,QAAQ,IAAI;AAAA,MACjB;AAAA,IACF,CAAC;AAGD,QAAI,QAAQ,MAAM,OAAO;AACvB,cAAQ,MAAM,WAAW,IAAI;AAAA,IAC/B;AACA,YAAQ,MAAM,OAAO;AAGrB,QAAI,gBAAgB;AACpB,UAAM,eAAe,KAAK,OAAO;AACjC,UAAM,SAAS,KAAK;AAEpB,SAAK,WAAW,OAAO,CAAC,SAAiB;AAEvC,UAAI,KAAK,SAAS,gBAAgB,GAAG;AACnC,aAAK,cAAc;AACnB,aAAK,UAAU,KAAK;AAGpB,YAAI,CAAC,iBAAiB,gBAAgB,QAAQ;AAC5C,0BAAgB;AAChB,qBAAW,MAAM;AAEf,mBAAO,MAAM,cAAc,eAAe,WAAW;AAAA,UACvD,GAAG,GAAG;AAAA,QACR;AAAA,MACF;AACA,UAAI,KAAK,SAAS,eAAe,GAAG;AAClC,aAAK,cAAc;AAAA,MACrB;AAEA,cAAQ,OAAO,MAAM,IAAI;AAAA,IAC3B,CAAC;AAGD,YAAQ,MAAM,GAAG,QAAQ,CAAC,SAAiB;AACzC,WAAK,eAAe,QAAQ,IAAI;AAAA,IAClC,CAAC;AAGD,SAAK,aAAa,GAAG,WAAW,MAAM;AACpC,UAAI,CAAC,KAAK,aAAa;AACrB,aAAK,UAAU,YAAY;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,SAAK,aAAa,GAAG,cAAc,CAAC,UAA2B;AAC7D,WAAK,oBAAoB;AACzB,WAAK,eAAe,oBAAoB,IAAI;AAC5C,UAAI,CAAC,KAAK,aAAa;AACrB,aAAK,UAAU;AAAA,UACb,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF,CAAC;AAED,SAAK,aAAa,MAAM;AAGxB,YAAQ,OAAO,GAAG,UAAU,MAAM;AAChC,YAAM,UAAU,QAAQ,OAAO,WAAW;AAC1C,YAAM,UAAU,QAAQ,OAAO,QAAQ;AACvC,WAAK,YAAY,OAAO,SAAS,UAAU,CAAC;AAC5C,WAAK,UAAU,OAAO,SAAS,OAAO;AAAA,IACxC,CAAC;AAGD,SAAK,WAAW,OAAO,CAAC,EAAE,SAAS,MAAM;AACvC,WAAK,QAAQ;AACb,cAAQ,KAAK,QAAQ;AAAA,IACvB,CAAC;AAGD,UAAM,WAAW,MAAM;AACrB,WAAK,QAAQ;AACb,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,YAAQ,GAAG,UAAU,QAAQ;AAC7B,YAAQ,GAAG,WAAW,QAAQ;AAAA,EAChC;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,qBAAqB,CAAC,KAAK,WAAY;AAGjD,UAAM,MAAM,YAAY;AACxB,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,gBAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACpC;AACA,UAAM,cAAc,aAAa,oBAAoB;AACrD;AAAA,MACE;AAAA,MACA,KAAK;AAAA,QACH;AAAA,UACE,WAAW,KAAK,kBAAkB;AAAA,UAClC,mBAAmB,KAAK,kBAAkB;AAAA,UAC1C,WAAW,KAAK,IAAI;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAMA,UAAM,SAAS,mCAAmC,WAAW;AAAA;AAC7D,SAAK,WAAW,MAAM,MAAM;AAE5B,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEQ,oBAA0B;AAChC,SAAK,oBAAoB;AACzB,SAAK,eAAe,oBAAoB,KAAK;AAC7C,SAAK,UAAU,KAAK;AAAA,EACtB;AAAA,EAEQ,UAAgB;AACtB,SAAK,aAAa,KAAK;AACvB,SAAK,UAAU,KAAK;AAEpB,QAAI,QAAQ,MAAM,OAAO;AACvB,cAAQ,MAAM,WAAW,KAAK;AAAA,IAChC;AACA,YAAQ,MAAM,MAAM;AAAA,EACtB;AAAA,EAEQ,aAAqB;AAE3B,QAAI;AACF,YAAM,WAAW,SAAS,gBAAgB,EAAE,UAAU,QAAQ,CAAC,EAAE,KAAK;AACtE,UAAI,SAAU,QAAO;AAAA,IACvB,QAAQ;AAAA,IAER;AAGA,UAAM,aAAa;AAAA,MACjB,KAAK,MAAM,QAAQ,OAAO,QAAQ;AAAA,MAClC;AAAA,MACA;AAAA,IACF;AAEA,eAAW,KAAK,YAAY;AAC1B,UAAI,WAAW,CAAC,EAAG,QAAO;AAAA,IAC5B;AAEA,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,cAAc,QAA0C;AAC5E,QAAM,UAAU,IAAI,WAAW,MAAM;AACrC,QAAM,QAAQ,MAAM;AACtB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/src/hooks/index.js
CHANGED
|
@@ -6,7 +6,4 @@ export * from "./events.js";
|
|
|
6
6
|
export * from "./config.js";
|
|
7
7
|
export * from "./daemon.js";
|
|
8
8
|
export * from "./auto-background.js";
|
|
9
|
-
export * from "./sms-notify.js";
|
|
10
|
-
export * from "./sms-webhook.js";
|
|
11
|
-
export * from "./sms-action-runner.js";
|
|
12
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/hooks/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * StackMemory Hooks Module\n * User-configurable hook system for automation and suggestions\n */\n\nexport * from './events.js';\nexport * from './config.js';\nexport * from './daemon.js';\nexport * from './auto-background.js';\
|
|
5
|
-
"mappings": ";;;;AAKA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;
|
|
4
|
+
"sourcesContent": ["/**\n * StackMemory Hooks Module\n * User-configurable hook system for automation and suggestions\n */\n\nexport * from './events.js';\nexport * from './config.js';\nexport * from './daemon.js';\nexport * from './auto-background.js';\n"],
|
|
5
|
+
"mappings": ";;;;AAKA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,63 +4,6 @@ const __filename = __fileURLToPath(import.meta.url);
|
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { logConfigInvalid } from "./security-logger.js";
|
|
7
|
-
const PromptOptionSchema = z.object({
|
|
8
|
-
key: z.string().max(10),
|
|
9
|
-
label: z.string().max(200),
|
|
10
|
-
action: z.string().max(500).optional()
|
|
11
|
-
});
|
|
12
|
-
const PendingPromptSchema = z.object({
|
|
13
|
-
id: z.string().max(32),
|
|
14
|
-
timestamp: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)),
|
|
15
|
-
message: z.string().max(1e3),
|
|
16
|
-
options: z.array(PromptOptionSchema).max(10),
|
|
17
|
-
type: z.enum(["options", "yesno", "freeform"]),
|
|
18
|
-
callback: z.string().max(500).optional(),
|
|
19
|
-
expiresAt: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
|
|
20
|
-
});
|
|
21
|
-
const NotifyOnSchema = z.object({
|
|
22
|
-
taskComplete: z.boolean(),
|
|
23
|
-
reviewReady: z.boolean(),
|
|
24
|
-
error: z.boolean(),
|
|
25
|
-
custom: z.boolean(),
|
|
26
|
-
contextSync: z.boolean().optional().default(true)
|
|
27
|
-
});
|
|
28
|
-
const QuietHoursSchema = z.object({
|
|
29
|
-
enabled: z.boolean(),
|
|
30
|
-
start: z.string().regex(/^\d{2}:\d{2}$/),
|
|
31
|
-
end: z.string().regex(/^\d{2}:\d{2}$/)
|
|
32
|
-
});
|
|
33
|
-
const SMSConfigSchema = z.object({
|
|
34
|
-
enabled: z.boolean(),
|
|
35
|
-
channel: z.enum(["whatsapp", "sms"]),
|
|
36
|
-
accountSid: z.string().max(100).optional(),
|
|
37
|
-
authToken: z.string().max(100).optional(),
|
|
38
|
-
smsFromNumber: z.string().max(20).optional(),
|
|
39
|
-
smsToNumber: z.string().max(20).optional(),
|
|
40
|
-
whatsappFromNumber: z.string().max(30).optional(),
|
|
41
|
-
whatsappToNumber: z.string().max(30).optional(),
|
|
42
|
-
fromNumber: z.string().max(20).optional(),
|
|
43
|
-
toNumber: z.string().max(20).optional(),
|
|
44
|
-
webhookUrl: z.string().url().max(500).optional(),
|
|
45
|
-
notifyOn: NotifyOnSchema,
|
|
46
|
-
quietHours: QuietHoursSchema.optional(),
|
|
47
|
-
responseTimeout: z.number().int().min(30).max(3600),
|
|
48
|
-
pendingPrompts: z.array(PendingPromptSchema).max(100)
|
|
49
|
-
});
|
|
50
|
-
const PendingActionSchema = z.object({
|
|
51
|
-
id: z.string().max(32),
|
|
52
|
-
promptId: z.string().max(32),
|
|
53
|
-
response: z.string().max(1e3),
|
|
54
|
-
action: z.string().max(500),
|
|
55
|
-
timestamp: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)),
|
|
56
|
-
status: z.enum(["pending", "running", "completed", "failed"]),
|
|
57
|
-
result: z.string().max(1e4).optional(),
|
|
58
|
-
error: z.string().max(1e3).optional()
|
|
59
|
-
});
|
|
60
|
-
const ActionQueueSchema = z.object({
|
|
61
|
-
actions: z.array(PendingActionSchema).max(1e3),
|
|
62
|
-
lastChecked: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
|
|
63
|
-
});
|
|
64
7
|
const AutoBackgroundConfigSchema = z.object({
|
|
65
8
|
enabled: z.boolean(),
|
|
66
9
|
timeoutMs: z.number().int().min(1e3).max(6e5),
|
|
@@ -68,53 +11,6 @@ const AutoBackgroundConfigSchema = z.object({
|
|
|
68
11
|
neverBackground: z.array(z.string().max(200)).max(100),
|
|
69
12
|
verbose: z.boolean().optional()
|
|
70
13
|
});
|
|
71
|
-
const SyncOptionsSchema = z.object({
|
|
72
|
-
autoSyncOnClose: z.boolean(),
|
|
73
|
-
minFrameDuration: z.number().int().min(0).max(3600),
|
|
74
|
-
// 0 to 1 hour
|
|
75
|
-
includeDecisions: z.boolean(),
|
|
76
|
-
includeFiles: z.boolean(),
|
|
77
|
-
includeTests: z.boolean(),
|
|
78
|
-
maxDigestLength: z.number().int().min(100).max(1e3)
|
|
79
|
-
// WhatsApp limit ~4096 chars
|
|
80
|
-
});
|
|
81
|
-
const ScheduleConfigSchema = z.object({
|
|
82
|
-
type: z.enum(["daily", "hourly", "interval"]),
|
|
83
|
-
time: z.string().regex(/^\d{2}:\d{2}$/).optional(),
|
|
84
|
-
// "HH:MM" for daily
|
|
85
|
-
intervalMinutes: z.number().int().min(5).max(1440).optional(),
|
|
86
|
-
// 5 min to 24 hours
|
|
87
|
-
includeInactive: z.boolean(),
|
|
88
|
-
// Include when no activity
|
|
89
|
-
quietHoursRespect: z.boolean()
|
|
90
|
-
// Respect quiet hours setting
|
|
91
|
-
});
|
|
92
|
-
const ScheduleSchema = z.object({
|
|
93
|
-
id: z.string().max(32),
|
|
94
|
-
config: ScheduleConfigSchema,
|
|
95
|
-
enabled: z.boolean(),
|
|
96
|
-
lastRun: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)).optional(),
|
|
97
|
-
nextRun: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/)).optional(),
|
|
98
|
-
createdAt: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
|
|
99
|
-
});
|
|
100
|
-
const ScheduleStorageSchema = z.object({
|
|
101
|
-
schedules: z.array(ScheduleSchema).max(10),
|
|
102
|
-
lastChecked: z.string().datetime({ offset: true }).or(z.string().regex(/^\d{4}-\d{2}-\d{2}T/))
|
|
103
|
-
});
|
|
104
|
-
const WhatsAppCommandSchema = z.object({
|
|
105
|
-
name: z.string().max(50),
|
|
106
|
-
description: z.string().max(200),
|
|
107
|
-
enabled: z.boolean(),
|
|
108
|
-
action: z.string().max(500).optional(),
|
|
109
|
-
// Safe action to execute
|
|
110
|
-
requiresArg: z.boolean().optional(),
|
|
111
|
-
argPattern: z.string().max(100).optional()
|
|
112
|
-
// Regex pattern for arg validation
|
|
113
|
-
});
|
|
114
|
-
const WhatsAppCommandsConfigSchema = z.object({
|
|
115
|
-
enabled: z.boolean(),
|
|
116
|
-
commands: z.array(WhatsAppCommandSchema).max(50)
|
|
117
|
-
});
|
|
118
14
|
const ModelProviderSchema = z.enum([
|
|
119
15
|
"anthropic",
|
|
120
16
|
"qwen",
|
|
@@ -175,23 +71,10 @@ function parseConfigSafe(schema, data, defaultValue, configName) {
|
|
|
175
71
|
return defaultValue;
|
|
176
72
|
}
|
|
177
73
|
export {
|
|
178
|
-
ActionQueueSchema,
|
|
179
74
|
AutoBackgroundConfigSchema,
|
|
180
75
|
ModelConfigSchema,
|
|
181
76
|
ModelProviderSchema,
|
|
182
77
|
ModelRouterConfigSchema,
|
|
183
|
-
NotifyOnSchema,
|
|
184
|
-
PendingActionSchema,
|
|
185
|
-
PendingPromptSchema,
|
|
186
|
-
PromptOptionSchema,
|
|
187
|
-
QuietHoursSchema,
|
|
188
|
-
SMSConfigSchema,
|
|
189
|
-
ScheduleConfigSchema,
|
|
190
|
-
ScheduleSchema,
|
|
191
|
-
ScheduleStorageSchema,
|
|
192
|
-
SyncOptionsSchema,
|
|
193
|
-
WhatsAppCommandSchema,
|
|
194
|
-
WhatsAppCommandsConfigSchema,
|
|
195
78
|
parseConfigSafe
|
|
196
79
|
};
|
|
197
80
|
//# sourceMappingURL=schemas.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/hooks/schemas.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Zod schemas for hook configuration validation\n * Prevents malformed or malicious configs from being loaded\n */\n\nimport { z } from 'zod';\nimport { logConfigInvalid } from './security-logger.js';\n\n//
|
|
5
|
-
"mappings": ";;;;AAKA,SAAS,SAAS;AAClB,SAAS,wBAAwB;AAG1B,MAAM,
|
|
4
|
+
"sourcesContent": ["/**\n * Zod schemas for hook configuration validation\n * Prevents malformed or malicious configs from being loaded\n */\n\nimport { z } from 'zod';\nimport { logConfigInvalid } from './security-logger.js';\n\n// Auto-background config schema\nexport const AutoBackgroundConfigSchema = z.object({\n enabled: z.boolean(),\n timeoutMs: z.number().int().min(1000).max(600000),\n alwaysBackground: z.array(z.string().max(200)).max(100),\n neverBackground: z.array(z.string().max(200)).max(100),\n verbose: z.boolean().optional(),\n});\n\n// Model Router schemas\nexport const ModelProviderSchema = z.enum([\n 'anthropic',\n 'qwen',\n 'openai',\n 'ollama',\n 'custom',\n]);\n\nexport const ModelConfigSchema = z.object({\n provider: ModelProviderSchema,\n model: z.string().max(100),\n baseUrl: z.string().url().max(500).optional(),\n apiKeyEnv: z.string().max(100),\n headers: z.record(z.string().max(500)).optional(),\n params: z.record(z.unknown()).optional(),\n});\n\nexport const ModelRouterConfigSchema = z.object({\n enabled: z.boolean(),\n defaultProvider: ModelProviderSchema,\n taskRouting: z\n .object({\n plan: ModelProviderSchema.optional(),\n think: ModelProviderSchema.optional(),\n code: ModelProviderSchema.optional(),\n review: ModelProviderSchema.optional(),\n })\n .optional()\n .default({}),\n fallback: z.object({\n enabled: z.boolean(),\n provider: ModelProviderSchema,\n onRateLimit: z.boolean(),\n onError: z.boolean(),\n onTimeout: z.boolean(),\n maxRetries: z.number().int().min(0).max(10),\n retryDelayMs: z.number().int().min(100).max(30000),\n }),\n providers: z\n .object({\n anthropic: ModelConfigSchema.optional(),\n qwen: ModelConfigSchema.optional(),\n openai: ModelConfigSchema.optional(),\n ollama: ModelConfigSchema.optional(),\n custom: ModelConfigSchema.optional(),\n })\n .optional()\n .default({}),\n thinkingMode: z.object({\n enabled: z.boolean(),\n budget: z.number().int().min(1000).max(100000).optional(),\n temperature: z.number().min(0).max(2).optional(),\n topP: z.number().min(0).max(1).optional(),\n }),\n});\n\n// Type exports\nexport type ModelRouterConfigValidated = z.infer<\n typeof ModelRouterConfigSchema\n>;\nexport type AutoBackgroundConfigValidated = z.infer<\n typeof AutoBackgroundConfigSchema\n>;\n\n/**\n * Safely parse and validate config, returning default on failure\n */\nexport function parseConfigSafe<T>(\n schema: z.ZodSchema<T>,\n data: unknown,\n defaultValue: T,\n configName: string\n): T {\n const result = schema.safeParse(data);\n if (result.success) {\n return result.data;\n }\n const errors = result.error.issues.map(\n (i) => `${i.path.join('.')}: ${i.message}`\n );\n logConfigInvalid(configName, errors);\n console.error(`[hooks] Invalid ${configName} config:`, errors.join(', '));\n return defaultValue;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/hooks/session-summary.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Session Summary Generator\n * Generates intelligent suggestions for what to do next after a Claude session\n */\n\nimport { execSync } from 'child_process';\nimport { pickNextLinearTask, TaskSuggestion } from './linear-task-picker.js';\n\nexport interface SessionContext {\n instanceId: string;\n exitCode: number | null;\n sessionStartTime: number;\n worktreePath?: string;\n branch?: string;\n task?: string;\n}\n\nexport interface Suggestion {\n key: string;\n label: string;\n action: string;\n priority: number;\n}\n\nexport interface SessionSummary {\n duration: string;\n exitCode: number | null;\n branch: string;\n status: 'success' | 'error' | 'interrupted';\n suggestions: Suggestion[];\n linearTask?: TaskSuggestion;\n}\n\n/**\n * Format duration in human-readable form\n */\nfunction formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}min`;\n }\n if (minutes > 0) {\n return `${minutes}min`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Get current git branch\n */\nfunction getCurrentBranch(): string {\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Check for uncommitted changes\n */\nfunction hasUncommittedChanges(): { changed: boolean; count: number } {\n try {\n const status = execSync('git status --porcelain', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const lines = status.trim().split('\\n').filter(Boolean);\n return { changed: lines.length > 0, count: lines.length };\n } catch {\n return { changed: false, count: 0 };\n }\n}\n\n/**\n * Check if we're in a worktree\n */\nfunction isInWorktree(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Check if it's a worktree (not the main repo)\n const gitDir = execSync('git rev-parse --git-dir', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n return gitDir.includes('.git/worktrees/');\n } catch {\n return false;\n }\n}\n\n/**\n * Check if tests exist and might need running\n */\nfunction hasTestScript(): boolean {\n try {\n const packageJson = execSync('cat package.json', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const pkg = JSON.parse(packageJson);\n return !!(pkg.scripts?.test || pkg.scripts?.['test:run']);\n } catch {\n return false;\n }\n}\n\n/**\n * Generate suggestions based on session context\n */\nasync function generateSuggestions(\n context: SessionContext\n): Promise<Suggestion[]> {\n const suggestions: Suggestion[] = [];\n let keyIndex = 1;\n\n const changes = hasUncommittedChanges();\n const inWorktree = isInWorktree();\n const hasTests = hasTestScript();\n\n // Error case - suggest reviewing logs\n if (context.exitCode !== 0 && context.exitCode !== null) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Review error logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -50',\n priority: 100,\n });\n }\n\n // Uncommitted changes - suggest commit or PR\n if (changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Commit changes (${changes.count} files)`,\n action: 'git add -A && git commit',\n priority: 90,\n });\n\n // If on feature branch, suggest PR\n const branch = getCurrentBranch();\n if (branch !== 'main' && branch !== 'master' && branch !== 'unknown') {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Create PR',\n action: 'gh pr create --fill',\n priority: 80,\n });\n }\n }\n\n // If tests exist and changes were made, suggest running tests\n if (hasTests && changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Run tests',\n action: 'npm run test:run',\n priority: 85,\n });\n }\n\n // Worktree-specific suggestions\n if (inWorktree) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Merge to main',\n action: 'cwm', // custom alias\n priority: 70,\n });\n }\n\n // Try to get next Linear task\n try {\n const linearTask = await pickNextLinearTask({ preferTestTasks: true });\n if (linearTask) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Start: ${linearTask.identifier} - ${linearTask.title.substring(0, 40)}${linearTask.title.length > 40 ? '...' : ''}${linearTask.hasTestRequirements ? ' (has tests)' : ''}`,\n action: `stackmemory task start ${linearTask.id} --assign-me`,\n priority: 60,\n });\n }\n } catch {\n // Linear not available, skip\n }\n\n // Long session suggestion\n const durationMs = Date.now() - context.sessionStartTime;\n if (durationMs > 30 * 60 * 1000) {\n // > 30 minutes\n suggestions.push({\n key: String(keyIndex++),\n label: 'Take a break',\n action: 'echo \"Great work! Time for a coffee break.\"',\n priority: 10,\n });\n }\n\n // Sort by priority (highest first) and re-key\n suggestions.sort((a, b) => b.priority - a.priority);\n\n // Ensure minimum 2 options always\n if (suggestions.length < 2) {\n // Add default options if not enough suggestions\n if (suggestions.length === 0) {\n suggestions.push({\n key: '1',\n label: 'Start new Claude session',\n action: 'claude-sm',\n priority: 50,\n });\n }\n if (suggestions.length < 2) {\n suggestions.push({\n key: '2',\n label: 'View session logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -30',\n priority: 40,\n });\n }\n }\n\n suggestions.forEach((s, i) => {\n s.key = String(i + 1);\n });\n\n return suggestions;\n}\n\n/**\n * Generate full session summary\n */\nexport async function generateSessionSummary(\n context: SessionContext\n): Promise<SessionSummary> {\n const durationMs = Date.now() - context.sessionStartTime;\n const duration = formatDuration(durationMs);\n const branch = context.branch || getCurrentBranch();\n\n let status: 'success' | 'error' | 'interrupted' = 'success';\n if (context.exitCode !== 0 && context.exitCode !== null) {\n status = 'error';\n }\n\n const suggestions = await generateSuggestions(context);\n\n // Extract linear task if present\n let linearTask: TaskSuggestion | undefined;\n try {\n linearTask = await pickNextLinearTask({ preferTestTasks: true });\n } catch {\n // Linear not available\n }\n\n return {\n duration,\n exitCode: context.exitCode,\n branch,\n status,\n suggestions,\n linearTask,\n };\n}\n\n/**\n * Format session summary as
|
|
4
|
+
"sourcesContent": ["/**\n * Session Summary Generator\n * Generates intelligent suggestions for what to do next after a Claude session\n */\n\nimport { execSync } from 'child_process';\nimport { pickNextLinearTask, TaskSuggestion } from './linear-task-picker.js';\n\nexport interface SessionContext {\n instanceId: string;\n exitCode: number | null;\n sessionStartTime: number;\n worktreePath?: string;\n branch?: string;\n task?: string;\n}\n\nexport interface Suggestion {\n key: string;\n label: string;\n action: string;\n priority: number;\n}\n\nexport interface SessionSummary {\n duration: string;\n exitCode: number | null;\n branch: string;\n status: 'success' | 'error' | 'interrupted';\n suggestions: Suggestion[];\n linearTask?: TaskSuggestion;\n}\n\n/**\n * Format duration in human-readable form\n */\nfunction formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n\n if (hours > 0) {\n return `${hours}h ${minutes % 60}min`;\n }\n if (minutes > 0) {\n return `${minutes}min`;\n }\n return `${seconds}s`;\n}\n\n/**\n * Get current git branch\n */\nfunction getCurrentBranch(): string {\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Check for uncommitted changes\n */\nfunction hasUncommittedChanges(): { changed: boolean; count: number } {\n try {\n const status = execSync('git status --porcelain', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const lines = status.trim().split('\\n').filter(Boolean);\n return { changed: lines.length > 0, count: lines.length };\n } catch {\n return { changed: false, count: 0 };\n }\n}\n\n/**\n * Check if we're in a worktree\n */\nfunction isInWorktree(): boolean {\n try {\n execSync('git rev-parse --is-inside-work-tree', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // Check if it's a worktree (not the main repo)\n const gitDir = execSync('git rev-parse --git-dir', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n return gitDir.includes('.git/worktrees/');\n } catch {\n return false;\n }\n}\n\n/**\n * Check if tests exist and might need running\n */\nfunction hasTestScript(): boolean {\n try {\n const packageJson = execSync('cat package.json', {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const pkg = JSON.parse(packageJson);\n return !!(pkg.scripts?.test || pkg.scripts?.['test:run']);\n } catch {\n return false;\n }\n}\n\n/**\n * Generate suggestions based on session context\n */\nasync function generateSuggestions(\n context: SessionContext\n): Promise<Suggestion[]> {\n const suggestions: Suggestion[] = [];\n let keyIndex = 1;\n\n const changes = hasUncommittedChanges();\n const inWorktree = isInWorktree();\n const hasTests = hasTestScript();\n\n // Error case - suggest reviewing logs\n if (context.exitCode !== 0 && context.exitCode !== null) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Review error logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -50',\n priority: 100,\n });\n }\n\n // Uncommitted changes - suggest commit or PR\n if (changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Commit changes (${changes.count} files)`,\n action: 'git add -A && git commit',\n priority: 90,\n });\n\n // If on feature branch, suggest PR\n const branch = getCurrentBranch();\n if (branch !== 'main' && branch !== 'master' && branch !== 'unknown') {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Create PR',\n action: 'gh pr create --fill',\n priority: 80,\n });\n }\n }\n\n // If tests exist and changes were made, suggest running tests\n if (hasTests && changes.changed) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Run tests',\n action: 'npm run test:run',\n priority: 85,\n });\n }\n\n // Worktree-specific suggestions\n if (inWorktree) {\n suggestions.push({\n key: String(keyIndex++),\n label: 'Merge to main',\n action: 'cwm', // custom alias\n priority: 70,\n });\n }\n\n // Try to get next Linear task\n try {\n const linearTask = await pickNextLinearTask({ preferTestTasks: true });\n if (linearTask) {\n suggestions.push({\n key: String(keyIndex++),\n label: `Start: ${linearTask.identifier} - ${linearTask.title.substring(0, 40)}${linearTask.title.length > 40 ? '...' : ''}${linearTask.hasTestRequirements ? ' (has tests)' : ''}`,\n action: `stackmemory task start ${linearTask.id} --assign-me`,\n priority: 60,\n });\n }\n } catch {\n // Linear not available, skip\n }\n\n // Long session suggestion\n const durationMs = Date.now() - context.sessionStartTime;\n if (durationMs > 30 * 60 * 1000) {\n // > 30 minutes\n suggestions.push({\n key: String(keyIndex++),\n label: 'Take a break',\n action: 'echo \"Great work! Time for a coffee break.\"',\n priority: 10,\n });\n }\n\n // Sort by priority (highest first) and re-key\n suggestions.sort((a, b) => b.priority - a.priority);\n\n // Ensure minimum 2 options always\n if (suggestions.length < 2) {\n // Add default options if not enough suggestions\n if (suggestions.length === 0) {\n suggestions.push({\n key: '1',\n label: 'Start new Claude session',\n action: 'claude-sm',\n priority: 50,\n });\n }\n if (suggestions.length < 2) {\n suggestions.push({\n key: '2',\n label: 'View session logs',\n action: 'cat ~/.claude/logs/claude-*.log | tail -30',\n priority: 40,\n });\n }\n }\n\n suggestions.forEach((s, i) => {\n s.key = String(i + 1);\n });\n\n return suggestions;\n}\n\n/**\n * Generate full session summary\n */\nexport async function generateSessionSummary(\n context: SessionContext\n): Promise<SessionSummary> {\n const durationMs = Date.now() - context.sessionStartTime;\n const duration = formatDuration(durationMs);\n const branch = context.branch || getCurrentBranch();\n\n let status: 'success' | 'error' | 'interrupted' = 'success';\n if (context.exitCode !== 0 && context.exitCode !== null) {\n status = 'error';\n }\n\n const suggestions = await generateSuggestions(context);\n\n // Extract linear task if present\n let linearTask: TaskSuggestion | undefined;\n try {\n linearTask = await pickNextLinearTask({ preferTestTasks: true });\n } catch {\n // Linear not available\n }\n\n return {\n duration,\n exitCode: context.exitCode,\n branch,\n status,\n suggestions,\n linearTask,\n };\n}\n\n/**\n * Format session summary as a compact message\n */\nexport function formatSummaryMessage(\n summary: SessionSummary,\n sessionId?: string\n): string {\n const statusEmoji = summary.status === 'success' ? '' : '';\n const exitInfo =\n summary.exitCode !== null ? ` | Exit: ${summary.exitCode}` : '';\n const sessionInfo = sessionId ? ` | Session: ${sessionId}` : '';\n\n let message = `Claude session complete ${statusEmoji}\\n`;\n message += `Duration: ${summary.duration}${exitInfo}${sessionInfo}\\n`;\n message += `Branch: ${summary.branch}\\n`;\n\n // Add Claude Code session URL if session ID is available\n if (sessionId) {\n message += `View: https://claude.ai/chat/${sessionId}\\n`;\n }\n\n message += '\\n';\n\n if (summary.suggestions.length > 0) {\n message += `What to do next:\\n`;\n for (const s of summary.suggestions.slice(0, 4)) {\n message += `${s.key}. ${s.label}\\n`;\n }\n message += `\\nReply with number or custom action`;\n } else {\n message += `No pending actions. Nice work!`;\n }\n\n return message;\n}\n\n/**\n * Get action for a suggestion key\n */\nexport function getActionForKey(\n suggestions: Suggestion[],\n key: string\n): string | null {\n const suggestion = suggestions.find((s) => s.key === key);\n return suggestion?.action || null;\n}\n"],
|
|
5
5
|
"mappings": ";;;;AAKA,SAAS,gBAAgB;AACzB,SAAS,0BAA0C;AA8BnD,SAAS,eAAe,IAAoB;AAC1C,QAAM,UAAU,KAAK,MAAM,KAAK,GAAI;AACpC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AAErC,MAAI,QAAQ,GAAG;AACb,WAAO,GAAG,KAAK,KAAK,UAAU,EAAE;AAAA,EAClC;AACA,MAAI,UAAU,GAAG;AACf,WAAO,GAAG,OAAO;AAAA,EACnB;AACA,SAAO,GAAG,OAAO;AACnB;AAKA,SAAS,mBAA2B;AAClC,MAAI;AACF,WAAO,SAAS,mCAAmC;AAAA,MACjD,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC,EAAE,KAAK;AAAA,EACV,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,wBAA6D;AACpE,MAAI;AACF,UAAM,SAAS,SAAS,0BAA0B;AAAA,MAChD,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AACD,UAAM,QAAQ,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACtD,WAAO,EAAE,SAAS,MAAM,SAAS,GAAG,OAAO,MAAM,OAAO;AAAA,EAC1D,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO,OAAO,EAAE;AAAA,EACpC;AACF;AAKA,SAAS,eAAwB;AAC/B,MAAI;AACF,aAAS,uCAAuC;AAAA,MAC9C,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,UAAM,SAAS,SAAS,2BAA2B;AAAA,MACjD,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC,EAAE,KAAK;AACR,WAAO,OAAO,SAAS,iBAAiB;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,gBAAyB;AAChC,MAAI;AACF,UAAM,cAAc,SAAS,oBAAoB;AAAA,MAC/C,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AACD,UAAM,MAAM,KAAK,MAAM,WAAW;AAClC,WAAO,CAAC,EAAE,IAAI,SAAS,QAAQ,IAAI,UAAU,UAAU;AAAA,EACzD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,eAAe,oBACb,SACuB;AACvB,QAAM,cAA4B,CAAC;AACnC,MAAI,WAAW;AAEf,QAAM,UAAU,sBAAsB;AACtC,QAAM,aAAa,aAAa;AAChC,QAAM,WAAW,cAAc;AAG/B,MAAI,QAAQ,aAAa,KAAK,QAAQ,aAAa,MAAM;AACvD,gBAAY,KAAK;AAAA,MACf,KAAK,OAAO,UAAU;AAAA,MACtB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,MAAI,QAAQ,SAAS;AACnB,gBAAY,KAAK;AAAA,MACf,KAAK,OAAO,UAAU;AAAA,MACtB,OAAO,mBAAmB,QAAQ,KAAK;AAAA,MACvC,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ,CAAC;AAGD,UAAM,SAAS,iBAAiB;AAChC,QAAI,WAAW,UAAU,WAAW,YAAY,WAAW,WAAW;AACpE,kBAAY,KAAK;AAAA,QACf,KAAK,OAAO,UAAU;AAAA,QACtB,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,YAAY,QAAQ,SAAS;AAC/B,gBAAY,KAAK;AAAA,MACf,KAAK,OAAO,UAAU;AAAA,MACtB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,MAAI,YAAY;AACd,gBAAY,KAAK;AAAA,MACf,KAAK,OAAO,UAAU;AAAA,MACtB,OAAO;AAAA,MACP,QAAQ;AAAA;AAAA,MACR,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,MAAI;AACF,UAAM,aAAa,MAAM,mBAAmB,EAAE,iBAAiB,KAAK,CAAC;AACrE,QAAI,YAAY;AACd,kBAAY,KAAK;AAAA,QACf,KAAK,OAAO,UAAU;AAAA,QACtB,OAAO,UAAU,WAAW,UAAU,MAAM,WAAW,MAAM,UAAU,GAAG,EAAE,CAAC,GAAG,WAAW,MAAM,SAAS,KAAK,QAAQ,EAAE,GAAG,WAAW,sBAAsB,iBAAiB,EAAE;AAAA,QAChL,QAAQ,0BAA0B,WAAW,EAAE;AAAA,QAC/C,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,QAAM,aAAa,KAAK,IAAI,IAAI,QAAQ;AACxC,MAAI,aAAa,KAAK,KAAK,KAAM;AAE/B,gBAAY,KAAK;AAAA,MACf,KAAK,OAAO,UAAU;AAAA,MACtB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAGA,cAAY,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAGlD,MAAI,YAAY,SAAS,GAAG;AAE1B,QAAI,YAAY,WAAW,GAAG;AAC5B,kBAAY,KAAK;AAAA,QACf,KAAK;AAAA,QACL,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AACA,QAAI,YAAY,SAAS,GAAG;AAC1B,kBAAY,KAAK;AAAA,QACf,KAAK;AAAA,QACL,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,cAAY,QAAQ,CAAC,GAAG,MAAM;AAC5B,MAAE,MAAM,OAAO,IAAI,CAAC;AAAA,EACtB,CAAC;AAED,SAAO;AACT;AAKA,eAAsB,uBACpB,SACyB;AACzB,QAAM,aAAa,KAAK,IAAI,IAAI,QAAQ;AACxC,QAAM,WAAW,eAAe,UAAU;AAC1C,QAAM,SAAS,QAAQ,UAAU,iBAAiB;AAElD,MAAI,SAA8C;AAClD,MAAI,QAAQ,aAAa,KAAK,QAAQ,aAAa,MAAM;AACvD,aAAS;AAAA,EACX;AAEA,QAAM,cAAc,MAAM,oBAAoB,OAAO;AAGrD,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,mBAAmB,EAAE,iBAAiB,KAAK,CAAC;AAAA,EACjE,QAAQ;AAAA,EAER;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,qBACd,SACA,WACQ;AACR,QAAM,cAAc,QAAQ,WAAW,YAAY,KAAK;AACxD,QAAM,WACJ,QAAQ,aAAa,OAAO,YAAY,QAAQ,QAAQ,KAAK;AAC/D,QAAM,cAAc,YAAY,eAAe,SAAS,KAAK;AAE7D,MAAI,UAAU,2BAA2B,WAAW;AAAA;AACpD,aAAW,aAAa,QAAQ,QAAQ,GAAG,QAAQ,GAAG,WAAW;AAAA;AACjE,aAAW,WAAW,QAAQ,MAAM;AAAA;AAGpC,MAAI,WAAW;AACb,eAAW,gCAAgC,SAAS;AAAA;AAAA,EACtD;AAEA,aAAW;AAEX,MAAI,QAAQ,YAAY,SAAS,GAAG;AAClC,eAAW;AAAA;AACX,eAAW,KAAK,QAAQ,YAAY,MAAM,GAAG,CAAC,GAAG;AAC/C,iBAAW,GAAG,EAAE,GAAG,KAAK,EAAE,KAAK;AAAA;AAAA,IACjC;AACA,eAAW;AAAA;AAAA,EACb,OAAO;AACL,eAAW;AAAA,EACb;AAEA,SAAO;AACT;AAKO,SAAS,gBACd,aACA,KACe;AACf,QAAM,aAAa,YAAY,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AACxD,SAAO,YAAY,UAAU;AAC/B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackmemoryai/stackmemory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"sync:setup": "./scripts/setup-background-sync.sh",
|
|
95
95
|
"prepare": "echo 'Prepare step completed'",
|
|
96
96
|
"verify:dist": "node scripts/verify-dist.cjs",
|
|
97
|
+
"test:smoke-db": "bash scripts/smoke-init-db.sh",
|
|
97
98
|
"rebuild:native": "npm rebuild better-sqlite3 || true",
|
|
98
99
|
"deps:reset": "rm -rf node_modules package-lock.json && npm ci"
|
|
99
100
|
},
|
|
@@ -123,7 +124,6 @@
|
|
|
123
124
|
"ignore": "^7.0.5",
|
|
124
125
|
"inquirer": "^9.3.8",
|
|
125
126
|
"msgpackr": "^1.10.1",
|
|
126
|
-
"ngrok": "^5.0.0-beta.2",
|
|
127
127
|
"node-pty": "^1.1.0",
|
|
128
128
|
"open": "^11.0.0",
|
|
129
129
|
"ora": "^9.0.0",
|