Skip to content
Open
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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ name: CI
on: [push, pull_request]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Ruff (pyflakes — undefined names, dead imports/vars, syntax errors)
run: ruff check coworker tests

pytest:
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 1 addition & 1 deletion coworker/automation/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from __future__ import annotations

from typing import Any, Callable, Optional
from typing import Any, Callable

import aisuite as ai

Expand Down
2 changes: 1 addition & 1 deletion coworker/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from __future__ import annotations

from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Callable

import aisuite as ai
Expand Down
2 changes: 1 addition & 1 deletion coworker/connectors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import asdict, dataclass, field
from dataclasses import asdict, dataclass
from enum import Enum
from typing import Any, Awaitable, Callable, Optional

Expand Down
2 changes: 1 addition & 1 deletion coworker/connectors/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from __future__ import annotations

from dataclasses import dataclass, field
from dataclasses import dataclass
from typing import Callable, Optional


Expand Down
2 changes: 1 addition & 1 deletion coworker/connectors/relay_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from typing import Any, Awaitable, Callable, Optional, Protocol

from .adapters import _SLACK_MENTION_RE, slack_event_to_event
from .base import BasePlatformAdapter, InteractionEvent, SendResult, SessionSource
from .base import BasePlatformAdapter, InteractionEvent, SendResult
from .senders import _send_slack, _send_slack_interactive
from .slack_addr import qualify

Expand Down
4 changes: 2 additions & 2 deletions coworker/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def _has_shell_operators(command: str) -> bool:
return any(op in command for op in _SHELL_OPERATORS)

from .risk import ( # re-exported for back-compat (manager.py imports WRITE_TOOLS)
SHELL_TOOL,
WRITE_TOOLS,
SHELL_TOOL, # noqa: F401
WRITE_TOOLS, # noqa: F401
RiskClass,
RiskOverrides,
classify,
Expand Down
2 changes: 1 addition & 1 deletion coworker/personas/loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import subprocess
from pathlib import Path
from typing import Callable, Optional
from typing import Callable

from .manifest import PersonaManifest

Expand Down
3 changes: 0 additions & 3 deletions coworker/server/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2656,8 +2656,6 @@ def approval_outcome(self, resolution: str, request, session_id: str):
"""Map an approval resolution (from any surface) to an ApprovalOutcome, handling
the task-persistent "always_task" vocabulary alongside the session-scoped ones.
"""
from ..engine import ApprovalOutcome

if resolution == "always_task":
self.mint_task_rule(
session_id,
Expand All @@ -2677,7 +2675,6 @@ def approval_outcome(self, resolution: str, request, session_id: str):
return ApprovalOutcome.DENY

def _scheduled_approver(self, task, session_id: str):
from ..engine import ApprovalOutcome
from ..permissions import WRITE_TOOLS

name_allowed = task.name_allowed_tools()
Expand Down
2 changes: 1 addition & 1 deletion coworker/skills/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import aisuite as ai

from ..secrets import state_dir
from .base import Skill, _parse_skill
from .base import _parse_skill

_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
_MAX_NAME = 64
Expand Down
2 changes: 1 addition & 1 deletion coworker/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import re
import threading
from collections import deque
from dataclasses import asdict, dataclass, field
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Optional

Expand Down
14 changes: 13 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ dependencies = [
]

[project.optional-dependencies]
dev = ["pytest>=8", "pytest-asyncio", "httpx"]
dev = ["pytest>=8", "pytest-asyncio", "httpx", "ruff>=0.16"]
# Inbound messaging listeners (outbound send_message needs only httpx, already a core dep).
# aiohttp is slack-bolt's Socket Mode transport at runtime (and the FakeSlack test harness
# drives the real handler) — declare it so CI installs it, not just transitively.
Expand All @@ -62,3 +62,15 @@ coworker = ["personas/builtin/*.md"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

[tool.ruff]
# CI actually runs 3.12 (see ci.yml); pinning here (rather than deriving from the
# 3.10-floor `requires-python`) avoids false undefined-name positives on names the
# stdlib only added later (e.g. `ExceptionGroup`, 3.11+).
target-version = "py312"

[tool.ruff.lint]
# Deliberately narrow (owner call — see issue #34): pyflakes catches real latent bugs
# (undefined names, dead imports/variables, shadowed-unused redefinitions) with ~zero
# false positives and no style opinions to bikeshed. Widen once this is settled in.
select = ["E9", "F"]
2 changes: 0 additions & 2 deletions tests/test_code_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import subprocess
from types import SimpleNamespace

import pytest

from coworker.tools.files import file_tools
from coworker.tools.git import git_tools
from coworker.tools.search import _py_grep, search_tools
Expand Down
2 changes: 0 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from __future__ import annotations

from pathlib import Path

from coworker.config import load_config


Expand Down
2 changes: 0 additions & 2 deletions tests/test_connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

from __future__ import annotations

import asyncio

import pytest

from coworker.connectors import (
Expand Down
1 change: 0 additions & 1 deletion tests/test_dm_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import asyncio

import pytest
from fastapi.testclient import TestClient

from coworker.connectors.base import MessageEvent, SessionSource
Expand Down
2 changes: 0 additions & 2 deletions tests/test_email_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

from email.message import EmailMessage

import pytest

from coworker.connectors.email_tools import (
build_search_criteria,
decode_mime_header,
Expand Down
1 change: 0 additions & 1 deletion tests/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import aisuite as ai
from coworker.conversations import ConversationStore
from coworker.memory import Scope, SQLiteMemoryStore, format_memories, memory_tools
from coworker.sessions import SessionRecord
Expand Down
2 changes: 1 addition & 1 deletion tests/test_message_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from fastapi.testclient import TestClient

from coworker.connectors.base import MessageEvent, MessageSource, SessionSource
from coworker.connectors.base import MessageEvent, SessionSource
from coworker.engine import TurnEngine
from coworker.permissions import PermissionEngine
from coworker.providers import AssistantTurn, ModelCapabilities, ProviderClient
Expand Down
2 changes: 1 addition & 1 deletion tests/test_multiroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import aisuite as ai
from coworker.engine import TurnEngine
from coworker.events import EventType
from coworker.permissions import Decision, Mode, PermissionEngine
from coworker.permissions import PermissionEngine
from coworker.providers import AssistantTurn, ToolCall
from coworker.roots import RootDir, normalize_roots, render_context
from coworker.tools import ToolRegistry
Expand Down
2 changes: 1 addition & 1 deletion tests/test_plan_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def test_discuss_mode_blocks_writes_without_plan_pressure(tmp_path):
],
)
permissions.mode = Mode.DISCUSS
events = _collect(engine, "tweak x.py")
_collect(engine, "tweak x.py")
assert not (tmp_path / "x.py").exists()
assert any(
m.get("role") == "tool" and "discuss mode is read-only" in m["content"]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_risk_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from types import SimpleNamespace

from coworker.overrides import RiskOverrideStore
from coworker.permissions import Mode, PermissionEngine
from coworker.permissions import PermissionEngine
from coworker.risk import RiskClass, classify

MCP_META = SimpleNamespace(requires_approval=True, category="mcp")
Expand Down
1 change: 0 additions & 1 deletion tests/test_skills_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import io
import zipfile

import pytest
from fastapi.testclient import TestClient

from coworker.providers import AssistantTurn, ModelCapabilities, ProviderClient
Expand Down
1 change: 0 additions & 1 deletion tests/test_skills_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from coworker.skills import (
SessionSkillStore,
SkillLoader,
SkillStore,
effective_skills,
skill_catalog_text,
skill_tools,
Expand Down
1 change: 0 additions & 1 deletion tests/test_skills_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from __future__ import annotations

import io
import json
import os
import zipfile
from pathlib import Path
Expand Down
2 changes: 1 addition & 1 deletion tests/test_slack_relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from coworker.connectors import relay_client
from coworker.connectors.adapters import make_adapter
from coworker.connectors.base import InteractionEvent, MessageEvent
from coworker.connectors.config import ConnectorSettings, load_settings
from coworker.connectors.config import load_settings
from coworker.connectors.relay_client import SlackRelayAdapter
from coworker.connectors.slack_addr import qualify, split
from coworker.connectors.tools import make_send_message_tool
Expand Down
1 change: 0 additions & 1 deletion tests/test_standing_approvals.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import asyncio

import aisuite as ai
import pytest

from coworker.automation import Schedule, ScheduledTask, Scheduler, TaskRun, TaskStore
from coworker.automation.models import grant_entries, rule_entry, rule_parts
Expand Down
2 changes: 0 additions & 2 deletions tests/test_subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import asyncio

import pytest

from coworker.connectors.base import MessageEvent, SessionSource
from coworker.subscriptions import (
ChannelBuffer,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_tools_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest

import aisuite as ai
from coworker.permissions import Decision, Mode, PermissionEngine
from coworker.permissions import Mode, PermissionEngine
from coworker.tools import ToolRegistry


Expand Down