From b682404471cf43fdce2d60f9e6eadf8f49ab6377 Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Sun, 22 Feb 2026 11:55:34 -0700 Subject: [PATCH 1/3] fix(nix): Simplify flake to dev environment only, remove uv2nix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove pyproject-nix, uv2nix, and pyproject-build-systems inputs. Remove packages/apps outputs — deepwork is not meant to be consumed as a nix dependency. The flake now only provides a devShell with system tools (python, uv, git, jq, gh, claude) and uses `uv tool install -e` to provide the editable deepwork CLI with dev dependencies. Co-Authored-By: Claude Opus 4.6 --- flake.lock | 74 +------------------------------------ flake.nix | 106 +++++------------------------------------------------ 2 files changed, 10 insertions(+), 170 deletions(-) diff --git a/flake.lock b/flake.lock index e5382a40..06c23b58 100644 --- a/flake.lock +++ b/flake.lock @@ -69,59 +69,10 @@ "type": "github" } }, - "pyproject-build-systems": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ], - "pyproject-nix": [ - "pyproject-nix" - ], - "uv2nix": [ - "uv2nix" - ] - }, - "locked": { - "lastModified": 1771423342, - "narHash": "sha256-7uXPiWB0YQ4HNaAqRvVndYL34FEp1ZTwVQHgZmyMtC8=", - "owner": "pyproject-nix", - "repo": "build-system-pkgs", - "rev": "04e9c186e01f0830dad3739088070e4c551191a4", - "type": "github" - }, - "original": { - "owner": "pyproject-nix", - "repo": "build-system-pkgs", - "type": "github" - } - }, - "pyproject-nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1771518446, - "narHash": "sha256-nFJSfD89vWTu92KyuJWDoTQJuoDuddkJV3TlOl1cOic=", - "owner": "pyproject-nix", - "repo": "pyproject.nix", - "rev": "eb204c6b3335698dec6c7fc1da0ebc3c6df05937", - "type": "github" - }, - "original": { - "owner": "pyproject-nix", - "repo": "pyproject.nix", - "type": "github" - } - }, "root": { "inputs": { "claude-code-nix": "claude-code-nix", - "nixpkgs": "nixpkgs_2", - "pyproject-build-systems": "pyproject-build-systems", - "pyproject-nix": "pyproject-nix", - "uv2nix": "uv2nix" + "nixpkgs": "nixpkgs_2" } }, "systems": { @@ -138,29 +89,6 @@ "repo": "default", "type": "github" } - }, - "uv2nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ], - "pyproject-nix": [ - "pyproject-nix" - ] - }, - "locked": { - "lastModified": 1771521001, - "narHash": "sha256-X0N7OjdQCZe/kURe0uYt+oJoXjEmI8k3R30EP4OvFFw=", - "owner": "pyproject-nix", - "repo": "uv2nix", - "rev": "51b184e6985f00091dc65d2a6ca36a08a69cafcb", - "type": "github" - }, - "original": { - "owner": "pyproject-nix", - "repo": "uv2nix", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index eef7dae4..e1f4c14d 100644 --- a/flake.nix +++ b/flake.nix @@ -6,58 +6,12 @@ # Claude Code with pre-built native binaries (hourly updates) claude-code-nix.url = "github:sadjow/claude-code-nix"; - - pyproject-nix = { - url = "github:pyproject-nix/pyproject.nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - uv2nix = { - url = "github:pyproject-nix/uv2nix"; - inputs.pyproject-nix.follows = "pyproject-nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - pyproject-build-systems = { - url = "github:pyproject-nix/build-system-pkgs"; - inputs.pyproject-nix.follows = "pyproject-nix"; - inputs.uv2nix.follows = "uv2nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; }; - outputs = { self, nixpkgs, claude-code-nix, pyproject-nix, uv2nix, pyproject-build-systems, ... }: + outputs = { self, nixpkgs, claude-code-nix, ... }: let inherit (nixpkgs) lib; - - # Systems to support forAllSystems = lib.genAttrs [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; - - # Load the uv workspace from uv.lock - workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; - - # Create overlay from uv.lock - prefer wheels for faster builds - overlay = workspace.mkPyprojectOverlay { sourcePreference = "wheel"; }; - - # Editable overlay for development (live-reload from src/) - editableOverlay = workspace.mkEditablePyprojectOverlay { root = "$REPO_ROOT"; }; - - # Build Python package sets for each system - pythonSets = forAllSystems (system: - let - pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; - }; - python = pkgs.python311; - in - (pkgs.callPackage pyproject-nix.build.packages { inherit python; }).overrideScope - (lib.composeManyExtensions [ - pyproject-build-systems.overlays.default - overlay - ]) - ); - in { devShells = forAllSystems (system: @@ -66,17 +20,11 @@ inherit system; config.allowUnfree = true; }; - - # Python set with editable overlay for development - pythonSet = pythonSets.${system}.overrideScope editableOverlay; - - # Virtual environment with all dependencies (including dev extras) - virtualenv = pythonSet.mkVirtualEnv "deepwork-dev-env" workspace.deps.all; in { default = pkgs.mkShell { packages = [ - virtualenv + pkgs.python311 pkgs.uv pkgs.git pkgs.jq @@ -85,27 +33,25 @@ ]; env = { - # Prevent uv from managing packages (Nix handles it) - UV_NO_SYNC = "1"; - UV_PYTHON = "${pythonSet.python}/bin/python"; UV_PYTHON_DOWNLOADS = "never"; DEEPWORK_DEV = "1"; }; shellHook = '' - # Required for editable overlay - unset PYTHONPATH export REPO_ROOT=$(git rev-parse --show-toplevel) - # Install deepwork as a uv tool (editable) so uvx picks up local source - # This makes the MCP config ("uvx deepwork serve") use the dev version + # Create project venv with deepwork + all dev deps (pytest, ruff, mypy, etc.) + uv sync --extra dev --quiet 2>/dev/null || true + export PATH="$REPO_ROOT/.venv/bin:$PATH" + + # Also register as a uv tool so `uvx deepwork serve` uses local source uv tool install -e "$REPO_ROOT" --quiet 2>/dev/null || true # Only show welcome message in interactive shells if [[ $- == *i* ]]; then echo "" - echo "DeepWork Development Environment (uv2nix)" - echo "==========================================" + echo "DeepWork Development Environment" + echo "================================" echo "" echo "Python: $(python --version) | uv: $(uv --version)" echo "" @@ -122,39 +68,5 @@ }; } ); - - # Package output - wrapped deepwork binary with isolated Python environment - # When consumed as a dependency in other flakes, the consuming devShell may - # include Python packages for a different version (e.g. python3.13 from - # azure-cli, awscli2). These pollute PYTHONPATH and cause symbol errors - # when deepwork's python3.11 tries to load python3.13 native extensions. - # Wrapping with --unset PYTHONPATH isolates deepwork from the host environment. - packages = forAllSystems (system: - let - pkgs = import nixpkgs { - inherit system; - config.allowUnfree = true; - }; - venv = pythonSets.${system}.mkVirtualEnv "deepwork-env" workspace.deps.default; - wrapped = pkgs.runCommand "deepwork-wrapped" { - nativeBuildInputs = [ pkgs.makeWrapper ]; - } '' - mkdir -p $out/bin - makeWrapper ${venv}/bin/deepwork $out/bin/deepwork \ - --unset PYTHONPATH - ''; - in { - default = wrapped; - deepwork = wrapped; # Alias for backwards compatibility - } - ); - - # Make deepwork runnable with 'nix run' - apps = forAllSystems (system: { - default = { - type = "app"; - program = "${self.packages.${system}.default}/bin/deepwork"; - }; - }); }; } From 1d7736e549d5ccb0ec555648e5ffedc5e141c262 Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Sun, 22 Feb 2026 12:33:21 -0700 Subject: [PATCH 2/3] fix: Resolve all mypy errors - Add types-jsonschema stub to dev deps - Handle None from repo.working_tree_dir in get_repo_root - Annotate json.load() return in job_schema.py - Annotate wrapper.get() return in claude_cli.py Co-Authored-By: Claude Opus 4.6 --- pyproject.toml | 1 + src/deepwork/mcp/claude_cli.py | 2 +- src/deepwork/schemas/job_schema.py | 3 ++- src/deepwork/utils/git.py | 2 ++ uv.lock | 26 ++++++++++++++++++++------ 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0e22967b..fea14805 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dev = [ "mypy>=1.0", "types-PyYAML", "types-aiofiles", + "types-jsonschema", ] [project.scripts] diff --git a/src/deepwork/mcp/claude_cli.py b/src/deepwork/mcp/claude_cli.py index a64a2b24..f76c6797 100644 --- a/src/deepwork/mcp/claude_cli.py +++ b/src/deepwork/mcp/claude_cli.py @@ -106,7 +106,7 @@ def _parse_wrapper(self, response_text: str) -> dict[str, Any]: f"Claude CLI returned error: {wrapper.get('result', 'Unknown error')}" ) - data = wrapper.get("structured_output") + data: dict[str, Any] = wrapper.get("structured_output") if data is None: raise ClaudeCLIError( "Claude CLI response missing 'structured_output' field. " diff --git a/src/deepwork/schemas/job_schema.py b/src/deepwork/schemas/job_schema.py index c3c0cb57..2974e835 100644 --- a/src/deepwork/schemas/job_schema.py +++ b/src/deepwork/schemas/job_schema.py @@ -19,7 +19,8 @@ def _load_schema() -> dict[str, Any]: """Load the JSON schema from file.""" with open(_SCHEMA_FILE) as f: - return json.load(f) + result: dict[str, Any] = json.load(f) + return result # Load the schema at module import time diff --git a/src/deepwork/utils/git.py b/src/deepwork/utils/git.py index 599d5297..e40114aa 100644 --- a/src/deepwork/utils/git.py +++ b/src/deepwork/utils/git.py @@ -61,6 +61,8 @@ def get_repo_root(path: Path | str) -> Path: GitError: If path is not in a Git repository """ repo = get_repo(path) + if repo.working_tree_dir is None: + raise GitError(f"Repository at {path} has no working tree (bare repo?)") return Path(repo.working_tree_dir) diff --git a/uv.lock b/uv.lock index 6e527013..981b9ee0 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] @@ -477,16 +474,20 @@ dev = [ { name = "pytest-mock" }, { name = "ruff" }, { name = "types-aiofiles" }, + { name = "types-jsonschema" }, { name = "types-pyyaml" }, ] [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 +495,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,9 +504,9 @@ 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-jsonschema", marker = "extra == 'dev'" }, { name = "types-pyyaml", marker = "extra == 'dev'" }, ] provides-extras = ["dev"] @@ -515,10 +514,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]] @@ -2175,6 +2177,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/0f/76917bab27e270bb6c32addd5968d69e558e5b6f7fb4ac4cbfa282996a96/types_aiofiles-25.1.0.20251011-py3-none-any.whl", hash = "sha256:8ff8de7f9d42739d8f0dadcceeb781ce27cd8d8c4152d4a7c52f6b20edb8149c", size = 14338, upload-time = "2025-10-11T02:44:50.054Z" }, ] +[[package]] +name = "types-jsonschema" +version = "4.26.0.20260202" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/07/68f63e715eb327ed2f5292e29e8be99785db0f72c7664d2c63bd4dbdc29d/types_jsonschema-4.26.0.20260202.tar.gz", hash = "sha256:29831baa4308865a9aec547a61797a06fc152b0dac8dddd531e002f32265cb07", size = 16168, upload-time = "2026-02-02T04:11:22.585Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/06/962d4f364f779d7389cd31a1bb581907b057f52f0ace2c119a8dd8409db6/types_jsonschema-4.26.0.20260202-py3-none-any.whl", hash = "sha256:41c95343abc4de9264e333a55e95dfb4d401e463856d0164eec9cb182e8746da", size = 15914, upload-time = "2026-02-02T04:11:21.61Z" }, +] + [[package]] name = "types-pyyaml" version = "6.0.12.20250915" From 1dadf5ddd89a5877928465f5aedfeda07f531224 Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Sun, 22 Feb 2026 12:38:43 -0700 Subject: [PATCH 3/3] fix(ci): Remove nix build step since packages output was removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flake no longer provides a packages output — it's dev-only now. Remove the `nix build` and package verification steps from CI. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/validate.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 26a6ddf8..741add4e 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -34,11 +34,3 @@ jobs: - name: Run tests run: pytest tests/ -v - - - name: Build deepwork package - run: nix build - - - name: Verify package output - run: | - ls -la result/bin/deepwork - ./result/bin/deepwork --version