Skip to content

feat: add extensible recipe registry and CLI command#125

Merged
Akash-nath29 merged 5 commits intodevfrom
feature
Jan 30, 2026
Merged

feat: add extensible recipe registry and CLI command#125
Akash-nath29 merged 5 commits intodevfrom
feature

Conversation

@Akash-nath29
Copy link
Copy Markdown
Owner

This pull request introduces a new "Recipe System" to Coderrr, allowing users to manage and execute pre-defined sets of tasks (recipes). The implementation includes recipe storage, listing, validation, and documentation, as well as initial testing to ensure the system works as intended.

Recipe System Implementation:

  • Added RecipeManager class in src/recipeManager.js for managing recipes, including directory setup, listing, and retrieval of recipes. It also creates a default "ping" recipe if none exist.
  • Added a recipe validator utility in src/utils/recipeValidator.js to ensure custom recipes have the required structure (name and a non-empty tasks array).

User Interface and Documentation:

  • Added src/recipeUI.js to display available recipes in a user-friendly, colorized format.
  • Added a new documentation section in docs/recipes.md explaining how to use and create recipes in Coderrr.

Testing:

  • Added a test in test/recipes.test.js to verify that the default "ping" recipe is present and correctly structured.

Dbansal06 and others added 5 commits January 30, 2026 23:49
Copilot AI review requested due to automatic review settings January 30, 2026 20:30
@vercel
Copy link
Copy Markdown

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:30pm

@github-actions
Copy link
Copy Markdown

🚀 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!

Copy link
Copy Markdown

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 PR introduces a first version of a “recipe system” that discovers JSON recipes from a user directory, exposes them via a new coderrr recipe CLI command, and documents how to create and list recipes, along with an initial test.

Changes:

  • Added RecipeManager to manage recipes under ~/.coderrr/recipes, including creating a default ping recipe, listing recipes, and loading a single recipe.
  • Added recipeUI and a new coderrr recipe CLI command for listing and (eventually) running recipes, plus a recipeValidator utility and a Jest test to verify the default recipe.
  • Added docs/recipes.md to explain recipe usage and how to define custom recipes.

Reviewed changes

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

Show a summary per file
File Description
src/recipeManager.js Implements recipe storage in ~/.coderrr/recipes, ensures the directory exists, seeds a default ping recipe, and provides listing and lookup helpers used by the CLI and tests.
src/recipeUI.js Adds a small chalk-based UI helper to pretty-print the available recipes read from RecipeManager.
src/utils/recipeValidator.js Introduces a utility for validating recipe objects (name and non-empty tasks), intended for use when loading custom recipes.
bin/coderrr.js Wires in the new recipe system through a recipe subcommand (with list and run modes) and keeps the existing insights command, extending the CLI surface area for recipes.
docs/recipes.md Documents the recipe concept, CLI usage (coderrr recipe --list / coderrr recipe <name>), and shows a JSON example for defining a custom recipe file.
test/recipes.test.js Adds a Jest test that exercises RecipeManager.listRecipes() and asserts that the default ping recipe is present and correctly named.

ensureDirectory() {
if (!fs.existsSync(RECIPES_DIR)) {
fs.mkdirSync(RECIPES_DIR, { recursive: true });
// Add a default "Hello World" recipe
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 comment says this creates a default "Hello World" recipe, but the actual recipe is named "ping" and has a different description. Please update the comment to match the behavior so future readers are not misled.

Suggested change
// Add a default "Hello World" recipe
// Add a default "ping" health check recipe

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

getRecipe(name) {
const filePath = path.join(RECIPES_DIR, `${name}.json`);
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
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.

JSON.parse is called on each recipe file without any error handling, so a single malformed or partially-written JSON file in the recipes directory will throw and break listRecipes() entirely. Consider wrapping this read/parse in a try/catch and either skipping invalid recipes or surfacing a clear validation error, potentially reusing the recipe validator utility.

Suggested change
return files.map(f => {
const content = JSON.parse(fs.readFileSync(path.join(RECIPES_DIR, f), 'utf8'));
return { id: f.replace('.json', ''), ...content };
});
}
getRecipe(name) {
const filePath = path.join(RECIPES_DIR, `${name}.json`);
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
const recipes = [];
for (const f of files) {
const filePath = path.join(RECIPES_DIR, f);
try {
const raw = fs.readFileSync(filePath, 'utf8');
const content = JSON.parse(raw);
recipes.push({ id: f.replace('.json', ''), ...content });
} catch (error) {
console.error(`Failed to read or parse recipe file "${f}": ${error.message}`);
}
}
return recipes;
}
getRecipe(name) {
const filePath = path.join(RECIPES_DIR, `${name}.json`);
if (fs.existsSync(filePath)) {
try {
const raw = fs.readFileSync(filePath, 'utf8');
return JSON.parse(raw);
} catch (error) {
throw new Error(`Failed to read or parse recipe "${name}": ${error.message}`);
}

Copilot uses AI. Check for mistakes.
getRecipe(name) {
const filePath = path.join(RECIPES_DIR, `${name}.json`);
if (fs.existsSync(filePath)) {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
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.

getRecipe also calls JSON.parse without guarding against invalid JSON, which means a corrupt recipe file will cause the CLI command to throw instead of returning null or a structured error. It would be safer to catch parse errors here and either return null or propagate a user-friendly error, ideally using the shared recipe validation logic.

Suggested change
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
const content = fs.readFileSync(filePath, 'utf8');
try {
return JSON.parse(content);
} catch (error) {
// If the recipe file contains invalid JSON, treat it as unavailable
console.error(`Failed to parse recipe "${name}" from ${filePath}: ${error.message}`);
return null;
}

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +16
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");
}
return {
valid: errors.length === 0,
errors
};
};

module.exports = { validateRecipe }; 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.

This validateRecipe helper is not imported or used anywhere, so recipes are never actually validated before use even though the PR description says custom recipes are validated. Either wire this into RecipeManager/the CLI flow (e.g., when loading or running recipes) or remove it until it is integrated to avoid dead code and confusion.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +8
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');
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.

This test uses the real RecipeManager singleton, which writes to and reads from ~/.coderrr/recipes and assumes a default ping recipe exists. That couples test results to the developer's home directory state (e.g., if they delete or change their recipes the test will fail), so consider isolating it by mocking os.homedir/fs or allowing RecipeManager to be pointed at a temporary directory.

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 JSON example code block is not closed with a matching fence, which will cause the Markdown renderer to treat the rest of the document as part of the code block. Please add a closing ``` line after the JSON snippet so the usage text renders correctly.

Copilot uses AI. Check for mistakes.
@Akash-nath29 Akash-nath29 merged commit eac657d into dev Jan 30, 2026
36 checks passed
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.

3 participants