From cd20d595c6c7faa109b7caf186d2cf1f78116390 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 30 Jun 2026 15:29:32 +0800 Subject: [PATCH] docs: add implementation roadmap and stage 0 plan --- ...-ticket-platform-implementation-roadmap.md | 99 + ...-06-30-stage-0-foundation-design-system.md | 1585 +++++++++++++++++ 2 files changed, 1684 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md create mode 100644 docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md diff --git a/docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md b/docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md new file mode 100644 index 0000000..52991bb --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md @@ -0,0 +1,99 @@ +# AI Ticket Platform Implementation Roadmap + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:writing-plans to expand each stage into a detailed implementation plan before writing code. Use superpowers:subagent-driven-development or superpowers:executing-plans when executing a detailed stage plan. + +**Goal:** Break the AI-native technical support ticket platform into independently deliverable implementation stages. + +**Architecture:** Build a modular monolith first. Keep Django apps, React feature modules, workers, model gateway, Agent runtime, and MCP integration separated by clear interfaces so heavily loaded areas can be split later. + +**Tech Stack:** Django, Django Ninja, PostgreSQL, pgvector, Redis, Celery, MinIO, React, Vite, Tailwind CSS, shadcn/ui, Radix UI, TanStack Table, OpenAI-compatible model adapter. + +--- + +## Source Spec + +- `docs/superpowers/specs/2026-06-30-ai-ticket-platform-design.md` + +## Planning Rule + +The product contains multiple independent subsystems. Do not implement the entire spec from one plan. Use this roadmap to create one detailed plan per stage. Each detailed plan must produce working, testable software. + +## Stage Breakdown + +| Stage | Name | Purpose | Depends On | Detailed Plan | +|---|---|---|---|---| +| 0 | Foundation and design system | Create the repo skeleton, local runtime, auth/RBAC baseline, audit baseline, frontend theme and component standards. | None | `docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md` | +| 1 | Ticket workflow and reusable solutions | Implement ticket lifecycle, comments, attachments, events, export, solution library, and solution links. | Stage 0 | Create after Stage 0 passes. | +| 2 | Knowledge base and vector retrieval | Implement knowledge bases, documents, parsing jobs, chunks, embeddings, and retrieval. | Stage 0, partial Stage 1 | Create after Stage 1 model contracts stabilize. | +| 3 | Model gateway and Agent/Skill runtime | Implement provider configs, model configs, model testing, Skill configs, built-in Agents, runs, steps, AI artifacts. | Stage 0, Stage 1, Stage 2 retrieval interfaces | Create after Stage 2 retrieval contracts exist. | +| 4 | MCP tools and trust boundary | Implement MCP server registration, tool sync, risk levels, Agent tool binding, tool calls, confirmation, audit. | Stage 3 | Create after Agent runtime exists. | +| 5 | Historical ticket migration and polish | Import historical tickets, standardize fields, vectorize, generate solution drafts, add dashboards and UI polish. | Stage 1, Stage 2, Stage 3 | Create after core flows work. | + +## Stage 0 Deliverables + +| Area | Deliverable | +|---|---| +| Backend | Django project, settings split, Django Ninja API root, health endpoint, accounts app, audit app, system app. | +| Frontend | Vite React project, Tailwind, shadcn/Radix base, theme tokens, layout shell, component preview page. | +| Runtime | Docker Compose with backend, frontend, PostgreSQL, Redis, MinIO, Celery worker, Celery beat. | +| Governance | Login baseline, users/groups/roles baseline, menu definitions, permission seed command, audit model. | +| Quality | pytest, ruff, mypy baseline, frontend lint/typecheck/build, pre-commit optional. | +| Documentation | `.env.example`, local startup guide, design system notes. | + +## Stage 1 Deliverables + +| Area | Deliverable | +|---|---| +| Ticket core | `ticket`, `ticket_category`, `ticket_comment`, `ticket_attachment`, `ticket_event`, `ticket_collaborator`. | +| Ticket APIs | Create/list/detail/update/status transition/comment/upload/export. | +| Ticket UI | List page, create page, detail page, status timeline, comments, attachments. | +| Solutions | `solution`, `ticket_resolution`, `ticket_solution_link`, solution list/detail/linking. | +| Audit | Ticket operations written to audit/event tables. | + +## Stage 2 Deliverables + +| Area | Deliverable | +|---|---| +| Knowledge | `knowledge_base`, `knowledge_document`, `knowledge_item`, `knowledge_chunk`. | +| Jobs | Document parsing, chunking, embedding enqueue/retry/status. | +| Retrieval | Keyword search plus vector search interface. | +| UI | Knowledge base, document status, knowledge item editor, search page. | + +## Stage 3 Deliverables + +| Area | Deliverable | +|---|---| +| Model gateway | `model_provider`, `model_config`, encrypted secrets, OpenAI-compatible client adapter. | +| Model testing | Single model test and multi-model comparison UI. | +| Skills | `skill_definition`, prompt template rendering, input/output schema validation. | +| Agents | `agent_definition`, `agent_run`, `agent_step`, `ticket_ai_artifact`. | +| Built-ins | Ticket creation Agent, support analysis Agent, knowledge distillation Agent. | + +## Stage 4 Deliverables + +| Area | Deliverable | +|---|---| +| MCP config | `tool_server`, `tool_definition`, `tool_call`. | +| Sync | Tool discovery/sync from configured MCP server. | +| Risk control | low/medium/high risk, human confirmation for high risk. | +| Agent integration | Authorized tool calls appear in `agent_step` and `tool_call`. | + +## Stage 5 Deliverables + +| Area | Deliverable | +|---|---| +| Migration | Import script and staging model for historical tickets. | +| AI cleanup | Summaries, categories, error codes, solution draft generation. | +| Retrieval | Historical tickets available for similar-ticket search. | +| Dashboards | Ticket trends, high-frequency problems, solution usage, Agent usage. | + +## Cross-Stage Invariants + +- Business writes generated by AI require user confirmation by default. +- Secrets are encrypted at rest. +- Raw model/tool payloads stored in logs must support masking. +- No business page may define one-off colors, status badges, or table styling. +- Every backend stage includes API tests. +- Every frontend stage includes typecheck and build verification. +- Commit after each coherent task. + diff --git a/docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md b/docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md new file mode 100644 index 0000000..1c2fb36 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md @@ -0,0 +1,1585 @@ +# Stage 0 Foundation And Design System Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build the initial runnable project foundation: backend, frontend, local runtime, RBAC/audit baseline, and frontend design system standards. + +**Architecture:** Use a modular monorepo with `backend/`, `frontend/`, `infra/`, and `docs/`. The backend is a Django modular monolith with Django Ninja APIs and dedicated apps for accounts, audit, and system configuration. The frontend is Vite React with Tailwind/shadcn-style primitives wrapped by product-level components. + +**Tech Stack:** Python 3.12, Django, Django Ninja, PostgreSQL, Redis, Celery, MinIO, pytest, ruff, mypy, React, Vite, TypeScript, Tailwind CSS, Radix UI, lucide-react, TanStack Table, React Hook Form, Zod. + +--- + +## Files To Create + +```text +backend/ + pyproject.toml + manage.py + config/ + __init__.py + settings/ + __init__.py + base.py + local.py + test.py + urls.py + asgi.py + wsgi.py + celery.py + apps/ + __init__.py + accounts/ + __init__.py + apps.py + models.py + schemas.py + api.py + permissions.py + management/ + __init__.py + commands/ + __init__.py + seed_permissions.py + tests/ + __init__.py + test_permissions_seed.py + audit/ + __init__.py + apps.py + models.py + api.py + tests/ + __init__.py + test_audit_api.py + system/ + __init__.py + apps.py + api.py + tests/ + __init__.py + test_health_api.py + tests/ + __init__.py + conftest.py +frontend/ + package.json + tsconfig.json + tsconfig.node.json + vite.config.ts + index.html + tailwind.config.ts + postcss.config.js + src/ + main.tsx + app/ + App.tsx + router.tsx + layout/ + AppShell.tsx + Sidebar.tsx + Topbar.tsx + components/ + ui/ + button.tsx + badge.tsx + card.tsx + app/ + AppPageHeader.tsx + AppStatusBadge.tsx + AppPriorityBadge.tsx + features/ + design-system/ + DesignSystemPreview.tsx + lib/ + cn.ts + styles/ + globals.css +infra/ + docker-compose.yml + backend.Dockerfile + frontend.Dockerfile +.env.example +docs/development/local-setup.md +docs/frontend/design-system.md +``` + +## Task 1: Create Backend Project Skeleton + +**Files:** +- Create: `backend/pyproject.toml` +- Create: `backend/manage.py` +- Create: `backend/config/settings/base.py` +- Create: `backend/config/settings/local.py` +- Create: `backend/config/settings/test.py` +- Create: `backend/config/urls.py` +- Create: `backend/config/asgi.py` +- Create: `backend/config/wsgi.py` +- Create: `backend/config/celery.py` +- Create: `backend/apps/system/api.py` +- Test: `backend/apps/system/tests/test_health_api.py` + +- [ ] **Step 1: Create the backend dependency manifest** + +Create `backend/pyproject.toml`: + +```toml +[project] +name = "ai-ticket-platform-backend" +version = "0.1.0" +description = "Backend for AI-native technical support ticket platform" +requires-python = ">=3.12" +dependencies = [ + "Django>=5.0,<5.3", + "django-ninja>=1.3,<2.0", + "psycopg[binary]>=3.2,<4.0", + "celery>=5.4,<6.0", + "redis>=5.0,<6.0", + "django-cors-headers>=4.4,<5.0", + "pydantic>=2.8,<3.0", + "python-dotenv>=1.0,<2.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.2,<9.0", + "pytest-django>=4.8,<5.0", + "ruff>=0.6,<1.0", + "mypy>=1.11,<2.0", + "django-stubs[compatible-mypy]>=5.0,<6.0", +] + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "config.settings.test" +python_files = ["test_*.py", "*_test.py"] +testpaths = ["apps", "tests"] + +[tool.ruff] +line-length = 100 +target-version = "py312" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] + +[tool.mypy] +python_version = "3.12" +plugins = ["mypy_django_plugin.main"] +ignore_missing_imports = true +strict_optional = true + +[tool.django-stubs] +django_settings_module = "config.settings.test" +``` + +- [ ] **Step 2: Create Django entry points** + +Create `backend/manage.py`: + +```python +#!/usr/bin/env python +import os +import sys + + +def main() -> None: + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() +``` + +Create `backend/config/asgi.py`: + +```python +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +application = get_asgi_application() +``` + +Create `backend/config/wsgi.py`: + +```python +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +application = get_wsgi_application() +``` + +Create `backend/config/celery.py`: + +```python +import os + +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +app = Celery("ai_ticket_platform") +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() +``` + +- [ ] **Step 3: Create settings** + +Create `backend/config/settings/base.py`: + +```python +from pathlib import Path +import os + +BASE_DIR = Path(__file__).resolve().parents[3] + +SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "dev-only-change-me") +DEBUG = os.getenv("DJANGO_DEBUG", "false").lower() == "true" +ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",") + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "corsheaders", + "apps.accounts", + "apps.audit", + "apps.system", +] + +MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + } +] + +WSGI_APPLICATION = "config.wsgi.application" +ASGI_APPLICATION = "config.asgi.application" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.getenv("POSTGRES_DB", "ai_ticket_platform"), + "USER": os.getenv("POSTGRES_USER", "ai_ticket"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD", "ai_ticket"), + "HOST": os.getenv("POSTGRES_HOST", "localhost"), + "PORT": int(os.getenv("POSTGRES_PORT", "5432")), + } +} + +LANGUAGE_CODE = "zh-hans" +TIME_ZONE = "Asia/Shanghai" +USE_I18N = True +USE_TZ = True + +STATIC_URL = "static/" +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +CORS_ALLOWED_ORIGINS = os.getenv( + "CORS_ALLOWED_ORIGINS", + "http://localhost:5173", +).split(",") + +CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0") +CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/1") +``` + +Create `backend/config/settings/local.py`: + +```python +from .base import * # noqa: F403 + +DEBUG = True +``` + +Create `backend/config/settings/test.py`: + +```python +from .base import * # noqa: F403 + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } +} + +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", +] +``` + +- [ ] **Step 4: Create API root and health endpoint** + +Create `backend/apps/system/api.py`: + +```python +from ninja import Router + +router = Router(tags=["system"]) + + +@router.get("/health") +def health(request): + return {"status": "ok"} +``` + +Create `backend/config/urls.py`: + +```python +from django.contrib import admin +from django.urls import path +from ninja import NinjaAPI + +from apps.system.api import router as system_router + +api = NinjaAPI(title="AI Ticket Platform API", version="0.1.0") +api.add_router("/system", system_router) + +urlpatterns = [ + path("admin/", admin.site.urls), + path("api/", api.urls), +] +``` + +- [ ] **Step 5: Write the failing health API test** + +Create `backend/apps/system/tests/test_health_api.py`: + +```python +def test_health_endpoint_returns_ok(client): + response = client.get("/api/system/health") + + assert response.status_code == 200 + assert response.json() == {"status": "ok"} +``` + +- [ ] **Step 6: Run the backend test** + +Run: + +```bash +cd backend +python -m pip install -e ".[dev]" +pytest apps/system/tests/test_health_api.py -v +``` + +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add backend +git commit -m "feat: add backend foundation" +``` + +## Task 2: Add Accounts Permission Seed Baseline + +**Files:** +- Create: `backend/apps/accounts/apps.py` +- Create: `backend/apps/accounts/models.py` +- Create: `backend/apps/accounts/permissions.py` +- Create: `backend/apps/accounts/management/commands/seed_permissions.py` +- Test: `backend/apps/accounts/tests/test_permissions_seed.py` + +- [ ] **Step 1: Create accounts app config and empty models** + +Create `backend/apps/accounts/apps.py`: + +```python +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.accounts" +``` + +Create `backend/apps/accounts/models.py`: + +```python +# Use Django's built-in User, Group, and Permission models for MVP RBAC. +``` + +- [ ] **Step 2: Define permission catalog** + +Create `backend/apps/accounts/permissions.py`: + +```python +from dataclasses import dataclass + + +@dataclass(frozen=True) +class PermissionSpec: + codename: str + name: str + + +PERMISSIONS: tuple[PermissionSpec, ...] = ( + PermissionSpec("ticket.view", "View tickets"), + PermissionSpec("ticket.create", "Create tickets"), + PermissionSpec("ticket.edit", "Edit tickets"), + PermissionSpec("ticket.export", "Export tickets"), + PermissionSpec("ticket.assign", "Assign tickets"), + PermissionSpec("ticket.close", "Close tickets"), + PermissionSpec("knowledge.view", "View knowledge"), + PermissionSpec("knowledge.manage", "Manage knowledge"), + PermissionSpec("solution.view", "View solutions"), + PermissionSpec("solution.manage", "Manage solutions"), + PermissionSpec("agent.use", "Use Agents"), + PermissionSpec("agent.manage", "Manage Agents"), + PermissionSpec("model.manage", "Manage model configs"), + PermissionSpec("tool.manage", "Manage MCP tools"), + PermissionSpec("audit.view", "View audit logs"), + PermissionSpec("system.manage", "Manage system settings"), +) + +DEFAULT_GROUPS: dict[str, tuple[str, ...]] = { + "System Admin": tuple(permission.codename for permission in PERMISSIONS), + "Knowledge Admin": ( + "ticket.view", + "knowledge.view", + "knowledge.manage", + "solution.view", + "solution.manage", + "agent.use", + ), + "Support Engineer": ( + "ticket.view", + "ticket.create", + "ticket.edit", + "ticket.assign", + "ticket.close", + "knowledge.view", + "solution.view", + "agent.use", + ), + "Requester": ( + "ticket.view", + "ticket.create", + "knowledge.view", + "solution.view", + ), + "Auditor": ( + "ticket.view", + "knowledge.view", + "solution.view", + "audit.view", + ), +} +``` + +- [ ] **Step 3: Create permission seed command** + +Create `backend/apps/accounts/management/commands/seed_permissions.py`: + +```python +from django.contrib.auth.models import Group, Permission +from django.contrib.contenttypes.models import ContentType +from django.core.management.base import BaseCommand + +from apps.accounts.permissions import DEFAULT_GROUPS, PERMISSIONS + + +class Command(BaseCommand): + help = "Seed MVP permissions and default user groups." + + def handle(self, *args, **options): + content_type, _ = ContentType.objects.get_or_create( + app_label="system", + model="platformpermission", + ) + + permissions_by_codename: dict[str, Permission] = {} + for spec in PERMISSIONS: + permission, _ = Permission.objects.get_or_create( + content_type=content_type, + codename=spec.codename, + defaults={"name": spec.name}, + ) + if permission.name != spec.name: + permission.name = spec.name + permission.save(update_fields=["name"]) + permissions_by_codename[spec.codename] = permission + + for group_name, codenames in DEFAULT_GROUPS.items(): + group, _ = Group.objects.get_or_create(name=group_name) + group.permissions.set(permissions_by_codename[codename] for codename in codenames) + + self.stdout.write(self.style.SUCCESS("Seeded permissions and groups.")) +``` + +- [ ] **Step 4: Write the seed command test** + +Create `backend/apps/accounts/tests/test_permissions_seed.py`: + +```python +from django.contrib.auth.models import Group, Permission +from django.core.management import call_command + +from apps.accounts.permissions import DEFAULT_GROUPS, PERMISSIONS + + +def test_seed_permissions_creates_permissions_and_groups(db): + call_command("seed_permissions") + + assert Permission.objects.filter( + codename__in=[permission.codename for permission in PERMISSIONS] + ).count() == len(PERMISSIONS) + + for group_name, codenames in DEFAULT_GROUPS.items(): + group = Group.objects.get(name=group_name) + actual = set(group.permissions.values_list("codename", flat=True)) + assert actual == set(codenames) +``` + +- [ ] **Step 5: Run the accounts test** + +Run: + +```bash +cd backend +pytest apps/accounts/tests/test_permissions_seed.py -v +``` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add backend/apps/accounts +git commit -m "feat: seed baseline RBAC permissions" +``` + +## Task 3: Add Audit Model And API + +**Files:** +- Create: `backend/apps/audit/apps.py` +- Create: `backend/apps/audit/models.py` +- Create: `backend/apps/audit/api.py` +- Modify: `backend/config/urls.py` +- Test: `backend/apps/audit/tests/test_audit_api.py` + +- [ ] **Step 1: Create audit app config** + +Create `backend/apps/audit/apps.py`: + +```python +from django.apps import AppConfig + + +class AuditConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.audit" +``` + +- [ ] **Step 2: Create operation audit model** + +Create `backend/apps/audit/models.py`: + +```python +from django.conf import settings +from django.db import models + + +class OperationAuditLog(models.Model): + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="operation_audit_logs", + ) + module = models.CharField(max_length=80) + action = models.CharField(max_length=80) + target_type = models.CharField(max_length=80) + target_id = models.CharField(max_length=120, blank=True) + request_snapshot = models.JSONField(default=dict, blank=True) + result = models.CharField(max_length=20) + ip_address = models.GenericIPAddressField(null=True, blank=True) + user_agent = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ["-created_at"] + indexes = [ + models.Index(fields=["module", "action"]), + models.Index(fields=["target_type", "target_id"]), + models.Index(fields=["created_at"]), + ] + + def __str__(self) -> str: + return f"{self.module}.{self.action}:{self.result}" +``` + +- [ ] **Step 3: Create read-only audit API** + +Create `backend/apps/audit/api.py`: + +```python +from datetime import datetime +from typing import Any + +from ninja import Router, Schema + +from apps.audit.models import OperationAuditLog + +router = Router(tags=["audit"]) + + +class OperationAuditLogOut(Schema): + id: int + module: str + action: str + target_type: str + target_id: str + request_snapshot: dict[str, Any] + result: str + ip_address: str | None + user_agent: str + created_at: datetime + + +@router.get("/operation-logs", response=list[OperationAuditLogOut]) +def list_operation_logs(request): + return OperationAuditLog.objects.all()[:100] +``` + +Modify `backend/config/urls.py` to include the audit router: + +```python +from django.contrib import admin +from django.urls import path +from ninja import NinjaAPI + +from apps.audit.api import router as audit_router +from apps.system.api import router as system_router + +api = NinjaAPI(title="AI Ticket Platform API", version="0.1.0") +api.add_router("/system", system_router) +api.add_router("/audit", audit_router) + +urlpatterns = [ + path("admin/", admin.site.urls), + path("api/", api.urls), +] +``` + +- [ ] **Step 4: Write the audit API test** + +Create `backend/apps/audit/tests/test_audit_api.py`: + +```python +from apps.audit.models import OperationAuditLog + + +def test_list_operation_logs_returns_recent_logs(client, db): + OperationAuditLog.objects.create( + module="system", + action="health_check", + target_type="system", + target_id="health", + result="success", + request_snapshot={"path": "/api/system/health"}, + ) + + response = client.get("/api/audit/operation-logs") + + assert response.status_code == 200 + payload = response.json() + assert len(payload) == 1 + assert payload[0]["module"] == "system" + assert payload[0]["action"] == "health_check" +``` + +- [ ] **Step 5: Run migrations and tests** + +Run: + +```bash +cd backend +python manage.py makemigrations audit +pytest apps/audit/tests/test_audit_api.py -v +``` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add backend/apps/audit backend/config/urls.py +git commit -m "feat: add operation audit baseline" +``` + +## Task 4: Create Frontend Foundation And Design Tokens + +**Files:** +- Create: `frontend/package.json` +- Create: `frontend/tsconfig.json` +- Create: `frontend/tsconfig.node.json` +- Create: `frontend/vite.config.ts` +- Create: `frontend/index.html` +- Create: `frontend/tailwind.config.ts` +- Create: `frontend/postcss.config.js` +- Create: `frontend/src/main.tsx` +- Create: `frontend/src/app/App.tsx` +- Create: `frontend/src/lib/cn.ts` +- Create: `frontend/src/styles/globals.css` + +- [ ] **Step 1: Create frontend package manifest** + +Create `frontend/package.json`: + +```json +{ + "name": "ai-ticket-platform-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "typecheck": "tsc -b", + "lint": "eslint src --ext ts,tsx" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "tailwind-merge": "^2.5.0", + "tailwindcss-animate": "^1.0.7", + "vite": "^6.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.4.20", + "eslint": "^9.16.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.16", + "typescript": "^5.7.0" + } +} +``` + +- [ ] **Step 2: Create Vite and TypeScript config** + +Create `frontend/tsconfig.json`: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ES2020"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} +``` + +Create `frontend/tsconfig.node.json`: + +```json +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} +``` + +Create `frontend/vite.config.ts`: + +```typescript +import react from '@vitejs/plugin-react' +import path from 'node:path' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + server: { + port: 5173 + } +}) +``` + +- [ ] **Step 3: Create Tailwind theme tokens** + +Create `frontend/tailwind.config.ts`: + +```typescript +import type { Config } from 'tailwindcss' + +const config: Config = { + darkMode: ['class'], + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + border: 'hsl(var(--border))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + surface: 'hsl(var(--surface))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + success: 'hsl(var(--success))', + warning: 'hsl(var(--warning))', + danger: 'hsl(var(--danger))', + info: 'hsl(var(--info))', + ai: 'hsl(var(--ai-accent))' + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + } + } + }, + plugins: [require('tailwindcss-animate')] +} + +export default config +``` + +Create `frontend/postcss.config.js`: + +```javascript +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} +``` + +Create `frontend/src/styles/globals.css`: + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 210 20% 98%; + --foreground: 222 25% 12%; + --surface: 0 0% 100%; + --muted: 210 18% 94%; + --muted-foreground: 215 12% 42%; + --border: 214 18% 86%; + --primary: 213 52% 32%; + --primary-foreground: 0 0% 100%; + --success: 151 55% 36%; + --warning: 38 86% 48%; + --danger: 0 70% 50%; + --info: 204 76% 44%; + --ai-accent: 186 52% 36%; + --radius: 0.5rem; + } + + body { + margin: 0; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + } +} +``` + +- [ ] **Step 4: Create minimal app entry** + +Create `frontend/index.html`: + +```html + + + + + + AI Ticket Platform + + +
+ + + +``` + +Create `frontend/src/lib/cn.ts`: + +```typescript +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +``` + +Create `frontend/src/app/App.tsx`: + +```tsx +export function App() { + return ( +
+
+

AI Ticket Platform

+

Foundation ready

+
+
+ ) +} +``` + +Create `frontend/src/main.tsx`: + +```tsx +import React from 'react' +import ReactDOM from 'react-dom/client' + +import { App } from './app/App' +import './styles/globals.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +) +``` + +- [ ] **Step 5: Verify frontend build** + +Run: + +```bash +cd frontend +npm install +npm run typecheck +npm run build +``` + +Expected: both commands pass. + +- [ ] **Step 6: Commit** + +```bash +git add frontend +git commit -m "feat: add frontend foundation and theme tokens" +``` + +## Task 5: Add Product Components And Preview Page + +**Files:** +- Create: `frontend/src/components/ui/button.tsx` +- Create: `frontend/src/components/ui/badge.tsx` +- Create: `frontend/src/components/ui/card.tsx` +- Create: `frontend/src/components/app/AppPageHeader.tsx` +- Create: `frontend/src/components/app/AppStatusBadge.tsx` +- Create: `frontend/src/components/app/AppPriorityBadge.tsx` +- Create: `frontend/src/features/design-system/DesignSystemPreview.tsx` +- Modify: `frontend/src/app/App.tsx` + +- [ ] **Step 1: Create base button** + +Create `frontend/src/components/ui/button.tsx`: + +```tsx +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '@/lib/cn' + +const buttonVariants = cva( + 'inline-flex h-9 items-center justify-center rounded-md px-3 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:opacity-90', + secondary: 'border border-border bg-surface text-foreground hover:bg-muted', + ghost: 'text-foreground hover:bg-muted', + danger: 'bg-danger text-white hover:opacity-90' + } + }, + defaultVariants: { + variant: 'default' + } + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +export const Button = React.forwardRef( + ({ className, variant, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button' + return + } +) + +Button.displayName = 'Button' +``` + +- [ ] **Step 2: Create badge and card primitives** + +Create `frontend/src/components/ui/badge.tsx`: + +```tsx +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/cn' + +const badgeVariants = cva( + 'inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium', + { + variants: { + variant: { + default: 'border-border bg-muted text-foreground', + success: 'border-transparent bg-success/10 text-success', + warning: 'border-transparent bg-warning/15 text-warning', + danger: 'border-transparent bg-danger/10 text-danger', + info: 'border-transparent bg-info/10 text-info', + ai: 'border-transparent bg-ai/10 text-ai' + } + }, + defaultVariants: { + variant: 'default' + } + } +) + +type BadgeProps = React.HTMLAttributes & VariantProps + +export function Badge({ className, variant, ...props }: BadgeProps) { + return +} +``` + +Create `frontend/src/components/ui/card.tsx`: + +```tsx +import * as React from 'react' + +import { cn } from '@/lib/cn' + +export function Card({ className, ...props }: React.HTMLAttributes) { + return
+} + +export function CardHeader({ className, ...props }: React.HTMLAttributes) { + return
+} + +export function CardContent({ className, ...props }: React.HTMLAttributes) { + return
+} +``` + +- [ ] **Step 3: Create product app components** + +Create `frontend/src/components/app/AppPageHeader.tsx`: + +```tsx +type AppPageHeaderProps = { + title: string + description?: string + actions?: React.ReactNode +} + +export function AppPageHeader({ title, description, actions }: AppPageHeaderProps) { + return ( +
+
+

{title}

+ {description ?

{description}

: null} +
+ {actions ?
{actions}
: null} +
+ ) +} +``` + +Create `frontend/src/components/app/AppStatusBadge.tsx`: + +```tsx +import { Badge } from '@/components/ui/badge' + +const STATUS_LABELS: Record = { + pending_info: '待补充', + pending: '待处理', + in_progress: '处理中', + pending_confirm: '待确认', + resolved: '已解决', + closed: '已关闭' +} + +export function AppStatusBadge({ status }: { status: string }) { + const variant = status === 'resolved' || status === 'closed' ? 'success' : 'info' + return {STATUS_LABELS[status] ?? status} +} +``` + +Create `frontend/src/components/app/AppPriorityBadge.tsx`: + +```tsx +import { Badge } from '@/components/ui/badge' + +const PRIORITY_LABELS: Record = { + low: '低', + medium: '中', + high: '高', + urgent: '紧急' +} + +export function AppPriorityBadge({ priority }: { priority: string }) { + const variant = priority === 'urgent' ? 'danger' : priority === 'high' ? 'warning' : 'default' + return {PRIORITY_LABELS[priority] ?? priority} +} +``` + +- [ ] **Step 4: Create design system preview** + +Create `frontend/src/features/design-system/DesignSystemPreview.tsx`: + +```tsx +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { AppPriorityBadge } from '@/components/app/AppPriorityBadge' +import { AppStatusBadge } from '@/components/app/AppStatusBadge' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader } from '@/components/ui/card' + +export function DesignSystemPreview() { + return ( +
+
+ 创建工单} + /> + + + +

Buttons

+
+ + + + + + +
+ + + +

Badges

+
+ + Default + Success + Warning + Danger + Info + AI + + + +
+
+
+ ) +} +``` + +Modify `frontend/src/app/App.tsx`: + +```tsx +import { DesignSystemPreview } from '@/features/design-system/DesignSystemPreview' + +export function App() { + return +} +``` + +- [ ] **Step 5: Verify frontend** + +Run: + +```bash +cd frontend +npm run typecheck +npm run build +``` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add frontend/src +git commit -m "feat: add design system preview components" +``` + +## Task 6: Add Local Docker Runtime + +**Files:** +- Create: `infra/docker-compose.yml` +- Create: `infra/backend.Dockerfile` +- Create: `infra/frontend.Dockerfile` +- Create: `.env.example` +- Create: `docs/development/local-setup.md` + +- [ ] **Step 1: Create environment example** + +Create `.env.example`: + +```dotenv +DJANGO_SECRET_KEY=dev-only-change-me +DJANGO_DEBUG=true +DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 +CORS_ALLOWED_ORIGINS=http://localhost:5173 + +POSTGRES_DB=ai_ticket_platform +POSTGRES_USER=ai_ticket +POSTGRES_PASSWORD=ai_ticket +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 + +CELERY_BROKER_URL=redis://redis:6379/0 +CELERY_RESULT_BACKEND=redis://redis:6379/1 + +MINIO_ROOT_USER=minio +MINIO_ROOT_PASSWORD=minio123 +``` + +- [ ] **Step 2: Create backend Dockerfile** + +Create `infra/backend.Dockerfile`: + +```dockerfile +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +COPY backend/pyproject.toml /app/pyproject.toml +RUN pip install --no-cache-dir -e ".[dev]" + +COPY backend /app + +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] +``` + +- [ ] **Step 3: Create frontend Dockerfile** + +Create `infra/frontend.Dockerfile`: + +```dockerfile +FROM node:22-slim + +WORKDIR /app + +COPY frontend/package.json /app/package.json +RUN npm install + +COPY frontend /app + +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"] +``` + +- [ ] **Step 4: Create Docker Compose** + +Create `infra/docker-compose.yml`: + +```yaml +services: + postgres: + image: pgvector/pgvector:pg16 + environment: + POSTGRES_DB: ${POSTGRES_DB:-ai_ticket_platform} + POSTGRES_USER: ${POSTGRES_USER:-ai_ticket} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ai_ticket} + ports: + - "5432:5432" + volumes: + - postgres-data:/var/lib/postgresql/data + + redis: + image: redis:7-alpine + ports: + - "6379:6379" + + minio: + image: minio/minio:latest + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minio123} + ports: + - "9000:9000" + - "9001:9001" + volumes: + - minio-data:/data + + backend: + build: + context: .. + dockerfile: infra/backend.Dockerfile + env_file: + - ../.env + ports: + - "8000:8000" + depends_on: + - postgres + - redis + volumes: + - ../backend:/app + + worker: + build: + context: .. + dockerfile: infra/backend.Dockerfile + env_file: + - ../.env + command: celery -A config.celery worker -l info + depends_on: + - backend + - redis + volumes: + - ../backend:/app + + frontend: + build: + context: .. + dockerfile: infra/frontend.Dockerfile + ports: + - "5173:5173" + volumes: + - ../frontend:/app + - frontend-node-modules:/app/node_modules + +volumes: + postgres-data: + minio-data: + frontend-node-modules: +``` + +- [ ] **Step 5: Create local setup docs** + +Create `docs/development/local-setup.md`: + +```markdown +# Local Setup + +## Prerequisites + +- Python 3.12 +- Node.js 22 +- Docker Desktop + +## First Run + +```bash +copy .env.example .env +cd infra +docker compose --env-file ../.env up --build +``` + +Backend: + +- API: http://localhost:8000/api/docs +- Health: http://localhost:8000/api/system/health + +Frontend: + +- App: http://localhost:5173 + +## Backend Tests + +```bash +cd backend +python -m pip install -e ".[dev]" +pytest -v +``` + +## Frontend Checks + +```bash +cd frontend +npm install +npm run typecheck +npm run build +``` +``` + +- [ ] **Step 6: Verify compose config** + +Run: + +```bash +cd infra +docker compose --env-file ../.env.example config +``` + +Expected: command prints a valid compose config without errors. + +- [ ] **Step 7: Commit** + +```bash +git add .env.example infra docs/development +git commit -m "feat: add local docker runtime" +``` + +## Task 7: Final Stage 0 Verification + +**Files:** +- Modify only if verification exposes an issue. + +- [ ] **Step 1: Run backend tests** + +Run: + +```bash +cd backend +pytest -v +``` + +Expected: all tests pass. + +- [ ] **Step 2: Run backend lint** + +Run: + +```bash +cd backend +ruff check . +``` + +Expected: no lint errors. + +- [ ] **Step 3: Run frontend checks** + +Run: + +```bash +cd frontend +npm run typecheck +npm run build +``` + +Expected: both commands pass. + +- [ ] **Step 4: Run compose config verification** + +Run: + +```bash +cd infra +docker compose --env-file ../.env.example config +``` + +Expected: valid config is printed. + +- [ ] **Step 5: Commit verification fixes if any** + +If any files were changed to fix verification failures: + +```bash +git add +git commit -m "fix: stabilize stage 0 foundation" +``` + +If no files changed, do not create an empty commit. + +## Self-Review Notes + +Spec coverage: + +- Covers project foundation, backend modular monolith start, frontend design system, RBAC seed, audit baseline, and local runtime. +- Does not implement tickets, knowledge, model gateway, Agents, Skills, or MCP. Those are intentionally deferred to later stage plans. + +Placeholder scan: + +- No unfinished placeholder markers are used. +- Later stages are referenced through the roadmap, not as incomplete Stage 0 work. + +Type consistency: + +- Backend app paths match `INSTALLED_APPS`. +- Frontend alias `@/*` matches Vite and TypeScript config. +- Docker service names match environment defaults.