Private
Public Access
conductor(archive): move legacy superpowers specs/plans from docs/superpowers/ to conductor/archive/superpowers/
Resolves HIGH-priority recommendation #3 from superpowers_review_20260619 §16.1 (dual-convention). 41 files moved (21 specs + 20 plans + 2 subdirs + root): - docs/superpowers/specs/*.md -> conductor/archive/superpowers/specs/*.md (21 files) - docs/superpowers/plans/*.md -> conductor/archive/superpowers/plans/*.md (20 files) Original taxonomy preserved (specs/ and plans/ subdirectories intact). The superpowers-plugin source itself remains at C:/Users/Ed/.cache/opencode/.../superpowers/skills/. Future spec/plan artifacts must use the conductor convention: conductor/tracks/<id>/spec.md + plan.md.
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
# Clean Install Test
|
||||
|
||||
**Date:** 2026-06-02
|
||||
**Status:** Draft (pending review)
|
||||
|
||||
---
|
||||
|
||||
## Context & Motivation
|
||||
|
||||
The user wants a "clean install" test that verifies Manual Slop works correctly when installed from scratch in an isolated environment. The test should:
|
||||
|
||||
1. Clone the repo to a temp directory (no shared state with the source)
|
||||
2. Install dependencies via `uv sync`
|
||||
3. Launch `sloppy.py --enable-test-hooks`
|
||||
4. Verify the Hook API responds (smoke test that the app is functional)
|
||||
|
||||
This is a defense against:
|
||||
- Repository changes that only work on the developer's machine
|
||||
- Dependency drift (works on dev, fails on fresh install)
|
||||
- Build/launch issues that only appear in clean environments
|
||||
|
||||
The target is the user's private Gitea server: `https://git.cozyair.dev/ed/manual_slop`. This is intentionally NOT a public GitHub URL — the test must work in the user's private infrastructure.
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- `tests/test_clean_install.py` — Opt-in pytest test
|
||||
- `pyproject.toml` update: add `clean_install` marker
|
||||
- Gating via `RUN_CLEAN_INSTALL_TEST=1` env var
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Auto-clone in CI (the user can opt in via a future CI workflow)
|
||||
- Continuous monitoring
|
||||
- Cloning from a specific branch/tag (uses HEAD of main by default)
|
||||
|
||||
---
|
||||
|
||||
## Design
|
||||
|
||||
### Test File Structure
|
||||
|
||||
```python
|
||||
# tests/test_clean_install.py
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
|
||||
REPO_URL = "https://git.cozyair.dev/ed/manual_slop"
|
||||
STARTUP_TIMEOUT_SECONDS = 30
|
||||
READINESS_POLL_INTERVAL = 0.5
|
||||
|
||||
|
||||
@pytest.mark.clean_install
|
||||
def test_clean_install_runs_with_hooks(tmp_path):
|
||||
"""Clone the repo, install deps, launch sloppy.py, verify Hook API."""
|
||||
if os.environ.get("RUN_CLEAN_INSTALL_TEST") != "1":
|
||||
pytest.skip("Set RUN_CLEAN_INSTALL_TEST=1 to enable")
|
||||
|
||||
clone_dir = tmp_path / "manual_slop"
|
||||
|
||||
# 1. Clone
|
||||
result = subprocess.run(
|
||||
["git", "clone", REPO_URL, str(clone_dir)],
|
||||
capture_output=True, text=True, timeout=60,
|
||||
)
|
||||
assert result.returncode == 0, f"Clone failed: {result.stderr}"
|
||||
|
||||
# 2. Install deps
|
||||
result = subprocess.run(
|
||||
["uv", "sync"],
|
||||
cwd=str(clone_dir),
|
||||
capture_output=True, text=True, timeout=180,
|
||||
)
|
||||
assert result.returncode == 0, f"uv sync failed: {result.stderr}"
|
||||
|
||||
# 3. Launch sloppy.py with hooks
|
||||
process = subprocess.Popen(
|
||||
["uv", "run", "sloppy.py", "--enable-test-hooks"],
|
||||
cwd=str(clone_dir),
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if os.name == 'nt' else 0,
|
||||
)
|
||||
|
||||
try:
|
||||
# 4. Poll /status endpoint
|
||||
start = time.time()
|
||||
ready = False
|
||||
while time.time() - start < STARTUP_TIMEOUT_SECONDS:
|
||||
if process.poll() is not None:
|
||||
pytest.fail(f"Process exited early. stderr: {process.stderr.read()[:2000]}")
|
||||
try:
|
||||
response = requests.get(
|
||||
"http://127.0.0.1:8999/status",
|
||||
timeout=1.0,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
payload = response.json()
|
||||
if payload.get("status") == "running":
|
||||
ready = True
|
||||
break
|
||||
except (requests.ConnectionError, requests.Timeout):
|
||||
pass
|
||||
time.sleep(READINESS_POLL_INTERVAL)
|
||||
|
||||
assert ready, f"Hook server did not respond within {STARTUP_TIMEOUT_SECONDS}s"
|
||||
|
||||
# 5. Test a write hook (any POST endpoint that should respond)
|
||||
response = requests.get(
|
||||
"http://127.0.0.1:8999/api/gui/mma_status",
|
||||
timeout=5.0,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
# The mma_status endpoint returns a dict; verify it has expected keys
|
||||
data = response.json()
|
||||
assert "status" in data or "mma_state" in data
|
||||
|
||||
finally:
|
||||
# 6. Cleanup
|
||||
if os.name == 'nt':
|
||||
subprocess.run(
|
||||
["taskkill", "/F", "/T", "/PID", str(process.pid)],
|
||||
capture_output=True,
|
||||
)
|
||||
else:
|
||||
process.terminate()
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
```
|
||||
|
||||
### `pyproject.toml` Update
|
||||
|
||||
```toml
|
||||
[tool.pytest.ini_options]
|
||||
markers = [
|
||||
"integration: integration tests requiring live GUI",
|
||||
"strict: tests that require strict mode",
|
||||
"clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)",
|
||||
]
|
||||
```
|
||||
|
||||
### Running the Test
|
||||
|
||||
**Default (skip):**
|
||||
```bash
|
||||
uv run pytest tests/test_clean_install.py -v
|
||||
# SKIPPED: Set RUN_CLEAN_INSTALL_TEST=1 to enable
|
||||
```
|
||||
|
||||
**Opt-in:**
|
||||
```bash
|
||||
RUN_CLEAN_INSTALL_TEST=1 uv run pytest tests/test_clean_install.py -v
|
||||
```
|
||||
|
||||
**Just the clean_install marker:**
|
||||
```bash
|
||||
RUN_CLEAN_INSTALL_TEST=1 uv run pytest -m clean_install -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- `tests/test_clean_install.py` — NEW
|
||||
- `pyproject.toml` — MODIFY: add `clean_install` marker
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- `RUN_CLEAN_INSTALL_TEST=1 uv run pytest tests/test_clean_install.py -v` passes when run in an environment with network access to `git.cozyair.dev`
|
||||
- Without the env var, the test skips (no network access required)
|
||||
- The test takes 30-90 seconds to run (clone + install + launch)
|
||||
- A failure in any step (clone, sync, launch, hook response) results in a clear error message
|
||||
- Process cleanup is robust (no orphaned processes on Windows or Unix)
|
||||
|
||||
---
|
||||
|
||||
## Risks
|
||||
|
||||
1. **Network dependency:** The test requires network access to `git.cozyair.dev`. In CI environments without that access, the test will fail. Mitigation: the env var gating makes this opt-in.
|
||||
2. **Clone target is private:** Unlike GitHub, the URL is on a private Gitea server. Test failure on a public CI would leak the existence of the private repo. Mitigation: only run on private infrastructure; the test is opt-in.
|
||||
3. **Port conflicts:** The test uses port 8999. If another process is using it, the test will fail. Mitigation: the polling loop detects early exit and reports the port-in-use error.
|
||||
4. **`uv sync` is slow:** On a fresh machine, `uv sync` can take 30-60 seconds. The test budget is 180 seconds which should be sufficient.
|
||||
Reference in New Issue
Block a user