k0ntext 3.0.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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +623 -0
  3. package/bin/k0ntext.js +12 -0
  4. package/dist/agents/cleanup-agent.d.ts +39 -0
  5. package/dist/agents/cleanup-agent.d.ts.map +1 -0
  6. package/dist/agents/cleanup-agent.js +56 -0
  7. package/dist/agents/cleanup-agent.js.map +1 -0
  8. package/dist/agents/performance-agent.d.ts +37 -0
  9. package/dist/agents/performance-agent.d.ts.map +1 -0
  10. package/dist/agents/performance-agent.js +91 -0
  11. package/dist/agents/performance-agent.js.map +1 -0
  12. package/dist/analyzer/index.d.ts +5 -0
  13. package/dist/analyzer/index.d.ts.map +1 -0
  14. package/dist/analyzer/index.js +5 -0
  15. package/dist/analyzer/index.js.map +1 -0
  16. package/dist/analyzer/intelligent-analyzer.d.ts +111 -0
  17. package/dist/analyzer/intelligent-analyzer.d.ts.map +1 -0
  18. package/dist/analyzer/intelligent-analyzer.js +537 -0
  19. package/dist/analyzer/intelligent-analyzer.js.map +1 -0
  20. package/dist/cli/commands/cleanup.d.ts +3 -0
  21. package/dist/cli/commands/cleanup.d.ts.map +1 -0
  22. package/dist/cli/commands/cleanup.js +24 -0
  23. package/dist/cli/commands/cleanup.js.map +1 -0
  24. package/dist/cli/commands/export.d.ts +9 -0
  25. package/dist/cli/commands/export.d.ts.map +1 -0
  26. package/dist/cli/commands/export.js +72 -0
  27. package/dist/cli/commands/export.js.map +1 -0
  28. package/dist/cli/commands/import.d.ts +9 -0
  29. package/dist/cli/commands/import.d.ts.map +1 -0
  30. package/dist/cli/commands/import.js +62 -0
  31. package/dist/cli/commands/import.js.map +1 -0
  32. package/dist/cli/commands/performance.d.ts +9 -0
  33. package/dist/cli/commands/performance.d.ts.map +1 -0
  34. package/dist/cli/commands/performance.js +36 -0
  35. package/dist/cli/commands/performance.js.map +1 -0
  36. package/dist/cli/commands/validate.d.ts +9 -0
  37. package/dist/cli/commands/validate.d.ts.map +1 -0
  38. package/dist/cli/commands/validate.js +82 -0
  39. package/dist/cli/commands/validate.js.map +1 -0
  40. package/dist/cli/commands/watch.d.ts +9 -0
  41. package/dist/cli/commands/watch.d.ts.map +1 -0
  42. package/dist/cli/commands/watch.js +72 -0
  43. package/dist/cli/commands/watch.js.map +1 -0
  44. package/dist/cli/generate.d.ts +3 -0
  45. package/dist/cli/generate.d.ts.map +1 -0
  46. package/dist/cli/generate.js +194 -0
  47. package/dist/cli/generate.js.map +1 -0
  48. package/dist/cli/index.d.ts +9 -0
  49. package/dist/cli/index.d.ts.map +1 -0
  50. package/dist/cli/index.js +448 -0
  51. package/dist/cli/index.js.map +1 -0
  52. package/dist/cli/sync.d.ts +26 -0
  53. package/dist/cli/sync.d.ts.map +1 -0
  54. package/dist/cli/sync.js +163 -0
  55. package/dist/cli/sync.js.map +1 -0
  56. package/dist/config/cleanup-config.d.ts +26 -0
  57. package/dist/config/cleanup-config.d.ts.map +1 -0
  58. package/dist/config/cleanup-config.js +21 -0
  59. package/dist/config/cleanup-config.js.map +1 -0
  60. package/dist/db/client.d.ts +284 -0
  61. package/dist/db/client.d.ts.map +1 -0
  62. package/dist/db/client.js +688 -0
  63. package/dist/db/client.js.map +1 -0
  64. package/dist/db/index.d.ts +6 -0
  65. package/dist/db/index.d.ts.map +1 -0
  66. package/dist/db/index.js +6 -0
  67. package/dist/db/index.js.map +1 -0
  68. package/dist/db/schema.d.ts +41 -0
  69. package/dist/db/schema.d.ts.map +1 -0
  70. package/dist/db/schema.js +226 -0
  71. package/dist/db/schema.js.map +1 -0
  72. package/dist/embeddings/index.d.ts +5 -0
  73. package/dist/embeddings/index.d.ts.map +1 -0
  74. package/dist/embeddings/index.js +5 -0
  75. package/dist/embeddings/index.js.map +1 -0
  76. package/dist/embeddings/openrouter.d.ts +133 -0
  77. package/dist/embeddings/openrouter.d.ts.map +1 -0
  78. package/dist/embeddings/openrouter.js +455 -0
  79. package/dist/embeddings/openrouter.js.map +1 -0
  80. package/dist/index.d.ts +14 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +18 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/mcp.d.ts +29 -0
  85. package/dist/mcp.d.ts.map +1 -0
  86. package/dist/mcp.js +257 -0
  87. package/dist/mcp.js.map +1 -0
  88. package/docs/ARCHIVE/MIGRATE_TO_NEW_REPO.md +222 -0
  89. package/docs/ARCHIVE/MIGRATE_TO_UNIFIED.md +220 -0
  90. package/docs/CLEANUP.md +76 -0
  91. package/docs/MCP_QUICKSTART.md +219 -0
  92. package/docs/QUICKSTART.md +119 -0
  93. package/docs/TROUBLESHOOTING.md +611 -0
  94. package/package.json +100 -0
  95. package/skills/context-optimize/SKILL.md +86 -0
  96. package/skills/implement/SKILL.md +150 -0
  97. package/skills/plan/SKILL.md +143 -0
  98. package/skills/research/SKILL.md +103 -0
  99. package/skills/validate/SKILL.md +62 -0
  100. package/skills/verify-docs/SKILL.md +77 -0
  101. package/src/agents/cleanup-agent.ts +96 -0
  102. package/src/agents/performance-agent.ts +117 -0
  103. package/src/analyzer/index.ts +10 -0
  104. package/src/analyzer/intelligent-analyzer.ts +640 -0
  105. package/src/cli/commands/cleanup.ts +26 -0
  106. package/src/cli/commands/export.ts +82 -0
  107. package/src/cli/commands/import.ts +73 -0
  108. package/src/cli/commands/performance.ts +40 -0
  109. package/src/cli/commands/validate.ts +98 -0
  110. package/src/cli/commands/watch.ts +83 -0
  111. package/src/cli/generate.ts +219 -0
  112. package/src/cli/index.ts +510 -0
  113. package/src/cli/sync.ts +194 -0
  114. package/src/config/cleanup-config.ts +42 -0
  115. package/src/db/client.ts +949 -0
  116. package/src/db/index.ts +19 -0
  117. package/src/db/schema.ts +241 -0
  118. package/src/embeddings/index.ts +11 -0
  119. package/src/embeddings/openrouter.ts +592 -0
  120. package/src/index.ts +57 -0
  121. package/src/mcp.ts +354 -0
  122. package/templates/AI_CONTEXT.md.template +245 -0
  123. package/templates/base/README.md +260 -0
  124. package/templates/base/RPI_WORKFLOW_PLAN.md +325 -0
  125. package/templates/base/agents/api-developer.md +76 -0
  126. package/templates/base/agents/context-engineer.md +525 -0
  127. package/templates/base/agents/core-architect.md +76 -0
  128. package/templates/base/agents/database-ops.md +76 -0
  129. package/templates/base/agents/deployment-ops.md +76 -0
  130. package/templates/base/agents/integration-hub.md +76 -0
  131. package/templates/base/analytics/README.md +114 -0
  132. package/templates/base/automation/config.json +58 -0
  133. package/templates/base/automation/generators/code-mapper.js +308 -0
  134. package/templates/base/automation/generators/index-builder.js +321 -0
  135. package/templates/base/automation/hooks/post-commit.sh +83 -0
  136. package/templates/base/automation/hooks/pre-commit.sh +103 -0
  137. package/templates/base/ci-templates/README.md +108 -0
  138. package/templates/base/ci-templates/github-actions/context-check.yml +144 -0
  139. package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -0
  140. package/templates/base/commands/analytics.md +238 -0
  141. package/templates/base/commands/auto-sync.md +172 -0
  142. package/templates/base/commands/collab.md +194 -0
  143. package/templates/base/commands/context-optimize.md +226 -0
  144. package/templates/base/commands/help.md +485 -0
  145. package/templates/base/commands/rpi-implement.md +164 -0
  146. package/templates/base/commands/rpi-plan.md +147 -0
  147. package/templates/base/commands/rpi-research.md +145 -0
  148. package/templates/base/commands/session-resume.md +144 -0
  149. package/templates/base/commands/session-save.md +112 -0
  150. package/templates/base/commands/validate-all.md +77 -0
  151. package/templates/base/commands/verify-docs-current.md +86 -0
  152. package/templates/base/config/base.json +57 -0
  153. package/templates/base/config/environments/development.json +13 -0
  154. package/templates/base/config/environments/production.json +17 -0
  155. package/templates/base/config/environments/staging.json +13 -0
  156. package/templates/base/config/local.json.example +21 -0
  157. package/templates/base/context/.meta/generated-at.json +18 -0
  158. package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -0
  159. package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -0
  160. package/templates/base/context/FILE_OWNERSHIP.md +57 -0
  161. package/templates/base/context/INTEGRATION_POINTS.md +92 -0
  162. package/templates/base/context/KNOWN_GOTCHAS.md +195 -0
  163. package/templates/base/context/TESTING_MAP.md +95 -0
  164. package/templates/base/context/WORKFLOW_INDEX.md +129 -0
  165. package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -0
  166. package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -0
  167. package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -0
  168. package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -0
  169. package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -0
  170. package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -0
  171. package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -0
  172. package/templates/base/knowledge/README.md +98 -0
  173. package/templates/base/knowledge/sessions/README.md +88 -0
  174. package/templates/base/knowledge/sessions/TEMPLATE.md +150 -0
  175. package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -0
  176. package/templates/base/knowledge/shared/decisions/README.md +49 -0
  177. package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -0
  178. package/templates/base/knowledge/shared/patterns/README.md +62 -0
  179. package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -0
  180. package/templates/base/plans/PLAN_TEMPLATE.md +316 -0
  181. package/templates/base/plans/active/.gitkeep +0 -0
  182. package/templates/base/plans/completed/.gitkeep +0 -0
  183. package/templates/base/research/RESEARCH_TEMPLATE.md +245 -0
  184. package/templates/base/research/active/.gitkeep +0 -0
  185. package/templates/base/research/completed/.gitkeep +0 -0
  186. package/templates/base/schemas/agent.schema.json +141 -0
  187. package/templates/base/schemas/anchors.schema.json +54 -0
  188. package/templates/base/schemas/automation.schema.json +93 -0
  189. package/templates/base/schemas/command.schema.json +134 -0
  190. package/templates/base/schemas/hashes.schema.json +40 -0
  191. package/templates/base/schemas/manifest.schema.json +117 -0
  192. package/templates/base/schemas/plan.schema.json +136 -0
  193. package/templates/base/schemas/research.schema.json +115 -0
  194. package/templates/base/schemas/roles.schema.json +34 -0
  195. package/templates/base/schemas/session.schema.json +77 -0
  196. package/templates/base/schemas/settings.schema.json +244 -0
  197. package/templates/base/schemas/staleness.schema.json +53 -0
  198. package/templates/base/schemas/team-config.schema.json +42 -0
  199. package/templates/base/schemas/workflow.schema.json +126 -0
  200. package/templates/base/session/checkpoints/.gitkeep +2 -0
  201. package/templates/base/session/current/state.json +20 -0
  202. package/templates/base/session/history/.gitkeep +2 -0
  203. package/templates/base/settings.json +3 -0
  204. package/templates/base/standards/COMPATIBILITY.md +219 -0
  205. package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -0
  206. package/templates/base/standards/QUALITY_CHECKLIST.md +211 -0
  207. package/templates/base/standards/README.md +66 -0
  208. package/templates/base/sync/anchors.json +6 -0
  209. package/templates/base/sync/hashes.json +6 -0
  210. package/templates/base/sync/staleness.json +10 -0
  211. package/templates/base/team/README.md +168 -0
  212. package/templates/base/team/config.json +79 -0
  213. package/templates/base/team/roles.json +145 -0
  214. package/templates/base/tools/bin/claude-context.js +151 -0
  215. package/templates/base/tools/lib/anchor-resolver.js +276 -0
  216. package/templates/base/tools/lib/config-loader.js +363 -0
  217. package/templates/base/tools/lib/detector.js +350 -0
  218. package/templates/base/tools/lib/diagnose.js +206 -0
  219. package/templates/base/tools/lib/drift-detector.js +373 -0
  220. package/templates/base/tools/lib/errors.js +199 -0
  221. package/templates/base/tools/lib/index.js +36 -0
  222. package/templates/base/tools/lib/init.js +192 -0
  223. package/templates/base/tools/lib/logger.js +230 -0
  224. package/templates/base/tools/lib/placeholder.js +201 -0
  225. package/templates/base/tools/lib/session-manager.js +354 -0
  226. package/templates/base/tools/lib/validate.js +521 -0
  227. package/templates/base/tools/package.json +49 -0
  228. package/templates/handlebars/aider-config.hbs +146 -0
  229. package/templates/handlebars/antigravity.hbs +377 -0
  230. package/templates/handlebars/claude.hbs +183 -0
  231. package/templates/handlebars/cline.hbs +62 -0
  232. package/templates/handlebars/continue-config.hbs +116 -0
  233. package/templates/handlebars/copilot.hbs +130 -0
  234. package/templates/handlebars/partials/gotcha-list.hbs +11 -0
  235. package/templates/handlebars/partials/header.hbs +3 -0
  236. package/templates/handlebars/partials/workflow-summary.hbs +16 -0
  237. package/templates/handlebars/windsurf-rules.hbs +69 -0
  238. package/templates/hooks/post-commit.hbs +28 -0
  239. package/templates/hooks/pre-commit.hbs +46 -0
@@ -0,0 +1,949 @@
1
+ /**
2
+ * Database Client
3
+ *
4
+ * SQLite database operations for AI context storage.
5
+ * Handles CRUD operations, queries, and vector search.
6
+ */
7
+
8
+ import Database from 'better-sqlite3';
9
+ import * as sqliteVec from 'sqlite-vec';
10
+ import { createHash } from 'crypto';
11
+ import path from 'path';
12
+ import fs from 'fs';
13
+ import {
14
+ SCHEMA_SQL,
15
+ VECTOR_SCHEMA_SQL,
16
+ SCHEMA_VERSION,
17
+ type ContextType,
18
+ type RelationType,
19
+ type SyncStatus,
20
+ type AITool
21
+ } from './schema.js';
22
+
23
+ /**
24
+ * Context item structure
25
+ */
26
+ export interface ContextItem {
27
+ id: string;
28
+ type: ContextType;
29
+ name: string;
30
+ content: string;
31
+ metadata?: Record<string, unknown>;
32
+ filePath?: string;
33
+ contentHash?: string;
34
+ createdAt?: string;
35
+ updatedAt?: string;
36
+ }
37
+
38
+ /**
39
+ * Knowledge graph edge
40
+ */
41
+ export interface GraphEdge {
42
+ id?: number;
43
+ sourceId: string;
44
+ targetId: string;
45
+ relationType: RelationType;
46
+ weight?: number;
47
+ metadata?: Record<string, unknown>;
48
+ }
49
+
50
+ /**
51
+ * Git commit record
52
+ */
53
+ export interface GitCommit {
54
+ sha: string;
55
+ message: string;
56
+ authorName?: string;
57
+ authorEmail?: string;
58
+ timestamp: string;
59
+ filesChanged?: string[];
60
+ stats?: { additions: number; deletions: number };
61
+ }
62
+
63
+ /**
64
+ * Sync state record
65
+ */
66
+ export interface SyncState {
67
+ id: string;
68
+ tool: string;
69
+ contentHash?: string;
70
+ lastSync: string;
71
+ filePath?: string;
72
+ status: SyncStatus;
73
+ metadata?: Record<string, unknown>;
74
+ }
75
+
76
+ /**
77
+ * AI tool configuration record
78
+ */
79
+ export interface AIToolConfig {
80
+ id: string;
81
+ toolName: AITool;
82
+ configPath: string;
83
+ content: string;
84
+ contentHash?: string;
85
+ lastSync: string;
86
+ status: SyncStatus;
87
+ metadata?: Record<string, unknown>;
88
+ }
89
+
90
+ /**
91
+ * Search result with similarity score
92
+ */
93
+ export interface SearchResult {
94
+ item: ContextItem;
95
+ similarity: number;
96
+ }
97
+
98
+ /**
99
+ * Database client for AI context storage
100
+ */
101
+ export class DatabaseClient {
102
+ private db: Database.Database;
103
+ private dbPath: string;
104
+
105
+ constructor(projectRoot: string, dbFileName = '.k0ntext.db') {
106
+ this.dbPath = path.join(projectRoot, dbFileName);
107
+
108
+ // Ensure directory exists
109
+ const dbDir = path.dirname(this.dbPath);
110
+ if (!fs.existsSync(dbDir)) {
111
+ fs.mkdirSync(dbDir, { recursive: true });
112
+ }
113
+
114
+ this.db = new Database(this.dbPath);
115
+
116
+ // Enable foreign keys
117
+ this.db.pragma('foreign_keys = ON');
118
+
119
+ // Load sqlite-vec extension
120
+ sqliteVec.load(this.db);
121
+
122
+ // Initialize schema
123
+ this.initSchema();
124
+ }
125
+
126
+ /**
127
+ * Migrate legacy database
128
+ */
129
+ private migrateLegacyDatabase(): void {
130
+ const legacyPath = path.join(process.cwd(), '.ai-context.db');
131
+ const newPath = this.dbPath;
132
+
133
+ if (fs.existsSync(legacyPath) && !fs.existsSync(newPath)) {
134
+ fs.copyFileSync(legacyPath, newPath);
135
+ console.log(`✓ Migrated .ai-context.db to .k0ntext.db`);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Initialize database schema
141
+ */
142
+ private initSchema(): void {
143
+ // Migrate legacy database first
144
+ this.migrateLegacyDatabase();
145
+
146
+ // Create core tables
147
+ this.db.exec(SCHEMA_SQL);
148
+
149
+ // Create vector table
150
+ this.db.exec(VECTOR_SCHEMA_SQL);
151
+
152
+ // Record schema version
153
+ const stmt = this.db.prepare(`
154
+ INSERT OR REPLACE INTO schema_version (version, applied_at)
155
+ VALUES (?, datetime('now'))
156
+ `);
157
+ stmt.run(SCHEMA_VERSION);
158
+ }
159
+
160
+ /**
161
+ * Execute callback within a transaction (sync)
162
+ */
163
+ transaction<T>(callback: () => T): T;
164
+
165
+ /**
166
+ * Execute callback within a transaction (async)
167
+ */
168
+ transaction<T>(callback: () => Promise<T>): Promise<T>;
169
+
170
+ /**
171
+ * Execute callback within a transaction (implementation)
172
+ */
173
+ transaction<T>(callback: () => T | Promise<T>): T | Promise<T> {
174
+ // Detect if callback is async by checking if it returns a Promise
175
+ const result = callback();
176
+
177
+ if (result instanceof Promise) {
178
+ // For async, use manual transaction control
179
+ return (async () => {
180
+ this.db.exec('BEGIN TRANSACTION');
181
+ try {
182
+ const value = await result;
183
+ this.db.exec('COMMIT');
184
+ return value;
185
+ } catch (error) {
186
+ this.db.exec('ROLLBACK');
187
+ throw error;
188
+ }
189
+ })();
190
+ } else {
191
+ // For sync, use better-sqlite3 transaction helper
192
+ const txn = this.db.transaction(callback as () => T);
193
+ return txn();
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Begin a manual transaction (returns rollback/commit functions)
199
+ */
200
+ beginTransaction(): { rollback: () => void; commit: () => void } {
201
+ this.db.exec('BEGIN TRANSACTION');
202
+ return {
203
+ rollback: () => this.db.exec('ROLLBACK'),
204
+ commit: () => this.db.exec('COMMIT')
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Check database connection health
210
+ */
211
+ healthCheck(): { healthy: boolean; error?: string } {
212
+ try {
213
+ this.db.prepare('SELECT 1').get();
214
+ return { healthy: true };
215
+ } catch (error) {
216
+ return {
217
+ healthy: false,
218
+ error: error instanceof Error ? error.message : String(error)
219
+ };
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Generate content hash for deduplication
225
+ */
226
+ private hashContent(content: string): string {
227
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
228
+ }
229
+
230
+ /**
231
+ * Generate a unique ID for a context item
232
+ */
233
+ private generateId(type: ContextType, name: string): string {
234
+ return `${type}:${name.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
235
+ }
236
+
237
+ // ==================== Context Items ====================
238
+
239
+ /**
240
+ * Insert or update a context item
241
+ */
242
+ upsertItem(item: Omit<ContextItem, 'id' | 'contentHash' | 'createdAt' | 'updatedAt'>): ContextItem {
243
+ const id = this.generateId(item.type, item.name);
244
+ const contentHash = this.hashContent(item.content);
245
+
246
+ const stmt = this.db.prepare(`
247
+ INSERT INTO context_items (id, type, name, content, metadata, file_path, content_hash, updated_at)
248
+ VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
249
+ ON CONFLICT(id) DO UPDATE SET
250
+ content = excluded.content,
251
+ metadata = excluded.metadata,
252
+ file_path = excluded.file_path,
253
+ content_hash = excluded.content_hash,
254
+ updated_at = datetime('now')
255
+ RETURNING *
256
+ `);
257
+
258
+ const row = stmt.get(
259
+ id,
260
+ item.type,
261
+ item.name,
262
+ item.content,
263
+ item.metadata ? JSON.stringify(item.metadata) : null,
264
+ item.filePath || null,
265
+ contentHash
266
+ ) as Record<string, unknown>;
267
+
268
+ return this.rowToItem(row);
269
+ }
270
+
271
+ /**
272
+ * Get a context item by ID
273
+ */
274
+ getItem(id: string): ContextItem | null {
275
+ const stmt = this.db.prepare('SELECT * FROM context_items WHERE id = ?');
276
+ const row = stmt.get(id) as Record<string, unknown> | undefined;
277
+ return row ? this.rowToItem(row) : null;
278
+ }
279
+
280
+ /**
281
+ * Get items by type
282
+ */
283
+ getItemsByType(type: ContextType): ContextItem[] {
284
+ const stmt = this.db.prepare('SELECT * FROM context_items WHERE type = ? ORDER BY name');
285
+ const rows = stmt.all(type) as Record<string, unknown>[];
286
+ return rows.map(row => this.rowToItem(row));
287
+ }
288
+
289
+ /**
290
+ * Get all items
291
+ */
292
+ getAllItems(): ContextItem[] {
293
+ const stmt = this.db.prepare('SELECT * FROM context_items ORDER BY type, name');
294
+ const rows = stmt.all() as Record<string, unknown>[];
295
+ return rows.map(row => this.rowToItem(row));
296
+ }
297
+
298
+ /**
299
+ * Delete a context item
300
+ */
301
+ deleteItem(id: string): boolean {
302
+ const stmt = this.db.prepare('DELETE FROM context_items WHERE id = ?');
303
+ const result = stmt.run(id);
304
+ return result.changes > 0;
305
+ }
306
+
307
+ /**
308
+ * Delete items older than specified days
309
+ */
310
+ deleteStaleItems(daysOld: number, type?: ContextType): number {
311
+ const stmt = this.db.prepare(`
312
+ DELETE FROM context_items
313
+ WHERE datetime(updated_at) < datetime('now', '-' || ? || ' days')
314
+ ${type ? 'AND type = ?' : ''}
315
+ `);
316
+ const result = stmt.run(...(type ? [daysOld, type] : [daysOld]));
317
+ return result.changes;
318
+ }
319
+
320
+ /**
321
+ * Search items by text (full-text grep-style)
322
+ */
323
+ searchText(query: string, type?: ContextType): ContextItem[] {
324
+ const pattern = `%${query}%`;
325
+ let sql = 'SELECT * FROM context_items WHERE (content LIKE ? OR name LIKE ?)';
326
+ const params: unknown[] = [pattern, pattern];
327
+
328
+ if (type) {
329
+ sql += ' AND type = ?';
330
+ params.push(type);
331
+ }
332
+
333
+ sql += ' ORDER BY name LIMIT 50';
334
+
335
+ const stmt = this.db.prepare(sql);
336
+ const rows = stmt.all(...params) as Record<string, unknown>[];
337
+ return rows.map(row => this.rowToItem(row));
338
+ }
339
+
340
+ /**
341
+ * Convert database row to ContextItem
342
+ */
343
+ private rowToItem(row: Record<string, unknown>): ContextItem {
344
+ return {
345
+ id: row.id as string,
346
+ type: row.type as ContextType,
347
+ name: row.name as string,
348
+ content: row.content as string,
349
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined,
350
+ filePath: row.file_path as string | undefined,
351
+ contentHash: row.content_hash as string | undefined,
352
+ createdAt: row.created_at as string | undefined,
353
+ updatedAt: row.updated_at as string | undefined
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Calculate relevance score for a search result
359
+ */
360
+ private calculateRelevance(
361
+ item: ContextItem,
362
+ query: string,
363
+ baseScore: number
364
+ ): number {
365
+ let score = baseScore;
366
+
367
+ // Boost score for exact name matches
368
+ if (item.name.toLowerCase().includes(query.toLowerCase())) {
369
+ score *= 1.5;
370
+ }
371
+
372
+ // Boost score for recently updated items
373
+ if (item.updatedAt) {
374
+ const daysSinceUpdate = (Date.now() - new Date(item.updatedAt).getTime()) / (1000 * 60 * 60 * 24);
375
+ if (daysSinceUpdate < 7) {
376
+ score *= 1.2; // 20% boost for items updated within a week
377
+ }
378
+ }
379
+
380
+ // Boost score for certain types
381
+ if (item.type === 'workflow' || item.type === 'agent') {
382
+ score *= 1.1;
383
+ }
384
+
385
+ return score;
386
+ }
387
+
388
+ // ==================== AI Tool Configs ====================
389
+
390
+ /**
391
+ * Upsert an AI tool configuration
392
+ */
393
+ upsertToolConfig(config: Omit<AIToolConfig, 'contentHash' | 'lastSync'>): AIToolConfig {
394
+ const contentHash = this.hashContent(config.content);
395
+
396
+ const stmt = this.db.prepare(`
397
+ INSERT INTO ai_tool_configs (id, tool_name, config_path, content, content_hash, last_sync, status, metadata)
398
+ VALUES (?, ?, ?, ?, ?, datetime('now'), ?, ?)
399
+ ON CONFLICT(id) DO UPDATE SET
400
+ content = excluded.content,
401
+ content_hash = excluded.content_hash,
402
+ last_sync = datetime('now'),
403
+ status = excluded.status,
404
+ metadata = excluded.metadata
405
+ RETURNING *
406
+ `);
407
+
408
+ const row = stmt.get(
409
+ config.id,
410
+ config.toolName,
411
+ config.configPath,
412
+ config.content,
413
+ contentHash,
414
+ config.status,
415
+ config.metadata ? JSON.stringify(config.metadata) : null
416
+ ) as Record<string, unknown>;
417
+
418
+ return {
419
+ id: row.id as string,
420
+ toolName: row.tool_name as AITool,
421
+ configPath: row.config_path as string,
422
+ content: row.content as string,
423
+ contentHash: row.content_hash as string,
424
+ lastSync: row.last_sync as string,
425
+ status: row.status as SyncStatus,
426
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined
427
+ };
428
+ }
429
+
430
+ /**
431
+ * Get tool configs by tool name
432
+ */
433
+ getToolConfigs(toolName: AITool): AIToolConfig[] {
434
+ const stmt = this.db.prepare('SELECT * FROM ai_tool_configs WHERE tool_name = ?');
435
+ const rows = stmt.all(toolName) as Record<string, unknown>[];
436
+
437
+ return rows.map(row => ({
438
+ id: row.id as string,
439
+ toolName: row.tool_name as AITool,
440
+ configPath: row.config_path as string,
441
+ content: row.content as string,
442
+ contentHash: row.content_hash as string,
443
+ lastSync: row.last_sync as string,
444
+ status: row.status as SyncStatus,
445
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined
446
+ }));
447
+ }
448
+
449
+ /**
450
+ * Get all tool configs
451
+ */
452
+ getAllToolConfigs(): AIToolConfig[] {
453
+ const stmt = this.db.prepare('SELECT * FROM ai_tool_configs ORDER BY tool_name');
454
+ const rows = stmt.all() as Record<string, unknown>[];
455
+
456
+ return rows.map(row => ({
457
+ id: row.id as string,
458
+ toolName: row.tool_name as AITool,
459
+ configPath: row.config_path as string,
460
+ content: row.content as string,
461
+ contentHash: row.content_hash as string,
462
+ lastSync: row.last_sync as string,
463
+ status: row.status as SyncStatus,
464
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined
465
+ }));
466
+ }
467
+
468
+ // ==================== Knowledge Graph ====================
469
+
470
+ /**
471
+ * Add a relationship to the knowledge graph
472
+ */
473
+ addRelation(edge: Omit<GraphEdge, 'id'>): GraphEdge {
474
+ const stmt = this.db.prepare(`
475
+ INSERT INTO knowledge_graph (source_id, target_id, relation_type, weight, metadata)
476
+ VALUES (?, ?, ?, ?, ?)
477
+ ON CONFLICT(source_id, target_id, relation_type) DO UPDATE SET
478
+ weight = excluded.weight,
479
+ metadata = excluded.metadata
480
+ RETURNING *
481
+ `);
482
+
483
+ const row = stmt.get(
484
+ edge.sourceId,
485
+ edge.targetId,
486
+ edge.relationType,
487
+ edge.weight ?? 1.0,
488
+ edge.metadata ? JSON.stringify(edge.metadata) : null
489
+ ) as Record<string, unknown>;
490
+
491
+ return {
492
+ id: row.id as number,
493
+ sourceId: row.source_id as string,
494
+ targetId: row.target_id as string,
495
+ relationType: row.relation_type as RelationType,
496
+ weight: row.weight as number,
497
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Get relations from a source item
503
+ */
504
+ getRelationsFrom(sourceId: string, relationType?: RelationType): GraphEdge[] {
505
+ let sql = `
506
+ SELECT kg.*, ci.name as target_name
507
+ FROM knowledge_graph kg
508
+ JOIN context_items ci ON kg.target_id = ci.id
509
+ WHERE kg.source_id = ?
510
+ `;
511
+ const params: unknown[] = [sourceId];
512
+
513
+ if (relationType) {
514
+ sql += ' AND kg.relation_type = ?';
515
+ params.push(relationType);
516
+ }
517
+
518
+ sql += ' ORDER BY kg.weight DESC';
519
+
520
+ const stmt = this.db.prepare(sql);
521
+ const rows = stmt.all(...params) as Record<string, unknown>[];
522
+
523
+ return rows.map(row => ({
524
+ id: row.id as number,
525
+ sourceId: row.source_id as string,
526
+ targetId: row.target_id as string,
527
+ relationType: row.relation_type as RelationType,
528
+ weight: row.weight as number,
529
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined
530
+ }));
531
+ }
532
+
533
+ /**
534
+ * Get relations to a target item
535
+ */
536
+ getRelationsTo(targetId: string, relationType?: RelationType): GraphEdge[] {
537
+ let sql = `
538
+ SELECT kg.*, ci.name as source_name
539
+ FROM knowledge_graph kg
540
+ JOIN context_items ci ON kg.source_id = ci.id
541
+ WHERE kg.target_id = ?
542
+ `;
543
+ const params: unknown[] = [targetId];
544
+
545
+ if (relationType) {
546
+ sql += ' AND kg.relation_type = ?';
547
+ params.push(relationType);
548
+ }
549
+
550
+ sql += ' ORDER BY kg.weight DESC';
551
+
552
+ const stmt = this.db.prepare(sql);
553
+ const rows = stmt.all(...params) as Record<string, unknown>[];
554
+
555
+ return rows.map(row => ({
556
+ id: row.id as number,
557
+ sourceId: row.source_id as string,
558
+ targetId: row.target_id as string,
559
+ relationType: row.relation_type as RelationType,
560
+ weight: row.weight as number,
561
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined
562
+ }));
563
+ }
564
+
565
+ /**
566
+ * Traverse the graph from a starting point
567
+ */
568
+ traverseGraph(startId: string, maxDepth = 3): Map<string, { item: ContextItem; depth: number }> {
569
+ const visited = new Map<string, { item: ContextItem; depth: number }>();
570
+ const queue: Array<{ id: string; depth: number }> = [{ id: startId, depth: 0 }];
571
+
572
+ while (queue.length > 0) {
573
+ const { id, depth } = queue.shift()!;
574
+
575
+ if (visited.has(id) || depth > maxDepth) continue;
576
+
577
+ const item = this.getItem(id);
578
+ if (!item) continue;
579
+
580
+ visited.set(id, { item, depth });
581
+
582
+ // Get all outgoing relations
583
+ const relations = this.getRelationsFrom(id);
584
+ for (const rel of relations) {
585
+ if (!visited.has(rel.targetId)) {
586
+ queue.push({ id: rel.targetId, depth: depth + 1 });
587
+ }
588
+ }
589
+ }
590
+
591
+ return visited;
592
+ }
593
+
594
+ // ==================== Git Commits ====================
595
+
596
+ /**
597
+ * Insert or update a git commit
598
+ */
599
+ upsertCommit(commit: GitCommit): void {
600
+ const stmt = this.db.prepare(`
601
+ INSERT INTO git_commits (sha, message, author_name, author_email, timestamp, files_changed, stats)
602
+ VALUES (?, ?, ?, ?, ?, ?, ?)
603
+ ON CONFLICT(sha) DO UPDATE SET
604
+ message = excluded.message,
605
+ files_changed = excluded.files_changed,
606
+ stats = excluded.stats
607
+ `);
608
+
609
+ stmt.run(
610
+ commit.sha,
611
+ commit.message,
612
+ commit.authorName || null,
613
+ commit.authorEmail || null,
614
+ commit.timestamp,
615
+ commit.filesChanged ? JSON.stringify(commit.filesChanged) : null,
616
+ commit.stats ? JSON.stringify(commit.stats) : null
617
+ );
618
+ }
619
+
620
+ /**
621
+ * Get recent commits
622
+ */
623
+ getRecentCommits(limit = 50): GitCommit[] {
624
+ const stmt = this.db.prepare(`
625
+ SELECT * FROM git_commits
626
+ ORDER BY timestamp DESC
627
+ LIMIT ?
628
+ `);
629
+
630
+ const rows = stmt.all(limit) as Record<string, unknown>[];
631
+
632
+ return rows.map(row => ({
633
+ sha: row.sha as string,
634
+ message: row.message as string,
635
+ authorName: row.author_name as string | undefined,
636
+ authorEmail: row.author_email as string | undefined,
637
+ timestamp: row.timestamp as string,
638
+ filesChanged: row.files_changed ? JSON.parse(row.files_changed as string) : undefined,
639
+ stats: row.stats ? JSON.parse(row.stats as string) : undefined
640
+ }));
641
+ }
642
+
643
+ // ==================== Sync State ====================
644
+
645
+ /**
646
+ * Update sync state for a tool
647
+ */
648
+ updateSyncState(state: SyncState): void {
649
+ const stmt = this.db.prepare(`
650
+ INSERT INTO sync_state (id, tool, content_hash, last_sync, file_path, status, metadata)
651
+ VALUES (?, ?, ?, datetime('now'), ?, ?, ?)
652
+ ON CONFLICT(id) DO UPDATE SET
653
+ content_hash = excluded.content_hash,
654
+ last_sync = datetime('now'),
655
+ status = excluded.status,
656
+ metadata = excluded.metadata
657
+ `);
658
+
659
+ stmt.run(
660
+ state.id,
661
+ state.tool,
662
+ state.contentHash || null,
663
+ state.filePath || null,
664
+ state.status,
665
+ state.metadata ? JSON.stringify(state.metadata) : null
666
+ );
667
+ }
668
+
669
+ /**
670
+ * Get sync state for a tool
671
+ */
672
+ getSyncState(tool: string): SyncState[] {
673
+ const stmt = this.db.prepare('SELECT * FROM sync_state WHERE tool = ?');
674
+ const rows = stmt.all(tool) as Record<string, unknown>[];
675
+
676
+ return rows.map(row => ({
677
+ id: row.id as string,
678
+ tool: row.tool as string,
679
+ contentHash: row.content_hash as string | undefined,
680
+ lastSync: row.last_sync as string,
681
+ filePath: row.file_path as string | undefined,
682
+ status: row.status as SyncStatus,
683
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined
684
+ }));
685
+ }
686
+
687
+ // ==================== Embeddings ====================
688
+
689
+ /**
690
+ * Store an embedding
691
+ */
692
+ storeEmbedding(contextId: string, embedding: number[]): void {
693
+ const stmt = this.db.prepare(`
694
+ INSERT INTO embeddings (context_id, embedding)
695
+ VALUES (?, ?)
696
+ ON CONFLICT(context_id) DO UPDATE SET
697
+ embedding = excluded.embedding
698
+ `);
699
+
700
+ // Convert to blob for sqlite-vec
701
+ const buffer = new Float32Array(embedding);
702
+ stmt.run(contextId, Buffer.from(buffer.buffer));
703
+ }
704
+
705
+ /**
706
+ * Search by embedding similarity
707
+ */
708
+ searchByEmbedding(queryEmbedding: number[], limit = 10): SearchResult[] {
709
+ const buffer = new Float32Array(queryEmbedding);
710
+
711
+ const stmt = this.db.prepare(`
712
+ SELECT
713
+ e.context_id,
714
+ e.embedding,
715
+ ci.*,
716
+ vec_distance_cosine(e.embedding, ?) as distance
717
+ FROM embeddings e
718
+ JOIN context_items ci ON e.context_id = ci.id
719
+ ORDER BY distance
720
+ LIMIT ?
721
+ `);
722
+
723
+ const rows = stmt.all(Buffer.from(buffer.buffer), limit) as Record<string, unknown>[];
724
+
725
+ return rows.map(row => ({
726
+ item: this.rowToItem(row),
727
+ similarity: 1 - (row.distance as number || 0) // Convert distance to similarity
728
+ }));
729
+ }
730
+
731
+ /**
732
+ * Hybrid search combining vector and text search
733
+ */
734
+ hybridSearch(
735
+ query: string,
736
+ queryEmbedding: number[] | null,
737
+ options: {
738
+ limit?: number;
739
+ type?: ContextType;
740
+ vectorWeight?: number; // 0-1, higher = more weight on semantic
741
+ } = {}
742
+ ): SearchResult[] {
743
+ const {
744
+ limit = 10,
745
+ type,
746
+ vectorWeight = 0.7
747
+ } = options;
748
+
749
+ const textResults = this.searchText(query, type);
750
+ const semanticResults = queryEmbedding ? this.searchByEmbedding(queryEmbedding, limit * 2) : [];
751
+
752
+ // Combine and score
753
+ const combinedScores = new Map<string, number>();
754
+
755
+ // Score text results (inverse of position)
756
+ for (let i = 0; i < textResults.length; i++) {
757
+ const score = (1 - i / textResults.length) * (1 - vectorWeight);
758
+ combinedScores.set(textResults[i].id, (combinedScores.get(textResults[i].id) || 0) + score);
759
+ }
760
+
761
+ // Score semantic results
762
+ for (const result of semanticResults) {
763
+ const score = result.similarity * vectorWeight;
764
+ combinedScores.set(result.item.id, (combinedScores.get(result.item.id) || 0) + score);
765
+ }
766
+
767
+ // Sort by combined score
768
+ const results = Array.from(combinedScores.entries())
769
+ .sort((a, b) => b[1] - a[1])
770
+ .slice(0, limit)
771
+ .map(([id]) => this.getItem(id)!)
772
+ .filter(item => item !== null);
773
+
774
+ return results.map(item => ({
775
+ item,
776
+ similarity: combinedScores.get(item.id) || 0
777
+ }));
778
+ }
779
+
780
+ /**
781
+ * Delete an embedding
782
+ */
783
+ deleteEmbedding(contextId: string): boolean {
784
+ const stmt = this.db.prepare('DELETE FROM embeddings WHERE context_id = ?');
785
+ const result = stmt.run(contextId);
786
+ return result.changes > 0;
787
+ }
788
+
789
+ /**
790
+ * Insert or update an embedding for a file by path
791
+ */
792
+ insertEmbedding(filePath: string, embedding: number[]): void {
793
+ const itemId = this.getItemIdByPath(filePath);
794
+
795
+ if (!itemId) {
796
+ throw new Error(`Cannot insert embedding: no item found for path ${filePath}`);
797
+ }
798
+
799
+ this.storeEmbedding(itemId, embedding);
800
+ }
801
+
802
+ /**
803
+ * Get item ID by file path
804
+ */
805
+ private getItemIdByPath(filePath: string): string | null {
806
+ const stmt = this.db.prepare('SELECT id FROM context_items WHERE file_path = ? LIMIT 1');
807
+ const row = stmt.get(filePath) as { id: string } | undefined;
808
+ return row?.id || null;
809
+ }
810
+
811
+ // ==================== Analytics ====================
812
+
813
+ /**
814
+ * Log a usage event
815
+ */
816
+ logUsage(toolName: string, query?: string, resultCount?: number, latencyMs?: number): void {
817
+ const stmt = this.db.prepare(`
818
+ INSERT INTO usage_analytics (tool_name, query, result_count, latency_ms)
819
+ VALUES (?, ?, ?, ?)
820
+ `);
821
+
822
+ stmt.run(toolName, query || null, resultCount ?? null, latencyMs ?? null);
823
+ }
824
+
825
+ /**
826
+ * Get usage statistics
827
+ */
828
+ getUsageStats(days = 30): { toolName: string; count: number; avgLatency: number }[] {
829
+ const stmt = this.db.prepare(`
830
+ SELECT
831
+ tool_name,
832
+ COUNT(*) as count,
833
+ AVG(latency_ms) as avg_latency
834
+ FROM usage_analytics
835
+ WHERE timestamp > datetime('now', '-' || ? || ' days')
836
+ GROUP BY tool_name
837
+ ORDER BY count DESC
838
+ `);
839
+
840
+ const rows = stmt.all(days) as Record<string, unknown>[];
841
+
842
+ return rows.map(row => ({
843
+ toolName: row.tool_name as string,
844
+ count: row.count as number,
845
+ avgLatency: row.avg_latency as number
846
+ }));
847
+ }
848
+
849
+ // ==================== Utility ====================
850
+
851
+ /**
852
+ * Get database path
853
+ */
854
+ getPath(): string {
855
+ return this.dbPath;
856
+ }
857
+
858
+ /**
859
+ * Get database statistics
860
+ */
861
+ getStats(): {
862
+ items: number;
863
+ relations: number;
864
+ commits: number;
865
+ embeddings: number;
866
+ toolConfigs: number;
867
+ } {
868
+ const itemCount = (this.db.prepare('SELECT COUNT(*) as count FROM context_items').get() as { count: number }).count;
869
+ const relationCount = (this.db.prepare('SELECT COUNT(*) as count FROM knowledge_graph').get() as { count: number }).count;
870
+ const commitCount = (this.db.prepare('SELECT COUNT(*) as count FROM git_commits').get() as { count: number }).count;
871
+ const toolConfigCount = (this.db.prepare('SELECT COUNT(*) as count FROM ai_tool_configs').get() as { count: number }).count;
872
+
873
+ let embeddingCount = 0;
874
+ try {
875
+ embeddingCount = (this.db.prepare('SELECT COUNT(*) as count FROM embeddings').get() as { count: number }).count;
876
+ } catch {
877
+ // Vector table might not exist yet
878
+ }
879
+
880
+ return {
881
+ items: itemCount,
882
+ relations: relationCount,
883
+ commits: commitCount,
884
+ embeddings: embeddingCount,
885
+ toolConfigs: toolConfigCount
886
+ };
887
+ }
888
+
889
+ /**
890
+ * Get raw database instance (for advanced operations)
891
+ */
892
+ getRawDb(): Database.Database {
893
+ return this.db;
894
+ }
895
+
896
+ /**
897
+ * Vacuum database to reclaim space
898
+ */
899
+ vacuum(): void {
900
+ this.db.exec('VACUUM');
901
+ }
902
+
903
+ /**
904
+ * Reindex database for optimization
905
+ */
906
+ reindex(): void {
907
+ this.db.exec('REINDEX');
908
+ }
909
+
910
+ /**
911
+ * Backup database to specified path
912
+ */
913
+ backup(backupPath: string): void {
914
+ try {
915
+ // Ensure backup directory exists
916
+ const backupDir = path.dirname(backupPath);
917
+ if (!fs.existsSync(backupDir)) {
918
+ fs.mkdirSync(backupDir, { recursive: true });
919
+ }
920
+
921
+ // Close the database before copying to ensure consistency
922
+ this.db.close();
923
+ fs.copyFileSync(this.dbPath, backupPath);
924
+ // Reopen the database
925
+ this.db = new Database(this.dbPath);
926
+ this.db.pragma('foreign_keys = ON');
927
+ sqliteVec.load(this.db);
928
+
929
+ console.log(`Database backed up to: ${backupPath}`);
930
+ } catch (error) {
931
+ // Try to reopen database if copy failed
932
+ try {
933
+ this.db = new Database(this.dbPath);
934
+ this.db.pragma('foreign_keys = ON');
935
+ sqliteVec.load(this.db);
936
+ } catch {
937
+ // Ignore reopen errors
938
+ }
939
+ throw new Error(`Failed to backup database: ${error instanceof Error ? error.message : error}`);
940
+ }
941
+ }
942
+
943
+ /**
944
+ * Close database connection
945
+ */
946
+ close(): void {
947
+ this.db.close();
948
+ }
949
+ }