@devflow-tools/database 0.15.0 → 0.16.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.
- package/CHANGELOG.md +21 -0
- package/__tests__/database.failure-category.test.ts +44 -0
- package/__tests__/database.test.ts +30 -0
- package/__tests__/node-sqlite.test.ts +8 -0
- package/dist/database.d.ts +121 -2
- package/dist/database.js +509 -23
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/node-sqlite.d.ts +1 -1
- package/dist/node-sqlite.js +8 -5
- package/package.json +2 -2
- package/src/database.ts +715 -52
- package/src/index.ts +15 -1
- package/src/node-sqlite.ts +9 -5
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [0.16.0](https://github.com/shilongfeicool/dev-flow/compare/v0.15.0...v0.16.0) (2026-07-22)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **batch2:** close final telemetry and migration races ([7340608](https://github.com/shilongfeicool/dev-flow/commit/7340608419e6cb476ed44662728f1dfe8466f4c9))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **batch4:** audit mcp surface health ([1b60d74](https://github.com/shilongfeicool/dev-flow/commit/1b60d74c8da23964743a82097244f585532871cb))
|
|
17
|
+
* **batch4:** consolidate hook lifecycle state ([803b221](https://github.com/shilongfeicool/dev-flow/commit/803b2219d5d73f3ce322f572ba625bb321f3b126))
|
|
18
|
+
* **batch4:** establish benchmark baseline pipeline ([8741192](https://github.com/shilongfeicool/dev-flow/commit/8741192f96280962160619fcdb0c47bf94315862))
|
|
19
|
+
* **batch4:** persist enforcer governance ([2ecf668](https://github.com/shilongfeicool/dev-flow/commit/2ecf668fb0493a1a649c4d16cc17f021d6f70031))
|
|
20
|
+
* **batch4:** wire workflow failure attribution ([682d12b](https://github.com/shilongfeicool/dev-flow/commit/682d12ba715d4003af33ad38610b5655f1b49d8a))
|
|
21
|
+
* **telemetry:** make global sqlite the primary writer ([751eb5b](https://github.com/shilongfeicool/dev-flow/commit/751eb5b48889220991af5016b5fe3e4d176f4770))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
6
27
|
# [0.15.0](https://github.com/shilongfeicool/dev-flow/compare/v0.14.4...v0.15.0) (2026-07-16)
|
|
7
28
|
|
|
8
29
|
**Note:** Version bump only for package @devflow-tools/database
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { DevFlowDatabase } from "../src/database.js";
|
|
6
|
+
|
|
7
|
+
describe("tool failure categories", () => {
|
|
8
|
+
let root: string;
|
|
9
|
+
let database: DevFlowDatabase;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
root = mkdtempSync(join(tmpdir(), "devflow-failure-category-"));
|
|
13
|
+
database = new DevFlowDatabase(root);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
database.close();
|
|
18
|
+
rmSync(root, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("persists and returns failure_category", () => {
|
|
22
|
+
database.insertToolCallEvent({
|
|
23
|
+
eventId: "event-1",
|
|
24
|
+
executionId: "execution-1",
|
|
25
|
+
sessionId: "session-1",
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
toolName: "Bash",
|
|
28
|
+
toolType: "direct",
|
|
29
|
+
isMcpTool: false,
|
|
30
|
+
mcpEnforced: false,
|
|
31
|
+
mcpFallback: false,
|
|
32
|
+
input: { command: "false" },
|
|
33
|
+
duration: 1,
|
|
34
|
+
error: "exit 1",
|
|
35
|
+
blocked: false,
|
|
36
|
+
failureCategory: "tool_error",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(database.listToolCallEvents("execution-1")[0])
|
|
40
|
+
.toMatchObject({ failureCategory: "tool_error", error: "exit 1" });
|
|
41
|
+
expect(database.listToolCallEventsBySession("session-1")[0])
|
|
42
|
+
.toMatchObject({ failureCategory: "tool_error" });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
|
2
2
|
import { DevFlowDatabase } from '../src/database';
|
|
3
3
|
import { rmSync, existsSync } from 'fs';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
+
import type { BenchmarkSuiteReport } from '@devflow-tools/sdk';
|
|
5
6
|
|
|
6
7
|
describe('DevFlowDatabase', () => {
|
|
7
8
|
const testDir = join(process.cwd(), '.devflow-test');
|
|
@@ -144,4 +145,33 @@ describe('DevFlowDatabase', () => {
|
|
|
144
145
|
expect(retrieved).toBeDefined();
|
|
145
146
|
expect(retrieved?.totalRuns).toBe(10);
|
|
146
147
|
});
|
|
148
|
+
|
|
149
|
+
it('should persist and list canonical benchmark reports', () => {
|
|
150
|
+
const report = {
|
|
151
|
+
runId: 'benchmark:test',
|
|
152
|
+
suiteId: 'default',
|
|
153
|
+
suiteName: 'Default',
|
|
154
|
+
suiteVersion: '1',
|
|
155
|
+
projectRoot: testDir,
|
|
156
|
+
commit: null,
|
|
157
|
+
createdAt: Date.now(),
|
|
158
|
+
rounds: 1,
|
|
159
|
+
status: 'completed',
|
|
160
|
+
tasks: [],
|
|
161
|
+
summary: {
|
|
162
|
+
baseline: { avgTokensUsed: 10, avgFilesTouched: 2, avgCorrectnessScore: 0.5, avgDurationMs: 5 },
|
|
163
|
+
devflow: { avgTokensUsed: 5, avgFilesTouched: 1, avgCorrectnessScore: 1, avgDurationMs: 4 },
|
|
164
|
+
delta: { tokenReductionRate: 0.5, fileReductionRate: 0.5, correctnessLift: 0.5, timeSavedRate: 0.2 },
|
|
165
|
+
},
|
|
166
|
+
regressions: [],
|
|
167
|
+
} satisfies BenchmarkSuiteReport;
|
|
168
|
+
|
|
169
|
+
db.insertBenchmarkReport(report);
|
|
170
|
+
|
|
171
|
+
expect(db.getBenchmarkReport<BenchmarkSuiteReport>(report.runId)).toEqual(report);
|
|
172
|
+
expect(db.getLatestBenchmarkReport<BenchmarkSuiteReport>('default')).toEqual(report);
|
|
173
|
+
expect(db.listBenchmarkReports()).toEqual([
|
|
174
|
+
expect.objectContaining({ runId: report.runId, suiteId: 'default' }),
|
|
175
|
+
]);
|
|
176
|
+
});
|
|
147
177
|
});
|
|
@@ -46,6 +46,14 @@ describe('NodeSqliteDatabase', () => {
|
|
|
46
46
|
expect(rows).toHaveLength(2);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it('normalizes boolean bind parameters for node:sqlite', () => {
|
|
50
|
+
db.exec('CREATE TABLE test (success INTEGER NOT NULL)');
|
|
51
|
+
db.prepare('INSERT INTO test (success) VALUES (?)').run(true);
|
|
52
|
+
|
|
53
|
+
expect(db.prepare('SELECT success FROM test WHERE success = ?').get(true))
|
|
54
|
+
.toMatchObject({ success: 1 });
|
|
55
|
+
});
|
|
56
|
+
|
|
49
57
|
it('should support transactions', () => {
|
|
50
58
|
db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)');
|
|
51
59
|
|
package/dist/database.d.ts
CHANGED
|
@@ -1,7 +1,89 @@
|
|
|
1
|
+
export interface BenchmarkReportRecord {
|
|
2
|
+
runId: string;
|
|
3
|
+
suiteId: string;
|
|
4
|
+
suiteVersion: string;
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
commit: string | null;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
status: 'completed';
|
|
9
|
+
tasks: unknown[];
|
|
10
|
+
}
|
|
11
|
+
export interface BenchmarkReportMetaRecord {
|
|
12
|
+
runId: string;
|
|
13
|
+
suiteId: string;
|
|
14
|
+
suiteVersion: string;
|
|
15
|
+
projectRoot: string;
|
|
16
|
+
commit: string | null;
|
|
17
|
+
taskCount: number;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
status: 'completed';
|
|
20
|
+
}
|
|
21
|
+
export interface FeedbackRecord {
|
|
22
|
+
id: string;
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
query: string;
|
|
25
|
+
rating: 'hit' | 'partial' | 'miss';
|
|
26
|
+
taskType?: string;
|
|
27
|
+
contextMode?: 'general' | 'task';
|
|
28
|
+
runId?: string;
|
|
29
|
+
tokenCount?: number;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
}
|
|
32
|
+
export interface TelemetryFailureRecord {
|
|
33
|
+
id: string;
|
|
34
|
+
operation: string;
|
|
35
|
+
payload: unknown;
|
|
36
|
+
error: string;
|
|
37
|
+
createdAt: number;
|
|
38
|
+
resolvedAt?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface GovernanceRuleRecord {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
gate: 1 | 2 | 3 | 4;
|
|
44
|
+
level: 'constitutional' | 'project' | 'session';
|
|
45
|
+
condition: Record<string, unknown>;
|
|
46
|
+
action: 'deny' | 'warn' | 'allow';
|
|
47
|
+
message: string;
|
|
48
|
+
enabled: boolean;
|
|
49
|
+
createdAt?: number;
|
|
50
|
+
updatedAt?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface GovernanceAuditRecord {
|
|
53
|
+
seq: number;
|
|
54
|
+
ts: number;
|
|
55
|
+
tool: string;
|
|
56
|
+
argsHash: string;
|
|
57
|
+
decision: 'allow' | 'deny' | 'warn';
|
|
58
|
+
ruleId: string | null;
|
|
59
|
+
sessionId: string;
|
|
60
|
+
prevHash: string;
|
|
61
|
+
signature: string;
|
|
62
|
+
}
|
|
63
|
+
export interface HookReceiptRecord {
|
|
64
|
+
projectRoot: string;
|
|
65
|
+
lastMcpCall?: number;
|
|
66
|
+
bypassCount?: number;
|
|
67
|
+
updatedAt: number;
|
|
68
|
+
}
|
|
69
|
+
export interface MemoryDistillCheckpointRecord {
|
|
70
|
+
id: string;
|
|
71
|
+
projectRoot: string;
|
|
72
|
+
sessionId?: string;
|
|
73
|
+
trigger: 'pre_compact' | 'session_end';
|
|
74
|
+
pendingEvents: number;
|
|
75
|
+
releasedLeases: number;
|
|
76
|
+
createdAt: number;
|
|
77
|
+
}
|
|
78
|
+
export declare function getGlobalDevFlowDbPath(home?: string): string;
|
|
79
|
+
export declare function openGlobalDevFlowDatabase(home?: string, options?: {
|
|
80
|
+
busyTimeoutMs?: number;
|
|
81
|
+
}): DevFlowDatabase;
|
|
1
82
|
export declare class DevFlowDatabase {
|
|
2
83
|
private db;
|
|
3
84
|
constructor(projectRoot: string, opts?: {
|
|
4
85
|
dbPath?: string;
|
|
86
|
+
busyTimeoutMs?: number;
|
|
5
87
|
});
|
|
6
88
|
private initializeSchema;
|
|
7
89
|
insertRun(run: any): void;
|
|
@@ -71,13 +153,15 @@ export declare class DevFlowDatabase {
|
|
|
71
153
|
blocked: boolean;
|
|
72
154
|
blockReason?: string;
|
|
73
155
|
workflowRunId?: string;
|
|
74
|
-
|
|
156
|
+
failureCategory?: string;
|
|
157
|
+
tokensUsed?: number;
|
|
158
|
+
}): boolean;
|
|
75
159
|
listToolCallEvents(executionId: string): any[];
|
|
76
160
|
updateToolCallEvent(eventId: string, updates: {
|
|
77
161
|
output?: string;
|
|
78
162
|
error?: string;
|
|
79
163
|
duration?: number;
|
|
80
|
-
}):
|
|
164
|
+
}): boolean;
|
|
81
165
|
getMcpCompliance(): {
|
|
82
166
|
overall: number;
|
|
83
167
|
bySkill: Record<string, number>;
|
|
@@ -101,6 +185,24 @@ export declare class DevFlowDatabase {
|
|
|
101
185
|
}): void;
|
|
102
186
|
getToolMetrics(sessionId?: string, toolName?: string, limit?: number, offset?: number): any[];
|
|
103
187
|
getToolMetricsSummary(days?: number): any[];
|
|
188
|
+
aggregatePendingToolMetrics(limit?: number): number;
|
|
189
|
+
insertFeedback(feedback: FeedbackRecord): void;
|
|
190
|
+
listFeedback(projectRoot?: string, limit?: number): FeedbackRecord[];
|
|
191
|
+
getFeedbackSummary(projectRoot?: string): {
|
|
192
|
+
total: number;
|
|
193
|
+
hits: number;
|
|
194
|
+
partials: number;
|
|
195
|
+
misses: number;
|
|
196
|
+
hitRate: number;
|
|
197
|
+
};
|
|
198
|
+
insertTelemetryFailure(failure: TelemetryFailureRecord): void;
|
|
199
|
+
resolveTelemetryFailure(id: string, resolvedAt?: number): void;
|
|
200
|
+
listTelemetryFailures(options?: {
|
|
201
|
+
unresolvedOnly?: boolean;
|
|
202
|
+
limit?: number;
|
|
203
|
+
}): TelemetryFailureRecord[];
|
|
204
|
+
countTelemetryFailures(unresolvedOnly?: boolean): number;
|
|
205
|
+
trimTelemetryFailures(maxRows?: number): number;
|
|
104
206
|
insertAccuracyQuery(q: {
|
|
105
207
|
id: string;
|
|
106
208
|
sessionId: string;
|
|
@@ -121,6 +223,23 @@ export declare class DevFlowDatabase {
|
|
|
121
223
|
note?: string;
|
|
122
224
|
}): void;
|
|
123
225
|
getAccuracyStats(engine?: string, since?: number): any;
|
|
226
|
+
insertBenchmarkReport<T extends BenchmarkReportRecord>(report: T): void;
|
|
227
|
+
getBenchmarkReport<T extends BenchmarkReportRecord = BenchmarkReportRecord>(runId: string): T | null;
|
|
228
|
+
getLatestBenchmarkReport<T extends BenchmarkReportRecord = BenchmarkReportRecord>(suiteId?: string): T | null;
|
|
229
|
+
listBenchmarkReports(limit?: number): BenchmarkReportMetaRecord[];
|
|
230
|
+
listGovernanceRules(includeDisabled?: boolean): GovernanceRuleRecord[];
|
|
231
|
+
getGovernanceRule(id: string): GovernanceRuleRecord | null;
|
|
232
|
+
upsertGovernanceRule(rule: GovernanceRuleRecord): GovernanceRuleRecord;
|
|
233
|
+
deleteGovernanceRule(id: string): boolean;
|
|
234
|
+
listGovernanceAudit(limit?: number, offset?: number): {
|
|
235
|
+
items: GovernanceAuditRecord[];
|
|
236
|
+
total: number;
|
|
237
|
+
};
|
|
238
|
+
getHookReceipt(projectRoot: string): HookReceiptRecord | null;
|
|
239
|
+
updateHookReceipt(projectRoot: string, updater: (current: HookReceiptRecord | null) => Omit<HookReceiptRecord, 'projectRoot' | 'updatedAt'>): HookReceiptRecord;
|
|
240
|
+
deleteHookReceipt(projectRoot: string): boolean;
|
|
241
|
+
recordMemoryDistillCheckpoint(checkpoint: MemoryDistillCheckpointRecord): void;
|
|
242
|
+
listMemoryDistillCheckpoints(projectRoot: string, limit?: number): MemoryDistillCheckpointRecord[];
|
|
124
243
|
all(sql: string, ...params: unknown[]): unknown[];
|
|
125
244
|
get(sql: string, ...params: unknown[]): unknown;
|
|
126
245
|
close(): void;
|