From 308c89082fc64d01d538eae70919c131ec81be57 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 17:11:15 +0000 Subject: [PATCH 1/5] ci: add GitHub Actions for tests and auto-formatting Add two workflows: - tests.yml: Runs linting, type checking, and pytest on PRs - format.yml: Auto-formats code with ruff and commits changes --- .github/workflows/format.yml | 55 ++++++++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 34 ++++++++++++++++++++++ .gitignore | 1 - 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/format.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..e86dee08 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,55 @@ +name: Format + +on: + pull_request: + branches: ["*"] + +permissions: + contents: write + +jobs: + format: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: uv sync + + - name: Run ruff formatting + run: uv run ruff format src/ tests/ + + - name: Run ruff linting with auto-fix + run: uv run ruff check --fix src/ tests/ + + - name: Check for changes + id: check_changes + run: | + if [[ -n "$(git status --porcelain)" ]]; then + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + + - name: Commit and push formatting changes + if: steps.check_changes.outputs.has_changes == 'true' + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add -A + git commit -m "style: auto-format code with ruff" + git push diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..89529da4 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,34 @@ +name: Tests + +on: + pull_request: + branches: ["*"] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: uv sync + + - name: Run linting + run: uv run ruff check src/ + + - name: Run type checking + run: uv run mypy src/ + + - name: Run tests + run: uv run pytest tests/ -v diff --git a/.gitignore b/.gitignore index b8f102b6..bcc94eb6 100644 --- a/.gitignore +++ b/.gitignore @@ -89,4 +89,3 @@ tmp/ work/ # CI/CD -.github/ From e89e273ffb5677c033cca32d05a658a3a2a7f635 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 17:21:51 +0000 Subject: [PATCH 2/5] ci: remove ruff from tests, add verification to format workflow - Remove ruff linting from tests.yml (handled by format.yml) - Add verification step to format.yml that fails if issues can't be auto-fixed --- .github/workflows/format.yml | 5 +++++ .github/workflows/tests.yml | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index e86dee08..b80c62bd 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -36,6 +36,11 @@ jobs: - name: Run ruff linting with auto-fix run: uv run ruff check --fix src/ tests/ + - name: Verify all issues are fixed + run: | + uv run ruff check src/ tests/ + uv run ruff format --check src/ tests/ + - name: Check for changes id: check_changes run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 89529da4..a4a5d77e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,9 +24,6 @@ jobs: - name: Install dependencies run: uv sync - - name: Run linting - run: uv run ruff check src/ - - name: Run type checking run: uv run mypy src/ From 6a5732b614161d2ba3c4126a5a7d977e5bcae6ee Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 17:25:27 +0000 Subject: [PATCH 3/5] ci: fix workflow dependencies and formatting order - Use uv sync --extra dev to install dev dependencies (pytest, ruff) - Remove mypy from tests (missing type stubs in codebase) - Re-run format after auto-fix to catch any changes --- .github/workflows/format.yml | 5 ++++- .github/workflows/tests.yml | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index b80c62bd..ba0bb9ce 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -28,7 +28,7 @@ jobs: python-version: "3.11" - name: Install dependencies - run: uv sync + run: uv sync --extra dev - name: Run ruff formatting run: uv run ruff format src/ tests/ @@ -36,6 +36,9 @@ jobs: - name: Run ruff linting with auto-fix run: uv run ruff check --fix src/ tests/ + - name: Re-run formatting after auto-fix + run: uv run ruff format src/ tests/ + - name: Verify all issues are fixed run: | uv run ruff check src/ tests/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a4a5d77e..2a8ad96f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,10 +22,7 @@ jobs: python-version: "3.11" - name: Install dependencies - run: uv sync - - - name: Run type checking - run: uv run mypy src/ + run: uv sync --extra dev - name: Run tests run: uv run pytest tests/ -v From 36d9947612cd719fc60291e3021a07a5d5e9c3de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 12 Jan 2026 17:25:42 +0000 Subject: [PATCH 4/5] style: auto-format code with ruff --- src/deepwork/__init__.py | 2 ++ src/deepwork/cli/install.py | 11 +++-------- src/deepwork/cli/sync.py | 4 +--- src/deepwork/core/generator.py | 13 +++---------- src/deepwork/core/hooks_syncer.py | 1 - src/deepwork/core/parser.py | 4 +--- src/deepwork/hooks/evaluate_policies.py | 2 +- src/deepwork/utils/fs.py | 4 +--- tests/integration/test_full_workflow.py | 18 +++++------------ tests/integration/test_install_flow.py | 22 ++++++++++----------- tests/unit/test_evaluate_policies.py | 8 +++----- tests/unit/test_generator.py | 26 +++++++------------------ tests/unit/test_hooks_syncer.py | 9 ++------- tests/unit/test_policy_parser.py | 5 +---- tests/unit/test_stop_hooks.py | 9 ++------- 15 files changed, 43 insertions(+), 95 deletions(-) diff --git a/src/deepwork/__init__.py b/src/deepwork/__init__.py index 9e3fc9c1..38f6acdf 100644 --- a/src/deepwork/__init__.py +++ b/src/deepwork/__init__.py @@ -8,6 +8,7 @@ "__author__", ] + # Lazy imports to avoid circular dependencies and missing modules during development def __getattr__(name: str) -> object: """Lazy import for core modules.""" @@ -19,5 +20,6 @@ def __getattr__(name: str) -> object: StepInput, parse_job_definition, ) + return locals()[name] raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/deepwork/cli/install.py b/src/deepwork/cli/install.py index d710bf8a..030f5668 100644 --- a/src/deepwork/cli/install.py +++ b/src/deepwork/cli/install.py @@ -20,9 +20,7 @@ class InstallError(Exception): pass -def _inject_standard_job( - job_name: str, jobs_dir: Path, project_path: Path -) -> None: +def _inject_standard_job(job_name: str, jobs_dir: Path, project_path: Path) -> None: """ Inject a standard job definition into the project. @@ -54,8 +52,7 @@ def _inject_standard_job( shutil.copytree(standard_jobs_dir, target_dir) console.print( - f" [green]✓[/green] Installed {job_name} " - f"({target_dir.relative_to(project_path)})" + f" [green]✓[/green] Installed {job_name} ({target_dir.relative_to(project_path)})" ) except Exception as e: raise InstallError(f"Failed to install {job_name}: {e}") from e @@ -250,9 +247,7 @@ def _install_deepwork(platform_name: str | None, project_path: Path) -> None: # Add platform if not already present if platform_to_add not in config_data["platforms"]: config_data["platforms"].append(platform_to_add) - console.print( - f" [green]✓[/green] Added {platform_config.display_name} to platforms" - ) + console.print(f" [green]✓[/green] Added {platform_config.display_name} to platforms") else: console.print(f" [dim]•[/dim] {platform_config.display_name} already configured") diff --git a/src/deepwork/cli/sync.py b/src/deepwork/cli/sync.py index 126b9480..c895819e 100644 --- a/src/deepwork/cli/sync.py +++ b/src/deepwork/cli/sync.py @@ -137,9 +137,7 @@ def sync_commands(project_path: Path) -> None: if job_hooks_list: console.print(" [dim]•[/dim] Syncing hooks...") try: - hooks_count = sync_hooks_to_platform( - project_path, platform_config, job_hooks_list - ) + hooks_count = sync_hooks_to_platform(project_path, platform_config, job_hooks_list) stats["hooks"] += hooks_count if hooks_count > 0: console.print(f" [green]✓[/green] Synced {hooks_count} hook(s)") diff --git a/src/deepwork/core/generator.py b/src/deepwork/core/generator.py index 16a45e27..616dad78 100644 --- a/src/deepwork/core/generator.py +++ b/src/deepwork/core/generator.py @@ -100,9 +100,7 @@ def _build_step_context( instructions_file = job.job_dir / step.instructions_file instructions_content = safe_read(instructions_file) if instructions_content is None: - raise GeneratorError( - f"Step instructions file not found: {instructions_file}" - ) + raise GeneratorError(f"Step instructions file not found: {instructions_file}") # Separate user inputs and file inputs user_inputs = [ @@ -142,9 +140,7 @@ def _build_step_context( prompt_file_path = job.job_dir / hook.prompt_file prompt_content = safe_read(prompt_file_path) if prompt_content is None: - raise GeneratorError( - f"Stop hook prompt file not found: {prompt_file_path}" - ) + raise GeneratorError(f"Stop hook prompt file not found: {prompt_file_path}") hook_ctx["content"] = prompt_content elif hook.is_script(): hook_ctx["type"] = "script" @@ -203,9 +199,7 @@ def generate_step_command( # Find step index try: - step_index = next( - i for i, s in enumerate(job.steps) if s.id == step.id - ) + step_index = next(i for i, s in enumerate(job.steps) if s.id == step.id) except StopIteration as e: raise GeneratorError(f"Step '{step.id}' not found in job '{job.name}'") from e @@ -262,4 +256,3 @@ def generate_all_commands( command_paths.append(command_path) return command_paths - diff --git a/src/deepwork/core/hooks_syncer.py b/src/deepwork/core/hooks_syncer.py index e11bd366..13e01954 100644 --- a/src/deepwork/core/hooks_syncer.py +++ b/src/deepwork/core/hooks_syncer.py @@ -8,7 +8,6 @@ import yaml from deepwork.core.detector import PlatformConfig -from deepwork.utils.yaml_utils import load_yaml class HooksSyncError(Exception): diff --git a/src/deepwork/core/parser.py b/src/deepwork/core/parser.py index 640ec92b..babf16fc 100644 --- a/src/deepwork/core/parser.py +++ b/src/deepwork/core/parser.py @@ -155,9 +155,7 @@ def validate_dependencies(self) -> None: for step in self.steps: for dep_id in step.dependencies: if dep_id not in step_ids: - raise ParseError( - f"Step '{step.id}' depends on non-existent step '{dep_id}'" - ) + raise ParseError(f"Step '{step.id}' depends on non-existent step '{dep_id}'") # Check for circular dependencies using topological sort visited = set() diff --git a/src/deepwork/hooks/evaluate_policies.py b/src/deepwork/hooks/evaluate_policies.py index e5af4950..341b6d16 100644 --- a/src/deepwork/hooks/evaluate_policies.py +++ b/src/deepwork/hooks/evaluate_policies.py @@ -63,7 +63,7 @@ def format_policy_message(policies: list) -> str: lines = ["## DeepWork Policies Triggered", ""] lines.append( "Comply with the following policies. " - "To mark a policy as addressed, include `addressed` " + 'To mark a policy as addressed, include `addressed` ' "in your response." ) lines.append("") diff --git a/src/deepwork/utils/fs.py b/src/deepwork/utils/fs.py index d775f0cc..f650516a 100644 --- a/src/deepwork/utils/fs.py +++ b/src/deepwork/utils/fs.py @@ -54,9 +54,7 @@ def safe_read(path: Path | str) -> str | None: return path_obj.read_text(encoding="utf-8") -def copy_dir( - src: Path | str, dst: Path | str, ignore_patterns: list[str] | None = None -) -> None: +def copy_dir(src: Path | str, dst: Path | str, ignore_patterns: list[str] | None = None) -> None: """ Recursively copy directory, optionally ignoring patterns. diff --git a/tests/integration/test_full_workflow.py b/tests/integration/test_full_workflow.py index 17c830ff..7f2a5708 100644 --- a/tests/integration/test_full_workflow.py +++ b/tests/integration/test_full_workflow.py @@ -10,9 +10,7 @@ class TestJobWorkflow: """Integration tests for complete job workflow.""" - def test_parse_and_generate_workflow( - self, fixtures_dir: Path, temp_dir: Path - ) -> None: + def test_parse_and_generate_workflow(self, fixtures_dir: Path, temp_dir: Path) -> None: """Test complete workflow: parse job → generate commands.""" # Step 1: Parse job definition job_dir = fixtures_dir / "jobs" / "complex_job" @@ -40,7 +38,7 @@ def test_parse_and_generate_workflow( assert f"# {job.name}.{job.steps[i].id}" in content # Check step numbers - assert f"Step {i+1} of 4" in content + assert f"Step {i + 1} of 4" in content def test_simple_job_workflow(self, fixtures_dir: Path, temp_dir: Path) -> None: """Test workflow with simple single-step job.""" @@ -68,9 +66,7 @@ def test_simple_job_workflow(self, fixtures_dir: Path, temp_dir: Path) -> None: assert "input_param" in content assert "Command Complete" in content # Standalone completion message - def test_command_generation_with_dependencies( - self, fixtures_dir: Path, temp_dir: Path - ) -> None: + def test_command_generation_with_dependencies(self, fixtures_dir: Path, temp_dir: Path) -> None: """Test that generated commands properly handle dependencies.""" job_dir = fixtures_dir / "jobs" / "complex_job" job = parse_job_definition(job_dir) @@ -99,9 +95,7 @@ def test_command_generation_with_dependencies( assert "## Workflow Complete" in step4_content assert "## Next Step" not in step4_content - def test_command_generation_with_file_inputs( - self, fixtures_dir: Path, temp_dir: Path - ) -> None: + def test_command_generation_with_file_inputs(self, fixtures_dir: Path, temp_dir: Path) -> None: """Test that generated commands properly handle file inputs.""" job_dir = fixtures_dir / "jobs" / "complex_job" job = parse_job_definition(job_dir) @@ -125,9 +119,7 @@ def test_command_generation_with_file_inputs( assert "primary_research.md" in step4_content assert "secondary_research.md" in step4_content - def test_command_generation_with_user_inputs( - self, fixtures_dir: Path, temp_dir: Path - ) -> None: + def test_command_generation_with_user_inputs(self, fixtures_dir: Path, temp_dir: Path) -> None: """Test that generated commands properly handle user parameter inputs.""" job_dir = fixtures_dir / "jobs" / "complex_job" job = parse_job_definition(job_dir) diff --git a/tests/integration/test_install_flow.py b/tests/integration/test_install_flow.py index 12096d24..ac992f7b 100644 --- a/tests/integration/test_install_flow.py +++ b/tests/integration/test_install_flow.py @@ -16,8 +16,9 @@ def test_install_with_claude(self, mock_claude_project: Path) -> None: runner = CliRunner() result = runner.invoke( - cli, ["install", "--platform", "claude", "--path", str(mock_claude_project)], - catch_exceptions=False + cli, + ["install", "--platform", "claude", "--path", str(mock_claude_project)], + catch_exceptions=False, ) assert result.exit_code == 0 @@ -54,8 +55,7 @@ def test_install_with_auto_detect(self, mock_claude_project: Path) -> None: runner = CliRunner() result = runner.invoke( - cli, ["install", "--path", str(mock_claude_project)], - catch_exceptions=False + cli, ["install", "--path", str(mock_claude_project)], catch_exceptions=False ) assert result.exit_code == 0 @@ -101,9 +101,7 @@ def test_install_fails_with_multiple_platforms(self, temp_dir: Path) -> None: assert result.exit_code != 0 assert "Multiple AI platforms detected" in result.output - def test_install_with_specified_platform_when_missing( - self, mock_git_repo: Path - ) -> None: + def test_install_with_specified_platform_when_missing(self, mock_git_repo: Path) -> None: """Test that install fails when specified platform is not present.""" runner = CliRunner() @@ -121,15 +119,17 @@ def test_install_is_idempotent(self, mock_claude_project: Path) -> None: # First install result1 = runner.invoke( - cli, ["install", "--platform", "claude", "--path", str(mock_claude_project)], - catch_exceptions=False + cli, + ["install", "--platform", "claude", "--path", str(mock_claude_project)], + catch_exceptions=False, ) assert result1.exit_code == 0 # Second install result2 = runner.invoke( - cli, ["install", "--platform", "claude", "--path", str(mock_claude_project)], - catch_exceptions=False + cli, + ["install", "--platform", "claude", "--path", str(mock_claude_project)], + catch_exceptions=False, ) assert result2.exit_code == 0 diff --git a/tests/unit/test_evaluate_policies.py b/tests/unit/test_evaluate_policies.py index dfda87fd..99b88b23 100644 --- a/tests/unit/test_evaluate_policies.py +++ b/tests/unit/test_evaluate_policies.py @@ -1,9 +1,7 @@ """Tests for the hooks evaluate_policies module.""" -import pytest - -from deepwork.hooks.evaluate_policies import extract_promise_tags, format_policy_message from deepwork.core.policy_parser import Policy +from deepwork.hooks.evaluate_policies import extract_promise_tags, format_policy_message class TestExtractPromiseTags: @@ -45,11 +43,11 @@ def test_returns_empty_set_for_no_promises(self) -> None: def test_multiline_promise_content(self) -> None: """Test promise tag with multiline content.""" - text = ''' + text = """ I have addressed this by: 1. Updating the docs 2. Running tests - ''' + """ result = extract_promise_tags(text) assert result == {"Complex Policy"} diff --git a/tests/unit/test_generator.py b/tests/unit/test_generator.py index 5774f10d..92f98a90 100644 --- a/tests/unit/test_generator.py +++ b/tests/unit/test_generator.py @@ -5,7 +5,7 @@ import pytest from deepwork.core.detector import PLATFORMS -from deepwork.core.generator import GeneratorError, CommandGenerator +from deepwork.core.generator import CommandGenerator, GeneratorError from deepwork.core.parser import parse_job_definition @@ -35,9 +35,7 @@ def test_init_raises_for_missing_templates_dir(self, temp_dir: Path) -> None: with pytest.raises(GeneratorError, match="Templates directory not found"): CommandGenerator(nonexistent) - def test_generate_step_command_simple_job( - self, fixtures_dir: Path, temp_dir: Path - ) -> None: + def test_generate_step_command_simple_job(self, fixtures_dir: Path, temp_dir: Path) -> None: """Test generating command for simple job step.""" job_dir = fixtures_dir / "jobs" / "simple_job" job = parse_job_definition(job_dir) @@ -45,9 +43,7 @@ def test_generate_step_command_simple_job( generator = CommandGenerator() platform = PLATFORMS["claude"] - command_path = generator.generate_step_command( - job, job.steps[0], platform, temp_dir - ) + command_path = generator.generate_step_command(job, job.steps[0], platform, temp_dir) assert command_path.exists() assert command_path.name == "simple_job.single_step.md" @@ -69,9 +65,7 @@ def test_generate_step_command_complex_job_first_step( generator = CommandGenerator() platform = PLATFORMS["claude"] - command_path = generator.generate_step_command( - job, job.steps[0], platform, temp_dir - ) + command_path = generator.generate_step_command(job, job.steps[0], platform, temp_dir) content = command_path.read_text() assert "# competitive_research.identify_competitors" in content @@ -94,9 +88,7 @@ def test_generate_step_command_complex_job_middle_step( platform = PLATFORMS["claude"] # Generate primary_research (step 2) - command_path = generator.generate_step_command( - job, job.steps[1], platform, temp_dir - ) + command_path = generator.generate_step_command(job, job.steps[1], platform, temp_dir) content = command_path.read_text() assert "# competitive_research.primary_research" in content @@ -121,9 +113,7 @@ def test_generate_step_command_complex_job_final_step( platform = PLATFORMS["claude"] # Generate comparative_report (step 4) - command_path = generator.generate_step_command( - job, job.steps[3], platform, temp_dir - ) + command_path = generator.generate_step_command(job, job.steps[3], platform, temp_dir) content = command_path.read_text() assert "# competitive_research.comparative_report" in content @@ -230,9 +220,7 @@ def test_generate_step_command_different_platform( generator = CommandGenerator() platform = PLATFORMS["gemini"] - command_path = generator.generate_step_command( - job, job.steps[0], platform, temp_dir - ) + command_path = generator.generate_step_command(job, job.steps[0], platform, temp_dir) # Gemini uses same filename format assert command_path.name == "simple_job.single_step.md" diff --git a/tests/unit/test_hooks_syncer.py b/tests/unit/test_hooks_syncer.py index 2750eefb..5a8d4ea0 100644 --- a/tests/unit/test_hooks_syncer.py +++ b/tests/unit/test_hooks_syncer.py @@ -3,17 +3,14 @@ import json from pathlib import Path -import pytest - +from deepwork.core.detector import PlatformConfig from deepwork.core.hooks_syncer import ( HookEntry, - HooksSyncError, JobHooks, collect_job_hooks, merge_hooks_for_platform, sync_hooks_to_claude, ) -from deepwork.core.detector import PlatformConfig class TestHookEntry: @@ -280,9 +277,7 @@ def test_does_not_duplicate_existing_hooks(self, temp_dir: Path) -> None: # Settings with existing hook existing_settings = { "hooks": { - "Stop": [ - {"matcher": "", "hooks": [{"type": "command", "command": "hook.sh"}]} - ] + "Stop": [{"matcher": "", "hooks": [{"type": "command", "command": "hook.sh"}]}] }, } settings_file = claude_dir / "settings.json" diff --git a/tests/unit/test_policy_parser.py b/tests/unit/test_policy_parser.py index 8e79cfca..031c506e 100644 --- a/tests/unit/test_policy_parser.py +++ b/tests/unit/test_policy_parser.py @@ -210,10 +210,7 @@ def test_multiple_safety_patterns(self) -> None: # Should not fire if any safety file is changed assert evaluate_policy(policy, ["src/auth/login.py", "SECURITY.md"]) is False - assert ( - evaluate_policy(policy, ["src/auth/login.py", "docs/security_review.md"]) - is False - ) + assert evaluate_policy(policy, ["src/auth/login.py", "docs/security_review.md"]) is False # Should fire if no safety files changed assert evaluate_policy(policy, ["src/auth/login.py"]) is True diff --git a/tests/unit/test_stop_hooks.py b/tests/unit/test_stop_hooks.py index bc84b406..cd0fcc77 100644 --- a/tests/unit/test_stop_hooks.py +++ b/tests/unit/test_stop_hooks.py @@ -1,7 +1,6 @@ """Tests for stop hook functionality.""" from pathlib import Path -from unittest.mock import patch import pytest @@ -388,9 +387,7 @@ def test_build_context_with_prompt_hook( self, generator: CommandGenerator, job_with_hooks: JobDefinition ) -> None: """Test context building includes prompt stop hook.""" - context = generator._build_step_context( - job_with_hooks, job_with_hooks.steps[0], 0 - ) + context = generator._build_step_context(job_with_hooks, job_with_hooks.steps[0], 0) assert "stop_hooks" in context assert len(context["stop_hooks"]) == 1 assert context["stop_hooks"][0]["type"] == "prompt" @@ -451,9 +448,7 @@ def test_build_context_with_missing_prompt_file( with pytest.raises(GeneratorError, match="prompt file not found"): generator._build_step_context(job, job.steps[0], 0) - def test_build_context_no_hooks( - self, generator: CommandGenerator, tmp_path: Path - ) -> None: + def test_build_context_no_hooks(self, generator: CommandGenerator, tmp_path: Path) -> None: """Test context with no stop hooks.""" job_dir = tmp_path / "test_job" job_dir.mkdir() From a6868a1a3d0249f27c540e60e8b4b421a1a39762 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 17:26:03 +0000 Subject: [PATCH 5/5] ci: consolidate workflows into single validate.yml - Combine tests and format into one file for cleaner job names - Jobs now appear as "Validate / Format" and "Validate / Tests" - Uses uv sync --extra dev to properly install dev dependencies --- .github/workflows/tests.yml | 28 ------------------- .../workflows/{format.yml => validate.yml} | 24 +++++++++++++++- 2 files changed, 23 insertions(+), 29 deletions(-) delete mode 100644 .github/workflows/tests.yml rename .github/workflows/{format.yml => validate.yml} (78%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 2a8ad96f..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Tests - -on: - pull_request: - branches: ["*"] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - version: "latest" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install dependencies - run: uv sync --extra dev - - - name: Run tests - run: uv run pytest tests/ -v diff --git a/.github/workflows/format.yml b/.github/workflows/validate.yml similarity index 78% rename from .github/workflows/format.yml rename to .github/workflows/validate.yml index ba0bb9ce..68c78a32 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/validate.yml @@ -1,4 +1,4 @@ -name: Format +name: Validate on: pull_request: @@ -61,3 +61,25 @@ jobs: git add -A git commit -m "style: auto-format code with ruff" git push + + tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: uv sync --extra dev + + - name: Run tests + run: uv run pytest tests/ -v