#!/usr/bin/env node import fs from "node:fs"; import path from "node:path"; import process from "node:process"; import YAML from "yaml"; const root = path.resolve(import.meta.dirname, ".."); const recipesDir = path.join(root, "culinary", "recipes"); const autofix = process.argv.includes("--fix") || process.argv.includes("--autofix"); const gerundMap = { "mixing": "Mix", "combining": "Combine", "adding": "Add", "whisking": "Whisk", "stirring": "Stir", "baking": "Bake", "cooking": "Cook", "heating": "Heat", "pouring": "Pour", "placing": "Place", "cutting": "Cut", "dicing": "Dice", "chopping": "Chop", "kneading": "Knead", "rolling": "Roll", "folding": "Fold", "preheating": "Preheat", "seasoning": "Season", "simmering": "Simmer", "boiling": "Boil", "cooling": "Cool", "refrigerating": "Refrigerate", "freezing": "Freeze", "storing": "Store", "serving": "Serve", "garnishing": "Garnish", }; function standardizeInstruction(text) { let clean = text.trim(); if (!clean) return clean; // Check if it's an inline note: (Note: ...) or (...) if (clean.startsWith("(") && clean.endsWith(")")) { return clean; } // Check if it's a section heading: ends with colon if (clean.endsWith(":")) { // Capitalize first character clean = clean.charAt(0).toUpperCase() + clean.slice(1); return clean; } // Check for common gerund starts const words = clean.split(/\s+/); const firstWordLower = words[0].toLowerCase(); if (gerundMap[firstWordLower]) { words[0] = gerundMap[firstWordLower]; clean = words.join(" "); } // Capitalize first letter clean = clean.charAt(0).toUpperCase() + clean.slice(1); // Replace trailing comma, semicolon, or dash with period clean = clean.replace(/[,;\-\s]+$/, ""); // Ensure terminal punctuation if not ending with : or ) if (!clean.endsWith(".") && !clean.endsWith("!") && !clean.endsWith("?") && !clean.endsWith(":") && !clean.endsWith(")")) { clean += "."; } return clean; } function lintRecipeFile(filePath) { const content = fs.readFileSync(filePath, "utf8"); const data = YAML.parse(content); if (!data || !Array.isArray(data.steps)) return { warnings: [], errors: [], changed: false }; const issues = []; let changed = false; const newSteps = data.steps.map((step, index) => { const original = step.instruction ?? ""; const standardized = standardizeInstruction(original); if (original !== standardized) { issues.push({ stepOrder: step.order ?? index + 1, original, standardized, }); if (autofix) { changed = true; return { ...step, instruction: standardized }; } } return step; }); if (changed && autofix) { data.steps = newSteps; fs.writeFileSync(filePath, YAML.stringify(data, { indent: 2, lineWidth: 0 }), "utf8"); } return { issues, changed }; } function main() { const files = fs.readdirSync(recipesDir).filter((f) => f.endsWith(".yaml")).sort(); let totalIssues = 0; let filesModified = 0; console.log(`Auditing ${files.length} recipe instruction files against Microsoft procedural guidelines...`); for (const file of files) { const filePath = path.join(recipesDir, file); const { issues, changed } = lintRecipeFile(filePath); if (issues.length > 0) { totalIssues += issues.length; if (changed) filesModified++; console.log(`\nšŸ“„ ${file} (${issues.length} issue${issues.length > 1 ? "s" : ""}):`); for (const issue of issues) { console.log(` Step ${issue.stepOrder}:`); console.log(` - Current: "${issue.original}"`); console.log(` + Standard: "${issue.standardized}"`); } } } console.log("\n--------------------------------------------------"); if (autofix) { console.log(`āœ… Standardized ${totalIssues} instructions across ${filesModified} recipe files.`); } else { console.log(`Found ${totalIssues} non-standard instructions.`); if (totalIssues > 0) { console.log("Run with --fix to apply automated standardization."); } } } main();