dude-claude-plugin 2026.2.5 → 2026.2.7
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/package.json +1 -1
- package/src/db.js +55 -46
- package/src/server.js +62 -32
package/package.json
CHANGED
package/src/db.js
CHANGED
|
@@ -32,6 +32,8 @@ function openDb() {
|
|
|
32
32
|
sqliteVec.load(d);
|
|
33
33
|
d.pragma('journal_mode = WAL');
|
|
34
34
|
d.pragma('foreign_keys = ON');
|
|
35
|
+
d.pragma('busy_timeout = 5000');
|
|
36
|
+
console.error(`[dude] Database opened: ${DB_PATH}`);
|
|
35
37
|
return d;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -156,9 +158,12 @@ export function listRecords({ kind, status, project } = {}) {
|
|
|
156
158
|
|
|
157
159
|
export function deleteRecord(id) {
|
|
158
160
|
const d = getDb();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
const tx = d.transaction(() => {
|
|
162
|
+
d.prepare('DELETE FROM record_embedding WHERE record_id = ?').run(id);
|
|
163
|
+
const result = d.prepare('DELETE FROM record WHERE id = ?').run(id);
|
|
164
|
+
return result.changes > 0;
|
|
165
|
+
});
|
|
166
|
+
return tx();
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
export function searchRecords(embedding, { kind, projectId, limit = 5 } = {}) {
|
|
@@ -213,53 +218,57 @@ export function upsertRecord({ id, projectId, kind, title, body = '', status = '
|
|
|
213
218
|
const proj = projectId ?? getCurrentProject().id;
|
|
214
219
|
const now = new Date().toISOString();
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
const tx = d.transaction(() => {
|
|
222
|
+
if (id) {
|
|
223
|
+
// Explicit update
|
|
224
|
+
d.prepare(`
|
|
225
|
+
UPDATE record SET kind = ?, title = ?, body = ?, status = ?, updated_at = ?
|
|
226
|
+
WHERE id = ?
|
|
227
|
+
`).run(kind, title, body, status, now, id);
|
|
222
228
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
// vec0 doesn't support UPDATE — delete then insert
|
|
230
|
+
d.prepare('DELETE FROM record_embedding WHERE record_id = ?').run(id);
|
|
231
|
+
d.prepare('INSERT INTO record_embedding (record_id, embedding) VALUES (?, ?)').run(BigInt(id), embeddingBuffer(embedding));
|
|
226
232
|
|
|
227
|
-
|
|
228
|
-
|
|
233
|
+
return getRecord(id);
|
|
234
|
+
}
|
|
229
235
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
236
|
+
// Dedup check: look for close matches in same project+kind
|
|
237
|
+
const candidates = d.prepare(`
|
|
238
|
+
SELECT re.record_id, re.distance
|
|
239
|
+
FROM record_embedding re
|
|
240
|
+
JOIN record r ON r.id = re.record_id
|
|
241
|
+
WHERE re.embedding MATCH ? AND k = 5
|
|
242
|
+
AND r.project_id = ? AND r.kind = ?
|
|
243
|
+
ORDER BY re.distance
|
|
244
|
+
LIMIT 1
|
|
245
|
+
`).all(embeddingBuffer(embedding), proj, kind);
|
|
246
|
+
|
|
247
|
+
if (candidates.length > 0 && candidates[0].distance <= 0.15) {
|
|
248
|
+
// Close match found — update existing record
|
|
249
|
+
const existingId = candidates[0].record_id;
|
|
250
|
+
d.prepare(`
|
|
251
|
+
UPDATE record SET title = ?, body = ?, status = ?, updated_at = ?
|
|
252
|
+
WHERE id = ?
|
|
253
|
+
`).run(title, body, status, now, existingId);
|
|
254
|
+
|
|
255
|
+
d.prepare('DELETE FROM record_embedding WHERE record_id = ?').run(existingId);
|
|
256
|
+
d.prepare('INSERT INTO record_embedding (record_id, embedding) VALUES (?, ?)').run(BigInt(existingId), embeddingBuffer(embedding));
|
|
257
|
+
|
|
258
|
+
return getRecord(existingId);
|
|
259
|
+
}
|
|
254
260
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
// Insert new record
|
|
262
|
+
const result = d.prepare(`
|
|
263
|
+
INSERT INTO record (project_id, kind, title, body, status, created_at, updated_at)
|
|
264
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
265
|
+
`).run(proj, kind, title, body, status, now, now);
|
|
260
266
|
|
|
261
|
-
|
|
262
|
-
|
|
267
|
+
const newId = result.lastInsertRowid;
|
|
268
|
+
d.prepare('INSERT INTO record_embedding (record_id, embedding) VALUES (?, ?)').run(BigInt(newId), embeddingBuffer(embedding));
|
|
269
|
+
|
|
270
|
+
return getRecord(Number(newId));
|
|
271
|
+
});
|
|
263
272
|
|
|
264
|
-
return
|
|
273
|
+
return tx();
|
|
265
274
|
}
|
package/src/server.js
CHANGED
|
@@ -32,11 +32,16 @@ export async function startServer() {
|
|
|
32
32
|
limit: z.number().int().positive().optional().describe('Max results (default 5)'),
|
|
33
33
|
},
|
|
34
34
|
async ({ query, kind, project, limit }) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
try {
|
|
36
|
+
const embedding = await embed(query);
|
|
37
|
+
const results = searchRecords(embedding, { kind, limit });
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
40
|
+
};
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error('[dude] search failed:', err);
|
|
43
|
+
return { content: [{ type: 'text', text: `Error in search: ${err.message}` }], isError: true };
|
|
44
|
+
}
|
|
40
45
|
},
|
|
41
46
|
);
|
|
42
47
|
|
|
@@ -52,15 +57,20 @@ export async function startServer() {
|
|
|
52
57
|
status: z.enum(['open', 'resolved', 'archived']).optional().describe('Defaults to open'),
|
|
53
58
|
},
|
|
54
59
|
async ({ id, kind, title, body, status }) => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
try {
|
|
61
|
+
const text = `${title} ${body || ''}`.trim();
|
|
62
|
+
const embedding = await embed(text);
|
|
63
|
+
const record = upsertRecord(
|
|
64
|
+
{ id, projectId: getCurrentProject().id, kind, title, body: body || '', status: status || 'open' },
|
|
65
|
+
embedding,
|
|
66
|
+
);
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],
|
|
69
|
+
};
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error('[dude] upsert_record failed:', err);
|
|
72
|
+
return { content: [{ type: 'text', text: `Error in upsert_record: ${err.message}` }], isError: true };
|
|
73
|
+
}
|
|
64
74
|
},
|
|
65
75
|
);
|
|
66
76
|
|
|
@@ -72,13 +82,18 @@ export async function startServer() {
|
|
|
72
82
|
id: z.number().int().describe('Record ID'),
|
|
73
83
|
},
|
|
74
84
|
async ({ id }) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
try {
|
|
86
|
+
const record = getRecord(id);
|
|
87
|
+
if (!record) {
|
|
88
|
+
return { content: [{ type: 'text', text: `Record ${id} not found.` }], isError: true };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],
|
|
92
|
+
};
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error('[dude] get_record failed:', err);
|
|
95
|
+
return { content: [{ type: 'text', text: `Error in get_record: ${err.message}` }], isError: true };
|
|
78
96
|
}
|
|
79
|
-
return {
|
|
80
|
-
content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],
|
|
81
|
-
};
|
|
82
97
|
},
|
|
83
98
|
);
|
|
84
99
|
|
|
@@ -92,10 +107,15 @@ export async function startServer() {
|
|
|
92
107
|
project: z.string().optional().describe('Project name, or "*" for all'),
|
|
93
108
|
},
|
|
94
109
|
async ({ kind, status, project }) => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
try {
|
|
111
|
+
const records = listRecords({ kind, status, project });
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: JSON.stringify(records, null, 2) }],
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('[dude] list_records failed:', err);
|
|
117
|
+
return { content: [{ type: 'text', text: `Error in list_records: ${err.message}` }], isError: true };
|
|
118
|
+
}
|
|
99
119
|
},
|
|
100
120
|
);
|
|
101
121
|
|
|
@@ -107,10 +127,15 @@ export async function startServer() {
|
|
|
107
127
|
id: z.number().int().describe('Record ID to delete'),
|
|
108
128
|
},
|
|
109
129
|
async ({ id }) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
130
|
+
try {
|
|
131
|
+
const deleted = deleteRecord(id);
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: 'text', text: deleted ? `Record ${id} deleted.` : `Record ${id} not found.` }],
|
|
134
|
+
};
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error('[dude] delete_record failed:', err);
|
|
137
|
+
return { content: [{ type: 'text', text: `Error in delete_record: ${err.message}` }], isError: true };
|
|
138
|
+
}
|
|
114
139
|
},
|
|
115
140
|
);
|
|
116
141
|
|
|
@@ -120,10 +145,15 @@ export async function startServer() {
|
|
|
120
145
|
'List all known projects.',
|
|
121
146
|
{},
|
|
122
147
|
async () => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
try {
|
|
149
|
+
const projects = listProjects();
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }],
|
|
152
|
+
};
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error('[dude] list_projects failed:', err);
|
|
155
|
+
return { content: [{ type: 'text', text: `Error in list_projects: ${err.message}` }], isError: true };
|
|
156
|
+
}
|
|
127
157
|
},
|
|
128
158
|
);
|
|
129
159
|
|