Skip to content

Commit d449c6c

Browse files
authored
chore(dx): align local typecheck workflow (#884)
* chore(dx): align local typecheck workflow * chore(types): enable unreachable mypy checks * fix(storyboards): align examples with 3.1 runner * docs(dx): clarify local setup path * fix(a2a): ignore scalar data parts
1 parent d5706cd commit d449c6c

34 files changed

Lines changed: 394 additions & 117 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ jobs:
4848
run: |
4949
mypy --strict tests/type_checks/
5050
51+
- name: Enforce adopter type-check fixture contract
52+
run: |
53+
python scripts/check_type_ignore_contract.py
54+
5155
- name: Run tests
5256
run: |
5357
pytest tests/ -v --cov=src/adcp --cov-report=term-missing

.pre-commit-config.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Pre-commit hooks for AdCP Python client
22
# See https://pre-commit.com for more information
3-
# Installation: uv add --dev pre-commit && uv run pre-commit install
3+
# Installation: make bootstrap
44

55
repos:
66
# Black code formatting
@@ -30,6 +30,19 @@ repos:
3030
types: [python]
3131
pass_filenames: false
3232
args: [src/adcp]
33+
- id: adopter-type-checks
34+
name: adopter type-check fixtures
35+
entry: uv run --extra dev mypy
36+
language: system
37+
types: [python]
38+
pass_filenames: false
39+
args: [--strict, tests/type_checks/]
40+
- id: adopter-type-ignore-contract
41+
name: "adopter type-check fixtures contain no type: ignore suppressions"
42+
entry: uv run python scripts/check_type_ignore_contract.py
43+
language: system
44+
types: [python]
45+
pass_filenames: false
3346
- id: check-commit-msg
3447
name: release-please commit subject check
3548
entry: scripts/check-commit-msg.sh

CLAUDE.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,19 @@ Wrong order creates invalid escape sequences!
184184

185185
## Pre-Commit Checks
186186

187-
Run these three checks locally before every commit — they mirror CI exactly:
187+
Run these checks locally before every commit:
188188

189189
```bash
190-
ruff check src/ # Linter
191-
mypy src/adcp/ # Type checker
192-
pytest tests/ -v # Tests
190+
make lint
191+
make typecheck-all
192+
make test
193193
```
194194

195-
All three must pass. CI runs them across Python 3.10–3.13; locally running on your current version catches most issues.
195+
All must pass. `make ci-local` runs the core local gate: lint, all type-check
196+
contracts, tests, and generated-code validation. Specialized CI jobs such as
197+
storyboard runners, Postgres conformance, and conventional-commit validation
198+
still run separately in GitHub Actions. CI runs the core matrix across Python
199+
3.10–3.13; locally running on your current version catches most issues.
196200

197201
## Parallel Agent Isolation (git worktrees)
198202

@@ -216,8 +220,7 @@ git worktree add /tmp/claude-issue-<N>-<slug> -b claude/issue-<N>-<slug> main
216220
```bash
217221
cd /tmp/claude-issue-<N>-<slug>
218222
cp "$(git rev-parse --git-common-dir)/../.env" .env # .env is not inherited
219-
pre-commit install # hooks are not inherited from parent worktree
220-
pip install -e .[dev] # install in this worktree's context
223+
make bootstrap # requires uv; installs deps and hooks here
221224
```
222225

223226
**Teardown (after branch is merged):**

CONTRIBUTING.md

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Thank you for your interest in contributing to the AdCP Python client!
44

55
## Development Setup
66

7+
This repository expects `uv` on your `PATH` for the local contributor
8+
environment because the pre-commit hooks run through `uv run` to match CI
9+
dependencies.
10+
711
1. Clone the repository:
812
```bash
913
git clone https://github.com/adcontextprotocol/adcp-client-python.git
@@ -12,42 +16,51 @@ cd adcp-client-python
1216

1317
2. Install dependencies and pre-commit hooks:
1418
```bash
15-
pip install -e ".[dev]"
16-
pre-commit install
17-
pre-commit install --hook-type commit-msg
19+
make bootstrap
1820
```
1921

2022
3. Run tests:
2123
```bash
22-
pytest
24+
make test
2325
```
2426

2527
4. Format code:
2628
```bash
27-
black src/ tests/
28-
ruff check src/ tests/ --fix
29+
make format
30+
make lint
2931
```
3032

3133
5. Type check:
3234
```bash
33-
mypy src/
35+
make typecheck-all
36+
```
37+
38+
For the core local CI-style pass before opening a PR, run:
39+
40+
```bash
41+
make ci-local
3442
```
3543

44+
This covers lint, all type-check contracts, tests, and generated-code
45+
validation. GitHub Actions still runs specialized jobs such as storyboard
46+
runners, Postgres conformance, and conventional-commit validation.
47+
3648
## Project Structure
3749

3850
```
3951
src/adcp/
4052
├── __init__.py # Main exports
4153
├── client.py # ADCPClient & ADCPMultiAgentClient
42-
├── protocols/
43-
│ ├── base.py # Protocol interface
44-
│ ├── a2a.py # A2A adapter
45-
│ └── mcp.py # MCP adapter
46-
├── types/
47-
│ ├── core.py # Core types
48-
│ └── tools.py # Generated from AdCP schema
49-
└── utils/
50-
└── operation_id.py # Utilities
54+
├── canonical_formats/ # Canonical format fixtures and adapters
55+
├── compat/ # Legacy protocol compatibility adapters
56+
├── decisioning/ # DecisioningPlatform framework
57+
├── protocols/ # A2A and MCP client adapters
58+
├── server/ # Server framework, auth, routing, middleware
59+
├── signing/ # Request signing, verification, JWKS, replay stores
60+
├── testing/ # In-process test helpers and test agents
61+
├── types/ # Public types, generated models, mypy plugin
62+
├── utils/ # Shared helpers
63+
└── validation/ # Schema validation hooks and loaders
5164
```
5265

5366
## Guidelines
@@ -68,7 +81,9 @@ src/adcp/
6881
### Type Safety
6982
- All functions must have type hints
7083
- Use Pydantic for data validation
71-
- Run `mypy` before committing
84+
- Run `make typecheck-all` before committing
85+
- `tests/type_checks/` is the adopter-facing type contract suite. Fixtures must
86+
pass `mypy --strict` without `# type: ignore` suppressions.
7287

7388
### Documentation
7489
- Add docstrings to all public functions

Makefile

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help format lint typecheck test test-type-checks regenerate-schemas pre-push ci-local clean install-dev check-schema-drift
1+
.PHONY: help check-uv bootstrap format lint lint-all typecheck typecheck-all test test-type-checks check-type-ignore-contract regenerate-schemas pre-push ci-local clean install-dev check-schema-drift
22

33
# Detect Python and use venv if available
44
PYTHON := $(shell if [ -f .venv/bin/python ]; then echo .venv/bin/python; else echo python3; fi)
@@ -7,28 +7,46 @@ PYTEST := $(shell if [ -f .venv/bin/pytest ]; then echo .venv/bin/pytest; else e
77
BLACK := $(shell if [ -f .venv/bin/black ]; then echo .venv/bin/black; else echo black; fi)
88
RUFF := $(shell if [ -f .venv/bin/ruff ]; then echo .venv/bin/ruff; else echo ruff; fi)
99
MYPY := $(shell if [ -f .venv/bin/mypy ]; then echo .venv/bin/mypy; else echo mypy; fi)
10+
UV := uv
1011

1112
help: ## Show this help message
1213
@echo 'Usage: make [target]'
1314
@echo ''
1415
@echo 'Available targets:'
1516
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}'
1617

17-
install-dev: ## Install package in development mode with dev dependencies
18+
install-dev: ## Legacy pip-only install; bootstrap is preferred for contributors
1819
$(PIP) install -e ".[dev]"
1920

21+
check-uv:
22+
@command -v $(UV) >/dev/null || { \
23+
echo "uv is required for bootstrap and pre-commit hooks. Install it from https://docs.astral.sh/uv/"; \
24+
exit 1; \
25+
}
26+
27+
bootstrap: check-uv ## Recommended contributor setup: uv-managed deps and git hooks
28+
$(UV) run --extra dev --group dev pre-commit install
29+
$(UV) run --extra dev --group dev pre-commit install --hook-type commit-msg
30+
2031
format: ## Format code with black (excludes generated files)
2132
$(BLACK) src/ tests/ scripts/
2233
@echo "✓ Code formatted successfully (_generated.py excluded via pyproject.toml)"
2334

2435
lint: ## Run linter (ruff) on source code
25-
$(RUFF) check src/ tests/
36+
$(RUFF) check src/
2637
@echo "✓ Linting passed"
2738

39+
lint-all: ## Run linter (ruff) on source and tests
40+
$(RUFF) check src/ tests/
41+
@echo "✓ Source and test linting passed"
42+
2843
typecheck: ## Run type checker (mypy) on source code
2944
$(MYPY) src/adcp/
3045
@echo "✓ Type checking passed"
3146

47+
typecheck-all: typecheck test-type-checks check-type-ignore-contract ## Run all type-check contracts
48+
@echo "✓ All type-check contracts passed"
49+
3250
test: ## Run test suite with coverage
3351
$(PYTEST) tests/ -v --cov=src/adcp --cov-report=term-missing
3452
@echo "✓ All tests passed"
@@ -41,6 +59,10 @@ test-type-checks: ## Run adopter-pattern type-check suite (mypy --strict, zero t
4159
$(MYPY) --strict tests/type_checks/
4260
@echo "✓ Adopter type-checks passed"
4361

62+
check-type-ignore-contract: ## Fail if adopter type-check fixtures use type: ignore suppressions
63+
$(PYTHON) scripts/check_type_ignore_contract.py
64+
@echo "✓ Adopter type-check fixtures contain no type: ignore suppressions"
65+
4466
test-generation: ## Run only code generation tests
4567
$(PYTEST) tests/test_code_generation.py -v
4668
@echo "✓ Code generation tests passed"
@@ -70,15 +92,15 @@ validate-generated: ## Validate generated code (syntax and imports)
7092
@$(PYTHON) -m py_compile src/adcp/types/_generated.py
7193
@echo "✓ Generated code validation passed"
7294

73-
pre-push: format lint typecheck test validate-generated ## Run all checks before pushing (format, lint, typecheck, test, validate)
95+
pre-push: format lint typecheck-all test validate-generated ## Run all checks before pushing (format, lint, typecheck, test, validate)
7496
@echo ""
7597
@echo "================================"
7698
@echo "✓ All pre-push checks passed!"
7799
@echo "================================"
78100
@echo ""
79101
@echo "Safe to push to remote."
80102

81-
ci-local: lint typecheck test validate-generated ## Run CI checks locally (without formatting)
103+
ci-local: lint typecheck-all test validate-generated ## Run core CI checks locally (without formatting)
82104
@echo ""
83105
@echo "================================"
84106
@echo "✓ All CI checks passed!"

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,21 +1461,29 @@ client = ADCPMultiAgentClient.from_env()
14611461

14621462
## Development
14631463

1464+
This repository uses `uv` for the contributor environment and pre-commit hooks.
1465+
Install `uv` first if it is not already on your `PATH`.
1466+
14641467
```bash
1465-
# Install with dev dependencies
1466-
pip install -e ".[dev]"
1468+
# Install dev dependencies and git hooks (requires uv)
1469+
make bootstrap
14671470

14681471
# Run tests
1469-
pytest
1472+
make test
14701473

1471-
# Type checking
1472-
mypy src/
1474+
# Type checking: source package plus adopter-facing type fixtures
1475+
make typecheck-all
14731476

14741477
# Format code
1475-
black src/ tests/
1476-
ruff check src/ tests/
1478+
make format
1479+
make lint
14771480
```
14781481

1482+
`make ci-local` runs the core local gate: lint, all type-check contracts, tests,
1483+
and generated-code validation. Specialized CI jobs such as storyboard runners,
1484+
Postgres conformance, and conventional-commit validation still run separately in
1485+
GitHub Actions.
1486+
14791487
## Contributing
14801488

14811489
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. All contributors must agree to the [AgenticAdvertising.Org IPR Policy](https://github.com/adcontextprotocol/adcp/blob/main/IPR_POLICY.md) — the bot prompts new contributors on their first PR and a single signature covers all AAO repositories.

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ dev = [
123123
"pytest>=7.0.0",
124124
"pytest-asyncio>=0.21.0",
125125
"pytest-cov>=4.0.0",
126-
"mypy>=1.20.0,<1.21",
126+
"mypy==1.20.2",
127127
"black>=23.0.0",
128128
"ruff>=0.1.0",
129129
# Pin to exact version: codegen's variant numbering (e.g. CreateMediaBuyResponse1 vs
@@ -148,6 +148,7 @@ dev = [
148148
# propagate. Required at runtime by examples/v3_reference_seller/
149149
# src/app.py and seed.py.
150150
"alembic>=1.13.0",
151+
"pre-commit>=4.4.0",
151152
]
152153
docs = [
153154
"pdoc3>=0.10.0",
@@ -225,6 +226,8 @@ python_version = "3.10"
225226
strict = true
226227
warn_return_any = true
227228
warn_unused_configs = true
229+
warn_unreachable = true
230+
strict_equality_for_none = true
228231
# adcp.types.SchemaVariant — see adcp.types.mypy_plugin for the marker
229232
# semantics. Adopters that want the same override-compat behavior on
230233
# their own cross-class field overrides should add this plugin to their
@@ -321,5 +324,5 @@ dev = [
321324
# from CI's, producing a different error count than CI on the same
322325
# source — and a dead-weight hook that everyone bypasses with
323326
# ``SKIP=mypy``.
324-
"mypy>=1.20.0",
327+
"mypy==1.20.2",
325328
]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
"""Fail if adopter type-check fixtures rely on type-ignore suppressions."""
3+
4+
from __future__ import annotations
5+
6+
import sys
7+
import tokenize
8+
from pathlib import Path
9+
10+
ROOT = Path(__file__).resolve().parents[1]
11+
TYPE_CHECK_DIR = ROOT / "tests" / "type_checks"
12+
13+
14+
def type_ignore_comments(path: Path) -> list[tuple[int, str]]:
15+
findings: list[tuple[int, str]] = []
16+
with path.open("rb") as handle:
17+
for token in tokenize.tokenize(handle.readline):
18+
if token.type != tokenize.COMMENT:
19+
continue
20+
comment = token.string.lstrip("#").strip()
21+
if comment.startswith("type: ignore"):
22+
findings.append((token.start[0], token.string.strip()))
23+
return findings
24+
25+
26+
def main() -> int:
27+
failures: list[str] = []
28+
for path in sorted(TYPE_CHECK_DIR.rglob("*.py")):
29+
for line_no, comment in type_ignore_comments(path):
30+
rel_path = path.relative_to(ROOT)
31+
failures.append(f"{rel_path}:{line_no}: {comment}")
32+
33+
if not failures:
34+
return 0
35+
36+
print(
37+
"tests/type_checks/ fixtures must pass mypy --strict without type-ignore suppressions.",
38+
file=sys.stderr,
39+
)
40+
for failure in failures:
41+
print(failure, file=sys.stderr)
42+
return 1
43+
44+
45+
if __name__ == "__main__":
46+
raise SystemExit(main())

src/adcp/adagents.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,7 @@ def _is_bare_authorized_agent_entry(agent: dict[str, Any]) -> bool:
13101310

13111311

13121312
def _build_domain_index(
1313-
properties: list[dict[str, Any]],
1313+
properties: list[Any],
13141314
) -> dict[str, list[dict[str, Any]]]:
13151315
"""Build a ``publisher_domain → [property, ...]`` index.
13161316
@@ -1933,7 +1933,7 @@ class AuthorizationContext:
19331933
raw_properties: Raw property data from adagents.json
19341934
"""
19351935

1936-
def __init__(self, properties: list[dict[str, Any]]):
1936+
def __init__(self, properties: list[Any]):
19371937
"""Initialize from list of properties.
19381938
19391939
Args:

src/adcp/capabilities.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,6 @@ def supports_v3(self) -> bool:
218218
Returns:
219219
True if major_versions includes 3.
220220
"""
221-
if self._caps.adcp is None:
222-
return False
223221
for v in self._caps.adcp.major_versions:
224222
if (v.root if hasattr(v, "root") else v) == 3:
225223
return True

0 commit comments

Comments
 (0)