diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a463b777..26a6ddf8 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -18,23 +18,27 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - version: "latest" + - name: Install Nix + uses: cachix/install-nix-action@v31 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" + - name: Setup Nix development environment + uses: nicknovitski/nix-develop@v1 - - name: Install dependencies + - name: Install Python dependencies run: uv sync --extra dev - name: Check formatting (ruff) run: | - uv run ruff format --check src/ tests/ - uv run ruff check src/ tests/ + ruff format --check src/ tests/ + ruff check src/ tests/ - name: Run tests - run: uv run pytest tests/ -v + 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 diff --git a/.gitignore b/.gitignore index 93694a7d..79b451c6 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,9 @@ dmypy.json *~ .DS_Store +# direnv +.direnv/ + # Jupyter Notebook .ipynb_checkpoints diff --git a/AGENTS.md b/AGENTS.md index 5f327754..3e469ef8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,4 +83,22 @@ deepwork/ ├── deepwork_jobs/ # ← Installed copy, NOT source of truth ├── deepwork_rules/ # ← Installed copy, NOT source of truth └── [bespoke_job]/ # ← Source of truth for bespoke only + +## Development Environment + +This project uses **Nix Flakes** to provide a reproducible development environment. + +### Using the Environment + +- **With direnv (Recommended)**: Just `cd` into the directory. The `.envrc` will automatically load the flake environment. +- **Without direnv**: Run `nix develop` to enter the shell. +- **Building**: Run `nix build` to build the package. + +**Note**: The flake is configured to automatically allow unfree packages (required for the BSL 1.1 license), so you do not need to set `NIXPKGS_ALLOW_UNFREE=1`. + +The environment includes: +- Python 3.11 +- uv (package manager) +- All dev dependencies (pytest, ruff, mypy, etc.) + ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c77b743..85e8994d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,6 +19,8 @@ Thank you for your interest in contributing to DeepWork! This guide will help yo - **Python 3.11 or higher** - Required for running DeepWork - **Git** - For version control - **Nix** (optional but recommended) - For reproducible development environment + - Nix flakes enabled (add `experimental-features = nix-command flakes` to `~/.config/nix/nix.conf`) +- **direnv** (optional) - For automatic environment activation when using Nix flakes - **uv** - Modern Python package installer (included in Nix environment) - **Signed CLA** - All contributors must sign the Contributor License Agreement (see below) @@ -61,9 +63,71 @@ For the full text, see [CLA.md](CLA/version_1/CLA.md). ## Development Setup -### Option 1: Using Nix (Recommended) +### Setting Up direnv (Optional but Recommended) -The easiest way to get started is using Nix, which provides a fully reproducible development environment with all dependencies pre-configured. +direnv automatically loads the Nix environment when you `cd` into the project directory: + +```bash +# Install direnv (if not already installed) +# On macOS with Homebrew: +brew install direnv + +# On Linux (Debian/Ubuntu): +apt-get install direnv + +# On NixOS or with Nix: +nix-env -i direnv + +# Add direnv hook to your shell +# For bash, add to ~/.bashrc: +eval "$(direnv hook bash)" + +# For zsh, add to ~/.zshrc: +eval "$(direnv hook zsh)" + +# For fish, add to ~/.config/fish/config.fish: +direnv hook fish | source + +# Restart your shell or source your rc file +source ~/.bashrc # or ~/.zshrc +``` + +Once direnv is set up, the environment will activate automatically when you enter the directory. + +### Option 1: Using Nix Flakes (Recommended) + +The easiest way to get started is using Nix flakes, which provides a fully reproducible development environment with all dependencies pre-configured. + +#### Quick Start with direnv (Recommended) + +If you have direnv installed, the entire development environment activates automatically when you `cd` into the project: + +```bash +# Clone the repository +git clone https://github.com/deepwork/deepwork.git +cd deepwork + +# Allow direnv (first time only) +direnv allow + +# That's it! Everything is ready: +deepwork --help # CLI works +pytest # Tests work +ruff check src/ # Linting works +``` + +The `.envrc` file contains `use flake`, which tells direnv to load the Nix flake's development shell. This automatically: + +1. Creates `.venv/` if it doesn't exist +2. Installs all dependencies via `uv sync --all-extras` +3. Adds `.venv/bin` to your PATH +4. Sets `PYTHONPATH` and `DEEPWORK_DEV=1` + +Every time you `cd` into the directory, the environment is ready instantly (venv is reused, deps are cached). + +#### Manual Flake Usage + +If you don't use direnv, you can manually enter the development environment: ```bash # Clone the repository @@ -71,15 +135,31 @@ git clone https://github.com/deepwork/deepwork.git cd deepwork # Enter the Nix development environment -nix-shell +nix develop ``` -When you enter `nix-shell`, you'll see a welcome message with available tools. The environment includes: -- Python 3.11 -- uv (package manager) -- pytest, ruff, mypy -- All Python dependencies -- Environment variables (`PYTHONPATH`, `DEEPWORK_DEV=1`) +#### What's Included + +The Nix environment provides: + +| Tool | Description | +|------|-------------| +| `deepwork` | CLI using your local source code (editable install) | +| `pytest` | Test runner with all plugins | +| `ruff` | Fast Python linter and formatter | +| `mypy` | Static type checker | +| `uv` | Python package manager | +| `python` | Python 3.11 interpreter | + +#### CI Usage + +For CI pipelines or scripts, use `nix develop --command`: + +```bash +nix develop --command pytest +nix develop --command ruff check src/ +nix develop --command mypy src/ +``` ### Option 2: Manual Setup (Without Nix) @@ -90,39 +170,38 @@ If you prefer not to use Nix: git clone https://github.com/deepwork/deepwork.git cd deepwork -# Create a virtual environment (optional but recommended) -python3.11 -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate - -# Install uv if you don't have it -pip install uv +# Install uv if you don't have it (see https://docs.astral.sh/uv/getting-started/installation/) +curl -LsSf https://astral.sh/uv/install.sh | sh -# Install dependencies -uv sync +# Create virtual environment and install all dependencies (including dev tools) +uv venv .venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate +uv sync --all-extras -# Set PYTHONPATH for development +# Set environment variables for development export PYTHONPATH="$PWD/src:$PYTHONPATH" export DEEPWORK_DEV=1 ``` ## Installing DeepWork Locally -To use your local development version of DeepWork, install it in **editable mode**. This allows you to make changes to the code and have them immediately reflected without reinstalling. +The development version of DeepWork is installed automatically in **editable mode**, meaning changes to source code are reflected immediately without reinstalling. -### Using uv (Recommended) +### With Nix (Automatic) -```bash -# Install in editable mode with development dependencies -uv pip install -e ".[dev]" +If you're using the Nix development environment, DeepWork is already installed in editable mode. No additional steps needed. -# Or if you're inside nix-shell -uv sync # Automatically installs in editable mode -``` +### Without Nix (Manual) + +If you set up manually, the `uv sync --all-extras` command installs DeepWork in editable mode automatically. -### Using pip +Alternatively, you can install explicitly: ```bash -# Install in editable mode with development dependencies +# Using uv +uv pip install -e ".[dev]" + +# Or using pip pip install -e ".[dev]" ``` @@ -132,11 +211,11 @@ pip install -e ".[dev]" # Check that the deepwork command is available deepwork --help -# Verify you're using the local version -which deepwork # Should point to your local environment +# Verify you're using the local development version +which deepwork # Should point to .venv/bin/deepwork -# Check version (should show 0.1.0 or current dev version) -python -c "import deepwork; print(deepwork.__version__)" +# Check version +deepwork --version ``` ## Testing Your Local Installation @@ -192,35 +271,35 @@ claude # Start Claude Code ## Running Tests -DeepWork has a comprehensive test suite with unit and integration tests. +DeepWork has a comprehensive test suite with 568+ tests. ### Run All Tests ```bash -# Using uv (recommended) -uv run pytest +# In Nix environment (interactive or CI) +pytest -# Or with explicit paths -uv run pytest tests/ -v +# Or using nix develop --command (CI-friendly, no interactive shell) +nix develop --command pytest -# Using pytest directly (if in nix-shell or venv) -pytest +# Using uv run (without Nix) +uv run pytest ``` ### Run Specific Test Types ```bash -# Unit tests only (147 tests) -uv run pytest tests/unit/ -v +# Unit tests only +pytest tests/unit/ -v -# Integration tests only (19 tests) -uv run pytest tests/integration/ -v +# Integration tests only +pytest tests/integration/ -v # Run a specific test file -uv run pytest tests/unit/core/test_parser.py -v +pytest tests/unit/core/test_parser.py -v # Run a specific test function -uv run pytest tests/unit/core/test_parser.py::test_parse_valid_job -v +pytest tests/unit/core/test_parser.py::test_parse_valid_job -v ``` ### Test with Coverage @@ -380,7 +459,7 @@ deepwork/ ├── doc/ # Documentation │ ├── architecture.md # Comprehensive architecture doc │ └── TEMPLATE_REVIEW.md -├── shell.nix # Nix development environment +├── flake.nix # Nix flake for development environment ├── pyproject.toml # Python project configuration ├── CLAUDE.md # Project context for Claude Code └── README.md # Project overview @@ -458,10 +537,10 @@ deepwork/ ### Quick Development Cycle ```bash -# In one terminal: Enter nix-shell and keep it open -nix-shell +# In one terminal: Enter Nix development environment +nix develop -# In nix-shell: Watch tests +# In Nix environment: Watch tests uv run pytest tests/unit/ --watch # In another terminal: Make changes to src/deepwork/ @@ -495,29 +574,45 @@ uv run pytest --profile ## Common Issues ### Issue: `deepwork` command not found -**Solution**: Make sure you've installed in editable mode: +**With Nix**: Re-enter the development environment: ```bash -uv pip install -e . +nix develop +# or if using direnv +direnv reload +``` + +**Without Nix**: Ensure venv is activated and dependencies synced: +```bash +source .venv/bin/activate +uv sync --all-extras ``` ### Issue: Tests failing with import errors -**Solution**: Set PYTHONPATH: +**Solution**: This usually means dependencies aren't installed. Re-sync: ```bash -export PYTHONPATH="$PWD/src:$PYTHONPATH" +uv sync --all-extras +``` + +### Issue: Changes not reflected +**Solution**: Verify editable install with uv: +```bash +uv pip list | grep deepwork +# Should show: deepwork (editable) with path to your local directory ``` -### Issue: Changes not reflected in test project -**Solution**: Verify editable install: +### Issue: Nix environment not loading +**Solution**: Ensure Nix is installed with flakes enabled: ```bash -pip list | grep deepwork -# Should show: deepwork 0.1.0 /path/to/your/local/deepwork/src +nix --version +# Add to ~/.config/nix/nix.conf if not already there: +# experimental-features = nix-command flakes ``` -### Issue: Nix shell not loading -**Solution**: Make sure Nix is installed and `` is available: +### Issue: Old venv causing conflicts +**Solution**: Remove and let Nix recreate it: ```bash -nix-shell --version -echo $NIX_PATH +rm -rf .venv +nix develop # Will recreate .venv automatically ``` ## License diff --git a/README.md b/README.md index 96d7e740..7c54d29d 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,37 @@ your-project/ - **[Architecture](doc/architecture.md)**: Complete design specification - **[Doc Specs](doc/doc-specs.md)**: Document specification format for output quality criteria - **[Contributing](CONTRIBUTING.md)**: Setup development environment and contribute +- **[Nix Flakes Guide](doc/nix-flake.md)**: Comprehensive guide for using DeepWork with Nix flakes + +## Development with Nix + +DeepWork is available as a Nix flake for reproducible development environments: + +```bash +# Using Nix flakes +nix develop + +# Or with direnv (automatic activation - recommended) +echo "use flake" > .envrc +direnv allow +``` + +The Nix environment provides all dependencies including Python 3.11, uv, pytest, ruff, and mypy. + +### Installing DeepWork from Flake + +You can also install deepwork directly from the flake: + +```bash +# Install deepwork from this flake +nix profile install github:Unsupervisedcom/deepwork + +# Or run it without installing +nix run github:Unsupervisedcom/deepwork -- --help + +# Or build the package +nix build github:Unsupervisedcom/deepwork +``` ## Project Structure diff --git a/doc/nix-flake.md b/doc/nix-flake.md new file mode 100644 index 00000000..26bf82ec --- /dev/null +++ b/doc/nix-flake.md @@ -0,0 +1,479 @@ +# Using DeepWork with Nix Flakes + +DeepWork provides a Nix flake for reproducible development environments and easy installation. This document covers how to use the flake in various scenarios. + +## Prerequisites + +- Nix with flakes support enabled +- Add to your `~/.config/nix/nix.conf`: + ``` + experimental-features = nix-command flakes + ``` + +## License Configuration + +DeepWork is licensed under the Business Source License 1.1 (BSL 1.1), which is not an OSI-approved open source license (though the source is available). + +The flake is configured to automatically allow unfree packages when importing `nixpkgs`, so you don't need to manually set `NIXPKGS_ALLOW_UNFREE=1` when building or using this flake directly. + +However, if you consume this flake in your own project, you may still need to configure your `nixpkgs` to allow unfree packages or specifically allow `deepwork` if your configuration overrides our default settings. + +## Development Environment + +### Quick Start with direnv (Recommended) + +direnv automatically activates the Nix environment when you enter the directory: + +```bash +# Clone the repository +git clone https://github.com/Unsupervisedcom/deepwork.git +cd deepwork + +# Allow direnv to load the environment +direnv allow + +# Environment activates automatically! +``` + +The `.envrc` file is already configured with `use flake`, so you'll get: +- Python 3.11 +- uv package manager +- All development dependencies (pytest, ruff, mypy) +- Automatic virtual environment activation +- Environment variables set (`PYTHONPATH`, `DEEPWORK_DEV=1`) + +### Manual Activation + +If you don't use direnv: + +```bash +# Enter development shell +nix develop + +# Or use the legacy command +nix-shell +``` + +### What's Included + +The development environment provides: + +- **Python 3.11** with pip and virtualenv +- **uv** - Fast Python package installer +- **Git** - Version control +- **jq** - JSON processing +- **Python packages**: + - jinja2, pyyaml, gitpython + - pytest, pytest-mock, pytest-cov + - click, rich +- **Development tools**: + - ruff - Python linter and formatter + - mypy - Static type checker + +## Installing DeepWork + +### From the Flake + +Install deepwork system-wide or in your user profile: + +```bash +# Install to your user profile +nix profile install github:Unsupervisedcom/deepwork + +# Verify installation +deepwork --help +``` + +### Running Without Installing + +```bash +# Run deepwork directly +nix run github:Unsupervisedcom/deepwork -- --help + +# Run a specific command +nix run github:Unsupervisedcom/deepwork -- install --platform claude +``` + +### Building the Package + +```bash +# Build the package +nix build github:Unsupervisedcom/deepwork + +# Result will be in ./result/ +ls -la result/bin/deepwork +``` + +## Using in Your Project + +DeepWork can be added to your existing Nix flake using a GitHub reference. This allows you to use DeepWork in your development environment or as a dependency for your projects. + +### As a Development Dependency + +Create a `flake.nix` in your project that references DeepWork via GitHub: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + # Add DeepWork via GitHub reference + deepwork.url = "github:Unsupervisedcom/deepwork"; + }; + + outputs = { self, nixpkgs, deepwork }: + let + system = "x86_64-linux"; # or your system + pkgs = import nixpkgs { inherit system; }; + in { + devShells.${system}.default = pkgs.mkShell { + buildInputs = [ + deepwork.packages.${system}.default + ]; + }; + }; +} +``` + +You can also pin to a specific version, tag, or commit: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + # Pin to a specific tag + deepwork.url = "github:Unsupervisedcom/deepwork/0.3.0"; + # Or pin to a specific commit + # deepwork.url = "github:Unsupervisedcom/deepwork/abc1234"; + # Or pin to a specific branch + # deepwork.url = "github:Unsupervisedcom/deepwork/main"; + }; + + outputs = { self, nixpkgs, deepwork }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + in { + devShells.${system}.default = pkgs.mkShell { + buildInputs = [ + deepwork.packages.${system}.default + ]; + }; + }; +} +``` + +### As a Runtime Dependency + +If you're building a Nix package that depends on DeepWork: + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + # Add DeepWork as a dependency via GitHub reference + deepwork.url = "github:Unsupervisedcom/deepwork"; + }; + + outputs = { self, nixpkgs, deepwork }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + in { + packages.${system}.default = pkgs.stdenv.mkDerivation { + name = "my-project"; + buildInputs = [ + deepwork.packages.${system}.default + ]; + }; + }; +} +``` + +### Using with flake-utils for Multi-System Support + +For projects that need to support multiple systems (Linux, macOS, etc.): + +```nix +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + # Add DeepWork via GitHub reference + deepwork.url = "github:Unsupervisedcom/deepwork"; + }; + + outputs = { self, nixpkgs, flake-utils, deepwork }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in { + devShells.default = pkgs.mkShell { + buildInputs = [ + deepwork.packages.${system}.default + ]; + }; + } + ); +} +``` + +### Recommended Project Configuration + +For a complete development environment with DeepWork and your preferred AI assistants, use this `flake.nix` structure. Note that `config.allowUnfree = true` is required to use DeepWork (BSL 1.1) and Claude Code. + +**flake.nix:** +```nix +{ + description = "AI-powered development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + deepwork = { + url = "github:Unsupervisedcom/deepwork"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, flake-utils, deepwork }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + # Required for DeepWork and Claude Code + config.allowUnfree = true; + }; + in + { + devShells.default = pkgs.mkShell { + buildInputs = [ + # DeepWork CLI + deepwork.packages.${system}.default + + # AI Agents + pkgs.claude-code + pkgs.gemini-cli + + # Additional tools + pkgs.python3 + pkgs.nodejs + pkgs.git + ]; + + shellHook = '' + echo "------------------------------------------------------------------" + echo "DeepWork Development Shell" + echo "------------------------------------------------------------------" + echo "Tools: deepwork, claude, gemini-cli" + echo "------------------------------------------------------------------" + + # Verify installation + deepwork --version + ''; + }; + } + ); +} +``` + +**.envrc:** +```bash +use flake +``` + +Then run: +```bash +direnv allow +# Environment with DeepWork and AI agents loads automatically +``` + +## Flake Outputs + +The flake provides several outputs: + +### devShells.default + +Development environment with all tools and dependencies. + +```bash +nix develop +``` + +### packages.default / packages.deepwork + +The deepwork Python package. + +```bash +nix build +nix build .#deepwork +``` + +### apps.default + +Runnable deepwork application. + +```bash +nix run +nix run .#default -- --help +``` + +## Updating Dependencies + +### Update nixpkgs + +```bash +# Update the flake lock file +nix flake update + +# Or update just nixpkgs +nix flake lock --update-input nixpkgs +``` + +### Update Python Dependencies + +Python dependencies are managed by `uv` and `pyproject.toml`: + +```bash +# Inside the development environment +nix develop +uv sync +``` + +## Troubleshooting + +### Flakes Not Enabled + +If you get an error about flakes not being recognized: + +```bash +# Add to ~/.config/nix/nix.conf +echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf +``` + +### Hooks Working with Different Installation Methods + +DeepWork hooks use the `deepwork hook` CLI command, which works consistently regardless of how deepwork was installed: + +**Installation Methods Supported:** +- Nix flake (`nix profile install github:Unsupervisedcom/deepwork`) +- pipx (`pipx install deepwork`) +- uv (`uv tool install deepwork`) +- pip (`pip install deepwork`) + +**How it Works:** + +The hook wrapper scripts (`.deepwork/hooks/claude_hook.sh`, `.gemini/hooks/gemini_hook.sh`) call: +```bash +deepwork hook rules_check +``` + +Instead of the old approach: +```bash +python -m deepwork.hooks.rules_check # ❌ Doesn't work with all install methods +``` + +**Requirements:** +1. The `deepwork` command must be in your PATH +2. For Nix flake users: Install with `nix profile install github:Unsupervisedcom/deepwork` +3. For pipx users: Install with `pipx install deepwork` +4. For uv users: Install with `uv tool install deepwork` + +All these methods ensure `deepwork` is available globally, so hooks work correctly outside of development environments. + +### Custom Hooks and Direct Python Invocation + +**Standard Hooks:** +Standard hooks (like `rules_check`) work seamlessly with all installation methods because they use the `deepwork hook` CLI command: + +```bash +# This works with Nix flake, pipx, uv, and pip installations +deepwork hook rules_check +``` + +**Custom Hooks:** +If you have custom hooks that need to invoke Python directly (e.g., `python -m deepwork.hooks.custom_hook` or `python myscript.py`), there are important considerations: + +**Nix Flake Installation:** +When deepwork is installed via `nix profile install`, only the `deepwork` command is exposed, not a Python interpreter with deepwork in its module path. This means: + +- ✅ **Works:** `deepwork hook custom_hook` (if you've registered your custom hook via the CLI) +- ❌ **Doesn't Work:** `python -m deepwork.hooks.custom_hook` (Python won't find the deepwork module) +- ❌ **Doesn't Work:** `python /path/to/custom_script.py` (if it imports deepwork) + +**Recommended Approach for Custom Hooks:** + +1. **Use the `deepwork hook` command** (Recommended): + Register your custom hook so it can be invoked via `deepwork hook`: + ```bash + # Your hook wrapper calls this + deepwork hook my_custom_hook + ``` + + This approach works consistently across all installation methods. + +2. **Use pipx or uv for custom hooks** (Alternative): + If you need direct Python access to deepwork modules: + ```bash + # Install with pipx (recommended for global tools) + pipx install deepwork + + # Now python can find deepwork + python -m deepwork.hooks.custom_hook + ``` + +3. **Development Environment Only** (Local Development): + For development and testing: + ```bash + # Use nix develop for full Python environment + nix develop + # Now python has access to deepwork modules + python -m deepwork.hooks.custom_hook + ``` + +**Summary:** +- **Standard hooks:** Work with all installation methods via `deepwork hook` +- **Custom hooks needing Python:** Use `deepwork hook` command or install via pipx/uv instead of Nix flake +- **Development/Testing:** Use `nix develop` for full Python environment access + +### direnv Not Working + +1. Make sure direnv is installed: + ```bash + nix-env -i direnv + ``` + +2. Add the hook to your shell rc file: + ```bash + # For bash (~/.bashrc) + eval "$(direnv hook bash)" + + # For zsh (~/.zshrc) + eval "$(direnv hook zsh)" + ``` + +3. Allow the directory: + ```bash + direnv allow + ``` + +### Environment Variables Not Set + +If `PYTHONPATH` or `DEEPWORK_DEV` are not set: + +```bash +# Manually source the hook +source .envrc +``` + +Or ensure you're in the Nix environment: + +```bash +nix develop +``` + +## Additional Resources + +- [Nix Flakes Documentation](https://nixos.wiki/wiki/Flakes) +- [direnv Documentation](https://direnv.net/) +- [DeepWork Contributing Guide](../CONTRIBUTING.md) +- [DeepWork Architecture](./architecture.md) diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..5a9df819 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1768564909, + "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..4c457ba6 --- /dev/null +++ b/flake.nix @@ -0,0 +1,107 @@ +{ + description = "DeepWork - Framework for enabling AI agents to perform complex, multi-step work tasks"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + # Allow unfree packages to support the Business Source License 1.1 + config.allowUnfree = true; + }; + # Read version from pyproject.toml to avoid duplication + pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml); + deepwork = pkgs.python311Packages.buildPythonPackage { + pname = "deepwork"; + version = pyproject.project.version; + src = ./.; + format = "pyproject"; + nativeBuildInputs = [ pkgs.python311Packages.hatchling ]; + # Required for `nix build` - must match pyproject.toml dependencies + propagatedBuildInputs = with pkgs.python311Packages; [ + click gitpython jinja2 jsonschema pyyaml rich rpds-py + ]; + doCheck = false; + }; + in + { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + # Python 3.11 - base interpreter for uv + python311 + + # uv manages all Python packages (deps, dev tools, etc.) + uv + + # Git for version control + git + + # System tools + jq # For JSON processing + ]; + + # Environment variables for uv integration with Nix + env = { + # Tell uv to use the Nix-provided Python interpreter + UV_PYTHON = "${pkgs.python311}/bin/python"; + # Prevent uv from downloading Python binaries + UV_PYTHON_DOWNLOADS = "never"; + # Development mode flag + DEEPWORK_DEV = "1"; + }; + + shellHook = '' + # Create venv if it doesn't exist + if [ ! -d .venv ]; then + echo "Creating virtual environment..." + uv venv .venv --quiet + fi + + # Sync dependencies (including dev extras like pytest, ruff, mypy) + # Run quietly - uv only outputs when changes are needed + uv sync --all-extras --quiet 2>/dev/null || uv sync --all-extras + + # Activate venv by setting environment variables directly + # This works reliably for both interactive shells and `nix develop --command` + export VIRTUAL_ENV="$PWD/.venv" + export PATH="$VIRTUAL_ENV/bin:$PATH" + unset PYTHONHOME + + # Set PYTHONPATH for editable install access to src/ + export PYTHONPATH="$PWD/src:$PYTHONPATH" + + # Only show welcome message in interactive shells + if [[ $- == *i* ]]; then + echo "" + echo "DeepWork Development Environment" + echo "================================" + echo "" + echo "Python: $(python --version) | uv: $(uv --version)" + echo "" + echo "Commands:" + echo " deepwork --help CLI (development version)" + echo " pytest Run tests" + echo " ruff check src/ Lint code" + echo " mypy src/ Type check" + echo "" + fi + ''; + }; + + # Make the package available as a flake output + packages.default = deepwork; + packages.deepwork = deepwork; + + # Make deepwork runnable with 'nix run' + apps.default = { + type = "app"; + program = "${deepwork}/bin/deepwork"; + }; + } + ); +} diff --git a/result b/result new file mode 120000 index 00000000..9fb3912b --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/hmxjpzgimzyf6gs76blmgxjims86cpq7-python3.11-deepwork-0.5.0 \ No newline at end of file diff --git a/shell.nix b/shell.nix deleted file mode 100644 index 5965287c..00000000 --- a/shell.nix +++ /dev/null @@ -1,67 +0,0 @@ -{ pkgs ? import {} }: - -pkgs.mkShell { - buildInputs = with pkgs; [ - # Python 3.11 or later - python311 - python311Packages.pip - python311Packages.virtualenv - - # Modern Python tooling - uv - - # Git for version control - git - - # Additional tools - jq # For JSON processing - - # Python development dependencies - python311Packages.jinja2 - python311Packages.pyyaml - python311Packages.gitpython - python311Packages.pytest - python311Packages.pytest-mock - python311Packages.pytest-cov - python311Packages.click - python311Packages.rich - - # Linting and type checking - ruff - mypy - ]; - - shellHook = '' - # Set up environment variables - export PYTHONPATH="$PWD/src:$PYTHONPATH" - export DEEPWORK_DEV=1 - - # Auto-sync dependencies and activate venv for direct deepwork access - echo "Setting up DeepWork development environment..." - uv sync --quiet 2>/dev/null || uv sync - - # Activate the virtual environment so 'deepwork' command is directly available - if [ -f .venv/bin/activate ]; then - source .venv/bin/activate - fi - - echo "" - echo "DeepWork Development Environment" - echo "================================" - echo "" - echo "Python version: $(python --version)" - echo "uv version: $(uv --version)" - echo "" - echo "Available tools:" - echo " - deepwork: CLI is ready (try 'deepwork --help')" - echo " - pytest: Testing framework" - echo " - ruff: Python linter and formatter" - echo " - mypy: Static type checker" - echo "" - echo "Quick start:" - echo " - 'deepwork --help' to see available commands" - echo " - 'pytest' to run tests" - echo " - Read doc/architecture.md for design details" - echo "" - ''; -} diff --git a/src/deepwork/cli/hook.py b/src/deepwork/cli/hook.py new file mode 100644 index 00000000..5182b20a --- /dev/null +++ b/src/deepwork/cli/hook.py @@ -0,0 +1,70 @@ +"""Hook command for DeepWork CLI. + +This command runs hook scripts, allowing hooks to use the `deepwork` CLI +instead of `python -m deepwork.hooks.*`, which works regardless of how +deepwork was installed (flake, pipx, uv, etc.). + +Usage: + deepwork hook rules_check + deepwork hook + +This is meant to be called from hook wrapper scripts (claude_hook.sh, gemini_hook.sh). +""" + +import importlib +import sys + +import click +from rich.console import Console + +console = Console() + + +class HookError(Exception): + """Exception raised for hook errors.""" + + pass + + +@click.command() +@click.argument("hook_name") +def hook(hook_name: str) -> None: + """ + Run a DeepWork hook by name. + + HOOK_NAME: Name of the hook to run (e.g., 'rules_check') + + This command imports and runs the hook module from deepwork.hooks.{hook_name}. + The hook receives stdin input and outputs to stdout, following the hook protocol. + + Examples: + deepwork hook rules_check + echo '{}' | deepwork hook rules_check + """ + try: + # Import the hook module + # If the hook_name contains a dot, treat it as a full module path + # Otherwise, assume it's a hook in the deepwork.hooks package + if "." in hook_name: + module_name = hook_name + else: + module_name = f"deepwork.hooks.{hook_name}" + try: + module = importlib.import_module(module_name) + except ModuleNotFoundError: + raise HookError( + f"Hook '{hook_name}' not found. Available hooks are in the deepwork.hooks package." + ) from None + + # Run the hook's main function if it exists + if hasattr(module, "main"): + sys.exit(module.main()) + else: + raise HookError(f"Hook module '{module_name}' does not have a main() function") + + except HookError as e: + console.print(f"[red]Error:[/red] {e}", style="bold red") + sys.exit(1) + except Exception as e: + console.print(f"[red]Unexpected error running hook:[/red] {e}", style="bold red") + sys.exit(1) diff --git a/src/deepwork/cli/main.py b/src/deepwork/cli/main.py index 24a0b717..840decbf 100644 --- a/src/deepwork/cli/main.py +++ b/src/deepwork/cli/main.py @@ -14,11 +14,13 @@ def cli() -> None: # Import commands +from deepwork.cli.hook import hook # noqa: E402 from deepwork.cli.install import install # noqa: E402 from deepwork.cli.sync import sync # noqa: E402 cli.add_command(install) cli.add_command(sync) +cli.add_command(hook) if __name__ == "__main__": diff --git a/src/deepwork/hooks/__init__.py b/src/deepwork/hooks/__init__.py index c64dcfc4..5e9d8d43 100644 --- a/src/deepwork/hooks/__init__.py +++ b/src/deepwork/hooks/__init__.py @@ -17,7 +17,7 @@ "Stop": [{ "hooks": [{ "type": "command", - "command": ".deepwork/hooks/claude_hook.sh deepwork.hooks.rules_check" + "command": ".deepwork/hooks/claude_hook.sh rules_check" }] }] } @@ -29,12 +29,15 @@ "AfterAgent": [{ "hooks": [{ "type": "command", - "command": ".gemini/hooks/gemini_hook.sh deepwork.hooks.rules_check" + "command": ".gemini/hooks/gemini_hook.sh rules_check" }] }] } } +The shell wrappers call `deepwork hook ` which works regardless +of how deepwork was installed (pipx, uv, nix flake, etc.). + Writing custom hooks: from deepwork.hooks.wrapper import ( HookInput, @@ -50,10 +53,13 @@ def my_hook(input: HookInput) -> HookOutput: return HookOutput(decision="block", reason="Complete X first") return HookOutput() - if __name__ == "__main__": + def main(): import os, sys platform = Platform(os.environ.get("DEEPWORK_HOOK_PLATFORM", "claude")) sys.exit(run_hook(my_hook, platform)) + + if __name__ == "__main__": + main() """ from deepwork.hooks.wrapper import ( diff --git a/src/deepwork/hooks/claude_hook.sh b/src/deepwork/hooks/claude_hook.sh index 7e13ad44..c9a53e12 100755 --- a/src/deepwork/hooks/claude_hook.sh +++ b/src/deepwork/hooks/claude_hook.sh @@ -6,14 +6,13 @@ # and work on any supported platform. # # Usage: -# claude_hook.sh +# claude_hook.sh # # Example: -# claude_hook.sh deepwork.hooks.rules_check +# claude_hook.sh rules_check # -# The Python module should implement a main() function that: -# 1. Calls deepwork.hooks.wrapper.run_hook() with a hook function -# 2. The hook function receives HookInput and returns HookOutput +# The hook is run via the deepwork CLI, which works regardless of how +# deepwork was installed (pipx, uv, nix flake, etc.). # # Environment variables set by Claude Code: # CLAUDE_PROJECT_DIR - Absolute path to project root @@ -26,12 +25,12 @@ set -e -# Get the Python module to run -PYTHON_MODULE="${1:-}" +# Get the hook name to run +HOOK_NAME="${1:-}" -if [ -z "${PYTHON_MODULE}" ]; then - echo "Usage: claude_hook.sh " >&2 - echo "Example: claude_hook.sh deepwork.hooks.rules_check" >&2 +if [ -z "${HOOK_NAME}" ]; then + echo "Usage: claude_hook.sh " >&2 + echo "Example: claude_hook.sh rules_check" >&2 exit 1 fi @@ -41,15 +40,12 @@ if [ ! -t 0 ]; then HOOK_INPUT=$(cat) fi -# Set platform environment variable for the Python module +# Set platform environment variable for the hook export DEEPWORK_HOOK_PLATFORM="claude" -# Run the Python module, passing the input via stdin -# The Python module is responsible for: -# 1. Reading stdin (normalized by wrapper) -# 2. Processing the hook logic -# 3. Writing JSON to stdout -echo "${HOOK_INPUT}" | python -m "${PYTHON_MODULE}" +# Run the hook via deepwork CLI +# This works regardless of how deepwork was installed (pipx, uv, nix flake, etc.) +echo "${HOOK_INPUT}" | deepwork hook "${HOOK_NAME}" exit_code=$? exit ${exit_code} diff --git a/src/deepwork/hooks/gemini_hook.sh b/src/deepwork/hooks/gemini_hook.sh index a2bb09da..0daa551e 100755 --- a/src/deepwork/hooks/gemini_hook.sh +++ b/src/deepwork/hooks/gemini_hook.sh @@ -6,14 +6,13 @@ # and work on any supported platform. # # Usage: -# gemini_hook.sh +# gemini_hook.sh # # Example: -# gemini_hook.sh deepwork.hooks.rules_check +# gemini_hook.sh rules_check # -# The Python module should implement a main() function that: -# 1. Calls deepwork.hooks.wrapper.run_hook() with a hook function -# 2. The hook function receives HookInput and returns HookOutput +# The hook is run via the deepwork CLI, which works regardless of how +# deepwork was installed (pipx, uv, nix flake, etc.). # # Environment variables set by Gemini CLI: # GEMINI_PROJECT_DIR - Absolute path to project root @@ -26,12 +25,12 @@ set -e -# Get the Python module to run -PYTHON_MODULE="${1:-}" +# Get the hook name to run +HOOK_NAME="${1:-}" -if [ -z "${PYTHON_MODULE}" ]; then - echo "Usage: gemini_hook.sh " >&2 - echo "Example: gemini_hook.sh deepwork.hooks.rules_check" >&2 +if [ -z "${HOOK_NAME}" ]; then + echo "Usage: gemini_hook.sh " >&2 + echo "Example: gemini_hook.sh rules_check" >&2 exit 1 fi @@ -41,15 +40,12 @@ if [ ! -t 0 ]; then HOOK_INPUT=$(cat) fi -# Set platform environment variable for the Python module +# Set platform environment variable for the hook export DEEPWORK_HOOK_PLATFORM="gemini" -# Run the Python module, passing the input via stdin -# The Python module is responsible for: -# 1. Reading stdin (normalized by wrapper) -# 2. Processing the hook logic -# 3. Writing JSON to stdout -echo "${HOOK_INPUT}" | python -m "${PYTHON_MODULE}" +# Run the hook via deepwork CLI +# This works regardless of how deepwork was installed (pipx, uv, nix flake, etc.) +echo "${HOOK_INPUT}" | deepwork hook "${HOOK_NAME}" exit_code=$? exit ${exit_code} diff --git a/src/deepwork/hooks/rules_check.py b/src/deepwork/hooks/rules_check.py index 3d71dcce..0a39fa3d 100644 --- a/src/deepwork/hooks/rules_check.py +++ b/src/deepwork/hooks/rules_check.py @@ -6,12 +6,15 @@ Rule files are loaded from .deepwork/rules/ directory as frontmatter markdown files. -Usage (via shell wrapper): - claude_hook.sh deepwork.hooks.rules_check - gemini_hook.sh deepwork.hooks.rules_check +Usage (via shell wrapper - recommended): + claude_hook.sh rules_check + gemini_hook.sh rules_check -Or directly with platform environment variable: - DEEPWORK_HOOK_PLATFORM=claude python -m deepwork.hooks.rules_check +Or directly via deepwork CLI: + deepwork hook rules_check + +Or with platform environment variable: + DEEPWORK_HOOK_PLATFORM=claude deepwork hook rules_check """ from __future__ import annotations