#pragma once // Step 478: Scaffold File Generation // Materializes an approved skeleton into concrete files and file operations. #include "ArchitectSkeletonGenerator.h" #include "FileOperations.h" #include #include #include #include #include #include struct ScaffoldFileSpec { std::string relativePath; std::string language; std::string content; }; struct ScaffoldOperation { std::string method; // fileCreate | fileWrite std::string path; // workspace-relative path std::string language; // for fileCreate std::string content; // for fileWrite }; struct ScaffoldPlan { std::string workspaceRoot; std::string projectName; std::vector directories; std::vector files; std::vector operations; std::vector notes; bool hasFile(const std::string& relPath) const { for (const auto& f : files) { if (f.relativePath == relPath) return true; } return false; } }; struct ScaffoldInput { std::string workspaceRoot; // target workspace root std::string projectName; SkeletonProjectSpec skeleton; bool includeWhetstoneState = true; }; class ArchitectScaffoldGenerator { public: static ScaffoldPlan buildPlan(const ScaffoldInput& in) { ScaffoldPlan out; out.workspaceRoot = in.workspaceRoot.empty() ? "." : in.workspaceRoot; out.projectName = sanitizeSegment(in.projectName.empty() ? "project" : in.projectName); const std::string base = out.projectName; std::vector languages; for (const auto& m : in.skeleton.modules) { languages.push_back(lower(m.language)); const std::string rel = base + "/" + modulePath(m); out.files.push_back({rel, lower(m.language), renderModuleFile(m)}); } if (containsAny(languages, {"typescript", "javascript"})) { out.files.push_back({base + "/package.json", "json", renderPackageJson(out.projectName)}); } if (containsAny(languages, {"python"})) { out.files.push_back({base + "/pyproject.toml", "toml", renderPyproject(out.projectName)}); } if (containsAny(languages, {"rust"})) { out.files.push_back({base + "/Cargo.toml", "toml", renderCargo(out.projectName)}); } if (containsAny(languages, {"cpp", "c"})) { out.files.push_back({base + "/CMakeLists.txt", "cmake", renderCmake(out.projectName)}); } out.files.push_back({base + "/.gitignore", "text", renderGitignore(languages)}); if (in.includeWhetstoneState) { out.files.push_back({base + "/.whetstone/workflow_state.json", "json", renderWorkflowState(in.skeleton)}); for (const auto& m : in.skeleton.modules) { const std::string sidecar = base + "/.whetstone/sidecars/" + sanitizeSegment(m.moduleName) + ".ast.json"; out.files.push_back({sidecar, "json", renderSidecar(m)}); } } collectDirectories(out); buildOperations(out); out.notes.push_back("Scaffold plan generated from approved skeleton"); out.notes.push_back("Operations are compatible with fileCreate/fileWrite MCP semantics"); return out; } static bool applyPlan(const ScaffoldPlan& plan, std::string* error = nullptr) { for (const auto& op : plan.operations) { auto [okPath, resolved] = fileOpsResolvePath(plan.workspaceRoot, op.path); if (!okPath) { if (error) *error = resolved; return false; } if (op.method == "fileCreate") { if (std::filesystem::exists(resolved)) continue; auto [ok, msg] = fileOpsCreate(resolved, op.language, "empty"); if (!ok) { if (error) *error = msg; return false; } continue; } if (op.method == "fileWrite") { auto [ok, msg, bytes] = fileOpsWrite(resolved, op.content); (void)bytes; if (!ok) { if (error) *error = msg; return false; } continue; } if (error) *error = "Unknown operation method: " + op.method; return false; } return true; } private: static std::string lower(const std::string& s) { std::string out = s; std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return out; } static std::string sanitizeSegment(const std::string& s) { std::string out; for (char c : s) { unsigned char uc = static_cast(c); if (std::isalnum(uc) || c == '_' || c == '-') out.push_back(c); else if (std::isspace(uc)) out.push_back('_'); } if (out.empty()) return "project"; return out; } static bool containsAny(const std::vector& values, std::initializer_list expected) { for (const auto& e : expected) { for (const auto& v : values) { if (v == e) return true; } } return false; } static std::string modulePath(const SkeletonModuleSpec& m) { const std::string mod = sanitizeSegment(m.moduleName); const std::string lang = lower(m.language); if (lang == "typescript") return "src/" + mod + "/" + mod + ".ts"; if (lang == "javascript") return "src/" + mod + "/" + mod + ".js"; if (lang == "python") return "src/" + mod + ".py"; if (lang == "rust") return "src/" + mod + ".rs"; if (lang == "cpp" || lang == "c") return "src/" + mod + ".h"; if (lang == "go") return "internal/" + mod + "/" + mod + ".go"; if (lang == "sql") return "db/" + mod + ".sql"; if (lang == "yaml" || lang == "yml") return "deploy/" + mod + ".yaml"; if (lang == "markdown" || lang == "md") return "docs/" + mod + ".md"; return "src/" + mod + ".txt"; } static std::string annotationPrefix(const std::string& lang) { if (lang == "sql") return "-- "; if (lang == "markdown" || lang == "md") return ""; return ""; } static std::string toIdentifier(const std::string& name) { std::string out; for (char c : name) { unsigned char uc = static_cast(c); if (std::isalnum(uc) || c == '_') out.push_back(c); else out.push_back('_'); } if (out.empty()) return "generated_fn"; if (std::isdigit(static_cast(out[0]))) out = "fn_" + out; return out; } static std::string renderFunction(const SkeletonFunctionSpec& fn, const std::string& lang) { std::ostringstream o; const std::string prefix = annotationPrefix(lang); const std::string suffix = annotationSuffix(lang); for (const auto& a : fn.annotations) { o << prefix << a.key << ": " << a.value << suffix << "\n"; } const std::string id = toIdentifier(fn.name); if (lang == "python") { o << "def " << id << "():\n pass\n\n"; } else if (lang == "typescript") { o << "export function " << id << "(): void {\n}\n\n"; } else if (lang == "javascript") { o << "export function " << id << "() {\n}\n\n"; } else if (lang == "rust") { o << "pub fn " << id << "() {\n}\n\n"; } else if (lang == "go") { o << "func " << id << "() {\n}\n\n"; } else if (lang == "cpp" || lang == "c") { o << "inline void " << id << "() {\n}\n\n"; } else if (lang == "sql") { o << "-- STUB: " << id << "\n\n"; } else if (lang == "yaml" || lang == "yml") { o << id << ": stub\n\n"; } else if (lang == "markdown" || lang == "md") { o << "### " << id << "\n\n"; } else { o << id << "\n\n"; } return o.str(); } static std::string renderModuleFile(const SkeletonModuleSpec& m) { std::ostringstream o; const std::string lang = lower(m.language); if (lang == "python") o << "# Auto-generated scaffold module: " << m.moduleName << "\n\n"; else if (lang == "sql") o << "-- Auto-generated scaffold module: " << m.moduleName << "\n\n"; else if (lang == "markdown" || lang == "md") o << "# Auto-generated scaffold module: " << m.moduleName << "\n\n"; else o << "// Auto-generated scaffold module: " << m.moduleName << "\n\n"; if (!m.dependencies.empty()) { if (lang == "python") o << "# dependencies: "; else if (lang == "sql") o << "-- dependencies: "; else if (lang == "markdown" || lang == "md") o << "Dependencies: "; else o << "// dependencies: "; for (size_t i = 0; i < m.dependencies.size(); ++i) { if (i) o << ", "; o << m.dependencies[i]; } o << "\n\n"; } for (const auto& fn : m.functions) o << renderFunction(fn, lang); if (m.functions.empty()) o << renderFunction({"execute", "void execute()", {}}, lang); return o.str(); } static std::string renderPackageJson(const std::string& projectName) { std::ostringstream o; o << "{\n" << " \"name\": \"" << projectName << "\",\n" << " \"version\": \"0.1.0\",\n" << " \"private\": true,\n" << " \"scripts\": {\n" << " \"build\": \"echo build\",\n" << " \"test\": \"echo test\"\n" << " }\n" << "}\n"; return o.str(); } static std::string renderPyproject(const std::string& projectName) { std::ostringstream o; o << "[project]\n" << "name = \"" << projectName << "\"\n" << "version = \"0.1.0\"\n" << "description = \"Generated by ArchitectScaffoldGenerator\"\n"; return o.str(); } static std::string renderCargo(const std::string& projectName) { std::ostringstream o; o << "[package]\n" << "name = \"" << projectName << "\"\n" << "version = \"0.1.0\"\n" << "edition = \"2021\"\n"; return o.str(); } static std::string renderCmake(const std::string& projectName) { std::ostringstream o; o << "cmake_minimum_required(VERSION 3.20)\n" << "project(" << projectName << " LANGUAGES CXX)\n\n" << "add_library(" << projectName << "_scaffold INTERFACE)\n"; return o.str(); } static std::string renderGitignore(const std::vector& languages) { std::ostringstream o; o << ".DS_Store\n.idea/\n.vscode/\n"; if (containsAny(languages, {"typescript", "javascript"})) o << "node_modules/\ndist/\n"; if (containsAny(languages, {"python"})) o << "__pycache__/\n*.pyc\n.venv/\n"; if (containsAny(languages, {"rust"})) o << "target/\n"; if (containsAny(languages, {"cpp", "c"})) o << "build/\n*.o\n"; o << ".whetstone/\n"; return o.str(); } static std::string renderWorkflowState(const SkeletonProjectSpec& sk) { std::ostringstream o; o << "{\n" << " \"status\": \"planned\",\n" << " \"moduleCount\": " << sk.modules.size() << ",\n" << " \"modules\": ["; for (size_t i = 0; i < sk.modules.size(); ++i) { if (i) o << ", "; o << "\"" << sanitizeSegment(sk.modules[i].moduleName) << "\""; } o << "]\n}\n"; return o.str(); } static std::string renderSidecar(const SkeletonModuleSpec& mod) { std::ostringstream o; o << "{\n" << " \"module\": \"" << sanitizeSegment(mod.moduleName) << "\",\n" << " \"language\": \"" << lower(mod.language) << "\",\n" << " \"functions\": ["; for (size_t i = 0; i < mod.functions.size(); ++i) { if (i) o << ", "; o << "{ \"name\": \"" << toIdentifier(mod.functions[i].name) << "\" }"; } o << "]\n}\n"; return o.str(); } static void collectDirectories(ScaffoldPlan& plan) { std::vector uniqueDirs; for (const auto& f : plan.files) { std::filesystem::path p(f.relativePath); auto dir = p.parent_path().string(); if (dir.empty()) continue; if (std::find(uniqueDirs.begin(), uniqueDirs.end(), dir) == uniqueDirs.end()) { uniqueDirs.push_back(dir); } } plan.directories = std::move(uniqueDirs); } static void buildOperations(ScaffoldPlan& plan) { plan.operations.clear(); for (const auto& f : plan.files) { plan.operations.push_back({"fileCreate", f.relativePath, f.language, ""}); plan.operations.push_back({"fileWrite", f.relativePath, "", f.content}); } } };