from __future__ import annotations import json import re from pathlib import Path, PurePosixPath from typing import Any, Mapping, Sequence MAX_GENERATED_FILES = 60 MAX_FILE_SIZE_BYTES = 250 * 1024 SYSTEM_FILENAMES = { "README.md", "PROJECT_EXPLANATION.md", "SETUP_INSTRUCTIONS.md", "FILE_STRUCTURE.md", "PACKAGE_REQUIREMENTS.md", "REQUIRED_INPUTS.md", ".env.example", } def slugify(value: str, fallback: str = "project-agent-output") -> str: slug = re.sub(r"[^a-zA-Z0-9]+", "-", value.lower()).strip("-") return slug or fallback def sanitize_relative_path(raw_path: str) -> Path: if not isinstance(raw_path, str): raise ValueError("Generated file paths must be strings.") normalized = raw_path.replace("\\", "/").strip() if not normalized: raise ValueError("Generated file paths cannot be empty.") if normalized.startswith(("/", "~")): raise ValueError(f"Absolute paths are not allowed: {raw_path}") if re.match(r"^[a-zA-Z]:", normalized): raise ValueError(f"Drive-qualified paths are not allowed: {raw_path}") posix_path = PurePosixPath(normalized) safe_parts: list[str] = [] for part in posix_path.parts: if part in {"", "."}: continue if part == "..": raise ValueError(f"Path traversal is not allowed: {raw_path}") safe_parts.append(part) if not safe_parts: raise ValueError(f"Generated file path is invalid: {raw_path}") return Path(*safe_parts) def ensure_within_directory(base_dir: Path, candidate: Path) -> bool: resolved_base = base_dir.resolve() resolved_candidate = candidate.resolve() return resolved_candidate.is_relative_to(resolved_base) def validate_generated_files(files: list[dict[str, str]]) -> list[dict[str, str]]: if len(files) > MAX_GENERATED_FILES: raise ValueError( f"Generated output exceeds the limit of {MAX_GENERATED_FILES} files." ) validated: list[dict[str, str]] = [] seen_paths: set[str] = set() for file_entry in files: raw_path = str(file_entry.get("path", "")) relative_path = sanitize_relative_path(raw_path) normalized_path = relative_path.as_posix() if normalized_path in seen_paths: continue content = str(file_entry.get("content", "")) content_size = len(content.encode("utf-8")) if content_size > MAX_FILE_SIZE_BYTES: raise ValueError( f"Generated file '{normalized_path}' exceeds the size limit of 250 KB." ) seen_paths.add(normalized_path) validated.append({"path": normalized_path, "content": content}) return validated def build_required_docs( preview: dict[str, Any], bundle_info: dict[str, Any] | None = None ) -> dict[str, str]: bundle_info = bundle_info or {} project_name = str(preview.get("projectName") or "Generated Project").strip() detected_choices = _listify(preview.get("detectedUserChoices")) chosen_stack = _listify(preview.get("chosenStack")) assumptions = _listify(preview.get("assumptions")) architecture = _listify(preview.get("architecture")) package_requirements = _listify(preview.get("packageRequirements")) install_commands = _listify(preview.get("installCommands")) run_commands = _listify(preview.get("runCommands")) modules = preview.get("modules") or [] required_inputs = _required_input_list(preview.get("requiredInputs")) env_variables = _env_list(preview.get("envVariables")) or _required_inputs_to_env_list( required_inputs ) file_tree = str( bundle_info.get("actualFileTree") or preview.get("fileTree") or "" ).strip() summary = str(preview.get("summary") or "No summary was provided by the model.").strip() problem_statement = str( preview.get("problemStatement") or "No problem statement was provided by the model." ).strip() selected_stack = preview.get("selectedStack") or {} readme = "\n".join( [ f"# {project_name}", "", summary, "", "## What Was Generated", "This ZIP contains a 100% runnable starter project from the latest preview, including dependency files, setup scripts, run scripts, starter source code, and required input guidance.", "", "## Problem Statement", problem_statement, "", "## Selected Stack", _selected_stack_text(selected_stack), "", "## Chosen Stack", _bullet_text(chosen_stack, "The generated plan did not include stack details."), "", "## Detected User Choices", _bullet_text( detected_choices, "The user did not explicitly specify language, tooling, or framework choices.", ), "", "## Architecture Highlights", _bullet_text(architecture, "Architecture details were not provided."), "", "## Core Modules", _modules_text(modules), "", "## Setup", "Fill `.env` from `.env.example`, then run the setup script before starting the project.", "- Windows: `setup.bat`", "- Mac/Linux: `setup.sh`", "", "## How To Run", _bullet_text(run_commands, "No run commands were provided."), "", "## Required Inputs", "Fill these values in `.env` before running the project.", "", _required_inputs_summary(required_inputs), "", "## Notes", _bullet_text(assumptions, "No assumptions were recorded."), ] ).strip() + "\n" explanation = "\n".join( [ f"# {project_name} Explanation", "", "## Summary", summary, "", "## Problem Statement", problem_statement, "", "## Selected Stack", _selected_stack_text(selected_stack), "", "## Architecture", _bullet_text(architecture, "Architecture details were not provided."), "", "## Modules", _modules_text(modules), "", "## Assumptions", _bullet_text(assumptions, "No assumptions were recorded."), ] ).strip() + "\n" setup_instructions = "\n".join( [ "# Setup Instructions", "", "## Normal Setup", "1. Review `README.md`, `PROJECT_EXPLANATION.md`, and `FILE_STRUCTURE.md` first.", "2. Review `PACKAGE_REQUIREMENTS.md` and `REQUIRED_INPUTS.md` before installing dependencies.", "3. Copy `.env.example` to `.env` and fill the required values.", "", "## Windows", "1. Fill `.env` from `.env.example`.", "2. Run `setup.bat`.", "3. Run `run.bat`.", "", "## Mac/Linux", "1. Fill `.env` from `.env.example`.", "2. Run `chmod +x setup.sh run.sh`.", "3. Run `./setup.sh`.", "4. Run `./run.sh`.", "", "## Setup Scripts", "- Windows: `setup.bat`", "- Mac/Linux: `setup.sh`", "", "## Selected Stack", _selected_stack_text(selected_stack), "", "## Install Commands", _bullet_text(install_commands, "No install commands were provided."), "", "## Run Commands", _bullet_text(run_commands, "No run commands were provided."), "", "## Environment Variables", _env_variables_text(env_variables), "", "## Troubleshooting", "- If dependencies fail to install, confirm Python, Node.js, or Maven is installed for the selected stack.", "- If the app cannot connect to a service, double-check the values in `.env` against `REQUIRED_INPUTS.md`.", "- If frontend and backend both start locally, verify `VITE_API_BASE_URL` or related API host settings match the backend URL.", "", "## Notes", _bullet_text(assumptions, "No additional assumptions were provided."), ] ).strip() + "\n" structure = "\n".join( [ "# File Structure", "", "## Final Generated Tree", "```text", file_tree or "No file tree was available.", "```", "", "## Included Modules", _modules_text(modules), ] ).strip() + "\n" package_docs = "\n".join( [ "# Package Requirements", "", "## Libraries And Packages", _bullet_text(package_requirements, "No package requirements were provided."), "", "## Install Commands", _bullet_text(install_commands, "No install commands were provided."), "", "## Run Commands", _bullet_text(run_commands, "No run commands were provided."), ] ).strip() + "\n" required_inputs_doc = "\n".join( [ "# Required Inputs", "", "Fill these values in `.env` before running the project.", "", "| Name | Required | Example | Where To Add | Purpose |", "|---|---|---|---|---|", _required_inputs_table(required_inputs), ] ).strip() + "\n" docs = { "README.md": readme, "PROJECT_EXPLANATION.md": explanation, "SETUP_INSTRUCTIONS.md": setup_instructions, "FILE_STRUCTURE.md": structure, "PACKAGE_REQUIREMENTS.md": package_docs, "REQUIRED_INPUTS.md": required_inputs_doc, ".env.example": build_env_example(required_inputs), } return docs def build_env_example(required_inputs: list[dict[str, Any]]) -> str: if not required_inputs: return ( "# No required inputs were detected for this starter.\n" "# Copy this file to .env if you want to override defaults later.\n" ) lines = [ "# Fill these values in .env before running the project.", "# Copy this file to .env and replace the example values as needed.", "", ] for variable in required_inputs: name = str(variable.get("name") or "").strip() example = str(variable.get("example") or "").strip() if name: lines.append(f"{name}={example}") return "\n".join(lines).rstrip() + "\n" def build_file_tree_from_paths(paths: list[str]) -> str: if not paths: return "" tree: dict[str, Any] = {} for raw_path in paths: parts = [part for part in raw_path.replace("\\", "/").split("/") if part] current = tree for part in parts: current = current.setdefault(part, {}) lines: list[str] = [] def walk(node: dict[str, Any], depth: int = 0) -> None: for name, child in sorted(node.items()): lines.append(f"{' ' * depth}{name}") walk(child, depth + 1) walk(tree) return "\n".join(lines) def _listify(value: Any) -> list[str]: if value is None: return [] if isinstance(value, str): items = [line.strip(" -*\t") for line in value.splitlines()] return [item for item in items if item] if isinstance(value, dict): return [ f"{key}: {str(item).strip()}" for key, item in value.items() if str(item).strip() ] if isinstance(value, (list, tuple, set)): items = [] for item in value: text = str(item).strip() if text: items.append(text) return items text = str(value).strip() return [text] if text else [] def _env_list(value: Any) -> list[dict[str, str]]: env_vars: list[dict[str, str]] = [] if isinstance(value, dict): value = [ { "name": key, "value": str(item).strip(), "description": "", } for key, item in value.items() ] if not isinstance(value, (list, tuple)): return env_vars for item in value: if not isinstance(item, dict): continue name = str(item.get("name") or "").strip() if not name: continue env_vars.append( { "name": name, "value": str(item.get("value") or "").strip(), "description": str(item.get("description") or "").strip(), } ) return env_vars def _required_input_list(value: Any) -> list[dict[str, Any]]: if isinstance(value, Mapping): value = [value] if not isinstance(value, (list, tuple)): return [] required_inputs: list[dict[str, Any]] = [] for item in value: if not isinstance(item, Mapping): continue name = str(item.get("name") or "").strip() if not name: continue required_inputs.append( { "name": name, "required": bool(item.get("required", True)), "example": str(item.get("example") or item.get("value") or "").strip(), "whereToAdd": str(item.get("whereToAdd") or ".env").strip() or ".env", "purpose": str(item.get("purpose") or item.get("description") or "").strip(), } ) return required_inputs def _bullet_text(items: list[str], fallback: str) -> str: if not items: return f"- {fallback}" return "\n".join(f"- {item}" for item in items) def _selected_stack_text(selected_stack: dict[str, Any]) -> str: if not isinstance(selected_stack, dict) or not selected_stack: return "- No explicit stack selection was recorded." ordered_labels = [ ("language", "Language"), ("frontend", "Frontend"), ("backend", "Backend"), ("database", "Database"), ("aiTools", "AI / Tools"), ("deployment", "Deployment"), ] lines = [] for key, label in ordered_labels: value = str(selected_stack.get(key) or "Auto").strip() lines.append(f"- {label}: {value}") return "\n".join(lines) def _env_variables_text(env_variables: list[dict[str, str]]) -> str: if not env_variables: return "- No environment variables are required." lines = [] for variable in env_variables: description = variable.get("description", "").strip() value = variable.get("value", "").strip() suffix = f" ({description})" if description else "" default_text = f" default `{value}`" if value else "" lines.append(f"- `{variable['name']}`{suffix}{default_text}") return "\n".join(lines) def _required_inputs_to_env_list(required_inputs: list[dict[str, Any]]) -> list[dict[str, str]]: env_vars: list[dict[str, str]] = [] for item in required_inputs: env_vars.append( { "name": str(item.get("name") or "").strip(), "value": str(item.get("example") or "").strip(), "description": str(item.get("purpose") or "").strip(), } ) return [item for item in env_vars if item["name"]] def _required_inputs_summary(required_inputs: list[dict[str, Any]]) -> str: if not required_inputs: return "- No required inputs were detected. `.env.example` is still included for future overrides." return "\n".join( f"- `{item['name']}` ({'required' if item.get('required', True) else 'optional'}): {item.get('purpose') or 'No description provided.'}" for item in required_inputs ) def _required_inputs_table(required_inputs: list[dict[str, Any]]) -> str: if not required_inputs: return "| None | No | n/a | `.env` | No required external values were detected for this starter. |" rows = [] for item in required_inputs: rows.append( "| {name} | {required} | {example} | {where_to_add} | {purpose} |".format( name=item.get("name") or "", required="Yes" if item.get("required", True) else "No", example=(item.get("example") or "").replace("|", "\\|"), where_to_add=(item.get("whereToAdd") or ".env").replace("|", "\\|"), purpose=(item.get("purpose") or "").replace("|", "\\|"), ) ) return "\n".join(rows) def _modules_text(modules: list[dict[str, Any]]) -> str: if not modules: return "- No modules were provided." lines: list[str] = [] for module in modules: name = str(module.get("name") or "Unnamed module").strip() purpose = str(module.get("purpose") or "No purpose provided.").strip() key_files = _listify(module.get("keyFiles")) lines.append(f"- {name}: {purpose}") if key_files: lines.append(f" Key files: {', '.join(key_files)}") return "\n".join(lines) MAX_CUSTOM_TEMPLATE_FILES = 8 MAX_CUSTOM_FILE_LINES = 300 def finalize_preview_files( *, project_name: str, selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], custom_manifest: Sequence[Mapping[str, Any]] | None = None, raw_files: Any = None, ) -> list[dict[str, str]]: standard_files = _build_standard_files(project_name, selected_stack, project_kind) custom_template_files = _build_custom_template_files( custom_manifest or [], project_name, selected_stack, project_kind, ) existing_files = _normalize_preview_files(raw_files) merged_files = _merge_file_entries(standard_files, custom_template_files) merged_files = _merge_file_entries(merged_files, existing_files) completed_files = _ensure_minimum_project_files( merged_files, project_name, selected_stack, project_kind, ) repaired_files = _repair_runtime_contract( completed_files, project_name, selected_stack, project_kind, ) return validate_generated_files(repaired_files) def build_preview_file_tree( files: Sequence[Mapping[str, Any]], *, include_env_example: bool, ) -> str: doc_paths = [ "README.md", "PROJECT_EXPLANATION.md", "SETUP_INSTRUCTIONS.md", "FILE_STRUCTURE.md", "PACKAGE_REQUIREMENTS.md", "REQUIRED_INPUTS.md", ".env.example", ] file_paths = [str(file_entry.get("path") or "").strip() for file_entry in files] return build_file_tree_from_paths([path for path in file_paths if path] + doc_paths) def _build_standard_files( project_name: str, selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[dict[str, str]]: files: dict[str, str] = {} if project_kind["isFullStack"]: if project_kind["hasFrontend"]: files.update( _build_frontend_files( str(selected_stack.get("frontend") or "React"), project_name, "frontend", ) ) if project_kind["hasBackend"]: files.update(_build_backend_files(selected_stack, project_name, "backend")) files.update(_build_root_scripts(selected_stack, project_kind)) elif project_kind["hasBackend"]: files.update(_build_backend_files(selected_stack, project_name, "")) files.update(_build_root_scripts(selected_stack, project_kind)) else: files.update( _build_frontend_files( str(selected_stack.get("frontend") or "React"), project_name, "", ) ) files.update(_build_root_scripts(selected_stack, project_kind)) return [{"path": path, "content": content} for path, content in files.items()] def _build_custom_template_files( manifest: Sequence[Mapping[str, Any]], project_name: str, selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[dict[str, str]]: files: list[dict[str, str]] = [] for item in list(manifest)[:MAX_CUSTOM_TEMPLATE_FILES]: path = _clean_relative_path(item.get("path")) purpose = str(item.get("purpose") or "").strip() if not path or not purpose: continue content = _build_custom_template_content( path, purpose, project_name, selected_stack, project_kind, ) files.append({"path": path, "content": _trim_content_lines(content)}) return files def _build_custom_template_content( path: str, purpose: str, project_name: str, selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> str: del selected_stack, project_kind stem = Path(path).stem pretty_name = stem.replace("_", " ").replace("-", " ").title() extension = Path(path).suffix.lower() if extension in {".jsx", ".tsx"}: if "page" in stem.lower() or "/pages/" in path: return f"""const cards = [ {{ title: "{pretty_name} Overview", detail: "{purpose}" }}, {{ title: "Starter Workflow", detail: "Use this page to connect forms, API requests, and user-facing business actions." }} ]; export default function {_safe_component_name(stem)}() {{ return (

{pretty_name}

{purpose}

); }} """ return f"""export default function {_safe_component_name(stem)}() {{ return (

{pretty_name}

{purpose}

); }} """ if extension == ".py": if "router" in path or "/routers/" in path: return f"""from fastapi import APIRouter router = APIRouter() @router.get("/") def read_{_safe_python_name(stem)}() -> dict[str, str]: return {{ "message": "{purpose}", "project": "{project_name}", }} """ if "schema" in path or "/schemas/" in path: return f"""from pydantic import BaseModel class {_safe_component_name(stem)}(BaseModel): name: str description: str = "{purpose}" """ if "model" in path or "/models/" in path: return f"""from dataclasses import dataclass @dataclass class {_safe_component_name(stem)}: name: str status: str = "ready" """ return f"""def {_safe_python_name(stem)}_summary() -> dict[str, str]: return {{ "name": "{pretty_name}", "purpose": "{purpose}", "project": "{project_name}", }} """ if extension in {".js", ".mjs"}: if "service" in path.lower(): return f"""export function get{_safe_component_name(stem)}Summary() {{ return {{ project: "{project_name}", purpose: "{purpose}" }}; }} """ if "controller" in path.lower(): return f"""export function {_safe_js_name(stem)}(_req, res) {{ res.json({{ project: "{project_name}", purpose: "{purpose}" }}); }} """ return f"""export const {_safe_js_name(stem)} = {{ project: "{project_name}", purpose: "{purpose}" }}; """ if extension == ".java": class_name = _safe_component_name(stem) return f"""package com.example.demo.service; import org.springframework.stereotype.Service; @Service public class {class_name} {{ public String summary() {{ return "{purpose}"; }} }} """ return f"# {pretty_name}\n\n{purpose}\n" def _ensure_minimum_project_files( files: Sequence[Mapping[str, str]], project_name: str, selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[dict[str, str]]: merged = {str(entry["path"]): str(entry["content"]) for entry in files} for path, content in _build_root_scripts(selected_stack, project_kind).items(): merged.setdefault(path, content) if project_kind["isFullStack"]: for path, content in _build_frontend_files( str(selected_stack.get("frontend") or "React"), project_name, "frontend", ).items(): merged.setdefault(path, content) for path, content in _build_backend_files(selected_stack, project_name, "backend").items(): merged.setdefault(path, content) elif project_kind["hasBackend"]: for path, content in _build_backend_files(selected_stack, project_name, "").items(): merged.setdefault(path, content) else: for path, content in _build_frontend_files( str(selected_stack.get("frontend") or "React"), project_name, "", ).items(): merged.setdefault(path, content) filler_index = 1 minimum_files = int(project_kind.get("minimumFiles") or 0) while len(merged) < minimum_files: filler_path = f"notes/starter-note-{filler_index}.md" merged.setdefault( filler_path, f"# Starter Note {filler_index}\n\nThis file preserves the complete minimum project structure while you continue iterating.\n", ) filler_index += 1 return [{"path": path, "content": content} for path, content in merged.items()] def _repair_runtime_contract( files: Sequence[Mapping[str, str]], project_name: str, selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[dict[str, str]]: standard_map = { item["path"]: item["content"] for item in _build_standard_files(project_name, selected_stack, project_kind) } merged = {str(entry["path"]): str(entry["content"]) for entry in files} for protected_path in _protected_runtime_paths(selected_stack, project_kind): template_content = standard_map.get(protected_path) if template_content is not None: merged[protected_path] = template_content for required_path in _required_runtime_paths(selected_stack, project_kind): template_content = standard_map.get(required_path) if template_content is None: continue if not str(merged.get(required_path, "")).strip(): merged[required_path] = template_content for path, content in list(merged.items()): if not str(content).strip(): replacement = standard_map.get(path) or _build_safe_fallback_content( path, project_name, ) merged[path] = replacement for path, content in list(merged.items()): if not path.endswith(".py"): continue if _python_compiles(content): continue merged[path] = standard_map.get(path) or _build_safe_fallback_content(path, project_name) for package_json_path in _package_json_paths(selected_stack, project_kind): if package_json_path not in merged or not _valid_package_json( merged[package_json_path], _expected_package_scripts(package_json_path, selected_stack, project_kind), ): template = standard_map.get(package_json_path) if template is not None: merged[package_json_path] = template for entry_path in _entry_validation_paths(selected_stack, project_kind): template = standard_map.get(entry_path) content = str(merged.get(entry_path, "")) if template is None: continue if not content.strip() or not _valid_entry_file(entry_path, content): merged[entry_path] = template if project_kind["hasBackend"]: for endpoint_path in _backend_endpoint_paths(selected_stack, project_kind): template = standard_map.get(endpoint_path) if template is None: continue content = str(merged.get(endpoint_path, "")) if not content.strip() or not _valid_backend_endpoint_file(endpoint_path, content): merged[endpoint_path] = template if project_kind["hasFrontend"]: for page_path in _frontend_page_paths(selected_stack, project_kind): template = standard_map.get(page_path) if template is None: continue if not str(merged.get(page_path, "")).strip(): merged[page_path] = template return [{"path": path, "content": content} for path, content in merged.items()] def _build_backend_files( selected_stack: Mapping[str, Any], project_name: str, prefix: str, ) -> dict[str, str]: backend = str(selected_stack.get("backend") or "FastAPI") if backend in {"FastAPI", "Flask"}: return _build_fastapi_backend_files(project_name, prefix) if backend in {"Express", "NestJS"}: return _build_express_backend_files(project_name, prefix) if backend == "Spring Boot": return _build_spring_backend_files(project_name, prefix) return _build_fastapi_backend_files(project_name, prefix) def _build_frontend_files(frontend: str, project_name: str, prefix: str) -> dict[str, str]: if frontend in {"React", "Next.js", "Vue"}: return _build_react_frontend_files(project_name, prefix) return _build_vanilla_frontend_files(project_name, prefix) def _build_fastapi_backend_files(project_name: str, prefix: str) -> dict[str, str]: app_prefix = _prefixed(prefix, "app") return { _prefixed(prefix, "requirements.txt"): "\n".join( [ "fastapi", "uvicorn[standard]", "pydantic", "pydantic-settings", "sqlalchemy", "python-dotenv", "aiosqlite", "", ] ), _prefixed(app_prefix, "__init__.py"): "", _prefixed(app_prefix, "main.py"): f"""from fastapi import FastAPI from app.routers import health, items app = FastAPI(title="{project_name} API") app.include_router(health.router) app.include_router(items.router, prefix="/api/items", tags=["items"]) @app.get("/") def read_root() -> dict[str, str]: return {{"status": "ok", "message": "Project is running"}} """, _prefixed(app_prefix, "routers/__init__.py"): "", _prefixed(app_prefix, "routers/health.py"): """from fastapi import APIRouter from app.schemas.health import HealthResponse router = APIRouter(tags=["health"]) @router.get("/health", response_model=HealthResponse) def healthcheck() -> HealthResponse: return HealthResponse(status="ok", message="Project is running") """, _prefixed(app_prefix, "routers/items.py"): """from fastapi import APIRouter from app.schemas.item import Item from app.services.item_service import list_items router = APIRouter() @router.get("/", response_model=list[Item]) def get_items() -> list[Item]: return list_items() """, _prefixed(app_prefix, "services/__init__.py"): "", _prefixed(app_prefix, "services/app_service.py"): f"""def get_app_summary() -> str: return "{project_name} includes routes, services, schemas, and configuration for quick iteration." """, _prefixed(app_prefix, "services/item_service.py"): """from app.schemas.item import Item def list_items() -> list[Item]: return [ Item(id=1, name="Starter task", status="ready"), Item(id=2, name="Next iteration", status="planned"), ] """, _prefixed(app_prefix, "models/__init__.py"): "", _prefixed(app_prefix, "models/base.py"): """from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): pass """, _prefixed(app_prefix, "models/item.py"): """from sqlalchemy import String from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base class ItemModel(Base): __tablename__ = "items" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(120)) status: Mapped[str] = mapped_column(String(40), default="ready") """, _prefixed(app_prefix, "schemas/__init__.py"): "", _prefixed(app_prefix, "schemas/health.py"): """from pydantic import BaseModel class HealthResponse(BaseModel): status: str message: str """, _prefixed(app_prefix, "schemas/item.py"): """from pydantic import BaseModel class Item(BaseModel): id: int name: str status: str """, _prefixed(app_prefix, "database.py"): """from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from app.config import settings engine = create_engine(settings.database_url, future=True) SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False) """, _prefixed(app_prefix, "config.py"): """from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env") app_env: str = "development" database_url: str = "sqlite:///./app.db" settings = Settings() """, } def _build_express_backend_files(project_name: str, prefix: str) -> dict[str, str]: return { _prefixed(prefix, "package.json"): json.dumps( { "name": project_name.lower().replace(" ", "-"), "version": "0.1.0", "private": True, "type": "module", "scripts": {"dev": "node --watch server.js", "start": "node server.js"}, "dependencies": {"cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2"}, }, indent=2, ) + "\n", _prefixed(prefix, "server.js"): f"""import cors from "cors"; import dotenv from "dotenv"; import express from "express"; import indexRouter from "./src/routes/index.js"; import itemsRouter from "./src/routes/items.js"; dotenv.config(); const app = express(); app.use(cors()); app.use(express.json()); app.use("/", indexRouter); app.use("/api/items", itemsRouter); const port = process.env.PORT || 8000; app.listen(port, () => {{ console.log("{project_name} API listening on port", port); }}); """, _prefixed(prefix, "src/routes/index.js"): """import { Router } from "express"; import { getStatus } from "../controllers/appController.js"; const router = Router(); router.get("/", getStatus); export default router; """, _prefixed(prefix, "src/routes/items.js"): """import { Router } from "express"; import { listItems } from "../controllers/itemController.js"; const router = Router(); router.get("/", listItems); export default router; """, _prefixed(prefix, "src/controllers/appController.js"): """export function getStatus(_req, res) { res.json({ status: "ok", message: "Project is running" }); } """, _prefixed(prefix, "src/controllers/itemController.js"): """import { getItems } from "../services/itemService.js"; export function listItems(_req, res) { res.json(getItems()); } """, _prefixed(prefix, "src/services/appService.js"): f"""export function getAppSummary() {{ return "{project_name} includes routes, controllers, services, and starter configuration."; }} """, _prefixed(prefix, "src/services/itemService.js"): """export function getItems() { return [ { id: 1, name: "Starter task", status: "ready" }, { id: 2, name: "Next iteration", status: "planned" } ]; } """, _prefixed(prefix, "src/models/itemModel.js"): """export const itemShape = { id: "number", name: "string", status: "string" }; """, } def _build_react_frontend_files(project_name: str, prefix: str) -> dict[str, str]: return { _prefixed(prefix, "package.json"): json.dumps( { "name": project_name.lower().replace(" ", "-") + "-frontend", "private": True, "version": "0.1.0", "type": "module", "scripts": {"dev": "vite", "build": "vite build", "preview": "vite preview"}, "dependencies": {"react": "^18.3.1", "react-dom": "^18.3.1"}, "devDependencies": {"vite": "^5.4.0", "@vitejs/plugin-react": "^4.3.1"}, }, indent=2, ) + "\n", _prefixed(prefix, "index.html"): """ Project Starter
""", _prefixed(prefix, "vite.config.js"): """import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], server: { port: 5173 } }); """, _prefixed(prefix, "src/main.jsx"): """import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./styles.css"; ReactDOM.createRoot(document.getElementById("root")).render( ); """, _prefixed(prefix, "src/App.jsx"): f"""import AppShell from "./components/AppShell"; import HomePage from "./pages/HomePage"; export default function App() {{ return ( ); }} """, _prefixed(prefix, "src/components/AppShell.jsx"): """export default function AppShell({ title, children }) { return (

Generated by Project Agent

{title}

{children}
); } """, _prefixed(prefix, "src/pages/HomePage.jsx"): """import { getProjectHealth } from "../services/api"; export default function HomePage() { const projectHealth = getProjectHealth(); return (

Starter Overview

This 100% runnable starter project is ready for your first feature slice.

API health source: {projectHealth}

); } """, _prefixed(prefix, "src/services/api.js"): """export function getProjectHealth() { return import.meta.env.VITE_API_BASE_URL || "http://localhost:8000"; } """, _prefixed(prefix, "src/styles.css"): """:root { color-scheme: light; font-family: "Segoe UI", Arial, sans-serif; background: #f5f7fb; color: #132238; } body { margin: 0; } .app-shell { max-width: 960px; margin: 0 auto; padding: 32px 20px 56px; } .hero { margin-bottom: 24px; } .eyebrow { font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase; color: #57657d; } .card { background: white; border-radius: 16px; padding: 24px; box-shadow: 0 18px 40px rgba(19, 34, 56, 0.08); } """, } def _build_vanilla_frontend_files(project_name: str, prefix: str) -> dict[str, str]: return { _prefixed(prefix, "package.json"): json.dumps( { "name": project_name.lower().replace(" ", "-") + "-frontend", "private": True, "version": "0.1.0", "type": "module", "scripts": {"dev": "vite", "build": "vite build", "preview": "vite preview"}, "devDependencies": {"vite": "^5.4.0"}, }, indent=2, ) + "\n", _prefixed(prefix, "index.html"): """ Project Starter
""", _prefixed(prefix, "vite.config.js"): """import { defineConfig } from "vite"; export default defineConfig({ server: { port: 5173 } }); """, _prefixed(prefix, "src/main.js"): f"""import {{ renderHomePage }} from "./views/home.js"; import "./styles.css"; document.querySelector("#app").innerHTML = renderHomePage("{project_name}"); """, _prefixed(prefix, "src/views/home.js"): """export function renderHomePage(title) { return `

Generated by Project Agent

${title}

This starter is ready for your first feature slice.

`; } """, _prefixed(prefix, "src/services/api.js"): """export function getApiBaseUrl() { return import.meta.env.VITE_API_BASE_URL || "http://localhost:8000"; } """, _prefixed(prefix, "src/styles.css"): """body { margin: 0; font-family: "Segoe UI", Arial, sans-serif; background: #f5f7fb; color: #132238; } .app-shell { max-width: 960px; margin: 0 auto; padding: 32px 20px 56px; } .card { background: white; border-radius: 16px; padding: 24px; box-shadow: 0 18px 40px rgba(19, 34, 56, 0.08); } .eyebrow { font-size: 0.75rem; text-transform: uppercase; color: #57657d; } """, } def _build_spring_backend_files(project_name: str, prefix: str) -> dict[str, str]: java_base = _prefixed(prefix, "src/main/java/com/example/demo") resources_base = _prefixed(prefix, "src/main/resources") return { _prefixed(prefix, "pom.xml"): """ 4.0.0 com.example demo 0.0.1-SNAPSHOT org.springframework.boot spring-boot-starter-parent 3.3.2 17 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-maven-plugin """, _prefixed(java_base, "Application.java"): """package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } """, _prefixed(java_base, "controller/AppController.java"): """package com.example.demo.controller; import com.example.demo.service.AppService; import java.util.Map; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class AppController { private final AppService appService; public AppController(AppService appService) { this.appService = appService; } @GetMapping("/") public Map status() { return Map.of("status", "ok", "message", "Project is running"); } } """, _prefixed(java_base, "service/AppService.java"): f"""package com.example.demo.service; import org.springframework.stereotype.Service; @Service public class AppService {{ public String status() {{ return "Project is running"; }} }} """, _prefixed(java_base, "model/AppModel.java"): """package com.example.demo.model; public class AppModel { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } """, _prefixed(java_base, "repository/AppRepository.java"): """package com.example.demo.repository; import com.example.demo.model.AppModel; import java.util.List; import org.springframework.stereotype.Repository; @Repository public class AppRepository { public List findAll() { return List.of(); } } """, _prefixed(resources_base, "application.properties"): """spring.application.name=demo server.port=8080 """, } def _build_root_scripts( selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> dict[str, str]: if project_kind["isFullStack"]: return _build_fullstack_scripts(selected_stack) if project_kind["hasBackend"]: backend = str(selected_stack.get("backend") or "FastAPI") if backend in {"FastAPI", "Flask"}: return _build_python_scripts(".") if backend in {"Express", "NestJS"}: return _build_node_scripts(".", "start") if backend == "Spring Boot": return _build_java_scripts(".") return _build_node_scripts(".", "dev") def _build_fullstack_scripts(selected_stack: Mapping[str, Any]) -> dict[str, str]: backend_setup = "" backend_run_windows = "echo No backend runtime configured.\n" backend_run_unix = 'echo "No backend runtime configured."\n' backend = str(selected_stack.get("backend") or "") if backend in {"FastAPI", "Flask"}: backend_setup = ( "if exist backend\\requirements.txt (\n" " pushd backend\n" " python -m venv .venv\n" " call .venv\\Scripts\\python -m pip install --upgrade pip\n" " call .venv\\Scripts\\pip install -r requirements.txt\n" " popd\n" ")\n" ) backend_run_windows = ( 'start "Backend" cmd /k "cd backend && .venv\\Scripts\\python -m uvicorn app.main:app --reload"\n' ) backend_run_unix = '(cd backend && . .venv/bin/activate && uvicorn app.main:app --reload) &\n' elif backend in {"Express", "NestJS"}: backend_setup = ( "if exist backend\\package.json (\n" " pushd backend\n" " call npm install\n" " popd\n" ")\n" ) backend_run_windows = 'start "Backend" cmd /k "cd backend && npm start"\n' backend_run_unix = '(cd backend && npm start) &\n' elif backend == "Spring Boot": backend_setup = ( "where mvn >nul 2>nul && (\n" " pushd backend\n" " call mvn install\n" " popd\n" ") || echo Maven not found. Skipping backend install.\n" ) backend_run_windows = 'start "Backend" cmd /k "cd backend && mvn spring-boot:run"\n' backend_run_unix = '(cd backend && mvn spring-boot:run) &\n' return { "setup.bat": f"""@echo off setlocal {backend_setup}if exist frontend\\package.json ( pushd frontend call npm install popd ) echo Setup complete. """, "setup.sh": """#!/usr/bin/env bash set -e if [ -f backend/requirements.txt ]; then ( cd backend python3 -m venv .venv . .venv/bin/activate python -m pip install --upgrade pip pip install -r requirements.txt ) fi if [ -f backend/package.json ]; then (cd backend && npm install) fi if [ -f backend/pom.xml ]; then if command -v mvn >/dev/null 2>&1; then (cd backend && mvn install) else echo "Maven not found. Skipping backend install." fi fi if [ -f frontend/package.json ]; then (cd frontend && npm install) fi echo "Setup complete." """, "run.bat": f"""@echo off setlocal {backend_run_windows}start "Frontend" cmd /k "cd frontend && npm run dev" """, "run.sh": f"""#!/usr/bin/env bash set -e {backend_run_unix}(cd frontend && npm run dev) & wait """, } def _build_python_scripts(target_dir: str) -> dict[str, str]: del target_dir return { "setup.bat": """@echo off setlocal python -m venv .venv call .venv\\Scripts\\python -m pip install --upgrade pip call .venv\\Scripts\\pip install -r requirements.txt echo Setup complete. """, "setup.sh": """#!/usr/bin/env bash set -e python3 -m venv .venv . .venv/bin/activate python -m pip install --upgrade pip pip install -r requirements.txt echo "Setup complete." """, "run.bat": """@echo off setlocal call .venv\\Scripts\\python -m uvicorn app.main:app --reload """, "run.sh": """#!/usr/bin/env bash set -e . .venv/bin/activate uvicorn app.main:app --reload """, } def _build_node_scripts(target_dir: str, script_name: str) -> dict[str, str]: directory_prefix = "" if target_dir in {"", "."} else f"{target_dir}/" windows_prefix = "" if target_dir in {"", "."} else f"{target_dir}\\" run_script = "npm start" if script_name == "start" else "npm run dev" return { "setup.bat": f"""@echo off setlocal pushd {windows_prefix or "."} call npm install popd echo Setup complete. """, "setup.sh": f"""#!/usr/bin/env bash set -e (cd {directory_prefix or "."} && npm install) echo "Setup complete." """, "run.bat": f"""@echo off setlocal pushd {windows_prefix or "."} call {run_script} popd """, "run.sh": f"""#!/usr/bin/env bash set -e (cd {directory_prefix or "."} && {run_script}) """, } def _protected_runtime_paths( selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> set[str]: paths: set[str] = {"setup.bat", "setup.sh", "run.bat", "run.sh"} backend_prefix = "backend/" if project_kind["isFullStack"] else "" frontend_prefix = "frontend/" if project_kind["isFullStack"] else "" if project_kind["hasBackend"]: backend = str(selected_stack.get("backend") or "FastAPI") if backend in {"FastAPI", "Flask"}: paths.update( { f"{backend_prefix}requirements.txt", f"{backend_prefix}app/main.py", f"{backend_prefix}app/routers/health.py", f"{backend_prefix}app/schemas/health.py", f"{backend_prefix}app/config.py", f"{backend_prefix}app/database.py", } ) elif backend in {"Express", "NestJS"}: paths.update( { f"{backend_prefix}package.json", f"{backend_prefix}server.js", f"{backend_prefix}src/routes/index.js", f"{backend_prefix}src/controllers/appController.js", } ) elif backend == "Spring Boot": paths.update( { f"{backend_prefix}pom.xml", f"{backend_prefix}src/main/java/com/example/demo/Application.java", f"{backend_prefix}src/main/java/com/example/demo/controller/AppController.java", f"{backend_prefix}src/main/resources/application.properties", } ) if project_kind["hasFrontend"]: frontend = str(selected_stack.get("frontend") or "React") if frontend in {"React", "Next.js", "Vue"}: paths.update( { f"{frontend_prefix}package.json", f"{frontend_prefix}index.html", f"{frontend_prefix}vite.config.js", f"{frontend_prefix}src/main.jsx", f"{frontend_prefix}src/App.jsx", f"{frontend_prefix}src/pages/HomePage.jsx", } ) else: paths.update( { f"{frontend_prefix}package.json", f"{frontend_prefix}index.html", f"{frontend_prefix}vite.config.js", f"{frontend_prefix}src/main.js", f"{frontend_prefix}src/views/home.js", } ) return paths def _required_runtime_paths( selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> set[str]: return set(_protected_runtime_paths(selected_stack, project_kind)) def _package_json_paths( selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[str]: paths: list[str] = [] backend_prefix = "backend/" if project_kind["isFullStack"] else "" frontend_prefix = "frontend/" if project_kind["isFullStack"] else "" if project_kind["hasBackend"] and str(selected_stack.get("backend") or "") in {"Express", "NestJS"}: paths.append(f"{backend_prefix}package.json") if project_kind["hasFrontend"]: paths.append(f"{frontend_prefix}package.json") return paths def _expected_package_scripts( package_json_path: str, selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> set[str]: if package_json_path.endswith("backend/package.json") or ( not project_kind["isFullStack"] and project_kind["hasBackend"] ): backend = str(selected_stack.get("backend") or "") if backend in {"Express", "NestJS"}: return {"start"} return {"dev"} def _entry_validation_paths( selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[str]: paths: list[str] = [] backend_prefix = "backend/" if project_kind["isFullStack"] else "" frontend_prefix = "frontend/" if project_kind["isFullStack"] else "" if project_kind["hasBackend"]: backend = str(selected_stack.get("backend") or "FastAPI") if backend in {"FastAPI", "Flask"}: paths.append(f"{backend_prefix}app/main.py") elif backend in {"Express", "NestJS"}: paths.append(f"{backend_prefix}server.js") elif backend == "Spring Boot": paths.append(f"{backend_prefix}src/main/java/com/example/demo/Application.java") if project_kind["hasFrontend"]: frontend = str(selected_stack.get("frontend") or "React") if frontend in {"React", "Next.js", "Vue"}: paths.extend([f"{frontend_prefix}src/main.jsx", f"{frontend_prefix}src/App.jsx"]) else: paths.extend([f"{frontend_prefix}src/main.js", f"{frontend_prefix}index.html"]) return paths def _backend_endpoint_paths( selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[str]: if not project_kind["hasBackend"]: return [] backend_prefix = "backend/" if project_kind["isFullStack"] else "" backend = str(selected_stack.get("backend") or "FastAPI") if backend in {"FastAPI", "Flask"}: return [f"{backend_prefix}app/main.py", f"{backend_prefix}app/routers/health.py"] if backend in {"Express", "NestJS"}: return [f"{backend_prefix}server.js", f"{backend_prefix}src/controllers/appController.js"] if backend == "Spring Boot": return [f"{backend_prefix}src/main/java/com/example/demo/controller/AppController.java"] return [] def _frontend_page_paths( selected_stack: Mapping[str, Any], project_kind: Mapping[str, Any], ) -> list[str]: if not project_kind["hasFrontend"]: return [] frontend_prefix = "frontend/" if project_kind["isFullStack"] else "" frontend = str(selected_stack.get("frontend") or "React") if frontend in {"React", "Next.js", "Vue"}: return [f"{frontend_prefix}src/App.jsx", f"{frontend_prefix}src/pages/HomePage.jsx"] return [f"{frontend_prefix}index.html", f"{frontend_prefix}src/views/home.js"] def _valid_package_json(content: str, required_scripts: set[str]) -> bool: try: payload = json.loads(content) except json.JSONDecodeError: return False if not isinstance(payload, Mapping): return False scripts = payload.get("scripts") if not isinstance(scripts, Mapping): return False return required_scripts.issubset({str(key) for key in scripts.keys()}) def _valid_entry_file(path: str, content: str) -> bool: text = content.strip() if not text: return False lower = path.lower() if lower.endswith("app/main.py"): return "FastAPI" in text and "app = FastAPI" in text and '@app.get("/")' in text if lower.endswith("server.js"): return "express" in text and 'app.use("/", indexRouter)' in text and "app.listen" in text if lower.endswith("src/main.jsx"): return "ReactDOM.createRoot" in text and 'from "./App"' in text if lower.endswith("src/app.jsx"): return "export default function App" in text if lower.endswith("src/main.js"): return "renderHomePage" in text and 'querySelector("#app")' in text if lower.endswith("index.html"): return '
' in text or '
' in text if lower.endswith("application.java"): return "@SpringBootApplication" in text and "SpringApplication.run" in text return True def _valid_backend_endpoint_file(path: str, content: str) -> bool: lower = path.lower() if lower.endswith("app/main.py"): return '"status": "ok"' in content and '"message": "Project is running"' in content if lower.endswith("routers/health.py"): return 'status="ok"' in content and 'message="Project is running"' in content if lower.endswith("server.js"): return 'app.use("/", indexRouter)' in content and "app.listen" in content if lower.endswith("appcontroller.js"): return 'status: "ok"' in content and 'message: "Project is running"' in content if lower.endswith("appcontroller.java"): return '"status", "ok"' in content and '"message", "Project is running"' in content return True def _python_compiles(content: str) -> bool: try: compile(content, "", "exec") except SyntaxError: return False return True def _build_safe_fallback_content(path: str, project_name: str) -> str: lower = path.lower() if lower.endswith(".py"): return f"""def generated_safe_summary() -> dict[str, str]: return {{"status": "ok", "message": "{project_name} safe fallback is loaded."}} """ if lower.endswith(".jsx"): return f"""export default function SafeFallback() {{ return

{project_name}

Safe fallback UI loaded.

; }} """ if lower.endswith(".js"): return f"""export const safeFallback = {{ status: "ok", message: "{project_name} safe fallback loaded" }}; """ if lower.endswith(".java"): return """package com.example.demo.service; public class SafeFallback { public String summary() { return "Safe fallback loaded"; } } """ return f"# Safe fallback\n\n{project_name} restored this file to preserve a runnable starter.\n" def _build_java_scripts(target_dir: str) -> dict[str, str]: directory_prefix = "" if target_dir in {"", "."} else f"{target_dir}/" windows_prefix = "" if target_dir in {"", "."} else f"{target_dir}\\" return { "setup.bat": f"""@echo off setlocal where mvn >nul 2>nul && ( pushd {windows_prefix or "."} call mvn install popd ) || ( echo Maven not found. Skipping install. ) """, "setup.sh": f"""#!/usr/bin/env bash set -e if command -v mvn >/dev/null 2>&1; then (cd {directory_prefix or "."} && mvn install) else echo "Maven not found. Skipping install." fi """, "run.bat": f"""@echo off setlocal pushd {windows_prefix or "."} call mvn spring-boot:run popd """, "run.sh": f"""#!/usr/bin/env bash set -e (cd {directory_prefix or "."} && mvn spring-boot:run) """, } def _normalize_preview_files(value: Any) -> list[dict[str, str]]: if not isinstance(value, Sequence) or isinstance(value, (str, bytes, bytearray)): return [] files: list[dict[str, str]] = [] for item in value: if not isinstance(item, Mapping): continue path = _clean_relative_path(item.get("path")) if not path: continue files.append( { "path": path, "content": _trim_content_lines(str(item.get("content") or ""), allow_long=True), } ) return files def _merge_file_entries( primary: Sequence[Mapping[str, str]], secondary: Sequence[Mapping[str, str]], ) -> list[dict[str, str]]: merged: dict[str, dict[str, str]] = {} for file_entry in primary: merged[str(file_entry["path"])] = { "path": str(file_entry["path"]), "content": str(file_entry["content"]), } for file_entry in secondary: merged[str(file_entry["path"])] = { "path": str(file_entry["path"]), "content": str(file_entry["content"]), } return list(merged.values()) def _clean_relative_path(value: Any) -> str: path = str(value or "").replace("\\", "/").strip().strip("/") if not path or path.startswith(".") or ".." in path.split("/"): return "" return path def _trim_content_lines(content: str, allow_long: bool = False) -> str: if allow_long: return content lines = content.splitlines() if len(lines) <= MAX_CUSTOM_FILE_LINES: return content return "\n".join(lines[:MAX_CUSTOM_FILE_LINES]).rstrip() + "\n" def _safe_component_name(value: str) -> str: parts = re.findall(r"[A-Za-z0-9]+", value) return "".join(part.capitalize() for part in parts) or "GeneratedComponent" def _safe_python_name(value: str) -> str: cleaned = re.sub(r"[^a-zA-Z0-9_]+", "_", value).strip("_").lower() return cleaned or "generated_item" def _safe_js_name(value: str) -> str: parts = re.findall(r"[A-Za-z0-9]+", value) if not parts: return "generatedItem" head = parts[0].lower() tail = "".join(part.capitalize() for part in parts[1:]) return head + tail def _prefixed(prefix: str, path: str) -> str: base = Path(prefix) if prefix else Path() return (base / path).as_posix()