feat: implement Formulation REST API (v1), MCP Server, and full-fidelity database backup/restore system
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user