From f1d8cef2bb7fdd853d0f92ef3256aa2808d605cd Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 20 Jul 2026 12:11:11 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=A7=20(config):=20Replace=20black/?= =?UTF-8?q?isort/autoflake=20with=20ruff=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate the python-sdk formatter/import tooling onto a single ruff-pre-commit block: ruff-check --fix (isort I + autoflake F401/F841) plus ruff-format (black-compatible). ruff.toml is the source of truth. Also exclude quickstart_snippets/ in ruff.toml to preserve the parity the replaced trio had via the pre-commit top-level exclude — the dir holds vendored, verbatim doc excerpts that ruff cannot lint/format. Refs AAASM-4915 Co-Authored-By: Claude Opus 4.8 (1M context) --- .pre-commit-config.yaml | 41 +++++++++-------------------------------- ruff.toml | 9 +++++++++ 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 531239b1..898150ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,39 +16,16 @@ repos: - id: trailing-whitespace - id: detect-private-key - - repo: https://github.com/PyCQA/isort - rev: 6.0.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Single Python formatter/linter (AAASM-4915): replaces the former + # isort + autoflake + black trio. `ruff-check --fix` folds in isort (rule + # `I`) and autoflake's unused-import/var removal (`F401`/`F841`); the shared + # ruff.toml is the source of truth for select/ignore/format config. + rev: v0.15.21 hooks: - - id: isort - args: # arguments to configure black - - --profile=black - - - repo: https://github.com/PyCQA/autoflake - rev: v2.3.1 - hooks: - - id: autoflake - args: - - --remove-all-unused-imports - - --ignore-init-module-imports - - --in-place - - - repo: https://github.com/psf/black - rev: 25.1.0 - hooks: - - id: black - args: # arguments to configure black - - --line-length=120 - # these folders won't be formatted by black - - --exclude="""\.git | - \.__pycache__| - \**.egg-info| - \.mypy_cache| - \.pytest_cache| - \.tox| - \.venv| - _build| - build| - dist""" + - id: ruff-check + args: [--fix] + - id: ruff-format - repo: local hooks: diff --git a/ruff.toml b/ruff.toml index f85e089d..c9cfe8a0 100644 --- a/ruff.toml +++ b/ruff.toml @@ -14,6 +14,15 @@ exclude = [ "dist", "docs_with_docusarus", "docs", + # AAASM-4915: vendored, verbatim quick-start excerpts (copied from + # ai-agent-assembly/examples; regenerated into docs/quick-start.md by the + # quickstart-tabs-check). Several are partial governance slices that are + # valid as doc excerpts but not standalone modules, so ruff can't lint or + # format them. The former black/isort/autoflake trio excluded this dir via + # the .pre-commit top-level `exclude`; keep that parity now that ruff is the + # single formatter/linter so the bare-CLI gates stay clean and the vendored + # copies are never rewritten. + "quickstart_snippets/", # AAASM-4434: generated gRPC/protobuf stubs (scripts/gen_proto.py), committed # verbatim; a CI drift check regenerates them and asserts no diff, so they # must not be hand-edited to satisfy lint (mirrors the mypy.ini ignore for the From 6d95d622577c90b8f9c3aed3291fa62b223ea177 Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 20 Jul 2026 12:13:44 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8E=A8=20(format):=20Reformat=20with?= =?UTF-8?q?=20ruff=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply ruff format across the tree after switching the formatter from black. Diff is minimal and black-equivalent: 6 files, 14 insertions / 15 deletions — only ruff's implicit-string-concat joining and assert-message parenthesization differ from black's output. Refs AAASM-4915 Co-Authored-By: Claude Opus 4.8 (1M context) --- agent_assembly/cli/adapter_validator.py | 2 +- scripts/generate_quickstart_tabs.py | 5 ++--- .../langchain/test_getattr_contract_with_langchain.py | 6 +++--- test/unit/adapters/test_failopen_conformance.py | 6 +++--- test/unit/cli/test_adapter_validator.py | 4 ++-- test/unit/test_runtime_import_isolation.py | 6 +++--- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/agent_assembly/cli/adapter_validator.py b/agent_assembly/cli/adapter_validator.py index dade03ca..c1152ca5 100644 --- a/agent_assembly/cli/adapter_validator.py +++ b/agent_assembly/cli/adapter_validator.py @@ -212,7 +212,7 @@ def _check_entry_point_metadata(cls: type, path_or_module: str) -> AdapterValida return AdapterValidationResult( check_name="entry_point_metadata", passed=False, - message=('pyproject.toml missing [project.entry-points."agent_assembly.adapters"] ' "section."), + message=('pyproject.toml missing [project.entry-points."agent_assembly.adapters"] section.'), ) class_qualname = f"{cls.__module__}:{cls.__qualname__}" diff --git a/scripts/generate_quickstart_tabs.py b/scripts/generate_quickstart_tabs.py index 04c7ee6c..3c5e2164 100644 --- a/scripts/generate_quickstart_tabs.py +++ b/scripts/generate_quickstart_tabs.py @@ -101,7 +101,7 @@ def build_block(vendor_dir: Path) -> str: framework_id = entry["framework_id"] snippet_path = vendor_dir / f"{framework_id}.py" if not snippet_path.is_file(): - raise GenerateError(f"manifest lists '{framework_id}' but its snippet is missing: " f"{snippet_path}") + raise GenerateError(f"manifest lists '{framework_id}' but its snippet is missing: {snippet_path}") code = snippet_path.read_text(encoding="utf-8").rstrip("\n") tabs.append(render_tab(entry["label"], code, entry.get("version_compat_note"))) @@ -115,8 +115,7 @@ def apply_block(doc_text: str, block: str) -> str: end = doc_text.find(END_MARKER) if start == -1 or end == -1: raise GenerateError( - f"markers not found in doc — expected both {BEGIN_MARKER!r} and " - f"{END_MARKER!r}. Add them to the doc first." + f"markers not found in doc — expected both {BEGIN_MARKER!r} and {END_MARKER!r}. Add them to the doc first." ) if end < start: raise GenerateError("END marker precedes BEGIN marker in the doc") diff --git a/test/unit/adapters/langchain/test_getattr_contract_with_langchain.py b/test/unit/adapters/langchain/test_getattr_contract_with_langchain.py index 120a7586..2fbc0a59 100644 --- a/test/unit/adapters/langchain/test_getattr_contract_with_langchain.py +++ b/test/unit/adapters/langchain/test_getattr_contract_with_langchain.py @@ -151,9 +151,9 @@ def test_contract_members_resolve_statically_not_via_getattr() -> None: if name in _OVERRIDDEN_MEMBERS: assert owner is AssemblyCallbackHandler else: - assert owner.__module__.startswith( - "langchain_core" - ), f"{name!r} resolved to {owner!r}, not the LangChain base contract" + assert owner.__module__.startswith("langchain_core"), ( + f"{name!r} resolved to {owner!r}, not the LangChain base contract" + ) # Live attribute access (bound methods + evaluated flag properties) also # resolves without ever delegating to the interceptor. diff --git a/test/unit/adapters/test_failopen_conformance.py b/test/unit/adapters/test_failopen_conformance.py index c6cdc9ee..2d433e85 100644 --- a/test/unit/adapters/test_failopen_conformance.py +++ b/test/unit/adapters/test_failopen_conformance.py @@ -53,9 +53,9 @@ async def test_failopen_conformance( posture = "enforce" if is_enforce else "fail-open" verb = "run" if want else "be blocked" - assert ( - ran is want - ), f"{adapter_name} under {mode_id} ({posture}) on {scenario}: tool expected to {verb}, but ran={ran}" + assert ran is want, ( + f"{adapter_name} under {mode_id} ({posture}) on {scenario}: tool expected to {verb}, but ran={ran}" + ) def test_every_adapter_has_a_driver() -> None: diff --git a/test/unit/cli/test_adapter_validator.py b/test/unit/cli/test_adapter_validator.py index 8a22bc17..04629e49 100644 --- a/test/unit/cli/test_adapter_validator.py +++ b/test/unit/cli/test_adapter_validator.py @@ -162,7 +162,7 @@ def test_valid_pyproject_passes(self, valid_adapter_cls: type, tmp_path: object) assert isinstance(tmp_path, pathlib.Path) pyproject = tmp_path / "pyproject.toml" qualname = f"{valid_adapter_cls.__module__}:{valid_adapter_cls.__qualname__}" - pyproject.write_text(f'[project.entry-points."agent_assembly.adapters"]\n' f'test_framework = "{qualname}"\n') + pyproject.write_text(f'[project.entry-points."agent_assembly.adapters"]\ntest_framework = "{qualname}"\n') result = _check_entry_point_metadata(valid_adapter_cls, str(tmp_path)) assert result.passed is True @@ -193,7 +193,7 @@ def test_traversal_path_resolved_to_valid_dir(self, valid_adapter_cls: type, tmp adapter_dir.mkdir() pyproject = adapter_dir / "pyproject.toml" qualname = f"{valid_adapter_cls.__module__}:{valid_adapter_cls.__qualname__}" - pyproject.write_text(f'[project.entry-points."agent_assembly.adapters"]\n' f'test_framework = "{qualname}"\n') + pyproject.write_text(f'[project.entry-points."agent_assembly.adapters"]\ntest_framework = "{qualname}"\n') # "/adapter/../adapter" canonicalizes back to "/adapter". traversal = str(adapter_dir / ".." / "adapter") result = _check_entry_point_metadata(valid_adapter_cls, traversal) diff --git a/test/unit/test_runtime_import_isolation.py b/test/unit/test_runtime_import_isolation.py index e8db9cb9..11ee2e6c 100644 --- a/test/unit/test_runtime_import_isolation.py +++ b/test/unit/test_runtime_import_isolation.py @@ -51,9 +51,9 @@ def test_runtime_import_does_not_pull_in_httpx() -> None: ["httpx", "pydantic"], "from agent_assembly.runtime import find_aasm_binary, init_assembly, is_running\nprint('ok')\n", ) - assert ( - result.returncode == 0 - ), f"agent_assembly.runtime should not require httpx/pydantic.\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert result.returncode == 0, ( + f"agent_assembly.runtime should not require httpx/pydantic.\nstdout: {result.stdout}\nstderr: {result.stderr}" + ) assert result.stdout.strip() == "ok" From 76005e0edb76d59a51617121418a822ac01ebf40 Mon Sep 17 00:00:00 2001 From: Bryant Date: Mon, 20 Jul 2026 12:14:34 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=9D=20(docs):=20Reference=20ruff?= =?UTF-8?q?=20format=20as=20the=20formatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the contributor/adapter docs and repo CLAUDE.md to name ruff format (and the new ruff check + ruff format pre-commit suite) now that black/isort/autoflake are gone. Refs AAASM-4915 Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/CLAUDE.md | 2 +- CONTRIBUTING.md | 4 ++-- docs/guides/authoring-adapters.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index efec4794..68199b0c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -51,7 +51,7 @@ uv sync # create .venv + install runtime + dev de .venv/bin/python -m pytest test/unit/cli/test_loader.py # one file .venv/bin/python -m pytest test/unit/cli/test_loader.py::TestLoadAdapterClass # one class .venv/bin/ruff check . -.venv/bin/black . # formatter gate (see .pre-commit-config.yaml) +.venv/bin/ruff format . # formatter gate (see .pre-commit-config.yaml) .venv/bin/mypy agent_assembly # strict; type-check the package, not test/ .venv/bin/pre-commit run --all-files ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e518733..d617918c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -130,9 +130,9 @@ cargo test --manifest-path native/aa-ffi-python/Cargo.toml ```bash uv run ruff check . # lint (config: ruff.toml; line length 120, target py312) -uv run black . # auto-format (formatter gate; see .pre-commit-config.yaml) +uv run ruff format . # auto-format (formatter gate; see .pre-commit-config.yaml) uv run mypy agent_assembly # type check (mypy.ini; strict on adapters.base/registry) -uv run pre-commit run --all-files # full pre-commit suite (isort + black + autoflake + mypy) +uv run pre-commit run --all-files # full pre-commit suite (ruff check + ruff format + mypy) ``` Pre-commit hooks block commits that fail any of the above. Do not bypass with `--no-verify`; fix the underlying issue. diff --git a/docs/guides/authoring-adapters.md b/docs/guides/authoring-adapters.md index b0b17b2f..c9dc42e4 100644 --- a/docs/guides/authoring-adapters.md +++ b/docs/guides/authoring-adapters.md @@ -327,7 +327,7 @@ repos but should still meet this bar), confirm: lifecycle (framework mocked). - [ ] Integration test under `test/integration/adapters//` exercises a minimal real flow. -- [ ] `uv run ruff check .`, `uv run black --check .`, and `uv run mypy agent_assembly` +- [ ] `uv run ruff check .`, `uv run ruff format --check .`, and `uv run mypy agent_assembly` are clean. - [ ] Entry point declared in `pyproject.toml` under `[project.entry-points."agent_assembly.adapters"]` (for standalone packages).