@devflow-tools/database 0.18.3 → 0.18.5
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/database.d.ts +15 -5
- package/dist/database.js +118 -30
- package/package.json +2 -2
package/dist/database.d.ts
CHANGED
|
@@ -198,6 +198,15 @@ export declare class DevFlowDatabase {
|
|
|
198
198
|
getRunSteps(runId: string): any[];
|
|
199
199
|
insertEvent(event: any): void;
|
|
200
200
|
getEvents(runId: string): any[];
|
|
201
|
+
appendDashboardRunEvent(event: {
|
|
202
|
+
runId: string;
|
|
203
|
+
projectRoot: string;
|
|
204
|
+
sequence: number;
|
|
205
|
+
kind: string;
|
|
206
|
+
payload?: unknown;
|
|
207
|
+
timestamp?: number;
|
|
208
|
+
}): void;
|
|
209
|
+
listDashboardRunEvents(runId: string, projectRoot: string, after?: number): any[];
|
|
201
210
|
getProjectStats(projectRoot: string): any | null;
|
|
202
211
|
upsertProjectStats(stats: any): void;
|
|
203
212
|
getRuns(): any[];
|
|
@@ -256,7 +265,7 @@ export declare class DevFlowDatabase {
|
|
|
256
265
|
metadata?: Record<string, unknown>;
|
|
257
266
|
}): void;
|
|
258
267
|
mergeSkillExecutionMetadata(executionId: string, incoming: Record<string, unknown>): Record<string, unknown> | null;
|
|
259
|
-
listSkillExecutions(limit: number, skillName?: string): any[];
|
|
268
|
+
listSkillExecutions(limit: number, skillName?: string, projectRoot?: string): any[];
|
|
260
269
|
insertToolCallEvent(params: {
|
|
261
270
|
eventId: string;
|
|
262
271
|
executionId: string;
|
|
@@ -299,7 +308,7 @@ export declare class DevFlowDatabase {
|
|
|
299
308
|
missedTools: string[];
|
|
300
309
|
mcpCallShare: number;
|
|
301
310
|
};
|
|
302
|
-
getMcpCompliance(): {
|
|
311
|
+
getMcpCompliance(projectRoot?: string): {
|
|
303
312
|
overall: number;
|
|
304
313
|
bySkill: Record<string, number>;
|
|
305
314
|
missedTools: string[];
|
|
@@ -323,8 +332,8 @@ export declare class DevFlowDatabase {
|
|
|
323
332
|
metadata?: string;
|
|
324
333
|
createdAt: number;
|
|
325
334
|
}): void;
|
|
326
|
-
getToolMetrics(sessionId?: string, toolName?: string, limit?: number, offset?: number): any[];
|
|
327
|
-
getToolMetricsSummary(days?: number): any[];
|
|
335
|
+
getToolMetrics(sessionId?: string, toolName?: string, limit?: number, offset?: number, projectRoot?: string): any[];
|
|
336
|
+
getToolMetricsSummary(days?: number, projectRoot?: string): any[];
|
|
328
337
|
aggregatePendingToolMetrics(limit?: number): number;
|
|
329
338
|
insertFeedback(feedback: FeedbackRecord): void;
|
|
330
339
|
listFeedback(projectRoot?: string, limit?: number): FeedbackRecord[];
|
|
@@ -357,12 +366,13 @@ export declare class DevFlowDatabase {
|
|
|
357
366
|
status?: string;
|
|
358
367
|
limit?: number;
|
|
359
368
|
offset?: number;
|
|
369
|
+
projectRoot?: string;
|
|
360
370
|
}): any[];
|
|
361
371
|
updateAccuracyFeedback(id: string, feedback: {
|
|
362
372
|
relevance: 'hit' | 'partial' | 'miss';
|
|
363
373
|
note?: string;
|
|
364
374
|
}): void;
|
|
365
|
-
getAccuracyStats(engine?: string, since?: number): any;
|
|
375
|
+
getAccuracyStats(engine?: string, since?: number, projectRoot?: string): any;
|
|
366
376
|
insertBenchmarkReport<T extends BenchmarkReportRecord>(report: T): void;
|
|
367
377
|
getBenchmarkReport<T extends BenchmarkReportRecord = BenchmarkReportRecord>(runId: string): T | null;
|
|
368
378
|
getLatestBenchmarkReport<T extends BenchmarkReportRecord = BenchmarkReportRecord>(suiteId?: string): T | null;
|
package/dist/database.js
CHANGED
|
@@ -159,6 +159,19 @@ class DevFlowDatabase {
|
|
|
159
159
|
FOREIGN KEY (run_id) REFERENCES runs(id)
|
|
160
160
|
);
|
|
161
161
|
|
|
162
|
+
CREATE TABLE IF NOT EXISTS dashboard_run_events (
|
|
163
|
+
run_id TEXT NOT NULL,
|
|
164
|
+
project_root TEXT NOT NULL,
|
|
165
|
+
sequence INTEGER NOT NULL,
|
|
166
|
+
kind TEXT NOT NULL,
|
|
167
|
+
payload TEXT,
|
|
168
|
+
timestamp INTEGER NOT NULL,
|
|
169
|
+
PRIMARY KEY (project_root, run_id, sequence)
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
CREATE INDEX IF NOT EXISTS idx_dashboard_run_events_project
|
|
173
|
+
ON dashboard_run_events(project_root, run_id, sequence);
|
|
174
|
+
|
|
162
175
|
CREATE TABLE IF NOT EXISTS project_stats (
|
|
163
176
|
project_root TEXT PRIMARY KEY,
|
|
164
177
|
total_runs INTEGER DEFAULT 0,
|
|
@@ -1077,6 +1090,34 @@ class DevFlowDatabase {
|
|
|
1077
1090
|
CREATE INDEX IF NOT EXISTS idx_merge_queue_project
|
|
1078
1091
|
ON devflow_merge_queue(project_root, state, created_at, merge_id);
|
|
1079
1092
|
`);
|
|
1093
|
+
// Migration: dashboard run event cursors originally keyed only by run/sequence.
|
|
1094
|
+
// Rebuild the small append-only table so project isolation is part of the key.
|
|
1095
|
+
try {
|
|
1096
|
+
const columns = this.db.prepare('PRAGMA table_info(dashboard_run_events)').all();
|
|
1097
|
+
if (columns.length > 0 && columns.some((column) => column.name === 'project_root')) {
|
|
1098
|
+
const primaryKey = this.db.prepare('PRAGMA table_info(dashboard_run_events)').all();
|
|
1099
|
+
if (primaryKey.filter((column) => column.pk > 0).map((column) => column.name).join(',') === 'run_id,sequence') {
|
|
1100
|
+
this.db.exec('ALTER TABLE dashboard_run_events RENAME TO dashboard_run_events_legacy');
|
|
1101
|
+
this.db.exec(`
|
|
1102
|
+
CREATE TABLE dashboard_run_events (
|
|
1103
|
+
run_id TEXT NOT NULL,
|
|
1104
|
+
project_root TEXT NOT NULL,
|
|
1105
|
+
sequence INTEGER NOT NULL,
|
|
1106
|
+
kind TEXT NOT NULL,
|
|
1107
|
+
payload TEXT,
|
|
1108
|
+
timestamp INTEGER NOT NULL,
|
|
1109
|
+
PRIMARY KEY (project_root, run_id, sequence)
|
|
1110
|
+
);
|
|
1111
|
+
INSERT OR IGNORE INTO dashboard_run_events (run_id, project_root, sequence, kind, payload, timestamp)
|
|
1112
|
+
SELECT run_id, project_root, sequence, kind, payload, timestamp FROM dashboard_run_events_legacy;
|
|
1113
|
+
DROP TABLE dashboard_run_events_legacy;
|
|
1114
|
+
CREATE INDEX IF NOT EXISTS idx_dashboard_run_events_project
|
|
1115
|
+
ON dashboard_run_events(project_root, run_id, sequence);
|
|
1116
|
+
`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
catch { }
|
|
1080
1121
|
// Migration: add session_id to skill_executions (SQLite compat — ignore if exists)
|
|
1081
1122
|
try {
|
|
1082
1123
|
this.db.exec('ALTER TABLE skill_executions ADD COLUMN session_id TEXT REFERENCES sessions(id)');
|
|
@@ -1414,6 +1455,28 @@ class DevFlowDatabase {
|
|
|
1414
1455
|
metadata: row.metadata ? JSON.parse(row.metadata) : null
|
|
1415
1456
|
}));
|
|
1416
1457
|
}
|
|
1458
|
+
appendDashboardRunEvent(event) {
|
|
1459
|
+
this.db.prepare(`
|
|
1460
|
+
INSERT OR IGNORE INTO dashboard_run_events (run_id, project_root, sequence, kind, payload, timestamp)
|
|
1461
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1462
|
+
`).run(event.runId, event.projectRoot, event.sequence, event.kind, event.payload === undefined ? null : JSON.stringify(event.payload), event.timestamp ?? Date.now());
|
|
1463
|
+
}
|
|
1464
|
+
listDashboardRunEvents(runId, projectRoot, after = 0) {
|
|
1465
|
+
const rows = this.db.prepare(`
|
|
1466
|
+
SELECT run_id, project_root, sequence, kind, payload, timestamp
|
|
1467
|
+
FROM dashboard_run_events
|
|
1468
|
+
WHERE run_id = ? AND project_root = ? AND sequence > ?
|
|
1469
|
+
ORDER BY sequence ASC
|
|
1470
|
+
`).all(runId, projectRoot, after);
|
|
1471
|
+
return rows.map((row) => ({
|
|
1472
|
+
runId: row.run_id,
|
|
1473
|
+
projectRoot: row.project_root,
|
|
1474
|
+
sequence: row.sequence,
|
|
1475
|
+
kind: row.kind,
|
|
1476
|
+
payload: row.payload ? JSON.parse(row.payload) : null,
|
|
1477
|
+
timestamp: row.timestamp,
|
|
1478
|
+
}));
|
|
1479
|
+
}
|
|
1417
1480
|
getProjectStats(projectRoot) {
|
|
1418
1481
|
const row = this.db.prepare('SELECT * FROM project_stats WHERE project_root = ?').get(projectRoot);
|
|
1419
1482
|
if (!row)
|
|
@@ -1759,14 +1822,21 @@ class DevFlowDatabase {
|
|
|
1759
1822
|
this.updateSkillExecution(executionId, { metadata: merged });
|
|
1760
1823
|
return merged;
|
|
1761
1824
|
}
|
|
1762
|
-
listSkillExecutions(limit, skillName) {
|
|
1763
|
-
let query = 'SELECT
|
|
1825
|
+
listSkillExecutions(limit, skillName, projectRoot) {
|
|
1826
|
+
let query = 'SELECT e.* FROM skill_executions e LEFT JOIN sessions s ON s.id = e.session_id';
|
|
1764
1827
|
const params = [];
|
|
1828
|
+
const conditions = [];
|
|
1765
1829
|
if (skillName) {
|
|
1766
|
-
|
|
1830
|
+
conditions.push('e.skill_name = ?');
|
|
1767
1831
|
params.push(skillName);
|
|
1768
1832
|
}
|
|
1769
|
-
|
|
1833
|
+
if (projectRoot) {
|
|
1834
|
+
conditions.push('s.project_root = ?');
|
|
1835
|
+
params.push(projectRoot);
|
|
1836
|
+
}
|
|
1837
|
+
if (conditions.length)
|
|
1838
|
+
query += ` WHERE ${conditions.join(' AND ')}`;
|
|
1839
|
+
query += ' ORDER BY e.started_at DESC LIMIT ?';
|
|
1770
1840
|
params.push(Number(limit));
|
|
1771
1841
|
const rows = this.db.prepare(query).all(...params);
|
|
1772
1842
|
return rows.map(row => ({
|
|
@@ -2038,8 +2108,10 @@ class DevFlowDatabase {
|
|
|
2038
2108
|
};
|
|
2039
2109
|
}
|
|
2040
2110
|
// ---- MCP Compliance ----
|
|
2041
|
-
getMcpCompliance() {
|
|
2042
|
-
const executions = this.db.prepare(
|
|
2111
|
+
getMcpCompliance(projectRoot) {
|
|
2112
|
+
const executions = this.db.prepare(projectRoot
|
|
2113
|
+
? 'SELECT e.execution_id, e.skill_name FROM skill_executions e JOIN sessions s ON s.id = e.session_id WHERE s.project_root = ?'
|
|
2114
|
+
: 'SELECT execution_id, skill_name FROM skill_executions').all(...(projectRoot ? [projectRoot] : []));
|
|
2043
2115
|
const bySkill = {};
|
|
2044
2116
|
const bySkillCounts = new Map();
|
|
2045
2117
|
let applicableObligations = 0;
|
|
@@ -2093,34 +2165,41 @@ class DevFlowDatabase {
|
|
|
2093
2165
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2094
2166
|
`).run(metric.id, metric.toolCallEventId, metric.sessionId, metric.toolName, metric.query ?? null, metric.status ?? 'success', metric.resultCount ?? 0, metric.resultSizeBytes ?? 0, metric.latencyMs ?? 0, metric.engine ?? null, metric.accuracyQueryId ?? null, metric.metadata ?? null, metric.createdAt);
|
|
2095
2167
|
}
|
|
2096
|
-
getToolMetrics(sessionId, toolName, limit = 100, offset = 0) {
|
|
2097
|
-
let sql = 'SELECT
|
|
2168
|
+
getToolMetrics(sessionId, toolName, limit = 100, offset = 0, projectRoot) {
|
|
2169
|
+
let sql = 'SELECT tm.* FROM tool_metrics tm LEFT JOIN sessions s ON s.id = tm.session_id WHERE 1=1';
|
|
2098
2170
|
const params = [];
|
|
2099
2171
|
if (sessionId) {
|
|
2100
|
-
sql += ' AND session_id = ?';
|
|
2172
|
+
sql += ' AND tm.session_id = ?';
|
|
2101
2173
|
params.push(sessionId);
|
|
2102
2174
|
}
|
|
2103
2175
|
if (toolName) {
|
|
2104
|
-
sql += ' AND tool_name = ?';
|
|
2176
|
+
sql += ' AND tm.tool_name = ?';
|
|
2105
2177
|
params.push(toolName);
|
|
2106
2178
|
}
|
|
2107
|
-
|
|
2179
|
+
if (projectRoot) {
|
|
2180
|
+
sql += ' AND s.project_root = ?';
|
|
2181
|
+
params.push(projectRoot);
|
|
2182
|
+
}
|
|
2183
|
+
sql += ' ORDER BY tm.created_at DESC LIMIT ? OFFSET ?';
|
|
2108
2184
|
params.push(limit, offset);
|
|
2109
2185
|
return this.db.prepare(sql).all(...params);
|
|
2110
2186
|
}
|
|
2111
|
-
getToolMetricsSummary(days = 7) {
|
|
2187
|
+
getToolMetricsSummary(days = 7, projectRoot) {
|
|
2112
2188
|
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
2189
|
+
const projectClause = projectRoot ? ' AND s.project_root = ?' : '';
|
|
2190
|
+
const params = projectRoot ? [cutoff, projectRoot] : [cutoff];
|
|
2113
2191
|
const rows = this.db.prepare(`
|
|
2114
|
-
SELECT tool_name, COUNT(*) as call_count,
|
|
2115
|
-
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success_count,
|
|
2116
|
-
SUM(CASE WHEN result_count = 0 THEN 1 ELSE 0 END) as empty_count,
|
|
2117
|
-
AVG(result_count) as avg_results,
|
|
2118
|
-
AVG(latency_ms) as avg_latency
|
|
2119
|
-
FROM tool_metrics
|
|
2120
|
-
|
|
2121
|
-
|
|
2192
|
+
SELECT tm.tool_name, COUNT(*) as call_count,
|
|
2193
|
+
SUM(CASE WHEN tm.status = 'success' THEN 1 ELSE 0 END) as success_count,
|
|
2194
|
+
SUM(CASE WHEN tm.result_count = 0 THEN 1 ELSE 0 END) as empty_count,
|
|
2195
|
+
AVG(tm.result_count) as avg_results,
|
|
2196
|
+
AVG(tm.latency_ms) as avg_latency
|
|
2197
|
+
FROM tool_metrics tm
|
|
2198
|
+
LEFT JOIN sessions s ON s.id = tm.session_id
|
|
2199
|
+
WHERE tm.created_at >= ?${projectClause}
|
|
2200
|
+
GROUP BY tm.tool_name
|
|
2122
2201
|
ORDER BY call_count DESC
|
|
2123
|
-
`).all(
|
|
2202
|
+
`).all(...params);
|
|
2124
2203
|
return rows.map((r) => ({
|
|
2125
2204
|
toolName: r.tool_name,
|
|
2126
2205
|
callCount: r.call_count,
|
|
@@ -2247,17 +2326,21 @@ class DevFlowDatabase {
|
|
|
2247
2326
|
`).run(q.id, q.sessionId, q.engine, q.query, q.topKResults, q.selectedIds ?? null, q.createdAt);
|
|
2248
2327
|
}
|
|
2249
2328
|
getAccuracyQueries(options) {
|
|
2250
|
-
let sql = 'SELECT
|
|
2329
|
+
let sql = 'SELECT aq.* FROM accuracy_queries aq LEFT JOIN sessions s ON s.id = aq.session_id WHERE 1=1';
|
|
2251
2330
|
const params = [];
|
|
2252
2331
|
if (options.engine) {
|
|
2253
|
-
sql += ' AND engine = ?';
|
|
2332
|
+
sql += ' AND aq.engine = ?';
|
|
2254
2333
|
params.push(options.engine);
|
|
2255
2334
|
}
|
|
2256
2335
|
if (options.status === 'annotated')
|
|
2257
|
-
sql += ' AND relevance_feedback IS NOT NULL';
|
|
2336
|
+
sql += ' AND aq.relevance_feedback IS NOT NULL';
|
|
2258
2337
|
if (options.status === 'unannotated')
|
|
2259
|
-
sql += ' AND relevance_feedback IS NULL';
|
|
2260
|
-
|
|
2338
|
+
sql += ' AND aq.relevance_feedback IS NULL';
|
|
2339
|
+
if (options.projectRoot) {
|
|
2340
|
+
sql += ' AND s.project_root = ?';
|
|
2341
|
+
params.push(options.projectRoot);
|
|
2342
|
+
}
|
|
2343
|
+
sql += ' ORDER BY aq.created_at DESC LIMIT ? OFFSET ?';
|
|
2261
2344
|
params.push(options.limit ?? 50, options.offset ?? 0);
|
|
2262
2345
|
const rows = this.db.prepare(sql).all(...params);
|
|
2263
2346
|
return rows.map((r) => ({
|
|
@@ -2273,24 +2356,29 @@ class DevFlowDatabase {
|
|
|
2273
2356
|
updateAccuracyFeedback(id, feedback) {
|
|
2274
2357
|
this.db.prepare('UPDATE accuracy_queries SET relevance_feedback = ?, annotator_note = ?, annotated_at = ? WHERE id = ?').run(feedback.relevance, feedback.note ?? null, Date.now(), id);
|
|
2275
2358
|
}
|
|
2276
|
-
getAccuracyStats(engine, since) {
|
|
2359
|
+
getAccuracyStats(engine, since, projectRoot) {
|
|
2277
2360
|
let where = 'WHERE 1=1';
|
|
2278
2361
|
const params = [];
|
|
2279
2362
|
if (engine) {
|
|
2280
|
-
where += ' AND engine = ?';
|
|
2363
|
+
where += ' AND aq.engine = ?';
|
|
2281
2364
|
params.push(engine);
|
|
2282
2365
|
}
|
|
2283
2366
|
if (since) {
|
|
2284
|
-
where += ' AND created_at >= ?';
|
|
2367
|
+
where += ' AND aq.created_at >= ?';
|
|
2285
2368
|
params.push(since);
|
|
2286
2369
|
}
|
|
2370
|
+
if (projectRoot) {
|
|
2371
|
+
where += ' AND s.project_root = ?';
|
|
2372
|
+
params.push(projectRoot);
|
|
2373
|
+
}
|
|
2287
2374
|
const rows = this.db.prepare(`
|
|
2288
2375
|
SELECT
|
|
2289
2376
|
engine,
|
|
2290
2377
|
COUNT(*) as totalQueries,
|
|
2291
2378
|
SUM(CASE WHEN relevance_feedback IS NOT NULL THEN 1 ELSE 0 END) as annotatedQueries,
|
|
2292
2379
|
SUM(CASE WHEN relevance_feedback = 'hit' THEN 1 ELSE 0 END) as hitCount
|
|
2293
|
-
FROM accuracy_queries
|
|
2380
|
+
FROM accuracy_queries aq
|
|
2381
|
+
LEFT JOIN sessions s ON s.id = aq.session_id ${where}
|
|
2294
2382
|
GROUP BY engine
|
|
2295
2383
|
`).all(...params);
|
|
2296
2384
|
const statsByEngine = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devflow-tools/database",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.5",
|
|
4
4
|
"description": "SQLite persistence primitives shared by DevFlow runtime packages.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"test": "vitest run"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@devflow-tools/sdk": "0.18.
|
|
15
|
+
"@devflow-tools/sdk": "0.18.5"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^22.15.0",
|