Cursor transcript sources have two legacy layouts and select .jsonl over .txt when both exist for a session. Moving Cursor behind a concrete provider keeps that selection policy explicit at the provider boundary instead of relying on the legacy parser adapter.\n\nThe provider preserves recursive project discovery, raw/full ID lookup, stale .txt path promotion, changed-path classification, content-hash fingerprinting, and parser output normalization while using the same Cursor discovery and parsing helpers as the previous sync path.
fix(parser): preserve cursor project-scoped source selection
Cursor session IDs are only unique within an encoded project directory, but the provider was resolving stored and changed paths through a root-wide lookup. That could silently select the same transcript stem from a different project and drop valid sources during discovery.
Resolve Cursor source promotion inside the project derived from the incoming path, add duplicate-stem coverage, and mark model output unsupported until the parser actually fills message models. This lets the Cursor branch enter shadow comparison as a real migration step.
Validation: go test -tags "fts5" ./internal/parser -run 'Test(CursorProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare cursor shadow parity
Cursor is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseCursorSession.
The test uses duplicate transcript stems in different encoded project directories to lock in the current parser ID behavior while proving provider source observation stays project-scoped.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesCursorLegacyParser|TestCursorProvider|TestParseCursor|TestCursorSessionID' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...
test(sync): assert cursor provider hash parity
Roborev job 2709 caught that the Cursor shadow parity fixture normalized the legacy session hash before proving the provider fingerprint matched the legacy parser hash. That left the test unable to detect a provider fingerprint regression that propagated into parsed output.
Assert hash parity before normalizing the legacy session for the full struct comparison, keeping the existing duplicate-stem fixture focused on provider/legacy equivalence.
Validation: go test -tags "fts5" ./internal/sync -run TestObserveProviderSourceMatchesCursorLegacyParser -count=1; go fmt ./...; go vet ./...; git diff --check
refactor(parser): fold cursor into provider
Move Cursor source discovery, lookup, and parse ownership onto the
concrete cursorProvider and remove the package-level
DiscoverCursorSessions, FindCursorSourceFile, and ParseCursorSession
free functions. Discovery and find-source bodies now live as
provider-owned helpers (discoverTranscriptPaths, cursorAddSeen,
cursorFindSourceFile) on the cursor source set, and parseSession is a
receiver method.
Make Cursor provider-authoritative and drop its legacy sync dispatch:
the classifyOnePath transcript block, the processFile case arm, the
processCursor method, and its now-orphaned validateCursorContainment
and findContainingDir helpers. Source classification, containment,
.txt/.jsonl precedence, and project-hint decoding are all reproduced
through the provider's changed-path and discovery paths, so runtime
behavior is preserved. ParseCursorTranscriptRelPath stays a shared
provider-neutral path validator used by both the engine's project
enrichment and the provider.
Replace the shadow-baseline test with provider API coverage plus a
guard asserting the legacy entrypoints stay gone, and remove cursor
from the pending-shim list.
fix(parser): cap cursor provider fingerprinting
Cursor parsing already rejects transcripts over 10 MiB, but the migrated provider fingerprint path still hashed the full source before parse. That made oversized files pay an unbounded read cost in the provider freshness path even though parse would never accept them.\n\nKeep normal-size content hashing intact and return only metadata for oversized Cursor transcripts so parse remains the sole place that reads up to the guarded cap.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'TestCursorProvider' -count=1; go vet ./...; git diff --check
Cursor now uses a concrete provider instead of the legacy adapter. The provider keeps the existing transcript selection rules, including recursive project discovery, nested transcript layouts, .jsonl preference over .txt, stale-path lookup promotion, changed-path classification, and content-hash fingerprinting.
The implementation reuses the existing Cursor discovery and parsing helpers so parser output remains aligned with the previous sync path while making Cursor source behavior explicit at the provider boundary.