# AI Coding Instructions for Ticket Manager

## Big Picture Architecture

- **Entry point**: `cmd/ticket-manager`. Startup runs the shared service manager first, then the API server, then blocks until context cancellation or runtime error and shuts both down gracefully.
- **HTTP API**: `internal/app` builds the Gin-based `apiserver.APIServer`. Business routes are registered under `/api/v1/ticketmanager`; `/healthz` and `/metrics` are root routes outside the business route path.
- **Dependency injection**: `go.uber.org/dig` is wired in `internal/deps`. Handler constructors return `app.HandlerResult`, which contributes `apiserver.Handler` values into the `apiHandlers` group.
- **Persistence**: the service uses the shared SDK database package and GORM repositories under `internal/services/ticket/repo`.
- **Runtime integrations**: Jira, Lark, and Agent Gateway integrations live under `internal/integrations`; service code depends on narrow interfaces rather than concrete external clients when practical.

## Core Conventions

- **API DTOs**: shared request/response structs belong in `packages/sdk-go/contracts/api/ticket-manager/...`, split by domain such as `ticket`, `dispatch`, `workspace`, `engineer`, and `lark`.
- **Domain entities**: shared domain structs belong in `packages/sdk-go/contracts/entity/ticket-manager/...`. Use stable import aliases when helpful, for example `tktent`, `dispatchent`, or domain-specific API aliases.
- **Database models**: GORM table models stay in `services/ticket-manager/models/...`. Do not put service-only table definitions into `packages/sdk-go`.
- **Service interfaces**: service-facing interfaces live in each service package's `api` subpackage, for example `internal/services/ticket/api`. Implementations live beside them in the parent service package.
- **Repositories**: ticket persistence interfaces live in `internal/services/ticket/repo` and are wrapped by `internal/services/ticket/api.TicketService`. Handlers and upper-level services should depend on `TicketService`, not repository interfaces.
- **Shared SDK usage**: reuse `packages/sdk-go` infrastructure helpers for API server responses, logging, config, database, transactions, Lark middleware, and service lifecycle before adding local duplicates.

## Handler and Service Patterns

- **Handlers**: new Gin handlers should implement `apiserver.Handler`, return `app.HandlerResult`, and register routes using `routers[app.APIRoutePath]`.
- **Request validation**: in handler-layer request DTOs, prefer Gin `binding` tags for required fields, mutual exclusion, and other structural validation before adding manual validation logic in handlers.
- **Routes**: keep all business routes under `app.APIRoutePath`. Do not add business endpoints to the root router; root is reserved for cross-cutting endpoints such as `/healthz` and `/metrics`.
- **Responses**: use `apiutils.Success` and `apiutils.Fail` from `packages/sdk-go/infra/apiserver/utils` instead of raw `c.JSON` or local response wrappers.
- **Logging**: use `logger.FromContext(...)` when a context is available. Use `logger.FromContext(ctx).V(4).Info(...)` for debug-style logs.
- **Service wiring**: add new constructors to `internal/deps/deps.go`. Use `dig.In` and `dig.Out` structs when a constructor needs grouped outputs or multiple dependencies.
- **Service manager**: background services should integrate through the shared `packages/sdk-go/infra/service` lifecycle instead of ad hoc goroutines when they need coordinated start/stop behavior.

## Integration Points

- **Jira**: runtime code depends directly on `packages/sdk-go/infra/jira/client.JiraClient`. Use the generated SDK mock in tests instead of handwritten Jira doubles.
- **Lark**: event callbacks use the shared Lark middleware and `LarkEventDispatcher` from `packages/sdk-go/infra/lark`. Do not rebuild custom verification, decryption, or event dispatch logic in handlers.
- **Agent tasks**: Dispatch triage and Workspace timeline use Agent Service `RunAndWait`; task advisor uses `Submit`. Every request carries an explicit scene and business key. Agent selection is centralized in `services.agent.agentID`, which defaults to empty and delegates selection to Gateway. Business TaskRequest carries optional AgentModel, selected by each scene's `agentModel` configuration; steering ignores the model override. Business validation and repair stay with the caller. Preserve single-replica timeline recovery through its existing periodic watchers and persisted window state; late callbacks must not advance a fresh run.
- **Agent Gateway**: Dispatch triage uses Ticket Manager's Agent Service task runner, uses the shared `services.agent.agentID` and optional `services.dispatch.triage.agentModel`, and waits for the matching terminal Task Event. The selected Agent must expose the `triage` skill and be able to access `services.agent.workingDirectory`; all enabled flows, including Lark messages, use the same Agent Service selection.
- **Ticket automation**: `internal/services/ingest` orchestrates Ticket creation and a persistent Dispatch trigger. Triage runs as a Dispatch action; `internal/services/ticket` must not invoke an agent runtime or store triage results.

## Unit Tests

- **Frameworks**: use standard `testing` plus `testify/require`.
- **Mocks**: prefer `go.uber.org/mock` generated mocks. Do not start by writing manual mock classes when an interface can be generated.
- **Parallelism**: call `t.Parallel()` where tests do not mutate shared process state, environment variables, global loggers, ports, or filesystem paths.
- **Module root**: run service tests from `services/ticket-manager`.
- **Mock generation**: use `make generate GEN_DIR=...` for packages that have `go:generate` targets. Run generation from the module root or use module-relative destinations; avoid paths that duplicate `services/ticket-manager`.
- **Repository tests**: use generated `TicketService` mocks for service/handler unit tests. Use real repository tests only when validating GORM persistence behavior.

## Developer Workflow

Run commands from `services/ticket-manager` unless noted otherwise.

- Build: `make build`
- Test: `make test`
- Lint: `make lint`
- Generate SDK Jira mock from `packages/sdk-go`: `GOBIN=$(go env GOPATH)/bin GO_MODULE=github.com/tidbcloud/aisupport/packages/sdk-go go generate ./infra/jira/client`
- Workspace timeline e2e dry run: `make e2e-dry-run`
- Workspace timeline e2e image-backed test: `make e2e-workspace-timeline`
- Workspace timeline e2e local `go run` mode: `make e2e-workspace-timeline-local`
- Workspace timeline e2e cleanup: `make e2e-cleanup`

## Database and Migrations

- Migration setup lives in `migrate/migrate.go` and uses the shared SDK migrator with the `ticket_manager_migrations` table.
- Table models are organized under `models/...` by domain. Keep schema changes in those model packages and wire migration behavior through the local migrate package.
- When changing schema/model definitions, run focused tests for the model and migrate packages, then the affected `internal/services/ticket/repo` tests.

## Adding Features

### Creating a New HTTP Endpoint

1. Define shared request/response DTOs in `packages/sdk-go/contracts/api/ticket-manager/...` if the shape is part of the service contract.
2. Define or reuse shared entities in `packages/sdk-go/contracts/entity/ticket-manager/...` when the response carries domain data.
3. Add the handler method in the relevant `internal/app/handlers/<domain>` package.
4. Register the route under `app.APIRoutePath`.
5. Add focused handler tests using generated mocks for service or repository dependencies.

### Creating a New Service

1. Define the interface in `internal/services/<domain>/api`.
2. Implement the service in `internal/services/<domain>`.
3. Use `dig.In`/`dig.Out` only where needed for clear DI wiring.
4. Register the constructor in `internal/deps/deps.go`.
5. Generate mocks for interfaces used by tests and add focused service tests.

### Creating New Repository Behavior

1. Put repositories under the owning service package, for example `internal/services/<domain>/repo`. Do not place new service-specific repository behavior in a cross-service or shared SDK package.
2. Keep repository interfaces narrow and expose upper-layer operations through the corresponding service API in `internal/services/<domain>/api`.
3. Repository implementations should own persistence mapping: convert shared SDK entities from `packages/sdk-go/contracts/entity/ticket-manager/...` to local GORM models in `services/ticket-manager/models/...` before writes, and convert models back to entities before returning data.
4. Keep conversion helpers close to the repository implementation and update them whenever entity or model fields change.
5. Add or maintain `go:generate` targets for generated mocks, then regenerate from the module root with `make generate GEN_DIR=...`.
6. Add repository tests for persistence behavior and handler/service tests against generated service mocks.

### Changing Schema or Models

1. Update the relevant model package under `models/...`.
2. Wire any migration behavior through `migrate/migrate.go`.
3. Update repository mapping logic if entity/model conversion changes.
4. Run focused model, migrate, and repository tests before broader `make test`.
