Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion src/agent_engine/core/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 3 additions & 2 deletions src/agent_engine/generate/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion tests/approvals/test_sanitization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from typing import Any

from agent_engine.approvals.sanitization import REDACTED, mask_arguments, mask_sensitive


Expand Down Expand Up @@ -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"
Expand Down
7 changes: 4 additions & 3 deletions tests/core/test_plugin_stub_scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Loading