This file provides guidelines for AI agents working on the SchedSim project (Kubernetes Scheduler Simulation Platform).
# Setup & Environment
make setup # Install all dev dependencies (idempotent)
make check-env # Verify dev environment; auto-fix if possible
# Build
make build # Full build: check-env + frontend + backend (CGO_ENABLED=1)
make build-backend # Build Go binary to bin/schedsim
make build-frontend # Build React assets to web/dist
# Test
make test # Run Go unit tests with coverage
make test-frontend # Run frontend tests with Vitest
make verify-scenarios # Verify built-in scenario templates against running backend
# Lint
make lint # Run golangci-lint + eslint
# Development
make start / make dev # Full dev env: check-env + ensure-frontend + sqlite-web + backend + frontend
make dev-backend # Backend with hot reload (air if installed, fallback go run)
make dev-frontend # Frontend Vite dev server (port 5173)
# Database
make sqlite-web # Launch sqlite_web in foreground (port 8081)
make sqlite-web-bg # Launch sqlite_web in background (auto-started by dev)
# Docker & Helm
make docker # Build Docker image
make docker-push # Push Docker image
make helm-lint # Validate Helm chart
make helm-package # Package Helm chart to dist/
# Production
make prod # Build + run binary
# Misc
make clean # Remove bin/, web/dist/, data/
make migrate # Run DB migrationnpm run dev # Start Vite dev server (port 5173)
npm run build # tsc -b && vite build
npm run lint # ESLint check
npm test # vitest run (single pass)
npm run test:watch # vitest (watch mode)
npm run preview # Preview production buildGo Backend:
# Run specific test function
go test ./internal/api/... -run TestClusterCreate -v
# Run tests in a specific file
go test ./internal/service/scenario_test.go -v
# Run with coverage
go test ./internal/... -coverFrontend (Vitest):
# Run specific test file
cd web && npx vitest run scenarioEditor.test.tsx
# Run tests matching a pattern
cd web && npx vitest run -t "ScenarioEditor"
# Run with coverage
cd web && npx vitest run --coverage| Element | Convention | Example |
|---|---|---|
| Files | lowercase_underscores | cluster_manager.go |
| Packages | lowercase single word | package cluster |
| Interfaces | PascalCase, describes capability | ClusterProvisioner |
| Structs | PascalCase | KindProvisioner |
| Constants | PascalCase or UPPER_SNAKE | MaxRetries or MAX_RETRIES |
| Test files | *_test.go |
cluster_test.go |
| Test functions | TestXxx |
TestClusterCreate |
- Wrap errors with context:
fmt.Errorf("creating cluster: %w", err) - Never suppress errors with empty catch blocks
- API layer returns structured errors:
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Detail string `json:"detail,omitempty"`
}- Use zap structured logger
- Log levels: Debug < Info < Warn < Error
- Include context fields: requestID, userID, clusterID
- NEVER log secrets (passwords, tokens, kubeconfigs)
cmd/schedsim/ # Main entry point only
internal/ # Private application code
├── analysis/ # Analysis & reporting module
├── api/ # HTTP layer (handler, middleware, router)
├── auth/ # Authentication (JWT, local auth)
├── cluster/ # Cluster lifecycle management
│ ├── deployer/ # Component deployers (scheduler, metrics, etc.)
│ └── mirror/ # Cluster snapshot & transform
├── config/ # Configuration loading (Viper)
├── engine/ # Scenario execution engine (DAG)
├── model/ # Data models (GORM)
├── plugin/ # Plugin system
├── service/ # Business logic layer
└── store/ # Database access layer (SQLite/PostgreSQL)
pkg/ # Public reusable libraries (utils, types)
web/ # Frontend React application
deploy/ # Deployment files (Docker, Helm, K8s manifests)
configs/ # Runtime configuration (schedsim.yaml)
scripts/ # Build & utility scripts
test/ # E2E and integration tests
- Use testify for assertions
- Prefer table-driven tests
- Mock external dependencies (K8s client, database)
- Target >70% coverage for new code
| Element | Convention | Example |
|---|---|---|
| Components | PascalCase | ScenarioEditor.tsx |
| Utils | camelCase | formatDate.ts |
| Types | PascalCase | Scenario.ts |
| Hooks | camelCase + use prefix | useWebSocket.ts |
| Stores | camelCase | useScenarioStore.ts |
| Constants | UPPER_SNAKE | MAX_NODES |
The project uses strict TypeScript with these settings:
strict: truenoUnusedLocals: truenoUnusedParameters: trueerasableSyntaxOnly: trueverbatimModuleSyntax: truenoFallthroughCasesInSwitch: truenoUncheckedSideEffectImports: true- Target: ES2022, Module: ESNext, moduleResolution: bundler
Always:
- Define explicit types for props, function returns
- Use
interfacefor object shapes - Use
typefor unions, primitives - Avoid
any- useunknownif necessary
- Use functional components + Hooks only (no class components)
- Define Props with TypeScript interface
- Keep components small (< 300 lines)
- Use Ant Design components for consistency
- Extract reusable logic into custom hooks
- Global state: Zustand (
useXxxStore.ts) - Local component state:
useState - Avoid prop drilling > 2 levels
// Order: 1. React hooks 2. Libraries 3. Internal modules (@/) 4. Relative paths 5. Types
import { useState, useEffect } from 'react'
import { Button, Card } from 'antd'
import { useScenarioStore } from '@/stores'
import { formatDate } from '@/utils'
import type { Scenario } from '@/types'Note: React 19 does not require
import React from 'react'for JSX. Useimport typefor type-only imports (enforced byverbatimModuleSyntax).
- Resource names: plural
/api/v1/scenarios - Version in URL:
/api/v1/ - Pagination:
?page=1&pageSize=20 - Sorting:
?sort=createdAt&order=desc - Filtering:
?status=running
// Success (single)
{ data: { ... } }
// Success (list)
{ data: [...], total: 100, page: 1, pageSize: 20 }
// Error
{ error: { code: 400, message: "Invalid input", detail: "..." } }main- Stable release branchdevelop- Development branchfeat/*- New featuresfix/*- Bug fixes
<type>(<scope>): <subject>
type: feat, fix, docs, style, refactor, test, chore, perf
scope: api, web, cluster, engine, auth, report, plugin
Example:
feat(cluster): add KWOK node batch creation support
fix(engine): resolve DAG execution order issue
test(api): add scenario CRUD unit tests
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript 5.9, Vite 7, Ant Design 6 |
| State | Zustand 5 |
| Charts | @ant-design/charts |
| Diagram | React Flow |
| Code Editor | Monaco Editor (@monaco-editor/react) |
| Terminal | xterm.js (@xterm/xterm) |
| Routing | React Router 7 |
| Backend | Go 1.26+, Gin, GORM |
| Database | SQLite (dev), PostgreSQL (prod) |
| K8s Client | client-go, k8s.io/api v0.36 |
| K8s Sim | Kind, KWOK |
| Cluster Runtime | Local Docker / Remote VM (SSH) / K8s-in-K8s |
| Auth | JWT (golang-jwt/jwt/v5) |
| Config | Viper |
| Logging | Zap |
| Testing | Vitest, Go testing, Playwright (E2E) |
| Deploy | Docker, Helm Chart |
// src/components/ScenarioEditor/ScenarioEditor.tsx
import { useState } from 'react'
import { Card } from 'antd'
import type { ScenarioEditorProps } from './types'
import { useScenarioStore } from '@/stores'
import { validateScenario } from '@/utils'
export const ScenarioEditor: React.FC<ScenarioEditorProps> = ({
scenarioId,
onSave
}) => {
const [loading, setLoading] = useState(false)
const scenario = useScenarioStore(s => s.current)
const handleSubmit = async () => {
setLoading(true)
try {
await onSave(scenario)
} finally {
setLoading(false)
}
}
return (
<Card>
{/* ... */}
</Card>
)
}// src/stores/scenarioStore.ts
import { create } from 'zustand'
interface ScenarioState {
current: Scenario | null
list: Scenario[]
setCurrent: (s: Scenario) => void
fetchList: () => Promise<void>
}
export const useScenarioStore = create<ScenarioState>((set) => ({
current: null,
list: [],
setCurrent: (scenario) => set({ current: scenario }),
fetchList: async () => {
const data = await api.getScenarios()
set({ list: data })
}
}))- Backend runs on port 8080
- Frontend runs on port 5173, proxies
/apiand/dbto 8080 - WebSocket endpoint:
/api/v1/wsfor real-time logs - SQLite Web browser on port 8081 (auto-started by
make dev) - Database auto-migrates on startup (GORM)
- Use
airfor Go hot reload (make setupinstalls it) - CGO_ENABLED=1 required (mattn/go-sqlite3)
- Path alias:
@→web/src/(configured in vite.config.ts + tsconfig)
The platform supports 3 cluster runtime backends (configured in configs/schedsim.yaml → clusterRuntime.backend):
| Backend | Description |
|---|---|
local |
Creates Kind clusters on the local Docker daemon (default) |
remote-vm |
Creates Kind clusters on a remote VM via SSH |
k8s-cluster |
Runs Kind-in-Pod on a target Kubernetes cluster |
CRITICAL: Every task completion MUST include a git commit.
When any development task is completed (bug fix, feature implementation, refactoring, etc.), the AI agent MUST:
-
Stage relevant files
git add <relevant-files>
-
Commit with descriptive message
git commit -m "<type>(<scope>): <subject> <detailed description of changes> - item 1 - item 2 "
-
Push to remote
git push origin main
<type>(<scope>): <subject>
<body>
Types:
feat: New featurefix: Bug fixrefactor: Code refactoringtest: Adding testsdocs: Documentationchore: Maintenance tasks
Scopes:
api: Backend API changesweb: Frontend changescluster: Cluster management (Kind, KWOK, runtime)engine: Execution engineauth: Authentication & authorizationreport: Report functionalityplugin: Plugin systemmirror: Cluster snapshot & mirrordeploy: Deployment configuration (Helm, Docker, K8s manifests)config: Configuration changesstore: Database & data layer
fix(web): resolve sidebar menu highlighting issue
- Changed from defaultOpenKeys to controlled openKeys state
- Added useEffect to respond to route changes
- Fixed dynamic route matching for detail pages
Files changed:
- web/src/components/Layout/Sidebar.tsx
Before committing, ensure:
- Code builds successfully (
make build) - Tests pass (
make test) - No TypeScript errors (
cd web && npm run build) - Lint passes (
make lint) - Commit message follows convention
Always commit after:
- Completing a bug fix
- Implementing a new feature
- Refactoring code
- Adding tests
- Updating documentation
- Fixing a broken build
Do NOT commit:
- Work in progress (unless creating a draft)
- Broken tests
- Sensitive files (.env, credentials, etc.)
- Generated files (node_modules, dist, etc.)
CRITICAL: Any change affecting runtime behavior MUST be reflected in the Helm Chart configuration under deploy/helm/schedsim/.
When modifying any of the following, you MUST also update the corresponding Helm deployment files:
| If you change... | You MUST update... |
|---|---|
configs/schedsim.yaml 新增/修改/删除配置项 |
deploy/helm/schedsim/values.yaml → config 段 |
Server 监听端口 (server.port) |
values.yaml → config.server.port + service.port + probe ports |
| 新增环境变量依赖 | deploy/helm/schedsim/templates/deployment.yaml → env 段 |
健康检查端点路径 (/health, /ready) |
values.yaml → livenessProbe / readinessProbe 路径 |
| 新增 Volume/存储需求 | deployment.yaml 模板 + values.yaml persistence 段 |
| 资源消耗显著变化(如启用新特性导致内存增长) | values.yaml → resources.limits / resources.requests |
| 应用版本发布 | Chart.yaml → appVersion + values.yaml → image.tag |
| 新增命令行参数 / 启动 flag | deployment.yaml 模板 → args 段 |
configs/schedsim.yaml ←→ deploy/helm/schedsim/values.yaml (config 段)
internal/api/router.go ←→ values.yaml (端口、健康检查路径)
cmd/schedsim/main.go ←→ deployment.yaml (args、env)
deploy/helm/schedsim/Chart.yaml → appVersion 需与发布版本一致
After making changes, MUST run the following to verify Helm template renders correctly:
helm template schedsim-dev deploy/helm/schedsim/ --debug 2>&1 | head -100If the AI agent modifies runtime configuration without updating Helm files:
- The commit MUST NOT proceed until sync is completed
- Add a
[DEPLOY-SYNC]tag in the commit message body when deployment files are updated alongside application code
feat(api): add prometheus metrics endpoint on /metrics
- Added /metrics endpoint with Go runtime metrics
- [DEPLOY-SYNC] Updated values.yaml: added metrics port 9090
- [DEPLOY-SYNC] Updated deployment.yaml: added metrics container port
- [DEPLOY-SYNC] Updated service.yaml: exposed metrics port
Files changed:
- internal/api/router.go
- deploy/helm/schedsim/values.yaml
- deploy/helm/schedsim/templates/deployment.yaml
- deploy/helm/schedsim/templates/service.yaml