Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"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(npm install:*)",
Comment thread
WilliamBergamin marked this conversation as resolved.
"Bash(npm test:*)",
"Bash(tree:*)",
"WebFetch(domain:docs.slack.dev)",
"WebFetch(domain:github.com)",
"WebFetch(domain:npmjs.com)",
"WebFetch(domain:raw.githubusercontent.com)"
]
}
}
332 changes: 332 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
# 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`

### `scripts/code_generator.rb`

The Ruby script that does the actual generation:

- Iterates over JSON files in `tmp/java-slack-sdk/json-logs/samples/api/`
- Converts method names to PascalCase (e.g., `chat.postMessage` → `ChatPostMessage`)
- Runs quicktype with `--just-types --all-properties-optional` to generate TypeScript interfaces
- Prepends a "DO NOT EDIT" banner and `import type { WebAPICallResult } from '../../WebClient'`
- Converts the root interface to a type alias intersected with `WebAPICallResult`
- Regenerates `packages/web-api/src/types/response/index.ts` with all exports
- Special handling: `admin.analytics.getFile` exports multiple types (`AdminAnalyticsGetFileResponse`, `AdminAnalyticsMemberDetails`, `AdminAnalyticsPublicChannelDetails`, `AdminAnalyticsPublicChannelMetadataDetails`)

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.

🪓 suggestion(non-blocking): I'm unsure if this is covered alright earlier, but I'd want to avoid adding too much about this script unless it's needed!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree 💯 I also saw this and hesitated, axing it now 🪓

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