Skip to content

Commit 4d15431

Browse files
chore: improve AGENTS.md (#1458)
1 parent 0fc5380 commit 4d15431

File tree

1 file changed

+100
-56
lines changed

1 file changed

+100
-56
lines changed

AGENTS.md

Lines changed: 100 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Slack Bolt for Python -- a framework for building Slack apps in Python.
1616

1717
## Environment Setup
1818

19+
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.
20+
1921
A python virtual environment (`venv`) should be activated before running any commands.
2022

2123
```bash
@@ -29,18 +31,30 @@ source .venv/bin/activate
2931
./scripts/install.sh
3032
```
3133

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-
3434
## Common Commands
3535

36-
### Testing
36+
### Pre-submission Checklist
3737

38-
Always use the project scripts instead of calling `pytest` directly:
38+
Before considering any work complete, you MUST run these commands in order and confirm they all pass:
39+
40+
```bash
41+
./scripts/format.sh --no-install # 1. Format
42+
./scripts/lint.sh --no-install # 2. Lint
43+
./scripts/run_tests.sh <relevant> # 3. Run relevant tests (see Testing below)
44+
./scripts/run_mypy.sh --no-install # 4. Type check
45+
```
46+
47+
To run everything at once (installs deps + formats + lints + tests + typechecks):
3948

4049
```bash
41-
# Install all dependencies and run all tests (formats, lints, tests, typechecks)
4250
./scripts/install_all_and_run_tests.sh
51+
```
4352

53+
### Testing
54+
55+
Always use the project scripts instead of calling `pytest` directly:
56+
57+
```bash
4458
# Run a single test file
4559
./scripts/run_tests.sh tests/scenario_tests/test_app.py
4660

@@ -51,16 +65,70 @@ Always use the project scripts instead of calling `pytest` directly:
5165
### Formatting, Linting, Type Checking
5266

5367
```bash
54-
# Format (black, line-length=125)
68+
# Format -- Black, configured in pyproject.toml
5569
./scripts/format.sh --no-install
5670

57-
# Lint (flake8, line-length=125, ignores: F841,F821,W503,E402)
71+
# Lint -- Flake8, configured in .flake8
5872
./scripts/lint.sh --no-install
5973

60-
# Type check (mypy)
74+
# Type check -- mypy, configured in pyproject.toml
6175
./scripts/run_mypy.sh --no-install
6276
```
6377

78+
## Critical Conventions
79+
80+
### Sync/Async Mirroring Rule
81+
82+
**When modifying any sync module, you MUST also update the corresponding async module (and vice versa).** This is the most important convention in this codebase.
83+
84+
Almost every module has both a sync and async variant. Async files use the `async_` prefix alongside their sync counterpart:
85+
86+
```text
87+
slack_bolt/middleware/custom_middleware.py # sync
88+
slack_bolt/middleware/async_custom_middleware.py # async
89+
90+
slack_bolt/context/say/say.py # sync
91+
slack_bolt/context/say/async_say.py # async
92+
93+
slack_bolt/listener/custom_listener.py # sync
94+
slack_bolt/listener/async_listener.py # async
95+
```
96+
97+
**Modules that come in sync/async pairs:**
98+
99+
- `slack_bolt/app/` -- `app.py` / `async_app.py`
100+
- `slack_bolt/middleware/` -- every middleware has an `async_` counterpart
101+
- `slack_bolt/listener/` -- `listener.py` / `async_listener.py`, plus error/completion/start handlers
102+
- `slack_bolt/listener_matcher/` -- `builtins.py` / `async_builtins.py`
103+
- `slack_bolt/context/` -- each subdirectory (e.g., `say/`, `ack/`, `respond/`) has `async_` variants
104+
- `slack_bolt/kwargs_injection/` -- `args.py` / `async_args.py`, `utils.py` / `async_utils.py`
105+
106+
**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`.
107+
108+
### Prefer the Middleware Pattern
109+
110+
Middleware is the project's preferred approach for cross-cutting concerns. Before adding logic to individual listeners or utility functions, consider whether it belongs as a built-in middleware in the framework.
111+
112+
**When to add built-in middleware:**
113+
114+
- Cross-cutting concerns that apply to many or all requests (logging, metrics, observability)
115+
- Request validation, transformation, or enrichment
116+
- Authorization extensions beyond the built-in `SingleTeamAuthorization`/`MultiTeamsAuthorization`
117+
- Feature-level request handling (the `Assistant` middleware in `slack_bolt/middleware/assistant/assistant.py` is the canonical example -- it intercepts assistant thread events and dispatches them to registered sub-listeners)
118+
119+
**How to add built-in middleware:**
120+
121+
1. Subclass `Middleware` (sync) and implement `process(self, *, req, resp, next)`. Call `next()` to continue the chain.
122+
2. Subclass `AsyncMiddleware` (async) and implement `async_process(self, *, req, resp, next)`. Call `await next()` to continue.
123+
3. Export from `slack_bolt/middleware/__init__.py` (sync) and `slack_bolt/middleware/async_builtins.py` (async).
124+
4. Register the middleware in `App.__init__()` (`slack_bolt/app/app.py`) and `AsyncApp.__init__()` (`slack_bolt/app/async_app.py`) where the default middleware chain is assembled.
125+
126+
**Canonical example:** `AttachingFunctionToken` (`slack_bolt/middleware/attaching_function_token/`) is a good small middleware to follow -- it has a clean sync/async pair, a focused `process()` method, and is properly exported and registered in the app's middleware chain.
127+
128+
### Single Runtime Dependency Rule
129+
130+
The core package depends ONLY on `slack_sdk` (defined in `pyproject.toml`). Never add runtime dependencies to `pyproject.toml`. Additional dependencies go in the appropriate `requirements/*.txt` file.
131+
64132
## Architecture
65133

66134
### Request Processing Pipeline
@@ -90,49 +158,12 @@ Listeners receive arguments by parameter name. The framework inspects function s
90158

91159
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.
92160

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-
124161
### AI Agents & Assistants
125162

126163
`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.
127164

128165
## Key Development Patterns
129166

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-
136167
### Adding a Context Utility
137168

138169
Each context utility lives in its own subdirectory under `slack_bolt/context/`:
@@ -153,14 +184,21 @@ Then wire it into `BoltContext` (`slack_bolt/context/context.py`) and `AsyncBolt
153184
2. Add `__init__.py` and `handler.py` (or `async_handler.py` for async frameworks)
154185
3. The handler converts the framework's request to `BoltRequest`, calls `app.dispatch()`, and converts `BoltResponse` back
155186
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/`)
187+
5. Add adapter tests in `tests/adapter_tests/` (sync) or `tests/adapter_tests_async/` (async)
157188

158189
### Adding a Kwargs-Injectable Argument
159190

160191
1. Add the new arg to `slack_bolt/kwargs_injection/args.py` and `async_args.py`
161192
2. Update the `Args` class with the new property
162193
3. Populate the arg in the appropriate context or listener setup code
163194

195+
## Security Considerations
196+
197+
- **Request Verification:** The built-in `RequestVerification` middleware validates `x-slack-signature` and `x-slack-request-timestamp` on every incoming HTTP request. Never disable this in production. It is automatically skipped for `socket_mode` requests.
198+
- **Tokens & Secrets:** `SLACK_SIGNING_SECRET` and `SLACK_BOT_TOKEN` must come from environment variables. Never hardcode or commit secrets.
199+
- **Authorization Middleware:** `SingleTeamAuthorization` and `MultiTeamsAuthorization` verify tokens and inject an authorized `WebClient` into the context. Do not bypass these.
200+
- **Tests:** Always use mock servers (`tests/mock_web_api_server/`) and dummy values. Never use real tokens in tests.
201+
164202
## Dependencies
165203

166204
The core package has a **single required runtime dependency**: `slack_sdk` (defined in `pyproject.toml`). Do not add runtime dependencies.
@@ -176,7 +214,9 @@ The core package has a **single required runtime dependency**: `slack_sdk` (defi
176214

177215
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).
178216

179-
## Test Organization
217+
## Test Organization and CI
218+
219+
### Directory Structure
180220

181221
- `tests/scenario_tests/` -- Integration-style tests with realistic Slack payloads
182222
- `tests/slack_bolt/` -- Unit tests mirroring the source structure
@@ -188,15 +228,19 @@ When adding a new dependency: add it to the appropriate `requirements/*.txt` fil
188228

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

191-
## Code Style
231+
### CI Pipeline
232+
233+
GitHub Actions (`.github/workflows/ci-build.yml`) runs on every push to `main` and every PR:
192234

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`
235+
- **Lint** -- `./scripts/lint.sh` on latest Python
236+
- **Typecheck** -- `./scripts/run_mypy.sh` on latest Python
237+
- **Unit tests** -- full test suite across Python 3.7--3.14 matrix
238+
- **Code coverage** -- uploaded to Codecov
197239

198-
## GitHub & CI/CD
240+
## PR and Commit Guidelines
199241

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
242+
- PRs target the `main` branch
243+
- You MUST run `./scripts/install_all_and_run_tests.sh` before submitting
244+
- PR template (`.github/pull_request_template.md`) requires: Summary, Testing steps, Category checkboxes (`App`, `AsyncApp`, Adapters, Docs, Others)
245+
- Requirements: CLA signed, test suite passes, code review approval
246+
- Commits should be atomic with descriptive messages. Reference related issue numbers.

0 commit comments

Comments
 (0)