@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 +27 -0
- package/__tests__/database.memory-turn-receipts.test.ts +27 -0
- package/__tests__/database.skill-executions.test.ts +62 -1
- package/dist/database.d.ts +46 -0
- package/dist/database.js +438 -48
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/node-sqlite.js +1 -6
- package/dist/obligation-ledger.d.ts +19 -0
- package/dist/obligation-ledger.js +35 -0
- package/dist/work-queue.d.ts +1 -1
- package/package.json +3 -3
- package/src/database.ts +552 -45
- package/src/index.ts +11 -0
- package/src/node-sqlite.ts +1 -6
- package/src/obligation-ledger.ts +57 -0
- package/src/work-queue.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
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',
|
package/dist/database.d.ts
CHANGED
|
@@ -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;
|