diff --git a/src/main/claude/claude-transcript-catch-up.test.ts b/src/main/claude/claude-transcript-catch-up.test.ts index d7027f1cf1d..ecfbd806a9f 100644 --- a/src/main/claude/claude-transcript-catch-up.test.ts +++ b/src/main/claude/claude-transcript-catch-up.test.ts @@ -477,7 +477,7 @@ describe('Claude chat catch-up', () => { expect(c.warn).not.toHaveBeenCalled() }) - // P saved aliases plus P unacknowledged queued prompts: P×J scope work and one J-item index. + // P saved aliases plus P unacknowledged queued prompts: two J-item indexes, not P×J scans. it.each([10, 100].flatMap((prompts) => [1_000, 10_000].map((items) => ({ prompts, items }))))( 'bounds retained-leaf work for $prompts aliases and local misses over $items journal items', async ({ prompts, items }) => { @@ -569,8 +569,7 @@ describe('Claude chat catch-up', () => { expect(c.journal.canonicalItemId(alias)).toBe(canonical) expect(committed).not.toHaveBeenCalled() expect(c.warn).not.toHaveBeenCalled() - expect(associationVisits).toBeGreaterThan(prompts * itemCount) - expect(associationVisits).toBeLessThanOrEqual((prompts + 1) * itemCount) + expect(associationVisits).toBeLessThanOrEqual(2 * itemCount) expect(viewVisits).toBeLessThanOrEqual(2 * itemCount) console.info('retained-leaf operation counts', { prompts, diff --git a/src/main/claude/claude-transcript-item-reader.ts b/src/main/claude/claude-transcript-item-reader.ts index 93bb10caa63..3fbdcf55a35 100644 --- a/src/main/claude/claude-transcript-item-reader.ts +++ b/src/main/claude/claude-transcript-item-reader.ts @@ -15,6 +15,7 @@ export function createClaudeTranscriptItemReader( ) { const dependencies = new Map() let localPrompts: Map | undefined + let promptTurns: Map | undefined const saved = (key: string) => { const held = dependencies.get(key) if (held) { @@ -63,19 +64,28 @@ export function createClaudeTranscriptItemReader( const scope = scopeOf(item) return scope.kind === 'turn' ? scope : undefined } - let scope: AgentJournalTurnScope | undefined - journal.visitItems((itemId, _sequence, body) => { - if ( - body.kind === 'turn' && - body.userItemId && - journal.canonicalItemId(body.userItemId) === item.itemId - ) { - const turn = saved(itemId) - if (turn) { - scope = scopeOf({ ...item, turnScope: { kind: 'turn', turnItemId: itemId } }) + // One pass, like localPrompts: a stale entry cannot commit past the catch-up guard. + if (!promptTurns) { + const turns = new Map() + journal.visitItems((itemId, _sequence, body) => { + if (body.kind === 'turn' && body.userItemId) { + const user = journal.canonicalItemId(body.userItemId) + const held = turns.get(user) + if (held) { + held.push(itemId) + } else { + turns.set(user, [itemId]) + } } + }) + promptTurns = turns + } + let scope: AgentJournalTurnScope | undefined + for (const turnItemId of promptTurns.get(item.itemId) ?? []) { + if (saved(turnItemId)) { + scope = scopeOf({ ...item, turnScope: { kind: 'turn', turnItemId } }) } - }) + } return scope } return { saved, localPrompt, scopeOf, promptScope, dependencies }