Skip to content

Commit 72a90d2

Browse files
chore(claude): add claude code support for maintainers (#1445)
Co-authored-by: William Bergamin <wbergamin@salesforce.com> Co-authored-by: William Bergamin <wbergamin@slack-corp.com>
1 parent 5a153e6 commit 72a90d2

File tree

9 files changed

+275
-32
lines changed

9 files changed

+275
-32
lines changed

.claude/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CLAUDE.local.md
2+
settings.local.json
3+
worktrees/
4+
plans/

.claude/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@../AGENTS.md

.claude/settings.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(./scripts/build_pypi_package.sh:*)",
5+
"Bash(./scripts/format.sh:*)",
6+
"Bash(./scripts/install_all_and_run_tests.sh:*)",
7+
"Bash(./scripts/lint.sh:*)",
8+
"Bash(./scripts/run_mypy.sh:*)",
9+
"Bash(./scripts/run_tests.sh:*)",
10+
"Bash(./scripts/install.sh:*)",
11+
"Bash(echo $VIRTUAL_ENV)",
12+
"Bash(gh issue view:*)",
13+
"Bash(gh label list:*)",
14+
"Bash(gh pr checks:*)",
15+
"Bash(gh pr diff:*)",
16+
"Bash(gh pr list:*)",
17+
"Bash(gh pr status:*)",
18+
"Bash(gh pr update-branch:*)",
19+
"Bash(gh pr view:*)",
20+
"Bash(gh search code:*)",
21+
"Bash(git diff:*)",
22+
"Bash(git grep:*)",
23+
"Bash(git log:*)",
24+
"Bash(git show:*)",
25+
"Bash(git status:*)",
26+
"Bash(grep:*)",
27+
"Bash(ls:*)",
28+
"Bash(tree:*)",
29+
"WebFetch(domain:github.com)",
30+
"WebFetch(domain:docs.slack.dev)",
31+
"WebFetch(domain:raw.githubusercontent.com)"
32+
]
33+
}
34+
}

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ venv/
1717
.venv*
1818
.env/
1919

20-
# claude
21-
.claude/*.local.json
22-
2320
# codecov / coverage
2421
.coverage
2522
cov_*

AGENTS.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# AGENTS.md - bolt-python
2+
3+
## Project Overview
4+
5+
Slack Bolt for Python -- a framework for building Slack apps in Python.
6+
7+
- **Foundation:** Built on top of `slack_sdk` (see `pyproject.toml` constraints).
8+
- **Execution Models:** Supports both synchronous (`App`) and asynchronous (`AsyncApp` using `asyncio`) execution. Async mode requires `aiohttp` as an additional dependency.
9+
- **Framework Adapters:** Features built-in adapters for web frameworks (Flask, FastAPI, Django, Tornado, Pyramid, and many more) and serverless environments (AWS Lambda, Google Cloud Functions).
10+
- **Python Version:** Requires Python 3.7+ as defined in `pyproject.toml`.
11+
12+
- **Repository**: <https://github.com/slackapi/bolt-python>
13+
- **Documentation**: <https://docs.slack.dev/tools/bolt-python/>
14+
- **PyPI**: <https://pypi.org/project/slack-bolt/>
15+
- **Current version**: defined in `slack_bolt/version.py` (referenced by `pyproject.toml` via `[tool.setuptools.dynamic]`)
16+
17+
## Environment Setup
18+
19+
A python virtual environment (`venv`) should be activated before running any commands.
20+
21+
```bash
22+
# Create a venv (first time only)
23+
python -m venv .venv
24+
25+
# Activate
26+
source .venv/bin/activate
27+
28+
# Install all dependencies
29+
./scripts/install.sh
30+
```
31+
32+
You can verify the venv is active by checking `echo $VIRTUAL_ENV`. If tools like `black`, `flake8`, `mypy` or `pytest` are not found, ask the user to activate the venv.
33+
34+
## Common Commands
35+
36+
### Testing
37+
38+
Always use the project scripts instead of calling `pytest` directly:
39+
40+
```bash
41+
# Install all dependencies and run all tests (formats, lints, tests, typechecks)
42+
./scripts/install_all_and_run_tests.sh
43+
44+
# Run a single test file
45+
./scripts/run_tests.sh tests/scenario_tests/test_app.py
46+
47+
# Run a single test function
48+
./scripts/run_tests.sh tests/scenario_tests/test_app.py::TestApp::test_name
49+
```
50+
51+
### Formatting, Linting, Type Checking
52+
53+
```bash
54+
# Format (black, line-length=125)
55+
./scripts/format.sh --no-install
56+
57+
# Lint (flake8, line-length=125, ignores: F841,F821,W503,E402)
58+
./scripts/lint.sh --no-install
59+
60+
# Type check (mypy)
61+
./scripts/run_mypy.sh --no-install
62+
```
63+
64+
## Architecture
65+
66+
### Request Processing Pipeline
67+
68+
Incoming requests flow through a middleware chain before reaching listeners:
69+
70+
1. **SSL Check** -> **Request Verification** (signature) -> **URL Verification** -> **Authorization** (token injection) -> **Ignoring Self Events** -> Custom middleware
71+
2. **Listener Matching** -- `ListenerMatcher` implementations check if a listener should handle the request
72+
3. **Listener Execution** -- listener-specific middleware runs, then `ack()` is called, then the handler executes
73+
74+
For FaaS environments (`process_before_response=True`), long-running handlers execute as "lazy listeners" in a thread pool after the ack response is returned.
75+
76+
### Core Abstractions
77+
78+
- **`App` / `AsyncApp`** (`slack_bolt/app/`) -- Central class. Registers listeners via decorators (`@app.event()`, `@app.action()`, `@app.command()`, `@app.message()`, `@app.view()`, `@app.shortcut()`, `@app.options()`, `@app.function()`). Dispatches incoming requests through middleware to matching listeners.
79+
- **`Middleware`** (`slack_bolt/middleware/`) -- Abstract base with `process(req, resp, next)`. Built-in: authorization, request verification, SSL check, URL verification, assistant, self-event ignoring.
80+
- **`Listener`** (`slack_bolt/listener/`) -- Has matchers, middleware, and an ack/handler function. `CustomListener` is the main implementation.
81+
- **`ListenerMatcher`** (`slack_bolt/listener_matcher/`) -- Determines if a listener handles a given request. Built-in matchers for events, actions, commands, messages (regex), shortcuts, views, options, functions.
82+
- **`BoltContext`** (`slack_bolt/context/`) -- Dict-like object passed to listeners with `client`, `say()`, `ack()`, `respond()`, `complete()`, `fail()`, plus event metadata (`user_id`, `channel_id`, `team_id`, etc.).
83+
- **`BoltRequest` / `BoltResponse`** (`slack_bolt/request/`, `slack_bolt/response/`) -- Request/response wrappers. Request has `mode` of "http" or "socket_mode".
84+
85+
### Kwargs Injection
86+
87+
Listeners receive arguments by parameter name. The framework inspects function signatures and injects matching args: `body`, `event`, `action`, `command`, `payload`, `context`, `client`, `ack`, `say`, `respond`, `logger`, `complete`, `fail`, `agent`, etc. Defined in `slack_bolt/kwargs_injection/args.py`.
88+
89+
### Adapter System
90+
91+
Each adapter in `slack_bolt/adapter/` converts between a web framework's request/response types and `BoltRequest`/`BoltResponse`. Adapters exist for: Flask, FastAPI, Django, Starlette, Sanic, Bottle, Tornado, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, Socket Mode, WSGI, ASGI, and more.
92+
93+
### Sync/Async Mirroring Pattern
94+
95+
**This is the most important pattern in this codebase.** Almost every module has both a sync and async variant. When you modify one, you almost always must modify the other.
96+
97+
**File naming convention:** Async files use the `async_` prefix alongside their sync counterpart:
98+
99+
```text
100+
slack_bolt/middleware/custom_middleware.py # sync
101+
slack_bolt/middleware/async_custom_middleware.py # async
102+
103+
slack_bolt/context/say/say.py # sync
104+
slack_bolt/context/say/async_say.py # async
105+
106+
slack_bolt/listener/custom_listener.py # sync
107+
slack_bolt/listener/async_listener.py # async
108+
109+
slack_bolt/adapter/fastapi/async_handler.py # async-only (no sync FastAPI adapter)
110+
slack_bolt/adapter/flask/handler.py # sync-only (no async Flask adapter)
111+
```
112+
113+
**Which modules come in sync/async pairs:**
114+
115+
- `slack_bolt/app/` -- `app.py` / `async_app.py`
116+
- `slack_bolt/middleware/` -- every middleware has an `async_` counterpart
117+
- `slack_bolt/listener/` -- `listener.py` / `async_listener.py`, plus error/completion/start handlers
118+
- `slack_bolt/listener_matcher/` -- `builtins.py` / `async_builtins.py`
119+
- `slack_bolt/context/` -- each subdirectory (e.g., `say/`, `ack/`, `respond/`) has `async_` variants
120+
- `slack_bolt/kwargs_injection/` -- `args.py` / `async_args.py`, `utils.py` / `async_utils.py`
121+
122+
**Adapters are an exception:** Most adapters are sync-only or async-only depending on the framework. Async-native frameworks (FastAPI, Starlette, Sanic, Tornado, ASGI, Socket Mode) have `async_handler.py`. Sync-only frameworks (Flask, Django, Bottle, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, WSGI) have `handler.py`.
123+
124+
### AI Agents & Assistants
125+
126+
`BoltAgent` (`slack_bolt/agent/`) provides `chat_stream()`, `set_status()`, and `set_suggested_prompts()` for AI-powered agents. `Assistant` middleware (`slack_bolt/middleware/assistant/`) handles assistant thread events.
127+
128+
## Key Development Patterns
129+
130+
### Adding or Modifying Middleware
131+
132+
1. Implement the sync version in `slack_bolt/middleware/` (subclass `Middleware`, implement `process()`)
133+
2. Implement the async version with `async_` prefix (subclass `AsyncMiddleware`, implement `async_process()`)
134+
3. Export built-in middleware from `slack_bolt/middleware/__init__.py` (sync) and `async_builtins.py` (async)
135+
136+
### Adding a Context Utility
137+
138+
Each context utility lives in its own subdirectory under `slack_bolt/context/`:
139+
140+
```text
141+
slack_bolt/context/my_util/
142+
__init__.py
143+
my_util.py # sync implementation
144+
async_my_util.py # async implementation
145+
internals.py # shared logic (optional)
146+
```
147+
148+
Then wire it into `BoltContext` (`slack_bolt/context/context.py`) and `AsyncBoltContext` (`slack_bolt/context/async_context.py`).
149+
150+
### Adding a New Adapter
151+
152+
1. Create `slack_bolt/adapter/<framework>/`
153+
2. Add `__init__.py` and `handler.py` (or `async_handler.py` for async frameworks)
154+
3. The handler converts the framework's request to `BoltRequest`, calls `app.dispatch()`, and converts `BoltResponse` back
155+
4. Add the framework to `requirements/adapter.txt` with version constraints
156+
5. Add adapter tests in `tests/adapter_tests/` (or `tests/adapter_tests_async/`)
157+
158+
### Adding a Kwargs-Injectable Argument
159+
160+
1. Add the new arg to `slack_bolt/kwargs_injection/args.py` and `async_args.py`
161+
2. Update the `Args` class with the new property
162+
3. Populate the arg in the appropriate context or listener setup code
163+
164+
## Dependencies
165+
166+
The core package has a **single required runtime dependency**: `slack_sdk` (defined in `pyproject.toml`). Do not add runtime dependencies.
167+
168+
**`requirements/` directory structure:**
169+
170+
- `async.txt` -- async runtime deps (`aiohttp`, `websockets`)
171+
- `adapter.txt` -- all framework adapter deps (Flask, Django, FastAPI, etc.)
172+
- `testing.txt` -- test runner deps (`pytest`, `pytest-asyncio`, includes `async.txt`)
173+
- `testing_without_asyncio.txt` -- test deps without async (`pytest`, `pytest-cov`)
174+
- `adapter_testing.txt` -- adapter-specific test deps (`moto`, `boddle`, `sanic-testing`)
175+
- `tools.txt` -- dev tools (`mypy`, `flake8`, `black`)
176+
177+
When adding a new dependency: add it to the appropriate `requirements/*.txt` file with version constraints, never to `pyproject.toml` `dependencies` (unless it's a core runtime dep, which is very rare).
178+
179+
## Test Organization
180+
181+
- `tests/scenario_tests/` -- Integration-style tests with realistic Slack payloads
182+
- `tests/slack_bolt/` -- Unit tests mirroring the source structure
183+
- `tests/adapter_tests/` and `tests/adapter_tests_async/` -- Framework adapter tests
184+
- `tests/mock_web_api_server/` -- Mock Slack API server used by tests
185+
- Async test variants use `_async` suffix directories
186+
187+
**Where to put new tests:** Mirror the source structure. For `slack_bolt/middleware/foo.py`, add tests in `tests/slack_bolt/middleware/test_foo.py`. For async variants, use the `_async` suffix directory or file naming pattern. Adapter tests go in `tests/adapter_tests/` (sync) or `tests/adapter_tests_async/` (async).
188+
189+
**Mock server:** Many tests use `tests/mock_web_api_server/` to simulate Slack API responses. Look at existing tests for usage patterns rather than making real API calls.
190+
191+
## Code Style
192+
193+
- **Black** formatter configured in `pyproject.toml` (line-length=125)
194+
- **Flake8** linter configured in `.flake8` (line-length=125, ignores: F841,F821,W503,E402)
195+
- **MyPy** configured in `pyproject.toml`
196+
- **pytest** configured in `pyproject.toml`
197+
198+
## GitHub & CI/CD
199+
200+
- `.github/` -- GitHub-specific configuration and documentation
201+
- `.github/workflows/` -- Continuous integration pipeline definitions that run on GitHub Actions
202+
- `.github/maintainers_guide.md` -- Maintainer workflows and release process

scripts/install.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
# Installs all dependencies of the project
3+
# ./scripts/install.sh
4+
5+
script_dir=`dirname $0`
6+
cd ${script_dir}/..
7+
rm -rf ./slack_bolt.egg-info
8+
9+
# Update pip to prevent warnings
10+
pip install -U pip
11+
12+
# The package causes a conflict with moto
13+
pip uninstall python-lambda
14+
15+
pip install -U -e .
16+
pip install -U -r requirements/testing.txt
17+
pip install -U -r requirements/adapter.txt
18+
pip install -U -r requirements/adapter_testing.txt
19+
pip install -U -r requirements/tools.txt
20+
21+
# To avoid errors due to the old versions of click forced by Chalice
22+
pip install -U pip click

scripts/install_all_and_run_tests.sh

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,19 @@
55

66
script_dir=`dirname $0`
77
cd ${script_dir}/..
8-
rm -rf ./slack_bolt.egg-info
98

10-
# Update pip to prevent warnings
11-
pip install -U pip
9+
test_target="${1:-tests/}"
1210

13-
# The package causes a conflict with moto
14-
pip uninstall python-lambda
11+
# keep in sync with LATEST_SUPPORTED_PY in .github/workflows/ci-build.yml
12+
LATEST_SUPPORTED_PY="3.14"
13+
current_py=$(python --version | sed -E 's/Python ([0-9]+\.[0-9]+).*/\1/')
1514

16-
test_target="$1"
15+
./scripts/install.sh
1716

18-
pip install -U -e .
19-
pip install -U -r requirements/testing.txt
20-
pip install -U -r requirements/adapter.txt
21-
pip install -U -r requirements/adapter_testing.txt
22-
pip install -U -r requirements/tools.txt
23-
# To avoid errors due to the old versions of click forced by Chalice
24-
pip install -U pip click
17+
./scripts/format.sh --no-install
18+
./scripts/lint.sh --no-install
19+
pytest $test_target
2520

26-
if [[ $test_target != "" ]]
27-
then
28-
./scripts/format.sh --no-install
29-
pytest $1
30-
else
31-
./scripts/format.sh --no-install
32-
./scripts/lint.sh --no-install
33-
pytest
21+
if [[ "$current_py" == "$LATEST_SUPPORTED_PY" ]]; then
3422
./scripts/run_mypy.sh --no-install
3523
fi

scripts/run_tests.sh

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@
66
script_dir=`dirname $0`
77
cd ${script_dir}/..
88

9-
test_target="$1"
9+
test_target="${1:-tests/}"
1010

1111
./scripts/format.sh --no-install
12-
13-
if [[ $test_target != "" ]]
14-
then
15-
pytest -vv $1
16-
else
17-
pytest
18-
fi
12+
pytest -vv $test_target

slack_bolt/warning/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33

44
class ExperimentalWarning(FutureWarning):
55
"""Warning for features that are still in experimental phase."""
6+
67
pass

0 commit comments

Comments
 (0)