UNPKG

aiwg

Version:

Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.

656 lines (553 loc) 19.1 kB
/** * Pattern Library * * Comprehensive, categorized database of AI writing patterns with detection algorithms, * severity levels, and replacement suggestions. */ import { readFile } from 'fs/promises'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { existsSync } from 'fs'; import * as yaml from 'yaml'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export type PatternCategory = | 'banned-phrases' | 'formulaic-structures' | 'hedging-language' | 'transition-words' | 'weak-verbs' | 'passive-voice' | 'generic-adjectives' | 'list-structures' | 'conclusion-phrases' | 'introduction-phrases'; export type PatternSeverity = 'critical' | 'high' | 'medium' | 'low' | 'warning' | 'info'; export type PatternFrequency = 'very-common' | 'common' | 'occasional' | 'rare'; export type PatternDomain = 'academic' | 'technical' | 'executive' | 'casual'; export interface AIPattern { id: string; category: PatternCategory; subcategory?: string; pattern: string | RegExp; severity: PatternSeverity; confidence: number; // 0-1: How strongly this indicates AI examples: string[]; replacements?: string[]; context?: string; frequency: PatternFrequency; domains?: PatternDomain[]; } export interface PatternMatch { pattern: AIPattern; match: string; position: { start: number; end: number }; context: string; severity: PatternSeverity; } export interface PatternFilter { categories?: PatternCategory[]; severities?: PatternSeverity[]; domains?: PatternDomain[]; minConfidence?: number; maxConfidence?: number; frequencies?: PatternFrequency[]; subcategories?: string[]; } export interface PatternAnalysisResult { totalMatches: number; matchesByCategory: Map<PatternCategory, number>; matchesBySeverity: Map<PatternSeverity, number>; uniquePatterns: number; averageConfidence: number; criticalMatches: PatternMatch[]; highPriorityMatches: PatternMatch[]; wordCount: number; patternDensity: number; // Matches per 100 words } export interface PatternComparisonResult { text1Matches: number; text2Matches: number; improvement: number; // Percentage reduction in patterns addedPatterns: PatternMatch[]; removedPatterns: PatternMatch[]; persistentPatterns: PatternMatch[]; } /** * Pattern Library - Comprehensive AI pattern detection database */ export class PatternLibrary { private patterns: AIPattern[] = []; private patternsByCategory: Map<PatternCategory, AIPattern[]> = new Map(); private patternsBySeverity: Map<PatternSeverity, AIPattern[]> = new Map(); private patternsById: Map<string, AIPattern> = new Map(); private initialized = false; constructor() { // Patterns loaded on first use (lazy loading) } /** * Initialize the library by loading all patterns */ async initialize(): Promise<void> { if (this.initialized) { return; } const patternsDir = join(__dirname, 'patterns'); const patternFiles = [ 'banned-phrases.json', 'formulaic-structures.json', 'hedging-language.json', 'weak-verbs.json', 'generic-adjectives.json', 'transition-words.json' ]; for (const file of patternFiles) { const filePath = join(patternsDir, file); if (existsSync(filePath)) { const content = await readFile(filePath, 'utf-8'); const filePatterns: AIPattern[] = JSON.parse(content); // Convert string patterns to RegExp for (const pattern of filePatterns) { if (typeof pattern.pattern === 'string') { pattern.pattern = this.createRegExpFromPattern(pattern.pattern); } this.addPattern(pattern); } } } this.initialized = true; } /** * Get all patterns */ getAllPatterns(): AIPattern[] { return [...this.patterns]; } /** * Get pattern by ID */ getPatternById(id: string): AIPattern | undefined { return this.patternsById.get(id); } /** * Get patterns by category */ getPatternsByCategory(category: PatternCategory): AIPattern[] { return this.patternsByCategory.get(category) || []; } /** * Get patterns by severity */ getPatternsBySeverity(severity: PatternSeverity): AIPattern[] { return this.patternsBySeverity.get(severity) || []; } /** * Get patterns by domain */ getPatternsByDomain(domain: PatternDomain): AIPattern[] { return this.patterns.filter(p => !p.domains || p.domains.length === 0 || p.domains.includes(domain) ); } /** * Detect all patterns in text */ detectPatterns(text: string): PatternMatch[] { const matches: PatternMatch[] = []; for (const pattern of this.patterns) { const patternMatches = this.findPatternMatches(text, pattern); matches.push(...patternMatches); } return matches; } /** * Detect patterns by category */ detectPatternsByCategory(text: string, category: PatternCategory): PatternMatch[] { const categoryPatterns = this.getPatternsByCategory(category); const matches: PatternMatch[] = []; for (const pattern of categoryPatterns) { const patternMatches = this.findPatternMatches(text, pattern); matches.push(...patternMatches); } return matches; } /** * Detect only critical patterns */ detectCriticalPatterns(text: string): PatternMatch[] { const criticalPatterns = this.getPatternsBySeverity('critical'); const matches: PatternMatch[] = []; for (const pattern of criticalPatterns) { const patternMatches = this.findPatternMatches(text, pattern); matches.push(...patternMatches); } return matches; } /** * Get pattern count */ getPatternCount(): number { return this.patterns.length; } /** * Get pattern count by category */ getPatternCountByCategory(): Map<PatternCategory, number> { const counts = new Map<PatternCategory, number>(); for (const [category, patterns] of this.patternsByCategory) { counts.set(category, patterns.length); } return counts; } /** * Get pattern count by severity */ getPatternCountBySeverity(): Map<PatternSeverity, number> { const counts = new Map<PatternSeverity, number>(); for (const [severity, patterns] of this.patternsBySeverity) { counts.set(severity, patterns.length); } return counts; } /** * Add a new pattern */ addPattern(pattern: AIPattern): void { // Check for duplicate ID if (this.patternsById.has(pattern.id)) { throw new Error(`Pattern with ID "${pattern.id}" already exists`); } this.patterns.push(pattern); this.patternsById.set(pattern.id, pattern); // Add to category index if (!this.patternsByCategory.has(pattern.category)) { this.patternsByCategory.set(pattern.category, []); } this.patternsByCategory.get(pattern.category)!.push(pattern); // Add to severity index if (!this.patternsBySeverity.has(pattern.severity)) { this.patternsBySeverity.set(pattern.severity, []); } this.patternsBySeverity.get(pattern.severity)!.push(pattern); } /** * Remove a pattern by ID */ removePattern(id: string): void { const pattern = this.patternsById.get(id); if (!pattern) { return; } // Remove from main array const index = this.patterns.findIndex(p => p.id === id); if (index !== -1) { this.patterns.splice(index, 1); } // Remove from indices this.patternsById.delete(id); const categoryPatterns = this.patternsByCategory.get(pattern.category); if (categoryPatterns) { const catIndex = categoryPatterns.findIndex(p => p.id === id); if (catIndex !== -1) { categoryPatterns.splice(catIndex, 1); } } const severityPatterns = this.patternsBySeverity.get(pattern.severity); if (severityPatterns) { const sevIndex = severityPatterns.findIndex(p => p.id === id); if (sevIndex !== -1) { severityPatterns.splice(sevIndex, 1); } } } /** * Update a pattern */ updatePattern(id: string, updates: Partial<AIPattern>): void { const pattern = this.patternsById.get(id); if (!pattern) { throw new Error(`Pattern with ID "${id}" not found`); } // If category or severity changes, need to update indices if (updates.category && updates.category !== pattern.category) { // Remove from old category const oldCategoryPatterns = this.patternsByCategory.get(pattern.category); if (oldCategoryPatterns) { const index = oldCategoryPatterns.findIndex(p => p.id === id); if (index !== -1) { oldCategoryPatterns.splice(index, 1); } } // Add to new category if (!this.patternsByCategory.has(updates.category)) { this.patternsByCategory.set(updates.category, []); } this.patternsByCategory.get(updates.category)!.push(pattern); } if (updates.severity && updates.severity !== pattern.severity) { // Remove from old severity const oldSeverityPatterns = this.patternsBySeverity.get(pattern.severity); if (oldSeverityPatterns) { const index = oldSeverityPatterns.findIndex(p => p.id === id); if (index !== -1) { oldSeverityPatterns.splice(index, 1); } } // Add to new severity if (!this.patternsBySeverity.has(updates.severity)) { this.patternsBySeverity.set(updates.severity, []); } this.patternsBySeverity.get(updates.severity)!.push(pattern); } // Apply updates Object.assign(pattern, updates); } /** * Search patterns by query string */ searchPatterns(query: string): AIPattern[] { const lowerQuery = query.toLowerCase(); return this.patterns.filter(pattern => { return ( pattern.id.toLowerCase().includes(lowerQuery) || pattern.category.toLowerCase().includes(lowerQuery) || (pattern.subcategory && pattern.subcategory.toLowerCase().includes(lowerQuery)) || (pattern.context && pattern.context.toLowerCase().includes(lowerQuery)) || pattern.examples.some(ex => ex.toLowerCase().includes(lowerQuery)) || (pattern.replacements && pattern.replacements.some(r => r.toLowerCase().includes(lowerQuery))) ); }); } /** * Filter patterns by criteria */ filterPatterns(filter: PatternFilter): AIPattern[] { let filtered = [...this.patterns]; if (filter.categories && filter.categories.length > 0) { filtered = filtered.filter(p => filter.categories!.includes(p.category)); } if (filter.severities && filter.severities.length > 0) { filtered = filtered.filter(p => filter.severities!.includes(p.severity)); } if (filter.domains && filter.domains.length > 0) { filtered = filtered.filter(p => !p.domains || p.domains.length === 0 || p.domains.some(d => filter.domains!.includes(d)) ); } if (filter.minConfidence !== undefined) { filtered = filtered.filter(p => p.confidence >= filter.minConfidence!); } if (filter.maxConfidence !== undefined) { filtered = filtered.filter(p => p.confidence <= filter.maxConfidence!); } if (filter.frequencies && filter.frequencies.length > 0) { filtered = filtered.filter(p => filter.frequencies!.includes(p.frequency)); } if (filter.subcategories && filter.subcategories.length > 0) { filtered = filtered.filter(p => p.subcategory && filter.subcategories!.includes(p.subcategory) ); } return filtered; } /** * Export patterns in various formats */ exportPatterns(format: 'json' | 'yaml' | 'markdown'): string { switch (format) { case 'json': return JSON.stringify(this.patterns, null, 2); case 'yaml': return yaml.stringify(this.patterns); case 'markdown': return this.exportAsMarkdown(); default: throw new Error(`Unsupported export format: ${format}`); } } /** * Import patterns from JSON or YAML */ importPatterns(data: string, format: 'json' | 'yaml'): void { let patterns: AIPattern[]; if (format === 'json') { patterns = JSON.parse(data); } else if (format === 'yaml') { patterns = yaml.parse(data); } else { throw new Error(`Unsupported import format: ${format}`); } for (const pattern of patterns) { // Convert string patterns to RegExp if (typeof pattern.pattern === 'string') { pattern.pattern = this.createRegExpFromPattern(pattern.pattern); } // Skip duplicates if (!this.patternsById.has(pattern.id)) { this.addPattern(pattern); } } } /** * Analyze text for AI patterns */ analyzeText(text: string): PatternAnalysisResult { const matches = this.detectPatterns(text); const wordCount = text.split(/\s+/).filter(w => w.length > 0).length; const matchesByCategory = new Map<PatternCategory, number>(); const matchesBySeverity = new Map<PatternSeverity, number>(); const uniquePatternIds = new Set<string>(); let totalConfidence = 0; for (const match of matches) { // Count by category const catCount = matchesByCategory.get(match.pattern.category) || 0; matchesByCategory.set(match.pattern.category, catCount + 1); // Count by severity const sevCount = matchesBySeverity.get(match.severity) || 0; matchesBySeverity.set(match.severity, sevCount + 1); // Track unique patterns uniquePatternIds.add(match.pattern.id); // Sum confidence totalConfidence += match.pattern.confidence; } const criticalMatches = matches.filter(m => m.severity === 'critical'); const highPriorityMatches = matches.filter(m => m.severity === 'critical' || m.severity === 'high' ); return { totalMatches: matches.length, matchesByCategory, matchesBySeverity, uniquePatterns: uniquePatternIds.size, averageConfidence: matches.length > 0 ? totalConfidence / matches.length : 0, criticalMatches, highPriorityMatches, wordCount, patternDensity: wordCount > 0 ? (matches.length / wordCount) * 100 : 0 }; } /** * Compare two texts for pattern changes */ compareTexts(text1: string, text2: string): PatternComparisonResult { const matches1 = this.detectPatterns(text1); const matches2 = this.detectPatterns(text2); // Calculate improvement const improvement = matches1.length > 0 ? ((matches1.length - matches2.length) / matches1.length) * 100 : 0; // Find pattern differences const patterns1 = new Set(matches1.map(m => m.pattern.id)); const patterns2 = new Set(matches2.map(m => m.pattern.id)); const addedPatterns = matches2.filter(m => !patterns1.has(m.pattern.id)); const removedPatterns = matches1.filter(m => !patterns2.has(m.pattern.id)); const persistentPatterns = matches2.filter(m => patterns1.has(m.pattern.id)); return { text1Matches: matches1.length, text2Matches: matches2.length, improvement, addedPatterns, removedPatterns, persistentPatterns }; } // Private helper methods /** * Find all matches of a pattern in text */ private findPatternMatches(text: string, pattern: AIPattern): PatternMatch[] { const matches: PatternMatch[] = []; const regex = pattern.pattern as RegExp; // Reset regex state regex.lastIndex = 0; let match: RegExpExecArray | null; while ((match = regex.exec(text)) !== null) { const start = match.index; const end = start + match[0].length; // Extract context (50 chars before and after) const contextStart = Math.max(0, start - 50); const contextEnd = Math.min(text.length, end + 50); const context = text.substring(contextStart, contextEnd); matches.push({ pattern, match: match[0], position: { start, end }, context, severity: pattern.severity }); // Prevent infinite loop on zero-width matches if (match[0].length === 0) { regex.lastIndex++; } } return matches; } /** * Create RegExp from pattern string */ private createRegExpFromPattern(patternStr: string): RegExp { // Check if pattern starts with ^ (line start anchor) const isLineStart = patternStr.startsWith('^'); // Handle special pattern syntax if (patternStr.includes('\\b')) { // Already has word boundaries return new RegExp(patternStr, 'gi'); } else if (isLineStart) { // Line start patterns don't need word boundaries return new RegExp(patternStr, 'gm'); } else { // Add word boundaries for phrase matching const escaped = patternStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Check if pattern ends in punctuation - if so, don't add trailing word boundary const endsWithPunctuation = /[,.:;!?]$/.test(patternStr); const startBoundary = '\\b'; const endBoundary = endsWithPunctuation ? '' : '\\b'; return new RegExp(`${startBoundary}${escaped}${endBoundary}`, 'gi'); } } /** * Export patterns as markdown documentation */ private exportAsMarkdown(): string { const lines: string[] = []; lines.push('# AI Writing Patterns Library\n'); lines.push(`Total Patterns: ${this.patterns.length}\n`); // Group by category const categories = Array.from(this.patternsByCategory.keys()).sort(); for (const category of categories) { const categoryPatterns = this.patternsByCategory.get(category)!; lines.push(`## ${category} (${categoryPatterns.length} patterns)\n`); // Group by subcategory const subcategories = new Map<string, AIPattern[]>(); for (const pattern of categoryPatterns) { const subcat = pattern.subcategory || 'General'; if (!subcategories.has(subcat)) { subcategories.set(subcat, []); } subcategories.get(subcat)!.push(pattern); } for (const [subcat, patterns] of subcategories) { lines.push(`### ${subcat}\n`); for (const pattern of patterns) { lines.push(`**${pattern.id}** (${pattern.severity}, confidence: ${pattern.confidence})`); lines.push(`- Pattern: \`${pattern.pattern}\``); lines.push(`- Context: ${pattern.context}`); lines.push(`- Examples:`); for (const example of pattern.examples.slice(0, 2)) { lines.push(` - "${example}"`); } if (pattern.replacements && pattern.replacements.length > 0) { lines.push(`- Replacements:`); for (const replacement of pattern.replacements.slice(0, 2)) { lines.push(` - ${replacement}`); } } lines.push(''); } } } return lines.join('\n'); } }