{"owner":"chenhg5","repo":"cc-connect","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","AGENTS.md"],"skills":{"CLAUDE.md":"# CC-Connect Development Guide\n\n## Project Overview\n\nCC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│                   cmd/cc-connect                │  ← entry point, CLI, daemon\n├─────────────────────────────────────────────────┤\n│                     config/                     │  ← TOML config parsing\n├─────────────────────────────────────────────────┤\n│                      core/                      │  ← engine, interfaces, i18n,\n│                                                 │     cards, sessions, registry\n├──────────────────────┬──────────────────────────┤\n│     agent/           │      platform/           │\n│  ├── claudecode/     │  ├── feishu/             │\n│  ├── codex/          │  ├── telegram/           │\n│  ├── cursor/         │  ├── discord/            │\n│  ├── gemini/         │  ├── slack/              │\n│  ├── iflow/          │  ├── dingtalk/           │\n│  ├── opencode/       │  ├── wecom/              │\n│  ├── acp/            │  ├── qq/                 │\n│  └── qoder/          │  ├── qqbot/              │\n│                      │  ├── line/               │\n│                      │  └── weibo/              │\n├──────────────────────┴──────────────────────────┤\n│                     daemon/                     │  ← systemd/launchd service\n└─────────────────────────────────────────────────┘\n```\n\n### Key Design Principles\n\n**`core/` is the nucleus.** It defines all interfaces (`Platform`, `Agent`, `AgentSession`, etc.) and contains the `Engine` that orchestrates message flow. The core package must **never** import from `agent/` or `platform/`.\n\n**Plugin architecture via registries.** Agents and platforms register themselves through `core.RegisterAgent()` and `core.RegisterPlatform()` in their `init()` functions. The engine creates instances via `core.CreateAgent()` / `core.CreatePlatform()` using string names from config.\n\n**Dependency direction:**\n```\ncmd/ → config/, core/, agent/*, platform/*\nagent/*   → core/   (never other agents or platforms)\nplatform/* → core/  (never other platforms or agents)\ncore/     → stdlib only (never agent/ or platform/)\n```\n\n### Core Interfaces\n\n- **`Platform`** — messaging platform adapter (Start, Reply, Send, Stop)\n- **`Agent`** — AI coding agent adapter (StartSession, ListSessions, Stop)\n- **`AgentSession`** — a running bidirectional session (Send, RespondPermission, Events)\n- **`Engine`** — the central orchestrator that routes messages between platforms and agents\n\nOptional capability interfaces (implement only when needed):\n- `CardSender` — rich card messages\n- `InlineButtonSender` — inline keyboard buttons\n- `ProviderSwitcher` — multi-model switching\n- `DoctorChecker` — agent-specific health checks\n- `AgentDoctorInfo` — CLI binary metadata for diagnostics\n\n## Development Rules\n\n### 1. No Hardcoding Platform or Agent Names in Core\n\nThe `core/` package must remain agnostic. Never write `if p.Name() == \"feishu\"` or `CreateAgent(\"claudecode\", ...)` in core. Use interfaces and capability checks instead:\n\n```go\n// BAD — hardcodes platform knowledge in core\nif p.Name() == \"feishu\" && supportsCards(p) {\n\n// GOOD — capability-based check\nif supportsCards(p) {\n```\n\n```go\n// BAD — hardcodes agent type\nagent, _ := CreateAgent(\"claudecode\", opts)\n\n// GOOD — derives from current agent\nagent, _ := CreateAgent(e.agent.Name(), opts)\n```\n\n### 2. Prefer Interfaces Over Type Switches\n\nWhen behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:\n\n```go\n// In core/\ntype AgentDoctorInfo interface {\n    CLIBinaryName() string\n    CLIDisplayName() string\n}\n\n// In agent/claudecode/\nfunc (a *Agent) CLIBinaryName() string  { return \"claude\" }\nfunc (a *Agent) CLIDisplayName() string { return \"Claude\" }\n\n// In core/ — query via interface, fallback gracefully\nif info, ok := agent.(AgentDoctorInfo); ok {\n    bin = info.CLIBinaryName()\n}\n```\n\n### 3. Configuration Over Code\n\n- Features that may vary per deployment should be configurable in `config.toml`\n- Use `map[string]any` options for agent/platform factories to stay flexible\n- Add new config fields with sensible defaults so existing configs don't break\n\n### 4. High Cohesion, Low Coupling\n\n- Each `agent/X/` package is self-contained: it handles process lifecycle, output parsing, and session management for agent X\n- Each `platform/X/` package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X\n- Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in `core/`\n\n### 5. Error Handling\n\n- Always wrap errors with context: `fmt.Errorf(\"feishu: reply card: %w\", err)`\n- Never silently swallow errors; at minimum log them with `slog.Error` / `slog.Warn`\n- Use `slog` (structured logging) consistently; never `log.Printf` or `fmt.Printf` for runtime logs\n- Redact tokens/secrets in error messages using `core.RedactToken()`\n\n### 6. Concurrency Safety\n\n- Agent sessions are accessed from multiple goroutines; protect shared state with `sync.Mutex` or `atomic` types\n- Use `context.Context` for cancellation propagation\n- Channels should have clear ownership; document who closes them\n- Prefer `sync.Once` for one-time teardown (`pendingPermission.resolve()`)\n\n### 7. i18n\n\nAll user-facing strings must go through `core/i18n.go`:\n- Define a `MsgKey` constant\n- Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)\n- Use `e.i18n.T(MsgKey)` or `e.i18n.Tf(MsgKey, args...)`\n\n## Code Style\n\n- Follow standard Go conventions (`gofmt`, `go vet`)\n- Use `strings.EqualFold` for case-insensitive comparisons\n- Avoid `init()` for anything other than platform/agent registration\n- Keep functions focused; extract helpers when a function exceeds ~80 lines\n- Naming: `New()` for constructors, `Get/Set` for accessors, avoid stuttering (`feishu.FeishuPlatform` → `feishu.Platform`)\n\n## Testing\n\n### Requirements\n\n- All new features must include unit tests\n- All bug fixes should include a regression test\n- Tests must pass before committing: `go test ./...`\n\n### Running Tests\n\n```bash\n# Full test suite\ngo test ./...\n\n# Specific package\ngo test ./core/ -v\n\n# Run specific test\ngo test ./core/ -run TestHandlePendingPermission -v\n\n# With race detector (CI)\ngo test -race ./...\n```\n\n### Test Patterns\n\n- Use stub types for `Platform` and `Agent` in core tests (see `core/engine_test.go`)\n- Test card rendering by inspecting the returned `*Card` struct, not JSON\n- For agent session tests, simulate event streams via channels\n\n## Selective Compilation\n\nEach agent and platform is imported via a separate `plugin_*.go` file with a\nbuild tag (e.g. `//go:build !no_feishu`). By default **all** agents and\nplatforms are compiled in.\n\n### Include only specific agents/platforms\n\n```bash\n# Only Claude Code agent + Feishu and Telegram platforms\nmake build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram\n\n# Multiple agents\nmake build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord\n```\n\n### Exclude specific agents/platforms\n\n```bash\n# Exclude some platforms you don't need\nmake build EXCLUDE=discord,dingtalk,qq,qqbot,line\n```\n\n### Direct build tag usage (without Make)\n\n```bash\ngo build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect\n```\n\nAvailable tags: `no_acp`, `no_claudecode`, `no_codex`, `no_cursor`, `no_gemini`,\n`no_iflow`, `no_opencode`, `no_qoder`, `no_feishu`, `no_telegram`,\n`no_discord`, `no_slack`, `no_dingtalk`, `no_wecom`, `no_weixin`, `no_qq`, `no_qqbot`,\n`no_line`, `no_weibo`, `no_matrix`, `no_webex`, `no_tuitui`.\n\n## Pre-Commit Checklist\n\n1. **Build passes**: `go build ./...`\n2. **Tests pass**: `go test ./...`\n3. **No new hardcoded platform/agent names in core**: grep for platform names in `core/*.go`\n4. **i18n complete**: all new user-facing strings have translations for all languages\n5. **No secrets in code**: no API keys, tokens, or credentials in source files\n\n## Adding a New Platform\n\n1. Create `platform/newplatform/newplatform.go`\n2. Implement `core.Platform` interface (and optional interfaces as needed)\n3. Register in `init()`: `core.RegisterPlatform(\"newplatform\", factory)`\n4. Create `cmd/cc-connect/plugin_platform_newplatform.go` with `//go:build !no_newplatform` tag\n5. Add `newplatform` to `ALL_PLATFORMS` in `Makefile`\n6. Add config example in `config.example.toml`\n7. Add unit tests\n\n## Adding a New Agent\n\n1. Create `agent/newagent/newagent.go`\n2. Implement `core.Agent` and `core.AgentSession` interfaces\n3. Register in `init()`: `core.RegisterAgent(\"newagent\", factory)`\n4. Create `cmd/cc-connect/plugin_agent_newagent.go` with `//go:build !no_newagent` tag\n5. Add `newagent` to `ALL_AGENTS` in `Makefile`\n6. Optionally implement `AgentDoctorInfo` for `cc-connect doctor` support\n7. Add config example in `config.example.toml`\n8. Add unit tests\n","AGENTS.md":"# CC-Connect Development Guide\n\n## Project Overview\n\nCC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│                   cmd/cc-connect                │  ← entry point, CLI, daemon\n├─────────────────────────────────────────────────┤\n│                     config/                     │  ← TOML config parsing\n├─────────────────────────────────────────────────┤\n│                      core/                      │  ← engine, interfaces, i18n,\n│                                                 │     cards, sessions, registry\n├──────────────────────┬──────────────────────────┤\n│     agent/           │      platform/           │\n│  ├── claudecode/     │  ├── feishu/             │\n│  ├── codex/          │  ├── telegram/           │\n│  ├── cursor/         │  ├── discord/            │\n│  ├── gemini/         │  ├── slack/              │\n│  ├── iflow/          │  ├── dingtalk/           │\n│  ├── opencode/       │  ├── wecom/              │\n│  ├── acp/            │  ├── qq/                 │\n│  └── qoder/          │  ├── qqbot/              │\n│                      │  ├── line/               │\n│                      │  ├── weibo/              │\n│                      │  └── cloud-web/          │\n├──────────────────────┴──────────────────────────┤\n│                     daemon/                     │  ← systemd/launchd service\n└─────────────────────────────────────────────────┘\n```\n\n### Key Design Principles\n\n**`core/` is the nucleus.** It defines all interfaces (`Platform`, `Agent`, `AgentSession`, etc.) and contains the `Engine` that orchestrates message flow. The core package must **never** import from `agent/` or `platform/`.\n\n**Plugin architecture via registries.** Agents and platforms register themselves through `core.RegisterAgent()` and `core.RegisterPlatform()` in their `init()` functions. The engine creates instances via `core.CreateAgent()` / `core.CreatePlatform()` using string names from config.\n\n**Dependency direction:**\n```\ncmd/ → config/, core/, agent/*, platform/*\nagent/*   → core/   (never other agents or platforms)\nplatform/* → core/  (never other platforms or agents)\ncore/     → stdlib only (never agent/ or platform/)\n```\n\n### Core Interfaces\n\n- **`Platform`** — messaging platform adapter (Start, Reply, Send, Stop)\n- **`Agent`** — AI coding agent adapter (StartSession, ListSessions, Stop)\n- **`AgentSession`** — a running bidirectional session (Send, RespondPermission, Events)\n- **`Engine`** — the central orchestrator that routes messages between platforms and agents\n\nOptional capability interfaces (implement only when needed):\n- `CardSender` — rich card messages\n- `InlineButtonSender` — inline keyboard buttons\n- `ProviderSwitcher` — multi-model switching\n- `DoctorChecker` — agent-specific health checks\n- `AgentDoctorInfo` — CLI binary metadata for diagnostics\n\n## Development Rules\n\n### 1. No Hardcoding Platform or Agent Names in Core\n\nThe `core/` package must remain agnostic. Never write `if p.Name() == \"feishu\"` or `CreateAgent(\"claudecode\", ...)` in core. Use interfaces and capability checks instead:\n\n```go\n// BAD — hardcodes platform knowledge in core\nif p.Name() == \"feishu\" && supportsCards(p) {\n\n// GOOD — capability-based check\nif supportsCards(p) {\n```\n\n```go\n// BAD — hardcodes agent type\nagent, _ := CreateAgent(\"claudecode\", opts)\n\n// GOOD — derives from current agent\nagent, _ := CreateAgent(e.agent.Name(), opts)\n```\n\n### 2. Prefer Interfaces Over Type Switches\n\nWhen behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:\n\n```go\n// In core/\ntype AgentDoctorInfo interface {\n    CLIBinaryName() string\n    CLIDisplayName() string\n}\n\n// In agent/claudecode/\nfunc (a *Agent) CLIBinaryName() string  { return \"claude\" }\nfunc (a *Agent) CLIDisplayName() string { return \"Claude\" }\n\n// In core/ — query via interface, fallback gracefully\nif info, ok := agent.(AgentDoctorInfo); ok {\n    bin = info.CLIBinaryName()\n}\n```\n\n### 3. Configuration Over Code\n\n- Features that may vary per deployment should be configurable in `config.toml`\n- Use `map[string]any` options for agent/platform factories to stay flexible\n- Add new config fields with sensible defaults so existing configs don't break\n\n### 4. High Cohesion, Low Coupling\n\n- Each `agent/X/` package is self-contained: it handles process lifecycle, output parsing, and session management for agent X\n- Each `platform/X/` package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X\n- Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in `core/`\n\n### 5. Error Handling\n\n- Always wrap errors with context: `fmt.Errorf(\"feishu: reply card: %w\", err)`\n- Never silently swallow errors; at minimum log them with `slog.Error` / `slog.Warn`\n- Use `slog` (structured logging) consistently; never `log.Printf` or `fmt.Printf` for runtime logs\n- Redact tokens/secrets in error messages using `core.RedactToken()`\n\n### 6. Concurrency Safety\n\n- Agent sessions are accessed from multiple goroutines; protect shared state with `sync.Mutex` or `atomic` types\n- Use `context.Context` for cancellation propagation\n- Channels should have clear ownership; document who closes them\n- Prefer `sync.Once` for one-time teardown (`pendingPermission.resolve()`)\n\n### 7. i18n\n\nAll user-facing strings must go through `core/i18n.go`:\n- Define a `MsgKey` constant\n- Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)\n- Use `e.i18n.T(MsgKey)` or `e.i18n.Tf(MsgKey, args...)`\n\n## Code Style\n\n- Follow standard Go conventions (`gofmt`, `go vet`)\n- Use `strings.EqualFold` for case-insensitive comparisons\n- Avoid `init()` for anything other than platform/agent registration\n- Keep functions focused; extract helpers when a function exceeds ~80 lines\n- Naming: `New()` for constructors, `Get/Set` for accessors, avoid stuttering (`feishu.FeishuPlatform` → `feishu.Platform`)\n\n## Testing\n\n### Requirements\n\n- All new features must include unit tests.\n- **All bug fixes MUST include a regression test in the same PR.** A bug\n  fix PR without a test that fails on the pre-fix code and passes on the\n  fixed code will not be merged. Name regression tests so the bug is\n  searchable later, e.g. `TestSwitchToAgentSession_PreservesHistory` for\n  the cmdSwitch history-loss bug.\n- Tests must pass before committing: `go test ./...`.\n- Changes that touch a Critical User Journey (CUJ) — see\n  `core/cuj_test.go` — should explicitly run `go test ./core/ -run TestCUJ`\n  before opening the PR.\n\n### Running Tests\n\n```bash\n# Full test suite\ngo test ./...\n\n# Specific package\ngo test ./core/ -v\n\n# Run specific test\ngo test ./core/ -run TestHandlePendingPermission -v\n\n# Run Critical User Journey tests (recommended for any core/engine.go or\n# core/session.go change)\ngo test ./core/ -run TestCUJ -v\n\n# With race detector (CI)\ngo test -race ./...\n```\n\n### Test Patterns\n\n- Use stub types for `Platform` and `Agent` in core tests (see `core/engine_test.go`).\n- Test card rendering by inspecting the returned `*Card` struct, not JSON.\n- For agent session tests, simulate event streams via channels.\n- **For multi-step user behavior, add a CUJ test in `core/cuj_test.go`.**\n  CUJ tests assert what a USER sees on the platform side across multiple\n  actions (e.g. \"create s1 → chat → /new s2 → /switch s1 → /history\n  must show s1's content\"). They exist because per-function unit tests\n  can all pass while a user journey is still broken — the `/switch\n  loses history` bug shipped in exactly that scenario despite full\n  unit coverage of every individual function involved.\n\n### Critical User Journeys (CUJ)\n\nA CUJ test is a USER-perspective end-to-end scenario, not a developer-\nperspective unit test. The current inventory of CUJs and their coverage\nstatus lives in:\n\n`projects/cc-connect/agents/qa-cursor/release-gate/CUJ-INVENTORY.md`\n(in the spaceship agency workspace; the registered authoritative copy).\n\nRules for adding/updating CUJ tests in `core/cuj_test.go`:\n\n1. Name: `TestCUJ_<group><id>_<short_camel_case>` (e.g. `TestCUJ_B3_SwitchPreservesHistory`).\n2. Use real `SessionManager` + real `Engine`; mock only external boundaries (`Platform` sender, `Agent` process).\n3. Drive the engine via `ReceiveMessage` — the same entrypoint platforms use, so engine/platform wiring is also covered.\n4. Assert what the USER sees via `p.getSent()`, not internal struct fields.\n5. ≥3 user actions per CUJ. A single-action assertion belongs in a unit test, not a CUJ.\n\nWhen a user-reported bug maps to an existing CUJ, add a sub-case to that\nCUJ rather than creating a new one.\n\n## Selective Compilation\n\nEach agent and platform is imported via a separate `plugin_*.go` file with a\nbuild tag (e.g. `//go:build !no_feishu`). By default **all** agents and\nplatforms are compiled in.\n\n### Include only specific agents/platforms\n\n```bash\n# Only Claude Code agent + Feishu and Telegram platforms\nmake build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram\n\n# Multiple agents\nmake build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord\n```\n\n### Exclude specific agents/platforms\n\n```bash\n# Exclude some platforms you don't need\nmake build EXCLUDE=discord,dingtalk,qq,qqbot,line\n```\n\n### Direct build tag usage (without Make)\n\n```bash\ngo build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect\n```\n\nAvailable tags: `no_acp`, `no_claudecode`, `no_codex`, `no_copilot`, `no_cursor`, `no_gemini`,\n`no_iflow`, `no_opencode`, `no_qoder`, `no_feishu`, `no_telegram`,\n`no_discord`, `no_slack`, `no_dingtalk`, `no_wecom`, `no_weixin`, `no_qq`, `no_qqbot`,\n`no_line`, `no_weibo`, `no_tuitui`.\n\n## Pre-Commit Checklist\n\n1. **Build passes**: `go build ./...`\n2. **Tests pass**: `go test ./...`\n3. **CUJ tests pass** (for any change in `core/engine.go`, `core/session.go`, `core/cron.go`, `core/timer.go`, or command handlers): `go test ./core/ -run TestCUJ`\n4. **Bug fix has a regression test**: a new test in this PR that fails on the pre-fix code and passes on the fix.\n5. **No new hardcoded platform/agent names in core**: grep for platform names in `core/*.go`.\n6. **i18n complete**: all new user-facing strings have translations for all languages.\n7. **No secrets in code**: no API keys, tokens, or credentials in source files.\n\n## Adding a New Platform\n\n1. Create `platform/newplatform/newplatform.go`\n2. Implement `core.Platform` interface (and optional interfaces as needed)\n3. Register in `init()`: `core.RegisterPlatform(\"newplatform\", factory)`\n4. Create `cmd/cc-connect/plugin_platform_newplatform.go` with `//go:build !no_newplatform` tag\n5. Add `newplatform` to `ALL_PLATFORMS` in `Makefile`\n6. Add config example in `config.example.toml`\n7. Add unit tests\n\n## Adding a New Agent\n\n1. Create `agent/newagent/newagent.go`\n2. Implement `core.Agent` and `core.AgentSession` interfaces\n3. Register in `init()`: `core.RegisterAgent(\"newagent\", factory)`\n4. Create `cmd/cc-connect/plugin_agent_newagent.go` with `//go:build !no_newagent` tag\n5. Add `newagent` to `ALL_AGENTS` in `Makefile`\n6. Optionally implement `AgentDoctorInfo` for `cc-connect doctor` support\n7. Add config example in `config.example.toml`\n8. Add unit tests\n"},"files":{"CLAUDE.md":"# CC-Connect Development Guide\n\n## Project Overview\n\nCC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│                   cmd/cc-connect                │  ← entry point, CLI, daemon\n├─────────────────────────────────────────────────┤\n│                     config/                     │  ← TOML config parsing\n├─────────────────────────────────────────────────┤\n│                      core/                      │  ← engine, interfaces, i18n,\n│                                                 │     cards, sessions, registry\n├──────────────────────┬──────────────────────────┤\n│     agent/           │      platform/           │\n│  ├── claudecode/     │  ├── feishu/             │\n│  ├── codex/          │  ├── telegram/           │\n│  ├── cursor/         │  ├── discord/            │\n│  ├── gemini/         │  ├── slack/              │\n│  ├── iflow/          │  ├── dingtalk/           │\n│  ├── opencode/       │  ├── wecom/              │\n│  ├── acp/            │  ├── qq/                 │\n│  └── qoder/          │  ├── qqbot/              │\n│                      │  ├── line/               │\n│                      │  └── weibo/              │\n├──────────────────────┴──────────────────────────┤\n│                     daemon/                     │  ← systemd/launchd service\n└─────────────────────────────────────────────────┘\n```\n\n### Key Design Principles\n\n**`core/` is the nucleus.** It defines all interfaces (`Platform`, `Agent`, `AgentSession`, etc.) and contains the `Engine` that orchestrates message flow. The core package must **never** import from `agent/` or `platform/`.\n\n**Plugin architecture via registries.** Agents and platforms register themselves through `core.RegisterAgent()` and `core.RegisterPlatform()` in their `init()` functions. The engine creates instances via `core.CreateAgent()` / `core.CreatePlatform()` using string names from config.\n\n**Dependency direction:**\n```\ncmd/ → config/, core/, agent/*, platform/*\nagent/*   → core/   (never other agents or platforms)\nplatform/* → core/  (never other platforms or agents)\ncore/     → stdlib only (never agent/ or platform/)\n```\n\n### Core Interfaces\n\n- **`Platform`** — messaging platform adapter (Start, Reply, Send, Stop)\n- **`Agent`** — AI coding agent adapter (StartSession, ListSessions, Stop)\n- **`AgentSession`** — a running bidirectional session (Send, RespondPermission, Events)\n- **`Engine`** — the central orchestrator that routes messages between platforms and agents\n\nOptional capability interfaces (implement only when needed):\n- `CardSender` — rich card messages\n- `InlineButtonSender` — inline keyboard buttons\n- `ProviderSwitcher` — multi-model switching\n- `DoctorChecker` — agent-specific health checks\n- `AgentDoctorInfo` — CLI binary metadata for diagnostics\n\n## Development Rules\n\n### 1. No Hardcoding Platform or Agent Names in Core\n\nThe `core/` package must remain agnostic. Never write `if p.Name() == \"feishu\"` or `CreateAgent(\"claudecode\", ...)` in core. Use interfaces and capability checks instead:\n\n```go\n// BAD — hardcodes platform knowledge in core\nif p.Name() == \"feishu\" && supportsCards(p) {\n\n// GOOD — capability-based check\nif supportsCards(p) {\n```\n\n```go\n// BAD — hardcodes agent type\nagent, _ := CreateAgent(\"claudecode\", opts)\n\n// GOOD — derives from current agent\nagent, _ := CreateAgent(e.agent.Name(), opts)\n```\n\n### 2. Prefer Interfaces Over Type Switches\n\nWhen behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:\n\n```go\n// In core/\ntype AgentDoctorInfo interface {\n    CLIBinaryName() string\n    CLIDisplayName() string\n}\n\n// In agent/claudecode/\nfunc (a *Agent) CLIBinaryName() string  { return \"claude\" }\nfunc (a *Agent) CLIDisplayName() string { return \"Claude\" }\n\n// In core/ — query via interface, fallback gracefully\nif info, ok := agent.(AgentDoctorInfo); ok {\n    bin = info.CLIBinaryName()\n}\n```\n\n### 3. Configuration Over Code\n\n- Features that may vary per deployment should be configurable in `config.toml`\n- Use `map[string]any` options for agent/platform factories to stay flexible\n- Add new config fields with sensible defaults so existing configs don't break\n\n### 4. High Cohesion, Low Coupling\n\n- Each `agent/X/` package is self-contained: it handles process lifecycle, output parsing, and session management for agent X\n- Each `platform/X/` package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X\n- Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in `core/`\n\n### 5. Error Handling\n\n- Always wrap errors with context: `fmt.Errorf(\"feishu: reply card: %w\", err)`\n- Never silently swallow errors; at minimum log them with `slog.Error` / `slog.Warn`\n- Use `slog` (structured logging) consistently; never `log.Printf` or `fmt.Printf` for runtime logs\n- Redact tokens/secrets in error messages using `core.RedactToken()`\n\n### 6. Concurrency Safety\n\n- Agent sessions are accessed from multiple goroutines; protect shared state with `sync.Mutex` or `atomic` types\n- Use `context.Context` for cancellation propagation\n- Channels should have clear ownership; document who closes them\n- Prefer `sync.Once` for one-time teardown (`pendingPermission.resolve()`)\n\n### 7. i18n\n\nAll user-facing strings must go through `core/i18n.go`:\n- Define a `MsgKey` constant\n- Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)\n- Use `e.i18n.T(MsgKey)` or `e.i18n.Tf(MsgKey, args...)`\n\n## Code Style\n\n- Follow standard Go conventions (`gofmt`, `go vet`)\n- Use `strings.EqualFold` for case-insensitive comparisons\n- Avoid `init()` for anything other than platform/agent registration\n- Keep functions focused; extract helpers when a function exceeds ~80 lines\n- Naming: `New()` for constructors, `Get/Set` for accessors, avoid stuttering (`feishu.FeishuPlatform` → `feishu.Platform`)\n\n## Testing\n\n### Requirements\n\n- All new features must include unit tests\n- All bug fixes should include a regression test\n- Tests must pass before committing: `go test ./...`\n\n### Running Tests\n\n```bash\n# Full test suite\ngo test ./...\n\n# Specific package\ngo test ./core/ -v\n\n# Run specific test\ngo test ./core/ -run TestHandlePendingPermission -v\n\n# With race detector (CI)\ngo test -race ./...\n```\n\n### Test Patterns\n\n- Use stub types for `Platform` and `Agent` in core tests (see `core/engine_test.go`)\n- Test card rendering by inspecting the returned `*Card` struct, not JSON\n- For agent session tests, simulate event streams via channels\n\n## Selective Compilation\n\nEach agent and platform is imported via a separate `plugin_*.go` file with a\nbuild tag (e.g. `//go:build !no_feishu`). By default **all** agents and\nplatforms are compiled in.\n\n### Include only specific agents/platforms\n\n```bash\n# Only Claude Code agent + Feishu and Telegram platforms\nmake build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram\n\n# Multiple agents\nmake build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord\n```\n\n### Exclude specific agents/platforms\n\n```bash\n# Exclude some platforms you don't need\nmake build EXCLUDE=discord,dingtalk,qq,qqbot,line\n```\n\n### Direct build tag usage (without Make)\n\n```bash\ngo build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect\n```\n\nAvailable tags: `no_acp`, `no_claudecode`, `no_codex`, `no_cursor`, `no_gemini`,\n`no_iflow`, `no_opencode`, `no_qoder`, `no_feishu`, `no_telegram`,\n`no_discord`, `no_slack`, `no_dingtalk`, `no_wecom`, `no_weixin`, `no_qq`, `no_qqbot`,\n`no_line`, `no_weibo`, `no_matrix`, `no_webex`, `no_tuitui`.\n\n## Pre-Commit Checklist\n\n1. **Build passes**: `go build ./...`\n2. **Tests pass**: `go test ./...`\n3. **No new hardcoded platform/agent names in core**: grep for platform names in `core/*.go`\n4. **i18n complete**: all new user-facing strings have translations for all languages\n5. **No secrets in code**: no API keys, tokens, or credentials in source files\n\n## Adding a New Platform\n\n1. Create `platform/newplatform/newplatform.go`\n2. Implement `core.Platform` interface (and optional interfaces as needed)\n3. Register in `init()`: `core.RegisterPlatform(\"newplatform\", factory)`\n4. Create `cmd/cc-connect/plugin_platform_newplatform.go` with `//go:build !no_newplatform` tag\n5. Add `newplatform` to `ALL_PLATFORMS` in `Makefile`\n6. Add config example in `config.example.toml`\n7. Add unit tests\n\n## Adding a New Agent\n\n1. Create `agent/newagent/newagent.go`\n2. Implement `core.Agent` and `core.AgentSession` interfaces\n3. Register in `init()`: `core.RegisterAgent(\"newagent\", factory)`\n4. Create `cmd/cc-connect/plugin_agent_newagent.go` with `//go:build !no_newagent` tag\n5. Add `newagent` to `ALL_AGENTS` in `Makefile`\n6. Optionally implement `AgentDoctorInfo` for `cc-connect doctor` support\n7. Add config example in `config.example.toml`\n8. Add unit tests\n","AGENTS.md":"# CC-Connect Development Guide\n\n## Project Overview\n\nCC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│                   cmd/cc-connect                │  ← entry point, CLI, daemon\n├─────────────────────────────────────────────────┤\n│                     config/                     │  ← TOML config parsing\n├─────────────────────────────────────────────────┤\n│                      core/                      │  ← engine, interfaces, i18n,\n│                                                 │     cards, sessions, registry\n├──────────────────────┬──────────────────────────┤\n│     agent/           │      platform/           │\n│  ├── claudecode/     │  ├── feishu/             │\n│  ├── codex/          │  ├── telegram/           │\n│  ├── cursor/         │  ├── discord/            │\n│  ├── gemini/         │  ├── slack/              │\n│  ├── iflow/          │  ├── dingtalk/           │\n│  ├── opencode/       │  ├── wecom/              │\n│  ├── acp/            │  ├── qq/                 │\n│  └── qoder/          │  ├── qqbot/              │\n│                      │  ├── line/               │\n│                      │  ├── weibo/              │\n│                      │  └── cloud-web/          │\n├──────────────────────┴──────────────────────────┤\n│                     daemon/                     │  ← systemd/launchd service\n└─────────────────────────────────────────────────┘\n```\n\n### Key Design Principles\n\n**`core/` is the nucleus.** It defines all interfaces (`Platform`, `Agent`, `AgentSession`, etc.) and contains the `Engine` that orchestrates message flow. The core package must **never** import from `agent/` or `platform/`.\n\n**Plugin architecture via registries.** Agents and platforms register themselves through `core.RegisterAgent()` and `core.RegisterPlatform()` in their `init()` functions. The engine creates instances via `core.CreateAgent()` / `core.CreatePlatform()` using string names from config.\n\n**Dependency direction:**\n```\ncmd/ → config/, core/, agent/*, platform/*\nagent/*   → core/   (never other agents or platforms)\nplatform/* → core/  (never other platforms or agents)\ncore/     → stdlib only (never agent/ or platform/)\n```\n\n### Core Interfaces\n\n- **`Platform`** — messaging platform adapter (Start, Reply, Send, Stop)\n- **`Agent`** — AI coding agent adapter (StartSession, ListSessions, Stop)\n- **`AgentSession`** — a running bidirectional session (Send, RespondPermission, Events)\n- **`Engine`** — the central orchestrator that routes messages between platforms and agents\n\nOptional capability interfaces (implement only when needed):\n- `CardSender` — rich card messages\n- `InlineButtonSender` — inline keyboard buttons\n- `ProviderSwitcher` — multi-model switching\n- `DoctorChecker` — agent-specific health checks\n- `AgentDoctorInfo` — CLI binary metadata for diagnostics\n\n## Development Rules\n\n### 1. No Hardcoding Platform or Agent Names in Core\n\nThe `core/` package must remain agnostic. Never write `if p.Name() == \"feishu\"` or `CreateAgent(\"claudecode\", ...)` in core. Use interfaces and capability checks instead:\n\n```go\n// BAD — hardcodes platform knowledge in core\nif p.Name() == \"feishu\" && supportsCards(p) {\n\n// GOOD — capability-based check\nif supportsCards(p) {\n```\n\n```go\n// BAD — hardcodes agent type\nagent, _ := CreateAgent(\"claudecode\", opts)\n\n// GOOD — derives from current agent\nagent, _ := CreateAgent(e.agent.Name(), opts)\n```\n\n### 2. Prefer Interfaces Over Type Switches\n\nWhen behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:\n\n```go\n// In core/\ntype AgentDoctorInfo interface {\n    CLIBinaryName() string\n    CLIDisplayName() string\n}\n\n// In agent/claudecode/\nfunc (a *Agent) CLIBinaryName() string  { return \"claude\" }\nfunc (a *Agent) CLIDisplayName() string { return \"Claude\" }\n\n// In core/ — query via interface, fallback gracefully\nif info, ok := agent.(AgentDoctorInfo); ok {\n    bin = info.CLIBinaryName()\n}\n```\n\n### 3. Configuration Over Code\n\n- Features that may vary per deployment should be configurable in `config.toml`\n- Use `map[string]any` options for agent/platform factories to stay flexible\n- Add new config fields with sensible defaults so existing configs don't break\n\n### 4. High Cohesion, Low Coupling\n\n- Each `agent/X/` package is self-contained: it handles process lifecycle, output parsing, and session management for agent X\n- Each `platform/X/` package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X\n- Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in `core/`\n\n### 5. Error Handling\n\n- Always wrap errors with context: `fmt.Errorf(\"feishu: reply card: %w\", err)`\n- Never silently swallow errors; at minimum log them with `slog.Error` / `slog.Warn`\n- Use `slog` (structured logging) consistently; never `log.Printf` or `fmt.Printf` for runtime logs\n- Redact tokens/secrets in error messages using `core.RedactToken()`\n\n### 6. Concurrency Safety\n\n- Agent sessions are accessed from multiple goroutines; protect shared state with `sync.Mutex` or `atomic` types\n- Use `context.Context` for cancellation propagation\n- Channels should have clear ownership; document who closes them\n- Prefer `sync.Once` for one-time teardown (`pendingPermission.resolve()`)\n\n### 7. i18n\n\nAll user-facing strings must go through `core/i18n.go`:\n- Define a `MsgKey` constant\n- Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)\n- Use `e.i18n.T(MsgKey)` or `e.i18n.Tf(MsgKey, args...)`\n\n## Code Style\n\n- Follow standard Go conventions (`gofmt`, `go vet`)\n- Use `strings.EqualFold` for case-insensitive comparisons\n- Avoid `init()` for anything other than platform/agent registration\n- Keep functions focused; extract helpers when a function exceeds ~80 lines\n- Naming: `New()` for constructors, `Get/Set` for accessors, avoid stuttering (`feishu.FeishuPlatform` → `feishu.Platform`)\n\n## Testing\n\n### Requirements\n\n- All new features must include unit tests.\n- **All bug fixes MUST include a regression test in the same PR.** A bug\n  fix PR without a test that fails on the pre-fix code and passes on the\n  fixed code will not be merged. Name regression tests so the bug is\n  searchable later, e.g. `TestSwitchToAgentSession_PreservesHistory` for\n  the cmdSwitch history-loss bug.\n- Tests must pass before committing: `go test ./...`.\n- Changes that touch a Critical User Journey (CUJ) — see\n  `core/cuj_test.go` — should explicitly run `go test ./core/ -run TestCUJ`\n  before opening the PR.\n\n### Running Tests\n\n```bash\n# Full test suite\ngo test ./...\n\n# Specific package\ngo test ./core/ -v\n\n# Run specific test\ngo test ./core/ -run TestHandlePendingPermission -v\n\n# Run Critical User Journey tests (recommended for any core/engine.go or\n# core/session.go change)\ngo test ./core/ -run TestCUJ -v\n\n# With race detector (CI)\ngo test -race ./...\n```\n\n### Test Patterns\n\n- Use stub types for `Platform` and `Agent` in core tests (see `core/engine_test.go`).\n- Test card rendering by inspecting the returned `*Card` struct, not JSON.\n- For agent session tests, simulate event streams via channels.\n- **For multi-step user behavior, add a CUJ test in `core/cuj_test.go`.**\n  CUJ tests assert what a USER sees on the platform side across multiple\n  actions (e.g. \"create s1 → chat → /new s2 → /switch s1 → /history\n  must show s1's content\"). They exist because per-function unit tests\n  can all pass while a user journey is still broken — the `/switch\n  loses history` bug shipped in exactly that scenario despite full\n  unit coverage of every individual function involved.\n\n### Critical User Journeys (CUJ)\n\nA CUJ test is a USER-perspective end-to-end scenario, not a developer-\nperspective unit test. The current inventory of CUJs and their coverage\nstatus lives in:\n\n`projects/cc-connect/agents/qa-cursor/release-gate/CUJ-INVENTORY.md`\n(in the spaceship agency workspace; the registered authoritative copy).\n\nRules for adding/updating CUJ tests in `core/cuj_test.go`:\n\n1. Name: `TestCUJ_<group><id>_<short_camel_case>` (e.g. `TestCUJ_B3_SwitchPreservesHistory`).\n2. Use real `SessionManager` + real `Engine`; mock only external boundaries (`Platform` sender, `Agent` process).\n3. Drive the engine via `ReceiveMessage` — the same entrypoint platforms use, so engine/platform wiring is also covered.\n4. Assert what the USER sees via `p.getSent()`, not internal struct fields.\n5. ≥3 user actions per CUJ. A single-action assertion belongs in a unit test, not a CUJ.\n\nWhen a user-reported bug maps to an existing CUJ, add a sub-case to that\nCUJ rather than creating a new one.\n\n## Selective Compilation\n\nEach agent and platform is imported via a separate `plugin_*.go` file with a\nbuild tag (e.g. `//go:build !no_feishu`). By default **all** agents and\nplatforms are compiled in.\n\n### Include only specific agents/platforms\n\n```bash\n# Only Claude Code agent + Feishu and Telegram platforms\nmake build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram\n\n# Multiple agents\nmake build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord\n```\n\n### Exclude specific agents/platforms\n\n```bash\n# Exclude some platforms you don't need\nmake build EXCLUDE=discord,dingtalk,qq,qqbot,line\n```\n\n### Direct build tag usage (without Make)\n\n```bash\ngo build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect\n```\n\nAvailable tags: `no_acp`, `no_claudecode`, `no_codex`, `no_copilot`, `no_cursor`, `no_gemini`,\n`no_iflow`, `no_opencode`, `no_qoder`, `no_feishu`, `no_telegram`,\n`no_discord`, `no_slack`, `no_dingtalk`, `no_wecom`, `no_weixin`, `no_qq`, `no_qqbot`,\n`no_line`, `no_weibo`, `no_tuitui`.\n\n## Pre-Commit Checklist\n\n1. **Build passes**: `go build ./...`\n2. **Tests pass**: `go test ./...`\n3. **CUJ tests pass** (for any change in `core/engine.go`, `core/session.go`, `core/cron.go`, `core/timer.go`, or command handlers): `go test ./core/ -run TestCUJ`\n4. **Bug fix has a regression test**: a new test in this PR that fails on the pre-fix code and passes on the fix.\n5. **No new hardcoded platform/agent names in core**: grep for platform names in `core/*.go`.\n6. **i18n complete**: all new user-facing strings have translations for all languages.\n7. **No secrets in code**: no API keys, tokens, or credentials in source files.\n\n## Adding a New Platform\n\n1. Create `platform/newplatform/newplatform.go`\n2. Implement `core.Platform` interface (and optional interfaces as needed)\n3. Register in `init()`: `core.RegisterPlatform(\"newplatform\", factory)`\n4. Create `cmd/cc-connect/plugin_platform_newplatform.go` with `//go:build !no_newplatform` tag\n5. Add `newplatform` to `ALL_PLATFORMS` in `Makefile`\n6. Add config example in `config.example.toml`\n7. Add unit tests\n\n## Adding a New Agent\n\n1. Create `agent/newagent/newagent.go`\n2. Implement `core.Agent` and `core.AgentSession` interfaces\n3. Register in `init()`: `core.RegisterAgent(\"newagent\", factory)`\n4. Create `cmd/cc-connect/plugin_agent_newagent.go` with `//go:build !no_newagent` tag\n5. Add `newagent` to `ALL_AGENTS` in `Makefile`\n6. Optionally implement `AgentDoctorInfo` for `cc-connect doctor` support\n7. Add config example in `config.example.toml`\n8. Add unit tests\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CC-Connect Development Guide\n\n## Project Overview\n\nCC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│                   cmd/cc-connect                │  ← entry point, CLI, daemon\n├─────────────────────────────────────────────────┤\n│                     config/                     │  ← TOML config parsing\n├─────────────────────────────────────────────────┤\n│                      core/                      │  ← engine, interfaces, i18n,\n│                                                 │     cards, sessions, registry\n├──────────────────────┬──────────────────────────┤\n│     agent/           │      platform/           │\n│  ├── claudecode/     │  ├── feishu/             │\n│  ├── codex/          │  ├── telegram/           │\n│  ├── cursor/         │  ├── discord/            │\n│  ├── gemini/         │  ├── slack/              │\n│  ├── iflow/          │  ├── dingtalk/           │\n│  ├── opencode/       │  ├── wecom/              │\n│  ├── acp/            │  ├── qq/                 │\n│  └── qoder/          │  ├── qqbot/              │\n│                      │  ├── line/               │\n│                      │  └── weibo/              │\n├──────────────────────┴──────────────────────────┤\n│                     daemon/                     │  ← systemd/launchd service\n└─────────────────────────────────────────────────┘\n```\n\n### Key Design Principles\n\n**`core/` is the nucleus.** It defines all interfaces (`Platform`, `Agent`, `AgentSession`, etc.) and contains the `Engine` that orchestrates message flow. The core package must **never** import from `agent/` or `platform/`.\n\n**Plugin architecture via registries.** Agents and platforms register themselves through `core.RegisterAgent()` and `core.RegisterPlatform()` in their `init()` functions. The engine creates instances via `core.CreateAgent()` / `core.CreatePlatform()` using string names from config.\n\n**Dependency direction:**\n```\ncmd/ → config/, core/, agent/*, platform/*\nagent/*   → core/   (never other agents or platforms)\nplatform/* → core/  (never other platforms or agents)\ncore/     → stdlib only (never agent/ or platform/)\n```\n\n### Core Interfaces\n\n- **`Platform`** — messaging platform adapter (Start, Reply, Send, Stop)\n- **`Agent`** — AI coding agent adapter (StartSession, ListSessions, Stop)\n- **`AgentSession`** — a running bidirectional session (Send, RespondPermission, Events)\n- **`Engine`** — the central orchestrator that routes messages between platforms and agents\n\nOptional capability interfaces (implement only when needed):\n- `CardSender` — rich card messages\n- `InlineButtonSender` — inline keyboard buttons\n- `ProviderSwitcher` — multi-model switching\n- `DoctorChecker` — agent-specific health checks\n- `AgentDoctorInfo` — CLI binary metadata for diagnostics\n\n## Development Rules\n\n### 1. No Hardcoding Platform or Agent Names in Core\n\nThe `core/` package must remain agnostic. Never write `if p.Name() == \"feishu\"` or `CreateAgent(\"claudecode\", ...)` in core. Use interfaces and capability checks instead:\n\n```go\n// BAD — hardcodes platform knowledge in core\nif p.Name() == \"feishu\" && supportsCards(p) {\n\n// GOOD — capability-based check\nif supportsCards(p) {\n```\n\n```go\n// BAD — hardcodes agent type\nagent, _ := CreateAgent(\"claudecode\", opts)\n\n// GOOD — derives from current agent\nagent, _ := CreateAgent(e.agent.Name(), opts)\n```\n\n### 2. Prefer Interfaces Over Type Switches\n\nWhen behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:\n\n```go\n// In core/\ntype AgentDoctorInfo interface {\n    CLIBinaryName() string\n    CLIDisplayName() string\n}\n\n// In agent/claudecode/\nfunc (a *Agent) CLIBinaryName() string  { return \"claude\" }\nfunc (a *Agent) CLIDisplayName() string { return \"Claude\" }\n\n// In core/ — query via interface, fallback gracefully\nif info, ok := agent.(AgentDoctorInfo); ok {\n    bin = info.CLIBinaryName()\n}\n```\n\n### 3. Configuration Over Code\n\n- Features that may vary per deployment should be configurable in `config.toml`\n- Use `map[string]any` options for agent/platform factories to stay flexible\n- Add new config fields with sensible defaults so existing configs don't break\n\n### 4. High Cohesion, Low Coupling\n\n- Each `agent/X/` package is self-contained: it handles process lifecycle, output parsing, and session management for agent X\n- Each `platform/X/` package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X\n- Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in `core/`\n\n### 5. Error Handling\n\n- Always wrap errors with context: `fmt.Errorf(\"feishu: reply card: %w\", err)`\n- Never silently swallow errors; at minimum log them with `slog.Error` / `slog.Warn`\n- Use `slog` (structured logging) consistently; never `log.Printf` or `fmt.Printf` for runtime logs\n- Redact tokens/secrets in error messages using `core.RedactToken()`\n\n### 6. Concurrency Safety\n\n- Agent sessions are accessed from multiple goroutines; protect shared state with `sync.Mutex` or `atomic` types\n- Use `context.Context` for cancellation propagation\n- Channels should have clear ownership; document who closes them\n- Prefer `sync.Once` for one-time teardown (`pendingPermission.resolve()`)\n\n### 7. i18n\n\nAll user-facing strings must go through `core/i18n.go`:\n- Define a `MsgKey` constant\n- Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)\n- Use `e.i18n.T(MsgKey)` or `e.i18n.Tf(MsgKey, args...)`\n\n## Code Style\n\n- Follow standard Go conventions (`gofmt`, `go vet`)\n- Use `strings.EqualFold` for case-insensitive comparisons\n- Avoid `init()` for anything other than platform/agent registration\n- Keep functions focused; extract helpers when a function exceeds ~80 lines\n- Naming: `New()` for constructors, `Get/Set` for accessors, avoid stuttering (`feishu.FeishuPlatform` → `feishu.Platform`)\n\n## Testing\n\n### Requirements\n\n- All new features must include unit tests\n- All bug fixes should include a regression test\n- Tests must pass before committing: `go test ./...`\n\n### Running Tests\n\n```bash\n# Full test suite\ngo test ./...\n\n# Specific package\ngo test ./core/ -v\n\n# Run specific test\ngo test ./core/ -run TestHandlePendingPermission -v\n\n# With race detector (CI)\ngo test -race ./...\n```\n\n### Test Patterns\n\n- Use stub types for `Platform` and `Agent` in core tests (see `core/engine_test.go`)\n- Test card rendering by inspecting the returned `*Card` struct, not JSON\n- For agent session tests, simulate event streams via channels\n\n## Selective Compilation\n\nEach agent and platform is imported via a separate `plugin_*.go` file with a\nbuild tag (e.g. `//go:build !no_feishu`). By default **all** agents and\nplatforms are compiled in.\n\n### Include only specific agents/platforms\n\n```bash\n# Only Claude Code agent + Feishu and Telegram platforms\nmake build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram\n\n# Multiple agents\nmake build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord\n```\n\n### Exclude specific agents/platforms\n\n```bash\n# Exclude some platforms you don't need\nmake build EXCLUDE=discord,dingtalk,qq,qqbot,line\n```\n\n### Direct build tag usage (without Make)\n\n```bash\ngo build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect\n```\n\nAvailable tags: `no_acp`, `no_claudecode`, `no_codex`, `no_cursor`, `no_gemini`,\n`no_iflow`, `no_opencode`, `no_qoder`, `no_feishu`, `no_telegram`,\n`no_discord`, `no_slack`, `no_dingtalk`, `no_wecom`, `no_weixin`, `no_qq`, `no_qqbot`,\n`no_line`, `no_weibo`, `no_matrix`, `no_webex`, `no_tuitui`.\n\n## Pre-Commit Checklist\n\n1. **Build passes**: `go build ./...`\n2. **Tests pass**: `go test ./...`\n3. **No new hardcoded platform/agent names in core**: grep for platform names in `core/*.go`\n4. **i18n complete**: all new user-facing strings have translations for all languages\n5. **No secrets in code**: no API keys, tokens, or credentials in source files\n\n## Adding a New Platform\n\n1. Create `platform/newplatform/newplatform.go`\n2. Implement `core.Platform` interface (and optional interfaces as needed)\n3. Register in `init()`: `core.RegisterPlatform(\"newplatform\", factory)`\n4. Create `cmd/cc-connect/plugin_platform_newplatform.go` with `//go:build !no_newplatform` tag\n5. Add `newplatform` to `ALL_PLATFORMS` in `Makefile`\n6. Add config example in `config.example.toml`\n7. Add unit tests\n\n## Adding a New Agent\n\n1. Create `agent/newagent/newagent.go`\n2. Implement `core.Agent` and `core.AgentSession` interfaces\n3. Register in `init()`: `core.RegisterAgent(\"newagent\", factory)`\n4. Create `cmd/cc-connect/plugin_agent_newagent.go` with `//go:build !no_newagent` tag\n5. Add `newagent` to `ALL_AGENTS` in `Makefile`\n6. Optionally implement `AgentDoctorInfo` for `cc-connect doctor` support\n7. Add config example in `config.example.toml`\n8. Add unit tests\n","category":"root","tokens":2278},{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# CC-Connect Development Guide\n\n## Project Overview\n\nCC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────┐\n│                   cmd/cc-connect                │  ← entry point, CLI, daemon\n├─────────────────────────────────────────────────┤\n│                     config/                     │  ← TOML config parsing\n├─────────────────────────────────────────────────┤\n│                      core/                      │  ← engine, interfaces, i18n,\n│                                                 │     cards, sessions, registry\n├──────────────────────┬──────────────────────────┤\n│     agent/           │      platform/           │\n│  ├── claudecode/     │  ├── feishu/             │\n│  ├── codex/          │  ├── telegram/           │\n│  ├── cursor/         │  ├── discord/            │\n│  ├── gemini/         │  ├── slack/              │\n│  ├── iflow/          │  ├── dingtalk/           │\n│  ├── opencode/       │  ├── wecom/              │\n│  ├── acp/            │  ├── qq/                 │\n│  └── qoder/          │  ├── qqbot/              │\n│                      │  ├── line/               │\n│                      │  ├── weibo/              │\n│                      │  └── cloud-web/          │\n├──────────────────────┴──────────────────────────┤\n│                     daemon/                     │  ← systemd/launchd service\n└─────────────────────────────────────────────────┘\n```\n\n### Key Design Principles\n\n**`core/` is the nucleus.** It defines all interfaces (`Platform`, `Agent`, `AgentSession`, etc.) and contains the `Engine` that orchestrates message flow. The core package must **never** import from `agent/` or `platform/`.\n\n**Plugin architecture via registries.** Agents and platforms register themselves through `core.RegisterAgent()` and `core.RegisterPlatform()` in their `init()` functions. The engine creates instances via `core.CreateAgent()` / `core.CreatePlatform()` using string names from config.\n\n**Dependency direction:**\n```\ncmd/ → config/, core/, agent/*, platform/*\nagent/*   → core/   (never other agents or platforms)\nplatform/* → core/  (never other platforms or agents)\ncore/     → stdlib only (never agent/ or platform/)\n```\n\n### Core Interfaces\n\n- **`Platform`** — messaging platform adapter (Start, Reply, Send, Stop)\n- **`Agent`** — AI coding agent adapter (StartSession, ListSessions, Stop)\n- **`AgentSession`** — a running bidirectional session (Send, RespondPermission, Events)\n- **`Engine`** — the central orchestrator that routes messages between platforms and agents\n\nOptional capability interfaces (implement only when needed):\n- `CardSender` — rich card messages\n- `InlineButtonSender` — inline keyboard buttons\n- `ProviderSwitcher` — multi-model switching\n- `DoctorChecker` — agent-specific health checks\n- `AgentDoctorInfo` — CLI binary metadata for diagnostics\n\n## Development Rules\n\n### 1. No Hardcoding Platform or Agent Names in Core\n\nThe `core/` package must remain agnostic. Never write `if p.Name() == \"feishu\"` or `CreateAgent(\"claudecode\", ...)` in core. Use interfaces and capability checks instead:\n\n```go\n// BAD — hardcodes platform knowledge in core\nif p.Name() == \"feishu\" && supportsCards(p) {\n\n// GOOD — capability-based check\nif supportsCards(p) {\n```\n\n```go\n// BAD — hardcodes agent type\nagent, _ := CreateAgent(\"claudecode\", opts)\n\n// GOOD — derives from current agent\nagent, _ := CreateAgent(e.agent.Name(), opts)\n```\n\n### 2. Prefer Interfaces Over Type Switches\n\nWhen behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:\n\n```go\n// In core/\ntype AgentDoctorInfo interface {\n    CLIBinaryName() string\n    CLIDisplayName() string\n}\n\n// In agent/claudecode/\nfunc (a *Agent) CLIBinaryName() string  { return \"claude\" }\nfunc (a *Agent) CLIDisplayName() string { return \"Claude\" }\n\n// In core/ — query via interface, fallback gracefully\nif info, ok := agent.(AgentDoctorInfo); ok {\n    bin = info.CLIBinaryName()\n}\n```\n\n### 3. Configuration Over Code\n\n- Features that may vary per deployment should be configurable in `config.toml`\n- Use `map[string]any` options for agent/platform factories to stay flexible\n- Add new config fields with sensible defaults so existing configs don't break\n\n### 4. High Cohesion, Low Coupling\n\n- Each `agent/X/` package is self-contained: it handles process lifecycle, output parsing, and session management for agent X\n- Each `platform/X/` package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X\n- Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in `core/`\n\n### 5. Error Handling\n\n- Always wrap errors with context: `fmt.Errorf(\"feishu: reply card: %w\", err)`\n- Never silently swallow errors; at minimum log them with `slog.Error` / `slog.Warn`\n- Use `slog` (structured logging) consistently; never `log.Printf` or `fmt.Printf` for runtime logs\n- Redact tokens/secrets in error messages using `core.RedactToken()`\n\n### 6. Concurrency Safety\n\n- Agent sessions are accessed from multiple goroutines; protect shared state with `sync.Mutex` or `atomic` types\n- Use `context.Context` for cancellation propagation\n- Channels should have clear ownership; document who closes them\n- Prefer `sync.Once` for one-time teardown (`pendingPermission.resolve()`)\n\n### 7. i18n\n\nAll user-facing strings must go through `core/i18n.go`:\n- Define a `MsgKey` constant\n- Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)\n- Use `e.i18n.T(MsgKey)` or `e.i18n.Tf(MsgKey, args...)`\n\n## Code Style\n\n- Follow standard Go conventions (`gofmt`, `go vet`)\n- Use `strings.EqualFold` for case-insensitive comparisons\n- Avoid `init()` for anything other than platform/agent registration\n- Keep functions focused; extract helpers when a function exceeds ~80 lines\n- Naming: `New()` for constructors, `Get/Set` for accessors, avoid stuttering (`feishu.FeishuPlatform` → `feishu.Platform`)\n\n## Testing\n\n### Requirements\n\n- All new features must include unit tests.\n- **All bug fixes MUST include a regression test in the same PR.** A bug\n  fix PR without a test that fails on the pre-fix code and passes on the\n  fixed code will not be merged. Name regression tests so the bug is\n  searchable later, e.g. `TestSwitchToAgentSession_PreservesHistory` for\n  the cmdSwitch history-loss bug.\n- Tests must pass before committing: `go test ./...`.\n- Changes that touch a Critical User Journey (CUJ) — see\n  `core/cuj_test.go` — should explicitly run `go test ./core/ -run TestCUJ`\n  before opening the PR.\n\n### Running Tests\n\n```bash\n# Full test suite\ngo test ./...\n\n# Specific package\ngo test ./core/ -v\n\n# Run specific test\ngo test ./core/ -run TestHandlePendingPermission -v\n\n# Run Critical User Journey tests (recommended for any core/engine.go or\n# core/session.go change)\ngo test ./core/ -run TestCUJ -v\n\n# With race detector (CI)\ngo test -race ./...\n```\n\n### Test Patterns\n\n- Use stub types for `Platform` and `Agent` in core tests (see `core/engine_test.go`).\n- Test card rendering by inspecting the returned `*Card` struct, not JSON.\n- For agent session tests, simulate event streams via channels.\n- **For multi-step user behavior, add a CUJ test in `core/cuj_test.go`.**\n  CUJ tests assert what a USER sees on the platform side across multiple\n  actions (e.g. \"create s1 → chat → /new s2 → /switch s1 → /history\n  must show s1's content\"). They exist because per-function unit tests\n  can all pass while a user journey is still broken — the `/switch\n  loses history` bug shipped in exactly that scenario despite full\n  unit coverage of every individual function involved.\n\n### Critical User Journeys (CUJ)\n\nA CUJ test is a USER-perspective end-to-end scenario, not a developer-\nperspective unit test. The current inventory of CUJs and their coverage\nstatus lives in:\n\n`projects/cc-connect/agents/qa-cursor/release-gate/CUJ-INVENTORY.md`\n(in the spaceship agency workspace; the registered authoritative copy).\n\nRules for adding/updating CUJ tests in `core/cuj_test.go`:\n\n1. Name: `TestCUJ_<group><id>_<short_camel_case>` (e.g. `TestCUJ_B3_SwitchPreservesHistory`).\n2. Use real `SessionManager` + real `Engine`; mock only external boundaries (`Platform` sender, `Agent` process).\n3. Drive the engine via `ReceiveMessage` — the same entrypoint platforms use, so engine/platform wiring is also covered.\n4. Assert what the USER sees via `p.getSent()`, not internal struct fields.\n5. ≥3 user actions per CUJ. A single-action assertion belongs in a unit test, not a CUJ.\n\nWhen a user-reported bug maps to an existing CUJ, add a sub-case to that\nCUJ rather than creating a new one.\n\n## Selective Compilation\n\nEach agent and platform is imported via a separate `plugin_*.go` file with a\nbuild tag (e.g. `//go:build !no_feishu`). By default **all** agents and\nplatforms are compiled in.\n\n### Include only specific agents/platforms\n\n```bash\n# Only Claude Code agent + Feishu and Telegram platforms\nmake build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram\n\n# Multiple agents\nmake build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord\n```\n\n### Exclude specific agents/platforms\n\n```bash\n# Exclude some platforms you don't need\nmake build EXCLUDE=discord,dingtalk,qq,qqbot,line\n```\n\n### Direct build tag usage (without Make)\n\n```bash\ngo build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect\n```\n\nAvailable tags: `no_acp`, `no_claudecode`, `no_codex`, `no_copilot`, `no_cursor`, `no_gemini`,\n`no_iflow`, `no_opencode`, `no_qoder`, `no_feishu`, `no_telegram`,\n`no_discord`, `no_slack`, `no_dingtalk`, `no_wecom`, `no_weixin`, `no_qq`, `no_qqbot`,\n`no_line`, `no_weibo`, `no_tuitui`.\n\n## Pre-Commit Checklist\n\n1. **Build passes**: `go build ./...`\n2. **Tests pass**: `go test ./...`\n3. **CUJ tests pass** (for any change in `core/engine.go`, `core/session.go`, `core/cron.go`, `core/timer.go`, or command handlers): `go test ./core/ -run TestCUJ`\n4. **Bug fix has a regression test**: a new test in this PR that fails on the pre-fix code and passes on the fix.\n5. **No new hardcoded platform/agent names in core**: grep for platform names in `core/*.go`.\n6. **i18n complete**: all new user-facing strings have translations for all languages.\n7. **No secrets in code**: no API keys, tokens, or credentials in source files.\n\n## Adding a New Platform\n\n1. Create `platform/newplatform/newplatform.go`\n2. Implement `core.Platform` interface (and optional interfaces as needed)\n3. Register in `init()`: `core.RegisterPlatform(\"newplatform\", factory)`\n4. Create `cmd/cc-connect/plugin_platform_newplatform.go` with `//go:build !no_newplatform` tag\n5. Add `newplatform` to `ALL_PLATFORMS` in `Makefile`\n6. Add config example in `config.example.toml`\n7. Add unit tests\n\n## Adding a New Agent\n\n1. Create `agent/newagent/newagent.go`\n2. Implement `core.Agent` and `core.AgentSession` interfaces\n3. Register in `init()`: `core.RegisterAgent(\"newagent\", factory)`\n4. Create `cmd/cc-connect/plugin_agent_newagent.go` with `//go:build !no_newagent` tag\n5. Add `newagent` to `ALL_AGENTS` in `Makefile`\n6. Optionally implement `AgentDoctorInfo` for `cc-connect doctor` support\n7. Add config example in `config.example.toml`\n8. Add unit tests\n","category":"root","tokens":2870}]}