Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3595,21 +3595,45 @@ export function correlateCrossProviderPrSessions(projects: ProjectSummary[]): vo
// Prompt-linked sessions become valid cwd anchors too. Attribute only when an
// exact cwd maps to one PR set; a main checkout used for multiple PRs remains
// intentionally ambiguous.
const refsByCwd = new Map<string, Map<string, string[]>>()
//
// Time-bounded (the eywa#160 lesson): cwd evidence also carries the evidence
// sessions' own activity window, and only sessions OVERLAPPING that window
// (plus a pad) inherit the PR. Without the bound, a repo whose only captured
// PR link was pasted once became a black hole — every session ever run in
// that checkout, a month of unrelated work included, was attributed to it
// (129 of 131 sessions, ~$7.4K direct, observed on real data). The rule's
// charter is "a tool session launched around PR work in this checkout",
// which is inherently a same-working-stretch claim.
const CWD_WINDOW_PAD_MS = 6 * 60 * 60 * 1000
type CwdAnchor = { refs: string[]; startMs: number; endMs: number }
const refsByCwd = new Map<string, Map<string, CwdAnchor>>()
for (const [session, evidenceRefs] of evidence) {
const cwd = normalizedWorkingDirectory(session.workingDirectory)
if (!cwd || evidenceRefs.length !== 1) continue
const startMs = Date.parse(session.firstTimestamp)
const endMs = Date.parse(session.lastTimestamp)
if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) continue
const refs = evidenceRefs.slice().sort()
const sets = refsByCwd.get(cwd) ?? new Map<string, string[]>()
sets.set(refs.join('\0'), refs)
const key = refs.join('\0')
const sets = refsByCwd.get(cwd) ?? new Map<string, CwdAnchor>()
const existing = sets.get(key)
sets.set(key, existing
? { refs, startMs: Math.min(existing.startMs, startMs), endMs: Math.max(existing.endMs, endMs) }
: { refs, startMs, endMs })
refsByCwd.set(cwd, sets)
}
for (const session of sessions) {
if (session.prLinks?.length || session.parentSessionId) continue
const cwd = normalizedWorkingDirectory(session.workingDirectory)
if (!cwd) continue
const sets = refsByCwd.get(cwd)
if (sets?.size === 1) assignCorrelatedPrs(session, [...sets.values()][0]!, 'working-directory')
if (sets?.size !== 1) continue
const anchor = [...sets.values()][0]!
const sessionStart = Date.parse(session.firstTimestamp)
const sessionEnd = Date.parse(session.lastTimestamp)
if (!Number.isFinite(sessionStart) || !Number.isFinite(sessionEnd)) continue
if (sessionEnd < anchor.startMs - CWD_WINDOW_PAD_MS || sessionStart > anchor.endMs + CWD_WINDOW_PAD_MS) continue
assignCorrelatedPrs(session, anchor.refs, 'working-directory')
}
}

Expand Down
25 changes: 25 additions & 0 deletions tests/cross-provider-pr-correlation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,28 @@ describe('cross-provider PR correlation', () => {
expect(codex.prAttributionSource).toBe('launcher-prompt')
})
})

describe('working-directory correlation is time-bounded (eywa#160 regression)', () => {
it('attributes a same-cwd session inside the evidence window, never one weeks later', () => {
// One session in the repo genuinely produced the PR link. A tool session
// an hour later in the same checkout is PR work; a session three weeks
// later in the same checkout is just... work. Before the bound, the
// later session (and 128 real siblings) inherited the PR, attributing a
// month of unrelated spend ($7.4K observed) to a single PR row.
const linked = session({ id: 'anchor', provider: 'claude', timestamp: '2026-07-11T10:00:00Z', message: 'open the PR', refs: [A], cwd: '/repo/eywa' })
const nearby = session({ id: 'nearby', provider: 'codex', timestamp: '2026-07-11T11:30:00Z', message: 'run the review suite for the change', cwd: '/repo/eywa' })
const weeksLater = session({ id: 'later', provider: 'codex', timestamp: '2026-08-02T09:00:00Z', message: 'completely unrelated feature work', cwd: '/repo/eywa' })
correlateCrossProviderPrSessions([project([linked, nearby, weeksLater])])
expect(nearby.prLinks).toEqual([A])
expect(nearby.prAttributionSource).toBe('working-directory')
expect(weeksLater.prLinks).toBeUndefined()
})

it('widens the window across multiple evidence sessions for the same PR', () => {
const early = session({ id: 'e1', provider: 'claude', timestamp: '2026-07-11T10:00:00Z', message: 'open', refs: [A], cwd: '/repo/eywa' })
const late = session({ id: 'e2', provider: 'claude', timestamp: '2026-07-14T10:00:00Z', message: 'follow-up', refs: [A], cwd: '/repo/eywa' })
const between = session({ id: 'mid', provider: 'codex', timestamp: '2026-07-12T12:00:00Z', message: 'work in between the two linked sessions', cwd: '/repo/eywa' })
correlateCrossProviderPrSessions([project([early, late, between])])
expect(between.prLinks).toEqual([A])
})
})
Loading