PRIX / src /config.ts
Rachit-Tw's picture
Upload 169 files
9284ad7 verified
Raw
History Blame Contribute Delete
7.64 kB
import {readFileSync, existsSync} from 'fs'
import {join} from 'path'
import {als} from './context'
import {prixExec} from './utils'
export interface PrixConfig {
version: string
severity: {
minimumLevel: 'critical' | 'major' | 'minor' | 'info'
showConfidenceThreshold: number
}
filters: {
excludePatterns: string[]
includePatterns: string[]
disableTypes: string[]
}
linting: {
enabled: boolean
eslint: boolean
prettier: boolean
failOnError: boolean
}
review: {
maxFilesPerPR: number
maxCommentsPerFile: number
denseMode: boolean
showImpactAnalysis: boolean
}
remedies: {
enabled: boolean
autoCreatePR: boolean
requireApproval: boolean
}
confidence: {
minCritical: number
minMajor: number
minMinor: number
minInfo: number
}
}
export const DEFAULT_PRIX_CONFIG: PrixConfig = {
version: '1.0',
severity: {
minimumLevel: 'minor',
showConfidenceThreshold: 50
},
filters: {
excludePatterns: [],
includePatterns: ['**/*'],
disableTypes: []
},
linting: {
enabled: true,
eslint: true,
prettier: true,
failOnError: false
},
review: {
maxFilesPerPR: 50,
maxCommentsPerFile: 10,
denseMode: false,
showImpactAnalysis: true
},
remedies: {
enabled: false,
autoCreatePR: false,
requireApproval: true
},
confidence: {
minCritical: 0,
minMajor: 60,
minMinor: 70,
minInfo: 80
}
}
export class PrixConfigLoader {
private config: PrixConfig | null = null
load(workingDir: string): PrixConfig {
if (this.config) {
return this.config
}
const possiblePaths = [
join(workingDir, '.prix.yml'),
join(workingDir, 'prix.yml'),
join(workingDir, '.prix.yaml'),
join(workingDir, 'prix.yaml')
]
for (const configPath of possiblePaths) {
if (existsSync(configPath)) {
try {
const content = readFileSync(configPath, 'utf8')
this.config = this.mergeWithDefaults(this.parseYAML(content))
return this.config
} catch (e) {
console.warn(`Failed to load prix config from ${configPath}: ${e}`)
}
}
}
this.config = DEFAULT_PRIX_CONFIG
return this.config
}
private parseYAML(content: string): Partial<PrixConfig> {
const result: Partial<PrixConfig> = {}
const lines = content.split('\n')
let currentSection: string | null = null
let currentSectionObj: Record<string, any> = {}
let sectionKey = ''
for (const line of lines) {
const sectionMatch = line.match(/^(\w+):\s*$/)
if (sectionMatch) {
if (currentSection && sectionKey) {
;(result as any)[sectionKey] = currentSectionObj
}
currentSection = sectionMatch[1]
sectionKey = currentSection
currentSectionObj = {}
continue
}
const keyValueMatch = line.match(/^\s*(\w+):\s*(.+)$/)
if (keyValueMatch && currentSection) {
const key = keyValueMatch[1]
let value: any = keyValueMatch[2].trim()
if (value === 'true') value = true
else if (value === 'false') value = false
else if (!isNaN(Number(value))) value = Number(value)
currentSectionObj[key] = value
}
}
if (currentSection && sectionKey) {
;(result as any)[sectionKey] = currentSectionObj
}
return result
}
private mergeWithDefaults(config: Partial<PrixConfig>): PrixConfig {
return {
version: config.version || DEFAULT_PRIX_CONFIG.version,
severity: {
...DEFAULT_PRIX_CONFIG.severity,
...config.severity
},
filters: {
...DEFAULT_PRIX_CONFIG.filters,
...config.filters
},
linting: {
...DEFAULT_PRIX_CONFIG.linting,
...config.linting
},
review: {
...DEFAULT_PRIX_CONFIG.review,
...config.review
},
remedies: {
...DEFAULT_PRIX_CONFIG.remedies,
...config.remedies
},
confidence: {
...DEFAULT_PRIX_CONFIG.confidence,
...config.confidence
}
}
}
shouldIncludeFile(config: PrixConfig, filename: string): boolean {
const {excludePatterns, includePatterns} = config.filters
for (const pattern of excludePatterns) {
if (this.matchesPattern(filename, pattern)) {
return false
}
}
if (includePatterns.length === 0) {
return true
}
for (const pattern of includePatterns) {
if (this.matchesPattern(filename, pattern)) {
return true
}
}
return false
}
private matchesPattern(filename: string, pattern: string): boolean {
const regexPattern = pattern
.replace(/\./g, '\\.')
.replace(/\*/g, '.*')
.replace(/\?/g, '.')
.replace(/\*\*/g, '.*')
return new RegExp(`^${regexPattern}$`).test(filename)
}
}
export const configLoader = new PrixConfigLoader()
export function getConfig(): PrixConfig {
const ctx = als.getStore()
const workingDir = ctx?.workingDir || process.cwd()
return configLoader.load(workingDir)
}
export function meetsConfidenceThreshold(
severity: 'critical' | 'major' | 'minor' | 'info',
confidence: number
): boolean {
const config = getConfig()
const thresholds = config.confidence
switch (severity) {
case 'critical':
return confidence >= thresholds.minCritical
case 'major':
return confidence >= thresholds.minMajor
case 'minor':
return confidence >= thresholds.minMinor
case 'info':
return confidence >= thresholds.minInfo
default:
return true
}
}
export function meetsMinimumSeverity(severity: string): boolean {
const config = getConfig()
const levels = ['critical', 'major', 'minor', 'info']
const minimumLevel = config.severity.minimumLevel
const minimumIndex = levels.indexOf(minimumLevel)
const severityIndex = levels.indexOf(severity.toLowerCase())
return severityIndex >= minimumIndex
}
export async function lintSuggestion(
suggestion: string,
filename: string,
language: string
): Promise<{valid: boolean; errors: string[]}> {
const config = getConfig()
if (!config.linting.enabled) {
return {valid: true, errors: []}
}
const errors: string[] = []
const workingDir = als.getStore()?.workingDir || process.cwd()
if (config.linting.prettier) {
try {
prixExec(
`echo "${suggestion.replace(
/"/g,
'\\"'
)}" | prettier --stdin-filepath ${filename}`,
{
cwd: workingDir,
stdio: 'pipe'
}
)
} catch {
errors.push('Prettier formatting check failed')
}
}
if (
config.linting.eslint &&
(language === 'javascript' || language === 'typescript')
) {
try {
const ext = filename.endsWith('.ts') ? 'ts' : 'js'
const tempFile = `.prix_temp_suggestion.${ext}`
prixExec(
`echo "${suggestion.replace(
/"/g,
'\\"'
)}" > ${tempFile} && npx eslint ${tempFile} --format json 2>&1 || true`,
{
cwd: workingDir,
stdio: 'pipe'
}
)
} catch {
errors.push('ESLint check failed')
}
}
return {
valid: config.linting.failOnError ? errors.length === 0 : true,
errors
}
}