@stackmemoryai/stackmemory 0.6.3 → 0.7.0

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.
@@ -0,0 +1,61 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import * as path from "path";
6
+ class ProjectRegistryManager {
7
+ constructor(adapter) {
8
+ this.adapter = adapter;
9
+ }
10
+ /** Register a new project */
11
+ async register(projectId, repoPath, displayName) {
12
+ const dbPath = this.computeDbPath(repoPath);
13
+ await this.adapter.registerProject({
14
+ projectId,
15
+ repoPath,
16
+ displayName,
17
+ dbPath
18
+ });
19
+ }
20
+ /** List all registered projects */
21
+ async list() {
22
+ return this.adapter.getRegisteredProjects();
23
+ }
24
+ /** Switch active project */
25
+ async switchTo(projectId) {
26
+ const projects = await this.list();
27
+ if (!projects.find((p) => p.projectId === projectId)) return false;
28
+ await this.adapter.setActiveProject(projectId);
29
+ return true;
30
+ }
31
+ /** Remove a project from registry (doesn't delete data) */
32
+ async remove(projectId) {
33
+ return this.adapter.removeProject(projectId);
34
+ }
35
+ /** Get current active project */
36
+ async getActive() {
37
+ const activeId = await this.adapter.getActiveProject();
38
+ if (!activeId) return null;
39
+ const projects = await this.list();
40
+ return projects.find((p) => p.projectId === activeId) || null;
41
+ }
42
+ /** Auto-detect project from cwd */
43
+ async detectAndRegister(cwd) {
44
+ const projectId = path.basename(cwd);
45
+ const existing = await this.list();
46
+ const found = existing.find((p) => p.repoPath === cwd);
47
+ if (found) {
48
+ await this.adapter.touchProject(found.projectId);
49
+ return found.projectId;
50
+ }
51
+ await this.register(projectId, cwd);
52
+ return projectId;
53
+ }
54
+ computeDbPath(repoPath) {
55
+ return path.join(repoPath, ".stackmemory", "memory.db");
56
+ }
57
+ }
58
+ export {
59
+ ProjectRegistryManager
60
+ };
61
+ //# sourceMappingURL=project-registry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/core/storage/project-registry.ts"],
4
+ "sourcesContent": ["/**\n * Project Registry Manager\n * Manages multi-repository support with a central project registry.\n * Projects are registered with their paths and DB locations, and one\n * project can be marked as active at a time.\n */\n\nimport * as path from 'path';\nimport type { DatabaseAdapter } from '../database/database-adapter.js';\n\nexport interface ProjectInfo {\n projectId: string;\n repoPath: string;\n displayName: string | null;\n dbPath: string;\n isActive: boolean;\n createdAt: number;\n lastAccessed: number;\n}\n\nexport class ProjectRegistryManager {\n constructor(private adapter: DatabaseAdapter) {}\n\n /** Register a new project */\n async register(\n projectId: string,\n repoPath: string,\n displayName?: string\n ): Promise<void> {\n const dbPath = this.computeDbPath(repoPath);\n await this.adapter.registerProject({\n projectId,\n repoPath,\n displayName,\n dbPath,\n });\n }\n\n /** List all registered projects */\n async list(): Promise<ProjectInfo[]> {\n return this.adapter.getRegisteredProjects();\n }\n\n /** Switch active project */\n async switchTo(projectId: string): Promise<boolean> {\n const projects = await this.list();\n if (!projects.find((p) => p.projectId === projectId)) return false;\n await this.adapter.setActiveProject(projectId);\n return true;\n }\n\n /** Remove a project from registry (doesn't delete data) */\n async remove(projectId: string): Promise<boolean> {\n return this.adapter.removeProject(projectId);\n }\n\n /** Get current active project */\n async getActive(): Promise<ProjectInfo | null> {\n const activeId = await this.adapter.getActiveProject();\n if (!activeId) return null;\n const projects = await this.list();\n return projects.find((p) => p.projectId === activeId) || null;\n }\n\n /** Auto-detect project from cwd */\n async detectAndRegister(cwd: string): Promise<string> {\n const projectId = path.basename(cwd);\n const existing = await this.list();\n const found = existing.find((p) => p.repoPath === cwd);\n if (found) {\n await this.adapter.touchProject(found.projectId);\n return found.projectId;\n }\n await this.register(projectId, cwd);\n return projectId;\n }\n\n private computeDbPath(repoPath: string): string {\n return path.join(repoPath, '.stackmemory', 'memory.db');\n }\n}\n"],
5
+ "mappings": ";;;;AAOA,YAAY,UAAU;AAaf,MAAM,uBAAuB;AAAA,EAClC,YAAoB,SAA0B;AAA1B;AAAA,EAA2B;AAAA;AAAA,EAG/C,MAAM,SACJ,WACA,UACA,aACe;AACf,UAAM,SAAS,KAAK,cAAc,QAAQ;AAC1C,UAAM,KAAK,QAAQ,gBAAgB;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,OAA+B;AACnC,WAAO,KAAK,QAAQ,sBAAsB;AAAA,EAC5C;AAAA;AAAA,EAGA,MAAM,SAAS,WAAqC;AAClD,UAAM,WAAW,MAAM,KAAK,KAAK;AACjC,QAAI,CAAC,SAAS,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS,EAAG,QAAO;AAC7D,UAAM,KAAK,QAAQ,iBAAiB,SAAS;AAC7C,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,OAAO,WAAqC;AAChD,WAAO,KAAK,QAAQ,cAAc,SAAS;AAAA,EAC7C;AAAA;AAAA,EAGA,MAAM,YAAyC;AAC7C,UAAM,WAAW,MAAM,KAAK,QAAQ,iBAAiB;AACrD,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,WAAW,MAAM,KAAK,KAAK;AACjC,WAAO,SAAS,KAAK,CAAC,MAAM,EAAE,cAAc,QAAQ,KAAK;AAAA,EAC3D;AAAA;AAAA,EAGA,MAAM,kBAAkB,KAA8B;AACpD,UAAM,YAAY,KAAK,SAAS,GAAG;AACnC,UAAM,WAAW,MAAM,KAAK,KAAK;AACjC,UAAM,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,GAAG;AACrD,QAAI,OAAO;AACT,YAAM,KAAK,QAAQ,aAAa,MAAM,SAAS;AAC/C,aAAO,MAAM;AAAA,IACf;AACA,UAAM,KAAK,SAAS,WAAW,GAAG;AAClC,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc,UAA0B;AAC9C,WAAO,KAAK,KAAK,UAAU,gBAAgB,WAAW;AAAA,EACxD;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,214 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ class InMemoryColdStorageProvider {
6
+ store = /* @__PURE__ */ new Map();
7
+ async upload(key, data) {
8
+ this.store.set(key, Buffer.from(data));
9
+ }
10
+ async download(key) {
11
+ const data = this.store.get(key);
12
+ return data ? Buffer.from(data) : null;
13
+ }
14
+ async exists(key) {
15
+ return this.store.has(key);
16
+ }
17
+ async delete(key) {
18
+ this.store.delete(key);
19
+ }
20
+ async list(prefix) {
21
+ const keys = [];
22
+ for (const key of this.store.keys()) {
23
+ if (key.startsWith(prefix)) {
24
+ keys.push(key);
25
+ }
26
+ }
27
+ return keys;
28
+ }
29
+ /** Test helper: get current store size */
30
+ get size() {
31
+ return this.store.size;
32
+ }
33
+ /** Test helper: clear all stored data */
34
+ clear() {
35
+ this.store.clear();
36
+ }
37
+ }
38
+ class StorageTierManager {
39
+ provider = null;
40
+ config;
41
+ rehydrateCache = /* @__PURE__ */ new Map();
42
+ constructor(config) {
43
+ this.config = config;
44
+ }
45
+ async initialize() {
46
+ switch (this.config.coldTierProvider) {
47
+ case "s3":
48
+ this.provider = await this.createS3Provider();
49
+ break;
50
+ case "gcs":
51
+ this.provider = await this.createGCSProvider();
52
+ break;
53
+ default:
54
+ this.provider = null;
55
+ }
56
+ }
57
+ /**
58
+ * Initialize with an externally-provided ColdStorageProvider.
59
+ * Useful for testing or custom backends.
60
+ */
61
+ initializeWithProvider(provider) {
62
+ this.provider = provider;
63
+ }
64
+ get isEnabled() {
65
+ return this.provider !== null;
66
+ }
67
+ /** Archive a frame to cold storage */
68
+ async archiveFrame(frame) {
69
+ if (!this.provider) return false;
70
+ const key = this.frameKey(frame.frame_id);
71
+ const data = Buffer.from(JSON.stringify(frame));
72
+ await this.provider.upload(key, data);
73
+ return true;
74
+ }
75
+ /** Archive multiple frames (batch) */
76
+ async archiveFrames(frames) {
77
+ let archived = 0;
78
+ for (const frame of frames) {
79
+ if (await this.archiveFrame(frame)) archived++;
80
+ }
81
+ return archived;
82
+ }
83
+ /** Rehydrate a frame from cold storage (with local cache) */
84
+ async rehydrateFrame(frameId) {
85
+ const cached = this.rehydrateCache.get(frameId);
86
+ const cacheMs = (this.config.rehydrateCacheMinutes ?? 30) * 60 * 1e3;
87
+ if (cached && Date.now() - cached.cachedAt < cacheMs) {
88
+ return cached.frame;
89
+ }
90
+ if (!this.provider) return null;
91
+ const key = this.frameKey(frameId);
92
+ const data = await this.provider.download(key);
93
+ if (!data) return null;
94
+ const frame = JSON.parse(data.toString());
95
+ this.rehydrateCache.set(frameId, { frame, cachedAt: Date.now() });
96
+ return frame;
97
+ }
98
+ /** Delete an archived frame from cold storage */
99
+ async deleteArchived(frameId) {
100
+ if (!this.provider) return false;
101
+ const key = this.frameKey(frameId);
102
+ const found = await this.provider.exists(key);
103
+ if (!found) return false;
104
+ await this.provider.delete(key);
105
+ this.rehydrateCache.delete(frameId);
106
+ return true;
107
+ }
108
+ /** Get storage stats */
109
+ async getStats() {
110
+ if (!this.provider) {
111
+ return { provider: "none", archivedFrames: 0, bucket: "" };
112
+ }
113
+ const prefix = this.config.coldTierPrefix ?? "stackmemory/frames/";
114
+ const keys = await this.provider.list(prefix);
115
+ return {
116
+ provider: this.config.coldTierProvider ?? "none",
117
+ archivedFrames: keys.length,
118
+ bucket: this.config.coldTierBucket ?? ""
119
+ };
120
+ }
121
+ /** Clear the rehydration cache */
122
+ clearCache() {
123
+ this.rehydrateCache.clear();
124
+ }
125
+ frameKey(frameId) {
126
+ const prefix = this.config.coldTierPrefix ?? "stackmemory/frames/";
127
+ return `${prefix}${frameId}.json`;
128
+ }
129
+ async createS3Provider() {
130
+ const {
131
+ S3Client,
132
+ PutObjectCommand,
133
+ GetObjectCommand,
134
+ HeadObjectCommand,
135
+ DeleteObjectCommand,
136
+ ListObjectsV2Command
137
+ } = await import("@aws-sdk/client-s3");
138
+ const client = new S3Client({});
139
+ const bucket = this.config.coldTierBucket;
140
+ return {
141
+ async upload(key, data) {
142
+ await client.send(
143
+ new PutObjectCommand({ Bucket: bucket, Key: key, Body: data })
144
+ );
145
+ },
146
+ async download(key) {
147
+ try {
148
+ const res = await client.send(
149
+ new GetObjectCommand({ Bucket: bucket, Key: key })
150
+ );
151
+ const bytes = await res.Body?.transformToByteArray();
152
+ return bytes ? Buffer.from(bytes) : null;
153
+ } catch {
154
+ return null;
155
+ }
156
+ },
157
+ async exists(key) {
158
+ try {
159
+ await client.send(
160
+ new HeadObjectCommand({ Bucket: bucket, Key: key })
161
+ );
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ },
167
+ async delete(key) {
168
+ await client.send(
169
+ new DeleteObjectCommand({ Bucket: bucket, Key: key })
170
+ );
171
+ },
172
+ async list(prefix) {
173
+ const res = await client.send(
174
+ new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix })
175
+ );
176
+ return (res.Contents ?? []).map((c) => c.Key).filter(Boolean);
177
+ }
178
+ };
179
+ }
180
+ async createGCSProvider() {
181
+ const { Storage } = await import("@google-cloud/storage");
182
+ const storage = new Storage();
183
+ const bucket = storage.bucket(this.config.coldTierBucket);
184
+ return {
185
+ async upload(key, data) {
186
+ await bucket.file(key).save(data);
187
+ },
188
+ async download(key) {
189
+ try {
190
+ const [data] = await bucket.file(key).download();
191
+ return data;
192
+ } catch {
193
+ return null;
194
+ }
195
+ },
196
+ async exists(key) {
197
+ const [fileExists] = await bucket.file(key).exists();
198
+ return fileExists;
199
+ },
200
+ async delete(key) {
201
+ await bucket.file(key).delete();
202
+ },
203
+ async list(prefix) {
204
+ const [files] = await bucket.getFiles({ prefix });
205
+ return files.map((f) => f.name);
206
+ }
207
+ };
208
+ }
209
+ }
210
+ export {
211
+ InMemoryColdStorageProvider,
212
+ StorageTierManager
213
+ };
214
+ //# sourceMappingURL=storage-tier-manager.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/core/storage/storage-tier-manager.ts"],
4
+ "sourcesContent": ["/**\n * StorageTierManager - Cold-tier frame archival to S3/GCS\n * Archives old frames to cloud storage before GC deletion,\n * with rehydration cache for on-demand retrieval.\n */\n\nimport type { Frame } from '../context/index.js';\n\nexport interface ColdStorageProvider {\n upload(key: string, data: Buffer): Promise<void>;\n download(key: string): Promise<Buffer | null>;\n exists(key: string): Promise<boolean>;\n delete(key: string): Promise<void>;\n list(prefix: string): Promise<string[]>;\n}\n\nexport interface StorageTierConfig {\n coldTierProvider?: 'none' | 's3' | 'gcs';\n coldTierBucket?: string;\n coldTierPrefix?: string; // default: 'stackmemory/frames/'\n migrationAgeDays?: number; // frames older than this get migrated (default: 60)\n rehydrateCacheMinutes?: number; // cache rehydrated frames for N minutes (default: 30)\n}\n\n/**\n * In-memory implementation of ColdStorageProvider for testing.\n */\nexport class InMemoryColdStorageProvider implements ColdStorageProvider {\n private store = new Map<string, Buffer>();\n\n async upload(key: string, data: Buffer): Promise<void> {\n this.store.set(key, Buffer.from(data));\n }\n\n async download(key: string): Promise<Buffer | null> {\n const data = this.store.get(key);\n return data ? Buffer.from(data) : null;\n }\n\n async exists(key: string): Promise<boolean> {\n return this.store.has(key);\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async list(prefix: string): Promise<string[]> {\n const keys: string[] = [];\n for (const key of this.store.keys()) {\n if (key.startsWith(prefix)) {\n keys.push(key);\n }\n }\n return keys;\n }\n\n /** Test helper: get current store size */\n get size(): number {\n return this.store.size;\n }\n\n /** Test helper: clear all stored data */\n clear(): void {\n this.store.clear();\n }\n}\n\nexport class StorageTierManager {\n private provider: ColdStorageProvider | null = null;\n private config: StorageTierConfig;\n private rehydrateCache = new Map<\n string,\n { frame: Frame; cachedAt: number }\n >();\n\n constructor(config: StorageTierConfig) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n switch (this.config.coldTierProvider) {\n case 's3':\n this.provider = await this.createS3Provider();\n break;\n case 'gcs':\n this.provider = await this.createGCSProvider();\n break;\n default:\n this.provider = null;\n }\n }\n\n /**\n * Initialize with an externally-provided ColdStorageProvider.\n * Useful for testing or custom backends.\n */\n initializeWithProvider(provider: ColdStorageProvider): void {\n this.provider = provider;\n }\n\n get isEnabled(): boolean {\n return this.provider !== null;\n }\n\n /** Archive a frame to cold storage */\n async archiveFrame(frame: Frame): Promise<boolean> {\n if (!this.provider) return false;\n const key = this.frameKey(frame.frame_id);\n const data = Buffer.from(JSON.stringify(frame));\n await this.provider.upload(key, data);\n return true;\n }\n\n /** Archive multiple frames (batch) */\n async archiveFrames(frames: Frame[]): Promise<number> {\n let archived = 0;\n for (const frame of frames) {\n if (await this.archiveFrame(frame)) archived++;\n }\n return archived;\n }\n\n /** Rehydrate a frame from cold storage (with local cache) */\n async rehydrateFrame(frameId: string): Promise<Frame | null> {\n // Check cache first\n const cached = this.rehydrateCache.get(frameId);\n const cacheMs = (this.config.rehydrateCacheMinutes ?? 30) * 60 * 1000;\n if (cached && Date.now() - cached.cachedAt < cacheMs) {\n return cached.frame;\n }\n\n if (!this.provider) return null;\n const key = this.frameKey(frameId);\n const data = await this.provider.download(key);\n if (!data) return null;\n\n const frame = JSON.parse(data.toString()) as Frame;\n this.rehydrateCache.set(frameId, { frame, cachedAt: Date.now() });\n return frame;\n }\n\n /** Delete an archived frame from cold storage */\n async deleteArchived(frameId: string): Promise<boolean> {\n if (!this.provider) return false;\n const key = this.frameKey(frameId);\n const found = await this.provider.exists(key);\n if (!found) return false;\n await this.provider.delete(key);\n this.rehydrateCache.delete(frameId);\n return true;\n }\n\n /** Get storage stats */\n async getStats(): Promise<{\n provider: string;\n archivedFrames: number;\n bucket: string;\n }> {\n if (!this.provider) {\n return { provider: 'none', archivedFrames: 0, bucket: '' };\n }\n const prefix = this.config.coldTierPrefix ?? 'stackmemory/frames/';\n const keys = await this.provider.list(prefix);\n return {\n provider: this.config.coldTierProvider ?? 'none',\n archivedFrames: keys.length,\n bucket: this.config.coldTierBucket ?? '',\n };\n }\n\n /** Clear the rehydration cache */\n clearCache(): void {\n this.rehydrateCache.clear();\n }\n\n private frameKey(frameId: string): string {\n const prefix = this.config.coldTierPrefix ?? 'stackmemory/frames/';\n return `${prefix}${frameId}.json`;\n }\n\n private async createS3Provider(): Promise<ColdStorageProvider> {\n const {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n DeleteObjectCommand,\n ListObjectsV2Command,\n } = await import('@aws-sdk/client-s3');\n const client = new S3Client({});\n const bucket = this.config.coldTierBucket!;\n\n return {\n async upload(key: string, data: Buffer) {\n await client.send(\n new PutObjectCommand({ Bucket: bucket, Key: key, Body: data })\n );\n },\n async download(key: string) {\n try {\n const res = await client.send(\n new GetObjectCommand({ Bucket: bucket, Key: key })\n );\n const bytes = await res.Body?.transformToByteArray();\n return bytes ? Buffer.from(bytes) : null;\n } catch {\n return null;\n }\n },\n async exists(key: string) {\n try {\n await client.send(\n new HeadObjectCommand({ Bucket: bucket, Key: key })\n );\n return true;\n } catch {\n return false;\n }\n },\n async delete(key: string) {\n await client.send(\n new DeleteObjectCommand({ Bucket: bucket, Key: key })\n );\n },\n async list(prefix: string) {\n const res = await client.send(\n new ListObjectsV2Command({ Bucket: bucket, Prefix: prefix })\n );\n return (res.Contents ?? []).map((c) => c.Key!).filter(Boolean);\n },\n };\n }\n\n private async createGCSProvider(): Promise<ColdStorageProvider> {\n const { Storage } = await import('@google-cloud/storage');\n const storage = new Storage();\n const bucket = storage.bucket(this.config.coldTierBucket!);\n\n return {\n async upload(key: string, data: Buffer) {\n await bucket.file(key).save(data);\n },\n async download(key: string) {\n try {\n const [data] = await bucket.file(key).download();\n return data;\n } catch {\n return null;\n }\n },\n async exists(key: string) {\n const [fileExists] = await bucket.file(key).exists();\n return fileExists;\n },\n async delete(key: string) {\n await bucket.file(key).delete();\n },\n async list(prefix: string) {\n const [files] = await bucket.getFiles({ prefix });\n return files.map((f) => f.name);\n },\n };\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/daemon/daemon-config.ts"],
4
- "sourcesContent": ["/**\n * Daemon Configuration Management\n * Handles loading, saving, and validating daemon configuration\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nexport interface DaemonServiceConfig {\n enabled: boolean;\n interval: number; // minutes\n}\n\nexport interface ContextServiceConfig extends DaemonServiceConfig {\n checkpointMessage?: string;\n}\n\nexport interface LinearServiceConfig extends DaemonServiceConfig {\n quietHours?: {\n start: number; // hour 0-23\n end: number;\n };\n retryAttempts: number;\n retryDelay: number; // ms\n}\n\nexport interface MaintenanceServiceConfig extends DaemonServiceConfig {\n staleFrameThresholdDays: number;\n ftsRebuildInterval: number; // hours\n embeddingBatchSize: number;\n vacuumInterval: number; // hours\n embeddingModel?: string; // default: 'Xenova/all-MiniLM-L6-v2'\n}\n\nexport interface FileWatchConfig extends DaemonServiceConfig {\n paths: string[];\n extensions: string[];\n ignore: string[];\n debounceMs: number;\n}\n\nexport interface DaemonConfig {\n version: string;\n context: ContextServiceConfig;\n linear: LinearServiceConfig;\n maintenance: MaintenanceServiceConfig;\n fileWatch: FileWatchConfig;\n heartbeatInterval: number; // seconds\n inactivityTimeout: number; // minutes, 0 = disabled\n logLevel: 'debug' | 'info' | 'warn' | 'error';\n}\n\nexport const DEFAULT_DAEMON_CONFIG: DaemonConfig = {\n version: '1.0.0',\n context: {\n enabled: true,\n interval: 15, // 15 minutes\n checkpointMessage: 'Auto-checkpoint',\n },\n linear: {\n enabled: false, // Disabled by default, requires setup\n interval: 60, // 60 minutes\n quietHours: { start: 22, end: 7 },\n retryAttempts: 3,\n retryDelay: 30000,\n },\n maintenance: {\n enabled: true,\n interval: 360, // 6 hours\n staleFrameThresholdDays: 30,\n ftsRebuildInterval: 24, // hours\n embeddingBatchSize: 50,\n vacuumInterval: 168, // weekly\n },\n fileWatch: {\n enabled: false, // Disabled by default\n interval: 0, // Not interval-based\n paths: ['.'],\n extensions: ['.ts', '.js', '.tsx', '.jsx', '.py', '.go', '.rs'],\n ignore: ['node_modules', '.git', 'dist', 'build', '.stackmemory'],\n debounceMs: 2000,\n },\n heartbeatInterval: 60, // 1 minute\n inactivityTimeout: 0, // Disabled by default\n logLevel: 'info',\n};\n\nexport interface DaemonStatus {\n running: boolean;\n pid?: number;\n startTime?: number;\n uptime?: number;\n services: {\n context: { enabled: boolean; lastRun?: number; saveCount?: number };\n linear: { enabled: boolean; lastRun?: number; syncCount?: number };\n maintenance: {\n enabled: boolean;\n lastRun?: number;\n staleFramesCleaned?: number;\n ftsRebuilds?: number;\n embeddingsGenerated?: number;\n };\n fileWatch: { enabled: boolean; eventsProcessed?: number };\n };\n errors: string[];\n}\n\n/**\n * Get the daemon directory path\n */\nexport function getDaemonDir(): string {\n const dir = join(homedir(), '.stackmemory', 'daemon');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/**\n * Get the logs directory path\n */\nexport function getLogsDir(): string {\n const dir = join(homedir(), '.stackmemory', 'logs');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/**\n * Get daemon file paths\n */\nexport function getDaemonPaths() {\n const daemonDir = getDaemonDir();\n const logsDir = getLogsDir();\n return {\n pidFile: join(daemonDir, 'daemon.pid'),\n statusFile: join(daemonDir, 'daemon.status'),\n configFile: join(daemonDir, 'config.json'),\n logFile: join(logsDir, 'daemon.log'),\n };\n}\n\n/**\n * Load daemon configuration\n */\nexport function loadDaemonConfig(): DaemonConfig {\n const { configFile } = getDaemonPaths();\n\n if (!existsSync(configFile)) {\n return { ...DEFAULT_DAEMON_CONFIG };\n }\n\n try {\n const content = readFileSync(configFile, 'utf8');\n const config = JSON.parse(content) as Partial<DaemonConfig>;\n return {\n ...DEFAULT_DAEMON_CONFIG,\n ...config,\n context: { ...DEFAULT_DAEMON_CONFIG.context, ...config.context },\n linear: { ...DEFAULT_DAEMON_CONFIG.linear, ...config.linear },\n maintenance: {\n ...DEFAULT_DAEMON_CONFIG.maintenance,\n ...config.maintenance,\n },\n fileWatch: { ...DEFAULT_DAEMON_CONFIG.fileWatch, ...config.fileWatch },\n };\n } catch {\n return { ...DEFAULT_DAEMON_CONFIG };\n }\n}\n\n/**\n * Save daemon configuration\n */\nexport function saveDaemonConfig(config: Partial<DaemonConfig>): void {\n const { configFile } = getDaemonPaths();\n const currentConfig = loadDaemonConfig();\n const newConfig = {\n ...currentConfig,\n ...config,\n context: { ...currentConfig.context, ...config.context },\n linear: { ...currentConfig.linear, ...config.linear },\n maintenance: { ...currentConfig.maintenance, ...config.maintenance },\n fileWatch: { ...currentConfig.fileWatch, ...config.fileWatch },\n };\n writeFileSync(configFile, JSON.stringify(newConfig, null, 2));\n}\n\n/**\n * Read daemon status\n */\nexport function readDaemonStatus(): DaemonStatus {\n const { statusFile, pidFile } = getDaemonPaths();\n\n const defaultStatus: DaemonStatus = {\n running: false,\n services: {\n context: { enabled: false },\n linear: { enabled: false },\n maintenance: { enabled: false },\n fileWatch: { enabled: false },\n },\n errors: [],\n };\n\n // Check PID file first\n if (!existsSync(pidFile)) {\n return defaultStatus;\n }\n\n try {\n const pidContent = readFileSync(pidFile, 'utf8').trim();\n const pid = parseInt(pidContent, 10);\n\n // Check if process is running\n try {\n process.kill(pid, 0);\n } catch {\n // Process not running\n return defaultStatus;\n }\n\n // Read status file\n if (!existsSync(statusFile)) {\n return { ...defaultStatus, running: true, pid };\n }\n\n const content = readFileSync(statusFile, 'utf8');\n const status = JSON.parse(content) as DaemonStatus;\n return {\n ...status,\n running: true,\n pid,\n uptime: status.startTime ? Date.now() - status.startTime : undefined,\n };\n } catch {\n return defaultStatus;\n }\n}\n\n/**\n * Write daemon status\n */\nexport function writeDaemonStatus(status: Partial<DaemonStatus>): void {\n const { statusFile } = getDaemonPaths();\n const currentStatus = readDaemonStatus();\n const newStatus = { ...currentStatus, ...status };\n writeFileSync(statusFile, JSON.stringify(newStatus, null, 2));\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Daemon Configuration Management\n * Handles loading, saving, and validating daemon configuration\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nexport interface DaemonServiceConfig {\n enabled: boolean;\n interval: number; // minutes\n}\n\nexport interface ContextServiceConfig extends DaemonServiceConfig {\n checkpointMessage?: string;\n}\n\nexport interface LinearServiceConfig extends DaemonServiceConfig {\n quietHours?: {\n start: number; // hour 0-23\n end: number;\n };\n retryAttempts: number;\n retryDelay: number; // ms\n}\n\nexport interface MaintenanceServiceConfig extends DaemonServiceConfig {\n staleFrameThresholdDays: number;\n ftsRebuildInterval: number; // hours\n embeddingBatchSize: number;\n vacuumInterval: number; // hours\n embeddingProvider?: 'transformers' | 'ollama' | 'openai' | 'none';\n embeddingModel?: string; // default: 'Xenova/all-MiniLM-L6-v2'\n embeddingDimension?: number;\n embeddingApiKey?: string;\n embeddingBaseUrl?: string;\n embeddingFallbackProviders?: Array<'transformers' | 'ollama' | 'openai'>;\n gcEnabled?: boolean; // default: true\n gcRetentionDays?: number; // default: 90\n gcBatchSize?: number; // default: 100\n coldTierProvider?: 'none' | 's3' | 'gcs'; // default: 'none'\n coldTierBucket?: string;\n coldTierPrefix?: string; // default: 'stackmemory/frames/'\n coldTierMigrationAgeDays?: number; // default: 60\n coldTierRehydrateCacheMinutes?: number; // default: 30\n}\n\nexport interface FileWatchConfig extends DaemonServiceConfig {\n paths: string[];\n extensions: string[];\n ignore: string[];\n debounceMs: number;\n}\n\nexport interface DaemonConfig {\n version: string;\n context: ContextServiceConfig;\n linear: LinearServiceConfig;\n maintenance: MaintenanceServiceConfig;\n fileWatch: FileWatchConfig;\n heartbeatInterval: number; // seconds\n inactivityTimeout: number; // minutes, 0 = disabled\n logLevel: 'debug' | 'info' | 'warn' | 'error';\n}\n\nexport const DEFAULT_DAEMON_CONFIG: DaemonConfig = {\n version: '1.0.0',\n context: {\n enabled: true,\n interval: 15, // 15 minutes\n checkpointMessage: 'Auto-checkpoint',\n },\n linear: {\n enabled: false, // Disabled by default, requires setup\n interval: 60, // 60 minutes\n quietHours: { start: 22, end: 7 },\n retryAttempts: 3,\n retryDelay: 30000,\n },\n maintenance: {\n enabled: true,\n interval: 360, // 6 hours\n staleFrameThresholdDays: 30,\n ftsRebuildInterval: 24, // hours\n embeddingBatchSize: 50,\n vacuumInterval: 168, // weekly\n },\n fileWatch: {\n enabled: false, // Disabled by default\n interval: 0, // Not interval-based\n paths: ['.'],\n extensions: ['.ts', '.js', '.tsx', '.jsx', '.py', '.go', '.rs'],\n ignore: ['node_modules', '.git', 'dist', 'build', '.stackmemory'],\n debounceMs: 2000,\n },\n heartbeatInterval: 60, // 1 minute\n inactivityTimeout: 0, // Disabled by default\n logLevel: 'info',\n};\n\nexport interface DaemonStatus {\n running: boolean;\n pid?: number;\n startTime?: number;\n uptime?: number;\n services: {\n context: { enabled: boolean; lastRun?: number; saveCount?: number };\n linear: { enabled: boolean; lastRun?: number; syncCount?: number };\n maintenance: {\n enabled: boolean;\n lastRun?: number;\n staleFramesCleaned?: number;\n ftsRebuilds?: number;\n embeddingsGenerated?: number;\n embeddingsTotal?: number;\n embeddingsRemaining?: number;\n framesGarbageCollected?: number;\n lastGcRun?: number;\n };\n fileWatch: { enabled: boolean; eventsProcessed?: number };\n };\n errors: string[];\n}\n\n/**\n * Get the daemon directory path\n */\nexport function getDaemonDir(): string {\n const dir = join(homedir(), '.stackmemory', 'daemon');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/**\n * Get the logs directory path\n */\nexport function getLogsDir(): string {\n const dir = join(homedir(), '.stackmemory', 'logs');\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/**\n * Get daemon file paths\n */\nexport function getDaemonPaths() {\n const daemonDir = getDaemonDir();\n const logsDir = getLogsDir();\n return {\n pidFile: join(daemonDir, 'daemon.pid'),\n statusFile: join(daemonDir, 'daemon.status'),\n configFile: join(daemonDir, 'config.json'),\n logFile: join(logsDir, 'daemon.log'),\n };\n}\n\n/**\n * Load daemon configuration\n */\nexport function loadDaemonConfig(): DaemonConfig {\n const { configFile } = getDaemonPaths();\n\n if (!existsSync(configFile)) {\n return { ...DEFAULT_DAEMON_CONFIG };\n }\n\n try {\n const content = readFileSync(configFile, 'utf8');\n const config = JSON.parse(content) as Partial<DaemonConfig>;\n return {\n ...DEFAULT_DAEMON_CONFIG,\n ...config,\n context: { ...DEFAULT_DAEMON_CONFIG.context, ...config.context },\n linear: { ...DEFAULT_DAEMON_CONFIG.linear, ...config.linear },\n maintenance: {\n ...DEFAULT_DAEMON_CONFIG.maintenance,\n ...config.maintenance,\n },\n fileWatch: { ...DEFAULT_DAEMON_CONFIG.fileWatch, ...config.fileWatch },\n };\n } catch {\n return { ...DEFAULT_DAEMON_CONFIG };\n }\n}\n\n/**\n * Save daemon configuration\n */\nexport function saveDaemonConfig(config: Partial<DaemonConfig>): void {\n const { configFile } = getDaemonPaths();\n const currentConfig = loadDaemonConfig();\n const newConfig = {\n ...currentConfig,\n ...config,\n context: { ...currentConfig.context, ...config.context },\n linear: { ...currentConfig.linear, ...config.linear },\n maintenance: { ...currentConfig.maintenance, ...config.maintenance },\n fileWatch: { ...currentConfig.fileWatch, ...config.fileWatch },\n };\n writeFileSync(configFile, JSON.stringify(newConfig, null, 2));\n}\n\n/**\n * Read daemon status\n */\nexport function readDaemonStatus(): DaemonStatus {\n const { statusFile, pidFile } = getDaemonPaths();\n\n const defaultStatus: DaemonStatus = {\n running: false,\n services: {\n context: { enabled: false },\n linear: { enabled: false },\n maintenance: { enabled: false },\n fileWatch: { enabled: false },\n },\n errors: [],\n };\n\n // Check PID file first\n if (!existsSync(pidFile)) {\n return defaultStatus;\n }\n\n try {\n const pidContent = readFileSync(pidFile, 'utf8').trim();\n const pid = parseInt(pidContent, 10);\n\n // Check if process is running\n try {\n process.kill(pid, 0);\n } catch {\n // Process not running\n return defaultStatus;\n }\n\n // Read status file\n if (!existsSync(statusFile)) {\n return { ...defaultStatus, running: true, pid };\n }\n\n const content = readFileSync(statusFile, 'utf8');\n const status = JSON.parse(content) as DaemonStatus;\n return {\n ...status,\n running: true,\n pid,\n uptime: status.startTime ? Date.now() - status.startTime : undefined,\n };\n } catch {\n return defaultStatus;\n }\n}\n\n/**\n * Write daemon status\n */\nexport function writeDaemonStatus(status: Partial<DaemonStatus>): void {\n const { statusFile } = getDaemonPaths();\n const currentStatus = readDaemonStatus();\n const newStatus = { ...currentStatus, ...status };\n writeFileSync(statusFile, JSON.stringify(newStatus, null, 2));\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -21,7 +21,11 @@ class DaemonMaintenanceService {
21
21
  lastVacuum: 0,
22
22
  staleFramesCleaned: 0,
23
23
  embeddingsGenerated: 0,
24
+ embeddingsTotal: 0,
25
+ embeddingsRemaining: 0,
24
26
  ftsRebuilds: 0,
27
+ framesGarbageCollected: 0,
28
+ lastGcRun: 0,
25
29
  errors: []
26
30
  };
27
31
  }
@@ -89,6 +93,7 @@ class DaemonMaintenanceService {
89
93
  await this.backfillEmbeddings(db);
90
94
  await this.maybeVacuum(db);
91
95
  await this.generateMissingDigests(db);
96
+ await this.runGC(db);
92
97
  await db.disconnect();
93
98
  this.state.lastRunTime = Date.now();
94
99
  this.onLog("INFO", "Maintenance cycle completed", {
@@ -143,12 +148,56 @@ class DaemonMaintenanceService {
143
148
  if (typeof db.getFramesMissingEmbeddings !== "function") return;
144
149
  if (!db.getFeatures?.().supportsVectorSearch) return;
145
150
  if (!this.embeddingProvider) return;
151
+ let sinceRowid;
152
+ if (typeof db.getMaintenanceState === "function") {
153
+ const lastId = await db.getMaintenanceState(
154
+ "embedding_backfill_last_id"
155
+ );
156
+ if (lastId != null) {
157
+ sinceRowid = parseInt(lastId, 10);
158
+ if (isNaN(sinceRowid)) sinceRowid = void 0;
159
+ }
160
+ }
146
161
  const frames = await db.getFramesMissingEmbeddings(
147
- this.config.embeddingBatchSize
162
+ this.config.embeddingBatchSize,
163
+ sinceRowid
148
164
  );
165
+ if (typeof db.getMaintenanceState === "function") {
166
+ const totalStr = await db.getMaintenanceState(
167
+ "embedding_backfill_total"
168
+ );
169
+ const completedStr = await db.getMaintenanceState(
170
+ "embedding_backfill_completed"
171
+ );
172
+ if (totalStr != null)
173
+ this.state.embeddingsTotal = parseInt(totalStr, 10) || 0;
174
+ if (completedStr != null) {
175
+ const completed = parseInt(completedStr, 10) || 0;
176
+ this.state.embeddingsRemaining = Math.max(
177
+ 0,
178
+ this.state.embeddingsTotal - completed
179
+ );
180
+ }
181
+ }
149
182
  if (frames.length === 0) return;
183
+ if (this.state.embeddingsTotal === 0 && typeof db.setMaintenanceState === "function") {
184
+ const rawDb = db.getRawDatabase?.();
185
+ if (rawDb) {
186
+ const countResult = rawDb.prepare(
187
+ `SELECT COUNT(*) as count FROM frames f
188
+ LEFT JOIN frame_embeddings ve ON f.frame_id = ve.frame_id
189
+ WHERE ve.frame_id IS NULL`
190
+ ).get();
191
+ this.state.embeddingsTotal = countResult.count;
192
+ await db.setMaintenanceState(
193
+ "embedding_backfill_total",
194
+ String(this.state.embeddingsTotal)
195
+ );
196
+ }
197
+ }
150
198
  this.onLog("INFO", `Generating embeddings for ${frames.length} frames`);
151
199
  let generated = 0;
200
+ let lastRowid;
152
201
  for (const frame of frames) {
153
202
  try {
154
203
  const text = [
@@ -160,12 +209,37 @@ class DaemonMaintenanceService {
160
209
  const embedding = await this.embeddingProvider.embed(text);
161
210
  await db.storeEmbedding(frame.frame_id, embedding);
162
211
  generated++;
212
+ const rawDb = db.getRawDatabase?.();
213
+ if (rawDb) {
214
+ const rowidRow = rawDb.prepare("SELECT rowid FROM frames WHERE frame_id = ?").get(frame.frame_id);
215
+ if (rowidRow) lastRowid = rowidRow.rowid;
216
+ }
163
217
  } catch (err) {
164
218
  this.addError(
165
219
  `Embedding frame ${frame.frame_id}: ${err instanceof Error ? err.message : String(err)}`
166
220
  );
167
221
  }
168
222
  }
223
+ if (typeof db.setMaintenanceState === "function") {
224
+ if (lastRowid != null) {
225
+ await db.setMaintenanceState(
226
+ "embedding_backfill_last_id",
227
+ String(lastRowid)
228
+ );
229
+ }
230
+ const prevCompleted = await db.getMaintenanceState(
231
+ "embedding_backfill_completed"
232
+ );
233
+ const totalCompleted = (parseInt(prevCompleted ?? "0", 10) || 0) + generated;
234
+ await db.setMaintenanceState(
235
+ "embedding_backfill_completed",
236
+ String(totalCompleted)
237
+ );
238
+ this.state.embeddingsRemaining = Math.max(
239
+ 0,
240
+ this.state.embeddingsTotal - totalCompleted
241
+ );
242
+ }
169
243
  this.state.embeddingsGenerated += generated;
170
244
  this.onLog("INFO", `Generated ${generated} embeddings`);
171
245
  } catch (err) {
@@ -206,17 +280,50 @@ class DaemonMaintenanceService {
206
280
  );
207
281
  }
208
282
  }
283
+ async runGC(db) {
284
+ try {
285
+ if (this.config.gcEnabled === false) return;
286
+ if (typeof db.runGC !== "function") return;
287
+ const result = await db.runGC({
288
+ retentionDays: this.config.gcRetentionDays ?? 90,
289
+ batchSize: this.config.gcBatchSize ?? 100,
290
+ dryRun: false
291
+ });
292
+ this.state.framesGarbageCollected += result.framesDeleted;
293
+ this.state.lastGcRun = Date.now();
294
+ if (result.framesDeleted > 0) {
295
+ this.onLog(
296
+ "INFO",
297
+ `GC deleted ${result.framesDeleted} expired frames`,
298
+ {
299
+ eventsDeleted: result.eventsDeleted,
300
+ anchorsDeleted: result.anchorsDeleted,
301
+ embeddingsDeleted: result.embeddingsDeleted
302
+ }
303
+ );
304
+ }
305
+ } catch (err) {
306
+ this.addError(`GC: ${err instanceof Error ? err.message : String(err)}`);
307
+ }
308
+ }
209
309
  async getDatabase() {
210
310
  try {
211
311
  const { SQLiteAdapter } = await import("../../core/database/sqlite-adapter.js");
212
- const { createTransformersProvider } = await import("../../core/database/transformers-embedding-provider.js");
312
+ const { EmbeddingProviderFactory } = await import("../../core/database/embedding-provider-factory.js");
213
313
  const dbPath = this.findDatabasePath();
214
314
  if (!dbPath) {
215
315
  this.onLog("WARN", "No database found for maintenance");
216
316
  return null;
217
317
  }
218
318
  if (!this.embeddingProvider) {
219
- this.embeddingProvider = await createTransformersProvider(this.config.embeddingModel) ?? null;
319
+ this.embeddingProvider = await EmbeddingProviderFactory.create({
320
+ provider: this.config.embeddingProvider ?? "transformers",
321
+ model: this.config.embeddingModel,
322
+ dimension: this.config.embeddingDimension,
323
+ apiKey: this.config.embeddingApiKey,
324
+ baseUrl: this.config.embeddingBaseUrl,
325
+ fallbackProviders: this.config.embeddingFallbackProviders
326
+ });
220
327
  }
221
328
  const adapter = new SQLiteAdapter("maintenance", {
222
329
  dbPath,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/daemon/services/maintenance-service.ts"],
4
- "sourcesContent": ["/**\n * Database Maintenance Service\n * Background housekeeping: stale frame cleanup, FTS rebuild,\n * embedding backfill, VACUUM, and digest generation\n */\n\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { MaintenanceServiceConfig } from '../daemon-config.js';\nimport type { EmbeddingProvider } from '../../core/database/embedding-provider.js';\n\nexport interface MaintenanceServiceState {\n lastRunTime: number;\n lastFtsRebuild: number;\n lastVacuum: number;\n staleFramesCleaned: number;\n embeddingsGenerated: number;\n ftsRebuilds: number;\n errors: string[];\n}\n\nexport class DaemonMaintenanceService {\n private config: MaintenanceServiceConfig;\n private state: MaintenanceServiceState;\n private embeddingProvider: EmbeddingProvider | null = null;\n private intervalId?: NodeJS.Timeout;\n private isRunning = false;\n private onLog: (level: string, message: string, data?: unknown) => void;\n\n constructor(\n config: MaintenanceServiceConfig,\n onLog: (level: string, message: string, data?: unknown) => void\n ) {\n this.config = config;\n this.onLog = onLog;\n this.state = {\n lastRunTime: 0,\n lastFtsRebuild: 0,\n lastVacuum: 0,\n staleFramesCleaned: 0,\n embeddingsGenerated: 0,\n ftsRebuilds: 0,\n errors: [],\n };\n }\n\n start(): void {\n if (this.isRunning || !this.config.enabled) {\n return;\n }\n\n this.isRunning = true;\n const intervalMs = this.config.interval * 60 * 1000;\n\n this.onLog('INFO', 'Maintenance service started', {\n interval: this.config.interval,\n staleThresholdDays: this.config.staleFrameThresholdDays,\n ftsRebuildInterval: this.config.ftsRebuildInterval,\n vacuumInterval: this.config.vacuumInterval,\n });\n\n // Schedule periodic maintenance\n this.intervalId = setInterval(() => {\n this.runMaintenance().catch((err) => {\n this.addError(\n `Maintenance cycle failed: ${err instanceof Error ? err.message : String(err)}`\n );\n });\n }, intervalMs);\n }\n\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n this.isRunning = false;\n this.onLog('INFO', 'Maintenance service stopped');\n }\n\n getState(): MaintenanceServiceState {\n return { ...this.state };\n }\n\n updateConfig(config: Partial<MaintenanceServiceConfig>): void {\n const wasRunning = this.isRunning;\n if (wasRunning) {\n this.stop();\n }\n\n this.config = { ...this.config, ...config };\n\n if (wasRunning && this.config.enabled) {\n this.start();\n }\n }\n\n /**\n * Force-run all maintenance tasks immediately\n */\n async forceRun(): Promise<void> {\n await this.runMaintenance();\n }\n\n /**\n * Run all maintenance tasks in sequence\n */\n async runMaintenance(): Promise<void> {\n const startTime = Date.now();\n this.onLog('INFO', 'Starting maintenance cycle');\n\n try {\n const db = await this.getDatabase();\n if (!db) {\n this.onLog('WARN', 'No database available for maintenance');\n return;\n }\n\n // Task 1: Stale frame cleanup\n await this.cleanStaleFrames(db);\n\n // Task 2: FTS rebuild (if due)\n await this.maybeRebuildFts(db);\n\n // Task 3: Embedding backfill (if provider configured)\n await this.backfillEmbeddings(db);\n\n // Task 4: VACUUM (if due)\n await this.maybeVacuum(db);\n\n // Task 5: Digest generation for frames missing digest_text\n await this.generateMissingDigests(db);\n\n await db.disconnect();\n\n this.state.lastRunTime = Date.now();\n this.onLog('INFO', 'Maintenance cycle completed', {\n durationMs: Date.now() - startTime,\n staleFramesCleaned: this.state.staleFramesCleaned,\n ftsRebuilds: this.state.ftsRebuilds,\n embeddingsGenerated: this.state.embeddingsGenerated,\n });\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n this.addError(errorMsg);\n this.onLog('ERROR', 'Maintenance cycle failed', { error: errorMsg });\n }\n }\n\n private async cleanStaleFrames(db: any): Promise<void> {\n try {\n const thresholdSec =\n Math.floor(Date.now() / 1000) -\n this.config.staleFrameThresholdDays * 24 * 3600;\n\n const rawDb = db.getRawDatabase?.();\n if (!rawDb) return;\n\n const result = rawDb\n .prepare(\n \"UPDATE frames SET state = 'stale' WHERE state = 'active' AND created_at < ?\"\n )\n .run(thresholdSec);\n\n const cleaned = result.changes || 0;\n this.state.staleFramesCleaned += cleaned;\n\n if (cleaned > 0) {\n this.onLog('INFO', `Marked ${cleaned} stale frames`);\n }\n } catch (err) {\n this.addError(\n `Stale frame cleanup: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async maybeRebuildFts(db: any): Promise<void> {\n try {\n const hoursSinceLastRebuild =\n (Date.now() - this.state.lastFtsRebuild) / (1000 * 3600);\n\n if (hoursSinceLastRebuild < this.config.ftsRebuildInterval) return;\n\n if (typeof db.rebuildFtsIndex === 'function') {\n await db.rebuildFtsIndex();\n this.state.lastFtsRebuild = Date.now();\n this.state.ftsRebuilds++;\n this.onLog('INFO', 'FTS index rebuilt');\n }\n } catch (err) {\n this.addError(\n `FTS rebuild: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async backfillEmbeddings(db: any): Promise<void> {\n try {\n if (typeof db.getFramesMissingEmbeddings !== 'function') return;\n if (!db.getFeatures?.().supportsVectorSearch) return;\n if (!this.embeddingProvider) return;\n\n const frames = await db.getFramesMissingEmbeddings(\n this.config.embeddingBatchSize\n );\n\n if (frames.length === 0) return;\n\n this.onLog('INFO', `Generating embeddings for ${frames.length} frames`);\n\n let generated = 0;\n for (const frame of frames) {\n try {\n const text = [\n frame.name,\n frame.digest_text,\n JSON.stringify(frame.inputs),\n ]\n .filter(Boolean)\n .join(' ');\n if (!text.trim()) continue;\n\n const embedding = await this.embeddingProvider.embed(text);\n await db.storeEmbedding(frame.frame_id, embedding);\n generated++;\n } catch (err) {\n this.addError(\n `Embedding frame ${frame.frame_id}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n this.state.embeddingsGenerated += generated;\n this.onLog('INFO', `Generated ${generated} embeddings`);\n } catch (err) {\n this.addError(\n `Embedding backfill: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async maybeVacuum(db: any): Promise<void> {\n try {\n const hoursSinceLastVacuum =\n (Date.now() - this.state.lastVacuum) / (1000 * 3600);\n\n if (hoursSinceLastVacuum < this.config.vacuumInterval) return;\n\n const rawDb = db.getRawDatabase?.();\n if (!rawDb) return;\n\n rawDb.pragma('optimize');\n rawDb.pragma('vacuum');\n\n this.state.lastVacuum = Date.now();\n this.onLog('INFO', 'Database optimized and vacuumed');\n } catch (err) {\n this.addError(\n `VACUUM: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async generateMissingDigests(db: any): Promise<void> {\n try {\n const rawDb = db.getRawDatabase?.();\n if (!rawDb) return;\n\n const count = (\n rawDb\n .prepare(\n \"SELECT COUNT(*) as count FROM frames WHERE digest_text IS NULL AND state = 'active'\"\n )\n .get() as { count: number }\n ).count;\n\n if (count > 0) {\n this.onLog('INFO', `Found ${count} frames missing digest_text`);\n }\n } catch (err) {\n this.addError(\n `Digest generation: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async getDatabase(): Promise<any> {\n try {\n const { SQLiteAdapter } =\n await import('../../core/database/sqlite-adapter.js');\n const { createTransformersProvider } =\n await import('../../core/database/transformers-embedding-provider.js');\n\n const dbPath = this.findDatabasePath();\n if (!dbPath) {\n this.onLog('WARN', 'No database found for maintenance');\n return null;\n }\n\n if (!this.embeddingProvider) {\n this.embeddingProvider =\n (await createTransformersProvider(this.config.embeddingModel)) ??\n null;\n }\n\n const adapter = new SQLiteAdapter('maintenance', {\n dbPath,\n embeddingProvider: this.embeddingProvider ?? undefined,\n });\n await adapter.connect();\n await adapter.initializeSchema();\n return adapter;\n } catch (err) {\n this.addError(\n `Database init: ${err instanceof Error ? err.message : String(err)}`\n );\n return null;\n }\n }\n\n private findDatabasePath(): string | null {\n const homeDir = homedir();\n const candidates = [\n join(process.cwd(), '.stackmemory', 'stackmemory.db'),\n join(homeDir, '.stackmemory', 'stackmemory.db'),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n }\n\n private addError(msg: string): void {\n this.state.errors.push(msg);\n if (this.state.errors.length > 10) {\n this.state.errors = this.state.errors.slice(-10);\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Database Maintenance Service\n * Background housekeeping: stale frame cleanup, FTS rebuild,\n * embedding backfill, VACUUM, and digest generation\n */\n\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport type { MaintenanceServiceConfig } from '../daemon-config.js';\nimport type { EmbeddingProvider } from '../../core/database/embedding-provider.js';\n\nexport interface MaintenanceServiceState {\n lastRunTime: number;\n lastFtsRebuild: number;\n lastVacuum: number;\n staleFramesCleaned: number;\n embeddingsGenerated: number;\n embeddingsTotal: number;\n embeddingsRemaining: number;\n ftsRebuilds: number;\n framesGarbageCollected: number;\n lastGcRun: number;\n errors: string[];\n}\n\nexport class DaemonMaintenanceService {\n private config: MaintenanceServiceConfig;\n private state: MaintenanceServiceState;\n private embeddingProvider: EmbeddingProvider | null = null;\n private intervalId?: NodeJS.Timeout;\n private isRunning = false;\n private onLog: (level: string, message: string, data?: unknown) => void;\n\n constructor(\n config: MaintenanceServiceConfig,\n onLog: (level: string, message: string, data?: unknown) => void\n ) {\n this.config = config;\n this.onLog = onLog;\n this.state = {\n lastRunTime: 0,\n lastFtsRebuild: 0,\n lastVacuum: 0,\n staleFramesCleaned: 0,\n embeddingsGenerated: 0,\n embeddingsTotal: 0,\n embeddingsRemaining: 0,\n ftsRebuilds: 0,\n framesGarbageCollected: 0,\n lastGcRun: 0,\n errors: [],\n };\n }\n\n start(): void {\n if (this.isRunning || !this.config.enabled) {\n return;\n }\n\n this.isRunning = true;\n const intervalMs = this.config.interval * 60 * 1000;\n\n this.onLog('INFO', 'Maintenance service started', {\n interval: this.config.interval,\n staleThresholdDays: this.config.staleFrameThresholdDays,\n ftsRebuildInterval: this.config.ftsRebuildInterval,\n vacuumInterval: this.config.vacuumInterval,\n });\n\n // Schedule periodic maintenance\n this.intervalId = setInterval(() => {\n this.runMaintenance().catch((err) => {\n this.addError(\n `Maintenance cycle failed: ${err instanceof Error ? err.message : String(err)}`\n );\n });\n }, intervalMs);\n }\n\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n this.isRunning = false;\n this.onLog('INFO', 'Maintenance service stopped');\n }\n\n getState(): MaintenanceServiceState {\n return { ...this.state };\n }\n\n updateConfig(config: Partial<MaintenanceServiceConfig>): void {\n const wasRunning = this.isRunning;\n if (wasRunning) {\n this.stop();\n }\n\n this.config = { ...this.config, ...config };\n\n if (wasRunning && this.config.enabled) {\n this.start();\n }\n }\n\n /**\n * Force-run all maintenance tasks immediately\n */\n async forceRun(): Promise<void> {\n await this.runMaintenance();\n }\n\n /**\n * Run all maintenance tasks in sequence\n */\n async runMaintenance(): Promise<void> {\n const startTime = Date.now();\n this.onLog('INFO', 'Starting maintenance cycle');\n\n try {\n const db = await this.getDatabase();\n if (!db) {\n this.onLog('WARN', 'No database available for maintenance');\n return;\n }\n\n // Task 1: Stale frame cleanup\n await this.cleanStaleFrames(db);\n\n // Task 2: FTS rebuild (if due)\n await this.maybeRebuildFts(db);\n\n // Task 3: Embedding backfill (if provider configured)\n await this.backfillEmbeddings(db);\n\n // Task 4: VACUUM (if due)\n await this.maybeVacuum(db);\n\n // Task 5: Digest generation for frames missing digest_text\n await this.generateMissingDigests(db);\n\n // Task 6: Incremental garbage collection\n await this.runGC(db);\n\n await db.disconnect();\n\n this.state.lastRunTime = Date.now();\n this.onLog('INFO', 'Maintenance cycle completed', {\n durationMs: Date.now() - startTime,\n staleFramesCleaned: this.state.staleFramesCleaned,\n ftsRebuilds: this.state.ftsRebuilds,\n embeddingsGenerated: this.state.embeddingsGenerated,\n });\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n this.addError(errorMsg);\n this.onLog('ERROR', 'Maintenance cycle failed', { error: errorMsg });\n }\n }\n\n private async cleanStaleFrames(db: any): Promise<void> {\n try {\n const thresholdSec =\n Math.floor(Date.now() / 1000) -\n this.config.staleFrameThresholdDays * 24 * 3600;\n\n const rawDb = db.getRawDatabase?.();\n if (!rawDb) return;\n\n const result = rawDb\n .prepare(\n \"UPDATE frames SET state = 'stale' WHERE state = 'active' AND created_at < ?\"\n )\n .run(thresholdSec);\n\n const cleaned = result.changes || 0;\n this.state.staleFramesCleaned += cleaned;\n\n if (cleaned > 0) {\n this.onLog('INFO', `Marked ${cleaned} stale frames`);\n }\n } catch (err) {\n this.addError(\n `Stale frame cleanup: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async maybeRebuildFts(db: any): Promise<void> {\n try {\n const hoursSinceLastRebuild =\n (Date.now() - this.state.lastFtsRebuild) / (1000 * 3600);\n\n if (hoursSinceLastRebuild < this.config.ftsRebuildInterval) return;\n\n if (typeof db.rebuildFtsIndex === 'function') {\n await db.rebuildFtsIndex();\n this.state.lastFtsRebuild = Date.now();\n this.state.ftsRebuilds++;\n this.onLog('INFO', 'FTS index rebuilt');\n }\n } catch (err) {\n this.addError(\n `FTS rebuild: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async backfillEmbeddings(db: any): Promise<void> {\n try {\n if (typeof db.getFramesMissingEmbeddings !== 'function') return;\n if (!db.getFeatures?.().supportsVectorSearch) return;\n if (!this.embeddingProvider) return;\n\n // Read checkpoint: last processed rowid\n let sinceRowid: number | undefined;\n if (typeof db.getMaintenanceState === 'function') {\n const lastId = await db.getMaintenanceState(\n 'embedding_backfill_last_id'\n );\n if (lastId != null) {\n sinceRowid = parseInt(lastId, 10);\n if (isNaN(sinceRowid)) sinceRowid = undefined;\n }\n }\n\n const frames = await db.getFramesMissingEmbeddings(\n this.config.embeddingBatchSize,\n sinceRowid\n );\n\n // Update total/remaining counts for progress reporting\n if (typeof db.getMaintenanceState === 'function') {\n const totalStr = await db.getMaintenanceState(\n 'embedding_backfill_total'\n );\n const completedStr = await db.getMaintenanceState(\n 'embedding_backfill_completed'\n );\n if (totalStr != null)\n this.state.embeddingsTotal = parseInt(totalStr, 10) || 0;\n if (completedStr != null) {\n const completed = parseInt(completedStr, 10) || 0;\n this.state.embeddingsRemaining = Math.max(\n 0,\n this.state.embeddingsTotal - completed\n );\n }\n }\n\n if (frames.length === 0) return;\n\n // If we have no total yet, count all frames missing embeddings\n if (\n this.state.embeddingsTotal === 0 &&\n typeof db.setMaintenanceState === 'function'\n ) {\n const rawDb = db.getRawDatabase?.();\n if (rawDb) {\n const countResult = rawDb\n .prepare(\n `SELECT COUNT(*) as count FROM frames f\n LEFT JOIN frame_embeddings ve ON f.frame_id = ve.frame_id\n WHERE ve.frame_id IS NULL`\n )\n .get() as { count: number };\n this.state.embeddingsTotal = countResult.count;\n await db.setMaintenanceState(\n 'embedding_backfill_total',\n String(this.state.embeddingsTotal)\n );\n }\n }\n\n this.onLog('INFO', `Generating embeddings for ${frames.length} frames`);\n\n let generated = 0;\n let lastRowid: number | undefined;\n for (const frame of frames) {\n try {\n const text = [\n frame.name,\n frame.digest_text,\n JSON.stringify(frame.inputs),\n ]\n .filter(Boolean)\n .join(' ');\n if (!text.trim()) continue;\n\n const embedding = await this.embeddingProvider.embed(text);\n await db.storeEmbedding(frame.frame_id, embedding);\n generated++;\n\n // Track the rowid of the last processed frame for checkpoint\n const rawDb = db.getRawDatabase?.();\n if (rawDb) {\n const rowidRow = rawDb\n .prepare('SELECT rowid FROM frames WHERE frame_id = ?')\n .get(frame.frame_id) as { rowid: number } | undefined;\n if (rowidRow) lastRowid = rowidRow.rowid;\n }\n } catch (err) {\n this.addError(\n `Embedding frame ${frame.frame_id}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n // Save checkpoint after batch\n if (typeof db.setMaintenanceState === 'function') {\n if (lastRowid != null) {\n await db.setMaintenanceState(\n 'embedding_backfill_last_id',\n String(lastRowid)\n );\n }\n const prevCompleted = await db.getMaintenanceState(\n 'embedding_backfill_completed'\n );\n const totalCompleted =\n (parseInt(prevCompleted ?? '0', 10) || 0) + generated;\n await db.setMaintenanceState(\n 'embedding_backfill_completed',\n String(totalCompleted)\n );\n\n this.state.embeddingsRemaining = Math.max(\n 0,\n this.state.embeddingsTotal - totalCompleted\n );\n }\n\n this.state.embeddingsGenerated += generated;\n this.onLog('INFO', `Generated ${generated} embeddings`);\n } catch (err) {\n this.addError(\n `Embedding backfill: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async maybeVacuum(db: any): Promise<void> {\n try {\n const hoursSinceLastVacuum =\n (Date.now() - this.state.lastVacuum) / (1000 * 3600);\n\n if (hoursSinceLastVacuum < this.config.vacuumInterval) return;\n\n const rawDb = db.getRawDatabase?.();\n if (!rawDb) return;\n\n rawDb.pragma('optimize');\n rawDb.pragma('vacuum');\n\n this.state.lastVacuum = Date.now();\n this.onLog('INFO', 'Database optimized and vacuumed');\n } catch (err) {\n this.addError(\n `VACUUM: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async generateMissingDigests(db: any): Promise<void> {\n try {\n const rawDb = db.getRawDatabase?.();\n if (!rawDb) return;\n\n const count = (\n rawDb\n .prepare(\n \"SELECT COUNT(*) as count FROM frames WHERE digest_text IS NULL AND state = 'active'\"\n )\n .get() as { count: number }\n ).count;\n\n if (count > 0) {\n this.onLog('INFO', `Found ${count} frames missing digest_text`);\n }\n } catch (err) {\n this.addError(\n `Digest generation: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n private async runGC(db: any): Promise<void> {\n try {\n if (this.config.gcEnabled === false) return;\n if (typeof db.runGC !== 'function') return;\n\n const result = await db.runGC({\n retentionDays: this.config.gcRetentionDays ?? 90,\n batchSize: this.config.gcBatchSize ?? 100,\n dryRun: false,\n });\n\n this.state.framesGarbageCollected += result.framesDeleted;\n this.state.lastGcRun = Date.now();\n\n if (result.framesDeleted > 0) {\n this.onLog(\n 'INFO',\n `GC deleted ${result.framesDeleted} expired frames`,\n {\n eventsDeleted: result.eventsDeleted,\n anchorsDeleted: result.anchorsDeleted,\n embeddingsDeleted: result.embeddingsDeleted,\n }\n );\n }\n } catch (err) {\n this.addError(`GC: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n private async getDatabase(): Promise<any> {\n try {\n const { SQLiteAdapter } =\n await import('../../core/database/sqlite-adapter.js');\n const { EmbeddingProviderFactory } =\n await import('../../core/database/embedding-provider-factory.js');\n\n const dbPath = this.findDatabasePath();\n if (!dbPath) {\n this.onLog('WARN', 'No database found for maintenance');\n return null;\n }\n\n if (!this.embeddingProvider) {\n this.embeddingProvider = await EmbeddingProviderFactory.create({\n provider: this.config.embeddingProvider ?? 'transformers',\n model: this.config.embeddingModel,\n dimension: this.config.embeddingDimension,\n apiKey: this.config.embeddingApiKey,\n baseUrl: this.config.embeddingBaseUrl,\n fallbackProviders: this.config.embeddingFallbackProviders,\n });\n }\n\n const adapter = new SQLiteAdapter('maintenance', {\n dbPath,\n embeddingProvider: this.embeddingProvider ?? undefined,\n });\n await adapter.connect();\n await adapter.initializeSchema();\n return adapter;\n } catch (err) {\n this.addError(\n `Database init: ${err instanceof Error ? err.message : String(err)}`\n );\n return null;\n }\n }\n\n private findDatabasePath(): string | null {\n const homeDir = homedir();\n const candidates = [\n join(process.cwd(), '.stackmemory', 'stackmemory.db'),\n join(homeDir, '.stackmemory', 'stackmemory.db'),\n ];\n\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n\n return null;\n }\n\n private addError(msg: string): void {\n this.state.errors.push(msg);\n if (this.state.errors.length > 10) {\n this.state.errors = this.state.errors.slice(-10);\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }