| export const GENERATED_FILE_PATTERNS = [
|
|
|
| 'node_modules/',
|
| '.next/',
|
| 'dist/',
|
| 'build/',
|
| 'coverage/',
|
| '.turbo/',
|
| '.cache/',
|
| '.git/',
|
| '.nuxt/',
|
| '.output/',
|
| '.vercel/',
|
| '.netlify/',
|
| '.serverless/',
|
|
|
|
|
| 'package-lock.json',
|
| 'yarn.lock',
|
| 'pnpm-lock.yaml',
|
| 'bun.lockb',
|
| 'composer.lock',
|
| 'Gemfile.lock',
|
| 'Cargo.lock',
|
| 'poetry.lock',
|
| 'requirements.txt',
|
| 'Pipfile.lock',
|
| 'go.sum',
|
|
|
|
|
| '*.min.js',
|
| '*.min.css',
|
| '*.min.html',
|
|
|
|
|
| '*.map',
|
| '*.js.map',
|
| '*.css.map',
|
|
|
|
|
| '*.log',
|
| 'npm-debug.log*',
|
| 'yarn-debug.log*',
|
| 'yarn-error.log*',
|
| 'pnpm-debug.log*',
|
|
|
|
|
| 'vendor/',
|
| 'generated/',
|
| '__generated__/',
|
| '.generated/',
|
| 'out/',
|
| 'artifacts/',
|
| '.artifact/',
|
|
|
|
|
| '*.snap',
|
| '*.snap.jsx',
|
| '*.snap.tsx',
|
|
|
|
|
| '*.svg',
|
| '*.png',
|
| '*.jpg',
|
| '*.jpeg',
|
| '*.gif',
|
| '*.webp',
|
| '*.ico',
|
| '*.bmp',
|
| '*.tiff',
|
|
|
|
|
| '*.woff',
|
| '*.woff2',
|
| '*.ttf',
|
| '*.eot',
|
| '*.otf',
|
|
|
|
|
| '*.pdf',
|
| '*.zip',
|
| '*.tar',
|
| '*.tar.gz',
|
| '*.rar',
|
| '*.7z',
|
|
|
|
|
| '*.bundle.js',
|
| '*.chunk.js',
|
| '*.chunk.css',
|
|
|
|
|
| '.vscode/',
|
| '.idea/',
|
| '*.swp',
|
| '*.swo',
|
| '*~',
|
| '.DS_Store',
|
| 'Thumbs.db',
|
|
|
|
|
| '.env',
|
| '.env.*',
|
| '*.env',
|
| '.env.local',
|
| '.env.development.local',
|
| '.env.test.local',
|
| '.env.production.local',
|
|
|
|
|
| '.github/workflows/*.log',
|
| '.gitlab-ci.yml',
|
| 'Jenkinsfile',
|
| 'azure-pipelines.yml'
|
| ]
|
|
|
| export const DIFF_LIMITS = {
|
| MAX_DIFF_CHARS: 4000,
|
| MAX_TOTAL_DIFF_CHARS: 15000,
|
| MAX_FILE_SIZE_CHARS: 100000
|
| } as const
|
|
|
| export const FILE_TYPE_PRIORITY = {
|
|
|
| HIGH: [
|
| '.ts',
|
| '.tsx',
|
| '.js',
|
| '.jsx',
|
| '.py',
|
| '.go',
|
| '.rs',
|
| '.java',
|
| '.kt',
|
| '.swift',
|
| '.rb',
|
| '.php',
|
| '.cs',
|
| '.cpp',
|
| '.c',
|
| '.h'
|
| ],
|
|
|
| MEDIUM: [
|
| '.json',
|
| '.yaml',
|
| '.yml',
|
| '.toml',
|
| '.ini',
|
| '.conf',
|
| '.config',
|
| '.xml'
|
| ],
|
|
|
| LOW: [
|
| '.md',
|
| '.txt',
|
| '.svg',
|
| '.png',
|
| '.jpg',
|
| '.jpeg',
|
| '.gif',
|
| '.webp',
|
| '.ico',
|
| '.woff',
|
| '.woff2',
|
| '.ttf',
|
| '.eot'
|
| ]
|
| } as const
|
|
|
| export const isGeneratedFile = (filePath: string): boolean => {
|
| const normalizedPath = filePath.replace(/\\/g, '/')
|
|
|
| for (const pattern of GENERATED_FILE_PATTERNS) {
|
| if (pattern.includes('*')) {
|
|
|
| const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*')
|
| const regex = new RegExp(regexPattern)
|
| if (regex.test(normalizedPath)) {
|
| return true
|
| }
|
| } else if (pattern.endsWith('/')) {
|
|
|
| if (
|
| normalizedPath.startsWith(pattern) ||
|
| normalizedPath.includes(`/${pattern}`)
|
| ) {
|
| return true
|
| }
|
| } else {
|
|
|
| if (
|
| normalizedPath === pattern ||
|
| normalizedPath.endsWith(`/${pattern}`)
|
| ) {
|
| return true
|
| }
|
| }
|
| }
|
|
|
| return false
|
| }
|
|
|
| export const getFilePriority = (
|
| filePath: string
|
| ): 'high' | 'medium' | 'low' => {
|
| const ext = filePath.split('.').pop()?.toLowerCase()
|
|
|
| if (!ext) return 'medium'
|
|
|
| const extWithDot = `.${ext}` as any
|
| if (FILE_TYPE_PRIORITY.HIGH.includes(extWithDot)) return 'high'
|
| if (FILE_TYPE_PRIORITY.MEDIUM.includes(extWithDot)) return 'medium'
|
| if (FILE_TYPE_PRIORITY.LOW.includes(extWithDot)) return 'low'
|
|
|
| return 'medium'
|
| }
|
|
|
| export const isDiffTooLarge = (diff: string): boolean => {
|
| return diff.length > DIFF_LIMITS.MAX_DIFF_CHARS
|
| }
|
|
|
| export const isTotalDiffTooLarge = (totalChars: number): boolean => {
|
| return totalChars > DIFF_LIMITS.MAX_TOTAL_DIFF_CHARS
|
| }
|
|
|
| export const generateIgnoredFileNotice = (filePath: string): string => {
|
| const normalizedPath = filePath.replace(/\\/g, '/')
|
|
|
|
|
| if (
|
| normalizedPath.includes('package-lock.json') ||
|
| normalizedPath.includes('yarn.lock') ||
|
| normalizedPath.includes('pnpm-lock.yaml') ||
|
| normalizedPath.includes('bun.lockb')
|
| ) {
|
| return `⚠️ Generated dependency file skipped: ${filePath}
|
|
|
| Large generated dependency files are excluded from AI review to reduce token usage and avoid noisy analysis.
|
|
|
| Recommendation:
|
| - Only commit lockfile changes when dependencies intentionally changed.
|
| - Avoid unnecessary lockfile churn.
|
|
|
| Suggested AI agent prompt:
|
| "Review package.json changes and regenerate lockfile only if dependency versions were intentionally updated."`
|
| }
|
|
|
|
|
| if (normalizedPath.includes('node_modules/')) {
|
| return `⚠️ Generated dependency directory skipped: ${filePath}
|
|
|
| Generated dependencies are excluded from review and planning pipelines.`
|
| }
|
|
|
|
|
| if (
|
| normalizedPath.includes('dist/') ||
|
| normalizedPath.includes('build/') ||
|
| normalizedPath.includes('.next/') ||
|
| normalizedPath.includes('.nuxt/')
|
| ) {
|
| return `⚠️ Build artifact skipped: ${filePath}
|
|
|
| Build output directories are excluded from AI review as they are generated from source code.`
|
| }
|
|
|
|
|
| return `⚠️ Generated file skipped: ${filePath}
|
|
|
| This file matches generated file patterns and is excluded from AI review to reduce token usage.`
|
| }
|
|
|
| export interface FilterMetrics {
|
| ignoredFiles: string[]
|
| totalCharsSkipped: number
|
| largestPreventedPayload: number
|
| }
|
|
|
| export const filterMetrics: FilterMetrics = {
|
| ignoredFiles: [],
|
| totalCharsSkipped: 0,
|
| largestPreventedPayload: 0
|
| }
|
|
|
| export const logIgnoredFile = (
|
| filePath: string,
|
| charsSkipped: number = 0
|
| ): void => {
|
| console.log('IGNORED_GENERATED_FILE', {filePath, charsSkipped})
|
| filterMetrics.ignoredFiles.push(filePath)
|
| filterMetrics.totalCharsSkipped += charsSkipped
|
| filterMetrics.largestPreventedPayload = Math.max(
|
| filterMetrics.largestPreventedPayload,
|
| charsSkipped
|
| )
|
| }
|
|
|
| export const logTokenSavings = (): void => {
|
| const estimatedTokens = Math.round(filterMetrics.totalCharsSkipped / 4)
|
| console.log('FILTERED_TOKEN_SAVINGS_ESTIMATE', {
|
| filesSkipped: filterMetrics.ignoredFiles.length,
|
| charsSkipped: filterMetrics.totalCharsSkipped,
|
| estimatedTokens,
|
| largestPreventedPayload: filterMetrics.largestPreventedPayload
|
| })
|
| }
|
|
|
| export const resetFilterMetrics = (): void => {
|
| filterMetrics.ignoredFiles = []
|
| filterMetrics.totalCharsSkipped = 0
|
| filterMetrics.largestPreventedPayload = 0
|
| }
|
|
|