Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .claude/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CLAUDE.local.md
settings.local.json
worktrees/
plans/
1 change: 1 addition & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@../AGENTS.md
37 changes: 37 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -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:*)",
Comment thread
WilliamBergamin marked this conversation as resolved.
"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)"
]
}
}
320 changes: 320 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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/<namespace>.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`).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📣 note: This is a good practice to continue for now, but I'd be so curious in "flattening" these arguments for each method...


**Example** — `ChatAppendStreamArguments` from `packages/web-api/src/types/request/chat.ts`:

```typescript
export interface ChatAppendStreamArguments extends TokenOverridable, ChannelAndTS, Partial<MarkdownText> {
/**
* @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<ChatAppendStreamArguments, ChatAppendStreamResponse>(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/<namespace>.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<Parameters<typeof web.chat.appendStream>>([
{
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/<namespace>.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/<namespace>.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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌟 praise: These are nice callouts! I might have need this sometimes before...


- **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.