feat: add extensible recipe registry and CLI command#125
Conversation
feat: add Coderrr Insights dashboard for productivity tracking and Skills for better workflow
feat: add extensible recipe registry and CLI command
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🚀 Thanks for opening a Pull Request! A maintainer will review this soon. Meanwhile: Your contribution helps make this project better! |
There was a problem hiding this comment.
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
RecipeManagerto manage recipes under~/.coderrr/recipes, including creating a defaultpingrecipe, listing recipes, and loading a single recipe. - Added
recipeUIand a newcoderrr recipeCLI command for listing and (eventually) running recipes, plus arecipeValidatorutility and a Jest test to verify the default recipe. - Added
docs/recipes.mdto 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 |
There was a problem hiding this comment.
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.
| // Add a default "Hello World" recipe | |
| // Add a default "ping" health check recipe |
| 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')); |
There was a problem hiding this comment.
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.
| 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}`); | |
| } |
| getRecipe(name) { | ||
| const filePath = path.join(RECIPES_DIR, `${name}.json`); | ||
| if (fs.existsSync(filePath)) { | ||
| return JSON.parse(fs.readFileSync(filePath, 'utf8')); |
There was a problem hiding this comment.
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.
| 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; | |
| } |
| 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 |
There was a problem hiding this comment.
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.
| 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'); |
There was a problem hiding this comment.
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.
| ```json | ||
| { | ||
| "name": "Quick Express", | ||
| "tasks": ["Initialize npm", "Install express", "Create app.js"] | ||
| } No newline at end of file |
There was a problem hiding this comment.
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.
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:
RecipeManagerclass insrc/recipeManager.jsfor managing recipes, including directory setup, listing, and retrieval of recipes. It also creates a default "ping" recipe if none exist.src/utils/recipeValidator.jsto ensure custom recipes have the required structure (nameand a non-emptytasksarray).User Interface and Documentation:
src/recipeUI.jsto display available recipes in a user-friendly, colorized format.docs/recipes.mdexplaining how to use and create recipes in Coderrr.Testing:
test/recipes.test.jsto verify that the default "ping" recipe is present and correctly structured.