@devflow-tools/database 0.16.11 → 0.16.13

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,33 @@
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.13](https://github.com/shilongfeicool/dev-flow/compare/v0.16.12...v0.16.13) (2026-07-26)
7
+
8
+
9
+ ### Features
10
+
11
+ * add retrieval quality control plane ([743bd12](https://github.com/shilongfeicool/dev-flow/commit/743bd12daa18ceec611dc82674b20b015d110f0c))
12
+
13
+
14
+
15
+
16
+
17
+ ## [0.16.12](https://github.com/shilongfeicool/dev-flow/compare/v0.16.11...v0.16.12) (2026-07-26)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **database:** support Node 24 sqlite result types ([40efed3](https://github.com/shilongfeicool/dev-flow/commit/40efed3d8c629990f5e29324b3aba673c16a091b))
23
+ * **memory:** make embedding model resilient ([e24fd08](https://github.com/shilongfeicool/dev-flow/commit/e24fd086b09d0327e5a69c118f02501caffcf280))
24
+ * **runtime:** close final reliability gaps ([86edd2b](https://github.com/shilongfeicool/dev-flow/commit/86edd2b49acf3559c9e66643ca913fc3a9728731))
25
+ * **runtime:** harden session reconciliation retries ([fc38e56](https://github.com/shilongfeicool/dev-flow/commit/fc38e561450540adecf7883666453f22fa4a9271))
26
+ * **runtime:** preserve reconciliation retry state ([2cec7fe](https://github.com/shilongfeicool/dev-flow/commit/2cec7fe5156a7ab48fbf19c7a427cbca71e7de09))
27
+ * **runtime:** reconcile session lifecycle telemetry ([87b2f7c](https://github.com/shilongfeicool/dev-flow/commit/87b2f7c0c04a572ab396d0da1cc13eada7779691))
28
+
29
+
30
+
31
+
32
+
6
33
  ## [0.16.10](https://github.com/shilongfeicool/dev-flow/compare/v0.16.9...v0.16.10) (2026-07-24)
7
34
 
8
35
 
@@ -68,4 +68,31 @@ describe('DevFlowDatabase memory turn receipts', () => {
68
68
  stopPromptedAt: 350,
69
69
  });
70
70
  });
71
+
72
+ it('lists every session turn in stable ascending order for reconciliation', () => {
73
+ for (let index = 0; index < 125; index += 1) {
74
+ database.beginMemoryTurn({
75
+ turnId: `turn:${String(index).padStart(3, '0')}`,
76
+ projectRoot: '/project',
77
+ sessionId: 'long-session',
78
+ promptHash: `hash-${index}`,
79
+ eventId: `event-${index}`,
80
+ createdAt: 1_000 + index,
81
+ });
82
+ }
83
+ database.beginMemoryTurn({
84
+ turnId: 'turn:other',
85
+ projectRoot: '/project',
86
+ sessionId: 'other-session',
87
+ promptHash: 'other',
88
+ eventId: 'event-other',
89
+ createdAt: 1,
90
+ });
91
+
92
+ const turns = database.listSessionMemoryTurnsForReconciliation('/project', 'long-session');
93
+ expect(turns).toHaveLength(125);
94
+ expect(turns[0]?.turnId).toBe('turn:000');
95
+ expect(turns.at(-1)?.turnId).toBe('turn:124');
96
+ expect(turns.every((turn, index) => turn.createdAt === 1_000 + index)).toBe(true);
97
+ });
71
98
  });
@@ -242,7 +242,14 @@ describe('DevFlowDatabase - Skill Executions', () => {
242
242
  attempts: 1, createdAt: 170,
243
243
  });
244
244
 
245
- db.reconcileSkillExecution('exec-facts', 'completed', 200);
245
+ db.reconcileSkillExecution('exec-facts', 'completed', 200, {
246
+ missedTools: [],
247
+ actualFailureCount: 0,
248
+ blockedCount: 99,
249
+ fallbackCount: 0,
250
+ fallbackReasons: ['stale_supplied_reason'],
251
+ evidenceContractOutcomes: [{ status: 'resolved' }],
252
+ });
246
253
 
247
254
  expect(db.getSkillExecution('exec-facts')).toMatchObject({
248
255
  missedMcpTools: ['react:context', 'react:domain'],
@@ -251,14 +258,68 @@ describe('DevFlowDatabase - Skill Executions', () => {
251
258
  fallbackCount: 2,
252
259
  fallbackReasons: ['direct_tool_during_context', 'daemon_timeout'],
253
260
  metadata: expect.objectContaining({
261
+ missedTools: ['react:context', 'react:domain'],
254
262
  actualFailureCount: 1,
263
+ blockedCount: 0,
255
264
  fallbackCount: 2,
265
+ fallbackReasons: ['direct_tool_during_context', 'daemon_timeout'],
256
266
  memoryReceiptIds: ['memory-receipt:host-commit'],
257
267
  distillReceiptIds: ['distill-receipt:batch-1'],
268
+ evidenceContractOutcomes: [{ status: 'resolved' }],
258
269
  }),
259
270
  });
260
271
  });
261
272
 
273
+ it('unions evidence metadata across retries without allowing stale counters', () => {
274
+ db.ensureSession({ id: 'session-evidence-retry', projectRoot: '/project', startedAt: 100 });
275
+ db.insertSkillExecution({
276
+ executionId: 'exec-evidence-retry',
277
+ sessionId: 'session-evidence-retry',
278
+ skillName: 'devflow:context',
279
+ startedAt: 100,
280
+ status: 'running',
281
+ });
282
+ db.insertToolCallEvent({
283
+ eventId: 'event-context', executionId: 'exec-evidence-retry',
284
+ sessionId: 'session-evidence-retry', timestamp: 120,
285
+ toolName: 'get_project_context', toolType: 'mcp', isMcpTool: true,
286
+ mcpToolName: 'get_project_context', mcpEnforced: true, mcpFallback: false,
287
+ input: {}, output: { files: ['src/App.tsx'] }, duration: 10, blocked: false,
288
+ });
289
+ const degradation = {
290
+ obligationId: 'obligation-1', contractId: 'contract-1', reason: 'bounded_fail_open',
291
+ degradedAt: 150, attempt: 2,
292
+ };
293
+ const outcome = {
294
+ obligationId: 'obligation-1', contractId: 'contract-1', status: 'degraded',
295
+ completedAt: 150, attempt: 2, reason: 'bounded_fail_open',
296
+ };
297
+
298
+ db.reconcileSkillExecution('exec-evidence-retry', 'completed', 200, {
299
+ evidenceDegradations: [degradation],
300
+ evidenceContractOutcomes: [outcome],
301
+ pendingEvidenceContractIds: ['contract-pending'],
302
+ });
303
+ db.reconcileSkillExecution('exec-evidence-retry', 'completed', 220, {
304
+ evidenceDegradations: [],
305
+ evidenceContractOutcomes: [
306
+ { attempt: 2, completedAt: 150, reason: 'bounded_fail_open', status: 'degraded',
307
+ contractId: 'contract-1', obligationId: 'obligation-1' },
308
+ ],
309
+ pendingEvidenceContractIds: [],
310
+ missedTools: ['stale'],
311
+ actualFailureCount: 99,
312
+ });
313
+
314
+ expect(db.getSkillExecution('exec-evidence-retry')?.metadata).toMatchObject({
315
+ evidenceDegradations: [degradation],
316
+ evidenceContractOutcomes: [outcome],
317
+ pendingEvidenceContractIds: ['contract-pending'],
318
+ missedTools: [],
319
+ actualFailureCount: 0,
320
+ });
321
+ });
322
+
262
323
  it('aggregates an empty findings collection as an empty result', () => {
263
324
  db.insertSkillExecution({
264
325
  executionId: 'exec_empty_findings',
@@ -1,4 +1,5 @@
1
1
  import type { EnqueueWorkInput, LeaseWorkInput, RequestSessionClosureInput, SessionClosureRecord, WorkError, WorkItemRecord, WorkQueueHealth } from './work-queue';
2
+ import { type SessionObligationRecord, type SessionObligationState } from './obligation-ledger';
2
3
  export interface BenchmarkReportRecord {
3
4
  runId: string;
4
5
  suiteId: string;
@@ -74,6 +75,22 @@ export interface ContextReceiptRecord {
74
75
  contextHash: string;
75
76
  issuedAt: number;
76
77
  expiresAt: number;
78
+ selectedFiles?: string[];
79
+ memoryIds?: string[];
80
+ canonicalNextAction?: string;
81
+ requestId?: string;
82
+ }
83
+ export interface ContextSelectionEventRecord {
84
+ id: string;
85
+ projectRoot: string;
86
+ sessionId: string;
87
+ executionId: string;
88
+ requestId?: string;
89
+ selectionType: 'code' | 'action';
90
+ candidateId: string;
91
+ toolName: string;
92
+ toolUseId?: string;
93
+ selectedAt: number;
77
94
  }
78
95
  export interface MemoryDistillCheckpointRecord {
79
96
  id: string;
@@ -82,6 +99,7 @@ export interface MemoryDistillCheckpointRecord {
82
99
  trigger: 'pre_compact' | 'session_end';
83
100
  pendingEvents: number;
84
101
  releasedLeases: number;
102
+ details?: Record<string, unknown>;
85
103
  createdAt: number;
86
104
  }
87
105
  export type MemoryTurnStatus = 'pending' | 'committed' | 'skipped';
@@ -168,6 +186,7 @@ export declare class DevFlowDatabase {
168
186
  availableMcpTools?: string[];
169
187
  }): void;
170
188
  getSkillExecution(executionId: string): any | null;
189
+ getLatestSkillExecutionForSession(sessionId: string): any | null;
171
190
  updateSkillExecution(executionId: string, updates: {
172
191
  status?: string;
173
192
  finishedAt?: number;
@@ -184,6 +203,7 @@ export declare class DevFlowDatabase {
184
203
  subagentCount?: number;
185
204
  metadata?: Record<string, unknown>;
186
205
  }): void;
206
+ mergeSkillExecutionMetadata(executionId: string, incoming: Record<string, unknown>): Record<string, unknown> | null;
187
207
  listSkillExecutions(limit: number, skillName?: string): any[];
188
208
  insertToolCallEvent(params: {
189
209
  eventId: string;
@@ -214,6 +234,7 @@ export declare class DevFlowDatabase {
214
234
  updateToolCallEvent(eventId: string, updates: {
215
235
  output?: string;
216
236
  error?: string;
237
+ failureCategory?: string;
217
238
  duration?: number;
218
239
  }): boolean;
219
240
  markToolCallBlocked(eventId: string, reason: string): boolean;
@@ -311,20 +332,43 @@ export declare class DevFlowDatabase {
311
332
  leaseWork(input: LeaseWorkInput): WorkItemRecord[];
312
333
  completeWork(id: string, owner: string, now?: number): boolean;
313
334
  retryWork(id: string, owner: string, error: WorkError, nextAttemptAt: number): boolean;
335
+ deferWork(id: string, owner: string, error: WorkError, nextAttemptAt: number): boolean;
314
336
  deadLetterWork(id: string, owner: string, error: WorkError): boolean;
315
337
  recoverExpiredWork(projectRoot: string, now?: number): number;
316
338
  getWorkQueueHealth(projectRoot: string, now?: number): WorkQueueHealth;
317
339
  private mapWorkItem;
318
340
  private mapSessionClosure;
319
341
  private refreshClosedSessionClosure;
342
+ private countSessionLifecyclePending;
320
343
  getHookReceipt(projectRoot: string): HookReceiptRecord | null;
321
344
  updateHookReceipt(projectRoot: string, updater: (current: HookReceiptRecord | null) => Omit<HookReceiptRecord, 'projectRoot' | 'updatedAt'>): HookReceiptRecord;
322
345
  deleteHookReceipt(projectRoot: string): boolean;
323
346
  upsertContextReceipt(receipt: ContextReceiptRecord): void;
324
347
  getContextReceipt(projectRoot: string, sessionId: string, executionId: string): ContextReceiptRecord | null;
348
+ getActiveContextReceipt(projectRoot: string, sessionId: string, executionId?: string, now?: number): ContextReceiptRecord | null;
349
+ recordContextSelectionEvent(event: ContextSelectionEventRecord): boolean;
350
+ listContextSelectionEvents(options: {
351
+ projectRoot: string;
352
+ sessionId?: string;
353
+ executionId?: string;
354
+ requestId?: string;
355
+ }): ContextSelectionEventRecord[];
325
356
  deleteContextReceipt(projectRoot: string, sessionId: string, executionId?: string): number;
326
357
  purgeExpiredContextReceipts(now?: number): number;
327
358
  beginMemoryTurn(input: Omit<MemoryTurnRecord, 'status' | 'memoryIds'>): MemoryTurnRecord;
359
+ upsertSessionObligation(record: SessionObligationRecord): SessionObligationRecord;
360
+ getSessionObligation(projectRoot: string, sessionId: string, obligationId: string): SessionObligationRecord | null;
361
+ listSessionObligations(projectRoot: string, sessionId: string, states?: SessionObligationState[]): SessionObligationRecord[];
362
+ resolveSessionObligation(input: {
363
+ projectRoot: string;
364
+ sessionId: string;
365
+ obligationId: string;
366
+ state: 'satisfied' | 'degraded' | 'cancelled';
367
+ receiptId?: string;
368
+ reason?: string;
369
+ resolvedAt?: number;
370
+ }): SessionObligationRecord;
371
+ countOpenSessionObligations(projectRoot: string, sessionId: string): number;
328
372
  getMemoryTurn(turnId: string): MemoryTurnRecord | null;
329
373
  getPendingMemoryTurn(projectRoot: string, sessionId: string): MemoryTurnRecord | null;
330
374
  commitMemoryTurn(input: {
@@ -342,7 +386,9 @@ export declare class DevFlowDatabase {
342
386
  decidedAt?: number;
343
387
  }): MemoryTurnRecord;
344
388
  markMemoryTurnStopPrompted(turnId: string, promptedAt?: number): boolean;
389
+ private ensureMemoryObligation;
345
390
  listMemoryTurns(projectRoot: string, sessionId?: string, limit?: number): MemoryTurnRecord[];
391
+ listSessionMemoryTurnsForReconciliation(projectRoot: string, sessionId: string): MemoryTurnRecord[];
346
392
  insertHookFallback(record: HookFallbackRecord): boolean;
347
393
  listHookFallbacks(filter?: {
348
394
  projectRoot?: string;