From e0cf4748ebf4c39d5f978b2f01c64f19c4c7ede9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 19:22:04 +0000 Subject: [PATCH 1/3] Move MCP endpoints and job handling code under deepwork.jobs namespace Reorganize source, tests, and specs for MCP endpoints (REQ-001, REQ-003, REQ-004, REQ-009) and job handling (REQ-002, REQ-008) under a unified `deepwork.jobs` namespace: Source: - deepwork.core.jobs -> deepwork.jobs.discovery - deepwork.core.parser -> deepwork.jobs.parser - deepwork.schemas.job_schema -> deepwork.jobs.schema - deepwork.mcp.* -> deepwork.jobs.mcp.* Tests: - tests/unit/test_jobs.py -> tests/unit/jobs/test_discovery.py - tests/unit/test_parser.py -> tests/unit/jobs/test_parser.py - tests/unit/mcp/* -> tests/unit/jobs/mcp/* Specs: - specs/deepwork/REQ-{001..004,008,009}-*.md -> specs/deepwork/jobs/ All 403 unit tests pass. https://claude.ai/code/session_017aa4FwLj9etksQHyd5wuKE --- .github/workflows/claude-code-test.yml | 2 +- .../{ => jobs}/REQ-001-mcp-workflow-tools.md | 0 .../REQ-002-job-definition-parsing.md | 0 .../REQ-003-workflow-session-management.md | 0 .../REQ-004-quality-review-system.md | 0 .../{ => jobs}/REQ-008-job-discovery.md | 0 .../REQ-009-claude-cli-subprocess.md | 0 src/deepwork/__init__.py | 2 +- src/deepwork/cli/serve.py | 2 +- src/deepwork/jobs/__init__.py | 11 +++++++++ .../{core/jobs.py => jobs/discovery.py} | 4 ++-- .../{schemas => jobs}/job.schema.json | 0 src/deepwork/{ => jobs}/mcp/__init__.py | 2 +- src/deepwork/{ => jobs}/mcp/claude_cli.py | 0 src/deepwork/{ => jobs}/mcp/quality_gate.py | 6 ++--- src/deepwork/{ => jobs}/mcp/schemas.py | 0 src/deepwork/{ => jobs}/mcp/server.py | 12 +++++----- src/deepwork/{ => jobs}/mcp/state.py | 2 +- src/deepwork/{ => jobs}/mcp/tools.py | 12 +++++----- src/deepwork/{core => jobs}/parser.py | 2 +- .../{schemas/job_schema.py => jobs/schema.py} | 0 tests/e2e/test_claude_code_integration.py | 8 +++---- .../test_quality_gate_integration.py | 6 ++--- tests/unit/jobs/__init__.py | 1 + tests/unit/jobs/mcp/__init__.py | 1 + .../{ => jobs}/mcp/test_async_interface.py | 8 +++---- tests/unit/{ => jobs}/mcp/test_claude_cli.py | 2 +- .../unit/{ => jobs}/mcp/test_quality_gate.py | 4 ++-- tests/unit/{ => jobs}/mcp/test_schemas.py | 2 +- tests/unit/{ => jobs}/mcp/test_state.py | 2 +- tests/unit/{ => jobs}/mcp/test_tools.py | 12 +++++----- .../{test_jobs.py => jobs/test_discovery.py} | 24 +++++++++---------- tests/unit/{ => jobs}/test_parser.py | 2 +- tests/unit/mcp/__init__.py | 1 - tests/unit/test_validation.py | 2 +- 35 files changed, 72 insertions(+), 60 deletions(-) rename specs/deepwork/{ => jobs}/REQ-001-mcp-workflow-tools.md (100%) rename specs/deepwork/{ => jobs}/REQ-002-job-definition-parsing.md (100%) rename specs/deepwork/{ => jobs}/REQ-003-workflow-session-management.md (100%) rename specs/deepwork/{ => jobs}/REQ-004-quality-review-system.md (100%) rename specs/deepwork/{ => jobs}/REQ-008-job-discovery.md (100%) rename specs/deepwork/{ => jobs}/REQ-009-claude-cli-subprocess.md (100%) create mode 100644 src/deepwork/jobs/__init__.py rename src/deepwork/{core/jobs.py => jobs/discovery.py} (96%) rename src/deepwork/{schemas => jobs}/job.schema.json (100%) rename src/deepwork/{ => jobs}/mcp/__init__.py (89%) rename src/deepwork/{ => jobs}/mcp/claude_cli.py (100%) rename src/deepwork/{ => jobs}/mcp/quality_gate.py (99%) rename src/deepwork/{ => jobs}/mcp/schemas.py (100%) rename src/deepwork/{ => jobs}/mcp/server.py (97%) rename src/deepwork/{ => jobs}/mcp/state.py (99%) rename src/deepwork/{ => jobs}/mcp/tools.py (98%) rename src/deepwork/{core => jobs}/parser.py (99%) rename src/deepwork/{schemas/job_schema.py => jobs/schema.py} (100%) create mode 100644 tests/unit/jobs/__init__.py create mode 100644 tests/unit/jobs/mcp/__init__.py rename tests/unit/{ => jobs}/mcp/test_async_interface.py (97%) rename tests/unit/{ => jobs}/mcp/test_claude_cli.py (99%) rename tests/unit/{ => jobs}/mcp/test_quality_gate.py (99%) rename tests/unit/{ => jobs}/mcp/test_schemas.py (99%) rename tests/unit/{ => jobs}/mcp/test_state.py (99%) rename tests/unit/{ => jobs}/mcp/test_tools.py (99%) rename tests/unit/{test_jobs.py => jobs/test_discovery.py} (92%) rename tests/unit/{ => jobs}/test_parser.py (99%) delete mode 100644 tests/unit/mcp/__init__.py diff --git a/.github/workflows/claude-code-test.yml b/.github/workflows/claude-code-test.yml index be277b77..1e591ffd 100644 --- a/.github/workflows/claude-code-test.yml +++ b/.github/workflows/claude-code-test.yml @@ -62,7 +62,7 @@ jobs: # Verify the fruits fixture parses correctly via deepwork's parser uv run python -c " from pathlib import Path - from deepwork.core.parser import parse_job_definition + from deepwork.jobs.parser import parse_job_definition job = parse_job_definition(Path('tests/fixtures/jobs/fruits')) diff --git a/specs/deepwork/REQ-001-mcp-workflow-tools.md b/specs/deepwork/jobs/REQ-001-mcp-workflow-tools.md similarity index 100% rename from specs/deepwork/REQ-001-mcp-workflow-tools.md rename to specs/deepwork/jobs/REQ-001-mcp-workflow-tools.md diff --git a/specs/deepwork/REQ-002-job-definition-parsing.md b/specs/deepwork/jobs/REQ-002-job-definition-parsing.md similarity index 100% rename from specs/deepwork/REQ-002-job-definition-parsing.md rename to specs/deepwork/jobs/REQ-002-job-definition-parsing.md diff --git a/specs/deepwork/REQ-003-workflow-session-management.md b/specs/deepwork/jobs/REQ-003-workflow-session-management.md similarity index 100% rename from specs/deepwork/REQ-003-workflow-session-management.md rename to specs/deepwork/jobs/REQ-003-workflow-session-management.md diff --git a/specs/deepwork/REQ-004-quality-review-system.md b/specs/deepwork/jobs/REQ-004-quality-review-system.md similarity index 100% rename from specs/deepwork/REQ-004-quality-review-system.md rename to specs/deepwork/jobs/REQ-004-quality-review-system.md diff --git a/specs/deepwork/REQ-008-job-discovery.md b/specs/deepwork/jobs/REQ-008-job-discovery.md similarity index 100% rename from specs/deepwork/REQ-008-job-discovery.md rename to specs/deepwork/jobs/REQ-008-job-discovery.md diff --git a/specs/deepwork/REQ-009-claude-cli-subprocess.md b/specs/deepwork/jobs/REQ-009-claude-cli-subprocess.md similarity index 100% rename from specs/deepwork/REQ-009-claude-cli-subprocess.md rename to specs/deepwork/jobs/REQ-009-claude-cli-subprocess.md diff --git a/src/deepwork/__init__.py b/src/deepwork/__init__.py index 0c85557f..68b6bd08 100644 --- a/src/deepwork/__init__.py +++ b/src/deepwork/__init__.py @@ -13,7 +13,7 @@ def __getattr__(name: str) -> object: """Lazy import for core modules.""" if name in ("JobDefinition", "ParseError", "Step", "StepInput", "parse_job_definition"): - from deepwork.core.parser import ( + from deepwork.jobs.parser import ( JobDefinition, ParseError, Step, diff --git a/src/deepwork/cli/serve.py b/src/deepwork/cli/serve.py index b5c88d06..87e673ef 100644 --- a/src/deepwork/cli/serve.py +++ b/src/deepwork/cli/serve.py @@ -110,7 +110,7 @@ def _serve_mcp( tmp_dir.mkdir(parents=True, exist_ok=True) # Create and run server - from deepwork.mcp.server import create_server + from deepwork.jobs.mcp.server import create_server server = create_server( project_root=project_path, diff --git a/src/deepwork/jobs/__init__.py b/src/deepwork/jobs/__init__.py new file mode 100644 index 00000000..89dff708 --- /dev/null +++ b/src/deepwork/jobs/__init__.py @@ -0,0 +1,11 @@ +"""DeepWork Jobs module. + +This module provides job definition parsing, discovery, MCP server integration, +and workflow session management for AI agent-driven workflows. + +Submodules: +- discovery: Job folder discovery and loading +- parser: Job definition (job.yml) parsing +- schema: Job YAML schema definition +- mcp: MCP server, tools, state, quality gate, and Claude CLI +""" diff --git a/src/deepwork/core/jobs.py b/src/deepwork/jobs/discovery.py similarity index 96% rename from src/deepwork/core/jobs.py rename to src/deepwork/jobs/discovery.py index ef0cef44..fe67d387 100644 --- a/src/deepwork/core/jobs.py +++ b/src/deepwork/jobs/discovery.py @@ -17,9 +17,9 @@ from dataclasses import dataclass from pathlib import Path -from deepwork.core.parser import JobDefinition, ParseError, parse_job_definition +from deepwork.jobs.parser import JobDefinition, ParseError, parse_job_definition -logger = logging.getLogger("deepwork.core.jobs") +logger = logging.getLogger("deepwork.jobs.discovery") @dataclass diff --git a/src/deepwork/schemas/job.schema.json b/src/deepwork/jobs/job.schema.json similarity index 100% rename from src/deepwork/schemas/job.schema.json rename to src/deepwork/jobs/job.schema.json diff --git a/src/deepwork/mcp/__init__.py b/src/deepwork/jobs/mcp/__init__.py similarity index 89% rename from src/deepwork/mcp/__init__.py rename to src/deepwork/jobs/mcp/__init__.py index bb6e5041..2f3a42c1 100644 --- a/src/deepwork/mcp/__init__.py +++ b/src/deepwork/jobs/mcp/__init__.py @@ -15,7 +15,7 @@ def create_server(*args, **kwargs): # type: ignore """Lazy import to avoid loading fastmcp at module import time.""" - from deepwork.mcp.server import create_server as _create_server + from deepwork.jobs.mcp.server import create_server as _create_server return _create_server(*args, **kwargs) diff --git a/src/deepwork/mcp/claude_cli.py b/src/deepwork/jobs/mcp/claude_cli.py similarity index 100% rename from src/deepwork/mcp/claude_cli.py rename to src/deepwork/jobs/mcp/claude_cli.py diff --git a/src/deepwork/mcp/quality_gate.py b/src/deepwork/jobs/mcp/quality_gate.py similarity index 99% rename from src/deepwork/mcp/quality_gate.py rename to src/deepwork/jobs/mcp/quality_gate.py index d6dcbb3a..376db9b9 100644 --- a/src/deepwork/mcp/quality_gate.py +++ b/src/deepwork/jobs/mcp/quality_gate.py @@ -12,8 +12,8 @@ import aiofiles -from deepwork.mcp.claude_cli import ClaudeCLI -from deepwork.mcp.schemas import ( +from deepwork.jobs.mcp.claude_cli import ClaudeCLI +from deepwork.jobs.mcp.schemas import ( QualityCriteriaResult, QualityGateResult, ReviewResult, @@ -517,7 +517,7 @@ async def evaluate( file_count = len(self._flatten_output_paths(outputs)) timeout = self.compute_timeout(file_count) - from deepwork.mcp.claude_cli import ClaudeCLIError + from deepwork.jobs.mcp.claude_cli import ClaudeCLIError try: data = await self._cli.run( diff --git a/src/deepwork/mcp/schemas.py b/src/deepwork/jobs/mcp/schemas.py similarity index 100% rename from src/deepwork/mcp/schemas.py rename to src/deepwork/jobs/mcp/schemas.py diff --git a/src/deepwork/mcp/server.py b/src/deepwork/jobs/mcp/server.py similarity index 97% rename from src/deepwork/mcp/server.py rename to src/deepwork/jobs/mcp/server.py index ccf1de6e..db48d342 100644 --- a/src/deepwork/mcp/server.py +++ b/src/deepwork/jobs/mcp/server.py @@ -19,18 +19,18 @@ from fastmcp import FastMCP -from deepwork.mcp.claude_cli import ClaudeCLI -from deepwork.mcp.quality_gate import QualityGate -from deepwork.mcp.schemas import ( +from deepwork.jobs.mcp.claude_cli import ClaudeCLI +from deepwork.jobs.mcp.quality_gate import QualityGate +from deepwork.jobs.mcp.schemas import ( AbortWorkflowInput, FinishedStepInput, StartWorkflowInput, ) -from deepwork.mcp.state import StateManager -from deepwork.mcp.tools import WorkflowTools +from deepwork.jobs.mcp.state import StateManager +from deepwork.jobs.mcp.tools import WorkflowTools # Configure logging -logger = logging.getLogger("deepwork.mcp") +logger = logging.getLogger("deepwork.jobs.mcp") def create_server( diff --git a/src/deepwork/mcp/state.py b/src/deepwork/jobs/mcp/state.py similarity index 99% rename from src/deepwork/mcp/state.py rename to src/deepwork/jobs/mcp/state.py index 0d890fa4..e0d88a18 100644 --- a/src/deepwork/mcp/state.py +++ b/src/deepwork/jobs/mcp/state.py @@ -18,7 +18,7 @@ import aiofiles -from deepwork.mcp.schemas import StackEntry, StepProgress, WorkflowSession +from deepwork.jobs.mcp.schemas import StackEntry, StepProgress, WorkflowSession class StateError(Exception): diff --git a/src/deepwork/mcp/tools.py b/src/deepwork/jobs/mcp/tools.py similarity index 98% rename from src/deepwork/mcp/tools.py rename to src/deepwork/jobs/mcp/tools.py index 3466baf0..38302af1 100644 --- a/src/deepwork/mcp/tools.py +++ b/src/deepwork/jobs/mcp/tools.py @@ -14,15 +14,15 @@ import aiofiles -from deepwork.core.jobs import JobLoadError, find_job_dir, load_all_jobs -from deepwork.core.parser import ( +from deepwork.jobs.discovery import JobLoadError, find_job_dir, load_all_jobs +from deepwork.jobs.parser import ( JobDefinition, OutputSpec, ParseError, Workflow, parse_job_definition, ) -from deepwork.mcp.schemas import ( +from deepwork.jobs.mcp.schemas import ( AbortWorkflowInput, AbortWorkflowResponse, ActiveStepInfo, @@ -38,12 +38,12 @@ StepStatus, WorkflowInfo, ) -from deepwork.mcp.state import StateManager +from deepwork.jobs.mcp.state import StateManager -logger = logging.getLogger("deepwork.mcp") +logger = logging.getLogger("deepwork.jobs.mcp") if TYPE_CHECKING: - from deepwork.mcp.quality_gate import QualityGate + from deepwork.jobs.mcp.quality_gate import QualityGate class ToolError(Exception): diff --git a/src/deepwork/core/parser.py b/src/deepwork/jobs/parser.py similarity index 99% rename from src/deepwork/core/parser.py rename to src/deepwork/jobs/parser.py index 8c5ddf38..9247ca9c 100644 --- a/src/deepwork/core/parser.py +++ b/src/deepwork/jobs/parser.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Any -from deepwork.schemas.job_schema import JOB_SCHEMA, LIFECYCLE_HOOK_EVENTS +from deepwork.jobs.schema import JOB_SCHEMA, LIFECYCLE_HOOK_EVENTS from deepwork.utils.validation import ValidationError, validate_against_schema from deepwork.utils.yaml_utils import YAMLError, load_yaml diff --git a/src/deepwork/schemas/job_schema.py b/src/deepwork/jobs/schema.py similarity index 100% rename from src/deepwork/schemas/job_schema.py rename to src/deepwork/jobs/schema.py diff --git a/tests/e2e/test_claude_code_integration.py b/tests/e2e/test_claude_code_integration.py index a2475b3b..9e60ea72 100644 --- a/tests/e2e/test_claude_code_integration.py +++ b/tests/e2e/test_claude_code_integration.py @@ -17,8 +17,8 @@ import pytest -from deepwork.mcp.state import StateManager -from deepwork.mcp.tools import WorkflowTools +from deepwork.jobs.mcp.state import StateManager +from deepwork.jobs.mcp.tools import WorkflowTools # Test input for deterministic validation TEST_INPUT = "apple, car, banana, chair, orange, table, mango, laptop, grape, bicycle" @@ -249,7 +249,7 @@ async def test_start_workflow_creates_session(self, project_with_job: Path) -> N assert len(fruits_job.workflows) >= 1 workflow_name = fruits_job.workflows[0].name - from deepwork.mcp.schemas import StartWorkflowInput + from deepwork.jobs.mcp.schemas import StartWorkflowInput input_data = StartWorkflowInput( goal="Test identifying and classifying fruits", @@ -283,7 +283,7 @@ async def test_workflow_step_progression(self, project_with_job: Path) -> None: assert len(fruits_job.workflows) >= 1 workflow_name = fruits_job.workflows[0].name - from deepwork.mcp.schemas import FinishedStepInput, StartWorkflowInput + from deepwork.jobs.mcp.schemas import FinishedStepInput, StartWorkflowInput start_input = StartWorkflowInput( goal="Test workflow progression", diff --git a/tests/integration/test_quality_gate_integration.py b/tests/integration/test_quality_gate_integration.py index 1a0f022b..38efa0c0 100644 --- a/tests/integration/test_quality_gate_integration.py +++ b/tests/integration/test_quality_gate_integration.py @@ -13,7 +13,7 @@ # - Use the MockQualityGate class # # If you need to test parsing logic or edge cases, add those tests to: -# tests/unit/mcp/test_quality_gate.py +# tests/unit/jobs/mcp/test_quality_gate.py # # These tests are SKIPPED in CI because they require Claude Code CLI to be # installed and authenticated. They are meant to be run locally during @@ -28,8 +28,8 @@ import pytest -from deepwork.mcp.claude_cli import ClaudeCLI -from deepwork.mcp.quality_gate import QualityGate +from deepwork.jobs.mcp.claude_cli import ClaudeCLI +from deepwork.jobs.mcp.quality_gate import QualityGate # Skip marker for tests that require real Claude CLI # GitHub Actions sets CI=true, as do most other CI systems diff --git a/tests/unit/jobs/__init__.py b/tests/unit/jobs/__init__.py new file mode 100644 index 00000000..3d641a53 --- /dev/null +++ b/tests/unit/jobs/__init__.py @@ -0,0 +1 @@ +"""Tests for jobs module.""" diff --git a/tests/unit/jobs/mcp/__init__.py b/tests/unit/jobs/mcp/__init__.py new file mode 100644 index 00000000..1e754c28 --- /dev/null +++ b/tests/unit/jobs/mcp/__init__.py @@ -0,0 +1 @@ +"""Tests for jobs MCP module.""" diff --git a/tests/unit/mcp/test_async_interface.py b/tests/unit/jobs/mcp/test_async_interface.py similarity index 97% rename from tests/unit/mcp/test_async_interface.py rename to tests/unit/jobs/mcp/test_async_interface.py index fa83f28e..ff69ef0d 100644 --- a/tests/unit/mcp/test_async_interface.py +++ b/tests/unit/jobs/mcp/test_async_interface.py @@ -9,10 +9,10 @@ import inspect from pathlib import Path -from deepwork.mcp.claude_cli import ClaudeCLI -from deepwork.mcp.quality_gate import MockQualityGate, QualityGate -from deepwork.mcp.state import StateManager -from deepwork.mcp.tools import WorkflowTools +from deepwork.jobs.mcp.claude_cli import ClaudeCLI +from deepwork.jobs.mcp.quality_gate import MockQualityGate, QualityGate +from deepwork.jobs.mcp.state import StateManager +from deepwork.jobs.mcp.tools import WorkflowTools class TestAsyncInterfaceRegression: diff --git a/tests/unit/mcp/test_claude_cli.py b/tests/unit/jobs/mcp/test_claude_cli.py similarity index 99% rename from tests/unit/mcp/test_claude_cli.py rename to tests/unit/jobs/mcp/test_claude_cli.py index e910fe0a..72aa2f75 100644 --- a/tests/unit/mcp/test_claude_cli.py +++ b/tests/unit/jobs/mcp/test_claude_cli.py @@ -9,7 +9,7 @@ import pytest -from deepwork.mcp.claude_cli import ClaudeCLI, ClaudeCLIError +from deepwork.jobs.mcp.claude_cli import ClaudeCLI, ClaudeCLIError def create_mock_subprocess( diff --git a/tests/unit/mcp/test_quality_gate.py b/tests/unit/jobs/mcp/test_quality_gate.py similarity index 99% rename from tests/unit/mcp/test_quality_gate.py rename to tests/unit/jobs/mcp/test_quality_gate.py index 55762cd5..023e7b6d 100644 --- a/tests/unit/mcp/test_quality_gate.py +++ b/tests/unit/jobs/mcp/test_quality_gate.py @@ -6,8 +6,8 @@ import pytest -from deepwork.mcp.claude_cli import ClaudeCLI, ClaudeCLIError -from deepwork.mcp.quality_gate import ( +from deepwork.jobs.mcp.claude_cli import ClaudeCLI, ClaudeCLIError +from deepwork.jobs.mcp.quality_gate import ( QUALITY_GATE_RESPONSE_SCHEMA, MockQualityGate, QualityGate, diff --git a/tests/unit/mcp/test_schemas.py b/tests/unit/jobs/mcp/test_schemas.py similarity index 99% rename from tests/unit/mcp/test_schemas.py rename to tests/unit/jobs/mcp/test_schemas.py index bff35472..09c26320 100644 --- a/tests/unit/mcp/test_schemas.py +++ b/tests/unit/jobs/mcp/test_schemas.py @@ -1,6 +1,6 @@ """Tests for MCP schemas.""" -from deepwork.mcp.schemas import ( +from deepwork.jobs.mcp.schemas import ( ActiveStepInfo, ExpectedOutput, FinishedStepInput, diff --git a/tests/unit/mcp/test_state.py b/tests/unit/jobs/mcp/test_state.py similarity index 99% rename from tests/unit/mcp/test_state.py rename to tests/unit/jobs/mcp/test_state.py index c1c1bdd2..9ffbf05c 100644 --- a/tests/unit/mcp/test_state.py +++ b/tests/unit/jobs/mcp/test_state.py @@ -4,7 +4,7 @@ import pytest -from deepwork.mcp.state import StateError, StateManager +from deepwork.jobs.mcp.state import StateError, StateManager @pytest.fixture diff --git a/tests/unit/mcp/test_tools.py b/tests/unit/jobs/mcp/test_tools.py similarity index 99% rename from tests/unit/mcp/test_tools.py rename to tests/unit/jobs/mcp/test_tools.py index ffc2246e..c8be8860 100644 --- a/tests/unit/mcp/test_tools.py +++ b/tests/unit/jobs/mcp/test_tools.py @@ -4,15 +4,15 @@ import pytest -from deepwork.mcp.quality_gate import MockQualityGate -from deepwork.mcp.schemas import ( +from deepwork.jobs.mcp.quality_gate import MockQualityGate +from deepwork.jobs.mcp.schemas import ( AbortWorkflowInput, FinishedStepInput, StartWorkflowInput, StepStatus, ) -from deepwork.mcp.state import StateError, StateManager -from deepwork.mcp.tools import ToolError, WorkflowTools +from deepwork.jobs.mcp.state import StateError, StateManager +from deepwork.jobs.mcp.tools import ToolError, WorkflowTools @pytest.fixture(autouse=True) @@ -22,7 +22,7 @@ def _isolate_job_folders(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Non Each test gets only its own .deepwork/jobs/ directory. """ monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda project_root: [project_root / ".deepwork" / "jobs"], ) @@ -1452,7 +1452,7 @@ class TestExternalRunnerSelfReview: @pytest.fixture def tools_self_review(self, project_root: Path, state_manager: StateManager) -> WorkflowTools: """Create WorkflowTools with quality gate but no external runner (self-review mode).""" - from deepwork.mcp.quality_gate import QualityGate + from deepwork.jobs.mcp.quality_gate import QualityGate return WorkflowTools( project_root=project_root, diff --git a/tests/unit/test_jobs.py b/tests/unit/jobs/test_discovery.py similarity index 92% rename from tests/unit/test_jobs.py rename to tests/unit/jobs/test_discovery.py index ab7aeb2b..6f3e16f8 100644 --- a/tests/unit/test_jobs.py +++ b/tests/unit/jobs/test_discovery.py @@ -1,10 +1,10 @@ -"""Tests for job folder discovery (deepwork.core.jobs).""" +"""Tests for job folder discovery (deepwork.jobs.discovery).""" from pathlib import Path import pytest -from deepwork.core.jobs import ( +from deepwork.jobs.discovery import ( ENV_ADDITIONAL_JOBS_FOLDERS, find_job_dir, get_job_folders, @@ -56,7 +56,7 @@ def test_default_folders_include_project_jobs(self, tmp_path: Path) -> None: # THIS TEST VALIDATES A HARD REQUIREMENT (REQ-008.1.3, REQ-008.1.4). # YOU MUST NOT MODIFY THIS TEST UNLESS THE REQUIREMENT CHANGES def test_default_folders_include_standard_jobs(self, tmp_path: Path) -> None: - from deepwork.core.jobs import _STANDARD_JOBS_DIR + from deepwork.jobs.discovery import _STANDARD_JOBS_DIR folders = get_job_folders(tmp_path) assert _STANDARD_JOBS_DIR in folders @@ -101,7 +101,7 @@ def test_loads_from_project_jobs(self, tmp_path: Path, monkeypatch: pytest.Monke jobs_dir = tmp_path / ".deepwork" / "jobs" _create_minimal_job(jobs_dir, "my_job") monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [pr / ".deepwork" / "jobs"], ) jobs, errors = load_all_jobs(tmp_path) @@ -119,7 +119,7 @@ def test_loads_from_multiple_folders( _create_minimal_job(folder_a, "job_a") _create_minimal_job(folder_b, "job_b") monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [folder_a, folder_b], ) jobs, errors = load_all_jobs(tmp_path) @@ -143,7 +143,7 @@ def test_first_folder_wins_for_duplicate_name( .replace("Test job same_name", "SHOULD NOT APPEAR") ) monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [folder_a, folder_b], ) jobs, errors = load_all_jobs(tmp_path) @@ -156,7 +156,7 @@ def test_skips_nonexistent_folders( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [tmp_path / "does_not_exist"], ) jobs, errors = load_all_jobs(tmp_path) @@ -171,7 +171,7 @@ def test_skips_invalid_jobs(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatc bad_job.mkdir(parents=True) (bad_job / "job.yml").write_text("invalid: [yaml") monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [folder], ) jobs, errors = load_all_jobs(tmp_path) @@ -191,7 +191,7 @@ def test_finds_in_first_folder(self, tmp_path: Path, monkeypatch: pytest.MonkeyP folder = tmp_path / "jobs" _create_minimal_job(folder, "target") monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [folder], ) result = find_job_dir(tmp_path, "target") @@ -205,7 +205,7 @@ def test_finds_in_second_folder(self, tmp_path: Path, monkeypatch: pytest.Monkey folder_a.mkdir() _create_minimal_job(folder_b, "target") monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [folder_a, folder_b], ) result = find_job_dir(tmp_path, "target") @@ -217,7 +217,7 @@ def test_returns_none_when_not_found( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [tmp_path], ) result = find_job_dir(tmp_path, "nonexistent") @@ -233,7 +233,7 @@ def test_prefers_first_folder_on_duplicate( _create_minimal_job(folder_a, "dup") _create_minimal_job(folder_b, "dup") monkeypatch.setattr( - "deepwork.core.jobs.get_job_folders", + "deepwork.jobs.discovery.get_job_folders", lambda pr: [folder_a, folder_b], ) result = find_job_dir(tmp_path, "dup") diff --git a/tests/unit/test_parser.py b/tests/unit/jobs/test_parser.py similarity index 99% rename from tests/unit/test_parser.py rename to tests/unit/jobs/test_parser.py index ef1a2145..b0e77f22 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/jobs/test_parser.py @@ -4,7 +4,7 @@ import pytest -from deepwork.core.parser import ( +from deepwork.jobs.parser import ( JobDefinition, OutputSpec, ParseError, diff --git a/tests/unit/mcp/__init__.py b/tests/unit/mcp/__init__.py deleted file mode 100644 index 34e50282..00000000 --- a/tests/unit/mcp/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for MCP module.""" diff --git a/tests/unit/test_validation.py b/tests/unit/test_validation.py index 8d0dea77..c9f06ef7 100644 --- a/tests/unit/test_validation.py +++ b/tests/unit/test_validation.py @@ -2,7 +2,7 @@ import pytest -from deepwork.schemas.job_schema import JOB_SCHEMA +from deepwork.jobs.schema import JOB_SCHEMA from deepwork.utils.validation import ValidationError, validate_against_schema From 9c3b0396ef5c97b265366bd6d369f801cde617a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 19:22:43 +0000 Subject: [PATCH 2/3] Update uv.lock after namespace refactoring https://claude.ai/code/session_017aa4FwLj9etksQHyd5wuKE --- uv.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/uv.lock b/uv.lock index 6e527013..72f95f40 100644 --- a/uv.lock +++ b/uv.lock @@ -459,13 +459,10 @@ dependencies = [ { name = "aiofiles" }, { name = "click" }, { name = "fastmcp" }, - { name = "gitpython" }, - { name = "jinja2" }, { name = "jsonschema" }, { name = "mcp" }, { name = "pydantic" }, { name = "pyyaml" }, - { name = "rich" }, ] [package.optional-dependencies] @@ -483,10 +480,13 @@ dev = [ [package.dev-dependencies] dev = [ { name = "fpdf2" }, + { name = "gitpython" }, + { name = "jinja2" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-mock" }, + { name = "rich" }, ] [package.metadata] @@ -494,8 +494,6 @@ requires-dist = [ { name = "aiofiles", specifier = ">=24.0.0" }, { name = "click", specifier = ">=8.1.0" }, { name = "fastmcp", specifier = ">=2.0" }, - { name = "gitpython", specifier = ">=3.1.0" }, - { name = "jinja2", specifier = ">=3.1.0" }, { name = "jsonschema", specifier = ">=4.17.0" }, { name = "mcp", specifier = ">=1.0.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0" }, @@ -505,7 +503,6 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0" }, { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.10" }, { name = "pyyaml", specifier = ">=6.0" }, - { name = "rich", specifier = ">=13.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, { name = "types-aiofiles", marker = "extra == 'dev'" }, { name = "types-pyyaml", marker = "extra == 'dev'" }, @@ -515,10 +512,13 @@ provides-extras = ["dev"] [package.metadata.requires-dev] dev = [ { name = "fpdf2", specifier = ">=2.8.5" }, + { name = "gitpython", specifier = ">=3.1.0" }, + { name = "jinja2", specifier = ">=3.1.0" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pytest-mock", specifier = ">=3.15.1" }, + { name = "rich", specifier = ">=13.0.0" }, ] [[package]] From cdc5dc90f33a4cf143151737c4add65ef6d29813 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Feb 2026 19:57:21 +0000 Subject: [PATCH 3/3] Fix ruff import sorting in jobs/mcp/tools.py https://claude.ai/code/session_017aa4FwLj9etksQHyd5wuKE --- src/deepwork/jobs/mcp/tools.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/deepwork/jobs/mcp/tools.py b/src/deepwork/jobs/mcp/tools.py index 38302af1..ad6c2c2c 100644 --- a/src/deepwork/jobs/mcp/tools.py +++ b/src/deepwork/jobs/mcp/tools.py @@ -15,13 +15,6 @@ import aiofiles from deepwork.jobs.discovery import JobLoadError, find_job_dir, load_all_jobs -from deepwork.jobs.parser import ( - JobDefinition, - OutputSpec, - ParseError, - Workflow, - parse_job_definition, -) from deepwork.jobs.mcp.schemas import ( AbortWorkflowInput, AbortWorkflowResponse, @@ -39,6 +32,13 @@ WorkflowInfo, ) from deepwork.jobs.mcp.state import StateManager +from deepwork.jobs.parser import ( + JobDefinition, + OutputSpec, + ParseError, + Workflow, + parse_job_definition, +) logger = logging.getLogger("deepwork.jobs.mcp")