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.
189 lines (164 loc) • 4.59 kB
JavaScript
/**
* Claude CLI Provider Adapter for External Ralph Loop
*
* Extracts existing Claude-specific spawn logic into the ProviderAdapter
* interface. This adapter is the default and reproduces the exact behavior
* of the pre-adapter codebase.
*
* @implements Plan: Multi-Provider Support for External Ralph Loop
*/
import { join } from 'path';
import { homedir } from 'os';
import { ProviderAdapter, registerProvider } from './provider-adapter.mjs';
export class ClaudeAdapter extends ProviderAdapter {
/** @returns {string} */
getBinary() {
return 'claude';
}
/** @returns {string} */
getName() {
return 'claude';
}
/**
* Claude supports all capabilities.
* @returns {import('./provider-adapter.mjs').ProviderCapabilities}
*/
getCapabilities() {
return {
streamJson: true,
sessionResume: true,
budgetControl: true,
systemPrompt: true,
agentMode: true,
mcpConfig: true,
maxTurns: true,
};
}
/**
* Build args for the main headless session (async spawn).
*
* Reproduces the exact argument construction from SessionLauncher.buildArgs().
*
* @param {import('./provider-adapter.mjs').SessionArgs} options
* @returns {string[]}
*/
buildSessionArgs(options) {
const args = [
// SECURITY: This flag bypasses ALL permission prompts
// Required for headless operation but enables:
// - Unrestricted file read/write
// - Arbitrary command execution
// - Network access without confirmation
// See docs/ralph-external-security.md
'--dangerously-skip-permissions',
'--print',
'--output-format', 'stream-json',
];
// Session tracking
if (options.sessionId) {
args.push('--session-id', options.sessionId);
}
// Verbose mode
if (options.verbose) {
args.push('--verbose');
}
// Model selection (Claude passes through directly)
if (options.model) {
args.push('--model', this.mapModel(options.model));
}
// Budget control
if (options.budget) {
args.push('--max-budget-usd', String(options.budget));
}
// Max turns control
if (options.maxTurns) {
args.push('--max-turns', String(options.maxTurns));
}
// MCP configuration
if (options.mcpConfig) {
const configJson = typeof options.mcpConfig === 'string'
? options.mcpConfig
: JSON.stringify(options.mcpConfig);
args.push('--mcp-config', configJson);
}
// System prompt injection
if (options.systemPrompt) {
args.push('--append-system-prompt', options.systemPrompt);
}
// The prompt itself (must be last)
args.push(options.prompt);
return args;
}
/**
* Build args for short analysis calls (spawnSync).
*
* Used by OutputAnalyzer, StateAssessor, and ClaudePromptGenerator.
*
* @param {import('./provider-adapter.mjs').AnalysisArgs} options
* @returns {string[]}
*/
buildAnalysisArgs(options) {
const args = [
'--dangerously-skip-permissions',
'--print',
'--output-format', 'json',
];
// Model selection
if (options.model) {
args.push('--model', this.mapModel(options.model));
}
// Agent flag (for specialized analysis agents)
if (options.agent) {
args.push('--agent', options.agent);
}
// The analysis prompt (must be last)
args.push(options.prompt);
return args;
}
/**
* Claude model names pass through directly.
* opus/sonnet/haiku are native Claude model aliases.
*
* @param {string} genericModel
* @returns {string}
*/
mapModel(genericModel) {
// Claude accepts opus, sonnet, haiku natively
return genericModel;
}
/**
* Environment overrides for headless Claude sessions.
* @returns {Object<string, string>}
*/
getEnvOverrides() {
return {
CI: 'true',
};
}
/**
* Get Claude session transcript path.
*
* Claude stores transcripts at:
* ~/.claude/projects/{encoded-path}/{session-id}.jsonl
*
* Path encoding: Replace `/` with `-`, prepend `-`
* Example: /foo/bar → -foo-bar
*
* @param {string} sessionId
* @param {string} workingDir
* @returns {string}
*/
getTranscriptPath(sessionId, workingDir) {
const encodedPath = workingDir.replace(/\//g, '-');
return join(
homedir(),
'.claude',
'projects',
encodedPath,
`${sessionId}.jsonl`
);
}
}
// Self-register on import
registerProvider('claude', () => new ClaudeAdapter());
export default ClaudeAdapter;