@gotza02/sequential-thinking 2026.2.31 → 2026.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codestore.d.ts +102 -2
- package/dist/codestore.js +499 -17
- package/dist/graph.d.ts +10 -1
- package/dist/graph.js +471 -93
- package/dist/http-server.d.ts +15 -0
- package/dist/http-server.js +732 -70
- package/dist/index.js +2 -0
- package/dist/lib.js +79 -28
- package/dist/notes.d.ts +39 -2
- package/dist/notes.js +233 -24
- package/dist/system_test.js +1 -1
- package/dist/tools/codestore.js +1 -1
- package/dist/tools/filesystem.js +359 -40
- package/dist/tools/sports.d.ts +2 -0
- package/dist/tools/sports.js +133 -0
- package/dist/tools/web.d.ts +1 -0
- package/dist/tools/web.js +52 -48
- package/dist/verify_all_tools.d.ts +1 -0
- package/dist/verify_all_tools.js +129 -0
- package/package.json +1 -1
package/dist/codestore.d.ts
CHANGED
|
@@ -7,22 +7,122 @@ export interface CodeSnippet {
|
|
|
7
7
|
tags: string[];
|
|
8
8
|
context?: string;
|
|
9
9
|
updatedAt: string;
|
|
10
|
+
embedding?: number[];
|
|
10
11
|
}
|
|
11
12
|
export interface CodeKnowledge {
|
|
13
|
+
version: string;
|
|
12
14
|
snippets: CodeSnippet[];
|
|
13
15
|
patterns: Record<string, string>;
|
|
16
|
+
metadata?: {
|
|
17
|
+
lastModified: string;
|
|
18
|
+
snippetCount: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface SearchOptions {
|
|
22
|
+
fuzzy?: boolean;
|
|
23
|
+
threshold?: number;
|
|
24
|
+
limit?: number;
|
|
25
|
+
includeCode?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface SearchResult {
|
|
28
|
+
snippet: CodeSnippet;
|
|
29
|
+
relevance: number;
|
|
30
|
+
matchHighlights?: {
|
|
31
|
+
title?: string[];
|
|
32
|
+
description?: string[];
|
|
33
|
+
tags?: string[];
|
|
34
|
+
};
|
|
14
35
|
}
|
|
15
36
|
export declare class CodeDatabase {
|
|
16
37
|
private filePath;
|
|
17
38
|
private db;
|
|
18
|
-
private
|
|
39
|
+
private lastModifiedTime;
|
|
19
40
|
private mutex;
|
|
20
41
|
constructor(storagePath?: string);
|
|
42
|
+
/**
|
|
43
|
+
* Load database from disk with automatic reload detection.
|
|
44
|
+
* Uses mtime to detect if file has been modified externally.
|
|
45
|
+
*/
|
|
21
46
|
private load;
|
|
47
|
+
/**
|
|
48
|
+
* Attempt to recover database from corrupted JSON
|
|
49
|
+
*/
|
|
50
|
+
private attemptRecovery;
|
|
51
|
+
/**
|
|
52
|
+
* Backup corrupted database file
|
|
53
|
+
*/
|
|
54
|
+
private backupCorruptedFile;
|
|
55
|
+
/**
|
|
56
|
+
* Atomic save using write-to-temp + rename pattern.
|
|
57
|
+
*/
|
|
22
58
|
private save;
|
|
59
|
+
/**
|
|
60
|
+
* Reload database from disk
|
|
61
|
+
*/
|
|
62
|
+
reload(): Promise<void>;
|
|
23
63
|
addSnippet(snippet: Omit<CodeSnippet, 'id' | 'updatedAt'>): Promise<CodeSnippet>;
|
|
24
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Enhanced search with relevance scoring and optional fuzzy matching
|
|
66
|
+
*/
|
|
67
|
+
searchSnippets(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Get snippet by ID
|
|
70
|
+
*/
|
|
71
|
+
getSnippet(id: string): Promise<CodeSnippet | null>;
|
|
72
|
+
/**
|
|
73
|
+
* Update snippet
|
|
74
|
+
*/
|
|
75
|
+
updateSnippet(id: string, updates: Partial<Omit<CodeSnippet, 'id' | 'updatedAt'>>): Promise<CodeSnippet | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Delete snippet
|
|
78
|
+
*/
|
|
79
|
+
deleteSnippet(id: string): Promise<boolean>;
|
|
80
|
+
/**
|
|
81
|
+
* List all snippets with optional filtering
|
|
82
|
+
*/
|
|
83
|
+
listSnippets(filter?: {
|
|
84
|
+
language?: string;
|
|
85
|
+
tags?: string[];
|
|
86
|
+
limit?: number;
|
|
87
|
+
}): Promise<CodeSnippet[]>;
|
|
25
88
|
learnPattern(name: string, description: string): Promise<void>;
|
|
26
89
|
getPattern(name: string): Promise<string | null>;
|
|
27
90
|
listAllPatterns(): Promise<Record<string, string>>;
|
|
91
|
+
deletePattern(name: string): Promise<boolean>;
|
|
92
|
+
/**
|
|
93
|
+
* Search patterns with fuzzy matching
|
|
94
|
+
*/
|
|
95
|
+
searchPatterns(query: string, limit?: number): Promise<Array<{
|
|
96
|
+
name: string;
|
|
97
|
+
description: string;
|
|
98
|
+
relevance: number;
|
|
99
|
+
}>>;
|
|
100
|
+
/**
|
|
101
|
+
* Get database statistics
|
|
102
|
+
*/
|
|
103
|
+
getStats(): Promise<{
|
|
104
|
+
snippetCount: number;
|
|
105
|
+
patternCount: number;
|
|
106
|
+
languages: Record<string, number>;
|
|
107
|
+
totalTags: string[];
|
|
108
|
+
lastModified: string | null;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Export database as JSON string
|
|
112
|
+
*/
|
|
113
|
+
export(): Promise<string>;
|
|
114
|
+
/**
|
|
115
|
+
* Import snippets from JSON string
|
|
116
|
+
*/
|
|
117
|
+
import(jsonData: string, merge?: boolean): Promise<number>;
|
|
118
|
+
/**
|
|
119
|
+
* Clear all data (use with caution!)
|
|
120
|
+
*/
|
|
121
|
+
clearAll(): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Prepare embeddings for semantic search
|
|
124
|
+
* This is a placeholder for future integration with embedding models
|
|
125
|
+
* Call this after adding/updating snippets to enable semantic search
|
|
126
|
+
*/
|
|
127
|
+
updateEmbeddings(): Promise<void>;
|
|
28
128
|
}
|
package/dist/codestore.js
CHANGED
|
@@ -1,35 +1,273 @@
|
|
|
1
1
|
import * as fs from 'fs/promises';
|
|
2
|
+
import { existsSync, statSync } from 'fs';
|
|
2
3
|
import * as path from 'path';
|
|
3
4
|
import { AsyncMutex } from './utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Simple fuzzy matching using character-by-character comparison
|
|
7
|
+
*/
|
|
8
|
+
function fuzzyMatch(pattern, text) {
|
|
9
|
+
const p = pattern.toLowerCase();
|
|
10
|
+
const t = text.toLowerCase();
|
|
11
|
+
let pIdx = 0;
|
|
12
|
+
let tIdx = 0;
|
|
13
|
+
let matches = 0;
|
|
14
|
+
while (pIdx < p.length && tIdx < t.length) {
|
|
15
|
+
if (p[pIdx] === t[tIdx]) {
|
|
16
|
+
matches++;
|
|
17
|
+
pIdx++;
|
|
18
|
+
}
|
|
19
|
+
tIdx++;
|
|
20
|
+
}
|
|
21
|
+
if (pIdx === p.length) {
|
|
22
|
+
// All pattern characters found - calculate score
|
|
23
|
+
const accuracy = matches / t.length;
|
|
24
|
+
const completeness = matches / p.length;
|
|
25
|
+
return (accuracy + completeness) / 2;
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Calculate relevance score for a snippet against a query
|
|
31
|
+
*/
|
|
32
|
+
function calculateRelevance(query, snippet, includeCode) {
|
|
33
|
+
const q = query.toLowerCase();
|
|
34
|
+
const qWords = q.split(/\s+/).filter(w => w.length > 0);
|
|
35
|
+
let score = 0;
|
|
36
|
+
const maxScore = qWords.length * 3; // Max possible score
|
|
37
|
+
for (const word of qWords) {
|
|
38
|
+
// Exact match in title = 3 points
|
|
39
|
+
if (snippet.title.toLowerCase().includes(word)) {
|
|
40
|
+
score += 3;
|
|
41
|
+
}
|
|
42
|
+
// Exact match in description = 2 points
|
|
43
|
+
else if (snippet.description.toLowerCase().includes(word)) {
|
|
44
|
+
score += 2;
|
|
45
|
+
}
|
|
46
|
+
// Match in tags = 2.5 points
|
|
47
|
+
else if (snippet.tags.some(t => t.toLowerCase().includes(word))) {
|
|
48
|
+
score += 2.5;
|
|
49
|
+
}
|
|
50
|
+
// Match in context = 1.5 points
|
|
51
|
+
else if (snippet.context?.toLowerCase().includes(word)) {
|
|
52
|
+
score += 1.5;
|
|
53
|
+
}
|
|
54
|
+
// Match in code (if enabled) = 1 point
|
|
55
|
+
else if (includeCode && snippet.code.toLowerCase().includes(word)) {
|
|
56
|
+
score += 1;
|
|
57
|
+
}
|
|
58
|
+
// Partial match (fuzzy) = 0.5 points
|
|
59
|
+
else {
|
|
60
|
+
let fuzzyFound = false;
|
|
61
|
+
const fields = [
|
|
62
|
+
snippet.title,
|
|
63
|
+
snippet.description,
|
|
64
|
+
...snippet.tags,
|
|
65
|
+
snippet.context || '',
|
|
66
|
+
includeCode ? snippet.code : ''
|
|
67
|
+
];
|
|
68
|
+
for (const field of fields) {
|
|
69
|
+
if (fuzzyMatch(word, field) > 0.3) {
|
|
70
|
+
score += 0.5;
|
|
71
|
+
fuzzyFound = true;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!fuzzyFound) {
|
|
76
|
+
// Very loose match - 0.1 points
|
|
77
|
+
if (fields.some(f => f.toLowerCase().includes(word.substring(0, Math.min(3, word.length))))) {
|
|
78
|
+
score += 0.1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return Math.min(score / maxScore, 1);
|
|
84
|
+
}
|
|
4
85
|
export class CodeDatabase {
|
|
5
86
|
filePath;
|
|
6
|
-
db = { snippets: [], patterns: {} };
|
|
7
|
-
|
|
87
|
+
db = { version: '2.0', snippets: [], patterns: {} };
|
|
88
|
+
lastModifiedTime = 0;
|
|
8
89
|
mutex = new AsyncMutex();
|
|
9
90
|
constructor(storagePath = 'code_database.json') {
|
|
10
91
|
this.filePath = path.resolve(storagePath);
|
|
11
92
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Load database from disk with automatic reload detection.
|
|
95
|
+
* Uses mtime to detect if file has been modified externally.
|
|
96
|
+
*/
|
|
97
|
+
async load(forceReload = false) {
|
|
15
98
|
try {
|
|
99
|
+
if (!existsSync(this.filePath)) {
|
|
100
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
101
|
+
this.lastModifiedTime = 0;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const stats = statSync(this.filePath);
|
|
105
|
+
const currentMtime = stats.mtimeMs;
|
|
106
|
+
// Skip reload if file hasn't been modified (unless forced)
|
|
107
|
+
if (!forceReload && currentMtime === this.lastModifiedTime && this.db.snippets.length > 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
16
110
|
const data = await fs.readFile(this.filePath, 'utf-8');
|
|
17
|
-
|
|
111
|
+
if (!data.trim()) {
|
|
112
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
113
|
+
this.lastModifiedTime = currentMtime;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(data);
|
|
118
|
+
// New format with version
|
|
119
|
+
if (parsed.version && parsed.snippets) {
|
|
120
|
+
this.db = parsed;
|
|
121
|
+
}
|
|
122
|
+
// Legacy format
|
|
123
|
+
else if (parsed.snippets || parsed.patterns) {
|
|
124
|
+
this.db = {
|
|
125
|
+
version: '2.0',
|
|
126
|
+
snippets: parsed.snippets || [],
|
|
127
|
+
patterns: parsed.patterns || {}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// Very old format (just array)
|
|
131
|
+
else if (Array.isArray(parsed)) {
|
|
132
|
+
this.db = {
|
|
133
|
+
version: '2.0',
|
|
134
|
+
snippets: parsed,
|
|
135
|
+
patterns: {}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
this.lastModifiedTime = currentMtime;
|
|
139
|
+
}
|
|
140
|
+
catch (parseError) {
|
|
141
|
+
console.error(`[CodeDatabase] Parse error, attempting recovery:`, parseError);
|
|
142
|
+
this.attemptRecovery(data);
|
|
143
|
+
this.lastModifiedTime = currentMtime;
|
|
144
|
+
}
|
|
18
145
|
}
|
|
19
146
|
catch (error) {
|
|
20
|
-
|
|
147
|
+
if (error.code === 'ENOENT') {
|
|
148
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
149
|
+
this.lastModifiedTime = 0;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.error(`[CodeDatabase] Load error:`, error);
|
|
153
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
154
|
+
this.lastModifiedTime = 0;
|
|
155
|
+
}
|
|
21
156
|
}
|
|
22
|
-
this.loaded = true;
|
|
23
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Attempt to recover database from corrupted JSON
|
|
160
|
+
*/
|
|
161
|
+
attemptRecovery(data) {
|
|
162
|
+
let braceCount = 0;
|
|
163
|
+
let lastValidIndex = -1;
|
|
164
|
+
for (let i = 0; i < data.length; i++) {
|
|
165
|
+
const char = data[i];
|
|
166
|
+
if (char === '{') {
|
|
167
|
+
braceCount++;
|
|
168
|
+
}
|
|
169
|
+
else if (char === '}') {
|
|
170
|
+
braceCount--;
|
|
171
|
+
if (braceCount === 0) {
|
|
172
|
+
lastValidIndex = i;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (lastValidIndex !== -1) {
|
|
177
|
+
try {
|
|
178
|
+
const recoveredData = data.substring(0, lastValidIndex + 1).trim();
|
|
179
|
+
const parsed = JSON.parse(recoveredData);
|
|
180
|
+
if (parsed.version && parsed.snippets) {
|
|
181
|
+
this.db = parsed;
|
|
182
|
+
}
|
|
183
|
+
else if (parsed.snippets || parsed.patterns) {
|
|
184
|
+
this.db = {
|
|
185
|
+
version: '2.0',
|
|
186
|
+
snippets: parsed.snippets || [],
|
|
187
|
+
patterns: parsed.patterns || {}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
else if (Array.isArray(parsed)) {
|
|
191
|
+
this.db = { version: '2.0', snippets: parsed, patterns: {} };
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
195
|
+
}
|
|
196
|
+
this.backupCorruptedFile(data);
|
|
197
|
+
console.log(`[CodeDatabase] Recovered ${this.db.snippets.length} snippets.`);
|
|
198
|
+
}
|
|
199
|
+
catch (recoveryError) {
|
|
200
|
+
console.error('[CodeDatabase] Recovery failed:', recoveryError);
|
|
201
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
202
|
+
this.backupCorruptedFile(data);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.error('[CodeDatabase] No valid JSON structure found.');
|
|
207
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Backup corrupted database file
|
|
212
|
+
*/
|
|
213
|
+
async backupCorruptedFile(corruptedData) {
|
|
214
|
+
try {
|
|
215
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
216
|
+
const backupPath = `${this.filePath}.corrupted.${timestamp}`;
|
|
217
|
+
await fs.writeFile(backupPath, corruptedData, 'utf-8');
|
|
218
|
+
console.error(`[CodeDatabase] Corrupted data backed up to: ${backupPath}`);
|
|
219
|
+
}
|
|
220
|
+
catch (backupError) {
|
|
221
|
+
console.error('[CodeDatabase] Failed to backup corrupted file:', backupError);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Atomic save using write-to-temp + rename pattern.
|
|
226
|
+
*/
|
|
24
227
|
async save() {
|
|
25
|
-
|
|
228
|
+
const tmpPath = `${this.filePath}.tmp`;
|
|
229
|
+
// Prepare storage format
|
|
230
|
+
const storage = {
|
|
231
|
+
version: '2.0',
|
|
232
|
+
snippets: this.db.snippets,
|
|
233
|
+
patterns: this.db.patterns,
|
|
234
|
+
metadata: {
|
|
235
|
+
lastModified: new Date().toISOString(),
|
|
236
|
+
snippetCount: this.db.snippets.length
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
try {
|
|
240
|
+
// Step 1: Write to temporary file
|
|
241
|
+
await fs.writeFile(tmpPath, JSON.stringify(storage, null, 2), 'utf-8');
|
|
242
|
+
// Step 2: Atomic rename
|
|
243
|
+
await fs.rename(tmpPath, this.filePath);
|
|
244
|
+
// Step 3: Update mtime cache
|
|
245
|
+
const stats = await fs.stat(this.filePath);
|
|
246
|
+
this.lastModifiedTime = stats.mtimeMs;
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
// Clean up temp file if something went wrong
|
|
250
|
+
try {
|
|
251
|
+
await fs.unlink(tmpPath);
|
|
252
|
+
}
|
|
253
|
+
catch { }
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Reload database from disk
|
|
259
|
+
*/
|
|
260
|
+
async reload() {
|
|
261
|
+
return this.mutex.dispatch(async () => {
|
|
262
|
+
await this.load(true);
|
|
263
|
+
});
|
|
26
264
|
}
|
|
27
265
|
async addSnippet(snippet) {
|
|
28
266
|
return this.mutex.dispatch(async () => {
|
|
29
267
|
await this.load();
|
|
30
268
|
const newSnippet = {
|
|
31
269
|
...snippet,
|
|
32
|
-
id: Math.random().toString(36).substring(2, 9)
|
|
270
|
+
id: `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`,
|
|
33
271
|
updatedAt: new Date().toISOString()
|
|
34
272
|
};
|
|
35
273
|
this.db.snippets.push(newSnippet);
|
|
@@ -37,14 +275,106 @@ export class CodeDatabase {
|
|
|
37
275
|
return newSnippet;
|
|
38
276
|
});
|
|
39
277
|
}
|
|
40
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Enhanced search with relevance scoring and optional fuzzy matching
|
|
280
|
+
*/
|
|
281
|
+
async searchSnippets(query, options = {}) {
|
|
41
282
|
return this.mutex.dispatch(async () => {
|
|
42
283
|
await this.load();
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
284
|
+
const { fuzzy = true, threshold = 0.1, limit = 50, includeCode = true } = options;
|
|
285
|
+
if (!query.trim()) {
|
|
286
|
+
return this.db.snippets.slice(0, limit).map(snippet => ({
|
|
287
|
+
snippet,
|
|
288
|
+
relevance: 1
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
const results = [];
|
|
292
|
+
for (const snippet of this.db.snippets) {
|
|
293
|
+
let relevance = 0;
|
|
294
|
+
if (fuzzy) {
|
|
295
|
+
relevance = calculateRelevance(query, snippet, includeCode);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Simple exact match only
|
|
299
|
+
const q = query.toLowerCase();
|
|
300
|
+
relevance =
|
|
301
|
+
(snippet.title.toLowerCase().includes(q) ? 1 : 0) +
|
|
302
|
+
(snippet.description.toLowerCase().includes(q) ? 0.8 : 0) +
|
|
303
|
+
(snippet.tags.some(t => t.toLowerCase().includes(q)) ? 0.9 : 0) +
|
|
304
|
+
(includeCode && snippet.code.toLowerCase().includes(q) ? 0.5 : 0);
|
|
305
|
+
relevance = Math.min(relevance, 1);
|
|
306
|
+
}
|
|
307
|
+
if (relevance >= threshold) {
|
|
308
|
+
results.push({
|
|
309
|
+
snippet,
|
|
310
|
+
relevance
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Sort by relevance (highest first)
|
|
315
|
+
results.sort((a, b) => b.relevance - a.relevance);
|
|
316
|
+
return results.slice(0, limit);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get snippet by ID
|
|
321
|
+
*/
|
|
322
|
+
async getSnippet(id) {
|
|
323
|
+
return this.mutex.dispatch(async () => {
|
|
324
|
+
await this.load();
|
|
325
|
+
return this.db.snippets.find(s => s.id === id) || null;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Update snippet
|
|
330
|
+
*/
|
|
331
|
+
async updateSnippet(id, updates) {
|
|
332
|
+
return this.mutex.dispatch(async () => {
|
|
333
|
+
await this.load();
|
|
334
|
+
const index = this.db.snippets.findIndex(s => s.id === id);
|
|
335
|
+
if (index === -1)
|
|
336
|
+
return null;
|
|
337
|
+
this.db.snippets[index] = {
|
|
338
|
+
...this.db.snippets[index],
|
|
339
|
+
...updates,
|
|
340
|
+
updatedAt: new Date().toISOString()
|
|
341
|
+
};
|
|
342
|
+
await this.save();
|
|
343
|
+
return this.db.snippets[index];
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Delete snippet
|
|
348
|
+
*/
|
|
349
|
+
async deleteSnippet(id) {
|
|
350
|
+
return this.mutex.dispatch(async () => {
|
|
351
|
+
await this.load();
|
|
352
|
+
const initialLength = this.db.snippets.length;
|
|
353
|
+
this.db.snippets = this.db.snippets.filter(s => s.id !== id);
|
|
354
|
+
if (this.db.snippets.length !== initialLength) {
|
|
355
|
+
await this.save();
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* List all snippets with optional filtering
|
|
363
|
+
*/
|
|
364
|
+
async listSnippets(filter) {
|
|
365
|
+
return this.mutex.dispatch(async () => {
|
|
366
|
+
await this.load();
|
|
367
|
+
let results = this.db.snippets;
|
|
368
|
+
if (filter?.language) {
|
|
369
|
+
results = results.filter(s => s.language.toLowerCase() === filter.language.toLowerCase());
|
|
370
|
+
}
|
|
371
|
+
if (filter?.tags && filter.tags.length > 0) {
|
|
372
|
+
results = results.filter(s => filter.tags.some(t => s.tags.includes(t)));
|
|
373
|
+
}
|
|
374
|
+
if (filter?.limit) {
|
|
375
|
+
results = results.slice(0, filter.limit);
|
|
376
|
+
}
|
|
377
|
+
return results;
|
|
48
378
|
});
|
|
49
379
|
}
|
|
50
380
|
async learnPattern(name, description) {
|
|
@@ -63,7 +393,159 @@ export class CodeDatabase {
|
|
|
63
393
|
async listAllPatterns() {
|
|
64
394
|
return this.mutex.dispatch(async () => {
|
|
65
395
|
await this.load();
|
|
66
|
-
return this.db.patterns;
|
|
396
|
+
return { ...this.db.patterns };
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async deletePattern(name) {
|
|
400
|
+
return this.mutex.dispatch(async () => {
|
|
401
|
+
await this.load();
|
|
402
|
+
if (name in this.db.patterns) {
|
|
403
|
+
delete this.db.patterns[name];
|
|
404
|
+
await this.save();
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Search patterns with fuzzy matching
|
|
412
|
+
*/
|
|
413
|
+
async searchPatterns(query, limit = 20) {
|
|
414
|
+
return this.mutex.dispatch(async () => {
|
|
415
|
+
await this.load();
|
|
416
|
+
if (!query.trim()) {
|
|
417
|
+
return Object.entries(this.db.patterns)
|
|
418
|
+
.slice(0, limit)
|
|
419
|
+
.map(([name, description]) => ({ name, description, relevance: 1 }));
|
|
420
|
+
}
|
|
421
|
+
const q = query.toLowerCase();
|
|
422
|
+
const results = [];
|
|
423
|
+
for (const [name, description] of Object.entries(this.db.patterns)) {
|
|
424
|
+
let relevance = 0;
|
|
425
|
+
// Exact name match = highest priority
|
|
426
|
+
if (name.toLowerCase() === q) {
|
|
427
|
+
relevance = 1;
|
|
428
|
+
}
|
|
429
|
+
// Name contains query
|
|
430
|
+
else if (name.toLowerCase().includes(q)) {
|
|
431
|
+
relevance = 0.8;
|
|
432
|
+
}
|
|
433
|
+
// Description contains query
|
|
434
|
+
else if (description.toLowerCase().includes(q)) {
|
|
435
|
+
relevance = 0.6;
|
|
436
|
+
}
|
|
437
|
+
// Fuzzy match
|
|
438
|
+
else {
|
|
439
|
+
const nameScore = fuzzyMatch(q, name);
|
|
440
|
+
const descScore = fuzzyMatch(q, description);
|
|
441
|
+
relevance = Math.max(nameScore, descScore) * 0.5;
|
|
442
|
+
}
|
|
443
|
+
if (relevance > 0.1) {
|
|
444
|
+
results.push({ name, description, relevance });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
results.sort((a, b) => b.relevance - a.relevance);
|
|
448
|
+
return results.slice(0, limit);
|
|
67
449
|
});
|
|
68
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Get database statistics
|
|
453
|
+
*/
|
|
454
|
+
async getStats() {
|
|
455
|
+
return this.mutex.dispatch(async () => {
|
|
456
|
+
await this.load();
|
|
457
|
+
const languages = {};
|
|
458
|
+
const allTags = new Set();
|
|
459
|
+
for (const snippet of this.db.snippets) {
|
|
460
|
+
languages[snippet.language] = (languages[snippet.language] || 0) + 1;
|
|
461
|
+
snippet.tags.forEach(t => allTags.add(t));
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
snippetCount: this.db.snippets.length,
|
|
465
|
+
patternCount: Object.keys(this.db.patterns).length,
|
|
466
|
+
languages,
|
|
467
|
+
totalTags: Array.from(allTags),
|
|
468
|
+
lastModified: this.db.metadata?.lastModified || null
|
|
469
|
+
};
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Export database as JSON string
|
|
474
|
+
*/
|
|
475
|
+
async export() {
|
|
476
|
+
return this.mutex.dispatch(async () => {
|
|
477
|
+
await this.load();
|
|
478
|
+
return JSON.stringify({
|
|
479
|
+
version: '2.0',
|
|
480
|
+
snippets: this.db.snippets,
|
|
481
|
+
patterns: this.db.patterns,
|
|
482
|
+
exportedAt: new Date().toISOString()
|
|
483
|
+
}, null, 2);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Import snippets from JSON string
|
|
488
|
+
*/
|
|
489
|
+
async import(jsonData, merge = false) {
|
|
490
|
+
return this.mutex.dispatch(async () => {
|
|
491
|
+
await this.load();
|
|
492
|
+
try {
|
|
493
|
+
const parsed = JSON.parse(jsonData);
|
|
494
|
+
let importedSnippets = [];
|
|
495
|
+
if (parsed.version && parsed.snippets) {
|
|
496
|
+
importedSnippets = parsed.snippets;
|
|
497
|
+
}
|
|
498
|
+
else if (Array.isArray(parsed)) {
|
|
499
|
+
importedSnippets = parsed;
|
|
500
|
+
}
|
|
501
|
+
if (merge) {
|
|
502
|
+
// Merge: avoid duplicates by ID
|
|
503
|
+
const existingIds = new Set(this.db.snippets.map(s => s.id));
|
|
504
|
+
for (const snippet of importedSnippets) {
|
|
505
|
+
if (!existingIds.has(snippet.id)) {
|
|
506
|
+
this.db.snippets.push(snippet);
|
|
507
|
+
existingIds.add(snippet.id);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
// Replace all
|
|
513
|
+
this.db.snippets = importedSnippets;
|
|
514
|
+
}
|
|
515
|
+
// Import patterns too
|
|
516
|
+
if (parsed.patterns) {
|
|
517
|
+
if (merge) {
|
|
518
|
+
Object.assign(this.db.patterns, parsed.patterns);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
this.db.patterns = parsed.patterns;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
await this.save();
|
|
525
|
+
return this.db.snippets.length;
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
throw new Error(`Failed to import database: ${error}`);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Clear all data (use with caution!)
|
|
534
|
+
*/
|
|
535
|
+
async clearAll() {
|
|
536
|
+
return this.mutex.dispatch(async () => {
|
|
537
|
+
this.db = { version: '2.0', snippets: [], patterns: {} };
|
|
538
|
+
await this.save();
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Prepare embeddings for semantic search
|
|
543
|
+
* This is a placeholder for future integration with embedding models
|
|
544
|
+
* Call this after adding/updating snippets to enable semantic search
|
|
545
|
+
*/
|
|
546
|
+
async updateEmbeddings() {
|
|
547
|
+
// TODO: Integrate with an embedding service (e.g., OpenAI embeddings, local transformers)
|
|
548
|
+
// For now, this is a no-op placeholder
|
|
549
|
+
console.log('[CodeDatabase] Embedding update not yet implemented. Requires external embedding service.');
|
|
550
|
+
}
|
|
69
551
|
}
|