# Spec: Project-Specific qen Wrapper Script

## Overview

The `proj/qen` wrapper script is a project-specific executable that ensures `qen` commands run in the correct project context, even when you have multiple projects or are in a different directory.

**Status**: Partially implemented but UNTESTED in integration.

## User Requirements

1. Generated wrapper at `proj/YYMMDD-project/qen` for each project
2. Uses `uvx qen` to ensure latest version (consistent with README)
3. Automatically passes `--meta` and `--proj` flags
4. Works from any directory (portable)
5. Executable permissions (`chmod +x`)
6. All template variables properly substituted
7. Basic integration test verifying wrapper works

## Current State

### What Works

- Template exists at [proj/qen](proj/qen:1-20)
- Template rendering in [src/qen/project.py:256-264](src/qen/project.py:256-264)
- File is created and made executable
- Unit tests verify file creation and `meta_path` variable

### What's Missing

- **NO integration test** that verifies the wrapper actually works
- No test that ALL template variables are substituted (tests are mocked)
- No verification that generated wrapper can execute commands

## Template Structure

**File**: `proj/qen` (bash script template)

```bash
#!/usr/bin/env bash
# QEN Project Wrapper - Auto-generated by qen init
# This script ensures qen commands run in the context of this project
# even if you have multiple projects configured.
#
# Project: ${project_name}
# Branch: ${branch_name}
# Created: ${date}

# Resolve the directory containing this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Project configuration
META_PATH="${meta_path}"
PROJECT_NAME="${project_name}"

# Run qen with project context using uvx
# This ensures we always use the latest qen version with the correct project settings
exec uvx qen --meta "$META_PATH" --proj "$PROJECT_NAME" "$@"
```

### Template Variables

All variables defined in [src/qen/project.py:218-226](src/qen/project.py:218-226):

| Variable | Example | Usage in Template |
|----------|---------|-------------------|
| `project_name` | `my-feature` | Line 6, 15 (PROJECT_NAME) |
| `branch_name` | `251208-my-feature` | Line 7 |
| `date` | `2025-12-08` | Line 8 |
| `timestamp` | `2025-12-08T10:30:00Z` | Not used |
| `folder_path` | `proj/251208-my-feature` | Not used |
| `github_org` | `data-yaml` | Not used |
| `meta_path` | `/path/to/meta` | Line 14 (META_PATH) |

## Implementation Status

### Already Implemented ✅

1. Template file exists at `proj/qen`
2. Template uses `uvx qen` (correct choice per README)
3. Template passes `--meta` and `--proj` flags (correct flags)
4. `create_project_structure()` renders template with all variables
5. File made executable with `chmod 0o755`
6. Unit tests verify:
   - File is created
   - File is executable
   - `meta_path` variable is passed to template

### Needs Implementation ❌

1. **Integration test** that:
   - Creates real project via `qen init`
   - Verifies ALL template variables are substituted (no `${...}` remain)
   - Verifies wrapper script is executable
   - Runs wrapper script with a simple command (e.g., `./qen status`)
   - Verifies command executes successfully

## Testing Strategy

### Current Tests (Unit)

**File**: [tests/unit/qen/test_project.py:528-579](tests/unit/qen/test_project.py:528-579)

```python
class TestQenExecutableCreation:
    def test_qen_executable_created(self, tmp_path: Path, mocker) -> None:
        """Test that qen executable is created during project structure creation."""
        # ✅ Tests file exists
        # ✅ Tests file is executable

    def test_qen_executable_has_meta_path_variable(self, tmp_path: Path, mocker) -> None:
        """Test that qen executable template receives meta_path variable."""
        # ✅ Tests meta_path is passed to render_template
        # ❌ Uses mocks - doesn't verify actual substitution
```

**Problem**: Tests use mocks so they don't verify actual template substitution.

### Needed Test (Integration)

**Pattern**: Follow [tests/integration/test_pr_status.py](../../tests/integration/test_pr_status.py)

**Key Insight**: Use `--meta` flag to specify qen-test as meta repo WITHOUT touching user's actual config.

**New file**: `tests/integration/test_qen_wrapper.py`

```python
"""Integration test for proj/qen wrapper script.

Tests that the auto-generated wrapper script:
1. Has all template variables substituted (no ${...} remain)
2. Is executable
3. Can run basic qen commands

Uses REAL qen-test repository for integration testing.
"""

import subprocess
from pathlib import Path

import pytest


@pytest.mark.integration
def test_qen_wrapper_generation(
    real_test_repo: Path,
    unique_prefix: str,
    cleanup_branches: list[str],
) -> None:
    """Test that qen init generates working wrapper script.

    Creates a real project in qen-test and verifies:
    - All files from ./proj templates are created
    - No template variables remain (no ${...})
    - Wrapper script is executable
    - Wrapper script can run commands

    Args:
        real_test_repo: Path to cloned qen-test repository
        unique_prefix: Unique prefix for test branches
        cleanup_branches: List to track branches for cleanup
    """
    # Generate project name with unique prefix
    project_name = f"{unique_prefix}-wrapper-test"

    # Run qen init to create project using --meta flag
    # IMPORTANT: Use --meta flag to specify qen-test as meta repo
    # This avoids touching the user's actual qen configuration
    result = subprocess.run(
        ["qen", "--meta", str(real_test_repo), "init", project_name, "--yes"],
        capture_output=True,
        text=True,
    )

    assert result.returncode == 0, f"qen init failed: {result.stderr}"

    # Track branch for cleanup
    # Extract branch name from project (YYMMDD-project-name format)
    import re
    from datetime import datetime
    date_prefix = datetime.now().strftime("%y%m%d")
    branch_name = f"{date_prefix}-{project_name}"
    cleanup_branches.append(branch_name)

    # Verify project directory exists
    project_dir = real_test_repo / "proj" / branch_name
    assert project_dir.exists(), f"Project directory not created: {project_dir}"

    # Verify all expected files exist
    expected_files = ["README.md", "pyproject.toml", ".gitignore", "qen", "repos"]
    for file in expected_files:
        file_path = project_dir / file
        assert file_path.exists(), f"Expected file missing: {file}"

    # Verify qen wrapper is executable
    qen_wrapper = project_dir / "qen"
    assert qen_wrapper.stat().st_mode & 0o111, "qen wrapper is not executable"

    # Verify NO template variables remain in any file
    for file in ["README.md", "pyproject.toml", ".gitignore", "qen"]:
        content = (project_dir / file).read_text()
        assert "${" not in content, f"Unsubstituted template variable in {file}: {content}"

    # Verify wrapper script can execute
    # Run a simple command: ./qen status (should work even with no repos)
    result = subprocess.run(
        ["./qen", "status"],
        cwd=project_dir,
        capture_output=True,
        text=True,
    )

    # Should succeed or fail gracefully (not with exec error)
    # Don't check return code - just verify it runs without bash errors
    assert "bash:" not in result.stderr.lower(), f"Wrapper execution failed: {result.stderr}"
    assert "command not found" not in result.stderr.lower(), f"uvx or qen not found: {result.stderr}"
```

## Implementation Steps

### Step 1: Write Integration Test

**File**: `tests/integration/test_qen_wrapper.py`

Create new integration test following the pattern above.

**Test Coverage**:

- ✅ `qen init <project>` creates project
- ✅ All template files exist (README.md, pyproject.toml, .gitignore, qen, repos/)
- ✅ No unsubstituted variables (`${...}`) in any file
- ✅ `qen` wrapper is executable
- ✅ `./qen status` runs without bash errors

### Step 2: Run Test and Fix Issues

```bash
# Run integration test
./poe test-integration tests/integration/test_qen_wrapper.py -v

# If test fails, debug and fix:
# - Template variable issues
# - Path resolution issues
# - Permission issues
```

### Step 3: Update Documentation

Add to README.md or CLAUDE.md:

- Document the `proj/qen` wrapper
- Explain how to use it
- Mention it uses `uvx qen` for latest version

## Success Criteria

- ✅ Integration test passes
- ✅ Test verifies all template variables are substituted
- ✅ Test verifies wrapper is executable
- ✅ Test verifies wrapper can run commands
- ✅ All existing tests still pass
- ✅ CI includes integration test (or documents how to run manually)

## Design Decisions (From User Guidance)

### 1. Tool Choice: `uvx qen` ✅

**Decision**: Always use `uvx qen`
**Rationale**: Consistent with README philosophy of "always latest"

### 2. Template Variables: Keep All ✅

**Decision**: Keep all 7 template variables
**Rationale**:

- Used variables: `project_name`, `branch_name`, `date`, `meta_path`
- Unused variables: `timestamp`, `folder_path`, `github_org`
- Keep unused for consistency across templates and future use

### 3. Testing: Add One Integration Test ✅

**Decision**: Single realistic integration test
**Rationale**:

- Verifies happy path end-to-end
- Tests against real qen-test repo
- Verifies actual substitution (not mocked)

### 4. Wrapper Intelligence: Dumb ✅

**Decision**: Keep wrapper simple - just pass flags
**Rationale**:

- No validation, no auto-fixing
- Fail fast if issues occur
- Keep it simple and reliable

### 5. Error Handling: Not Our Problem ✅

**Decision**: No special handling if meta repo is deleted/moved
**Rationale**:

- If meta repo is gone, everything breaks anyway
- Don't try to recover or handle gracefully
- Let it fail naturally

## Out of Scope

- Custom wrapper templates
- Smart error handling (validation, recovery)
- Alternative to `uvx qen` (system `qen`, pip install, etc.)
- Updating wrapper when project moves
- Wrapper configuration options

## Future Enhancements (Not Now)

- Auto-update wrapper when qen version changes
- Wrapper validates meta repo exists before running
- Custom wrapper templates per project
- Optional flags in wrapper (e.g., `--verbose` by default)
