@iflow-mcp/ejmockler-brutalist
Version:
Deploy Claude, Codex & Gemini CLI agents to demolish your work before users do. Real file analysis. Brutal honesty. Now with conversation continuation & intelligent pagination.
138 lines • 5.86 kB
JavaScript
/**
* BrutalistToolGenerator: The magic that transforms domains into tools
*
* This is the compositional algebra:
* CritiqueDomain × CriticPersona × ArgumentSpace × ExecutionStrategy = ToolConfig
*/
import { composeDomains } from '../domains/critique-domain.js';
import { inferCacheKeys, inferPrimaryArg } from '../domains/argument-space.js';
export class BrutalistToolGenerator {
/**
* Generate a single tool from domain + persona + argument space + strategy
*/
generateTool(domain, persona, argSpace, strategy) {
// System prompts are now retrieved at execution time from system-prompts.ts
// This reduces MCP initialization context and separates schema from execution concerns
// Note: Still generate systemPrompt for backwards compatibility with old tool-definitions.ts,
// but it's optional in ToolConfig. New code should use getSystemPrompt(analysisType).
// Merge argument schemas
const schemaExtensions = this.buildSchemaExtensions(argSpace);
// Infer cache keys and primary arg
const cacheKeyFields = inferCacheKeys(argSpace);
const primaryArgField = inferPrimaryArg(argSpace);
// Build context function if needed
const contextBuilder = this.buildContextBuilder(argSpace, domain);
// Map domain ID to analysis type (for backwards compatibility)
const analysisType = this.mapDomainToAnalysisType(domain.id);
return {
name: `roast_${domain.id}`,
description: this.renderDescription(domain, persona, strategy),
analysisType,
// systemPrompt omitted - retrieved at execution time via getSystemPrompt()
schemaExtensions,
cacheKeyFields,
primaryArgField,
contextBuilder: contextBuilder || undefined
};
}
/**
* Compose multiple domains into a single composite tool
*/
composeDomainsToTool(domains, persona, argSpace, strategy) {
const compositeId = domains.map(d => d.id).join('_and_');
const compositeName = domains.map(d => d.name).join(' and ');
const compositeDomain = composeDomains(domains, compositeId, compositeName);
return this.generateTool(compositeDomain, persona, argSpace, strategy);
}
/**
* Generate multiple tools from a domain across different personas/strategies
*/
generateVariations(domain, personas, argSpace, strategies) {
const tools = [];
for (const persona of personas) {
for (const strategy of strategies) {
tools.push(this.generateTool(domain, persona, argSpace, strategy));
}
}
return tools;
}
// Private helpers
buildSchemaExtensions(argSpace) {
// Combine base + domain arguments
const combined = argSpace.base.merge(argSpace.domain);
return combined.shape;
}
buildContextBuilder(argSpace, domain) {
// If argument space has a computed function, use it to build context
if (argSpace.computed) {
return (args) => {
const computed = argSpace.computed(args);
const parts = [];
// Add analysis type
parts.push(`${domain.name} analysis`);
// Add notable computed values
for (const [key, value] of Object.entries(computed)) {
if (value && key !== 'workingDirectory') {
parts.push(`${key}: ${value}`);
}
}
// Add user context if provided
if (args.context) {
parts.push(args.context);
}
return parts.join('. ');
};
}
// Simple context builder for basic cases
return (args) => {
return args.context || '';
};
}
renderDescription(domain, persona, strategy) {
const toneDesc = this.describeTone(persona.tone);
const modeDesc = this.describeMode(strategy.mode);
// Combine the flavorful intro with the domain's detailed description
return `Deploy ${toneDesc} AI critics to ${modeDesc} your ${domain.name.toLowerCase()}. ${domain.description}.`;
}
describeTone(tone) {
switch (tone) {
case 'brutal': return 'brutal';
case 'constructive': return 'constructive';
case 'balanced': return 'balanced';
case 'pedagogical': return 'pedagogical';
default: return 'expert';
}
}
describeMode(mode) {
switch (mode) {
case 'parallel': return 'systematically destroy';
case 'debate': return 'debate and challenge';
case 'sequential': return 'methodically analyze';
case 'tournament': return 'compete to find flaws in';
default: return 'analyze';
}
}
// NOTE: This method is now unused, as domain.description is directly injected
describeCapabilities(capabilities) {
const first = capabilities[0] || 'analyze';
return first.replace(/_/g, ' ');
}
mapDomainToAnalysisType(domainId) {
// Map new domain IDs to old analysis types for backwards compatibility
const mapping = {
'codebase': 'codebase',
'file_structure': 'fileStructure',
'dependencies': 'dependencies',
'git_history': 'gitHistory',
'test_coverage': 'testCoverage',
'idea': 'idea',
'architecture': 'architecture',
'research': 'research',
'security': 'security',
'product': 'product',
'infrastructure': 'infrastructure'
};
return mapping[domainId] || domainId;
}
}
//# sourceMappingURL=tool-generator.js.map