From 593ee61dce0d209c3d2351f6f88c7021359ee5d8 Mon Sep 17 00:00:00 2001 From: guyash1 Date: Sun, 19 Jul 2026 08:53:57 +0300 Subject: [PATCH 1/3] ci: run make check and make validate on PRs and pushes to main (#23) --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..aeb9f98d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +# CI — the quality gate (issue #23). +# +# Runs `make check` (ruff + mypy + pytest) and `make validate` (offline +# validation of the flagship example) on every pull request and on pushes +# to main. Single Python version per pyproject.toml (requires-python >=3.11); +# no matrix, no caching tuning — see the issue's non-goals. +# +# To make this a required status check, protect `main` in the repo settings +# and require the "check" job (maintainer action, not part of this file). + +name: CI + +on: + pull_request: + push: + branches: [main] + +# Cancel superseded runs of the same PR/branch to save runner minutes. +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + cache-dependency-path: pyproject.toml + + - name: Install (editable + dev extras) + run: make install + + - name: Quality gate (lint + typecheck + test) + run: make check + + - name: Validate flagship example (offline) + run: make validate From 7976bdc3cb8b81a0070b2131a0ce6347e8a976e4 Mon Sep 17 00:00:00 2001 From: guyash1 Date: Sun, 19 Jul 2026 09:18:42 +0300 Subject: [PATCH 2/3] fix: resolve pre-existing lint and mypy errors so CI lands green MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two unused imports, two overlong lines, and a missing dict annotation that mypy flagged — all present on main before this branch; fixed here so the new CI workflow passes on its own PR. --- src/agent_engine/core/validator.py | 2 +- src/agent_engine/generate/generator.py | 5 +++-- tests/approvals/test_sanitization.py | 4 +++- tests/core/test_plugin_stub_scan.py | 7 ++++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/agent_engine/core/validator.py b/src/agent_engine/core/validator.py index 979270fa..f4fba741 100644 --- a/src/agent_engine/core/validator.py +++ b/src/agent_engine/core/validator.py @@ -4,7 +4,7 @@ from agent_engine.core.errors import ValidationError from agent_engine.core.plugin_stubs import scan_unimplemented_plugins -from agent_engine.core.spec import GraphNode, OrchestratorSpec, SystemSpec +from agent_engine.core.spec import GraphNode, SystemSpec class SystemSpecValidator: diff --git a/src/agent_engine/generate/generator.py b/src/agent_engine/generate/generator.py index 23d06ba6..f2c1da16 100644 --- a/src/agent_engine/generate/generator.py +++ b/src/agent_engine/generate/generator.py @@ -5,7 +5,6 @@ from agent_engine.core.spec import AgentSpec, GraphNode, SystemSpec from agent_engine.generate.manifest import ( - MANIFEST_NAME, ensure_plugins_manifest_exists, manifest_package, update_manifest, @@ -42,7 +41,9 @@ def generate(self, spec: SystemSpec, base_dir: Path) -> GenerateResult: tools_dir = base_dir / "plugins" / "tools" tools_dir.mkdir(parents=True, exist_ok=True) for tool_id, description in tool_ids.items(): - self._write(tools_dir / f"{tool_id}.py", _tool_stub(tool_id, description), result, base_dir) + self._write( + tools_dir / f"{tool_id}.py", _tool_stub(tool_id, description), result, base_dir + ) resolvers_dir = base_dir / "plugins" / "resolvers" resolvers_dir.mkdir(parents=True, exist_ok=True) diff --git a/tests/approvals/test_sanitization.py b/tests/approvals/test_sanitization.py index 7c386eb0..79cf449c 100644 --- a/tests/approvals/test_sanitization.py +++ b/tests/approvals/test_sanitization.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import Any + from agent_engine.approvals.sanitization import REDACTED, mask_arguments, mask_sensitive @@ -42,7 +44,7 @@ def test_recursive_masking_in_nested_structures() -> None: def test_original_arguments_unchanged() -> None: - args = {"password": "hunter2", "nested": {"token": "t"}} + args: dict[str, Any] = {"password": "hunter2", "nested": {"token": "t"}} mask_arguments(args) assert args["password"] == "hunter2" assert args["nested"]["token"] == "t" diff --git a/tests/core/test_plugin_stub_scan.py b/tests/core/test_plugin_stub_scan.py index e3f5e47f..8839f5e4 100644 --- a/tests/core/test_plugin_stub_scan.py +++ b/tests/core/test_plugin_stub_scan.py @@ -430,9 +430,10 @@ def test_missing_prompt_file_is_reported_with_hint(tmp_path: Path) -> None: ) errors = scan(spec, tmp_path) assert len(errors) == 1 - assert any( - "a.prompts.system" in e and "Prompt file not found: prompts/a/system.md — run `agentctl generate` to create the stub" in e - for e in errors + expected = ( + "Prompt file not found: prompts/a/system.md — " + "run `agentctl generate` to create the stub" ) + assert any("a.prompts.system" in e and expected in e for e in errors) From 98cd6c12caef2289d8a1c3f4b7d302f22171e3aa Mon Sep 17 00:00:00 2001 From: guyash1 Date: Sun, 19 Jul 2026 09:26:42 +0300 Subject: [PATCH 3/3] fix: use typing_extensions.TypedDict in local MCP tools for Python 3.11 pydantic refuses typing.TypedDict on Python < 3.12, so FastMCP's schema generation crashed the example MCP server at startup on CI's 3.11 (passed locally on 3.13). Caught by the new CI's first run. --- .../mcps/local_knowledge_mcp/tools.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/enterprise-knowledge-assistant/mcps/local_knowledge_mcp/tools.py b/examples/enterprise-knowledge-assistant/mcps/local_knowledge_mcp/tools.py index a4aacaa9..be4b7239 100644 --- a/examples/enterprise-knowledge-assistant/mcps/local_knowledge_mcp/tools.py +++ b/examples/enterprise-knowledge-assistant/mcps/local_knowledge_mcp/tools.py @@ -3,7 +3,11 @@ from __future__ import annotations import hashlib -from typing import TypedDict + +# typing_extensions rather than typing: pydantic (which FastMCP uses to build +# tool schemas from these types) rejects typing.TypedDict on Python < 3.12, +# and this project supports 3.11. +from typing_extensions import TypedDict class Document(TypedDict):