
Pattern Extractor
- 1 installs
- Updated March 2, 2026
- dundas/uhr
Analyze session transcripts to extract error, success, and decision patterns, update technical-patterns.md, and generate preventive pre-flight checks.
About
Mines transcript history for recurring error, success, and decision patterns and writes them back into a technical-patterns knowledge base. A developer uses it to turn past sessions into preventive checks and best practices.
- Categorizes anti-patterns, best practices, and decisions
- Generates pre-flight checks from repeated errors
Pattern Extractor by the numbers
- 1 all-time installs (skills.sh)
- Ranked #2,479 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Jul 8, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dundas/uhr --skill pattern-extractorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| Last updated | March 2, 2026 |
| Repository | dundas/uhr ↗ |
What it does
Analyze session transcripts to extract error, success, and decision patterns, update technical-patterns.md, and generate preventive pre-flight checks.
Files
Pattern Extractor
Purpose
Close the learning loop by automatically analyzing transcript history to extract patterns, update technical knowledge, and suggest preventive measures.
Goal
Transform experience into actionable knowledge without manual intervention.
Use Cases
1. Automated Learning: Extract lessons from recent work 2. Error Prevention: Find repeated mistakes and suggest pre-flight checks 3. Best Practices: Identify successful patterns to replicate 4. Decision Documentation: Capture decision rationale for future reference 5. Cross-Brain Knowledge: Generate insights for sharing with other brains
When to Use
Trigger automatically:
- Daily (via cron): Extract patterns from last 24 hours
- Weekly: Extract patterns from last 7 days
- After major work: Extract patterns from specific session
Trigger manually:
- After encountering repeated errors
- Before starting similar work (learn from past attempts)
- When updating technical-patterns.md
Usage
# Extract patterns from recent transcripts
/pattern-extractor --last 7
# Extract specific pattern types
/pattern-extractor --errors-only --last 30
/pattern-extractor --decisions-only --since "2026-02-01"
/pattern-extractor --successes-only --last 14
# Update technical patterns file
/pattern-extractor --last 30 --update-file memory/technical-patterns.md
# Generate pre-flight checks from errors
/pattern-extractor --generate-checks --last 90
# Export for cross-brain sharing
/pattern-extractor --last 7 --format json --output patterns.jsonProcess
1. Transcript Analysis
- Load transcripts from specified time range (uses transcript-query)
- Parse tool uses, errors, user feedback, outcomes
- Build timeline of activities
2. Pattern Detection
Error Patterns:
Analyze for:
- Tool errors (Read failed, Edit failed, Bash failed)
- Repeated mistakes (same error multiple times)
- Error sequences (Error A → leads to → Error B)
- Root causes (what triggered the error chain)Success Patterns:
Analyze for:
- Successful completions (task done, tests pass, user approves)
- Techniques that worked (tool sequences, approaches)
- Fast resolutions (problem → solution quickly)
- User praise (positive feedback patterns)Decision Patterns:
Analyze for:
- Architecture decisions (chose X over Y because Z)
- Trade-offs considered (pros/cons lists)
- Rationale documented (why this approach)
- Alternatives rejected (what was considered but not chosen)3. Pattern Categorization
Anti-Patterns (things to avoid):
- Frequency: How often does this mistake occur?
- Impact: What's the cost (time lost, bugs introduced)?
- Root cause: Why does this happen?
- Prevention: How to stop it?
Best Practices (things to replicate):
- Success rate: How reliably does this work?
- Context: When is this applicable?
- Steps: What's the procedure?
- Metrics: How to measure success?
Decisions (for future reference):
- Problem: What was being solved?
- Solution: What was chosen?
- Reasoning: Why was this the best option?
- Outcome: Did it work?
4. Knowledge Base Update
Update technical-patterns.md:
## Anti-Patterns (Last Updated: 2026-02-06)
### Pattern: Always Read before Write/Edit
**Frequency**: 22 occurrences (last 90 days)
**Impact**: File operations fail, wasted time
**Root Cause**: Assumption file exists without verification
**Prevention**:
- Pre-flight check: Has this file been read in session?
- Auto-suggest: "File not read yet - would you like to read it first?"
**Last Seen**: 2026-02-05 (session abc123...)
### Pattern: Verify paths when switching repos
**Frequency**: 21 occurrences (last 90 days)
**Impact**: Operations on wrong files, potential data loss
**Root Cause**: Context switching without path verification
**Prevention**:
- Pre-flight check: Verify cwd matches expected repo
- Prompt pattern: "Working in {repo} - is this correct?"
**Last Seen**: 2026-02-04 (session def456...)Update best-practices.md (if exists):
## Best Practices (Last Updated: 2026-02-06)
### Pattern: Use native FormData for file uploads
**Success Rate**: 100% (5/5 attempts)
**Context**: Uploading files to APIs requiring multipart/form-data
**Steps**:
1. Create FormData instance
2. Append file as Blob (not Buffer)
3. Let fetch auto-set Content-Type boundary
4. Don't manually set Content-Type header
**Metrics**: Zero upload failures after adopting this pattern
**First Success**: 2026-02-05 (mech-llms audio upload)5. Generate Preventive Measures
Pre-flight Checks (code suggestions):
// Suggested pre-flight check for Write/Edit tools
function preWriteCheck(filePath, sessionContext) {
const hasBeenRead = sessionContext.filesRead.includes(filePath);
if (!hasBeenRead) {
return {
warning: true,
message: `File ${filePath} has not been read in this session. Read it first?`,
suggestion: `Read(${filePath})`
};
}
return { warning: false };
}Tool Wrappers (safety layer):
// Suggested wrapper for Bash tool when switching repos
async function safeBash(command, expectedRepo) {
const cwd = process.cwd();
if (!cwd.includes(expectedRepo)) {
const userConfirm = await askUser(
`Command will run in ${cwd}. Expected ${expectedRepo}. Continue?`
);
if (!userConfirm) return { cancelled: true };
}
return await Bash(command);
}6. Output Formats
Markdown (human-readable, for memory files):
# Pattern Analysis: 2026-02-06
## Summary
- Transcripts analyzed: 42 (last 30 days)
- Error patterns found: 8
- Success patterns found: 12
- Decisions documented: 6
## New Insights
...JSON (machine-readable, for cross-brain sharing):
{
"generated": "2026-02-06T14:30:00Z",
"period": {
"days": 30,
"transcripts": 42,
"sessions": 38
},
"patterns": {
"errors": [
{
"pattern": "Read before Write violation",
"frequency": 22,
"impact": "high",
"prevention": "pre-flight check",
"code": "function preWriteCheck() { ... }"
}
],
"successes": [...],
"decisions": [...]
}
}Integration Points
1. Daily Automation (cron)
# Run daily at 6am, update technical patterns
0 6 * * * cd ~/dev_env/decisive_redux && /pattern-extractor --last 1 --update-file memory/technical-patterns.md2. Session Start Hook
// Load recent patterns at session start
import { exec } from 'child_process';
const patterns = await exec('pattern-extractor --last 7 --format json');
console.log(`📊 Recent patterns: ${patterns.errors.length} errors, ${patterns.successes.length} wins`);3. Pre-Tool Execution Hooks
// Before Write tool
const writeCheck = await exec(`pattern-extractor --check Write --file ${filePath}`);
if (writeCheck.warnings.length > 0) {
// Show warnings to user
}4. Cross-Brain Knowledge Sharing
// Extract patterns and broadcast to AgentDispatch hub
const patterns = await exec('pattern-extractor --last 7 --format json');
await agentDispatch.broadcast({
type: 'knowledge_share',
from: 'decisive-gm',
patterns: patterns,
timestamp: new Date().toISOString()
});Architecture
Dependencies
transcript-queryskill (transcript parsing)- Node.js fs/promises (file I/O)
- Optional:
mech-llms(Claude API for pattern summarization)
File Structure
.claude/skills/pattern-extractor/
├── SKILL.md # This file
├── pattern-extractor.mjs # CLI entry point
├── lib/
│ ├── pattern-detector.js # Core pattern detection logic
│ ├── error-analyzer.js # Error pattern analysis
│ ├── success-analyzer.js # Success pattern analysis
│ ├── decision-analyzer.js # Decision pattern analysis
│ └── check-generator.js # Pre-flight check code generation
└── templates/
├── technical-patterns.md # Template for pattern doc
└── pre-flight-checks.js # Template for checksKey Algorithms
Pattern Detection Algorithm
For each transcript in time range:
1. Parse tool uses and outcomes
2. Identify error sequences
3. Detect repeated patterns (same error multiple times)
4. Find successful completions
5. Extract decision points
Group similar patterns:
- Cluster by error message similarity
- Cluster by tool sequence similarity
- Cluster by decision context
Rank by importance:
- Frequency × Impact = Priority
- High priority patterns bubble upPrevention Suggestion Algorithm
For each error pattern:
1. Identify trigger condition
2. Determine detection point (when can we catch this?)
3. Generate pre-flight check code
4. Suggest integration point (which tool hook?)
Example:
Error: "Write failed - file not read"
Trigger: Write tool called without prior Read
Detection: Before Write execution
Check: Has file been read in session?
Integration: Pre-Write hookOutput Example
# Pattern Analysis Report
**Generated**: 2026-02-06 14:30 UTC
**Period**: Last 30 days (42 transcripts)
---
## 🔴 High-Priority Anti-Patterns
### 1. Read-Before-Write Violation
**Occurrences**: 22 times (0.73/day)
**Impact**: High (causes file operation failures)
**Cost**: ~15 min/occurrence = 5.5 hours wasted
**Pattern**:Write(file.ts) → Error: "File not read" → Read(file.ts) → Write(file.ts) → Success
**Root Cause**: Assumption that file context is available without reading
**Prevention**:// Suggested pre-Write hook if (!session.filesRead.includes(targetFile)) { warn("File not read. Reading automatically..."); await Read(targetFile); }
**Recommendation**: Implement pre-flight check ✅ HIGH PRIORITY
---
## ✅ Successful Patterns to Replicate
### 1. Native FormData for Uploads
**Success Rate**: 100% (5/5)
**Context**: File uploads to external APIs
**Pattern**:const formData = new FormData(); formData.append('file', new Blob([fileBuffer]), filename); // Let fetch auto-set Content-Type - don't set manually const response = await fetch(url, { method: 'POST', body: formData });
**Why it works**: APIs like Together.ai need proper boundary in Content-Type
**When to use**: Any external API requiring multipart/form-data
---
## 📋 Recent Decisions
### 1. Agent Teams Integration in dev-workflow-orchestrator
**Date**: 2026-02-06
**Context**: Should workflow offer agent teams automatically?
**Decision**: Yes, with analysis phase
**Reasoning**:
- Intelligent automation - offers when beneficial
- User maintains control - can decline
- Clear cost/benefit - shows 3-5x token cost vs 2-3x speed
**Alternatives Considered**:
- ❌ Always use teams (too expensive)
- ❌ Never suggest teams (misses opportunities)
- ✅ Analyze and suggest (balanced approach)
**Outcome**: Implemented in phase 3 of workflow
---
## 🎯 Recommendations
1. **Implement pre-flight checks** (code provided above) - prevents 22 errors/month
2. **Replicate FormData pattern** - 100% success rate for uploads
3. **Document decision pattern** - agent teams suggestion logic is reusable
---
**Next Pattern Extraction**: 2026-02-07 06:00 UTC (automated)Limitations
- Pattern detection requires clean transcript data
- Code generation is suggestions only (needs review)
- Cross-brain patterns need standardized format
- May miss subtle patterns that humans would catch
Future Enhancements
1. ML-based pattern detection: Use embeddings to find semantic patterns 2. Auto-fix suggestions: Generate full PR for common fixes 3. Pattern prediction: "You're about to make error X based on context" 4. Cross-repo patterns: Find patterns across portfolio projects 5. User feedback loop: Track if suggested patterns actually help
Security & Privacy
- Patterns may contain sensitive information from transcripts
- Filter out credentials, API keys, personal data
- Sanitize before cross-brain sharing
- Respect transcript privacy settings
---
Created: 2026-02-06 Dependencies: transcript-query, Node.js 18+ Related Skills: brain-briefing, transcript-query, memory-manager Status: Ready to implement
/**
* Pattern Detector
*
* Core logic for detecting error patterns, success patterns, and decision patterns
* from transcript history.
*/
import { TranscriptParser } from '../../transcript-query/lib/transcript-parser.js';
import { promises as fs } from 'fs';
export class PatternDetector {
constructor() {
this.parser = new TranscriptParser();
}
/**
* Extract patterns from transcripts
*/
async extractPatterns(directory, options = {}) {
const {
last = null,
since = null,
errorsOnly = false,
successesOnly = false,
decisionsOnly = false
} = options;
// Get transcripts
const transcripts = await this.parser.listTranscripts(directory);
console.log(` Found ${transcripts.length} total transcripts`);
// Filter by date
let filtered = transcripts;
if (last) {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - last);
filtered = transcripts.filter(t => new Date(t.timestamp) >= cutoff);
} else if (since) {
const cutoffDate = new Date(since);
filtered = transcripts.filter(t => new Date(t.timestamp) >= cutoffDate);
}
console.log(` Analyzing ${filtered.length} transcripts`);
// Parse all transcripts
const parsedData = [];
for (const transcript of filtered) {
try {
const data = await this.parser.parseTranscript(transcript.path);
parsedData.push({ ...transcript, data });
} catch (err) {
console.warn(` Warning: Failed to parse ${transcript.sessionId}: ${err.message}`);
}
}
// Extract patterns (including compliance dimension added 2026-02-07)
const patterns = {
generated: new Date().toISOString(),
period: {
days: last || null,
since: since || null,
transcripts: parsedData.length
},
errors: errorsOnly || !successesOnly && !decisionsOnly ? this.detectErrorPatterns(parsedData) : [],
successes: successesOnly || !errorsOnly && !decisionsOnly ? this.detectSuccessPatterns(parsedData) : [],
decisions: decisionsOnly || !errorsOnly && !successesOnly ? this.detectDecisionPatterns(parsedData) : [],
compliance: this.detectCompliancePatterns(parsedData)
};
return patterns;
}
/**
* Detect error patterns
*/
detectErrorPatterns(transcripts) {
const errorMap = new Map();
for (const transcript of transcripts) {
const { data } = transcript;
// Find tool errors
for (const toolUse of data.toolUses || []) {
if (toolUse.error) {
const key = `${toolUse.tool}:${this.normalizeError(toolUse.error)}`;
if (!errorMap.has(key)) {
errorMap.set(key, {
pattern: `${toolUse.tool} failed: ${this.normalizeError(toolUse.error)}`,
tool: toolUse.tool,
error: this.normalizeError(toolUse.error),
occurrences: [],
frequency: 0,
impact: this.assessImpact(toolUse.tool, toolUse.error),
prevention: this.suggestPrevention(toolUse.tool, toolUse.error)
});
}
const pattern = errorMap.get(key);
pattern.frequency++;
pattern.occurrences.push({
sessionId: data.sessionId,
timestamp: data.startTime,
context: toolUse.parameters
});
}
}
// Find error sequences (Error A → Error B)
for (let i = 0; i < (data.toolUses?.length || 0) - 1; i++) {
const current = data.toolUses[i];
const next = data.toolUses[i + 1];
if (current.error && next.error) {
const key = `sequence:${current.tool}→${next.tool}`;
if (!errorMap.has(key)) {
errorMap.set(key, {
pattern: `Error sequence: ${current.tool} fails → ${next.tool} fails`,
type: 'sequence',
frequency: 0,
occurrences: []
});
}
errorMap.get(key).frequency++;
errorMap.get(key).occurrences.push({
sessionId: data.sessionId,
timestamp: data.startTime
});
}
}
}
// Convert to array and sort by frequency × impact
return Array.from(errorMap.values())
.map(p => ({
...p,
priority: p.frequency * (p.impact || 1)
}))
.sort((a, b) => b.priority - a.priority);
}
/**
* Detect success patterns
*/
detectSuccessPatterns(transcripts) {
const successMap = new Map();
for (const transcript of transcripts) {
const { data } = transcript;
// Look for successful tool sequences
const toolSequence = (data.toolUses || [])
.filter(t => !t.error)
.map(t => t.tool)
.slice(0, 5); // First 5 tools
if (toolSequence.length >= 2) {
const key = toolSequence.join('→');
if (!successMap.has(key)) {
successMap.set(key, {
pattern: `Successful sequence: ${key}`,
sequence: toolSequence,
frequency: 0,
sessions: []
});
}
successMap.get(key).frequency++;
successMap.get(key).sessions.push({
sessionId: data.sessionId,
timestamp: data.startTime,
duration: data.summary?.durationMs
});
}
// Look for user praise patterns
for (const msg of data.messages || []) {
if (msg.type === 'user' && this.isPraise(msg.content)) {
const key = 'user-praise';
if (!successMap.has(key)) {
successMap.set(key, {
pattern: 'User provided positive feedback',
frequency: 0,
examples: []
});
}
successMap.get(key).frequency++;
successMap.get(key).examples.push({
sessionId: data.sessionId,
feedback: msg.content.substring(0, 100)
});
}
}
}
return Array.from(successMap.values())
.sort((a, b) => b.frequency - a.frequency);
}
/**
* Detect compliance patterns (Project Vend robustness dimension)
* Looks for times the agent agreed without questioning, bypassed checks,
* or acted under pressure without verification.
*/
detectCompliancePatterns(transcripts) {
const complianceIssues = [];
for (const transcript of transcripts) {
const { data } = transcript;
for (const msg of data.messages || []) {
if (msg.type === 'assistant') {
// Check for compliance without questioning
if (this.isUnquestionedCompliance(msg.content)) {
complianceIssues.push({
type: 'unquestioned_compliance',
sessionId: data.sessionId,
timestamp: data.startTime,
context: msg.content.substring(0, 300),
severity: 'orange'
});
}
// Check for bypassing safety checks
if (this.isSafetyBypass(msg.content)) {
complianceIssues.push({
type: 'safety_bypass',
sessionId: data.sessionId,
timestamp: data.startTime,
context: msg.content.substring(0, 300),
severity: 'red'
});
}
}
// Check for urgency-driven requests from user
if (msg.type === 'user' && this.isUrgencyPressure(msg.content)) {
complianceIssues.push({
type: 'urgency_pressure',
sessionId: data.sessionId,
timestamp: data.startTime,
context: msg.content.substring(0, 300),
severity: 'yellow'
});
}
}
}
return complianceIssues;
}
/**
* Detect decision patterns
*/
detectDecisionPatterns(transcripts) {
const decisions = [];
for (const transcript of transcripts) {
const { data } = transcript;
// Look for decision keywords in messages
for (const msg of data.messages || []) {
if (msg.type === 'assistant' && this.isDecision(msg.content)) {
decisions.push({
sessionId: data.sessionId,
timestamp: data.startTime,
branch: data.gitBranch,
decision: this.extractDecision(msg.content),
context: msg.content.substring(0, 500)
});
}
}
}
return decisions;
}
/**
* Pre-flight check for tool execution
*/
async preFlightCheck(tool, filePath) {
const warnings = [];
// Example: Check if file was read before Write/Edit
if ((tool === 'Write' || tool === 'Edit') && filePath) {
// This would need session context - placeholder for now
warnings.push({
type: 'read-before-write',
message: `File ${filePath} should be read before ${tool}`,
suggestion: `Read ${filePath} first to verify contents`
});
}
return {
tool,
file: filePath,
warnings,
safe: warnings.length === 0
};
}
/**
* Format patterns as markdown
*/
formatMarkdown(patterns) {
let output = `# Pattern Analysis Report\n\n`;
output += `**Generated**: ${new Date(patterns.generated).toLocaleString()}\n`;
output += `**Period**: ${patterns.period.days ? `Last ${patterns.period.days} days` : `Since ${patterns.period.since}`}\n`;
output += `**Transcripts**: ${patterns.period.transcripts}\n\n`;
output += `---\n\n`;
// Errors
if (patterns.errors.length > 0) {
output += `## 🔴 Error Patterns (${patterns.errors.length})\n\n`;
for (const error of patterns.errors.slice(0, 10)) {
output += `### ${error.pattern}\n`;
output += `**Frequency**: ${error.frequency} occurrences\n`;
output += `**Impact**: ${error.impact || 'unknown'}\n`;
if (error.prevention) {
output += `**Prevention**: ${error.prevention}\n`;
}
output += `\n`;
}
}
// Successes
if (patterns.successes.length > 0) {
output += `## ✅ Success Patterns (${patterns.successes.length})\n\n`;
for (const success of patterns.successes.slice(0, 10)) {
output += `### ${success.pattern}\n`;
output += `**Frequency**: ${success.frequency} times\n`;
output += `\n`;
}
}
// Decisions
if (patterns.decisions.length > 0) {
output += `## 📋 Recent Decisions (${patterns.decisions.length})\n\n`;
for (const decision of patterns.decisions.slice(0, 5)) {
output += `### ${decision.decision || 'Decision'}\n`;
output += `**Date**: ${new Date(decision.timestamp).toLocaleDateString()}\n`;
output += `**Session**: ${decision.sessionId.substring(0, 8)}...\n`;
if (decision.branch) {
output += `**Branch**: ${decision.branch}\n`;
}
output += `\n`;
}
}
// Compliance (Project Vend robustness dimension)
if (patterns.compliance && patterns.compliance.length > 0) {
output += `## ⚠️ Compliance Patterns (${patterns.compliance.length})\n\n`;
output += `> Flags times the agent may have been too agreeable or bypassed checks.\n\n`;
const bySeverity = { red: [], orange: [], yellow: [] };
for (const issue of patterns.compliance) {
(bySeverity[issue.severity] || bySeverity.yellow).push(issue);
}
for (const [severity, issues] of Object.entries(bySeverity)) {
if (issues.length === 0) continue;
const icon = severity === 'red' ? '🔴' : severity === 'orange' ? '🟠' : '🟡';
output += `### ${icon} ${severity.toUpperCase()} (${issues.length})\n\n`;
for (const issue of issues.slice(0, 5)) {
output += `- **${issue.type}** — Session ${issue.sessionId.substring(0, 8)}...\n`;
}
output += `\n`;
}
}
return output;
}
/**
* Update technical patterns file
*/
async updateTechnicalPatterns(patterns, filePath) {
let content = '';
try {
content = await fs.readFile(filePath, 'utf-8');
} catch (err) {
// File doesn't exist, create new
content = '# Technical Patterns\n\n## Anti-Patterns\n\n## Best Practices\n\n';
}
// Append new patterns
content += `\n\n## Pattern Update: ${new Date().toLocaleDateString()}\n\n`;
content += this.formatMarkdown(patterns);
await fs.writeFile(filePath, content, 'utf-8');
}
/**
* Generate pre-flight check code
*/
generatePreFlightChecks(patterns) {
let code = `/**
* Pre-flight checks generated from pattern analysis
* Generated: ${new Date().toISOString()}
*/
export const preFlightChecks = {
`;
// Generate checks for common errors
for (const error of patterns.errors.slice(0, 5)) {
if (error.tool === 'Write' || error.tool === 'Edit') {
code += `
${error.tool.toLowerCase()}: (filePath, sessionContext) => {
// Check: ${error.pattern}
// Frequency: ${error.frequency} occurrences
const warnings = [];
if (!sessionContext.filesRead?.includes(filePath)) {
warnings.push({
type: 'read-before-write',
message: 'File not read in this session',
suggestion: \`Read(\${filePath}) before ${error.tool}\`
});
}
return { warnings, safe: warnings.length === 0 };
},
`;
}
}
code += `};
`;
return code;
}
// Helper methods
normalizeError(error) {
// Normalize error messages (remove file paths, line numbers, etc.)
return error
.replace(/\/[^\s]+/g, '<path>')
.replace(/line \d+/g, 'line <N>')
.replace(/\d+/g, '<N>');
}
assessImpact(tool, error) {
// Simple heuristic for impact
if (error.includes('not found') || error.includes('does not exist')) return 3;
if (error.includes('permission') || error.includes('denied')) return 4;
if (error.includes('syntax') || error.includes('parse')) return 2;
return 1;
}
suggestPrevention(tool, error) {
if (tool === 'Write' || tool === 'Edit') {
if (error.includes('not found') || error.includes('not read')) {
return 'Read file before Write/Edit';
}
}
if (tool === 'Bash') {
if (error.includes('not found') || error.includes('command')) {
return 'Verify command exists before execution';
}
}
return null;
}
isPraise(content) {
const praiseWords = ['great', 'excellent', 'perfect', 'good job', 'well done', 'nice', 'awesome', 'thanks'];
return praiseWords.some(word => content.toLowerCase().includes(word));
}
// Compliance detection helpers (Project Vend robustness dimension)
isUnquestionedCompliance(content) {
// Detect patterns where agent agrees without questioning premise
const complianceMarkers = [
'sure, i\'ll',
'of course,',
'right away',
'i\'ll do that now',
'proceeding with',
'happy to help with that'
];
const questionMarkers = ['why', 'are you sure', 'should we', 'have you considered', 'alternatively'];
const lower = content.toLowerCase();
const hasCompliance = complianceMarkers.some(m => lower.includes(m));
const hasQuestion = questionMarkers.some(m => lower.includes(m));
// Compliance without any questioning is a flag
return hasCompliance && !hasQuestion;
}
isSafetyBypass(content) {
const bypassMarkers = [
'--no-verify',
'--force',
'skip test',
'skip check',
'force push',
'without testing',
'--no-edit',
'disable validation'
];
return bypassMarkers.some(m => content.toLowerCase().includes(m));
}
isUrgencyPressure(content) {
const urgencyMarkers = [
'asap',
'right now',
'immediately',
'urgent',
'before the demo',
'need this shipped',
'no time to',
'skip the'
];
return urgencyMarkers.some(m => content.toLowerCase().includes(m));
}
isDecision(content) {
const decisionWords = ['decided', 'chose', 'selected', 'went with', 'decision:', 'alternative', 'option', 'vs'];
return decisionWords.some(word => content.toLowerCase().includes(word));
}
extractDecision(content) {
// Extract first sentence that looks like a decision
const sentences = content.split(/[.!?]\s+/);
for (const sentence of sentences) {
if (this.isDecision(sentence)) {
return sentence.substring(0, 200);
}
}
return 'Decision made';
}
}
#!/usr/bin/env bun
/**
* Pattern Extractor CLI
*
* Analyzes transcripts to extract error patterns, success patterns, and decisions.
* Automates the learning loop.
*/
import { PatternDetector } from './lib/pattern-detector.js';
import { promises as fs } from 'fs';
import path from 'path';
const args = process.argv.slice(2);
// Parse arguments
const options = {
last: null,
since: null,
errorsOnly: false,
successesOnly: false,
decisionsOnly: false,
updateFile: null,
generateChecks: false,
format: 'markdown',
output: null,
check: null, // For pre-flight check mode
file: null // File to check
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--help' || arg === '-h') {
console.log(`
Pattern Extractor - Automated Learning from Transcripts
Usage:
pattern-extractor [options]
Options:
--last N Analyze last N days
--since DATE Analyze since date (YYYY-MM-DD)
--errors-only Extract only error patterns
--successes-only Extract only success patterns
--decisions-only Extract only decision patterns
--update-file PATH Update technical patterns file
--generate-checks Generate pre-flight check code
--format FORMAT Output format (markdown|json)
--output FILE Save to file instead of stdout
--check TOOL Pre-flight check mode (e.g., --check Write --file path/to/file)
--file PATH File path for pre-flight check
Examples:
# Extract patterns from last week
pattern-extractor --last 7
# Extract only errors from last month
pattern-extractor --errors-only --last 30
# Update technical patterns file
pattern-extractor --last 30 --update-file memory/technical-patterns.md
# Generate pre-flight checks
pattern-extractor --generate-checks --last 90
# Pre-flight check before Write
pattern-extractor --check Write --file src/api/auth.ts
# Export for cross-brain sharing
pattern-extractor --last 7 --format json --output patterns.json
`);
process.exit(0);
} else if (arg === '--last' && i + 1 < args.length) {
options.last = parseInt(args[++i], 10);
} else if (arg === '--since' && i + 1 < args.length) {
options.since = args[++i];
} else if (arg === '--errors-only') {
options.errorsOnly = true;
} else if (arg === '--successes-only') {
options.successesOnly = true;
} else if (arg === '--decisions-only') {
options.decisionsOnly = true;
} else if (arg === '--update-file' && i + 1 < args.length) {
options.updateFile = args[++i];
} else if (arg === '--generate-checks') {
options.generateChecks = true;
} else if (arg === '--format' && i + 1 < args.length) {
options.format = args[++i];
} else if (arg === '--output' && i + 1 < args.length) {
options.output = args[++i];
} else if (arg === '--check' && i + 1 < args.length) {
options.check = args[++i];
} else if (arg === '--file' && i + 1 < args.length) {
options.file = args[++i];
}
}
// Main execution
async function main() {
const detector = new PatternDetector();
// Pre-flight check mode
if (options.check) {
const result = await detector.preFlightCheck(options.check, options.file);
console.log(JSON.stringify(result, null, 2));
process.exit(result.warnings.length > 0 ? 1 : 0);
}
// Pattern extraction mode
console.log('📊 Extracting patterns from transcripts...');
const patterns = await detector.extractPatterns(process.cwd(), options);
// Format output
let output;
if (options.format === 'json') {
output = JSON.stringify(patterns, null, 2);
} else {
output = detector.formatMarkdown(patterns);
}
// Save or print
if (options.output) {
await fs.writeFile(options.output, output, 'utf-8');
console.log(`✅ Patterns saved to ${options.output}`);
} else {
console.log(output);
}
// Update technical patterns file if requested
if (options.updateFile) {
await detector.updateTechnicalPatterns(patterns, options.updateFile);
console.log(`✅ Updated ${options.updateFile}`);
}
// Generate pre-flight checks if requested
if (options.generateChecks) {
const checks = detector.generatePreFlightChecks(patterns);
const checksPath = '.claude/hooks/pre-flight-checks.js';
await fs.mkdir(path.dirname(checksPath), { recursive: true });
await fs.writeFile(checksPath, checks, 'utf-8');
console.log(`✅ Generated pre-flight checks at ${checksPath}`);
}
}
main().catch(console.error);