@devflow-tools/database 0.15.0 → 0.16.2

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,38 @@
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.2](https://github.com/shilongfeicool/dev-flow/compare/v0.16.1...v0.16.2) (2026-07-22)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * harden Claude plugin lifecycle ([992e74f](https://github.com/shilongfeicool/dev-flow/commit/992e74ff0a37cdb5132736eba3926be7639ac806))
12
+
13
+
14
+
15
+
16
+
17
+ # [0.16.0](https://github.com/shilongfeicool/dev-flow/compare/v0.15.0...v0.16.0) (2026-07-22)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **batch2:** close final telemetry and migration races ([7340608](https://github.com/shilongfeicool/dev-flow/commit/7340608419e6cb476ed44662728f1dfe8466f4c9))
23
+
24
+
25
+ ### Features
26
+
27
+ * **batch4:** audit mcp surface health ([1b60d74](https://github.com/shilongfeicool/dev-flow/commit/1b60d74c8da23964743a82097244f585532871cb))
28
+ * **batch4:** consolidate hook lifecycle state ([803b221](https://github.com/shilongfeicool/dev-flow/commit/803b2219d5d73f3ce322f572ba625bb321f3b126))
29
+ * **batch4:** establish benchmark baseline pipeline ([8741192](https://github.com/shilongfeicool/dev-flow/commit/8741192f96280962160619fcdb0c47bf94315862))
30
+ * **batch4:** persist enforcer governance ([2ecf668](https://github.com/shilongfeicool/dev-flow/commit/2ecf668fb0493a1a649c4d16cc17f021d6f70031))
31
+ * **batch4:** wire workflow failure attribution ([682d12b](https://github.com/shilongfeicool/dev-flow/commit/682d12ba715d4003af33ad38610b5655f1b49d8a))
32
+ * **telemetry:** make global sqlite the primary writer ([751eb5b](https://github.com/shilongfeicool/dev-flow/commit/751eb5b48889220991af5016b5fe3e4d176f4770))
33
+
34
+
35
+
36
+
37
+
6
38
  # [0.15.0](https://github.com/shilongfeicool/dev-flow/compare/v0.14.4...v0.15.0) (2026-07-16)
7
39
 
8
40
  **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,97 @@
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 ContextReceiptRecord {
70
+ projectRoot: string;
71
+ sessionId: string;
72
+ executionId: string;
73
+ contextHash: string;
74
+ issuedAt: number;
75
+ expiresAt: number;
76
+ }
77
+ export interface MemoryDistillCheckpointRecord {
78
+ id: string;
79
+ projectRoot: string;
80
+ sessionId?: string;
81
+ trigger: 'pre_compact' | 'session_end';
82
+ pendingEvents: number;
83
+ releasedLeases: number;
84
+ createdAt: number;
85
+ }
86
+ export declare function getGlobalDevFlowDbPath(home?: string): string;
87
+ export declare function openGlobalDevFlowDatabase(home?: string, options?: {
88
+ busyTimeoutMs?: number;
89
+ }): DevFlowDatabase;
1
90
  export declare class DevFlowDatabase {
2
91
  private db;
3
92
  constructor(projectRoot: string, opts?: {
4
93
  dbPath?: string;
94
+ busyTimeoutMs?: number;
5
95
  });
6
96
  private initializeSchema;
7
97
  insertRun(run: any): void;
@@ -23,6 +113,12 @@ export declare class DevFlowDatabase {
23
113
  label?: string;
24
114
  startedAt: number;
25
115
  }): void;
116
+ ensureSession(params: {
117
+ id: string;
118
+ projectRoot: string;
119
+ label?: string;
120
+ startedAt?: number;
121
+ }): void;
26
122
  getSession(id: string): any | null;
27
123
  listSessions(limit: number, offset: number, projectRoot?: string): any[];
28
124
  closeSession(id: string, finishedAt?: number): void;
@@ -48,6 +144,7 @@ export declare class DevFlowDatabase {
48
144
  mcpToolCalls?: number;
49
145
  directToolCalls?: number;
50
146
  subagentCount?: number;
147
+ metadata?: Record<string, unknown>;
51
148
  }): void;
52
149
  listSkillExecutions(limit: number, skillName?: string): any[];
53
150
  insertToolCallEvent(params: {
@@ -71,13 +168,18 @@ export declare class DevFlowDatabase {
71
168
  blocked: boolean;
72
169
  blockReason?: string;
73
170
  workflowRunId?: string;
74
- }): void;
171
+ failureCategory?: string;
172
+ tokensUsed?: number;
173
+ toolUseId?: string;
174
+ }): boolean;
75
175
  listToolCallEvents(executionId: string): any[];
76
176
  updateToolCallEvent(eventId: string, updates: {
77
177
  output?: string;
78
178
  error?: string;
79
179
  duration?: number;
80
- }): void;
180
+ }): boolean;
181
+ markToolCallBlocked(eventId: string, reason: string): boolean;
182
+ reconcileSkillExecution(executionId: string, status: 'completed' | 'failed', finishedAt?: number, metadata?: Record<string, unknown>): void;
81
183
  getMcpCompliance(): {
82
184
  overall: number;
83
185
  bySkill: Record<string, number>;
@@ -101,6 +203,24 @@ export declare class DevFlowDatabase {
101
203
  }): void;
102
204
  getToolMetrics(sessionId?: string, toolName?: string, limit?: number, offset?: number): any[];
103
205
  getToolMetricsSummary(days?: number): any[];
206
+ aggregatePendingToolMetrics(limit?: number): number;
207
+ insertFeedback(feedback: FeedbackRecord): void;
208
+ listFeedback(projectRoot?: string, limit?: number): FeedbackRecord[];
209
+ getFeedbackSummary(projectRoot?: string): {
210
+ total: number;
211
+ hits: number;
212
+ partials: number;
213
+ misses: number;
214
+ hitRate: number;
215
+ };
216
+ insertTelemetryFailure(failure: TelemetryFailureRecord): void;
217
+ resolveTelemetryFailure(id: string, resolvedAt?: number): void;
218
+ listTelemetryFailures(options?: {
219
+ unresolvedOnly?: boolean;
220
+ limit?: number;
221
+ }): TelemetryFailureRecord[];
222
+ countTelemetryFailures(unresolvedOnly?: boolean): number;
223
+ trimTelemetryFailures(maxRows?: number): number;
104
224
  insertAccuracyQuery(q: {
105
225
  id: string;
106
226
  sessionId: string;
@@ -121,6 +241,27 @@ export declare class DevFlowDatabase {
121
241
  note?: string;
122
242
  }): void;
123
243
  getAccuracyStats(engine?: string, since?: number): any;
244
+ insertBenchmarkReport<T extends BenchmarkReportRecord>(report: T): void;
245
+ getBenchmarkReport<T extends BenchmarkReportRecord = BenchmarkReportRecord>(runId: string): T | null;
246
+ getLatestBenchmarkReport<T extends BenchmarkReportRecord = BenchmarkReportRecord>(suiteId?: string): T | null;
247
+ listBenchmarkReports(limit?: number): BenchmarkReportMetaRecord[];
248
+ listGovernanceRules(includeDisabled?: boolean): GovernanceRuleRecord[];
249
+ getGovernanceRule(id: string): GovernanceRuleRecord | null;
250
+ upsertGovernanceRule(rule: GovernanceRuleRecord): GovernanceRuleRecord;
251
+ deleteGovernanceRule(id: string): boolean;
252
+ listGovernanceAudit(limit?: number, offset?: number): {
253
+ items: GovernanceAuditRecord[];
254
+ total: number;
255
+ };
256
+ getHookReceipt(projectRoot: string): HookReceiptRecord | null;
257
+ updateHookReceipt(projectRoot: string, updater: (current: HookReceiptRecord | null) => Omit<HookReceiptRecord, 'projectRoot' | 'updatedAt'>): HookReceiptRecord;
258
+ deleteHookReceipt(projectRoot: string): boolean;
259
+ upsertContextReceipt(receipt: ContextReceiptRecord): void;
260
+ getContextReceipt(projectRoot: string, sessionId: string, executionId: string): ContextReceiptRecord | null;
261
+ deleteContextReceipt(projectRoot: string, sessionId: string, executionId?: string): number;
262
+ purgeExpiredContextReceipts(now?: number): number;
263
+ recordMemoryDistillCheckpoint(checkpoint: MemoryDistillCheckpointRecord): void;
264
+ listMemoryDistillCheckpoints(projectRoot: string, limit?: number): MemoryDistillCheckpointRecord[];
124
265
  all(sql: string, ...params: unknown[]): unknown[];
125
266
  get(sql: string, ...params: unknown[]): unknown;
126
267
  close(): void;