opencode-config/.opencode/scripts/project_contract.py

95 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Project contract: single source of truth for project types, stacks,
expected structure, frontend markers.
Imported by both ``spec-status.py`` and ``project-status.py`` via
``importlib.util.spec_from_file_location`` (no ``sys.path`` mutation).
Stdlib-only imports.
Contract symbols:
ProjectType — StrEnum with 7 members (incl. MCP_SERVER + UNKNOWN)
VALID_TYPES — set[str] excluding "unknown"
STACK_REQUIRED — dict[type -> list[str]] of mandatory stack items
STRUCTURE_EXPECTED — dict[type -> list[str]] of expected top-level dirs/files
FRONTEND_STACK_MARKERS — dict with keys for fullstack frontend detection
"""
from __future__ import annotations
from enum import StrEnum
class ProjectType(StrEnum):
"""Project type enum (auto-detected or spec-declared)."""
FULLSTACK = "fullstack"
BACKEND = "backend"
CLI = "cli"
BOT = "bot"
WORKER = "worker"
MCP_SERVER = "mcp-server"
UNKNOWN = "unknown"
# Excludes "unknown" — used by spec-status PROJECT_TYPE phase validation.
VALID_TYPES: set[str] = {t.value for t in ProjectType if t != ProjectType.UNKNOWN}
# Mandatory stack items per project type (spec-status Phase 2 STACK).
# `uv` stays in ALL stacks (build-tool, mentioned in stack.md).
STACK_REQUIRED: dict[str, list[str]] = {
"backend": ["fastapi", "tortoise", "uv", "pytest", "ruff", "mypy", "loguru", "pydantic"],
"fullstack": [
"fastapi",
"tortoise",
"svelte",
"sveltekit",
"biome",
"uv",
"ruff",
"mypy",
"pytest",
"tailwind",
"shadcn",
"typescript",
],
"mcp-server": ["fastapi", "mcp", "patchright", "uv"],
"cli": ["typer", "uv", "hatchling", "ruff", "mypy", "pytest"],
"bot": ["aiogram", "fastapi", "uv", "ruff", "mypy", "pytest"],
"worker": ["prefect", "uv", "ruff", "mypy", "pytest"],
}
# Expected top-level structure per type (project-status check_structure).
# BACKEND is resolved dynamically by ``_expected_backend_paths`` (src/<pkg>/...),
# so it is NOT in this dict. mcp-server structure is TBD out of scope.
# Keys are strings (project type values), NOT ProjectType enum members —
# kept as plain strings for portability across both oracles.
STRUCTURE_EXPECTED: dict[str, list[str]] = {
"fullstack": ["backend", "frontend"],
"cli": ["src"], # src/<package>/ — checked generically
"bot": ["src/bot.py"],
"worker": ["src/flow.py"],
"unknown": [],
}
# Fullstack frontend stack markers (project-status _check_type_specific_structure).
# `tailwindcss` + `bits-ui` (shadcn-svelte proxy) in package.json deps, plus
# `components.json` (shadcn config) and `tsconfig.json` (TypeScript) existence.
FRONTEND_STACK_MARKERS: dict[str, list[str]] = {
"fullstack_package_deps": ["tailwindcss", "bits-ui"],
"fullstack_files": ["frontend/components.json", "frontend/tsconfig.json"],
}
# Fullstack mobile-first markers (project-status _check_mobile_first, issue #278).
# Distinct from FRONTEND_STACK_MARKERS so the stack check stays focused on the
# Tailwind/shadcn/TS trio. Keys:
# fullstack_files — paths (relative to repo root) that must exist
# for PWA + mobile Playwright + a11y to be present.
# fullstack_app_html_markers — substrings that must appear in app.html <head>.
MOBILE_FIRST_MARKERS: dict[str, list[str]] = {
"fullstack_files": [
"frontend/static/manifest.webmanifest",
"frontend/tests/e2e/mobile.spec.ts",
"frontend/tests/e2e/accessibility.spec.ts",
],
"fullstack_app_html_markers": ["viewport", "manifest"],
}