tasq/node_modules/@claude-flow/hooks/dist/reasoningbank/guidance-provider.js

350 lines
13 KiB
JavaScript

/**
* V3 Guidance Provider
*
* Generates Claude-visible guidance output from ReasoningBank patterns.
* Outputs plain text (exit 0) or JSON with additionalContext.
*
* @module @claude-flow/hooks/reasoningbank/guidance-provider
*/
import { ReasoningBank } from './index.js';
/**
* Security patterns to block
*/
const BLOCKED_PATTERNS = ['.env', '.pem', '.key', 'credentials', 'secret', 'password'];
const WARNED_PATTERNS = ['prod', 'production', 'live', 'deploy'];
/**
* Dangerous commands to block
*/
const DANGEROUS_COMMANDS = [
'rm -rf',
'drop database',
'truncate',
'push.*--force|--force.*push',
'reset --hard',
'format c:',
];
/**
* Risky commands to warn about
*/
const RISKY_COMMANDS = ['npm publish', 'git push', 'deploy', 'kubectl apply'];
/**
* Guidance Provider class
*
* Converts ReasoningBank patterns into Claude-visible guidance.
*/
export class GuidanceProvider {
reasoningBank;
constructor(reasoningBank) {
this.reasoningBank = reasoningBank || new ReasoningBank();
}
/**
* Initialize the provider
*/
async initialize() {
await this.reasoningBank.initialize();
}
/**
* Generate session start context
* Returns plain text that Claude will see
*/
async generateSessionContext() {
const stats = this.reasoningBank.getStats();
const lines = [
'## V3 Development Context',
'',
'**Architecture**: Domain-Driven Design with 15 @claude-flow modules',
'**Priority**: Security-first (CVE-1, CVE-2, CVE-3 remediation)',
'',
'**Performance Targets**:',
'- HNSW search: 150x-12,500x faster (<1ms)',
'- Flash Attention: 2.49x-7.47x speedup',
'- Memory: 50-75% reduction',
'',
'**Active Patterns**:',
'- Use TDD London School (mock-first)',
'- Event sourcing for state changes',
'- agentic-flow@alpha as core foundation',
'- Bounded contexts with clear interfaces',
'',
'**Code Quality Rules**:',
'- Files under 500 lines',
'- No hardcoded secrets',
'- Input validation at boundaries',
'- Typed interfaces for all public APIs',
'',
`**Learned Patterns**: ${stats.shortTermCount + stats.longTermCount} available`,
`**Avg Search Time**: ${stats.avgSearchTime.toFixed(2)}ms`,
];
return lines.join('\n');
}
/**
* Generate user prompt context
* Returns plain text guidance based on prompt analysis
*/
async generatePromptContext(prompt) {
const guidance = await this.reasoningBank.generateGuidance({
event: 'pre-route',
timestamp: new Date(),
routing: { task: prompt },
});
const lines = [];
// Add domain-specific guidance
if (guidance.recommendations.length > 0) {
const domains = this.detectDomains(prompt);
for (const domain of domains) {
lines.push(`**${this.capitalize(domain)} Guidance**:`);
for (const rec of guidance.recommendations.slice(0, 5)) {
lines.push(`- ${rec}`);
}
lines.push('');
}
}
// Add relevant patterns
if (guidance.patterns.length > 0) {
lines.push('**Relevant Learned Patterns**:');
for (const { pattern, similarity } of guidance.patterns.slice(0, 3)) {
lines.push(`- ${pattern.strategy} (${(similarity * 100).toFixed(0)}% match)`);
}
}
return lines.join('\n');
}
/**
* Generate pre-edit guidance
* Returns JSON for Claude hook system
*/
async generatePreEditGuidance(filePath) {
// Security checks - block sensitive files
for (const pattern of BLOCKED_PATTERNS) {
if (filePath.toLowerCase().includes(pattern)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: `Security: Cannot edit ${pattern} files directly. Use environment variables instead.`,
},
};
}
}
// Warn about production files
for (const pattern of WARNED_PATTERNS) {
if (filePath.toLowerCase().includes(pattern)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'ask',
permissionDecisionReason: `This appears to be a ${pattern} file. Confirm this edit is intentional.`,
},
};
}
}
// Generate file-specific guidance from patterns
const guidance = await this.reasoningBank.generateGuidance({
event: 'pre-edit',
timestamp: new Date(),
file: { path: filePath, operation: 'modify' },
});
let contextGuidance = '';
// File type specific guidance
if (/test|spec/i.test(filePath)) {
contextGuidance = 'Testing file: Use TDD London School patterns. Mock dependencies, test behavior not implementation.';
}
else if (/security|auth/i.test(filePath)) {
contextGuidance = 'Security module: Validate inputs, use parameterized queries, no hardcoded secrets.';
}
else if (/memory|cache/i.test(filePath)) {
contextGuidance = 'Memory module: Consider HNSW indexing, batch operations, proper cleanup.';
}
else if (/swarm|coordinator/i.test(filePath)) {
contextGuidance = 'Swarm module: Use event-driven communication, handle failures gracefully.';
}
else if (/\.ts$/.test(filePath)) {
contextGuidance = 'TypeScript: Use strict types, avoid any, export interfaces for public APIs.';
}
// Add pattern-based guidance
if (guidance.patterns.length > 0) {
const patternHints = guidance.patterns
.slice(0, 2)
.map(p => p.pattern.strategy)
.join('; ');
contextGuidance += ` Similar patterns: ${patternHints}`;
}
if (contextGuidance) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'allow',
additionalContext: contextGuidance,
},
};
}
return { decision: 'allow' };
}
/**
* Generate post-edit feedback
* Returns JSON with quality feedback
*/
async generatePostEditFeedback(filePath, fileContent) {
const issues = [];
if (fileContent) {
// Check for console.log in non-test files
if (!/test|spec/i.test(filePath) && fileContent.includes('console.log')) {
issues.push('Remove console.log statements (use proper logging)');
}
// Check for TODO/FIXME
if (/TODO|FIXME|HACK/i.test(fileContent)) {
issues.push('Address TODO/FIXME comments before committing');
}
// Check for any type in TypeScript
if (/\.ts$/.test(filePath) && /:\s*any\b/.test(fileContent)) {
issues.push("Replace 'any' types with specific types");
}
// Check file size
const lines = fileContent.split('\n').length;
if (lines > 500) {
issues.push(`File exceeds 500 lines (${lines}). Consider splitting.`);
}
// Check for hardcoded secrets
if (/password\s*=\s*['"][^'"]+['"]|api[_-]?key\s*=\s*['"][^'"]+['"]/i.test(fileContent)) {
issues.push('Possible hardcoded secret detected. Use environment variables.');
}
}
// Store the edit as a pattern
await this.reasoningBank.storePattern(`Edit: ${filePath}`, 'code', {
operation: 'modify',
issues: issues.length,
});
if (issues.length > 0) {
return {
decision: 'allow',
reason: `Edit completed. Review suggestions:\n- ${issues.join('\n- ')}`,
hookSpecificOutput: {
hookEventName: 'PostToolUse',
additionalContext: `Quality check found items to address:\n- ${issues.join('\n- ')}`,
},
};
}
return { decision: 'allow' };
}
/**
* Generate pre-command guidance
* Returns JSON with risk assessment
*/
async generatePreCommandGuidance(command) {
// Block dangerous commands
for (const pattern of DANGEROUS_COMMANDS) {
if (new RegExp(pattern, 'i').test(command)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: `Destructive command blocked: ${pattern}. Use safer alternatives.`,
},
};
}
}
// Warn about risky commands
for (const pattern of RISKY_COMMANDS) {
if (command.toLowerCase().includes(pattern)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'ask',
permissionDecisionReason: `This command has external effects (${pattern}). Confirm before proceeding.`,
},
};
}
}
// Guide test commands
if (/npm test|vitest|jest|pnpm test/i.test(command)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'allow',
additionalContext: 'Running tests. If failures occur, fix them before proceeding.',
},
};
}
// Guide build commands
if (/npm run build|tsc|pnpm build/i.test(command)) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'allow',
additionalContext: 'Building project. Watch for type errors. All must pass before commit.',
},
};
}
return { decision: 'allow' };
}
/**
* Generate task routing guidance
* Returns plain text with agent recommendation
*/
async generateRoutingGuidance(task) {
const routing = await this.reasoningBank.routeTask(task);
const lines = [
`**Recommended Agent**: ${routing.agent}`,
`**Confidence**: ${routing.confidence}%`,
`**Reasoning**: ${routing.reasoning}`,
'',
];
if (routing.alternatives.length > 0) {
lines.push('**Alternatives**:');
for (const alt of routing.alternatives) {
lines.push(`- ${alt.agent} (${alt.confidence}%)`);
}
lines.push('');
}
if (routing.historicalPerformance) {
lines.push('**Historical Performance**:');
lines.push(`- Success rate: ${(routing.historicalPerformance.successRate * 100).toFixed(0)}%`);
lines.push(`- Avg quality: ${(routing.historicalPerformance.avgQuality * 100).toFixed(0)}%`);
lines.push(`- Similar tasks: ${routing.historicalPerformance.taskCount}`);
}
lines.push('');
lines.push(`Use Task tool with subagent_type="${routing.agent}" for optimal results.`);
return lines.join('\n');
}
/**
* Generate stop check
* Returns exit code 2 + stderr if work incomplete
*/
async generateStopCheck() {
const stats = this.reasoningBank.getStats();
// Check if there are uncommitted patterns
if (stats.shortTermCount > 10) {
return {
shouldStop: false,
reason: `${stats.shortTermCount} patterns not yet consolidated. Run consolidation before stopping.`,
};
}
return { shouldStop: true };
}
// ===== Helper Methods =====
detectDomains(text) {
const domains = [];
const lowerText = text.toLowerCase();
if (/security|auth|password|token|secret|cve|vuln/i.test(lowerText)) {
domains.push('security');
}
if (/test|spec|mock|coverage|tdd|assert/i.test(lowerText)) {
domains.push('testing');
}
if (/perf|optim|fast|slow|memory|cache|speed/i.test(lowerText)) {
domains.push('performance');
}
if (/architect|design|ddd|domain|refactor|struct/i.test(lowerText)) {
domains.push('architecture');
}
if (/fix|bug|error|issue|broken|fail|debug/i.test(lowerText)) {
domains.push('debugging');
}
return domains;
}
capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
}
// Export singleton
export const guidanceProvider = new GuidanceProvider();
//# sourceMappingURL=guidance-provider.js.map