context-forge
Version:
AI orchestration platform with autonomous teams, enhancement planning, migration tools, 25+ slash commands, checkpoints & hooks. Multi-IDE: Claude, Cursor, Windsurf, Cline, Copilot
236 lines (227 loc) ⢠9.61 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runPrpCommand = void 0;
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const ora_1 = __importDefault(require("ora"));
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const inquirer_1 = __importDefault(require("inquirer"));
const child_process_1 = require("child_process");
exports.runPrpCommand = new commander_1.Command('run-prp')
.description('Execute a PRP (Product Requirement Prompt) with Claude Code')
.argument('[prp-name]', 'Name of the PRP file (without .md extension)')
.option('-p, --prp-path <path>', 'Direct path to PRP file')
.option('-i, --interactive', 'Run in interactive mode', true)
.option('-o, --output-format <format>', 'Output format: text, json, stream-json', 'text')
.option('-m, --model <model>', 'CLI executable for the LLM', 'claude')
.action(async (prpName, options) => {
console.log(chalk_1.default.blue.bold('\nš Context Forge PRP Runner\n'));
const spinner = (0, ora_1.default)();
try {
// Determine PRP file path
let prpPath;
if (options.prpPath) {
prpPath = path_1.default.resolve(options.prpPath);
}
else if (prpName) {
// Look in PRPs directory
prpPath = path_1.default.join(process.cwd(), 'PRPs', `${prpName}.md`);
// Also check with -prp suffix
if (!(await fs_extra_1.default.pathExists(prpPath))) {
prpPath = path_1.default.join(process.cwd(), 'PRPs', `${prpName}-prp.md`);
}
}
else {
// Interactive selection
const prpsDir = path_1.default.join(process.cwd(), 'PRPs');
if (!(await fs_extra_1.default.pathExists(prpsDir))) {
console.error(chalk_1.default.red('No PRPs directory found. Run this command from a Context Forge project.'));
process.exit(1);
}
const prpFiles = (await fs_extra_1.default.readdir(prpsDir)).filter((f) => f.endsWith('.md') && !f.includes('README') && !f.includes('template'));
if (prpFiles.length === 0) {
console.error(chalk_1.default.red('No PRP files found in PRPs directory.'));
process.exit(1);
}
const { selectedPrp } = await inquirer_1.default.prompt([
{
type: 'list',
name: 'selectedPrp',
message: 'Select a PRP to execute:',
choices: prpFiles.map((f) => ({
name: f.replace('.md', ''),
value: path_1.default.join(prpsDir, f),
})),
},
]);
prpPath = selectedPrp;
}
// Verify PRP exists
if (!(await fs_extra_1.default.pathExists(prpPath))) {
console.error(chalk_1.default.red(`PRP file not found: ${prpPath}`));
process.exit(1);
}
console.log(chalk_1.default.cyan(`\nš Loading PRP: ${path_1.default.basename(prpPath)}`));
// Read PRP content
const prpContent = await fs_extra_1.default.readFile(prpPath, 'utf-8');
// Build meta prompt
const metaPrompt = buildMetaPrompt(prpContent);
// Check if claude is available
spinner.start('Checking Claude Code availability...');
const claudeAvailable = await checkClaudeAvailable();
spinner.stop();
if (!claudeAvailable) {
console.error(chalk_1.default.red('\nClaude Code not found. Please install it first:'));
console.log(chalk_1.default.yellow('npm install -g @anthropic-ai/claude-code'));
process.exit(1);
}
// Execute with Claude
console.log(chalk_1.default.cyan('\nš¤ Executing PRP with Claude Code...\n'));
if (options.interactive) {
// Interactive mode - user can continue conversation
await runInteractive(metaPrompt);
}
else {
// Headless mode - single execution
await runHeadless(metaPrompt, options.outputFormat);
}
}
catch (error) {
spinner.fail('PRP execution failed');
console.error(chalk_1.default.red('Error:'), error);
process.exit(1);
}
});
function buildMetaPrompt(prpContent) {
return `Ingest and understand the Product Requirement Prompt (PRP) below in detail.
# WORKFLOW GUIDANCE:
## Planning Phase
- Think hard before you code. Create a comprehensive plan addressing all requirements.
- Break down complex tasks into smaller, manageable steps.
- Use the TodoWrite tool to create and track your implementation plan.
- Identify implementation patterns from existing code to follow.
## Implementation Phase
- Follow code conventions and patterns found in existing files.
- Implement one component at a time and verify it works correctly.
- Write clear, maintainable code with appropriate comments.
- Consider error handling, edge cases, and potential security issues.
- Use type hints to ensure type safety.
## Testing Phase
- Test each component thoroughly as you build it.
- Use the provided validation gates to verify your implementation.
- Verify that all requirements have been satisfied.
- Run all validation commands and fix any issues.
## Example Implementation Approach:
1. Analyze the PRP requirements in detail
2. Search for and understand existing patterns in the codebase
3. Search the Web and gather additional context and examples
4. Create a step-by-step implementation plan with TodoWrite
5. Implement core functionality first, then additional features
6. Test and validate each component
7. Ensure all validation gates pass
When all validation gates pass and requirements are met, output "ā
PRP COMPLETE" on a new line.
---
# PRP CONTENT:
${prpContent}`;
}
async function checkClaudeAvailable() {
return new Promise((resolve) => {
const check = (0, child_process_1.spawn)('which', ['claude']);
check.on('close', (code) => {
resolve(code === 0);
});
check.on('error', () => {
resolve(false);
});
});
}
async function runInteractive(prompt) {
return new Promise((resolve, reject) => {
// Start claude in interactive mode with prompt via stdin
const claude = (0, child_process_1.spawn)('claude', [], {
stdio: ['pipe', 'inherit', 'inherit'],
});
// Send the prompt
claude.stdin.write(prompt);
claude.stdin.end();
claude.on('close', (code) => {
if (code === 0) {
console.log(chalk_1.default.green('\nā
PRP execution completed successfully!'));
resolve();
}
else {
reject(new Error(`Claude exited with code ${code}`));
}
});
claude.on('error', (err) => {
reject(err);
});
});
}
async function runHeadless(prompt, outputFormat) {
return new Promise((resolve, reject) => {
// Run claude with -p flag for single execution
const args = ['-p', prompt];
if (outputFormat !== 'text') {
args.push('--output-format', outputFormat);
}
const claude = (0, child_process_1.spawn)('claude', args, {
stdio: ['ignore', 'pipe', 'pipe'],
});
let output = '';
let errorOutput = '';
claude.stdout.on('data', (data) => {
const chunk = data.toString();
output += chunk;
if (outputFormat === 'stream-json') {
// Stream JSON output line by line
const lines = chunk.split('\n').filter((line) => line.trim());
for (const line of lines) {
try {
const parsed = JSON.parse(line);
console.log(chalk_1.default.gray('[Stream]'), parsed);
}
catch {
// Not valid JSON, skip
}
}
}
});
claude.stderr.on('data', (data) => {
errorOutput += data.toString();
});
claude.on('close', (code) => {
if (code === 0) {
if (outputFormat === 'text') {
console.log(output);
}
else if (outputFormat === 'json') {
try {
const parsed = JSON.parse(output);
console.log(JSON.stringify(parsed, null, 2));
}
catch {
console.error(chalk_1.default.red('Failed to parse JSON output'));
console.log(output);
}
}
if (output.includes('ā
PRP COMPLETE')) {
console.log(chalk_1.default.green('\nā
PRP execution completed successfully!'));
}
resolve();
}
else {
console.error(chalk_1.default.red('Error output:'), errorOutput);
reject(new Error(`Claude exited with code ${code}`));
}
});
claude.on('error', (err) => {
reject(err);
});
});
}
//# sourceMappingURL=run-prp.js.map