add-integration
GitHub指导如何添加新的第三方集成(如Jira/Linear),涵盖后端服务结构、健康轮询、前端钩子及UI组件复用,支持Mock测试与E2E验证。
Trigger Scenarios
Install
npx skills add kdlbs/kandev --skill add-integration -g -y
SKILL.md
Frontmatter
{
"name": "add-integration",
"description": "Add a new third-party integration (Jira\/Linear-style) — per-workspace credentials, 90s auth-health poller, settings page, link\/import buttons. Use when scaffolding a new external service integration."
}
Adding a new third-party integration
Planner Entry
Load /spec-driven-development for a substantial integration. The primary
session plans, investigates existing patterns, and may directly implement a
small localized slice. Delegate only independent backend, frontend, test, or
documentation packets whose isolation clearly helps; workers do not spawn.
Jira and Linear are the model: per-workspace credentials, a 90s auth-health poller, a settings page with status banner + reconnect CTA, link/import buttons that gate on availability. New integrations should reuse the shared shapes rather than copying either.
Backend (apps/backend/internal/<name>/)
- Mirror the package layout:
service.go,store.go,client.go,provider.go,handlers.go,models.go,poller.go. ExposeProvide(writer, reader *sqlx.DB, secrets SecretStore, eventBus bus.EventBus, log *logger.Logger) (*Service, func() error, error). PassnilforeventBuswhen the integration doesn't publish events; both Jira and Linear take and use it for issue-watch publishing. - Use
internal/integrations/secretadapterinstead of writing your own upsert wrapper aroundsecrets.SecretStore. The adapter satisfies any per-integrationSecretStoreinterface shaped as{Reveal, Set, Delete, Exists}. - Use
internal/integrations/healthpollfor the auth-health loop. Implement theProberinterface (ListConfiguredWorkspaces+RecordAuthHealth) on a small adapter and lethealthpoll.New("name", prober, log)own Start/Stop/ticker. Keep integration-specific loops (JQL polling, webhook reconciliation, etc.) separate, like jira's issue-watch loop. - Wire the service via a per-domain
init<Name>Service(...)helper incmd/kandev/services.go, not inline inprovideServices. - Ship a
mock_client.go+mock_controller.gonext to the real client.Providebranches onKANDEV_MOCK_<NAME>=trueand returns the in-memory client;RegisterMockRoutes(router, svc, log)mounts/api/v1/<name>/mock/*only when the service was built with the mock. The e2e backend fixture sets the env var so Playwright tests drive the mock viaapiClient.mock<Name>*()helpers — see jira/linear for the layout.
Frontend
- Hooks live under
hooks/domains/<name>/, notcomponents/<name>/. - Use
hooks/domains/integrations/use-integration-availability.tsanduse-integration-enabled.ts— each integration'suseXAvailable/useXEnabledshould be a one-line wrapper passing the storage key + sync event + config-fetch function. - Settings page reuses
<IntegrationAuthStatusBanner>(components/integrations/auth-status-banner.tsx). - "Auth required / reconnect" UI reuses
<IntegrationAuthErrorMessage>(components/integrations/auth-error-message.tsx) — supply the integration's display name, regex check, and reconnect href. - Link / import popovers reuse
<ValidatedPopover>(components/integrations/validated-popover.tsx) — supply the icon, label, key regex, fetch function, and success callback.
Where Jira and Linear deliberately diverge
- Issue model: Jira uses transitions + JQL; Linear uses state IDs + structured filters. Don't merge these schemas — the upstream APIs are genuinely different.
- Watch filter persistence: Jira stores the JQL string verbatim; Linear stores the structured
SearchFilteras JSON infilter_json(Linear has no JQL equivalent). The orchestrator emitsNewJiraIssueEvent/NewLinearIssueEventrespectively and dedups by issue key (Jira) vs identifier (Linear). - Health column extras: Linear's
linear_configsrow carries anorg_slugcaptured from successful probes; Jira's row does not.
Version History
- b4239d8 Current 2026-07-24 17:31


