@intrect/openswarm 0.2.1 → 0.3.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 (155) hide show
  1. package/README.md +236 -331
  2. package/config.example.yaml +36 -13
  3. package/dist/adapters/base.d.ts.map +1 -1
  4. package/dist/adapters/base.js +4 -0
  5. package/dist/adapters/base.js.map +1 -1
  6. package/dist/adapters/cryptoQuantAdapter.js +1 -1
  7. package/dist/adapters/cryptoQuantAdapter.js.map +1 -1
  8. package/dist/adapters/gpt.d.ts +15 -0
  9. package/dist/adapters/gpt.d.ts.map +1 -0
  10. package/dist/adapters/gpt.js +274 -0
  11. package/dist/adapters/gpt.js.map +1 -0
  12. package/dist/adapters/index.d.ts +2 -0
  13. package/dist/adapters/index.d.ts.map +1 -1
  14. package/dist/adapters/index.js +6 -0
  15. package/dist/adapters/index.js.map +1 -1
  16. package/dist/adapters/local.d.ts +22 -0
  17. package/dist/adapters/local.d.ts.map +1 -0
  18. package/dist/adapters/local.js +277 -0
  19. package/dist/adapters/local.js.map +1 -0
  20. package/dist/adapters/types.d.ts +6 -1
  21. package/dist/adapters/types.d.ts.map +1 -1
  22. package/dist/agents/pairPipeline.d.ts.map +1 -1
  23. package/dist/agents/pairPipeline.js +21 -6
  24. package/dist/agents/pairPipeline.js.map +1 -1
  25. package/dist/agents/pipelineGuards.d.ts.map +1 -1
  26. package/dist/agents/pipelineGuards.js +84 -2
  27. package/dist/agents/pipelineGuards.js.map +1 -1
  28. package/dist/auth/index.d.ts +3 -0
  29. package/dist/auth/index.d.ts.map +1 -0
  30. package/dist/auth/index.js +6 -0
  31. package/dist/auth/index.js.map +1 -0
  32. package/dist/auth/oauthPkce.d.ts +21 -0
  33. package/dist/auth/oauthPkce.d.ts.map +1 -0
  34. package/dist/auth/oauthPkce.js +212 -0
  35. package/dist/auth/oauthPkce.js.map +1 -0
  36. package/dist/auth/oauthStore.d.ts +24 -0
  37. package/dist/auth/oauthStore.d.ts.map +1 -0
  38. package/dist/auth/oauthStore.js +96 -0
  39. package/dist/auth/oauthStore.js.map +1 -0
  40. package/dist/automation/autonomousRunner.d.ts +5 -5
  41. package/dist/automation/runnerTypes.d.ts +1 -1
  42. package/dist/automation/runnerTypes.d.ts.map +1 -1
  43. package/dist/cli/authHandler.d.ts +16 -0
  44. package/dist/cli/authHandler.d.ts.map +1 -0
  45. package/dist/cli/authHandler.js +93 -0
  46. package/dist/cli/authHandler.js.map +1 -0
  47. package/dist/cli/checkHandler.d.ts +25 -0
  48. package/dist/cli/checkHandler.d.ts.map +1 -0
  49. package/dist/cli/checkHandler.js +465 -0
  50. package/dist/cli/checkHandler.js.map +1 -0
  51. package/dist/cli.js +69 -0
  52. package/dist/cli.js.map +1 -1
  53. package/dist/core/config.d.ts +17 -4
  54. package/dist/core/config.d.ts.map +1 -1
  55. package/dist/core/config.js +21 -8
  56. package/dist/core/config.js.map +1 -1
  57. package/dist/core/service.d.ts.map +1 -1
  58. package/dist/core/service.js +18 -8
  59. package/dist/core/service.js.map +1 -1
  60. package/dist/core/types.d.ts +4 -2
  61. package/dist/core/types.d.ts.map +1 -1
  62. package/dist/issues/graphql/resolvers.d.ts +252 -0
  63. package/dist/issues/graphql/resolvers.d.ts.map +1 -0
  64. package/dist/issues/graphql/resolvers.js +88 -0
  65. package/dist/issues/graphql/resolvers.js.map +1 -0
  66. package/dist/issues/graphql/server.d.ts +13 -0
  67. package/dist/issues/graphql/server.d.ts.map +1 -0
  68. package/dist/issues/graphql/server.js +56 -0
  69. package/dist/issues/graphql/server.js.map +1 -0
  70. package/dist/issues/graphql/typeDefs.d.ts +2 -0
  71. package/dist/issues/graphql/typeDefs.d.ts.map +1 -0
  72. package/dist/issues/graphql/typeDefs.js +251 -0
  73. package/dist/issues/graphql/typeDefs.js.map +1 -0
  74. package/dist/issues/index.d.ts +8 -0
  75. package/dist/issues/index.d.ts.map +1 -0
  76. package/dist/issues/index.js +11 -0
  77. package/dist/issues/index.js.map +1 -0
  78. package/dist/issues/issueBoardHtml.d.ts +2 -0
  79. package/dist/issues/issueBoardHtml.d.ts.map +1 -0
  80. package/dist/issues/issueBoardHtml.js +677 -0
  81. package/dist/issues/issueBoardHtml.js.map +1 -0
  82. package/dist/issues/linearBridge.d.ts +27 -0
  83. package/dist/issues/linearBridge.d.ts.map +1 -0
  84. package/dist/issues/linearBridge.js +211 -0
  85. package/dist/issues/linearBridge.js.map +1 -0
  86. package/dist/issues/memoryBridge.d.ts +35 -0
  87. package/dist/issues/memoryBridge.d.ts.map +1 -0
  88. package/dist/issues/memoryBridge.js +184 -0
  89. package/dist/issues/memoryBridge.js.map +1 -0
  90. package/dist/issues/schema.d.ts +162 -0
  91. package/dist/issues/schema.d.ts.map +1 -0
  92. package/dist/issues/schema.js +121 -0
  93. package/dist/issues/schema.js.map +1 -0
  94. package/dist/issues/sqliteStore.d.ts +90 -0
  95. package/dist/issues/sqliteStore.d.ts.map +1 -0
  96. package/dist/issues/sqliteStore.js +488 -0
  97. package/dist/issues/sqliteStore.js.map +1 -0
  98. package/dist/knowledge/index.d.ts.map +1 -1
  99. package/dist/knowledge/index.js +9 -3
  100. package/dist/knowledge/index.js.map +1 -1
  101. package/dist/linear/linear.d.ts +4 -0
  102. package/dist/linear/linear.d.ts.map +1 -1
  103. package/dist/linear/linear.js +27 -0
  104. package/dist/linear/linear.js.map +1 -1
  105. package/dist/registry/bsDetector.d.ts +24 -0
  106. package/dist/registry/bsDetector.d.ts.map +1 -0
  107. package/dist/registry/bsDetector.js +276 -0
  108. package/dist/registry/bsDetector.js.map +1 -0
  109. package/dist/registry/entityScanner.d.ts +36 -0
  110. package/dist/registry/entityScanner.d.ts.map +1 -0
  111. package/dist/registry/entityScanner.js +693 -0
  112. package/dist/registry/entityScanner.js.map +1 -0
  113. package/dist/registry/graphql/resolvers.d.ts +778 -0
  114. package/dist/registry/graphql/resolvers.d.ts.map +1 -0
  115. package/dist/registry/graphql/resolvers.js +127 -0
  116. package/dist/registry/graphql/resolvers.js.map +1 -0
  117. package/dist/registry/graphql/typeDefs.d.ts +2 -0
  118. package/dist/registry/graphql/typeDefs.d.ts.map +1 -0
  119. package/dist/registry/graphql/typeDefs.js +276 -0
  120. package/dist/registry/graphql/typeDefs.js.map +1 -0
  121. package/dist/registry/index.d.ts +12 -0
  122. package/dist/registry/index.d.ts.map +1 -0
  123. package/dist/registry/index.js +18 -0
  124. package/dist/registry/index.js.map +1 -0
  125. package/dist/registry/issueBridge.d.ts +8 -0
  126. package/dist/registry/issueBridge.d.ts.map +1 -0
  127. package/dist/registry/issueBridge.js +30 -0
  128. package/dist/registry/issueBridge.js.map +1 -0
  129. package/dist/registry/memoryBridge.d.ts +13 -0
  130. package/dist/registry/memoryBridge.d.ts.map +1 -0
  131. package/dist/registry/memoryBridge.js +60 -0
  132. package/dist/registry/memoryBridge.js.map +1 -0
  133. package/dist/registry/schema.d.ts +307 -0
  134. package/dist/registry/schema.d.ts.map +1 -0
  135. package/dist/registry/schema.js +139 -0
  136. package/dist/registry/schema.js.map +1 -0
  137. package/dist/registry/sqliteStore.d.ts +101 -0
  138. package/dist/registry/sqliteStore.d.ts.map +1 -0
  139. package/dist/registry/sqliteStore.js +688 -0
  140. package/dist/registry/sqliteStore.js.map +1 -0
  141. package/dist/support/chatBackend.d.ts.map +1 -1
  142. package/dist/support/chatBackend.js +35 -4
  143. package/dist/support/chatBackend.js.map +1 -1
  144. package/dist/support/chatTui.d.ts.map +1 -1
  145. package/dist/support/chatTui.js +109 -3
  146. package/dist/support/chatTui.js.map +1 -1
  147. package/dist/support/dashboardHtml.d.ts +1 -1
  148. package/dist/support/dashboardHtml.d.ts.map +1 -1
  149. package/dist/support/dashboardHtml.js +1 -0
  150. package/dist/support/dashboardHtml.js.map +1 -1
  151. package/dist/support/web.d.ts.map +1 -1
  152. package/dist/support/web.js +16 -3
  153. package/dist/support/web.js.map +1 -1
  154. package/package.json +9 -3
  155. package/templates/TOOLS.md +2 -2
@@ -0,0 +1,688 @@
1
+ // ============================================
2
+ // OpenSwarm - Code Registry SQLite Store
3
+ // Created: 2026-04-10
4
+ // Purpose: better-sqlite3 기반 코드 엔티티 레지스트리
5
+ // Dependencies: better-sqlite3, nanoid
6
+ // ============================================
7
+ import Database from 'better-sqlite3';
8
+ import { nanoid } from 'nanoid';
9
+ import { resolve } from 'node:path';
10
+ import { homedir } from 'node:os';
11
+ import { mkdirSync } from 'node:fs';
12
+ const DEFAULT_DB_PATH = resolve(homedir(), '.openswarm', 'registry.db');
13
+ // ============ Store 구현 ============
14
+ export class SqliteRegistryStore {
15
+ db;
16
+ constructor(dbPath) {
17
+ const path = dbPath ?? DEFAULT_DB_PATH;
18
+ mkdirSync(resolve(path, '..'), { recursive: true });
19
+ this.db = new Database(path);
20
+ this.db.pragma('journal_mode = WAL');
21
+ this.db.pragma('foreign_keys = ON');
22
+ this.migrate();
23
+ }
24
+ migrate() {
25
+ this.db.exec(`
26
+ CREATE TABLE IF NOT EXISTS code_entities (
27
+ id TEXT PRIMARY KEY,
28
+ project_id TEXT NOT NULL,
29
+ kind TEXT NOT NULL,
30
+ name TEXT NOT NULL,
31
+ qualified_name TEXT NOT NULL UNIQUE,
32
+ file_path TEXT NOT NULL,
33
+ line_start INTEGER,
34
+ line_end INTEGER,
35
+ signature TEXT,
36
+ status TEXT DEFAULT 'active',
37
+ deprecated_at TEXT,
38
+ deprecated_reason TEXT,
39
+ has_tests INTEGER DEFAULT 0,
40
+ test_file TEXT,
41
+ author TEXT,
42
+ maintainer TEXT,
43
+ complexity_score INTEGER,
44
+ risk_level TEXT DEFAULT 'low',
45
+ description TEXT DEFAULT '',
46
+ notes TEXT DEFAULT '',
47
+ knowledge_node_id TEXT,
48
+ created_at TEXT NOT NULL,
49
+ updated_at TEXT NOT NULL
50
+ );
51
+
52
+ CREATE TABLE IF NOT EXISTS code_entity_tags (
53
+ entity_id TEXT NOT NULL,
54
+ tag TEXT NOT NULL,
55
+ value TEXT,
56
+ PRIMARY KEY (entity_id, tag),
57
+ FOREIGN KEY (entity_id) REFERENCES code_entities(id) ON DELETE CASCADE
58
+ );
59
+
60
+ CREATE TABLE IF NOT EXISTS code_entity_warnings (
61
+ id TEXT PRIMARY KEY,
62
+ entity_id TEXT NOT NULL,
63
+ severity TEXT NOT NULL,
64
+ category TEXT NOT NULL,
65
+ message TEXT NOT NULL,
66
+ resolved INTEGER DEFAULT 0,
67
+ resolved_at TEXT,
68
+ created_at TEXT NOT NULL,
69
+ FOREIGN KEY (entity_id) REFERENCES code_entities(id) ON DELETE CASCADE
70
+ );
71
+
72
+ CREATE TABLE IF NOT EXISTS code_entity_relations (
73
+ source_id TEXT NOT NULL,
74
+ target_id TEXT NOT NULL,
75
+ relation_type TEXT NOT NULL,
76
+ PRIMARY KEY (source_id, target_id, relation_type),
77
+ FOREIGN KEY (source_id) REFERENCES code_entities(id) ON DELETE CASCADE,
78
+ FOREIGN KEY (target_id) REFERENCES code_entities(id) ON DELETE CASCADE
79
+ );
80
+
81
+ CREATE TABLE IF NOT EXISTS code_entity_issue_links (
82
+ entity_id TEXT NOT NULL,
83
+ issue_id TEXT NOT NULL,
84
+ linked_at TEXT NOT NULL,
85
+ PRIMARY KEY (entity_id, issue_id),
86
+ FOREIGN KEY (entity_id) REFERENCES code_entities(id) ON DELETE CASCADE
87
+ );
88
+
89
+ CREATE TABLE IF NOT EXISTS code_entity_memory_links (
90
+ entity_id TEXT NOT NULL,
91
+ memory_id TEXT NOT NULL,
92
+ linked_at TEXT NOT NULL,
93
+ PRIMARY KEY (entity_id, memory_id),
94
+ FOREIGN KEY (entity_id) REFERENCES code_entities(id) ON DELETE CASCADE
95
+ );
96
+
97
+ CREATE TABLE IF NOT EXISTS code_entity_events (
98
+ id TEXT PRIMARY KEY,
99
+ entity_id TEXT NOT NULL,
100
+ type TEXT NOT NULL,
101
+ old_value TEXT,
102
+ new_value TEXT,
103
+ content TEXT,
104
+ actor TEXT DEFAULT 'system',
105
+ created_at TEXT NOT NULL,
106
+ FOREIGN KEY (entity_id) REFERENCES code_entities(id) ON DELETE CASCADE
107
+ );
108
+
109
+ -- FTS5 전문검색
110
+ CREATE VIRTUAL TABLE IF NOT EXISTS code_entities_fts USING fts5(
111
+ name, qualified_name, description, notes, signature,
112
+ content=code_entities, content_rowid=rowid
113
+ );
114
+
115
+ -- 인덱스
116
+ CREATE INDEX IF NOT EXISTS idx_ce_project ON code_entities(project_id);
117
+ CREATE INDEX IF NOT EXISTS idx_ce_kind ON code_entities(kind);
118
+ CREATE INDEX IF NOT EXISTS idx_ce_file ON code_entities(file_path);
119
+ CREATE INDEX IF NOT EXISTS idx_ce_status ON code_entities(status);
120
+ CREATE INDEX IF NOT EXISTS idx_ce_has_tests ON code_entities(has_tests);
121
+ CREATE INDEX IF NOT EXISTS idx_ce_risk ON code_entities(risk_level);
122
+ CREATE INDEX IF NOT EXISTS idx_ce_knowledge ON code_entities(knowledge_node_id);
123
+ CREATE INDEX IF NOT EXISTS idx_ce_events_entity ON code_entity_events(entity_id);
124
+ CREATE INDEX IF NOT EXISTS idx_ce_events_created ON code_entity_events(created_at);
125
+ CREATE INDEX IF NOT EXISTS idx_ce_tags_tag ON code_entity_tags(tag);
126
+ CREATE INDEX IF NOT EXISTS idx_ce_warnings_sev ON code_entity_warnings(severity);
127
+ CREATE INDEX IF NOT EXISTS idx_ce_warnings_entity ON code_entity_warnings(entity_id);
128
+
129
+ -- FTS 트리거
130
+ CREATE TRIGGER IF NOT EXISTS ce_fts_ai AFTER INSERT ON code_entities BEGIN
131
+ INSERT INTO code_entities_fts(rowid, name, qualified_name, description, notes, signature)
132
+ VALUES (new.rowid, new.name, new.qualified_name, new.description, new.notes, new.signature);
133
+ END;
134
+ CREATE TRIGGER IF NOT EXISTS ce_fts_ad AFTER DELETE ON code_entities BEGIN
135
+ INSERT INTO code_entities_fts(code_entities_fts, rowid, name, qualified_name, description, notes, signature)
136
+ VALUES ('delete', old.rowid, old.name, old.qualified_name, old.description, old.notes, old.signature);
137
+ END;
138
+ CREATE TRIGGER IF NOT EXISTS ce_fts_au AFTER UPDATE ON code_entities BEGIN
139
+ INSERT INTO code_entities_fts(code_entities_fts, rowid, name, qualified_name, description, notes, signature)
140
+ VALUES ('delete', old.rowid, old.name, old.qualified_name, old.description, old.notes, old.signature);
141
+ INSERT INTO code_entities_fts(rowid, name, qualified_name, description, notes, signature)
142
+ VALUES (new.rowid, new.name, new.qualified_name, new.description, new.notes, new.signature);
143
+ END;
144
+ `);
145
+ }
146
+ // ============ 엔티티 CRUD ============
147
+ registerEntity(input) {
148
+ const id = nanoid(12);
149
+ const now = new Date().toISOString();
150
+ const qualifiedName = `${input.filePath}::${input.name}`;
151
+ const insertEntity = this.db.prepare(`
152
+ INSERT INTO code_entities (
153
+ id, project_id, kind, name, qualified_name, file_path,
154
+ line_start, line_end, signature, status,
155
+ has_tests, test_file, author, maintainer,
156
+ complexity_score, risk_level, description, notes,
157
+ knowledge_node_id, created_at, updated_at
158
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
159
+ `);
160
+ const insertTag = this.db.prepare('INSERT OR IGNORE INTO code_entity_tags (entity_id, tag, value) VALUES (?, ?, ?)');
161
+ const insertEvent = this.db.prepare(`
162
+ INSERT INTO code_entity_events (id, entity_id, type, new_value, actor, created_at)
163
+ VALUES (?, ?, 'created', ?, 'system', ?)
164
+ `);
165
+ const transaction = this.db.transaction(() => {
166
+ insertEntity.run(id, input.projectId, input.kind, input.name, qualifiedName, input.filePath, input.lineStart ?? null, input.lineEnd ?? null, input.signature ?? null, input.status ?? 'active', input.hasTests ? 1 : 0, input.testFile ?? null, input.author ?? null, input.maintainer ?? null, input.complexityScore ?? null, input.riskLevel ?? 'low', input.description ?? '', input.notes ?? '', input.knowledgeNodeId ?? null, now, now);
167
+ for (const t of input.tags ?? []) {
168
+ insertTag.run(id, t.tag, t.value ?? null);
169
+ }
170
+ insertEvent.run(nanoid(12), id, input.name, now);
171
+ });
172
+ transaction();
173
+ const entity = this.getEntity(id);
174
+ if (!entity)
175
+ throw new Error(`Failed to register entity: ${qualifiedName} — row not found after insert`);
176
+ return entity;
177
+ }
178
+ bulkRegisterEntities(inputs) {
179
+ const results = [];
180
+ const transaction = this.db.transaction(() => {
181
+ for (const input of inputs) {
182
+ results.push(this.registerEntity(input));
183
+ }
184
+ });
185
+ transaction();
186
+ return results;
187
+ }
188
+ getEntity(id) {
189
+ const row = this.db.prepare('SELECT * FROM code_entities WHERE id = ?').get(id);
190
+ if (!row)
191
+ return null;
192
+ return this.rowToEntity(row);
193
+ }
194
+ getEntityByName(qualifiedName) {
195
+ const row = this.db.prepare('SELECT * FROM code_entities WHERE qualified_name = ?').get(qualifiedName);
196
+ if (!row)
197
+ return null;
198
+ return this.rowToEntity(row);
199
+ }
200
+ updateEntity(id, patch, actor = 'system') {
201
+ const existing = this.getEntity(id);
202
+ if (!existing)
203
+ return null;
204
+ const now = new Date().toISOString();
205
+ const fields = [];
206
+ const values = [];
207
+ const fieldMap = {
208
+ name: 'name', lineStart: 'line_start', lineEnd: 'line_end',
209
+ signature: 'signature', hasTests: 'has_tests', testFile: 'test_file',
210
+ maintainer: 'maintainer', complexityScore: 'complexity_score',
211
+ riskLevel: 'risk_level', description: 'description', notes: 'notes',
212
+ };
213
+ for (const [key, col] of Object.entries(fieldMap)) {
214
+ if (key in patch && patch[key] !== undefined) {
215
+ const val = patch[key];
216
+ fields.push(`${col} = ?`);
217
+ values.push(key === 'hasTests' ? (val ? 1 : 0) : (val ?? null));
218
+ }
219
+ }
220
+ if (fields.length === 0)
221
+ return existing;
222
+ // qualified_name 갱신 (name 변경 시)
223
+ if (patch.name && patch.name !== existing.name) {
224
+ fields.push('qualified_name = ?');
225
+ values.push(`${existing.filePath}::${patch.name}`);
226
+ }
227
+ fields.push('updated_at = ?');
228
+ values.push(now);
229
+ values.push(id);
230
+ this.db.prepare(`UPDATE code_entities SET ${fields.join(', ')} WHERE id = ?`).run(...values);
231
+ this.addEvent(id, 'updated', {
232
+ content: `fields: ${Object.keys(patch).join(', ')}`,
233
+ actor,
234
+ });
235
+ return this.getEntity(id);
236
+ }
237
+ removeEntity(id) {
238
+ const result = this.db.prepare('DELETE FROM code_entities WHERE id = ?').run(id);
239
+ return result.changes > 0;
240
+ }
241
+ listEntities(filter) {
242
+ const conditions = [];
243
+ const params = [];
244
+ if (filter?.projectId) {
245
+ conditions.push('e.project_id = ?');
246
+ params.push(filter.projectId);
247
+ }
248
+ if (filter?.kind && filter.kind.length > 0) {
249
+ conditions.push(`e.kind IN (${filter.kind.map(() => '?').join(',')})`);
250
+ params.push(...filter.kind);
251
+ }
252
+ if (filter?.status && filter.status.length > 0) {
253
+ conditions.push(`e.status IN (${filter.status.map(() => '?').join(',')})`);
254
+ params.push(...filter.status);
255
+ }
256
+ if (filter?.filePath) {
257
+ conditions.push('e.file_path = ?');
258
+ params.push(filter.filePath);
259
+ }
260
+ if (filter?.hasTests !== undefined) {
261
+ conditions.push('e.has_tests = ?');
262
+ params.push(filter.hasTests ? 1 : 0);
263
+ }
264
+ if (filter?.riskLevel && filter.riskLevel.length > 0) {
265
+ conditions.push(`e.risk_level IN (${filter.riskLevel.map(() => '?').join(',')})`);
266
+ params.push(...filter.riskLevel);
267
+ }
268
+ if (filter?.author) {
269
+ conditions.push('e.author = ?');
270
+ params.push(filter.author);
271
+ }
272
+ if (filter?.tags && filter.tags.length > 0) {
273
+ conditions.push(`e.id IN (
274
+ SELECT entity_id FROM code_entity_tags WHERE tag IN (${filter.tags.map(() => '?').join(',')})
275
+ )`);
276
+ params.push(...filter.tags);
277
+ }
278
+ // FTS 전문검색
279
+ let ftsJoin = '';
280
+ if (filter?.search) {
281
+ ftsJoin = 'INNER JOIN code_entities_fts ON code_entities_fts.rowid = e.rowid';
282
+ conditions.push('code_entities_fts MATCH ?');
283
+ params.push(filter.search);
284
+ }
285
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
286
+ const limit = filter?.limit ?? 50;
287
+ const offset = filter?.offset ?? 0;
288
+ const countRow = this.db.prepare(`SELECT COUNT(*) as cnt FROM code_entities e ${ftsJoin} ${where}`).get(...params);
289
+ const total = countRow.cnt;
290
+ const rows = this.db.prepare(`
291
+ SELECT e.* FROM code_entities e ${ftsJoin} ${where}
292
+ ORDER BY e.file_path, e.line_start NULLS LAST, e.name
293
+ LIMIT ? OFFSET ?
294
+ `).all(...params, limit, offset);
295
+ return {
296
+ entities: this.rowsToEntities(rows),
297
+ total,
298
+ };
299
+ }
300
+ // ============ 상태 관리 ============
301
+ deprecateEntity(id, reason, actor = 'system') {
302
+ const existing = this.getEntity(id);
303
+ if (!existing)
304
+ return null;
305
+ const now = new Date().toISOString();
306
+ this.db.prepare(`
307
+ UPDATE code_entities SET status = 'deprecated', deprecated_at = ?, deprecated_reason = ?, updated_at = ?
308
+ WHERE id = ?
309
+ `).run(now, reason ?? null, now, id);
310
+ this.addEvent(id, 'deprecated', {
311
+ oldValue: existing.status,
312
+ newValue: 'deprecated',
313
+ content: reason,
314
+ actor,
315
+ });
316
+ return this.getEntity(id);
317
+ }
318
+ changeEntityStatus(id, status, actor = 'system') {
319
+ const existing = this.getEntity(id);
320
+ if (!existing)
321
+ return null;
322
+ const now = new Date().toISOString();
323
+ this.db.prepare('UPDATE code_entities SET status = ?, updated_at = ? WHERE id = ?').run(status, now, id);
324
+ this.addEvent(id, 'status_changed', {
325
+ oldValue: existing.status,
326
+ newValue: status,
327
+ actor,
328
+ });
329
+ return this.getEntity(id);
330
+ }
331
+ // ============ 태그 ============
332
+ addTag(entityId, tag, value) {
333
+ this.db.prepare('INSERT OR REPLACE INTO code_entity_tags (entity_id, tag, value) VALUES (?, ?, ?)').run(entityId, tag, value ?? null);
334
+ const now = new Date().toISOString();
335
+ this.db.prepare('UPDATE code_entities SET updated_at = ? WHERE id = ?').run(now, entityId);
336
+ this.addEvent(entityId, 'tag_added', { newValue: tag });
337
+ }
338
+ removeTag(entityId, tag) {
339
+ this.db.prepare('DELETE FROM code_entity_tags WHERE entity_id = ? AND tag = ?').run(entityId, tag);
340
+ this.addEvent(entityId, 'tag_removed', { oldValue: tag });
341
+ }
342
+ getTags(entityId) {
343
+ return this.db.prepare('SELECT tag, value FROM code_entity_tags WHERE entity_id = ?').all(entityId).map(r => ({ tag: r.tag, value: r.value ?? undefined }));
344
+ }
345
+ // ============ 경고 ============
346
+ addWarning(entityId, severity, category, message) {
347
+ const id = nanoid(12);
348
+ const now = new Date().toISOString();
349
+ this.db.prepare(`
350
+ INSERT INTO code_entity_warnings (id, entity_id, severity, category, message, created_at)
351
+ VALUES (?, ?, ?, ?, ?, ?)
352
+ `).run(id, entityId, severity, category, message, now);
353
+ this.db.prepare('UPDATE code_entities SET updated_at = ? WHERE id = ?').run(now, entityId);
354
+ this.addEvent(entityId, 'warning_added', {
355
+ newValue: `${severity}:${category}`,
356
+ content: message,
357
+ });
358
+ return {
359
+ id, entityId, severity, category, message,
360
+ resolved: false, createdAt: now,
361
+ };
362
+ }
363
+ resolveWarning(warningId) {
364
+ const now = new Date().toISOString();
365
+ const warning = this.db.prepare('SELECT * FROM code_entity_warnings WHERE id = ?').get(warningId);
366
+ if (!warning)
367
+ return false;
368
+ this.db.prepare('UPDATE code_entity_warnings SET resolved = 1, resolved_at = ? WHERE id = ?').run(now, warningId);
369
+ this.addEvent(warning.entity_id, 'warning_resolved', {
370
+ oldValue: `${warning.severity}:${warning.category}`,
371
+ content: warning.message,
372
+ });
373
+ return true;
374
+ }
375
+ getWarnings(entityId) {
376
+ return this.db.prepare('SELECT * FROM code_entity_warnings WHERE entity_id = ? ORDER BY created_at DESC').all(entityId).map(this.rowToWarning);
377
+ }
378
+ getUnresolvedWarnings(severity) {
379
+ const where = severity
380
+ ? 'WHERE resolved = 0 AND severity = ?'
381
+ : 'WHERE resolved = 0';
382
+ const params = severity ? [severity] : [];
383
+ return this.db.prepare(`SELECT * FROM code_entity_warnings ${where} ORDER BY
384
+ CASE severity WHEN 'critical' THEN 0 WHEN 'error' THEN 1 WHEN 'warning' THEN 2 ELSE 3 END,
385
+ created_at DESC`).all(...params).map(this.rowToWarning);
386
+ }
387
+ // ============ 관계 ============
388
+ addRelation(sourceId, targetId, relationType) {
389
+ this.db.prepare('INSERT OR IGNORE INTO code_entity_relations (source_id, target_id, relation_type) VALUES (?, ?, ?)').run(sourceId, targetId, relationType);
390
+ }
391
+ removeRelation(sourceId, targetId, relationType) {
392
+ this.db.prepare('DELETE FROM code_entity_relations WHERE source_id = ? AND target_id = ? AND relation_type = ?').run(sourceId, targetId, relationType);
393
+ }
394
+ getRelations(entityId) {
395
+ return this.db.prepare(`
396
+ SELECT r.target_id, e.name as target_name, r.relation_type
397
+ FROM code_entity_relations r
398
+ JOIN code_entities e ON e.id = r.target_id
399
+ WHERE r.source_id = ?
400
+ `).all(entityId).map(r => ({
401
+ targetId: r.target_id,
402
+ targetName: r.target_name,
403
+ relationType: r.relation_type,
404
+ }));
405
+ }
406
+ // ============ 이슈/메모리 연결 ============
407
+ linkIssue(entityId, issueId) {
408
+ const now = new Date().toISOString();
409
+ this.db.prepare('INSERT OR IGNORE INTO code_entity_issue_links (entity_id, issue_id, linked_at) VALUES (?, ?, ?)').run(entityId, issueId, now);
410
+ this.addEvent(entityId, 'issue_linked', { newValue: issueId });
411
+ }
412
+ unlinkIssue(entityId, issueId) {
413
+ this.db.prepare('DELETE FROM code_entity_issue_links WHERE entity_id = ? AND issue_id = ?').run(entityId, issueId);
414
+ }
415
+ getLinkedIssues(entityId) {
416
+ return this.db.prepare('SELECT issue_id FROM code_entity_issue_links WHERE entity_id = ? ORDER BY linked_at').all(entityId).map(r => r.issue_id);
417
+ }
418
+ /** 이슈 ID로 연결된 엔티티 ID 목록 반환 (역방향 조회) */
419
+ getEntitiesByIssueId(issueId) {
420
+ const rows = this.db.prepare('SELECT entity_id FROM code_entity_issue_links WHERE issue_id = ?').all(issueId);
421
+ const entities = [];
422
+ for (const row of rows) {
423
+ const entity = this.getEntity(row.entity_id);
424
+ if (entity)
425
+ entities.push(entity);
426
+ }
427
+ return entities;
428
+ }
429
+ linkMemory(entityId, memoryId) {
430
+ const now = new Date().toISOString();
431
+ this.db.prepare('INSERT OR IGNORE INTO code_entity_memory_links (entity_id, memory_id, linked_at) VALUES (?, ?, ?)').run(entityId, memoryId, now);
432
+ this.addEvent(entityId, 'memory_linked', { newValue: memoryId });
433
+ }
434
+ getLinkedMemories(entityId) {
435
+ return this.db.prepare('SELECT memory_id FROM code_entity_memory_links WHERE entity_id = ? ORDER BY linked_at').all(entityId).map(r => r.memory_id);
436
+ }
437
+ // ============ 이벤트 ============
438
+ addEvent(entityId, type, data) {
439
+ const id = nanoid(12);
440
+ const now = new Date().toISOString();
441
+ this.db.prepare(`
442
+ INSERT INTO code_entity_events (id, entity_id, type, old_value, new_value, content, actor, created_at)
443
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
444
+ `).run(id, entityId, type, data?.oldValue ?? null, data?.newValue ?? null, data?.content ?? null, data?.actor ?? 'system', now);
445
+ return {
446
+ id, entityId, type,
447
+ oldValue: data?.oldValue,
448
+ newValue: data?.newValue,
449
+ content: data?.content,
450
+ actor: data?.actor ?? 'system',
451
+ createdAt: now,
452
+ };
453
+ }
454
+ getEvents(entityId, limit = 50) {
455
+ return this.db.prepare('SELECT * FROM code_entity_events WHERE entity_id = ? ORDER BY created_at DESC LIMIT ?').all(entityId, limit).map(this.rowToEvent);
456
+ }
457
+ // ============ 특화 쿼리 ============
458
+ fileBrief(filePath) {
459
+ const rows = this.db.prepare('SELECT * FROM code_entities WHERE file_path = ? ORDER BY line_start NULLS LAST, name').all(filePath);
460
+ const entities = this.rowsToEntities(rows);
461
+ const deprecated = entities.filter(e => e.status === 'deprecated').length;
462
+ const untested = entities.filter(e => !e.hasTests).length;
463
+ const warnings = entities.reduce((sum, e) => sum + e.warnings.filter(w => !w.resolved).length, 0);
464
+ const broken = entities.filter(e => e.status === 'broken').length;
465
+ const parts = [`${entities.length} entities`];
466
+ if (deprecated > 0)
467
+ parts.push(`${deprecated} deprecated`);
468
+ if (untested > 0)
469
+ parts.push(`${untested} untested`);
470
+ if (warnings > 0)
471
+ parts.push(`${warnings} warnings`);
472
+ if (broken > 0)
473
+ parts.push(`${broken} broken`);
474
+ return {
475
+ filePath,
476
+ summary: parts.join(', '),
477
+ entities,
478
+ };
479
+ }
480
+ deprecatedEntities(projectId) {
481
+ const where = projectId
482
+ ? "WHERE status = 'deprecated' AND project_id = ?"
483
+ : "WHERE status = 'deprecated'";
484
+ const params = projectId ? [projectId] : [];
485
+ const rows = this.db.prepare(`SELECT * FROM code_entities ${where} ORDER BY deprecated_at DESC`).all(...params);
486
+ return this.rowsToEntities(rows);
487
+ }
488
+ untestedEntities(projectId) {
489
+ const where = projectId
490
+ ? "WHERE has_tests = 0 AND status = 'active' AND project_id = ?"
491
+ : "WHERE has_tests = 0 AND status = 'active'";
492
+ const params = projectId ? [projectId] : [];
493
+ const rows = this.db.prepare(`SELECT * FROM code_entities ${where} ORDER BY
494
+ CASE risk_level WHEN 'high' THEN 0 WHEN 'medium' THEN 1 ELSE 2 END,
495
+ complexity_score DESC NULLS LAST`).all(...params);
496
+ return this.rowsToEntities(rows);
497
+ }
498
+ highRiskEntities(projectId) {
499
+ const where = projectId
500
+ ? "WHERE risk_level = 'high' AND project_id = ?"
501
+ : "WHERE risk_level = 'high'";
502
+ const params = projectId ? [projectId] : [];
503
+ const rows = this.db.prepare(`SELECT * FROM code_entities ${where} ORDER BY complexity_score DESC NULLS LAST`).all(...params);
504
+ return this.rowsToEntities(rows);
505
+ }
506
+ entitiesByTag(tag, value) {
507
+ const query = value
508
+ ? `SELECT e.* FROM code_entities e
509
+ JOIN code_entity_tags t ON t.entity_id = e.id
510
+ WHERE t.tag = ? AND t.value = ?`
511
+ : `SELECT e.* FROM code_entities e
512
+ JOIN code_entity_tags t ON t.entity_id = e.id
513
+ WHERE t.tag = ?`;
514
+ const params = value ? [tag, value] : [tag];
515
+ const rows = this.db.prepare(query).all(...params);
516
+ return this.rowsToEntities(rows);
517
+ }
518
+ searchEntities(query, limit = 20) {
519
+ // FTS5 검색 시도
520
+ let ftsRows = [];
521
+ try {
522
+ ftsRows = this.db.prepare(`
523
+ SELECT e.* FROM code_entities e
524
+ INNER JOIN code_entities_fts ON code_entities_fts.rowid = e.rowid
525
+ WHERE code_entities_fts MATCH ?
526
+ LIMIT ?
527
+ `).all(query, limit);
528
+ }
529
+ catch (err) {
530
+ const msg = err instanceof Error ? err.message : String(err);
531
+ if (!msg.includes('fts5') && !msg.includes('MATCH')) {
532
+ console.warn('[Registry] searchEntities FTS error:', msg);
533
+ }
534
+ }
535
+ let results = this.rowsToEntities(ftsRows);
536
+ // FTS 결과가 부족하면 LIKE 폴백 (camelCase, 부분 매칭)
537
+ if (results.length < limit) {
538
+ const escapedQuery = query.replace(/[%_]/g, ch => `\\${ch}`);
539
+ const likePattern = `%${escapedQuery}%`;
540
+ const existingIds = new Set(results.map(e => e.id));
541
+ const fallbackRows = this.db.prepare(`
542
+ SELECT * FROM code_entities
543
+ WHERE (name LIKE ? ESCAPE '\\' OR qualified_name LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\' OR signature LIKE ? ESCAPE '\\')
544
+ LIMIT ?
545
+ `).all(likePattern, likePattern, likePattern, likePattern, likePattern, limit);
546
+ const fallback = this.rowsToEntities(fallbackRows)
547
+ .filter(e => !existingIds.has(e.id));
548
+ results.push(...fallback.slice(0, limit - results.length));
549
+ }
550
+ return results;
551
+ }
552
+ // ============ 통계 ============
553
+ getStats(projectId) {
554
+ const where = projectId ? 'WHERE project_id = ?' : '';
555
+ const params = projectId ? [projectId] : [];
556
+ const total = this.db.prepare(`SELECT COUNT(*) as cnt FROM code_entities ${where}`).get(...params).cnt;
557
+ const byKind = this.db.prepare(`SELECT kind, COUNT(*) as cnt FROM code_entities ${where} GROUP BY kind`).all(...params).map(r => ({ kind: r.kind, count: r.cnt }));
558
+ const byStatus = this.db.prepare(`SELECT status, COUNT(*) as cnt FROM code_entities ${where} GROUP BY status`).all(...params).map(r => ({ status: r.status, count: r.cnt }));
559
+ const deprecated = this.db.prepare(`SELECT COUNT(*) as cnt FROM code_entities ${where ? where + " AND" : "WHERE"} status = 'deprecated'`).get(...params).cnt;
560
+ const untested = this.db.prepare(`SELECT COUNT(*) as cnt FROM code_entities ${where ? where + " AND" : "WHERE"} has_tests = 0 AND status = 'active'`).get(...params).cnt;
561
+ const highRisk = this.db.prepare(`SELECT COUNT(*) as cnt FROM code_entities ${where ? where + " AND" : "WHERE"} risk_level = 'high'`).get(...params).cnt;
562
+ const withWarnings = this.db.prepare(projectId
563
+ ? `SELECT COUNT(DISTINCT w.entity_id) as cnt FROM code_entity_warnings w
564
+ JOIN code_entities e ON e.id = w.entity_id
565
+ WHERE w.resolved = 0 AND e.project_id = ?`
566
+ : `SELECT COUNT(DISTINCT entity_id) as cnt FROM code_entity_warnings WHERE resolved = 0`).get(...params).cnt;
567
+ return { total, byKind, byStatus, deprecated, untested, highRisk, withWarnings };
568
+ }
569
+ // ============ 유틸 ============
570
+ close() {
571
+ this.db.close();
572
+ }
573
+ /** 단일 엔티티 변환 (개별 서브쿼리 — 단건 조회용) */
574
+ rowToEntity(row) {
575
+ const id = row.id;
576
+ return this.buildEntity(row, this.getTags(id), this.getWarnings(id), this.getLinkedIssues(id), this.getLinkedMemories(id));
577
+ }
578
+ /** 배치 엔티티 변환 (N+1 방지 — 리스트 조회용) */
579
+ rowsToEntities(rows) {
580
+ if (rows.length === 0)
581
+ return [];
582
+ const ids = rows.map(r => r.id);
583
+ const placeholders = ids.map(() => '?').join(',');
584
+ // 배치 태그 로딩
585
+ const tagRows = this.db.prepare(`SELECT entity_id, tag, value FROM code_entity_tags WHERE entity_id IN (${placeholders})`).all(...ids);
586
+ const tagsByEntity = new Map();
587
+ for (const r of tagRows) {
588
+ const list = tagsByEntity.get(r.entity_id) ?? [];
589
+ list.push({ tag: r.tag, value: r.value ?? undefined });
590
+ tagsByEntity.set(r.entity_id, list);
591
+ }
592
+ // 배치 경고 로딩
593
+ const warningRows = this.db.prepare(`SELECT * FROM code_entity_warnings WHERE entity_id IN (${placeholders}) ORDER BY created_at DESC`).all(...ids);
594
+ const warningsByEntity = new Map();
595
+ for (const r of warningRows) {
596
+ const list = warningsByEntity.get(r.entity_id) ?? [];
597
+ list.push(this.rowToWarning(r));
598
+ warningsByEntity.set(r.entity_id, list);
599
+ }
600
+ // 배치 이슈 링크 로딩
601
+ const issueRows = this.db.prepare(`SELECT entity_id, issue_id FROM code_entity_issue_links WHERE entity_id IN (${placeholders}) ORDER BY linked_at`).all(...ids);
602
+ const issuesByEntity = new Map();
603
+ for (const r of issueRows) {
604
+ const list = issuesByEntity.get(r.entity_id) ?? [];
605
+ list.push(r.issue_id);
606
+ issuesByEntity.set(r.entity_id, list);
607
+ }
608
+ // 배치 메모리 링크 로딩
609
+ const memoryRows = this.db.prepare(`SELECT entity_id, memory_id FROM code_entity_memory_links WHERE entity_id IN (${placeholders}) ORDER BY linked_at`).all(...ids);
610
+ const memorysByEntity = new Map();
611
+ for (const r of memoryRows) {
612
+ const list = memorysByEntity.get(r.entity_id) ?? [];
613
+ list.push(r.memory_id);
614
+ memorysByEntity.set(r.entity_id, list);
615
+ }
616
+ return rows.map(row => this.buildEntity(row, tagsByEntity.get(row.id) ?? [], warningsByEntity.get(row.id) ?? [], issuesByEntity.get(row.id) ?? [], memorysByEntity.get(row.id) ?? []));
617
+ }
618
+ buildEntity(row, tags, warnings, linkedIssueIds, linkedMemoryIds) {
619
+ return {
620
+ id: row.id,
621
+ projectId: row.project_id,
622
+ kind: row.kind,
623
+ name: row.name,
624
+ qualifiedName: row.qualified_name,
625
+ filePath: row.file_path,
626
+ lineStart: row.line_start ?? undefined,
627
+ lineEnd: row.line_end ?? undefined,
628
+ signature: row.signature ?? undefined,
629
+ status: row.status,
630
+ deprecatedAt: row.deprecated_at ?? undefined,
631
+ deprecatedReason: row.deprecated_reason ?? undefined,
632
+ hasTests: row.has_tests === 1,
633
+ testFile: row.test_file ?? undefined,
634
+ author: row.author ?? undefined,
635
+ maintainer: row.maintainer ?? undefined,
636
+ complexityScore: row.complexity_score ?? undefined,
637
+ riskLevel: row.risk_level,
638
+ description: row.description ?? '',
639
+ notes: row.notes ?? '',
640
+ knowledgeNodeId: row.knowledge_node_id ?? undefined,
641
+ tags,
642
+ warnings,
643
+ linkedIssueIds,
644
+ linkedMemoryIds,
645
+ createdAt: row.created_at,
646
+ updatedAt: row.updated_at,
647
+ };
648
+ }
649
+ rowToWarning(row) {
650
+ return {
651
+ id: row.id,
652
+ entityId: row.entity_id,
653
+ severity: row.severity,
654
+ category: row.category,
655
+ message: row.message,
656
+ resolved: row.resolved === 1,
657
+ resolvedAt: row.resolved_at ?? undefined,
658
+ createdAt: row.created_at,
659
+ };
660
+ }
661
+ rowToEvent(row) {
662
+ return {
663
+ id: row.id,
664
+ entityId: row.entity_id,
665
+ type: row.type,
666
+ oldValue: row.old_value ?? undefined,
667
+ newValue: row.new_value ?? undefined,
668
+ content: row.content ?? undefined,
669
+ actor: row.actor,
670
+ createdAt: row.created_at,
671
+ };
672
+ }
673
+ }
674
+ // 싱글톤
675
+ let storeInstance = null;
676
+ export function getRegistryStore(dbPath) {
677
+ if (!storeInstance) {
678
+ storeInstance = new SqliteRegistryStore(dbPath);
679
+ }
680
+ return storeInstance;
681
+ }
682
+ export function closeRegistryStore() {
683
+ if (storeInstance) {
684
+ storeInstance.close();
685
+ storeInstance = null;
686
+ }
687
+ }
688
+ //# sourceMappingURL=sqliteStore.js.map