UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

486 lines 18.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); /** * Pattern Library - Comprehensive AI pattern detection database */ export class PatternLibrary { patterns = []; patternsByCategory = new Map(); patternsBySeverity = new Map(); patternsById = new Map(); initialized = false; constructor() { // Patterns loaded on first use (lazy loading) } /** * Initialize the library by loading all patterns */ async initialize() { 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 = 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() { return [...this.patterns]; } /** * Get pattern by ID */ getPatternById(id) { return this.patternsById.get(id); } /** * Get patterns by category */ getPatternsByCategory(category) { return this.patternsByCategory.get(category) || []; } /** * Get patterns by severity */ getPatternsBySeverity(severity) { return this.patternsBySeverity.get(severity) || []; } /** * Get patterns by domain */ getPatternsByDomain(domain) { return this.patterns.filter(p => !p.domains || p.domains.length === 0 || p.domains.includes(domain)); } /** * Detect all patterns in text */ detectPatterns(text) { const matches = []; for (const pattern of this.patterns) { const patternMatches = this.findPatternMatches(text, pattern); matches.push(...patternMatches); } return matches; } /** * Detect patterns by category */ detectPatternsByCategory(text, category) { const categoryPatterns = this.getPatternsByCategory(category); const matches = []; for (const pattern of categoryPatterns) { const patternMatches = this.findPatternMatches(text, pattern); matches.push(...patternMatches); } return matches; } /** * Detect only critical patterns */ detectCriticalPatterns(text) { const criticalPatterns = this.getPatternsBySeverity('critical'); const matches = []; for (const pattern of criticalPatterns) { const patternMatches = this.findPatternMatches(text, pattern); matches.push(...patternMatches); } return matches; } /** * Get pattern count */ getPatternCount() { return this.patterns.length; } /** * Get pattern count by category */ getPatternCountByCategory() { const counts = new Map(); for (const [category, patterns] of this.patternsByCategory) { counts.set(category, patterns.length); } return counts; } /** * Get pattern count by severity */ getPatternCountBySeverity() { const counts = new Map(); for (const [severity, patterns] of this.patternsBySeverity) { counts.set(severity, patterns.length); } return counts; } /** * Add a new pattern */ addPattern(pattern) { // 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) { 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, updates) { 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) { 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) { 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) { 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, format) { let patterns; 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) { const matches = this.detectPatterns(text); const wordCount = text.split(/\s+/).filter(w => w.length > 0).length; const matchesByCategory = new Map(); const matchesBySeverity = new Map(); const uniquePatternIds = new Set(); 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, text2) { 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 */ findPatternMatches(text, pattern) { const matches = []; const regex = pattern.pattern; // Reset regex state regex.lastIndex = 0; let match; 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 */ createRegExpFromPattern(patternStr) { // 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 */ exportAsMarkdown() { const lines = []; 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(); 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'); } } //# sourceMappingURL=pattern-library.js.map