fix(optimize): exclude a young project's founding session from cost-outlier detection (#666)

A project's first/bootstrap session is a different kind of work (one-off
scaffolding), not an expensive instance of routine work. While a project is
still young its peer sample is thin, so the leave-one-out average is dominated
by a few routine sessions and the founding session's legitimately high one-off
cost trips the >2x outlier multiplier. Exclude each young project's earliest
costed session from the outlier finding only; mature projects are unaffected so
genuine outliers still surface. Young = fewer than 2x MIN_SESSIONS_FOR_OUTLIER
costed sessions.

Fixes #664
This commit is contained in:
ozymandiashh 2026-07-16 12:03:06 +03:00 committed by GitHub
parent e2cba003a8
commit 4ef32c27cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 90 additions and 2 deletions

View file

@ -82,6 +82,10 @@ const MCP_NEW_CONFIG_GRACE_MS = 24 * 60 * 60 * 1000
const BASH_DEFAULT_LIMIT = 30000
const BASH_RECOMMENDED_LIMIT = 15000
const MIN_SESSIONS_FOR_OUTLIER = 3
// A project is still bootstrapping until it has twice the minimum sessions
// needed to evaluate outliers; below that, its peer average is too thin to
// distinguish founding work from waste.
const YOUNG_PROJECT_SESSION_LIMIT = 2 * MIN_SESSIONS_FOR_OUTLIER
const SESSION_OUTLIER_MULTIPLIER = 2
const MIN_SESSION_OUTLIER_COST_USD = 1
const SESSION_OUTLIER_PREVIEW = 5
@ -2267,6 +2271,29 @@ export function detectSessionOutliers(projects: ProjectSummary[], excludedSessio
}
}
function findYoungProjectFirstSessionIds(projects: ProjectSummary[]): Set<string> {
const firstSessionIds = new Set<string>()
for (const project of projects) {
const costed = project.sessions.filter(s => s.totalCostUSD > 0)
if (costed.length >= YOUNG_PROJECT_SESSION_LIMIT) continue
let firstSession: ProjectSummary['sessions'][number] | null = null
for (const session of costed) {
if (
firstSession === null
|| new Date(session.firstTimestamp).getTime() < new Date(firstSession.firstTimestamp).getTime()
) {
firstSession = session
}
}
if (firstSession) firstSessionIds.add(firstSession.sessionId)
}
return firstSessionIds
}
// ============================================================================
// Scoring
// ============================================================================
@ -2391,7 +2418,8 @@ export async function scanAndDetect(
.filter(c => !lowWorthSessionIds.has(c.sessionId))
.map(c => c.sessionId),
)
const outlierExclusions = new Set([...lowWorthSessionIds, ...contextBloatVisibleIds])
const firstSessionIds = findYoungProjectFirstSessionIds(projects)
const outlierExclusions = new Set([...lowWorthSessionIds, ...contextBloatVisibleIds, ...firstSessionIds])
const syncDetectors: Array<() => WasteFinding | null> = [
() => detectCacheBloat(apiCalls, projects, dateRange),
() => detectLowReadEditRatio(toolCalls),

View file

@ -1,4 +1,15 @@
import { describe, it, expect } from 'vitest'
import { describe, it, expect, vi } from 'vitest'
vi.mock('../src/providers/index.js', async (importOriginal) => {
type ProvidersModule = typeof import('../src/providers/index.js')
const actual = await importOriginal<ProvidersModule>()
return {
...actual,
async discoverAllSessions() {
return []
},
}
})
import {
detectJunkReads,
@ -10,6 +21,7 @@ import {
detectCapabilityReliability,
detectLowWorthSessions,
detectSessionOutliers,
scanAndDetect,
computeHealth,
computeTrend,
buildOptimizeJsonReport,
@ -61,6 +73,22 @@ function projectWithSessions(costs: number[], project = 'app'): ProjectSummary {
}
}
function projectWithDeliveredSessions(costs: number[], project = 'app'): ProjectSummary {
const summary = projectWithSessions(costs, project)
for (const session of summary.sessions) {
session.bashBreakdown = { 'git commit -m test': { calls: 1 } }
}
return summary
}
function optimizeDateRange(day: number) {
const padded = String(day).padStart(2, '0')
return {
start: new Date(`2026-06-${padded}T00:00:00Z`),
end: new Date(`2026-06-${padded}T23:59:59Z`),
}
}
type TestSession = ProjectSummary['sessions'][number]
function contextSession(
@ -978,6 +1006,38 @@ describe('detectSessionOutliers', () => {
expect(finding).not.toBeNull()
expect(finding!.explanation).toContain('app/s4')
})
it('scanAndDetect excludes the earliest high-cost session while the project is young', async () => {
const result = await scanAndDetect(
[projectWithDeliveredSessions([20, 1, 1, 1])],
optimizeDateRange(1),
)
const finding = result.findings.find(f => f.id === 'cost-outliers')
expect(finding).toBeUndefined()
})
it('scanAndDetect still flags a later high-cost session while the project is young', async () => {
const result = await scanAndDetect(
[projectWithDeliveredSessions([1, 20, 1, 1])],
optimizeDateRange(2),
)
const finding = result.findings.find(f => f.id === 'cost-outliers')
expect(finding).toBeDefined()
expect(finding!.explanation).toContain('app/s2')
})
it('scanAndDetect still flags the earliest high-cost session once the project is mature', async () => {
const result = await scanAndDetect(
[projectWithDeliveredSessions([20, 1, 1, 1, 1, 1])],
optimizeDateRange(3),
)
const finding = result.findings.find(f => f.id === 'cost-outliers')
expect(finding).toBeDefined()
expect(finding!.explanation).toContain('app/s1')
})
})
describe('computeHealth', () => {