aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
467 lines (404 loc) • 15.1 kB
JavaScript
/**
* Claude-Powered Prompt Generator for External Ralph Loop
*
* Replaces template-based prompts with Claude-generated dynamic prompts
* that adapt to project state, previous iteration history, and PID metrics.
*
* @implements Issue #22 - Claude Intelligence Layer
* @references REF-015 Self-Refine, REF-021 Reflexion
*/
import { spawnSync } from 'child_process';
/**
* @typedef {Object} PromptContext
* @property {string} objective - Task objective
* @property {string} completionCriteria - Completion criteria
* @property {number} iteration - Current iteration number
* @property {number} maxIterations - Maximum iterations
* @property {string} loopId - Loop identifier
* @property {Object} stateAssessment - Output from StateAssessor
* @property {Object} pidMetrics - PID controller metrics
* @property {Object} validationResults - Pre-iteration validation results
* @property {Object} history - Previous iteration history
* @property {string[]} learnings - Accumulated learnings
* @property {Object} strategyPlan - Strategy from StrategyPlanner
*/
/**
* @typedef {Object} GeneratedPrompt
* @property {string} prompt - Main prompt text
* @property {string} systemContext - System context injection
* @property {string[]} focusAreas - Priority focus areas
* @property {string[]} toolSuggestions - Recommended tools to use
* @property {Object} metadata - Generation metadata
*/
/**
* @typedef {Object} ClaudePromptGeneratorOptions
* @property {string} [model='sonnet'] - Claude model for generation
* @property {number} [timeout=90000] - Generation timeout in ms
* @property {boolean} [verbose=false] - Enable verbose output
*/
export class ClaudePromptGenerator {
/**
* @param {ClaudePromptGeneratorOptions} options
*/
constructor(options = {}) {
this.model = options.model || 'sonnet';
this.timeout = options.timeout || 90000;
this.verbose = options.verbose || false;
/** @type {import('./provider-adapter.mjs').ProviderAdapter|null} */
this.providerAdapter = null;
}
/**
* Set the provider adapter for CLI abstraction
* @param {import('./provider-adapter.mjs').ProviderAdapter} adapter
*/
setProviderAdapter(adapter) {
this.providerAdapter = adapter;
}
/**
* Generate dynamic prompt using Claude analysis
* @param {PromptContext} context - Context for prompt generation
* @returns {Promise<GeneratedPrompt>}
*/
async generate(context) {
if (this.verbose) {
console.log('[ClaudePromptGenerator] Generating dynamic prompt...');
}
try {
// Build analysis prompt for Claude
const analysisPrompt = this._buildAnalysisPrompt(context);
// Use adapter for binary and args if available
const binary = this.providerAdapter ? this.providerAdapter.getBinary() : 'claude';
const args = this.providerAdapter
? this.providerAdapter.buildAnalysisArgs({
prompt: analysisPrompt,
model: this.model,
})
: [
'--dangerously-skip-permissions',
'--print',
'--output-format', 'json',
'--model', this.model,
analysisPrompt,
];
const result = spawnSync(binary, args, {
encoding: 'utf8',
timeout: this.timeout,
});
if (result.status !== 0) {
console.error('[ClaudePromptGenerator] Claude invocation failed:', result.stderr);
return this._buildFallbackPrompt(context);
}
// Parse Claude's response
const generated = this._parseClaudeResponse(result.stdout);
if (!generated) {
console.warn('[ClaudePromptGenerator] Failed to parse response, using fallback');
return this._buildFallbackPrompt(context);
}
// Enhance with context-aware tool suggestions
generated.toolSuggestions = this._suggestTools(context, generated);
return generated;
} catch (error) {
console.error('[ClaudePromptGenerator] Error:', error.message);
return this._buildFallbackPrompt(context);
}
}
/**
* Build analysis prompt for Claude
* @private
* @param {PromptContext} context
* @returns {string}
*/
_buildAnalysisPrompt(context) {
const parts = [];
parts.push('# Generate Dynamic Claude Code Session Prompt');
parts.push('');
parts.push('You are generating a prompt for a Claude Code session that is part of an External Ralph loop.');
parts.push('Analyze the provided context and generate a focused, actionable prompt.');
parts.push('');
// Task context
parts.push('## Task Information');
parts.push('');
parts.push(`**Objective**: ${context.objective}`);
parts.push(`**Completion Criteria**: ${context.completionCriteria}`);
parts.push(`**Iteration**: ${context.iteration} / ${context.maxIterations}`);
parts.push(`**Loop ID**: ${context.loopId}`);
parts.push('');
// State assessment
if (context.stateAssessment) {
parts.push('## Current State');
parts.push('');
parts.push(`**Progress**: ${context.stateAssessment.estimatedProgress || 0}%`);
parts.push(`**Phase**: ${context.stateAssessment.phase || 'unknown'}`);
if (context.stateAssessment.accomplishments?.length > 0) {
parts.push('');
parts.push('**Accomplishments**:');
for (const acc of context.stateAssessment.accomplishments.slice(0, 5)) {
parts.push(`- ${acc}`);
}
}
if (context.stateAssessment.blockers?.length > 0) {
parts.push('');
parts.push('**Blockers**:');
for (const blocker of context.stateAssessment.blockers) {
parts.push(`- ${blocker}`);
}
}
}
// PID metrics
if (context.pidMetrics) {
parts.push('');
parts.push('## Control Metrics');
parts.push('');
parts.push(`**Completion Gap**: ${(context.pidMetrics.proportional * 100).toFixed(1)}%`);
parts.push(`**Accumulated Error**: ${(context.pidMetrics.integral || 0).toFixed(2)}`);
parts.push(`**Trend**: ${context.pidMetrics.trend || 'unknown'}`);
if (context.pidMetrics.velocity !== undefined) {
const velocitySign = context.pidMetrics.velocity >= 0 ? '+' : '';
parts.push(`**Velocity**: ${velocitySign}${(context.pidMetrics.velocity * 100).toFixed(1)}%/iteration`);
}
}
// Strategy plan
if (context.strategyPlan) {
parts.push('');
parts.push('## Recommended Strategy');
parts.push('');
parts.push(`**Approach**: ${context.strategyPlan.approach || 'continue'}`);
if (context.strategyPlan.reasoning) {
parts.push(`**Reasoning**: ${context.strategyPlan.reasoning}`);
}
if (context.strategyPlan.priorities?.length > 0) {
parts.push('');
parts.push('**Priorities**:');
for (let i = 0; i < Math.min(3, context.strategyPlan.priorities.length); i++) {
parts.push(`${i + 1}. ${context.strategyPlan.priorities[i]}`);
}
}
}
// Validation results
if (context.validationResults) {
parts.push('');
parts.push('## Pre-Iteration Validation');
parts.push('');
parts.push(`**Status**: ${context.validationResults.passed ? 'PASSED' : 'FAILED'}`);
if (context.validationResults.issues?.length > 0) {
parts.push('');
parts.push('**Issues to Address**:');
for (const issue of context.validationResults.issues.slice(0, 3)) {
parts.push(`- ${issue.message} (${issue.severity})`);
}
}
}
// Learnings
if (context.learnings?.length > 0) {
parts.push('');
parts.push('## Accumulated Learnings');
parts.push('');
for (const learning of context.learnings.slice(-5)) {
parts.push(`- ${learning}`);
}
}
// Output format instructions
parts.push('');
parts.push('## Output Format');
parts.push('');
parts.push('Provide a JSON response with:');
parts.push('');
parts.push('```json');
parts.push('{');
parts.push(' "prompt": "The main prompt text for Claude Code session",');
parts.push(' "systemContext": "System context to inject before the prompt",');
parts.push(' "focusAreas": ["Priority 1", "Priority 2", "Priority 3"],');
parts.push(' "reasoning": "Why this prompt structure is appropriate",');
parts.push(' "urgency": "low|normal|high|critical"');
parts.push('}');
parts.push('```');
parts.push('');
parts.push('**Guidelines**:');
parts.push('');
parts.push('1. Make the prompt specific and actionable');
parts.push('2. Reference concrete files/paths when available');
parts.push('3. Prioritize based on PID metrics and validation results');
parts.push('4. If blockers exist, address them first');
parts.push('5. Include completion signal instructions: `{"ralph_external_completion": true, "success": boolean}`');
parts.push('6. Adapt tone based on urgency level');
return parts.join('\n');
}
/**
* Parse Claude's JSON response
* @private
* @param {string} stdout
* @returns {GeneratedPrompt|null}
*/
_parseClaudeResponse(stdout) {
try {
// Extract JSON from stdout
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
return null;
}
const parsed = JSON.parse(jsonMatch[0]);
// Validate required fields
if (!parsed.prompt || typeof parsed.prompt !== 'string') {
console.error('[ClaudePromptGenerator] Invalid response: missing prompt');
return null;
}
return {
prompt: parsed.prompt,
systemContext: parsed.systemContext || '',
focusAreas: Array.isArray(parsed.focusAreas) ? parsed.focusAreas : [],
toolSuggestions: [], // Populated by _suggestTools
metadata: {
reasoning: parsed.reasoning || '',
urgency: parsed.urgency || 'normal',
generatedAt: new Date().toISOString(),
model: this.model,
},
};
} catch (error) {
console.error('[ClaudePromptGenerator] Parse error:', error.message);
return null;
}
}
/**
* Build fallback prompt when Claude generation fails
* @private
* @param {PromptContext} context
* @returns {GeneratedPrompt}
*/
_buildFallbackPrompt(context) {
const parts = [];
parts.push('# Continue External Ralph Loop');
parts.push('');
parts.push(`**Objective**: ${context.objective}`);
parts.push(`**Completion Criteria**: ${context.completionCriteria}`);
parts.push(`**Iteration**: ${context.iteration} / ${context.maxIterations}`);
parts.push('');
if (context.stateAssessment?.estimatedProgress !== undefined) {
parts.push(`**Current Progress**: ~${context.stateAssessment.estimatedProgress}%`);
parts.push('');
}
if (context.stateAssessment?.blockers?.length > 0) {
parts.push('## Priority: Address Blockers');
parts.push('');
for (const blocker of context.stateAssessment.blockers) {
parts.push(`- ${blocker}`);
}
parts.push('');
}
if (context.validationResults?.issues?.length > 0) {
parts.push('## Pre-Iteration Issues');
parts.push('');
for (const issue of context.validationResults.issues) {
parts.push(`- ${issue.message}`);
}
parts.push('');
}
parts.push('## Instructions');
parts.push('');
parts.push('1. Continue working toward the objective');
parts.push('2. Address any blockers or validation issues first');
parts.push('3. Use /ralph for iterative subtasks if needed');
parts.push('4. Update .aiwg/ artifacts to track progress');
parts.push('5. When complete, output: `{"ralph_external_completion": true, "success": true}`');
return {
prompt: parts.join('\n'),
systemContext: `Resuming External Ralph loop ${context.loopId}, iteration ${context.iteration}`,
focusAreas: context.stateAssessment?.blockers || ['Continue implementation'],
toolSuggestions: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'],
metadata: {
reasoning: 'Fallback prompt due to Claude generation failure',
urgency: 'normal',
generatedAt: new Date().toISOString(),
model: 'fallback',
},
};
}
/**
* Suggest appropriate tools based on context
* @private
* @param {PromptContext} context
* @param {GeneratedPrompt} generated
* @returns {string[]}
*/
_suggestTools(context, generated) {
const suggestions = new Set();
// Always suggest basic tools
suggestions.add('Read');
suggestions.add('Write');
// State-based suggestions
if (context.stateAssessment) {
const { phase, filesChanged } = context.stateAssessment;
// Early phase - exploration
if (phase === 'early') {
suggestions.add('Glob');
suggestions.add('Grep');
suggestions.add('Read');
}
// Mid/late phase - implementation
if (phase === 'mid' || phase === 'late') {
suggestions.add('Edit');
suggestions.add('Write');
suggestions.add('Bash');
}
// If tests involved
if (filesChanged?.byCategory?.test?.length > 0) {
suggestions.add('Bash'); // For running tests
}
// If config changes
if (filesChanged?.byCategory?.config?.length > 0) {
suggestions.add('Edit');
}
}
// Validation-based suggestions
if (context.validationResults?.issues?.length > 0) {
suggestions.add('Grep'); // For finding issues
suggestions.add('Edit'); // For fixing issues
}
// Strategy-based suggestions
if (context.strategyPlan?.approach === 'debug') {
suggestions.add('Bash'); // For debugging commands
suggestions.add('Grep'); // For searching logs
}
return Array.from(suggestions);
}
/**
* Validate generated prompt quality
* @param {GeneratedPrompt} generated
* @returns {{valid: boolean, issues: string[]}}
*/
validatePrompt(generated) {
const issues = [];
if (!generated.prompt || generated.prompt.length < 50) {
issues.push('Prompt is too short (< 50 characters)');
}
if (!generated.prompt.includes('ralph_external_completion')) {
issues.push('Prompt missing completion signal instructions');
}
if (generated.focusAreas.length === 0) {
issues.push('No focus areas specified');
}
if (generated.toolSuggestions.length === 0) {
issues.push('No tool suggestions provided');
}
return {
valid: issues.length === 0,
issues,
};
}
/**
* Get summary of prompt generation
* @param {GeneratedPrompt} generated
* @returns {Object}
*/
getSummary(generated) {
return {
promptLength: generated.prompt.length,
focusAreaCount: generated.focusAreas.length,
toolSuggestionCount: generated.toolSuggestions.length,
urgency: generated.metadata.urgency,
model: generated.metadata.model,
generatedAt: generated.metadata.generatedAt,
};
}
}
export default ClaudePromptGenerator;