diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000..87707b8 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,66 @@ +--- +name: claude +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + # Manual fallback for ad-hoc runs from the Actions tab (default branch only). + workflow_dispatch: + inputs: + prompt: + description: Instruction for Claude (free text). + required: true + default: Summarise the repository structure and call out anything unusual. + +# Least-privilege. id-token: write is required by the action even with +# OAuth-token auth — it calls the GitHub OIDC endpoint to mint a +# short-lived GitHub token regardless of how Anthropic auth is set up. +permissions: + contents: write + pull-requests: write + issues: write + id-token: write + +# Serialise per-actor-per-issue. The actor key matters: when Claude +# posts its "I'm working…" ack comment, that comment fires another +# issue_comment event from `claude[bot]`. Without partitioning by actor, +# `cancel-in-progress` would cancel Claude's own in-flight reply run. +concurrency: + group: claude-${{ github.actor }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + claude: + # Only run when @claude is explicitly mentioned, to avoid spend on unrelated activity. + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + # Empty for comment-triggered runs (auto-detect from @claude mention). + # Populated for workflow_dispatch runs from the operator's input. + prompt: ${{ github.event.inputs.prompt || '' }} + # Sign commits via the GitHub API so Claude's pushes are "Verified". + use_commit_signing: true + # Cap turns + pin model. Bump model when a newer GA Sonnet ships. + claude_args: | + --max-turns 10 + --model claude-sonnet-4-6 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a1029b0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,80 @@ +# CLAUDE.md + +Guidance for Claude (Code Action, managed Code Review, local Claude Code +sessions) when working in this repository. + +## What this repo is + +An open-source **MCP (Model Context Protocol) server** providing +comprehensive email capabilities over IMAP and SMTP. It exposes tools, +prompts, and resources to AI assistants for reading, sending, +scheduling, organising, and analysing email across multiple accounts. + +- **Language / runtime**: TypeScript (ESM), Node.js ≥ 24. +- **Package manager**: pnpm 9 (do not introduce npm or yarn). +- **Transport modes**: stdio (default), Streamable HTTP. +- **License**: LGPL-3.0-or-later. +- **Public repo**: be mindful that issue and PR comments are world-readable. + +See `README.md` for the feature list and `docs/` for deeper guides. + +## Stack and tooling + +| Concern | Tool | +|---|---| +| Format / import organisation | Biome (`pnpm format`, `pnpm format:check`) | +| Lint | ESLint (Airbnb Extended + TS strict) (`pnpm lint`) | +| Combined static checks | `pnpm check` (Biome + ESLint) | +| Type-check | `pnpm typecheck` | +| Unit tests | Vitest (`pnpm test`) | +| Integration tests | Vitest with `vitest.config.integration.ts` (`pnpm test:integration`) — uses testcontainers | +| Pre-commit hooks | lefthook | +| Versioning / changelog | cocogitto (`cog`) | +| Release | goreleaser | + +Always run `pnpm check && pnpm typecheck && pnpm test` before declaring +work done. For changes touching IMAP/SMTP behaviour, run +`pnpm test:integration` as well. + +## Conventions to follow + +- **Commits**: Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, + `test:`, `chore:`, `ci:`). cocogitto enforces this. Use `pnpm commit` + if unsure. +- **Branches**: topic branches off `develop`. PRs target `develop`. + `main` is the release line, fed by `develop` → `main` merges. +- **Files layout**: business logic in `src/services/`, MCP wiring in + `src/tools/`, `src/prompts/`, `src/resources/`. Keep them decoupled — + services must be unit-testable without mocking MCP transports. +- **Workflows**: lowercase kebab-case `name:`, explicit `permissions:` + block per workflow, prefer the shared workflows under + `codefuturist/shared-workflows` over re-implementing common steps. + +## What to do / not do + +- **Prefer editing existing files** over creating new ones. +- **Do not** hardcode credentials, API keys, OAuth client secrets, or + example email addresses with real domains in source or tests. +- **Do not** log passwords, OAuth tokens, or full message bodies at + `info` or above — they may end up in user-shared logs. +- **Do not** bump `engines.node` below 24 (existing baseline). +- **Do not** add a new transport without updating both `README.md` and + the MCP capability negotiation. +- **TypeScript**: keep `strict` on, no `any` without an explicit + comment justifying it. +- **Async correctness**: IMAP IDLE, watcher, scheduler, and rate-limiter + code is concurrency-sensitive. Don't fire-and-forget promises; await + or attach `.catch(...)`. +- **Public API**: tool names, parameter schemas, and resource URIs are + part of the MCP API surface. Renames or schema changes are breaking + changes and need a major version bump (see `cog.toml`). + +## Validation before you call it done + +1. `pnpm check` — Biome + ESLint clean. +2. `pnpm typecheck` — no type errors. +3. `pnpm test` — unit tests green. +4. `pnpm test:integration` — only if touching IMAP/SMTP, watcher, + scheduler, or transport code. +5. For Docker-affecting changes: `pnpm docker:build` succeeds. +6. For workflow changes: `actionlint` clean (`pnpm report` includes it). diff --git a/REVIEW.md b/REVIEW.md new file mode 100644 index 0000000..4e1567e --- /dev/null +++ b/REVIEW.md @@ -0,0 +1,79 @@ +# Review instructions + +Used by Anthropic's managed Code Review service +(). Inert until that +service is enabled for this repo. Read alongside `CLAUDE.md`, which is +shared context for all Claude tooling. + +## What Important (🔴) means here + +Reserve 🔴 for findings that would, if merged: + +- **Leak credentials**: hardcoded passwords, OAuth client secrets, API + keys, or app-specific passwords committed to source/tests/fixtures; + log statements at `info` or above that include passwords, OAuth + tokens, refresh tokens, or full message bodies. +- **Break MCP protocol compliance**: tool/resource/prompt schemas that + violate the MCP SDK contracts, malformed JSON-RPC responses, missing + required capability declarations. +- **Cause data loss or corruption**: destructive IMAP operations + (`EXPUNGE`, `STORE \Deleted`, move-to-Trash) without explicit + confirmation gating; SMTP send paths that bypass the audit trail or + rate limiter. +- **Introduce a silent concurrency bug**: unawaited promises in the + IDLE watcher / scheduler / rate-limiter; shared mutable state across + account connections without synchronisation; race conditions on + reconnect logic. +- **Break the public API**: tool rename, parameter schema change, or + resource URI change without a major-version bump per `cog.toml`. +- **Open a TLS hole**: any code path that disables certificate + verification, allows downgrade from STARTTLS, or accepts plaintext + auth on non-loopback connections. + +Style, naming, refactoring, and "could be DRYer" are 🟡 Nit at most. + +## Cap the nits + +Report at most **five** 🟡 Nits per review. If you found more, say +"plus N similar items" in the summary. Lead the summary with +"No blocking issues" when nothing 🔴 was found. + +## Skip these + +- Anything CI already enforces: Biome formatting, ESLint, TypeScript + errors, Vitest failures, actionlint, the shared `ci-node.yml` + workflow checks. +- Generated artefacts: `dist/`, `node_modules/`, `coverage/`, `reports/`. +- Lockfiles: `pnpm-lock.yaml`, `package-lock.json`. +- Dependabot/Renovate-only PRs: comment only on logic regressions, not + changelogs or version bumps. +- `CHANGELOG.md` — generated by cocogitto. + +## Always check + +- New tool / prompt / resource registrations have matching unit tests + under `src/**/__tests__/` or `tests/`. +- New IMAP-touching code paths have an integration test added under + `tests/integration/`. +- New environment variables and config keys are documented in + `README.md` (Install / Usage) and reflected in any `.env.example` or + config template. +- Error responses do not embed raw credentials, OAuth tokens, or + internal stack traces. +- Public exports in `src/index.ts` (or equivalent entry) have not + changed shape without a corresponding `cog.toml` major bump entry. + +## Verification bar + +Behaviour claims need a `file:line` citation in the diff or surrounding +code. Do not flag based on naming alone — open the file, confirm. + +## Re-review convergence + +After the first review on a PR, suppress new 🟡 Nits and post only 🔴 +Important findings. The author already saw the style notes once. + +## Summary shape + +Open the review body with a one-line tally: +`N important, M nits` (or `No blocking issues` when N=0).