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
JavaScript
/**
* 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