Skip to content

feat: add extensible recipe registry and CLI command#126

Merged
Akash-nath29 merged 5 commits intomainfrom
dev
Jan 30, 2026
Merged

feat: add extensible recipe registry and CLI command#126
Akash-nath29 merged 5 commits intomainfrom
dev

Conversation

@Akash-nath29
Copy link
Owner

This pull request introduces a new "Recipe System" feature to Coderrr, allowing users to define, list, and manage sets of tasks as reusable recipes. The implementation includes documentation, core management logic, a user interface for listing recipes, validation utilities, and automated tests to ensure functionality.

Recipe System Implementation

  • Added the RecipeManager class in src/recipeManager.js to handle recipe storage, listing, and retrieval, including automatic creation of a default "ping" recipe for health checks.
  • Created a user interface in src/recipeUI.js for displaying available recipes in the terminal with formatted output using chalk.

Documentation

  • Added a new section to docs/recipes.md explaining how to use the recipe system, including listing, running, and creating custom recipes.

Validation and Testing

  • Implemented a recipe validation utility in src/utils/recipeValidator.js to ensure custom recipes have the required fields and structure.
  • Added a test in test/recipes.test.js to verify that the default "ping" recipe is present and correctly configured.

Copilot AI review requested due to automatic review settings January 30, 2026 20:44
@vercel
Copy link

vercel bot commented Jan 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
coderrr-backend Ready Ready Preview, Comment Jan 30, 2026 8:44pm

@github-actions
Copy link

🚀 Thanks for opening a Pull Request!

A maintainer will review this soon.

Meanwhile:
✔ Ensure tests are passing
✔ Link related issues
✔ Code follows the project guidelines

Your contribution helps make this project better!

@Akash-nath29 Akash-nath29 merged commit 16f91ec into main Jan 30, 2026
34 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request introduces a Recipe System feature to Coderrr, allowing users to define reusable sets of tasks as JSON files. The implementation includes a recipe manager, UI for listing recipes, validation utilities, documentation, CLI integration, and a basic test.

Changes:

  • Added RecipeManager class to manage recipe storage in ~/.coderrr/recipes/ with automatic creation of a default "ping" recipe
  • Created UI component for displaying available recipes with formatted terminal output
  • Implemented recipe validation utility to check recipe structure (though not yet integrated)
  • Added CLI command coderrr recipe [name] with --list option (execution logic incomplete)
  • Documented basic recipe usage and custom recipe creation format

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/recipeManager.js Core recipe management class with methods for listing and retrieving recipes from ~/.coderrr/recipes/
src/recipeUI.js Terminal UI component for displaying recipe lists with chalk-based formatting
src/utils/recipeValidator.js Validation utility for checking recipe structure (currently unused)
bin/coderrr.js CLI integration adding recipe command with list functionality; execution logic is incomplete
docs/recipes.md Documentation explaining recipe usage, listing, and custom recipe creation
test/recipes.test.js Basic test verifying the default "ping" recipe exists

const recipes = recipeManager.listRecipes();
const ping = recipes.find(r => r.id === 'ping');
expect(ping).toBeDefined();
expect(ping.name).toBe('ping');
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test expects the "ping" recipe to have a "name" field set to "ping", but according to the RecipeManager implementation (line 17 in recipeManager.js), the recipe object has a "name" field set to "ping". However, the test is checking ping.name which comes from the object returned by listRecipes(). The listRecipes() method spreads the content object after setting the "id" property (line 29: { id: f.replace('.json', ''), ...content }), so the recipe should have both "id" and "name" fields. This test should pass, but it's testing for the "name" field when the "id" field is what was used to find the recipe. Consider testing ping.id instead since that's the field used in the find condition, or test both fields for completeness.

Copilot uses AI. Check for mistakes.
console.log(chalk.yellow('No recipes found in ~/.coderrr/recipes'));
} else {
recipes.forEach(r => {
console.log(`${chalk.cyan.bold(r.id)}: ${chalk.white(r.description || 'No description')}`);
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI displays r.description || 'No description', but the recipeValidator.js doesn't require the "description" field. This creates an inconsistency where custom recipes without descriptions will show "No description", but the validator won't flag this as an issue. Consider either making "description" optional in the validator with a default value, or adding a recommendation for including descriptions in the documentation.

Suggested change
console.log(`${chalk.cyan.bold(r.id)}: ${chalk.white(r.description || 'No description')}`);
const hasDescription = typeof r.description === 'string' && r.description.trim().length > 0;
if (hasDescription) {
console.log(`${chalk.cyan.bold(r.id)}: ${chalk.white(r.description)}`);
} else {
console.log(chalk.cyan.bold(r.id));
}

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +30
return files.map(f => {
const content = JSON.parse(fs.readFileSync(path.join(RECIPES_DIR, f), 'utf8'));
return { id: f.replace('.json', ''), ...content };
});
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The listRecipes() method will throw an error if any JSON file in the recipes directory is malformed. Unlike other managers in the codebase (e.g., configManager.js lines 31-40 and insights.js lines 40-42), there's no try-catch error handling. If a user creates an invalid JSON file, this will crash the application. Consider wrapping the file reading and parsing in a try-catch block and either skipping invalid recipes or returning an error structure for each failed recipe.

Suggested change
return files.map(f => {
const content = JSON.parse(fs.readFileSync(path.join(RECIPES_DIR, f), 'utf8'));
return { id: f.replace('.json', ''), ...content };
});
return files.reduce((recipes, f) => {
try {
const content = JSON.parse(fs.readFileSync(path.join(RECIPES_DIR, f), 'utf8'));
recipes.push({ id: f.replace('.json', ''), ...content });
} catch (error) {
// Skip malformed recipe files to avoid crashing the application
}
return recipes;
}, []);

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +38
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
return null;
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getRecipe() method will throw an error if the recipe file contains malformed JSON. Similar to listRecipes(), there's no error handling. Following the error handling pattern seen in configManager.js (lines 31-40), consider wrapping the file read and JSON parse operations in a try-catch block and returning null on error, which is already the expected return value when a file doesn't exist.

Suggested change
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
return null;
if (!fs.existsSync(filePath)) {
return null;
}
try {
const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content);
} catch (error) {
// If the recipe file is malformed or unreadable, treat it as missing
return null;
}

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +18
Save a `.json` file in `~/.coderrr/recipes/`:
```json
{
"name": "Quick Express",
"tasks": ["Initialize npm", "Install express", "Create app.js"]
} No newline at end of file
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation doesn't mention that the JSON filename (e.g., "quick-express.json") serves as the recipe ID used to run it. Based on the implementation in recipeManager.js (line 29) and bin/coderrr.js (line 48), users would run coderrr recipe quick-express (using the filename without .json extension), not coderrr recipe "Quick Express" (using the "name" field). Consider adding a note explaining that the filename becomes the recipe ID, such as: "Note: The filename (without .json) will be the ID used to run the recipe, e.g., save as quick-express.json and run with coderrr recipe quick-express."

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +10
List available recipes:
`coderrr recipe --list`

Run a recipe:
`coderrr recipe <name>`
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command syntax shown in the documentation uses backticks for inline code formatting, but the commands are split across two lines without proper markdown formatting. Line 7 shows coderrr recipe --list as inline code, which is correct. However, lines 9-10 split the command explanation across two lines in a way that might be confusing. Consider reformatting to match line 6-7's pattern, using backticks around the command on a single line for consistency.

Suggested change
List available recipes:
`coderrr recipe --list`
Run a recipe:
`coderrr recipe <name>`
List available recipes: `coderrr recipe --list`
Run a recipe: `coderrr recipe <name>`

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +9
* Validates the structure of a custom recipe
*/
const validateRecipe = (recipe) => {
const errors = [];
if (!recipe.name) errors.push("Missing 'name' field");
if (!Array.isArray(recipe.tasks) || recipe.tasks.length === 0) {
errors.push("'tasks' must be a non-empty array");
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validator checks for required fields "name" and "tasks", but the default recipe created in recipeManager.js also includes a "description" field. Additionally, according to the PR description and CLI implementation (bin/coderrr.js line 48-54), recipes are retrieved and executed by their ID (filename without .json), not by their "name" field. Consider either: (1) adding an "id" field requirement to the validator, or (2) clarifying that "name" serves as the display name while the filename serves as the ID. The validator is currently unused in the codebase, which means custom recipes won't be validated when loaded.

Suggested change
* Validates the structure of a custom recipe
*/
const validateRecipe = (recipe) => {
const errors = [];
if (!recipe.name) errors.push("Missing 'name' field");
if (!Array.isArray(recipe.tasks) || recipe.tasks.length === 0) {
errors.push("'tasks' must be a non-empty array");
}
* Validates the structure of a custom recipe.
*
* Notes:
* - `name` is the human-readable display name for the recipe.
* - The recipe "ID" is derived from the recipe filename (without `.json`),
* not from any field within the recipe object.
* - `description` is optional, but when present it should be a string.
*/
const validateRecipe = (recipe) => {
const errors = [];
// Basic shape check
if (!recipe || typeof recipe !== 'object') {
errors.push("Recipe must be an object");
} else {
// Validate display name
if (typeof recipe.name !== 'string' || recipe.name.trim().length === 0) {
errors.push("Missing or invalid 'name' field (expected non-empty string)");
}
// Validate tasks array
if (!Array.isArray(recipe.tasks) || recipe.tasks.length === 0) {
errors.push("'tasks' must be a non-empty array");
}
// Validate optional description
if (Object.prototype.hasOwnProperty.call(recipe, 'description') &&
typeof recipe.description !== 'string') {
errors.push("If provided, 'description' must be a string");
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +18
```json
{
"name": "Quick Express",
"tasks": ["Initialize npm", "Install express", "Create app.js"]
} No newline at end of file
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation file is missing a closing brace for the JSON code block. The JSON example that starts on line 14 with three backticks and "json" is never closed with three closing backticks. This will cause the markdown to render incorrectly. Add a line with three backticks (```) after line 18 to properly close the code block.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +10
describe('Recipe System', () => {
test('should find the default ping recipe', () => {
const recipes = recipeManager.listRecipes();
const ping = recipes.find(r => r.id === 'ping');
expect(ping).toBeDefined();
expect(ping.name).toBe('ping');
});
}); No newline at end of file
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test coverage for the recipe system is minimal. The test only verifies that the default "ping" recipe exists and has a name field. Following the pattern in insights.test.js which tests the full module functionality, consider adding tests for: (1) listing recipes when the directory is empty or doesn't exist, (2) listing multiple recipes, (3) retrieving a specific recipe with getRecipe(), (4) handling malformed JSON files gracefully, and (5) verifying the recipe validator functionality. This would provide more comprehensive coverage of the new feature.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants