From c4a822c3494d2b1211e00537633f3b3d3c4202ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:06:07 +0000 Subject: [PATCH 1/2] Initial plan From 85bec3b4d88078a205ccae0a681fbdb5ff53b4c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 00:11:17 +0000 Subject: [PATCH 2/2] Show job source folders when DEEPWORK_ADDITIONAL_JOBS_FOLDERS is set Co-authored-by: ncrmro <8276365+ncrmro@users.noreply.github.com> Agent-Logs-Url: https://github.com/Unsupervisedcom/deepwork/sessions/a27ec3ce-8be9-495f-b665-6273dcb0f8ac --- src/deepwork/jobs/discovery.py | 28 +++++++++++ src/deepwork/jobs/mcp/server.py | 24 +++++++++ tests/unit/jobs/test_discovery.py | 82 +++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/src/deepwork/jobs/discovery.py b/src/deepwork/jobs/discovery.py index fe67d387..61868878 100644 --- a/src/deepwork/jobs/discovery.py +++ b/src/deepwork/jobs/discovery.py @@ -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]]: @@ -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 @@ -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( @@ -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 diff --git a/src/deepwork/jobs/mcp/server.py b/src/deepwork/jobs/mcp/server.py index 7743efa0..7256695e 100644 --- a/src/deepwork/jobs/mcp/server.py +++ b/src/deepwork/jobs/mcp/server.py @@ -14,6 +14,7 @@ from __future__ import annotations import logging +import os import shutil from pathlib import Path from typing import Any @@ -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, @@ -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") diff --git a/tests/unit/jobs/test_discovery.py b/tests/unit/jobs/test_discovery.py index 64c52184..ed309757 100644 --- a/tests/unit/jobs/test_discovery.py +++ b/tests/unit/jobs/test_discovery.py @@ -1,5 +1,6 @@ """Tests for job folder discovery (deepwork.jobs.discovery).""" +import logging from pathlib import Path import pytest @@ -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" + )