diff --git a/packages/docs/src/content/docs/config/scan.mdx b/packages/docs/src/content/docs/config/scan.mdx index 07f72a6c..2f2300c9 100644 --- a/packages/docs/src/content/docs/config/scan.mdx +++ b/packages/docs/src/content/docs/config/scan.mdx @@ -41,7 +41,7 @@ maxFileLines = 3000 maxChangedLines number -
Max changed lines to analyze after ignores are applied. Default: 10000.
+
Max changed lines to analyze for PR and diff-based scans after ignores are applied. Explicit local file-target runs do not enforce this budget. Default: 10000.
@@ -59,6 +59,7 @@ maxFileLines = 3000
-When a run exceeds `maxFiles` or `maxChangedLines`, Warden scans files in the -existing diff order until the budget is exhausted and records the remaining -files as skipped. +When a PR or diff-based run exceeds `maxFiles` or `maxChangedLines`, Warden +scans files in the existing diff order until the budget is exhausted and records +the remaining files as skipped. Explicit local file-target runs still enforce +`maxFiles`, but not `maxChangedLines`. diff --git a/packages/warden/src/cli/context.ts b/packages/warden/src/cli/context.ts index c331b070..fc0af12a 100644 --- a/packages/warden/src/cli/context.ts +++ b/packages/warden/src/cli/context.ts @@ -150,6 +150,7 @@ export async function buildFileEventContext(options: FileContextOptions): Promis files, }, diffContextSource: { type: 'working-tree' }, + explicitFileTargets: true, repoPath: cwd, }; } diff --git a/packages/warden/src/sdk/prepare.test.ts b/packages/warden/src/sdk/prepare.test.ts index 3bb58799..7459c84d 100644 --- a/packages/warden/src/sdk/prepare.test.ts +++ b/packages/warden/src/sdk/prepare.test.ts @@ -5,7 +5,7 @@ import { join } from 'node:path'; import { afterEach, describe, it, expect } from 'vitest'; import { prepareFiles } from './prepare.js'; import type { EventContext, FileChange } from '../types/index.js'; -import { buildLocalEventContext } from '../cli/context.js'; +import { buildFileEventContext, buildLocalEventContext } from '../cli/context.js'; function makeContext( files: { @@ -283,6 +283,43 @@ describe('prepareFiles', () => { }); }); + it('does not apply changed-line budget to explicit file targets', async () => { + const repoPath = mkdtempSync(join(tmpdir(), 'warden-file-targets-')); + tempDirs.push(repoPath); + mkdirSync(join(repoPath, 'src'), { recursive: true }); + writeFileSync(join(repoPath, 'src/one.ts'), 'one\ntwo\nthree'); + writeFileSync(join(repoPath, 'src/two.ts'), 'one\ntwo\nthree'); + + const context = await buildFileEventContext({ + patterns: ['src/*.ts'], + cwd: repoPath, + }); + + const result = prepareFiles(context, { + scan: { maxChangedLines: 3 }, + }); + + expect(result.files.map((file) => file.filename)).toEqual(['src/one.ts', 'src/two.ts']); + expect(result.skippedFiles).toEqual([]); + }); + + it('keeps file-count budget for explicit file targets', () => { + const context = makeContext([ + { filename: 'src/one.ts', patch: '@@ -0,0 +1,1 @@\n+one', additions: 1 }, + { filename: 'src/two.ts', patch: '@@ -0,0 +1,1 @@\n+two', additions: 1 }, + ]); + context.explicitFileTargets = true; + + const result = prepareFiles(context, { + scan: { maxFiles: 1, maxChangedLines: 1 }, + }); + + expect(result.files.map((file) => file.filename)).toEqual(['src/one.ts']); + expect(result.skippedFiles).toEqual([ + { filename: 'src/two.ts', reason: 'limit:file_count' }, + ]); + }); + it('skips patchless files before applying changed-line budget', () => { const context = makeContext([ { filename: 'src/large-diff.ts', additions: 10 }, diff --git a/packages/warden/src/sdk/prepare.ts b/packages/warden/src/sdk/prepare.ts index 84808b91..d79e9ca0 100644 --- a/packages/warden/src/sdk/prepare.ts +++ b/packages/warden/src/sdk/prepare.ts @@ -58,6 +58,7 @@ export function prepareFiles( ignore: options.ignore, scan: options.scan, diffContextSource: context.diffContextSource, + enforceChangedLineBudget: context.explicitFileTargets !== true, }); skippedFiles.push(...scanPolicy.skippedFiles); diff --git a/packages/warden/src/sdk/scan-policy.ts b/packages/warden/src/sdk/scan-policy.ts index 1150de3a..7e097966 100644 --- a/packages/warden/src/sdk/scan-policy.ts +++ b/packages/warden/src/sdk/scan-policy.ts @@ -83,6 +83,8 @@ export interface ScanPolicyOptions { ignore?: IgnoreConfig; scan?: ScanConfig; diffContextSource?: DiffContextSource; + /** Whether to enforce PR/diff changed-line scan budgets. Defaults to true. */ + enforceChangedLineBudget?: boolean; } export type ScannableFileChange = FileChange & { patch: string }; @@ -387,6 +389,7 @@ export function applyScanPolicy( ): ApplyScanPolicyResult { const scan = effectiveScanConfig(options.scan); const diffContextSource = options.diffContextSource ?? { type: 'working-tree' }; + const enforceChangedLineBudget = options.enforceChangedLineBudget ?? true; const skippedFiles: SkippedFile[] = []; const eligible: ScannableFileChange[] = []; @@ -418,7 +421,7 @@ export function applyScanPolicy( } const fileChangedLines = changedLines(file); - if (consumedChangedLines + fileChangedLines > scan.maxChangedLines) { + if (enforceChangedLineBudget && consumedChangedLines + fileChangedLines > scan.maxChangedLines) { skippedFiles.push({ filename: file.filename, reason: 'limit:changed_lines' }); continue; } diff --git a/packages/warden/src/triggers/matcher.test.ts b/packages/warden/src/triggers/matcher.test.ts index f7976c9b..360588f3 100644 --- a/packages/warden/src/triggers/matcher.test.ts +++ b/packages/warden/src/triggers/matcher.test.ts @@ -460,10 +460,18 @@ describe('filterContextByPaths', () => { }); it('preserves other context fields', () => { - const result = filterContextByPaths(baseContext, { paths: ['src/**/*.ts'] }); - expect(result.eventType).toBe(baseContext.eventType); - expect(result.repository).toBe(baseContext.repository); - expect(result.repoPath).toBe(baseContext.repoPath); + const context: EventContext = { + ...baseContext, + diffContextSource: { type: 'working-tree' }, + explicitFileTargets: true, + }; + + const result = filterContextByPaths(context, { paths: ['src/**/*.ts'] }); + expect(result.eventType).toBe(context.eventType); + expect(result.repository).toBe(context.repository); + expect(result.repoPath).toBe(context.repoPath); + expect(result.diffContextSource).toBe(context.diffContextSource); + expect(result.explicitFileTargets).toBe(true); expect(result.pullRequest!.title).toBe('Test PR'); }); }); diff --git a/packages/warden/src/types/index.ts b/packages/warden/src/types/index.ts index 6aed9bb2..828cad55 100644 --- a/packages/warden/src/types/index.ts +++ b/packages/warden/src/types/index.ts @@ -446,6 +446,7 @@ export const EventContextSchema = z.object({ pullRequest: PullRequestContextSchema.optional(), repoPath: z.string(), diffContextSource: DiffContextSourceSchema.optional(), + explicitFileTargets: z.boolean().optional(), }); export type EventContext = z.infer;