Repository navigation
fix: skip the Kiro rescan when the database has not changed (#178) - #179
Conversation
…n#178) modifiedSince is applied after the JSON parse, so an unchanged store still pays the full read. Gate on the existing Antigravity file signature (db + wal, not shm) and return empty so the merge cache keeps what it already has.
|
Thanks @Borisserz — reusing Requesting one change before merge. That change is on me. The spec in #178 was incomplete#178 said a 1. The skip survives a month rollover that empties
|
|
Took (b). The skip is now an argument, not a process-lifetime map.
On (2):
|
|
Thank you for your contribution! |
…n#178) (chattymin#179) * fix: skip the Kiro rescan when the database has not changed (chattymin#178) modifiedSince is applied after the JSON parse, so an unchanged store still pays the full read. Gate on the existing Antigravity file signature (db + wal, not shm) and return empty so the merge cache keeps what it already has. * fix: keep the Kiro skip signature with the cached entries (chattymin#178) A process-lifetime skip map survived the month-key drop that empties `existing`, so the first poll on the 1st returned [] and zeroed the week/block window. Signatures now live on Cached next to the entries they justify.
Summary
LocalAdditionalUsageReader.kiroEntriesre-reads and re-parses every stored Kiro conversation on every poll. Unlike Cursor and Copilot, Kiro rewrites a conversation's whole history JSON in place, so there is no monotonic row id. That was the right call for #170; the cost is this issue.modifiedSinceis applied insidekiroTurnEntries, after the row has been read out of SQLite andjsonObjecthas parsed it. Narrowing the window skips entry construction, not the read or the parse. Owner measured 209–360 ms on a 30×80 synthetic database, and a 37 ms floor on an empty window.The cheap gate is the file's own signature. This reuses
LocalAntigravityUsageReader.signature(main file +-wal, never-shm) rather than copying it — a WAL commit leaves the main file's mtime alone, and a read-only connection writes read marks into-shm, which would make the skip invalidate itself on every poll.An unchanged database returns
[]. The.kirocache mergesexisting + loaded, so a scan that returns nothing keeps the cached entries. A failed open (BUSY / corrupt) does not occupy the skip slot.Skip state is not a process-lifetime map. Signatures live on
LocalAdditionalUsageCache.Cachednext to the entries they justify (option (b) from the #179 review — the same shape Antigravity uses). The actor passesprevious?.kiroSignatures ?? [:]. A month-key drop setspreviousto nil, so bothexistingand the skip die together. Without that, the first poll on the 1st returns[],dedupKeepMax([] + [])is[], and the week/block window that still reaches into last month reads 0 until Kiro next writes.The
[Entry]overload always passes[:], so a one-shot read (and the parse-stability test) actually re-reads.Class recorded in
docs/reference/defect-log.md: a filter applied after parsing bounds the output, not the work; a skip that outlivesexistingzeros the window the skip was supposed to keep.Type of change
UI changes
No visible change. Totals stay the same; an unchanged Kiro database is no longer fully re-parsed every 120s. Month rollover no longer drops last-month Kiro turns from the week/block totals.
Checklist
swift buildandswift testpass locallyTests
KiroUsageTests— each branch alone:testUnchangedDatabaseSkipsTheRescan— the The Kiro reader re-parses every conversation on every poll, and narrowing the window does not help #178 cost. Second scan with the first scan's signatures returns[];dedupKeepMax(existing + loaded)keeps the first scan's ids.testWalCommitForcesARescan— a newer-walsibling must invalidate the skip. Keying ondata.sqlite3alone would stay green here.testShmChurnDoesNotForceARescan—-shmis a read mark, not a write. Including it would make hit rate 0.testFailedOpenDoesNotOccupyTheSkipSlot— garbage bytes must not appear in the returnedsignaturesmap, or a later valid file with a lucky-matching mtime would be treated as empty forever.testRescanningTheSameDatabaseProducesStableEntryIDs— still a parse-stability test. The[Entry]overload passes no known signatures, so it cannot pass by returning[].testSkipDoesNotSurviveAnEmptyExistingSet— the fix: skip the Kiro rescan when the database has not changed (#178) #179 review reproduction. One Aug 31 turn; August scan sees it; September week window (modifiedSinceAug 30) with emptyexistingmust still see it. Confirmed red against the process-lifetime cache at4c27d3b.testSkipStateIsPassedByTheCallerNotHeldByTheReader— source lock: noKiroDatabaseScanCache/clearKiroScanCache/recordedKiroSignature; the actor readsprevious?.kiroSignatures.swift test --filter KiroUsageTests: 29 tests, 0 failures.Full suite: 682 pass, 9 skipped, 0 failures.
How to verify
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ swift test --filter KiroUsageTestsNo UI steps. A machine with a large Kiro
data.sqlite3should see the second poll of an idle session return immediately instead of re-parsing every conversation. Crossing the 1st with an unchanged Kiro database must still show last week's turns in the week/block totals.