lemming-cli 0.4.1__tar.gz → 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lemming_cli-0.4.3/.dockerignore +30 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.github/workflows/ci.yml +1 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/Dockerfile +10 -3
- lemming_cli-0.4.1/README.md → lemming_cli-0.4.3/PKG-INFO +51 -16
- lemming_cli-0.4.1/PKG-INFO → lemming_cli-0.4.3/README.md +29 -38
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/SKILL.md +24 -7
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/pyproject.toml +2 -2
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/config_test.py +1 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/config.py +1 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/exec_cli.py +6 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/exec_cli_test.py +18 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/cli.py +4 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/container.py +36 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/container_test.py +31 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/models.py +1 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/runner.py +79 -11
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/runner_test.py +107 -4
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/dashboard.spec.js +1 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/favicon.spec.js +1 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/screenshots.spec.js +2 -2
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/uv.lock +1 -1
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.github/workflows/publish.yml +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.github/workflows/screenshots.yml +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.gitignore +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.prettierignore +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/.prettierrc +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/LICENSE +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/biome.json +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docker-compose.yml +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/EVALS.md +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/HOOKS.md +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/dashboard-desktop.png +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/dashboard-mobile.png +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/task-log-desktop.png +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/docs/screenshots/task-log-mobile.png +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/package-lock.json +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/package.json +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/playwright.config.js +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/__init__.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/__init__.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/auth.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/auth_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/config.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/conftest.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/context.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/context_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/directories.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/directories_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/files.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/files_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/hooks.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/hooks_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/logging.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/logging_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/loop.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/loop_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/main.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/main_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/tasks.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api/tasks_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/api_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/brief_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/__init__.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/brief.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/config_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/goal.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/goal_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/hooks.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/hooks_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/machine_output_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/main.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/main_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/operations.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/operations_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/progress.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/progress_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/readability_cli.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/readability_cli_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/skill.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/skill_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/stop_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/tasks.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli/tasks_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/cli_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/conftest.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/__init__.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/__main__.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/cli_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/fixtures.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/fixtures_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/harness.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/harness_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/readability.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/readability_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/roadmap.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/roadmap_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/scenarios.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/scenarios_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/suites.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/trial.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/evals/trial_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/hooks.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/hooks_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/integration_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/main.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/main_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/models_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/orchestrator.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/orchestrator_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/paths.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/paths_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/persistence.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/persistence_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/50-readability.md +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/60-testing.md +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/70-ux.md +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/hooks/90-roadmap.md +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts/taskrunner.md +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/prompts_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/providers.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/providers_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/scope.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/scope_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/shutdown.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/shutdown_integration_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/shutdown_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/__init__.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/lifecycle.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/lifecycle_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/limits.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/operations.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/operations_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/progress.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/progress_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/queries.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/tasks/queries_test.py +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/dashboard.test.js +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/favicon.js +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/files.html +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/files.spec.js +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/index.html +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/index.js +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/logs.html +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/logs.test.js +0 -0
- {lemming_cli-0.4.1 → lemming_cli-0.4.3}/src/lemming/web/mancha.js +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# The image builds its own environments, so host-built ones are not just
|
|
2
|
+
# wasted bytes in the context — they are the wrong platform's binaries
|
|
3
|
+
# landing on top of what `uv sync` and `npm install` are about to create.
|
|
4
|
+
.venv
|
|
5
|
+
node_modules
|
|
6
|
+
|
|
7
|
+
# History and build products the image never reads.
|
|
8
|
+
.git
|
|
9
|
+
.github
|
|
10
|
+
dist
|
|
11
|
+
test-results
|
|
12
|
+
docs/screenshots
|
|
13
|
+
|
|
14
|
+
# Agent worktrees are whole checkouts of this repo, each with its own
|
|
15
|
+
# dependencies installed, and they dwarf the source they sit beside.
|
|
16
|
+
.claude
|
|
17
|
+
|
|
18
|
+
# Local state and caches.
|
|
19
|
+
.pytest_cache
|
|
20
|
+
.ruff_cache
|
|
21
|
+
__pycache__
|
|
22
|
+
*.pyc
|
|
23
|
+
.DS_Store
|
|
24
|
+
|
|
25
|
+
# Roadmaps and their locks belong to whoever is running lemming, not to
|
|
26
|
+
# the image; a stale one baked in would be read as that project's state.
|
|
27
|
+
tasks.yml
|
|
28
|
+
*.lock
|
|
29
|
+
!uv.lock
|
|
30
|
+
!package-lock.json
|
|
@@ -11,7 +11,8 @@ RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
|
|
11
11
|
&& rm -rf /var/lib/apt/lists/*
|
|
12
12
|
|
|
13
13
|
# Install AI coding CLIs (antigravity ships a native binary, not an npm package)
|
|
14
|
-
RUN npm install -g
|
|
14
|
+
RUN npm install -g --allow-scripts=opencode-ai \
|
|
15
|
+
@openai/codex @anthropic-ai/claude-code opencode-ai \
|
|
15
16
|
&& curl -fsSL https://antigravity.google/cli/install.sh | bash -s -- --dir /usr/local/bin
|
|
16
17
|
|
|
17
18
|
# Copy cloudflared and uv binaries directly from their official images
|
|
@@ -22,8 +23,14 @@ WORKDIR /opt/lemming
|
|
|
22
23
|
COPY . .
|
|
23
24
|
RUN uv sync --no-dev --frozen
|
|
24
25
|
|
|
25
|
-
#
|
|
26
|
-
|
|
26
|
+
# Frontend dependencies are always needed: postinstall vendors mancha into
|
|
27
|
+
# the web assets. The browsers are not, and they are more than half the
|
|
28
|
+
# image, so anything that will never drive one builds without them.
|
|
29
|
+
ARG INSTALL_BROWSERS=true
|
|
30
|
+
RUN npm install \
|
|
31
|
+
&& if [ "$INSTALL_BROWSERS" = "true" ]; then \
|
|
32
|
+
npx playwright install --with-deps; \
|
|
33
|
+
fi
|
|
27
34
|
|
|
28
35
|
# Pre-create the lemming home directory so users can bind-mount a .env file into it
|
|
29
36
|
RUN mkdir -p /root/.local/lemming
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lemming-cli
|
|
3
|
+
Version: 0.4.3
|
|
4
|
+
Summary: An autonomous, iterative task runner for AI coding agents.
|
|
5
|
+
Project-URL: Homepage, https://github.com/owahltinez/lemming
|
|
6
|
+
Project-URL: Repository, https://github.com/owahltinez/lemming
|
|
7
|
+
Author: Lemming Contributors
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,ai,antigravity,automation,claude,opencode
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Requires-Dist: beautifulsoup4>=4.13.3
|
|
13
|
+
Requires-Dist: click>=8.0.0
|
|
14
|
+
Requires-Dist: fastapi>=0.124.4
|
|
15
|
+
Requires-Dist: markdownify>=0.14.1
|
|
16
|
+
Requires-Dist: pydantic>=2.12.5
|
|
17
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
18
|
+
Requires-Dist: readability-cli>=0.7.0
|
|
19
|
+
Requires-Dist: requests>=2.32.3
|
|
20
|
+
Requires-Dist: uvicorn>=0.33.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
1
23
|
# Lemming 🐹
|
|
2
24
|
|
|
3
25
|
**The transparent, tool-agnostic orchestrator for autonomous AI coding agents.**
|
|
@@ -16,8 +38,8 @@ human-readable `tasks.yml` file.
|
|
|
16
38
|
update is recorded in a human-readable `tasks.yml` file. You can step in,
|
|
17
39
|
adjust the roadmap, or swap agents at any time.
|
|
18
40
|
- **Tool Agnostic**: Lemming doesn't care which agent you use. It works
|
|
19
|
-
out-of-the-box with `agy`, `
|
|
20
|
-
scripts.
|
|
41
|
+
out-of-the-box with `agy`, `opencode`, `claude`, `codex`, or even your own
|
|
42
|
+
custom scripts.
|
|
21
43
|
- **Resilient Execution**: With built-in heartbeat monitoring, automatic
|
|
22
44
|
retries, and progress tracking, Lemming handles process crashes and rate
|
|
23
45
|
limits gracefully.
|
|
@@ -71,7 +93,7 @@ Start the autonomous loop.
|
|
|
71
93
|
|
|
72
94
|
```bash
|
|
73
95
|
# Run using the project's configured agent (defaults to the first
|
|
74
|
-
# supported runner found on PATH: agy,
|
|
96
|
+
# supported runner found on PATH: agy, opencode, claude, or codex)
|
|
75
97
|
lemming run
|
|
76
98
|
|
|
77
99
|
# Flags passed after -- are sent directly to the underlying runner
|
|
@@ -118,22 +140,25 @@ removed unless it failed — in which case it is kept, and its path printed, so
|
|
|
118
140
|
the log can be read. Kept directories live in `~/.local/lemming/exec-*` and
|
|
119
141
|
are retired automatically a week later, so a recent failure is always still
|
|
120
142
|
there to inspect. Stdout carries the agent's message alone and everything
|
|
121
|
-
else goes to stderr, so the output can be consumed directly.
|
|
143
|
+
else goes to stderr, so the output can be consumed directly. Stderr reports
|
|
144
|
+
the ephemeral tasks file and task ID at startup; pass those to `lemming
|
|
145
|
+
--tasks-file <path> status <id>` or `logs <id>` to inspect an active run. Use
|
|
146
|
+
the global verbose flag (`lemming -v exec ...`) to stream runner activity.
|
|
122
147
|
|
|
123
148
|
Note that the agent runs unattended (`--yolo` by default), so it does not
|
|
124
149
|
inherit the permission prompts of whatever launched it.
|
|
125
150
|
|
|
126
151
|
### Teaching Your Agent to Use It
|
|
127
152
|
|
|
128
|
-
Lemming ships an Agent Skill so that an agent already running — Claude
|
|
129
|
-
Gemini CLI, Cursor — can reach any other agent CLI without knowing how
|
|
130
|
-
one spells its flags. Install it once:
|
|
153
|
+
Lemming ships an Agent Skill so that an agent already running — Codex, Claude
|
|
154
|
+
Code, Gemini CLI, Cursor — can reach any other agent CLI without knowing how
|
|
155
|
+
each one spells its flags. Install it once:
|
|
131
156
|
|
|
132
157
|
```bash
|
|
133
158
|
# Cross-tool location (~/.agents/skills), and report any tool-specific ones
|
|
134
159
|
lemming skill install
|
|
135
160
|
|
|
136
|
-
# Cover
|
|
161
|
+
# Cover detected tools that require their own skill directory too
|
|
137
162
|
lemming skill install --all
|
|
138
163
|
|
|
139
164
|
# This repository only
|
|
@@ -268,7 +293,7 @@ lemming hooks list
|
|
|
268
293
|
lemming config list
|
|
269
294
|
|
|
270
295
|
# Persist configuration to tasks.yml
|
|
271
|
-
lemming config set runner
|
|
296
|
+
lemming config set runner opencode
|
|
272
297
|
```
|
|
273
298
|
|
|
274
299
|
### Evaluating Prompt Changes
|
|
@@ -373,8 +398,9 @@ These come before the subcommand and apply to all of them.
|
|
|
373
398
|
per-task `--runner`/`--model` overrides anything passed here.
|
|
374
399
|
- **`exec [<description>]`**: Run one task, or one set of reviews, outside any
|
|
375
400
|
roadmap. Prints the agent's closing message to stdout and everything else to
|
|
376
|
-
stderr
|
|
377
|
-
|
|
401
|
+
stderr, including its tasks file and task ID at startup. Use `lemming -v
|
|
402
|
+
exec ...` to stream runner activity. Exits non-zero if the task did not
|
|
403
|
+
complete. See [One-off tasks](#one-off-tasks-without-a-roadmap).
|
|
378
404
|
- `-f/--file`: Read the description from a file, or `-` for stdin. Unlike
|
|
379
405
|
`add`, there is no length cap.
|
|
380
406
|
- `--review <names>`: Reviews to run after the task, comma-separated or
|
|
@@ -449,7 +475,10 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
|
|
|
449
475
|
|
|
450
476
|
- **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
|
|
451
477
|
the project workspace with `--add-dir`
|
|
452
|
-
- **
|
|
478
|
+
- **OpenCode**: Runs non-interactively via `opencode run`, adds `--format json`,
|
|
479
|
+
and, in YOLO mode, adds `--auto`. For its Google provider, Lemming makes an
|
|
480
|
+
existing `GOOGLE_API_KEY` or `GEMINI_API_KEY` available under OpenCode's
|
|
481
|
+
native `GOOGLE_GENERATIVE_AI_API_KEY` name.
|
|
453
482
|
- **Claude**: Adds `--dangerously-skip-permissions`
|
|
454
483
|
- **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
|
|
455
484
|
YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
|
|
@@ -511,10 +540,16 @@ pushing a `v*` tag triggers the `publish.yml` GitHub Actions workflow, which
|
|
|
511
540
|
builds the package with `uv build` and uploads it.
|
|
512
541
|
|
|
513
542
|
```bash
|
|
514
|
-
# 1. Bump
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
git
|
|
543
|
+
# 1. Bump pyproject.toml and uv.lock together, commit, and push
|
|
544
|
+
uv version --bump patch --no-sync
|
|
545
|
+
version="$(uv version --short)"
|
|
546
|
+
git add pyproject.toml uv.lock
|
|
547
|
+
git commit -m "Bump version to $version"
|
|
548
|
+
git push origin main
|
|
549
|
+
|
|
550
|
+
# 2. After CI passes, tag the release and push the tag
|
|
551
|
+
git tag "v$version"
|
|
552
|
+
git push origin "v$version"
|
|
518
553
|
```
|
|
519
554
|
|
|
520
555
|
---
|
|
@@ -1,25 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: lemming-cli
|
|
3
|
-
Version: 0.4.1
|
|
4
|
-
Summary: An autonomous, iterative task runner for AI coding agents.
|
|
5
|
-
Project-URL: Homepage, https://github.com/owahltinez/lemming
|
|
6
|
-
Project-URL: Repository, https://github.com/owahltinez/lemming
|
|
7
|
-
Author: Lemming Contributors
|
|
8
|
-
License: MIT
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Keywords: agents,ai,aider,antigravity,automation,claude
|
|
11
|
-
Requires-Python: >=3.12
|
|
12
|
-
Requires-Dist: beautifulsoup4>=4.13.3
|
|
13
|
-
Requires-Dist: click>=8.0.0
|
|
14
|
-
Requires-Dist: fastapi>=0.124.4
|
|
15
|
-
Requires-Dist: markdownify>=0.14.1
|
|
16
|
-
Requires-Dist: pydantic>=2.12.5
|
|
17
|
-
Requires-Dist: pyyaml>=6.0.3
|
|
18
|
-
Requires-Dist: readability-cli>=0.7.0
|
|
19
|
-
Requires-Dist: requests>=2.32.3
|
|
20
|
-
Requires-Dist: uvicorn>=0.33.0
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
|
|
23
1
|
# Lemming 🐹
|
|
24
2
|
|
|
25
3
|
**The transparent, tool-agnostic orchestrator for autonomous AI coding agents.**
|
|
@@ -38,8 +16,8 @@ human-readable `tasks.yml` file.
|
|
|
38
16
|
update is recorded in a human-readable `tasks.yml` file. You can step in,
|
|
39
17
|
adjust the roadmap, or swap agents at any time.
|
|
40
18
|
- **Tool Agnostic**: Lemming doesn't care which agent you use. It works
|
|
41
|
-
out-of-the-box with `agy`, `
|
|
42
|
-
scripts.
|
|
19
|
+
out-of-the-box with `agy`, `opencode`, `claude`, `codex`, or even your own
|
|
20
|
+
custom scripts.
|
|
43
21
|
- **Resilient Execution**: With built-in heartbeat monitoring, automatic
|
|
44
22
|
retries, and progress tracking, Lemming handles process crashes and rate
|
|
45
23
|
limits gracefully.
|
|
@@ -93,7 +71,7 @@ Start the autonomous loop.
|
|
|
93
71
|
|
|
94
72
|
```bash
|
|
95
73
|
# Run using the project's configured agent (defaults to the first
|
|
96
|
-
# supported runner found on PATH: agy,
|
|
74
|
+
# supported runner found on PATH: agy, opencode, claude, or codex)
|
|
97
75
|
lemming run
|
|
98
76
|
|
|
99
77
|
# Flags passed after -- are sent directly to the underlying runner
|
|
@@ -140,22 +118,25 @@ removed unless it failed — in which case it is kept, and its path printed, so
|
|
|
140
118
|
the log can be read. Kept directories live in `~/.local/lemming/exec-*` and
|
|
141
119
|
are retired automatically a week later, so a recent failure is always still
|
|
142
120
|
there to inspect. Stdout carries the agent's message alone and everything
|
|
143
|
-
else goes to stderr, so the output can be consumed directly.
|
|
121
|
+
else goes to stderr, so the output can be consumed directly. Stderr reports
|
|
122
|
+
the ephemeral tasks file and task ID at startup; pass those to `lemming
|
|
123
|
+
--tasks-file <path> status <id>` or `logs <id>` to inspect an active run. Use
|
|
124
|
+
the global verbose flag (`lemming -v exec ...`) to stream runner activity.
|
|
144
125
|
|
|
145
126
|
Note that the agent runs unattended (`--yolo` by default), so it does not
|
|
146
127
|
inherit the permission prompts of whatever launched it.
|
|
147
128
|
|
|
148
129
|
### Teaching Your Agent to Use It
|
|
149
130
|
|
|
150
|
-
Lemming ships an Agent Skill so that an agent already running — Claude
|
|
151
|
-
Gemini CLI, Cursor — can reach any other agent CLI without knowing how
|
|
152
|
-
one spells its flags. Install it once:
|
|
131
|
+
Lemming ships an Agent Skill so that an agent already running — Codex, Claude
|
|
132
|
+
Code, Gemini CLI, Cursor — can reach any other agent CLI without knowing how
|
|
133
|
+
each one spells its flags. Install it once:
|
|
153
134
|
|
|
154
135
|
```bash
|
|
155
136
|
# Cross-tool location (~/.agents/skills), and report any tool-specific ones
|
|
156
137
|
lemming skill install
|
|
157
138
|
|
|
158
|
-
# Cover
|
|
139
|
+
# Cover detected tools that require their own skill directory too
|
|
159
140
|
lemming skill install --all
|
|
160
141
|
|
|
161
142
|
# This repository only
|
|
@@ -290,7 +271,7 @@ lemming hooks list
|
|
|
290
271
|
lemming config list
|
|
291
272
|
|
|
292
273
|
# Persist configuration to tasks.yml
|
|
293
|
-
lemming config set runner
|
|
274
|
+
lemming config set runner opencode
|
|
294
275
|
```
|
|
295
276
|
|
|
296
277
|
### Evaluating Prompt Changes
|
|
@@ -395,8 +376,9 @@ These come before the subcommand and apply to all of them.
|
|
|
395
376
|
per-task `--runner`/`--model` overrides anything passed here.
|
|
396
377
|
- **`exec [<description>]`**: Run one task, or one set of reviews, outside any
|
|
397
378
|
roadmap. Prints the agent's closing message to stdout and everything else to
|
|
398
|
-
stderr
|
|
399
|
-
|
|
379
|
+
stderr, including its tasks file and task ID at startup. Use `lemming -v
|
|
380
|
+
exec ...` to stream runner activity. Exits non-zero if the task did not
|
|
381
|
+
complete. See [One-off tasks](#one-off-tasks-without-a-roadmap).
|
|
400
382
|
- `-f/--file`: Read the description from a file, or `-` for stdin. Unlike
|
|
401
383
|
`add`, there is no length cap.
|
|
402
384
|
- `--review <names>`: Reviews to run after the task, comma-separated or
|
|
@@ -471,7 +453,10 @@ Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
|
|
|
471
453
|
|
|
472
454
|
- **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
|
|
473
455
|
the project workspace with `--add-dir`
|
|
474
|
-
- **
|
|
456
|
+
- **OpenCode**: Runs non-interactively via `opencode run`, adds `--format json`,
|
|
457
|
+
and, in YOLO mode, adds `--auto`. For its Google provider, Lemming makes an
|
|
458
|
+
existing `GOOGLE_API_KEY` or `GEMINI_API_KEY` available under OpenCode's
|
|
459
|
+
native `GOOGLE_GENERATIVE_AI_API_KEY` name.
|
|
475
460
|
- **Claude**: Adds `--dangerously-skip-permissions`
|
|
476
461
|
- **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
|
|
477
462
|
YOLO mode, adds `--dangerously-bypass-approvals-and-sandbox`
|
|
@@ -533,10 +518,16 @@ pushing a `v*` tag triggers the `publish.yml` GitHub Actions workflow, which
|
|
|
533
518
|
builds the package with `uv build` and uploads it.
|
|
534
519
|
|
|
535
520
|
```bash
|
|
536
|
-
# 1. Bump
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
git
|
|
521
|
+
# 1. Bump pyproject.toml and uv.lock together, commit, and push
|
|
522
|
+
uv version --bump patch --no-sync
|
|
523
|
+
version="$(uv version --short)"
|
|
524
|
+
git add pyproject.toml uv.lock
|
|
525
|
+
git commit -m "Bump version to $version"
|
|
526
|
+
git push origin main
|
|
527
|
+
|
|
528
|
+
# 2. After CI passes, tag the release and push the tag
|
|
529
|
+
git tag "v$version"
|
|
530
|
+
git push origin "v$version"
|
|
540
531
|
```
|
|
541
532
|
|
|
542
533
|
---
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lemming
|
|
3
|
-
description: One interface over the AI coding agent CLIs (agy, claude, codex). Delegate a single task to another agent to spare a quota or play to its strengths; run a readability, testing, or UX review over changed files, a branch, or a path; or drive a multi-task project through a roadmap that survives context limits. Use whenever work should run under a different agent than the current one, when reviewing a diff before opening a pull request, or when a job is too large for one agent run.
|
|
3
|
+
description: One interface over the AI coding agent CLIs (agy, claude, codex, opencode). Delegate a single task to another agent to spare a quota or play to its strengths; run a readability, testing, or UX review over changed files, a branch, or a path; or drive a multi-task project through a roadmap that survives context limits. Use whenever work should run under a different agent than the current one, when reviewing a diff before opening a pull request, or when a job is too large for one agent run.
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# lemming
|
|
8
8
|
|
|
9
9
|
One interface over several agent CLIs. You do not need to know how `agy`,
|
|
10
|
-
`claude`, or `
|
|
10
|
+
`claude`, `codex`, or `opencode` spell their flags — only `lemming`.
|
|
11
11
|
|
|
12
12
|
Use `exec` for one unit of work, and a roadmap for anything larger than a
|
|
13
13
|
single agent run can hold.
|
|
@@ -15,16 +15,32 @@ single agent run can hold.
|
|
|
15
15
|
## Delegate one task
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
lemming exec "Fix the flaky heartbeat test in src/runner_test.py" --runner codex
|
|
18
|
+
lemming -v exec "Fix the flaky heartbeat test in src/runner_test.py" --runner codex
|
|
19
19
|
|
|
20
20
|
# Pipe a longer handoff rather than fighting shell quoting; no length limit
|
|
21
|
-
cat handoff.md | lemming exec -f - --runner agy
|
|
21
|
+
cat handoff.md | lemming -v exec -f - --runner agy
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
The agent's closing message comes back on **stdout**, everything else on
|
|
25
25
|
stderr, and the exit code is 0 only if the task completed. Read that message
|
|
26
26
|
instead of hunting through logs.
|
|
27
27
|
|
|
28
|
+
When you launch `exec` as a subprocess, use the global `-v` flag (before
|
|
29
|
+
`exec`) and continue monitoring its output until it exits. Verbose mode streams
|
|
30
|
+
the runner's activity on stderr. At startup, stderr also reports the exact
|
|
31
|
+
`Tasks file` and `Task ID`; use them from another process to inspect the
|
|
32
|
+
agent's durable findings or full log without guessing which `exec-*` directory
|
|
33
|
+
belongs to the run:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
lemming --tasks-file <reported-path> status <reported-task-id>
|
|
37
|
+
lemming --tasks-file <reported-path> logs <reported-task-id>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The task runner is required to record its approach first and concise findings
|
|
41
|
+
as it works. These entries are checkpoints rather than a continuous trace, so
|
|
42
|
+
use verbose output or the log to tell whether it is actively making progress.
|
|
43
|
+
|
|
28
44
|
The task starts with an empty context and sees nothing of your conversation,
|
|
29
45
|
so give it everything it needs.
|
|
30
46
|
|
|
@@ -94,9 +110,10 @@ turns one off for the project.
|
|
|
94
110
|
atomicity to rely on.
|
|
95
111
|
- **Concurrent runs in one checkout interleave their edits.** Give each
|
|
96
112
|
writing run its own worktree and address it with `-C`.
|
|
97
|
-
- **
|
|
98
|
-
|
|
99
|
-
|
|
113
|
+
- **Every run reports its state.** The tasks file and task ID are printed on
|
|
114
|
+
stderr at startup. Successful state is removed on exit unless `--keep` is
|
|
115
|
+
used; failures keep their directory and print it again for recovery. Kept
|
|
116
|
+
failure state is retired automatically after a week.
|
|
100
117
|
- **`--tasks-file` and `-C` are group options.** They go before the
|
|
101
118
|
subcommand — `lemming -C ../other exec ...`, never `exec -C ../other`.
|
|
102
119
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lemming-cli"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.3"
|
|
8
8
|
description = "An autonomous, iterative task runner for AI coding agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
]
|
|
13
13
|
license = { text = "MIT" }
|
|
14
14
|
requires-python = ">=3.12"
|
|
15
|
-
keywords = ["ai", "agents", "automation", "antigravity", "claude", "
|
|
15
|
+
keywords = ["ai", "agents", "automation", "antigravity", "claude", "opencode"]
|
|
16
16
|
dependencies = [
|
|
17
17
|
"beautifulsoup4>=4.13.3",
|
|
18
18
|
"click>=8.0.0",
|
|
@@ -20,7 +20,7 @@ def test_run_loop(client, test_tasks):
|
|
|
20
20
|
def test_get_runners(client):
|
|
21
21
|
response = client.get("/api/runners")
|
|
22
22
|
assert response.status_code == 200
|
|
23
|
-
assert response.json() == ["agy", "
|
|
23
|
+
assert response.json() == ["agy", "opencode", "claude", "codex"]
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def test_project_goal_isolation(client, test_tasks):
|
|
@@ -50,7 +50,7 @@ def config_set(ctx: click.Context, key: str, value: str):
|
|
|
50
50
|
discard the pinned model.
|
|
51
51
|
|
|
52
52
|
Examples:
|
|
53
|
-
lemming config set runner
|
|
53
|
+
lemming config set runner opencode
|
|
54
54
|
lemming config set model gemini-3.6-flash-high
|
|
55
55
|
lemming config set model default
|
|
56
56
|
lemming config set retries 5
|
|
@@ -324,6 +324,12 @@ def exec_command(
|
|
|
324
324
|
else _create_review_task(exec_dir, reviews, config)
|
|
325
325
|
)
|
|
326
326
|
|
|
327
|
+
# A supervising process needs an exact, race-free handle to the ephemeral
|
|
328
|
+
# roadmap while the run is active. Keep it on stderr so stdout remains the
|
|
329
|
+
# agent's closing-message interface.
|
|
330
|
+
click.echo(f"Tasks file: {tasks_file}", err=True)
|
|
331
|
+
click.echo(f"Task ID: {task_id}", err=True)
|
|
332
|
+
|
|
327
333
|
shutdown.clear_drain()
|
|
328
334
|
shutdown.install_handlers()
|
|
329
335
|
|
|
@@ -80,6 +80,24 @@ def test_exec_keeps_progress_chatter_off_stdout(workspace):
|
|
|
80
80
|
assert "Attempt" in result.stderr
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def test_exec_announces_monitoring_coordinates_on_stderr(workspace):
|
|
84
|
+
"""A supervisor can inspect an active exec without guessing its state."""
|
|
85
|
+
seen = {}
|
|
86
|
+
|
|
87
|
+
def finish(cmd, tasks_file, task_id, *args, **kwargs):
|
|
88
|
+
seen["tasks_file"] = tasks_file
|
|
89
|
+
seen["task_id"] = task_id
|
|
90
|
+
return _finish("Done.")(cmd, tasks_file, task_id, *args, **kwargs)
|
|
91
|
+
|
|
92
|
+
with mock.patch("lemming.runner.run_with_heartbeat", finish):
|
|
93
|
+
result = CliRunner().invoke(cli, ["exec", "Fix the flaky test"])
|
|
94
|
+
|
|
95
|
+
assert result.exit_code == 0, result.stderr
|
|
96
|
+
assert result.stdout.strip() == "Done."
|
|
97
|
+
assert f"Tasks file: {seen['tasks_file']}" in result.stderr
|
|
98
|
+
assert f"Task ID: {seen['task_id']}" in result.stderr
|
|
99
|
+
|
|
100
|
+
|
|
83
101
|
def test_exec_removes_its_state_directory_on_success(workspace):
|
|
84
102
|
"""A one-shot leaves nothing behind when it worked."""
|
|
85
103
|
with mock.patch("lemming.runner.run_with_heartbeat", _finish("Done.")):
|
|
@@ -178,6 +178,10 @@ def run(
|
|
|
178
178
|
)
|
|
179
179
|
click.echo(f"Building eval image '{image}'...")
|
|
180
180
|
container.build_image(context, image=image, docker=docker)
|
|
181
|
+
# The image is what the trials need; the cache that produced it is
|
|
182
|
+
# dead weight, and it outgrows the image several times over across
|
|
183
|
+
# repeated runs.
|
|
184
|
+
container.prune_build_cache(docker=docker)
|
|
181
185
|
|
|
182
186
|
if run_dir is None:
|
|
183
187
|
run_dir = pathlib.Path(tempfile.mkdtemp(prefix="lemming-evals-"))
|
|
@@ -19,6 +19,7 @@ DEFAULT_FORWARD_ENV = (
|
|
|
19
19
|
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
20
20
|
"GEMINI_API_KEY",
|
|
21
21
|
"GOOGLE_API_KEY",
|
|
22
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
# Paths inside the container; the harness mounts host dirs onto these.
|
|
@@ -41,7 +42,41 @@ def build_image(
|
|
|
41
42
|
image: Tag for the built image.
|
|
42
43
|
docker: Docker-compatible CLI binary to invoke.
|
|
43
44
|
"""
|
|
44
|
-
|
|
45
|
+
# No scenario drives a browser, and they are more than half the image.
|
|
46
|
+
subprocess.run(
|
|
47
|
+
[
|
|
48
|
+
docker,
|
|
49
|
+
"build",
|
|
50
|
+
"--build-arg",
|
|
51
|
+
"INSTALL_BROWSERS=false",
|
|
52
|
+
"--tag",
|
|
53
|
+
image,
|
|
54
|
+
str(context),
|
|
55
|
+
],
|
|
56
|
+
check=True,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def prune_build_cache(docker: str = "docker") -> None:
|
|
61
|
+
"""Discards the build cache left behind by an eval image build.
|
|
62
|
+
|
|
63
|
+
Cache accumulates faster than images do — a run rebuilding this image a
|
|
64
|
+
few times leaves tens of gigabytes behind — and nothing reuses it once
|
|
65
|
+
the trials have finished.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
docker: Docker-compatible CLI binary to invoke.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
subprocess.run(
|
|
72
|
+
[docker, "builder", "prune", "--force", "--all"],
|
|
73
|
+
check=False,
|
|
74
|
+
capture_output=True,
|
|
75
|
+
)
|
|
76
|
+
except OSError:
|
|
77
|
+
# This runs after the results are in; failing to tidy up must never
|
|
78
|
+
# be the reason a completed eval run reports an error.
|
|
79
|
+
pass
|
|
45
80
|
|
|
46
81
|
|
|
47
82
|
def trial_command(
|
|
@@ -84,3 +84,34 @@ class TestRunTrial(unittest.TestCase):
|
|
|
84
84
|
|
|
85
85
|
if __name__ == "__main__":
|
|
86
86
|
unittest.main()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BuildImageTest(unittest.TestCase):
|
|
90
|
+
"""The build must not carry what the eval trials never use."""
|
|
91
|
+
|
|
92
|
+
@unittest.mock.patch("subprocess.run")
|
|
93
|
+
def test_builds_without_browsers(
|
|
94
|
+
self, run: unittest.mock.MagicMock
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Browsers are over half the image and no trial drives one."""
|
|
97
|
+
container.build_image(pathlib.Path("/repo"))
|
|
98
|
+
|
|
99
|
+
argv = run.call_args[0][0]
|
|
100
|
+
self.assertIn("--build-arg", argv)
|
|
101
|
+
self.assertIn("INSTALL_BROWSERS=false", argv)
|
|
102
|
+
|
|
103
|
+
@unittest.mock.patch("subprocess.run")
|
|
104
|
+
def test_prunes_the_build_cache(self, run: unittest.mock.MagicMock) -> None:
|
|
105
|
+
"""Repeated eval runs accumulate cache faster than images."""
|
|
106
|
+
container.prune_build_cache()
|
|
107
|
+
|
|
108
|
+
argv = run.call_args[0][0]
|
|
109
|
+
self.assertEqual(argv[:3], ["docker", "builder", "prune"])
|
|
110
|
+
self.assertIn("--force", argv)
|
|
111
|
+
|
|
112
|
+
@unittest.mock.patch("subprocess.run", side_effect=OSError("no docker"))
|
|
113
|
+
def test_pruning_never_fails_a_run(
|
|
114
|
+
self, run: unittest.mock.MagicMock
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Cleanup runs after the results are in; it must not lose them."""
|
|
117
|
+
container.prune_build_cache()
|