Use when generating a weekly meal plan for the Meal OS system. Triggered by "/meal-plan" or "plan meals this week" or "weekly meal plan".
Generate an optimized weekly meal plan with an aisle-grouped shopping list.
Announce at start:
Generating weekly meal plan...
Options (all optional):
• meal count: "/meal-plan 4 meals"
• skip protein: "/meal-plan skip salmon"
• force-include: "/meal-plan include lemon-garlic chicken"
• combine freely: "/meal-plan 4 meals skip chicken"
The user may provide optional constraints along with the command:
If no constraints are given, use defaults: 5 meals, no exclusions.
Read these files in parallel:
Read all data/recipes/*.md files using Glob to find them, then Read each one.
For each recipe, parse the YAML frontmatter to extract:
nameproteincanonical_ingredients (list)rating (may be blank)total_timetagsIf 0 recipes exist: Stop and tell the user: "No recipes found in data/recipes/. Run /meal-add to add recipes first."
If 1-4 recipes exist: Proceed but note: "Only N recipes in rotation — plan will include all available meals."
Read data/flyers/*.md files. Use the filename date to determine recency.
Current flyers: files with a date within the last 7 days (based on today's date vs. filename date YYYY-MM-DD).
For each current flyer, parse:
Read the most recent data/meal-plans/*.md file (by filename date — use the single most recent file regardless of age).
Extract the list of meal names from the "Selected Meals" section. These meals get a repetition penalty.
If the most recent plan is older than 7 days, still apply the penalty but add a note in the Warnings section: "Repetition penalty applied from a plan dated [date] — may be stale."
If no plan files exist at all, no repetition penalty applies.
Read data/config.yaml for:
stores list (for flyer matching and sale annotations)excluded_ingredients (filter recipes before scoring)ingredient_thresholds (for shopping list count checks)rotation_target (for rotation tightness warnings)If the file does not exist: Stop and tell the user: "data/config.yaml is missing. Run /meal-setup first to initialize the project, then re-run /meal-plan."
Read data/canonical-ingredients.yaml for:
If the file does not exist: Stop and tell the user: "data/canonical-ingredients.yaml is missing. Run /meal-setup first to initialize the project, then re-run /meal-plan."
Read data/receipts/price-history.yaml if it exists.
For each canonical ingredient, compute the average price from all ledger entries. Use these averages to estimate recipe costs when no cost data exists in the recipe's Ratings section.
If the file doesn't exist, skip — cost estimates will only use recipe Ratings data (if any).
Apply the scoring formula to each recipe. Use these explicit weights:
| Factor | Computation | Weight |
|---|---|---|
| Family rating | Use rating field value (1-5 scale). If blank/unrated, use 3.5 (3.0 base + 0.5 new-recipe bonus to encourage trying new recipes during Phase 1 discovery). | Direct value |
| On-sale bonus | +1.0 for each canonical ingredient that appears in a current flyer's "Core Ingredients On Sale" section. If the same ingredient is on sale at both stores, count the bonus once (use the cheaper price for the shopping list). | +1.0 per on-sale ingredient |
| Loss leader bonus | +2.0 for each canonical ingredient that appears in a current flyer's "Likely Loss Leaders" section. If an ingredient is both on-sale and a loss leader, use +2.0 (not both bonuses stacked). | +2.0 per loss-leader ingredient (replaces on-sale bonus for that ingredient) |
| Prep burden | If total_time > 45 min (or if total_time is blank, estimate from instructions length), flag as high-effort. | Tracked, applied later |
| Cost estimate | If the recipe has an estimated cost in Ratings > $15 for 4 servings, apply penalty. If no cost data, skip this factor. | -0.5 if expensive |
| Repetition | If this recipe appeared in last week's plan, apply flat penalty. | -2.0 |
Individual score = family_rating + (on_sale_bonus * count_of_on_sale_only_ingredients) + (loss_leader_bonus * count_of_loss_leader_ingredients) - cost_penalty - repetition_penalty
Ingredient overlap bonus: +0.5 for each canonical ingredient that appears in 2 or more of the selected meals. This rewards meal combinations that share ingredients, reducing the shopping list.
Prep burden constraint: If more than 2 of the selected meals are high-effort (>45 min), apply -1.0 penalty per additional high-effort meal beyond the 2nd.
If the recipe pool is small (fewer than [meal_count] + 2 candidates): Skip combination enumeration. Just use the top [meal_count] recipes by individual score and note in the plan that overlap scoring was not applied.
protein value before scoringShow the proposed plan before writing any files:
Proposed meal plan — Week of YYYY-MM-DD
Meals (N):
1. [Recipe Name] [score: X.X] [⏱ XX min] [⚠ non-mediterranean if flagged]
On-sale ingredients: [canonical_id — $X.XX @ Store, ...] or none
Loss leader ingredients: [canonical_id — $X.XX @ Store, ...] or none
2. [Recipe Name] ...
3. ...
Shared ingredients: [N] — [list of canonical IDs shared by 2+ meals]
On-sale matches: [N] ingredients from these meals are on sale this week
Constraints applied: [e.g. "5 meals, no exclusions" or "skipped salmon"]
Flyer used: [store name, date range] or "none (no current flyer)"
Confirm this plan? (yes / swap [#] for [recipe name] / change to [N] meals / cancel)
Swap handling: If the user says "swap 2 for Rosemary Chicken", replace that slot with the named recipe (or the next highest-scoring eligible recipe if no name is given), re-check overlap, re-show the preview.
Cancel: Stop — do not write any files.
Yes: Continue to Step 5.
Ensure directories exist: run mkdir -p data/meal-plans data/shopping-lists via Bash before writing.
Write to data/meal-plans/YYYY-MM-DD-plan.md (using today's date):
---