diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 000000000..3a2f7f6a1 --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1,4 @@ +CLAUDE.local.md +settings.local.json +worktrees/ +plans/ diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 000000000..dba71e970 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +@../AGENTS.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..a05e5d4f0 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,37 @@ +{ + "permissions": { + "allow": [ + "Bash(echo:*)", + "Bash(gh issue view:*)", + "Bash(gh label list:*)", + "Bash(gh pr checks:*)", + "Bash(gh pr diff:*)", + "Bash(gh pr list:*)", + "Bash(gh pr status:*)", + "Bash(gh pr update-branch:*)", + "Bash(gh pr view:*)", + "Bash(gh search code:*)", + "Bash(git diff:*)", + "Bash(git grep:*)", + "Bash(git log:*)", + "Bash(git show:*)", + "Bash(git status:*)", + "Bash(grep:*)", + "Bash(ls:*)", + "Bash(node --version:*)", + "Bash(npm --version:*)", + "Bash(npm config:*)", + "Bash(npm install)", + "Bash(npm install:*)", + "Bash(npm run build:*)", + "Bash(npm run lint:*)", + "Bash(npm run lint:fix:*)", + "Bash(npm test:*)", + "Bash(tree:*)", + "WebFetch(domain:docs.slack.dev)", + "WebFetch(domain:github.com)", + "WebFetch(domain:npmjs.com)", + "WebFetch(domain:raw.githubusercontent.com)" + ] + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..9ce654995 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,320 @@ +# AGENTS.md — node-slack-sdk + +Instructions for AI coding agents working on this repository. + +## Project Overview + +- **Repository**: https://github.com/slackapi/node-slack-sdk +- **Documentation**: https://docs.slack.dev/tools/node-slack-sdk/ +- **Monorepo** managed with npm workspaces, TypeScript-first +- Publishes multiple `@slack/*` packages to npm + +## Package Architecture + +| Package | Folder | Description | +|---------|--------|-------------| +| [`@slack/web-api`](https://www.npmjs.com/package/@slack/web-api) | `packages/web-api` | Core Web API client (most complex package) | +| [`@slack/types`](https://www.npmjs.com/package/@slack/types) | `packages/types` | Shared Slack platform TypeScript types (Block Kit, events, etc.) | +| [`@slack/logger`](https://www.npmjs.com/package/@slack/logger) | `packages/logger` | Logging utility used by other packages | +| [`@slack/webhook`](https://www.npmjs.com/package/@slack/webhook) | `packages/webhook` | Incoming webhook client | +| [`@slack/oauth`](https://www.npmjs.com/package/@slack/oauth) | `packages/oauth` | OAuth flow handling and token management | +| [`@slack/socket-mode`](https://www.npmjs.com/package/@slack/socket-mode) | `packages/socket-mode` | WebSocket-based real-time event client | +| [`@slack/cli-hooks`](https://www.npmjs.com/package/@slack/cli-hooks) | `packages/cli-hooks` | CLI integration hooks for Slack CLI | + +Additional internal packages: `cli-test` (test utilities), `rtm-api` (legacy RTM client). + +### Dependency Graph (Build Order) + +```txt +Independent (no internal deps): cli-hooks, cli-test +Base: logger, types +Depends on base: web-api, webhook +Depends on web-api: oauth, rtm-api, socket-mode +``` + +Build packages in this order — a package can only be built after its dependencies. + +## Build System + +```bash +# Install all dependencies from the repo root +npm install + +# Lint all packages (Biome) +npm run lint +npm run lint:fix + +# Build a specific package +npm run build --workspace=packages/web-api + +# Test a specific package +npm test --workspace=packages/web-api +``` + +### Full Build Order (from CI) + +```bash +# 1. No internal dependencies +npm run build --workspace=@slack/cli-hooks +npm run build --workspace=@slack/cli-test + +# 2. Base dependencies +npm run build --workspace=@slack/logger +npm run build --workspace=@slack/types + +# 3. Packages requiring base dependencies +npm run build --workspace=@slack/web-api +npm run build --workspace=@slack/webhook + +# 4. Packages depending on web-api +npm run build --workspace=@slack/oauth +npm run build --workspace=@slack/rtm-api +npm run build --workspace=@slack/socket-mode +``` + +## Critical Rules + +1. **Never manually edit `packages/web-api/src/types/response/*.ts`** — these files are auto-generated by `scripts/generate-web-api-types.sh`. They contain a "DO NOT EDIT" banner. +2. **Request types ARE manually maintained** — `packages/web-api/src/types/request/` is hand-written code; edit these responsibly. +3. **Build packages in dependency order** — see the dependency graph above. +4. **Use Biome**, not ESLint or Prettier — config is in `biome.json` at repo root. +5. **TypeScript 5.9.3**, Node 18+. + +## Code Conventions + +- **Formatting**: configured in `biome.json` +- **Test files**: `*.spec.ts` using Mocha + chai + sinon; coverage via c8 +- **Type tests**: `*.test-d.ts` using tsd +- **Naming conventions for request/response types**: + - Request: `{Namespace}{Action}Arguments` (e.g., `ChatPostMessageArguments`) + - Response: `{Namespace}{Action}Response` (e.g., `ChatPostMessageResponse`) +- **Method names**: camelCase matching the Slack API method (e.g., `chat.postMessage` → `postMessage`) + +## Adding a New Slack API Method + +This is the most common contribution. Follow these steps in order. + +### Step 1: Look Up the API Method Documentation + +Reference: `https://docs.slack.dev/reference/methods/{method.name}` + +For example, for `chat.appendStream`: https://docs.slack.dev/reference/methods/chat.appendStream + +### Step 2: Generate Response Types + +Run the generation script from the repo root: + +```bash +bash scripts/generate-web-api-types.sh +``` + +This script: + +1. Clones/updates `slackapi/java-slack-sdk` into `tmp/java-slack-sdk` +2. Reads JSON response samples from `tmp/java-slack-sdk/json-logs/samples/api/` +3. Runs `scripts/code_generator.rb` which uses quicktype to generate TypeScript types +4. Outputs `*Response.ts` files to `packages/web-api/src/types/response/` +5. Regenerates `packages/web-api/src/types/response/index.ts` +6. Runs `npm run lint:fix` on the generated files + +**Prerequisites**: Ruby and npm must be installed. + +Generated response types look like this (example: `ChatAppendStreamResponse.ts`): + +```typescript +import type { WebAPICallResult } from '../../WebClient'; +export type ChatAppendStreamResponse = WebAPICallResult & { + channel?: string; + error?: string; + needed?: string; + ok?: boolean; + provided?: string; + ts?: string; +}; +``` + +All generated files extend `WebAPICallResult` and have all properties optional. + +**Note**: If the JSON sample doesn't exist yet in `java-slack-sdk`, the API method has not been added to that project yet. The maintainers need to add it there first before it can be added here. + +### Step 3: Add Request Argument Types + +File: `packages/web-api/src/types/request/.ts` + +Create an interface/type for the method's arguments. Reuse mixins from `packages/web-api/src/types/request/common.ts`: + +| Mixin | Purpose | +|-------|---------| +| `TokenOverridable` | Optional `token` override | +| `CursorPaginationEnabled` | `cursor` + `limit` pagination | +| `TimelinePaginationEnabled` | `oldest` + `latest` + `inclusive` pagination | +| `TraditionalPagingEnabled` | `count` + `page` pagination | +| `OptionalTeamAssignable` | Optional `team_id` | +| `LocaleAware` | Optional `include_locale` | + +Also reuse namespace-specific mixins from the same file (e.g., `Channel`, `ChannelAndTS`, `AsUser` in `chat.ts`). + +**Example** — `ChatAppendStreamArguments` from `packages/web-api/src/types/request/chat.ts`: + +```typescript +export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, Partial { + /** + * @description An array of chunk objects to append to the stream. + * Either `markdown_text` or `chunks` is required. + */ + chunks?: AnyChunk[]; +} +``` + +### Step 4: Export Request Types + +File: `packages/web-api/src/types/request/index.ts` + +Add the new type to the appropriate export block: + +```typescript +export type { + ChatAppendStreamArguments, + ChatStartStreamArguments, + ChatStopStreamArguments, + // ... existing exports +} from './chat'; +``` + +### Step 5: Add Method Binding + +File: `packages/web-api/src/methods.ts` + +1. Import the argument and response types at the top of the file. +2. Add a method binding in the appropriate namespace object of the `Methods` class. + +**Import pattern**: + +```typescript +// Request types are imported from the request index barrel +import type { + ChatAppendStreamArguments, + // ... +} from './types/request'; + +// Response types are imported from individual files +import type { ChatAppendStreamResponse } from './types/response/ChatAppendStreamResponse'; +``` + +**Note**: Request types are imported from the barrel `./types/request` (already grouped by namespace), but response types are imported from their individual files (e.g., `./types/response/ChatAppendStreamResponse`). + +**Binding pattern** — within the appropriate namespace object in the `Methods` class: + +```typescript +public readonly chat = { + /** + * @description Appends text to an existing streaming conversation. + * @see {@link https://docs.slack.dev/reference/methods/chat.appendStream `chat.appendStream` API reference}. + */ + appendStream: bindApiCall(this, 'chat.appendStream'), + // ... +}; +``` + +**`bindApiCall` vs `bindApiCallWithOptionalArgument`**: + +- `bindApiCall` — for methods with **required** arguments (most methods) +- `bindApiCallWithOptionalArgument` — for methods where **all arguments are optional** + +### Step 6: Add Type Tests + +File: `packages/web-api/test/types/methods/.test-d.ts` + +Add both sad path (should error) and happy path (should compile) tests: + +```typescript +import { expectAssignable, expectError } from 'tsd'; +import { WebClient } from '../../../src/WebClient'; + +const web = new WebClient('TOKEN'); + +// chat.appendStream +// -- sad path +expectError(web.chat.appendStream()); // lacking argument +expectError(web.chat.appendStream({})); // empty argument +expectError( + web.chat.appendStream({ + channel: 'C1234', // missing ts and markdown_text + }), +); + +// -- happy path +expectAssignable>([ + { + channel: 'C1234', + ts: '1234.56', + markdown_text: 'hello', + }, +]); +``` + +### Summary Checklist + +- [ ] Looked up method docs at `https://docs.slack.dev/reference/methods/{method.name}` +- [ ] Generated response types via `scripts/generate-web-api-types.sh` (or created manually if JSON sample unavailable) +- [ ] Added request argument type in `packages/web-api/src/types/request/.ts` +- [ ] Exported new request type from `packages/web-api/src/types/request/index.ts` +- [ ] Imported argument + response types in `packages/web-api/src/methods.ts` +- [ ] Added method binding with `bindApiCall` in appropriate namespace in `packages/web-api/src/methods.ts` +- [ ] Added sad/happy path type tests in `packages/web-api/test/types/methods/.test-d.ts` +- [ ] Verified: `npm test --workspace=packages/web-api` + +## Code Generation Details + +### `scripts/generate-web-api-types.sh` + +Entry point for response type generation. It: + +1. Clones or pulls `slackapi/java-slack-sdk` into `tmp/` +2. Runs `npm install` in `scripts/` to install quicktype +3. Executes `scripts/code_generator.rb` +4. Runs `npm run lint:fix` on `packages/web-api` + +## Testing + +- **Unit tests**: Mocha + chai + sinon (`*.spec.ts` files), coverage via c8 +- **Type tests**: tsd (`*.test-d.ts` files in `packages/web-api/test/types/`) +- **Integration tests**: CommonJS, ESM, and TypeScript compatibility checks +- **CI matrix**: Node 18.x, 20.x, 22.x on Ubuntu + Windows + +Per-package test commands: + +```bash +npm test --workspace=packages/web-api # all tests (types + integration + unit) +npm run test:unit --workspace=packages/web-api +npm run test:types --workspace=packages/web-api +``` + +## Web API Client Architecture + +`packages/web-api/src/WebClient.ts`: + +- Extends the `Methods` class (which defines all API method bindings in `methods.ts`) +- Uses Axios for HTTP requests +- `p-queue` for request concurrency control +- `p-retry` for automatic retries with backoff +- Built-in cursor pagination via `paginate()` +- Streaming support via `chatStream()` using `ChatStreamer` +- Rate limiting with emitted events + +## Common Pitfalls + +- **Don't edit generated response types** — use `scripts/generate-web-api-types.sh` instead. Look for the "DO NOT EDIT" banner. +- **Build in dependency order** — building `web-api` before `logger` and `types` will fail. +- **Use Biome, not ESLint/Prettier** — this repo uses Biome exclusively (`biome.json`). +- **Biome overrides exist for generated code** — `packages/web-api/src/types/response/**/*.ts` has relaxed rules (`noBannedTypes: off`, `noExplicitAny: off`). +- **Import organization is disabled** for `packages/web-api/src/index.ts` in Biome config. +- **Response type imports use individual files**, not the barrel — e.g., `import type { ChatPostMessageResponse } from './types/response/ChatPostMessageResponse'`. + +## Development Philosophy + +- **Follow existing patterns exactly** — when adding a new method, match the style of adjacent methods. +- **Reuse mixins** from `common.ts` and namespace-specific files rather than duplicating field definitions. +- **Every API method needs four things**: request type, response type, method binding, and type tests. +- **Naming conventions**: PascalCase for types (`ChatPostMessageArguments`), camelCase for methods (`postMessage`). +- **JSDoc on method bindings**: Always include `@description` and `@see` with a link to the API reference.