@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 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
 
@@ -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
- }): void;
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
- }): void;
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;