Skip to content
Draft
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
28 changes: 28 additions & 0 deletions src/deepwork/jobs/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ def get_job_folders(project_root: Path) -> list[Path]:
return folders


def _get_extra_folders() -> set[Path]:
"""Return the set of extra job folders from the environment variable.

Parses the same environment variable as :func:`get_job_folders` using
identical logic, returning only the *extra* entries as a set.
"""
raw = os.environ.get(ENV_ADDITIONAL_JOBS_FOLDERS, "")
if not raw:
return set()
return {
Path(entry.strip())
for entry in raw.split(":")
if entry.strip()
}


def load_all_jobs(
project_root: Path,
) -> tuple[list[JobDefinition], list[JobLoadError]]:
Expand All @@ -79,10 +95,13 @@ def load_all_jobs(
jobs: list[JobDefinition] = []
errors: list[JobLoadError] = []

extra_folders = _get_extra_folders()

for folder in get_job_folders(project_root):
if not folder.exists() or not folder.is_dir():
continue

folder_jobs: list[str] = []
for job_dir in sorted(folder.iterdir()):
if not job_dir.is_dir() or not (job_dir / "job.yml").exists():
continue
Expand All @@ -94,6 +113,7 @@ def load_all_jobs(
job = parse_job_definition(job_dir)
jobs.append(job)
seen_names.add(job_dir.name)
folder_jobs.append(job_dir.name)
except ParseError as e:
logger.warning("Skipping invalid job '%s': %s", job_dir.name, e)
errors.append(
Expand All @@ -104,6 +124,14 @@ def load_all_jobs(
)
)

if folder_jobs and folder in extra_folders:
logger.info(
"Loaded %d job(s) from shared folder %s: %s",
len(folder_jobs),
folder,
", ".join(folder_jobs),
)

return jobs, errors


Expand Down
24 changes: 24 additions & 0 deletions src/deepwork/jobs/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from __future__ import annotations

import logging
import os
import shutil
from pathlib import Path
from typing import Any
Expand Down Expand Up @@ -56,6 +57,26 @@ def _ensure_schema_available(project_root: Path) -> None:
logger.warning("Could not copy schema to %s", target)


def _log_job_source_folders(project_root: Path) -> None:
"""Log the job source folders at startup.

When DEEPWORK_ADDITIONAL_JOBS_FOLDERS is set, logs a notice showing all
job source folders so users can see where jobs are installed from.
"""
from deepwork.jobs.discovery import ENV_ADDITIONAL_JOBS_FOLDERS, get_job_folders

extra_raw = os.environ.get(ENV_ADDITIONAL_JOBS_FOLDERS, "")
if not extra_raw:
return

folders = get_job_folders(project_root)
logger.info(
"Job source folders (%s is set): %s",
ENV_ADDITIONAL_JOBS_FOLDERS,
", ".join(str(f) for f in folders),
)


def create_server(
project_root: Path | str,
enable_quality_gate: bool = True,
Expand Down Expand Up @@ -85,6 +106,9 @@ def create_server(
# Copy the job schema to a stable location so agents can always reference it
_ensure_schema_available(project_path)

# Log job source folders, highlighting any shared/extra folders
_log_job_source_folders(project_path)

# Initialize components
state_manager = StateManager(project_root=project_path, platform=platform or "claude")

Expand Down
82 changes: 82 additions & 0 deletions tests/unit/jobs/test_discovery.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for job folder discovery (deepwork.jobs.discovery)."""

import logging
from pathlib import Path

import pytest
Expand Down Expand Up @@ -238,3 +239,84 @@ def test_prefers_first_folder_on_duplicate(
)
result = find_job_dir(tmp_path, "dup")
assert result == folder_a / "dup"


class TestLoadAllJobsLogging:
"""Tests for logging behavior when DEEPWORK_ADDITIONAL_JOBS_FOLDERS is set."""

def test_logs_jobs_loaded_from_extra_folder(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
"""Jobs loaded from extra folders are logged at INFO level."""
extra_folder = tmp_path / "shared_jobs"
_create_minimal_job(extra_folder, "shared_job")

monkeypatch.setenv(ENV_ADDITIONAL_JOBS_FOLDERS, str(extra_folder))

with caplog.at_level(logging.INFO, logger="deepwork.jobs.discovery"):
load_all_jobs(tmp_path)

matching = [
r for r in caplog.records
if str(extra_folder) in r.message and "shared_job" in r.message
]
assert matching, (
"Expected a single log message containing both the extra folder path "
"and the loaded job name"
)

def test_no_extra_logging_without_env_var(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
"""No extra-folder log messages when DEEPWORK_ADDITIONAL_JOBS_FOLDERS is not set."""
monkeypatch.delenv(ENV_ADDITIONAL_JOBS_FOLDERS, raising=False)

with caplog.at_level(logging.INFO, logger="deepwork.jobs.discovery"):
load_all_jobs(tmp_path)

shared_folder_logs = [
r for r in caplog.records if "shared folder" in r.message.lower()
]
assert len(shared_folder_logs) == 0, (
"No shared-folder log messages should appear when env var is not set"
)


class TestServerJobSourceLogging:
"""Tests for _log_job_source_folders in server.py."""

def test_logs_all_folders_when_env_var_set(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
"""When DEEPWORK_ADDITIONAL_JOBS_FOLDERS is set, all job folders are logged."""
from deepwork.jobs.discovery import ENV_ADDITIONAL_JOBS_FOLDERS
from deepwork.jobs.mcp.server import _log_job_source_folders

extra = tmp_path / "extra_jobs"
monkeypatch.setenv(ENV_ADDITIONAL_JOBS_FOLDERS, str(extra))

with caplog.at_level(logging.INFO, logger="deepwork.jobs.mcp"):
_log_job_source_folders(tmp_path)

assert any(ENV_ADDITIONAL_JOBS_FOLDERS in record.message for record in caplog.records), (
"Log message should mention the environment variable name"
)
assert any(str(extra) in record.message for record in caplog.records), (
"Log message should include the extra folder path"
)

def test_no_log_when_env_var_not_set(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
"""When DEEPWORK_ADDITIONAL_JOBS_FOLDERS is not set, nothing is logged."""
from deepwork.jobs.discovery import ENV_ADDITIONAL_JOBS_FOLDERS
from deepwork.jobs.mcp.server import _log_job_source_folders

monkeypatch.delenv(ENV_ADDITIONAL_JOBS_FOLDERS, raising=False)

with caplog.at_level(logging.INFO, logger="deepwork.jobs.mcp"):
_log_job_source_folders(tmp_path)

assert len(caplog.records) == 0, (
"No log messages should appear when env var is not set"
)