@principles/pd-cli 1.145.2 → 1.146.1
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/README.md +11 -2
- package/dist/commands/build-trajectory-evidence.d.ts +25 -6
- package/dist/commands/build-trajectory-evidence.d.ts.map +1 -1
- package/dist/commands/build-trajectory-evidence.js +174 -114
- package/dist/commands/build-trajectory-evidence.js.map +1 -1
- package/dist/commands/console.d.ts +13 -0
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +104 -0
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +212 -17
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/build-trajectory-evidence.ts +211 -122
- package/src/commands/console.ts +105 -0
- package/src/commands/pain-record.ts +224 -16
- package/src/index.ts +1 -1
- package/tests/commands/build-trajectory-evidence.test.ts +226 -161
- package/tests/commands/console-open.test.ts +175 -0
- package/tests/commands/pain-record-async.test.ts +36 -30
- package/tests/commands/pain-record-session-parser.test.ts +127 -0
- package/tests/commands/pain-record.test.ts +205 -37
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for
|
|
2
|
+
* Tests for acquireTrajectoryEvidenceFromDb — PRI-341 extraction behavior,
|
|
3
|
+
* PRI-642 typed classification (the legacy array wrapper was removed with
|
|
4
|
+
* its last production consumer).
|
|
3
5
|
*
|
|
4
6
|
* Uses real temporary SQLite DBs (trajectory.db) to validate evidence extraction.
|
|
5
7
|
*/
|
|
@@ -9,7 +11,7 @@ import * as fs from 'fs';
|
|
|
9
11
|
import * as path from 'path';
|
|
10
12
|
import * as os from 'os';
|
|
11
13
|
import { MAX_EVIDENCE_ENTRIES } from '@principles/core/runtime-v2';
|
|
12
|
-
import {
|
|
14
|
+
import { acquireTrajectoryEvidenceFromDb } from '../../src/commands/build-trajectory-evidence.js';
|
|
13
15
|
|
|
14
16
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
15
17
|
|
|
@@ -127,7 +129,7 @@ function insertToolCall(
|
|
|
127
129
|
|
|
128
130
|
// ── Tests ───────────────────────────────────────────────────────────────────
|
|
129
131
|
|
|
130
|
-
describe('
|
|
132
|
+
describe('acquireTrajectoryEvidenceFromDb — behavior via typed API (PRI-341 extraction, PRI-642 wrapper removal)', () => {
|
|
131
133
|
beforeEach(() => {
|
|
132
134
|
createStateDir();
|
|
133
135
|
});
|
|
@@ -140,198 +142,261 @@ describe('buildTrajectoryEvidenceFromDb — PRI-341', () => {
|
|
|
140
142
|
}
|
|
141
143
|
});
|
|
142
144
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
/** Available-class entries, or the unavailable reason for assertions. */
|
|
146
|
+
function acquire(sessionId: string | undefined) {
|
|
147
|
+
return acquireTrajectoryEvidenceFromDb(stateDir, sessionId, tmpDir);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function entriesOf(sessionId: string) {
|
|
151
|
+
const result = acquire(sessionId);
|
|
152
|
+
expect(result.status).toBe('available');
|
|
153
|
+
return result.status === 'available' ? result.entries : [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function insertSession(db: Database.Database, sessionId: string): void {
|
|
157
|
+
db.prepare('INSERT INTO sessions (session_id, started_at, updated_at) VALUES (?, ?, ?)')
|
|
158
|
+
.run(sessionId, '2026-01-01T09:00:00Z', '2026-01-01T09:00:00Z');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
it('returns available entries (≤ MAX_EVIDENCE_ENTRIES) from assistant turns', () => {
|
|
146
162
|
const db = createTrajectoryDb();
|
|
163
|
+
insertSession(db, '123');
|
|
147
164
|
insertAssistantTurn(db, '123', 'First assistant response about backups', '2026-01-01T10:00:00Z');
|
|
148
165
|
insertAssistantTurn(db, '123', 'Second response about validation', '2026-01-01T10:01:00Z');
|
|
149
166
|
insertAssistantTurn(db, '123', 'Third response about error handling', '2026-01-01T10:02:00Z');
|
|
150
167
|
db.close();
|
|
151
168
|
|
|
152
|
-
const
|
|
169
|
+
const entries = entriesOf('123');
|
|
153
170
|
|
|
154
|
-
expect(
|
|
155
|
-
expect(
|
|
156
|
-
for (const entry of
|
|
171
|
+
expect(entries.length).toBeGreaterThan(0);
|
|
172
|
+
expect(entries.length).toBeLessThanOrEqual(MAX_EVIDENCE_ENTRIES);
|
|
173
|
+
for (const entry of entries) {
|
|
157
174
|
expect(entry.sourceRef).toBeTruthy();
|
|
158
175
|
expect(entry.note).toBeTruthy();
|
|
159
|
-
expect(typeof entry.sourceRef).toBe('string');
|
|
160
|
-
expect(typeof entry.note).toBe('string');
|
|
161
176
|
}
|
|
162
177
|
});
|
|
163
178
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
179
|
+
it('classifies an undefined session as session_not_found (no placeholder entries)', () => {
|
|
180
|
+
const db = createTrajectoryDb();
|
|
181
|
+
db.close();
|
|
182
|
+
const result = acquire(undefined);
|
|
183
|
+
expect(result.status).toBe('unavailable');
|
|
184
|
+
if (result.status !== 'unavailable') return;
|
|
185
|
+
expect(result.reasonCode).toBe('session_not_found');
|
|
186
|
+
});
|
|
168
187
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
188
|
+
it('surfaces user correction turns with correctionDetected=true', () => {
|
|
189
|
+
const db = createTrajectoryDb();
|
|
190
|
+
insertSession(db, '123');
|
|
191
|
+
insertUserTurn(db, '123', 'Please fix the backup logic', true, '2026-01-01T09:59:00Z');
|
|
192
|
+
insertAssistantTurn(db, '123', 'I will fix the backup logic', '2026-01-01T10:00:00Z');
|
|
193
|
+
db.close();
|
|
194
|
+
|
|
195
|
+
const entries = entriesOf('123');
|
|
196
|
+
|
|
197
|
+
const ownerEntry = entries.find(e => e.sourceRef.startsWith('owner_message:'));
|
|
198
|
+
expect(ownerEntry).toBeDefined();
|
|
199
|
+
expect(ownerEntry!.note).toContain('fix the backup logic');
|
|
172
200
|
});
|
|
173
201
|
|
|
174
|
-
it('
|
|
175
|
-
const
|
|
202
|
+
it('extracts failed tool_calls as evidence entries (PRI-358)', () => {
|
|
203
|
+
const db = createTrajectoryDb();
|
|
204
|
+
insertSession(db, '123');
|
|
205
|
+
insertToolCall(db, '123', 'bash', 'failure', 'non_zero_exit', 1, '2026-01-01T10:00:00Z');
|
|
206
|
+
insertToolCall(db, '123', 'write_file', 'success', null, 0, '2026-01-01T10:01:00Z');
|
|
207
|
+
insertToolCall(db, '123', 'bash', 'failure', 'timeout', 124, '2026-01-01T10:02:00Z');
|
|
208
|
+
db.close();
|
|
209
|
+
|
|
210
|
+
const entries = entriesOf('123');
|
|
176
211
|
|
|
177
|
-
|
|
178
|
-
expect(
|
|
212
|
+
const failureEntries = entries.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
213
|
+
expect(failureEntries.length).toBe(2);
|
|
214
|
+
expect(failureEntries[0].note).toContain('bash');
|
|
215
|
+
expect(failureEntries[0].note).toContain('non_zero_exit');
|
|
216
|
+
expect(failureEntries[1].note).toContain('timeout');
|
|
179
217
|
});
|
|
180
218
|
|
|
181
|
-
it('
|
|
182
|
-
const
|
|
219
|
+
it('limits failed tool_calls to 3 entries', () => {
|
|
220
|
+
const db = createTrajectoryDb();
|
|
221
|
+
insertSession(db, '123');
|
|
222
|
+
insertToolCall(db, '123', 'bash', 'failure', 'err1', 1, '2026-01-01T10:00:00Z');
|
|
223
|
+
insertToolCall(db, '123', 'bash', 'failure', 'err2', 2, '2026-01-01T10:01:00Z');
|
|
224
|
+
insertToolCall(db, '123', 'bash', 'failure', 'err3', 3, '2026-01-01T10:02:00Z');
|
|
225
|
+
insertToolCall(db, '123', 'bash', 'failure', 'err4', 4, '2026-01-01T10:03:00Z');
|
|
226
|
+
db.close();
|
|
227
|
+
|
|
228
|
+
const entries = entriesOf('123');
|
|
183
229
|
|
|
184
|
-
|
|
185
|
-
expect(
|
|
230
|
+
const failureEntries = entries.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
231
|
+
expect(failureEntries.length).toBe(3);
|
|
186
232
|
});
|
|
187
233
|
|
|
188
|
-
|
|
189
|
-
|
|
234
|
+
it('handles a missing tool_calls table gracefully (classified unavailable, no throw)', () => {
|
|
235
|
+
const dbPath = path.join(stateDir, 'trajectory.db');
|
|
236
|
+
const db = new Database(dbPath);
|
|
237
|
+
db.exec("CREATE TABLE IF NOT EXISTS sessions (session_id TEXT PRIMARY KEY, started_at TEXT, updated_at TEXT)");
|
|
238
|
+
db.exec("CREATE TABLE IF NOT EXISTS assistant_turns (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, sanitized_text TEXT, created_at TEXT NOT NULL)");
|
|
239
|
+
db.exec("CREATE TABLE IF NOT EXISTS user_turns (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, turn_index INTEGER NOT NULL DEFAULT 0, raw_excerpt TEXT, correction_detected INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL)");
|
|
240
|
+
db.prepare('INSERT INTO sessions (session_id, started_at, updated_at) VALUES (?, ?, ?)').run('123', '2026-01-01T09:00:00Z', '2026-01-01T09:00:00Z');
|
|
241
|
+
db.close();
|
|
242
|
+
|
|
243
|
+
const result = acquire('123');
|
|
244
|
+
expect(result.status).toBe('unavailable');
|
|
245
|
+
if (result.status !== 'unavailable') return;
|
|
246
|
+
expect(['empty_trajectory', 'evidence_read_failed']).toContain(result.reasonCode);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('includes resultPreview in tool failure evidence note', () => {
|
|
190
250
|
const db = createTrajectoryDb();
|
|
191
|
-
|
|
192
|
-
|
|
251
|
+
insertSession(db, '123');
|
|
252
|
+
insertToolCall(db, '123', 'bash', 'failure', 'ENOENT', 1, '2026-01-01T10:00:00Z', 'Error: no such file or directory');
|
|
193
253
|
db.close();
|
|
194
254
|
|
|
195
|
-
const
|
|
255
|
+
const entries = entriesOf('123');
|
|
196
256
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
expect(
|
|
200
|
-
expect(
|
|
257
|
+
const failureEntry = entries.find(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
258
|
+
expect(failureEntry).toBeDefined();
|
|
259
|
+
expect(failureEntry!.note).toContain('ENOENT');
|
|
260
|
+
expect(failureEntry!.note).toContain('Error: no such file or directory');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('includes truncation warning when stop_reason is length', () => {
|
|
264
|
+
const db = createTrajectoryDb();
|
|
265
|
+
insertSession(db, '123');
|
|
266
|
+
db.prepare("INSERT INTO assistant_turns (session_id, sanitized_text, stop_reason, created_at) VALUES (?, ?, ?, ?)")
|
|
267
|
+
.run('123', 'Partial output truncated...', 'length', '2026-01-01T10:00:00Z');
|
|
268
|
+
db.close();
|
|
269
|
+
|
|
270
|
+
const entries = entriesOf('123');
|
|
271
|
+
|
|
272
|
+
const agentEntry = entries.find(e => e.sourceRef.startsWith('agent_turn:'));
|
|
273
|
+
expect(agentEntry).toBeDefined();
|
|
274
|
+
expect(agentEntry!.note).toContain('Partial output truncated...');
|
|
275
|
+
expect(agentEntry!.note).toContain('[TRUNCATED: output cut off by length limit]');
|
|
201
276
|
});
|
|
202
277
|
|
|
203
|
-
|
|
204
|
-
it('returns trajectory:empty placeholder when DB has no turns for session', () => {
|
|
278
|
+
it('does not include truncation warning when stop_reason is end_turn', () => {
|
|
205
279
|
const db = createTrajectoryDb();
|
|
280
|
+
insertSession(db, '123');
|
|
281
|
+
db.prepare("INSERT INTO assistant_turns (session_id, sanitized_text, stop_reason, created_at) VALUES (?, ?, ?, ?)")
|
|
282
|
+
.run('123', 'Complete output', 'end_turn', '2026-01-01T10:00:00Z');
|
|
206
283
|
db.close();
|
|
207
284
|
|
|
208
|
-
const
|
|
285
|
+
const entries = entriesOf('123');
|
|
209
286
|
|
|
210
|
-
|
|
211
|
-
expect(
|
|
287
|
+
const agentEntry = entries.find(e => e.sourceRef.startsWith('agent_turn:'));
|
|
288
|
+
expect(agentEntry).toBeDefined();
|
|
289
|
+
expect(agentEntry!.note).toBe('Complete output');
|
|
290
|
+
expect(agentEntry!.note).not.toContain('[TRUNCATED');
|
|
212
291
|
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ── PRI-642 Scope A — typed acquisition from trajectory.db (SPEC §7.3) ───────
|
|
213
295
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
it('extracts failed tool_calls as evidence entries', () => {
|
|
218
|
-
const db = createTrajectoryDb();
|
|
219
|
-
insertToolCall(db, '123', 'bash', 'failure', 'non_zero_exit', 1, '2026-01-01T10:00:00Z');
|
|
220
|
-
insertToolCall(db, '123', 'write_file', 'success', null, 0, '2026-01-01T10:01:00Z');
|
|
221
|
-
insertToolCall(db, '123', 'bash', 'failure', 'timeout', 124, '2026-01-01T10:02:00Z');
|
|
222
|
-
db.close();
|
|
223
|
-
|
|
224
|
-
const evidence = buildTrajectoryEvidenceFromDb(stateDir, '123', tmpDir);
|
|
225
|
-
|
|
226
|
-
const failureEntries = evidence.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
227
|
-
expect(failureEntries.length).toBe(2);
|
|
228
|
-
expect(failureEntries[0].note).toContain('bash');
|
|
229
|
-
expect(failureEntries[0].note).toContain('non_zero_exit');
|
|
230
|
-
expect(failureEntries[1].note).toContain('timeout');
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('does not add tool_call_failure entries when no failures exist', () => {
|
|
234
|
-
const db = createTrajectoryDb();
|
|
235
|
-
insertToolCall(db, '123', 'bash', 'success', null, 0, '2026-01-01T10:00:00Z');
|
|
236
|
-
db.close();
|
|
237
|
-
|
|
238
|
-
const evidence = buildTrajectoryEvidenceFromDb(stateDir, '123', tmpDir);
|
|
239
|
-
|
|
240
|
-
const failureEntries = evidence.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
241
|
-
expect(failureEntries.length).toBe(0);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('limits failed tool_calls to 3 entries', () => {
|
|
245
|
-
const db = createTrajectoryDb();
|
|
246
|
-
insertToolCall(db, '123', 'bash', 'failure', 'err1', 1, '2026-01-01T10:00:00Z');
|
|
247
|
-
insertToolCall(db, '123', 'bash', 'failure', 'err2', 2, '2026-01-01T10:01:00Z');
|
|
248
|
-
insertToolCall(db, '123', 'bash', 'failure', 'err3', 3, '2026-01-01T10:02:00Z');
|
|
249
|
-
insertToolCall(db, '123', 'bash', 'failure', 'err4', 4, '2026-01-01T10:03:00Z');
|
|
250
|
-
db.close();
|
|
251
|
-
|
|
252
|
-
const evidence = buildTrajectoryEvidenceFromDb(stateDir, '123', tmpDir);
|
|
253
|
-
|
|
254
|
-
const failureEntries = evidence.filter(e => e.sourceRef.startsWith('tool_call_failure:'));
|
|
255
|
-
expect(failureEntries.length).toBe(3);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('handles missing tool_calls table gracefully', () => {
|
|
259
|
-
// Create DB without tool_calls table
|
|
260
|
-
const dbPath = path.join(stateDir, 'trajectory.db');
|
|
261
|
-
const db = new Database(dbPath);
|
|
262
|
-
db.exec(`
|
|
263
|
-
CREATE TABLE IF NOT EXISTS sessions (session_id TEXT PRIMARY KEY, started_at TEXT, updated_at TEXT)
|
|
264
|
-
`);
|
|
265
|
-
db.exec(`
|
|
266
|
-
CREATE TABLE IF NOT EXISTS assistant_turns (
|
|
267
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL,
|
|
268
|
-
sanitized_text TEXT, created_at TEXT NOT NULL
|
|
269
|
-
)
|
|
270
|
-
`);
|
|
271
|
-
db.exec(`
|
|
272
|
-
CREATE TABLE IF NOT EXISTS user_turns (
|
|
273
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL,
|
|
274
|
-
turn_index INTEGER NOT NULL DEFAULT 0, raw_excerpt TEXT,
|
|
275
|
-
correction_detected INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL
|
|
276
|
-
)
|
|
277
|
-
`);
|
|
278
|
-
db.close();
|
|
279
|
-
|
|
280
|
-
const evidence = buildTrajectoryEvidenceFromDb(stateDir, '123', tmpDir);
|
|
281
|
-
|
|
282
|
-
// Should not throw, should have some evidence (trajectory:empty or unavailable)
|
|
283
|
-
expect(evidence.length).toBeGreaterThan(0);
|
|
284
|
-
// Should NOT have tool_call_failure:unavailable since we have no other evidence
|
|
285
|
-
// and the table simply doesn't exist (not an error condition worth reporting)
|
|
286
|
-
});
|
|
296
|
+
describe('acquireTrajectoryEvidenceFromDb — typed CLI acquisition (PRI-642 Scope A)', () => {
|
|
297
|
+
beforeEach(() => {
|
|
298
|
+
createStateDir();
|
|
287
299
|
});
|
|
288
300
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
301
|
+
afterEach(() => {
|
|
302
|
+
try {
|
|
303
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
304
|
+
} catch {
|
|
305
|
+
// ignore cleanup errors
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('returns available with entries for a session that has turns', async () => {
|
|
310
|
+
const { acquireTrajectoryEvidenceFromDb } = await import('../../src/commands/build-trajectory-evidence.js');
|
|
311
|
+
const db = createTrajectoryDb();
|
|
312
|
+
// Turn writers upsert the sessions row — mirror the real DB shape.
|
|
313
|
+
db.prepare('INSERT INTO sessions (session_id, started_at, updated_at) VALUES (?, ?, ?)')
|
|
314
|
+
.run('real-session', '2026-01-01T09:00:00Z', '2026-01-01T09:00:00Z');
|
|
315
|
+
insertUserTurn(db, 'real-session', 'Owner correction', true, '2026-01-01T09:59:00Z');
|
|
316
|
+
insertAssistantTurn(db, 'real-session', 'assistant text', '2026-01-01T10:00:00Z');
|
|
317
|
+
db.close();
|
|
318
|
+
|
|
319
|
+
const result = acquireTrajectoryEvidenceFromDb(stateDir, 'real-session', tmpDir);
|
|
320
|
+
|
|
321
|
+
expect(result.status).toBe('available');
|
|
322
|
+
if (result.status !== 'available') return;
|
|
323
|
+
expect(result.entries.length).toBeGreaterThan(0);
|
|
324
|
+
expect(result.entries.some(e => e.sourceRef.startsWith('owner_message:'))).toBe(true);
|
|
325
|
+
expect(result.entries.some(e => e.sourceRef.startsWith('agent_turn:'))).toBe(true);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('returns unavailable/session_not_found for a session absent from the sessions table (SPEC 12.1.4)', async () => {
|
|
329
|
+
const { acquireTrajectoryEvidenceFromDb } = await import('../../src/commands/build-trajectory-evidence.js');
|
|
330
|
+
const db = createTrajectoryDb();
|
|
331
|
+
insertAssistantTurn(db, 'other-session', 'unrelated', '2026-01-01T10:00:00Z');
|
|
332
|
+
db.close();
|
|
333
|
+
|
|
334
|
+
const result = acquireTrajectoryEvidenceFromDb(stateDir, 'no-such-session', tmpDir);
|
|
335
|
+
|
|
336
|
+
expect(result.status).toBe('unavailable');
|
|
337
|
+
if (result.status !== 'unavailable') return;
|
|
338
|
+
expect(result.reasonCode).toBe('session_not_found');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('returns unavailable/session_not_found for the "cli" and "unknown" sentinels', async () => {
|
|
342
|
+
const { acquireTrajectoryEvidenceFromDb } = await import('../../src/commands/build-trajectory-evidence.js');
|
|
343
|
+
const db = createTrajectoryDb();
|
|
344
|
+
db.close();
|
|
345
|
+
|
|
346
|
+
const cliResult = acquireTrajectoryEvidenceFromDb(stateDir, 'cli', tmpDir);
|
|
347
|
+
const unknownResult = acquireTrajectoryEvidenceFromDb(stateDir, 'unknown', tmpDir);
|
|
348
|
+
|
|
349
|
+
expect(cliResult.status).toBe('unavailable');
|
|
350
|
+
expect(unknownResult.status).toBe('unavailable');
|
|
351
|
+
if (cliResult.status !== 'unavailable' || unknownResult.status !== 'unavailable') return;
|
|
352
|
+
expect(cliResult.reasonCode).toBe('session_not_found');
|
|
353
|
+
expect(unknownResult.reasonCode).toBe('session_not_found');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('returns unavailable/trajectory_unavailable when trajectory.db is missing', async () => {
|
|
357
|
+
const { acquireTrajectoryEvidenceFromDb } = await import('../../src/commands/build-trajectory-evidence.js');
|
|
358
|
+
|
|
359
|
+
const result = acquireTrajectoryEvidenceFromDb(stateDir, 'some-session', tmpDir);
|
|
360
|
+
|
|
361
|
+
expect(result.status).toBe('unavailable');
|
|
362
|
+
if (result.status !== 'unavailable') return;
|
|
363
|
+
expect(result.reasonCode).toBe('trajectory_unavailable');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('returns unavailable/empty_trajectory when the session exists but has no turns or tool calls', async () => {
|
|
367
|
+
const { acquireTrajectoryEvidenceFromDb } = await import('../../src/commands/build-trajectory-evidence.js');
|
|
368
|
+
const db = createTrajectoryDb();
|
|
369
|
+
db.prepare(`
|
|
370
|
+
INSERT INTO sessions (session_id, started_at, updated_at)
|
|
371
|
+
VALUES (?, ?, ?)
|
|
372
|
+
`).run('quiet-session', '2026-01-01T09:00:00Z', '2026-01-01T09:00:00Z');
|
|
373
|
+
db.close();
|
|
374
|
+
|
|
375
|
+
const result = acquireTrajectoryEvidenceFromDb(stateDir, 'quiet-session', tmpDir);
|
|
376
|
+
|
|
377
|
+
expect(result.status).toBe('unavailable');
|
|
378
|
+
if (result.status !== 'unavailable') return;
|
|
379
|
+
expect(result.reasonCode).toBe('empty_trajectory');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('returns a different reasonCode for unreadable DB vs empty trajectory (exec-prompt item 5)', async () => {
|
|
383
|
+
const { acquireTrajectoryEvidenceFromDb } = await import('../../src/commands/build-trajectory-evidence.js');
|
|
384
|
+
const db = createTrajectoryDb();
|
|
385
|
+
db.close();
|
|
386
|
+
// Corrupt the DB file after close so the read-only open fails.
|
|
387
|
+
const dbPath = path.join(stateDir, 'trajectory.db');
|
|
388
|
+
fs.writeFileSync(dbPath, Buffer.from('this is not a sqlite database at all'));
|
|
389
|
+
|
|
390
|
+
const unreadable = acquireTrajectoryEvidenceFromDb(stateDir, 'some-session', tmpDir);
|
|
391
|
+
|
|
392
|
+
const stateDir2 = createStateDir();
|
|
393
|
+
const db2 = createTrajectoryDb();
|
|
394
|
+
db2.close();
|
|
395
|
+
|
|
396
|
+
expect(unreadable.status).toBe('unavailable');
|
|
397
|
+
if (unreadable.status !== 'unavailable') return;
|
|
398
|
+
// Unreadable DB must not share a reasonCode with a real-but-empty session.
|
|
399
|
+
expect(unreadable.reasonCode).not.toBe('empty_trajectory');
|
|
400
|
+
expect(unreadable.reasonCode).toBe('evidence_read_failed');
|
|
336
401
|
});
|
|
337
402
|
});
|