anchi-kit 2.2.0 → 2.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.
Potentially problematic release.
This version of anchi-kit might be problematic. Click here for more details.
- package/.cursor/commands/entity.md +135 -0
- package/.cursor/commands/memory/add.md +65 -0
- package/.cursor/commands/memory/load.md +74 -0
- package/.cursor/commands/memory/save.md +68 -0
- package/.cursor/commands/memory.md +141 -0
- package/README.md +427 -127
- package/package.json +1 -1
- package/src/cli.js +6 -0
- package/src/commands/memory.js +158 -0
- package/src/lib/contextDatabase.js +362 -0
- package/src/lib/memoryManager.js +250 -0
- package/.antigravity/agent/code-reviewer.md +0 -141
- package/.antigravity/agent/debugger.md +0 -75
- package/.antigravity/agent/docs-manager.md +0 -120
- package/.antigravity/agent/git-manager.md +0 -60
- package/.antigravity/agent/planner-researcher.md +0 -101
- package/.antigravity/agent/planner.md +0 -88
- package/.antigravity/agent/project-manager.md +0 -113
- package/.antigravity/agent/researcher.md +0 -174
- package/.antigravity/agent/solution-brainstormer.md +0 -90
- package/.antigravity/agent/system-architecture.md +0 -193
- package/.antigravity/agent/tester.md +0 -96
- package/.antigravity/agent/ui-ux-designer.md +0 -233
- package/.antigravity/agent/ui-ux-developer.md +0 -98
- package/.antigravity/command/cook.md +0 -7
- package/.antigravity/command/debug.md +0 -10
- package/.antigravity/command/design/3d.md +0 -65
- package/.antigravity/command/design/fast.md +0 -18
- package/.antigravity/command/design/good.md +0 -21
- package/.antigravity/command/design/screenshot.md +0 -22
- package/.antigravity/command/design/video.md +0 -22
- package/.antigravity/command/docs/init.md +0 -11
- package/.antigravity/command/docs/summarize.md +0 -10
- package/.antigravity/command/docs/update.md +0 -18
- package/.antigravity/command/fix/ci.md +0 -8
- package/.antigravity/command/fix/fast.md +0 -11
- package/.antigravity/command/fix/hard.md +0 -15
- package/.antigravity/command/fix/logs.md +0 -16
- package/.antigravity/command/fix/test.md +0 -18
- package/.antigravity/command/fix/types.md +0 -10
- package/.antigravity/command/git/cm.md +0 -5
- package/.antigravity/command/git/cp.md +0 -4
- package/.antigravity/command/plan/ci.md +0 -12
- package/.antigravity/command/plan/two.md +0 -13
- package/.antigravity/command/plan.md +0 -10
- package/.antigravity/command/test.md +0 -7
- package/.antigravity/command/watzup.md +0 -8
- package/ANTIGRAVITY.md +0 -36
- package/GEMINI.md +0 -75
- package/scripts/prepare-release-assets.cjs +0 -97
- package/scripts/send-discord-release.cjs +0 -204
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// anchi-kit Memory CLI Command
|
|
3
|
+
// =============================================================================
|
|
4
|
+
|
|
5
|
+
const { loadMemory, saveMemory, clearMemory, getMemorySummary, addNote, addDecision } = require('../lib/memoryManager');
|
|
6
|
+
|
|
7
|
+
async function memory(action = 'show', arg = null) {
|
|
8
|
+
const targetDir = process.cwd();
|
|
9
|
+
|
|
10
|
+
console.log('');
|
|
11
|
+
console.log('🧠 anchi-kit Memory');
|
|
12
|
+
console.log('═══════════════════════════════════════════════════════════════');
|
|
13
|
+
console.log('');
|
|
14
|
+
|
|
15
|
+
switch (action) {
|
|
16
|
+
case 'show':
|
|
17
|
+
case undefined:
|
|
18
|
+
showMemory(targetDir);
|
|
19
|
+
break;
|
|
20
|
+
|
|
21
|
+
case 'save':
|
|
22
|
+
saveProjectMemory(targetDir, arg);
|
|
23
|
+
break;
|
|
24
|
+
|
|
25
|
+
case 'load':
|
|
26
|
+
loadProjectMemory(targetDir);
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
case 'clear':
|
|
30
|
+
clearProjectMemory(targetDir);
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case 'add':
|
|
34
|
+
addToMemory(targetDir, arg);
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
default:
|
|
38
|
+
console.log('Usage:');
|
|
39
|
+
console.log(' npx anchi-kit memory show Show current memory');
|
|
40
|
+
console.log(' npx anchi-kit memory save Save context');
|
|
41
|
+
console.log(' npx anchi-kit memory load Load context');
|
|
42
|
+
console.log(' npx anchi-kit memory clear Clear memory');
|
|
43
|
+
console.log(' npx anchi-kit memory add "note" Add a note');
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function showMemory(targetDir) {
|
|
51
|
+
const memory = loadMemory(targetDir);
|
|
52
|
+
|
|
53
|
+
console.log('📂 Project:');
|
|
54
|
+
if (memory.project.name) {
|
|
55
|
+
console.log(` Name: ${memory.project.name}`);
|
|
56
|
+
if (memory.project.description) {
|
|
57
|
+
console.log(` Description: ${memory.project.description}`);
|
|
58
|
+
}
|
|
59
|
+
if (memory.project.tech_stack.length > 0) {
|
|
60
|
+
console.log(` Tech: ${memory.project.tech_stack.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
if (memory.project.architecture) {
|
|
63
|
+
console.log(` Architecture: ${memory.project.architecture}`);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
console.log(' (No project info saved)');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log('📌 Last Session:');
|
|
71
|
+
if (memory.session.last_task) {
|
|
72
|
+
console.log(` Task: ${memory.session.last_task}`);
|
|
73
|
+
if (memory.session.current_phase) {
|
|
74
|
+
console.log(` Phase: ${memory.session.current_phase}`);
|
|
75
|
+
}
|
|
76
|
+
console.log(` Last accessed: ${memory.session.last_accessed || 'Never'}`);
|
|
77
|
+
} else {
|
|
78
|
+
console.log(' (No session info)');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log('🧠 Decisions:');
|
|
83
|
+
if (memory.decisions.length > 0) {
|
|
84
|
+
const recent = memory.decisions.slice(-5);
|
|
85
|
+
for (const d of recent) {
|
|
86
|
+
console.log(` • ${d.decision}`);
|
|
87
|
+
}
|
|
88
|
+
if (memory.decisions.length > 5) {
|
|
89
|
+
console.log(` ... and ${memory.decisions.length - 5} more`);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
console.log(' (No decisions recorded)');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log('📝 Notes:');
|
|
97
|
+
if (memory.notes.length > 0) {
|
|
98
|
+
const recent = memory.notes.slice(-5);
|
|
99
|
+
for (const n of recent) {
|
|
100
|
+
console.log(` • ${n.note}`);
|
|
101
|
+
}
|
|
102
|
+
if (memory.notes.length > 5) {
|
|
103
|
+
console.log(` ... and ${memory.notes.length - 5} more`);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
console.log(' (No notes)');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function saveProjectMemory(targetDir, note) {
|
|
111
|
+
const memory = loadMemory(targetDir);
|
|
112
|
+
if (note) {
|
|
113
|
+
memory.notes.push({
|
|
114
|
+
id: Date.now(),
|
|
115
|
+
note: note,
|
|
116
|
+
category: 'session',
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const path = saveMemory(targetDir, memory);
|
|
121
|
+
console.log('✅ Memory saved!');
|
|
122
|
+
console.log(` Location: ${path}`);
|
|
123
|
+
console.log(` Stats:`);
|
|
124
|
+
console.log(` • Decisions: ${memory.decisions.length}`);
|
|
125
|
+
console.log(` • Notes: ${memory.notes.length}`);
|
|
126
|
+
console.log(` • Entities: ${Object.keys(memory.entities).length}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function loadProjectMemory(targetDir) {
|
|
130
|
+
const summary = getMemorySummary(targetDir);
|
|
131
|
+
console.log(summary);
|
|
132
|
+
console.log('');
|
|
133
|
+
console.log('✅ Context loaded! AI now remembers your project.');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function clearProjectMemory(targetDir) {
|
|
137
|
+
clearMemory(targetDir);
|
|
138
|
+
console.log('✅ Memory cleared!');
|
|
139
|
+
console.log(' All project context has been reset.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function addToMemory(targetDir, content) {
|
|
143
|
+
if (!content) {
|
|
144
|
+
console.log('Usage: npx anchi-kit memory add "your note"');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (content.startsWith('decision:')) {
|
|
149
|
+
const parts = content.slice(9).split(' - ');
|
|
150
|
+
addDecision(targetDir, parts[0].trim(), parts[1] || '');
|
|
151
|
+
console.log('✅ Decision recorded!');
|
|
152
|
+
} else {
|
|
153
|
+
addNote(targetDir, content);
|
|
154
|
+
console.log('✅ Note added!');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = { memory };
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// anchi-kit SQLite Context Database
|
|
3
|
+
// Phase 2: Persistent storage with relationships
|
|
4
|
+
// =============================================================================
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const DB_DIR = '.cursor/context';
|
|
10
|
+
const DB_FILE = 'context.db';
|
|
11
|
+
|
|
12
|
+
// SQLite schema for context database
|
|
13
|
+
const SCHEMA = `
|
|
14
|
+
-- Project information
|
|
15
|
+
CREATE TABLE IF NOT EXISTS project (
|
|
16
|
+
id INTEGER PRIMARY KEY,
|
|
17
|
+
name TEXT,
|
|
18
|
+
description TEXT,
|
|
19
|
+
tech_stack TEXT,
|
|
20
|
+
architecture TEXT,
|
|
21
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
22
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
-- Session history
|
|
26
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
27
|
+
id INTEGER PRIMARY KEY,
|
|
28
|
+
task TEXT,
|
|
29
|
+
phase TEXT,
|
|
30
|
+
files_opened TEXT,
|
|
31
|
+
started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
32
|
+
ended_at DATETIME
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
-- Decisions log
|
|
36
|
+
CREATE TABLE IF NOT EXISTS decisions (
|
|
37
|
+
id INTEGER PRIMARY KEY,
|
|
38
|
+
decision TEXT NOT NULL,
|
|
39
|
+
reason TEXT,
|
|
40
|
+
files_affected TEXT,
|
|
41
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
-- Notes
|
|
45
|
+
CREATE TABLE IF NOT EXISTS notes (
|
|
46
|
+
id INTEGER PRIMARY KEY,
|
|
47
|
+
note TEXT NOT NULL,
|
|
48
|
+
category TEXT DEFAULT 'general',
|
|
49
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- Entities (knowledge graph nodes)
|
|
53
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
54
|
+
id INTEGER PRIMARY KEY,
|
|
55
|
+
name TEXT UNIQUE NOT NULL,
|
|
56
|
+
type TEXT,
|
|
57
|
+
description TEXT,
|
|
58
|
+
metadata TEXT,
|
|
59
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
60
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
-- Entity relationships (knowledge graph edges)
|
|
64
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
65
|
+
id INTEGER PRIMARY KEY,
|
|
66
|
+
source_id INTEGER NOT NULL,
|
|
67
|
+
target_id INTEGER NOT NULL,
|
|
68
|
+
relation_type TEXT NOT NULL,
|
|
69
|
+
metadata TEXT,
|
|
70
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
71
|
+
FOREIGN KEY (source_id) REFERENCES entities(id),
|
|
72
|
+
FOREIGN KEY (target_id) REFERENCES entities(id)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
-- File analysis cache
|
|
76
|
+
CREATE TABLE IF NOT EXISTS file_cache (
|
|
77
|
+
id INTEGER PRIMARY KEY,
|
|
78
|
+
file_path TEXT UNIQUE NOT NULL,
|
|
79
|
+
summary TEXT,
|
|
80
|
+
entities TEXT,
|
|
81
|
+
last_modified DATETIME,
|
|
82
|
+
analyzed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
83
|
+
);
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get database path
|
|
88
|
+
*/
|
|
89
|
+
function getDbPath(targetDir) {
|
|
90
|
+
return path.join(targetDir, DB_DIR, DB_FILE);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get database directory
|
|
95
|
+
*/
|
|
96
|
+
function getDbDir(targetDir) {
|
|
97
|
+
return path.join(targetDir, DB_DIR);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if better-sqlite3 is available
|
|
102
|
+
*/
|
|
103
|
+
function hasSqlite() {
|
|
104
|
+
try {
|
|
105
|
+
require('better-sqlite3');
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Initialize database (creates if not exists)
|
|
114
|
+
* Note: Requires better-sqlite3 package
|
|
115
|
+
*/
|
|
116
|
+
function initDatabase(targetDir) {
|
|
117
|
+
if (!hasSqlite()) {
|
|
118
|
+
console.log('SQLite not available. Using JSON fallback.');
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const Database = require('better-sqlite3');
|
|
123
|
+
const dbDir = getDbDir(targetDir);
|
|
124
|
+
const dbPath = getDbPath(targetDir);
|
|
125
|
+
|
|
126
|
+
if (!fs.existsSync(dbDir)) {
|
|
127
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const db = new Database(dbPath);
|
|
131
|
+
db.exec(SCHEMA);
|
|
132
|
+
|
|
133
|
+
return db;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get or create database instance
|
|
138
|
+
*/
|
|
139
|
+
let _db = null;
|
|
140
|
+
function getDatabase(targetDir) {
|
|
141
|
+
if (!_db) {
|
|
142
|
+
_db = initDatabase(targetDir);
|
|
143
|
+
}
|
|
144
|
+
return _db;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Close database connection
|
|
149
|
+
*/
|
|
150
|
+
function closeDatabase() {
|
|
151
|
+
if (_db) {
|
|
152
|
+
_db.close();
|
|
153
|
+
_db = null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// =============================================================================
|
|
158
|
+
// CRUD Operations
|
|
159
|
+
// =============================================================================
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Set project info
|
|
163
|
+
*/
|
|
164
|
+
function setProject(targetDir, info) {
|
|
165
|
+
const db = getDatabase(targetDir);
|
|
166
|
+
if (!db) return null;
|
|
167
|
+
|
|
168
|
+
const stmt = db.prepare(`
|
|
169
|
+
INSERT OR REPLACE INTO project (id, name, description, tech_stack, architecture, updated_at)
|
|
170
|
+
VALUES (1, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
171
|
+
`);
|
|
172
|
+
|
|
173
|
+
stmt.run(
|
|
174
|
+
info.name,
|
|
175
|
+
info.description,
|
|
176
|
+
JSON.stringify(info.tech_stack || []),
|
|
177
|
+
info.architecture
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return info;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get project info
|
|
185
|
+
*/
|
|
186
|
+
function getProject(targetDir) {
|
|
187
|
+
const db = getDatabase(targetDir);
|
|
188
|
+
if (!db) return null;
|
|
189
|
+
|
|
190
|
+
const row = db.prepare('SELECT * FROM project WHERE id = 1').get();
|
|
191
|
+
if (!row) return null;
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
name: row.name,
|
|
195
|
+
description: row.description,
|
|
196
|
+
tech_stack: JSON.parse(row.tech_stack || '[]'),
|
|
197
|
+
architecture: row.architecture,
|
|
198
|
+
created_at: row.created_at,
|
|
199
|
+
updated_at: row.updated_at,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Add decision
|
|
205
|
+
*/
|
|
206
|
+
function addDecisionDb(targetDir, decision, reason = '', filesAffected = []) {
|
|
207
|
+
const db = getDatabase(targetDir);
|
|
208
|
+
if (!db) return null;
|
|
209
|
+
|
|
210
|
+
const stmt = db.prepare(`
|
|
211
|
+
INSERT INTO decisions (decision, reason, files_affected)
|
|
212
|
+
VALUES (?, ?, ?)
|
|
213
|
+
`);
|
|
214
|
+
|
|
215
|
+
const result = stmt.run(decision, reason, JSON.stringify(filesAffected));
|
|
216
|
+
return result.lastInsertRowid;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get recent decisions
|
|
221
|
+
*/
|
|
222
|
+
function getDecisions(targetDir, limit = 10) {
|
|
223
|
+
const db = getDatabase(targetDir);
|
|
224
|
+
if (!db) return [];
|
|
225
|
+
|
|
226
|
+
return db.prepare(`
|
|
227
|
+
SELECT * FROM decisions ORDER BY created_at DESC LIMIT ?
|
|
228
|
+
`).all(limit);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Add note
|
|
233
|
+
*/
|
|
234
|
+
function addNoteDb(targetDir, note, category = 'general') {
|
|
235
|
+
const db = getDatabase(targetDir);
|
|
236
|
+
if (!db) return null;
|
|
237
|
+
|
|
238
|
+
const stmt = db.prepare(`
|
|
239
|
+
INSERT INTO notes (note, category) VALUES (?, ?)
|
|
240
|
+
`);
|
|
241
|
+
|
|
242
|
+
const result = stmt.run(note, category);
|
|
243
|
+
return result.lastInsertRowid;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get recent notes
|
|
248
|
+
*/
|
|
249
|
+
function getNotes(targetDir, category = null, limit = 10) {
|
|
250
|
+
const db = getDatabase(targetDir);
|
|
251
|
+
if (!db) return [];
|
|
252
|
+
|
|
253
|
+
if (category) {
|
|
254
|
+
return db.prepare(`
|
|
255
|
+
SELECT * FROM notes WHERE category = ? ORDER BY created_at DESC LIMIT ?
|
|
256
|
+
`).all(category, limit);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return db.prepare(`
|
|
260
|
+
SELECT * FROM notes ORDER BY created_at DESC LIMIT ?
|
|
261
|
+
`).all(limit);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Upsert entity
|
|
266
|
+
*/
|
|
267
|
+
function upsertEntityDb(targetDir, name, type, description = '', metadata = {}) {
|
|
268
|
+
const db = getDatabase(targetDir);
|
|
269
|
+
if (!db) return null;
|
|
270
|
+
|
|
271
|
+
const stmt = db.prepare(`
|
|
272
|
+
INSERT INTO entities (name, type, description, metadata, updated_at)
|
|
273
|
+
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
274
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
275
|
+
type = excluded.type,
|
|
276
|
+
description = excluded.description,
|
|
277
|
+
metadata = excluded.metadata,
|
|
278
|
+
updated_at = CURRENT_TIMESTAMP
|
|
279
|
+
`);
|
|
280
|
+
|
|
281
|
+
stmt.run(name, type, description, JSON.stringify(metadata));
|
|
282
|
+
return name;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get entity
|
|
287
|
+
*/
|
|
288
|
+
function getEntityDb(targetDir, name) {
|
|
289
|
+
const db = getDatabase(targetDir);
|
|
290
|
+
if (!db) return null;
|
|
291
|
+
|
|
292
|
+
const row = db.prepare('SELECT * FROM entities WHERE name = ?').get(name);
|
|
293
|
+
if (!row) return null;
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
id: row.id,
|
|
297
|
+
name: row.name,
|
|
298
|
+
type: row.type,
|
|
299
|
+
description: row.description,
|
|
300
|
+
metadata: JSON.parse(row.metadata || '{}'),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Add relation between entities
|
|
306
|
+
*/
|
|
307
|
+
function addRelation(targetDir, sourceName, targetName, relationType, metadata = {}) {
|
|
308
|
+
const db = getDatabase(targetDir);
|
|
309
|
+
if (!db) return null;
|
|
310
|
+
|
|
311
|
+
const source = db.prepare('SELECT id FROM entities WHERE name = ?').get(sourceName);
|
|
312
|
+
const target = db.prepare('SELECT id FROM entities WHERE name = ?').get(targetName);
|
|
313
|
+
|
|
314
|
+
if (!source || !target) return null;
|
|
315
|
+
|
|
316
|
+
const stmt = db.prepare(`
|
|
317
|
+
INSERT INTO relations (source_id, target_id, relation_type, metadata)
|
|
318
|
+
VALUES (?, ?, ?, ?)
|
|
319
|
+
`);
|
|
320
|
+
|
|
321
|
+
const result = stmt.run(source.id, target.id, relationType, JSON.stringify(metadata));
|
|
322
|
+
return result.lastInsertRowid;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get relations for entity
|
|
327
|
+
*/
|
|
328
|
+
function getRelations(targetDir, entityName) {
|
|
329
|
+
const db = getDatabase(targetDir);
|
|
330
|
+
if (!db) return [];
|
|
331
|
+
|
|
332
|
+
return db.prepare(`
|
|
333
|
+
SELECT
|
|
334
|
+
r.*,
|
|
335
|
+
s.name as source_name,
|
|
336
|
+
t.name as target_name
|
|
337
|
+
FROM relations r
|
|
338
|
+
JOIN entities s ON r.source_id = s.id
|
|
339
|
+
JOIN entities t ON r.target_id = t.id
|
|
340
|
+
WHERE s.name = ? OR t.name = ?
|
|
341
|
+
`).all(entityName, entityName);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
module.exports = {
|
|
345
|
+
SCHEMA,
|
|
346
|
+
getDbPath,
|
|
347
|
+
initDatabase,
|
|
348
|
+
getDatabase,
|
|
349
|
+
closeDatabase,
|
|
350
|
+
hasSqlite,
|
|
351
|
+
// CRUD
|
|
352
|
+
setProject,
|
|
353
|
+
getProject,
|
|
354
|
+
addDecisionDb,
|
|
355
|
+
getDecisions,
|
|
356
|
+
addNoteDb,
|
|
357
|
+
getNotes,
|
|
358
|
+
upsertEntityDb,
|
|
359
|
+
getEntityDb,
|
|
360
|
+
addRelation,
|
|
361
|
+
getRelations,
|
|
362
|
+
};
|