lemming-cli 0.8.0__tar.gz → 0.8.1__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.8.0 → lemming_cli-0.8.1}/PKG-INFO +5 -4
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/README.md +4 -3
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/docs/HOOKS.md +6 -2
- lemming_cli-0.8.1/docs/screenshots/dashboard-desktop.png +0 -0
- lemming_cli-0.8.1/docs/screenshots/dashboard-mobile.png +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/pyproject.toml +1 -1
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/tasks.py +18 -2
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/tasks_test.py +47 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/tasks.py +14 -2
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/tasks_test.py +24 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/container.py +45 -22
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/harness.py +6 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/harness_test.py +21 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/readability.py +16 -20
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/readability_test.py +16 -13
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/operations.py +5 -1
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/operations_test.py +3 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/dashboard.spec.js +103 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/dashboard.test.js +3 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/index.html +76 -1
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/index.js +41 -1
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/uv.lock +1 -1
- lemming_cli-0.8.0/docs/screenshots/dashboard-desktop.png +0 -0
- lemming_cli-0.8.0/docs/screenshots/dashboard-mobile.png +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/.dockerignore +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/.github/workflows/ci.yml +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/.github/workflows/publish.yml +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/.github/workflows/screenshots.yml +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/.gitignore +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/.prettierignore +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/.prettierrc +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/Dockerfile +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/LICENSE +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/SKILL.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/biome.json +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/docker-compose.yml +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/docs/EVALS.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/docs/WORKSPACE.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/docs/screenshots/task-log-desktop.png +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/docs/screenshots/task-log-mobile.png +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/package-lock.json +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/package.json +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/playwright.config.js +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/auth.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/auth_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/config.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/config_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/conftest.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/context.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/context_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/directories.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/directories_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/files.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/files_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/hooks.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/hooks_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/logging.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/logging_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/loop.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/loop_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/main.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/api/main_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/artifact_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/brief_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/artifact.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/brief.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/config.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/config_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/exec_cli.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/exec_cli_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/goal.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/goal_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/hooks.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/hooks_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/machine_output_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/main.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/main_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/operations.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/operations_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/progress.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/progress_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/readability_cli.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/readability_cli_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/skill.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/skill_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/cli/stop_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/conftest.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/__main__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/cli.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/cli_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/container_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/fixtures.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/fixtures_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/metrics.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/metrics_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/README.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/legacy.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/clean/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/clean/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/dead-code/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/dead-code/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/false-reuse/calc/limits.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/false-reuse/calc/limits_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/lint-debt/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/lint-debt/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/README.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/calc/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/README.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/README.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/calc/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/README.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/hidden/stats/summary_hidden_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/README.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/stats/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/stats/summary.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/stats/summary_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/report.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/report_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/roadmap.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/roadmap_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/scenarios.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/scenarios_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/suites.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/task.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/task_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/trial.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/evals/trial_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/hooks.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/hooks_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/integration_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/main.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/main_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/models.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/models_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/orchestrator.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/orchestrator_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/paths.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/paths_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/persistence.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/persistence_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/50-readability.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/60-testing.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/70-ux.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/90-roadmap.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/prompts/taskrunner.md +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/prompts.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/prompts_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/providers.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/providers_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/runner.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/runner_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/scope.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/scope_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/shutdown.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/shutdown_integration_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/shutdown_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/__init__.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/lifecycle.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/lifecycle_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/limits.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/progress.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/progress_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/queries.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/tasks/queries_test.py +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/favicon.js +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/favicon.spec.js +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/files.html +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/files.spec.js +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/logs.html +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/logs.test.js +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/mancha.js +0 -0
- {lemming_cli-0.8.0 → lemming_cli-0.8.1}/src/lemming/web/screenshots.spec.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lemming-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: An autonomous, iterative task runner for AI coding agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/owahltinez/lemming
|
|
6
6
|
Project-URL: Repository, https://github.com/owahltinez/lemming
|
|
@@ -226,8 +226,8 @@ lemming serve --tunnel cloudflare
|
|
|
226
226
|
folders directly from the UI.
|
|
227
227
|
- **Browse Files**: Quickly open your workspace in a separate window to inspect
|
|
228
228
|
files and directory structure alongside the roadmap.
|
|
229
|
-
- **Interactive Controls**: Add tasks, edit the goal, and
|
|
230
|
-
loop from your browser.
|
|
229
|
+
- **Interactive Controls**: Add tasks and pick their reviews, edit the goal, and
|
|
230
|
+
manage the execution loop from your browser.
|
|
231
231
|
|
|
232
232
|
---
|
|
233
233
|
|
|
@@ -387,7 +387,8 @@ These come before the subcommand and apply to all of them.
|
|
|
387
387
|
`--model`, `--oneshot`, and `--hooks` (the task's reviews, comma-separated;
|
|
388
388
|
inherited from `--parent` when omitted).
|
|
389
389
|
- **`edit <id>`**: Modify a task's description, runner, model, position,
|
|
390
|
-
oneshot flag (`--oneshot/--no-oneshot`), or reviews (`--hooks
|
|
390
|
+
oneshot flag (`--oneshot/--no-oneshot`), or reviews (`--hooks`;
|
|
391
|
+
`--project-hooks` restores the project's).
|
|
391
392
|
- **`brief <id> [text]`**: View or set a task's long-form brief. Unlike the
|
|
392
393
|
description it has no length cap, and it is appended to the runner prompt
|
|
393
394
|
automatically — the right home for measured timings, exact failing selectors,
|
|
@@ -206,8 +206,8 @@ lemming serve --tunnel cloudflare
|
|
|
206
206
|
folders directly from the UI.
|
|
207
207
|
- **Browse Files**: Quickly open your workspace in a separate window to inspect
|
|
208
208
|
files and directory structure alongside the roadmap.
|
|
209
|
-
- **Interactive Controls**: Add tasks, edit the goal, and
|
|
210
|
-
loop from your browser.
|
|
209
|
+
- **Interactive Controls**: Add tasks and pick their reviews, edit the goal, and
|
|
210
|
+
manage the execution loop from your browser.
|
|
211
211
|
|
|
212
212
|
---
|
|
213
213
|
|
|
@@ -367,7 +367,8 @@ These come before the subcommand and apply to all of them.
|
|
|
367
367
|
`--model`, `--oneshot`, and `--hooks` (the task's reviews, comma-separated;
|
|
368
368
|
inherited from `--parent` when omitted).
|
|
369
369
|
- **`edit <id>`**: Modify a task's description, runner, model, position,
|
|
370
|
-
oneshot flag (`--oneshot/--no-oneshot`), or reviews (`--hooks
|
|
370
|
+
oneshot flag (`--oneshot/--no-oneshot`), or reviews (`--hooks`;
|
|
371
|
+
`--project-hooks` restores the project's).
|
|
371
372
|
- **`brief <id> [text]`**: View or set a task's long-form brief. Unlike the
|
|
372
373
|
description it has no length cap, and it is appended to the runner prompt
|
|
373
374
|
automatically — the right home for measured timings, exact failing selectors,
|
|
@@ -193,10 +193,14 @@ A task can pick its own reviews, which replace the project's for that task:
|
|
|
193
193
|
|
|
194
194
|
```bash
|
|
195
195
|
lemming add "Redesign the settings page" --hooks ux,testing
|
|
196
|
-
lemming edit <id> --hooks ''
|
|
197
|
-
lemming edit <id> --hooks '*'
|
|
196
|
+
lemming edit <id> --hooks '' # no reviews for this task
|
|
197
|
+
lemming edit <id> --hooks '*' # every review available now
|
|
198
|
+
lemming edit <id> --project-hooks # back to the project's reviews
|
|
198
199
|
```
|
|
199
200
|
|
|
201
|
+
In the dashboard, tick **Custom reviews** when adding or editing a task to pick
|
|
202
|
+
its reviews; leave it unticked to use the project's.
|
|
203
|
+
|
|
200
204
|
`--hooks` accepts only reviews (priority below 90). Hooks at 90 and above, such
|
|
201
205
|
as `roadmap`, orchestrate the queue and always follow the project's list. A task
|
|
202
206
|
added with `--parent` and no `--hooks` inherits its parent's choice, so a split
|
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import fastapi
|
|
4
4
|
import pydantic
|
|
5
5
|
|
|
6
|
-
from .. import models, paths, persistence
|
|
6
|
+
from .. import hooks, models, paths, persistence
|
|
7
7
|
from ..tasks import lifecycle, operations, queries
|
|
8
8
|
from . import context, loop
|
|
9
9
|
|
|
@@ -27,6 +27,11 @@ class AddTaskRequest(pydantic.BaseModel):
|
|
|
27
27
|
index: int = -1
|
|
28
28
|
parent: str | None = None
|
|
29
29
|
parent_tasks_file: str | None = None
|
|
30
|
+
hooks: list[str] | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Validates an update's hooks against the field they are stored in.
|
|
34
|
+
_TASK_HOOKS = pydantic.TypeAdapter(models.Task.model_fields["hooks"].annotation)
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
@router.post("/api/tasks")
|
|
@@ -54,6 +59,9 @@ def add_task(
|
|
|
54
59
|
index=task.index,
|
|
55
60
|
parent=task.parent,
|
|
56
61
|
parent_tasks_file=task.parent_tasks_file,
|
|
62
|
+
hooks=None
|
|
63
|
+
if task.hooks is None
|
|
64
|
+
else hooks.parse_reviews(task.hooks, tasks_file),
|
|
57
65
|
)
|
|
58
66
|
except ValueError as e:
|
|
59
67
|
raise fastapi.HTTPException(400, str(e))
|
|
@@ -85,7 +93,10 @@ def update_task(
|
|
|
85
93
|
update: dict,
|
|
86
94
|
project: str | None = None,
|
|
87
95
|
):
|
|
88
|
-
"""Update a task's description, runner, position, status, or
|
|
96
|
+
"""Update a task's description, runner, position, status, parent or hooks.
|
|
97
|
+
|
|
98
|
+
An explicit null for hooks restores the project's reviews.
|
|
99
|
+
"""
|
|
89
100
|
tasks_file = context.resolve_tasks_file(request.app.state, project)
|
|
90
101
|
status = update.get("status")
|
|
91
102
|
if status == models.TaskStatus.SUPERSEDED:
|
|
@@ -113,6 +124,9 @@ def update_task(
|
|
|
113
124
|
require_progress = True
|
|
114
125
|
|
|
115
126
|
try:
|
|
127
|
+
task_hooks = _TASK_HOOKS.validate_python(update.get("hooks"))
|
|
128
|
+
if task_hooks is not None:
|
|
129
|
+
task_hooks = hooks.parse_reviews(task_hooks, tasks_file)
|
|
116
130
|
updated_task = operations.update_task(
|
|
117
131
|
tasks_file,
|
|
118
132
|
task_id,
|
|
@@ -122,6 +136,8 @@ def update_task(
|
|
|
122
136
|
status=status,
|
|
123
137
|
require_progress=require_progress,
|
|
124
138
|
parent=update.get("parent"),
|
|
139
|
+
hooks=task_hooks,
|
|
140
|
+
project_hooks="hooks" in update and task_hooks is None,
|
|
125
141
|
)
|
|
126
142
|
return updated_task
|
|
127
143
|
except ValueError as e:
|
|
@@ -53,6 +53,53 @@ def test_add_task_with_runner(client, test_tasks):
|
|
|
53
53
|
assert response.json()["runner"] == "claude"
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def test_add_task_with_hooks(client, test_tasks):
|
|
57
|
+
response = client.post(
|
|
58
|
+
"/api/tasks",
|
|
59
|
+
json={"description": "Reviewed", "hooks": ["ux", "readability"]},
|
|
60
|
+
)
|
|
61
|
+
assert response.status_code == 200
|
|
62
|
+
assert response.json()["hooks"] == ["readability", "ux"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_add_task_rejects_invalid_hooks(client, test_tasks):
|
|
66
|
+
for hooks, message in (
|
|
67
|
+
(["nope"], "Unknown review 'nope'"),
|
|
68
|
+
(["roadmap"], "orchestrates the roadmap"),
|
|
69
|
+
):
|
|
70
|
+
response = client.post(
|
|
71
|
+
"/api/tasks", json={"description": "Bad", "hooks": hooks}
|
|
72
|
+
)
|
|
73
|
+
assert response.status_code == 400
|
|
74
|
+
assert message in response.json()["detail"]
|
|
75
|
+
assert len(persistence.load_tasks(test_tasks).tasks) == 3
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_update_task_hooks_and_reset(client, test_tasks):
|
|
79
|
+
url = "/api/tasks/task2/update"
|
|
80
|
+
|
|
81
|
+
response = client.post(url, json={"hooks": []})
|
|
82
|
+
assert response.status_code == 200
|
|
83
|
+
assert response.json()["hooks"] == []
|
|
84
|
+
|
|
85
|
+
response = client.post(url, json={"description": "Still custom"})
|
|
86
|
+
assert response.json()["hooks"] == []
|
|
87
|
+
|
|
88
|
+
response = client.post(url, json={"hooks": None})
|
|
89
|
+
assert response.status_code == 200
|
|
90
|
+
assert response.json()["hooks"] is None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_update_task_rejects_invalid_hooks(client, test_tasks):
|
|
94
|
+
for hooks in (["nope"], ["roadmap"], "ux", [1]):
|
|
95
|
+
response = client.post("/api/tasks/task2/update", json={"hooks": hooks})
|
|
96
|
+
assert response.status_code == 400, hooks
|
|
97
|
+
task2 = next(
|
|
98
|
+
t for t in persistence.load_tasks(test_tasks).tasks if t.id == "task2"
|
|
99
|
+
)
|
|
100
|
+
assert task2.hooks is None
|
|
101
|
+
|
|
102
|
+
|
|
56
103
|
def test_delete_completed_tasks(client, test_tasks):
|
|
57
104
|
response = client.post("/api/tasks/delete-completed")
|
|
58
105
|
assert response.status_code == 200
|
|
@@ -222,6 +222,11 @@ def add(
|
|
|
222
222
|
"every review, '' for none)."
|
|
223
223
|
),
|
|
224
224
|
)
|
|
225
|
+
@click.option(
|
|
226
|
+
"--project-hooks",
|
|
227
|
+
is_flag=True,
|
|
228
|
+
help="Restore the project's reviews for this task.",
|
|
229
|
+
)
|
|
225
230
|
@click.pass_context
|
|
226
231
|
def edit(
|
|
227
232
|
ctx: click.Context,
|
|
@@ -235,8 +240,13 @@ def edit(
|
|
|
235
240
|
parent_tasks_file: str | None,
|
|
236
241
|
oneshot: bool | None,
|
|
237
242
|
hooks_value: str | None,
|
|
243
|
+
project_hooks: bool,
|
|
238
244
|
):
|
|
239
245
|
"""Edits a task's description, runner, model, position, parent or flags."""
|
|
246
|
+
if project_hooks and hooks_value is not None:
|
|
247
|
+
click.echo("Error: Cannot provide both --hooks and --project-hooks.")
|
|
248
|
+
ctx.exit(1)
|
|
249
|
+
|
|
240
250
|
if file:
|
|
241
251
|
if description:
|
|
242
252
|
click.echo("Error: Cannot provide both description and --file.")
|
|
@@ -254,11 +264,12 @@ def edit(
|
|
|
254
264
|
and parent_tasks_file is None
|
|
255
265
|
and oneshot is None
|
|
256
266
|
and hooks_value is None
|
|
267
|
+
and not project_hooks
|
|
257
268
|
):
|
|
258
269
|
click.echo(
|
|
259
270
|
"Error: At least one of --description, --runner, --model,"
|
|
260
|
-
" --index, --parent, --parent-tasks-file, --oneshot,
|
|
261
|
-
" must be provided."
|
|
271
|
+
" --index, --parent, --parent-tasks-file, --oneshot, --hooks,"
|
|
272
|
+
" or --project-hooks must be provided."
|
|
262
273
|
)
|
|
263
274
|
ctx.exit(1)
|
|
264
275
|
|
|
@@ -276,6 +287,7 @@ def edit(
|
|
|
276
287
|
parent_tasks_file=parent_tasks_file,
|
|
277
288
|
oneshot=oneshot,
|
|
278
289
|
hooks=_parse_task_hooks(hooks_value, tasks_file),
|
|
290
|
+
project_hooks=project_hooks,
|
|
279
291
|
)
|
|
280
292
|
click.echo(f"Task {target_task.id} updated.")
|
|
281
293
|
except ValueError as e:
|
|
@@ -454,6 +454,30 @@ class TestCLITaskModel(unittest.TestCase):
|
|
|
454
454
|
updated = persistence.load_tasks(self.test_tasks_file).tasks[0]
|
|
455
455
|
self.assertEqual(updated.hooks, [])
|
|
456
456
|
|
|
457
|
+
def test_edit_project_hooks_restores_project_default(self):
|
|
458
|
+
task = self._add("--hooks", "ux")
|
|
459
|
+
|
|
460
|
+
result = self.cli_runner.invoke(
|
|
461
|
+
cli.cli, self.base_args + ["edit", task.id, "--project-hooks"]
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
self.assertEqual(result.exit_code, 0, result.output)
|
|
465
|
+
updated = persistence.load_tasks(self.test_tasks_file).tasks[0]
|
|
466
|
+
self.assertIsNone(updated.hooks)
|
|
467
|
+
|
|
468
|
+
def test_edit_rejects_hooks_with_project_hooks(self):
|
|
469
|
+
task = self._add("--hooks", "ux")
|
|
470
|
+
|
|
471
|
+
result = self.cli_runner.invoke(
|
|
472
|
+
cli.cli,
|
|
473
|
+
self.base_args
|
|
474
|
+
+ ["edit", task.id, "--hooks", "", "--project-hooks"],
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
self.assertEqual(result.exit_code, 1, result.output)
|
|
478
|
+
updated = persistence.load_tasks(self.test_tasks_file).tasks[0]
|
|
479
|
+
self.assertEqual(updated.hooks, ["ux"])
|
|
480
|
+
|
|
457
481
|
def test_add_without_hooks_uses_project_default(self):
|
|
458
482
|
self.assertIsNone(self._add().hooks)
|
|
459
483
|
|
|
@@ -78,6 +78,43 @@ def prune_build_cache(docker: str = "docker") -> None:
|
|
|
78
78
|
pass
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def uv_command(
|
|
82
|
+
uv_args: list[str],
|
|
83
|
+
options: list[str],
|
|
84
|
+
image: str = DEFAULT_IMAGE,
|
|
85
|
+
docker: str = "docker",
|
|
86
|
+
) -> list[str]:
|
|
87
|
+
"""Returns the docker argv that runs uv in the image's lemming project.
|
|
88
|
+
|
|
89
|
+
The image's own entrypoint serves the web UI, so it is replaced with uv
|
|
90
|
+
and the command runs with the lemming install's interpreter.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
uv_args: Arguments following uv run --project /opt/lemming.
|
|
94
|
+
options: Extra docker run options (mounts, env, network).
|
|
95
|
+
image: Image tag to run.
|
|
96
|
+
docker: Docker-compatible CLI binary to invoke.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
The full docker run argv.
|
|
100
|
+
"""
|
|
101
|
+
return [
|
|
102
|
+
docker,
|
|
103
|
+
"run",
|
|
104
|
+
"--rm",
|
|
105
|
+
*options,
|
|
106
|
+
"--workdir",
|
|
107
|
+
WORKSPACE_MOUNT,
|
|
108
|
+
"--entrypoint",
|
|
109
|
+
"uv",
|
|
110
|
+
image,
|
|
111
|
+
"run",
|
|
112
|
+
"--project",
|
|
113
|
+
"/opt/lemming",
|
|
114
|
+
*uv_args,
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
81
118
|
def trial_command(
|
|
82
119
|
workspace: pathlib.Path,
|
|
83
120
|
lemming_home: pathlib.Path,
|
|
@@ -103,42 +140,28 @@ def trial_command(
|
|
|
103
140
|
Returns:
|
|
104
141
|
The full docker run argv.
|
|
105
142
|
"""
|
|
106
|
-
|
|
107
|
-
docker,
|
|
108
|
-
"run",
|
|
109
|
-
"--rm",
|
|
143
|
+
options = [
|
|
110
144
|
"--volume",
|
|
111
145
|
f"{workspace}:{WORKSPACE_MOUNT}",
|
|
112
146
|
"--volume",
|
|
113
147
|
f"{lemming_home}:{HOME_MOUNT}",
|
|
114
148
|
"--env",
|
|
115
149
|
f"LEMMING_HOME={HOME_MOUNT}",
|
|
116
|
-
"--workdir",
|
|
117
|
-
WORKSPACE_MOUNT,
|
|
118
|
-
"--entrypoint",
|
|
119
|
-
"uv",
|
|
120
150
|
]
|
|
121
151
|
|
|
122
152
|
# Bare --env NAME keeps the secret's value out of the argv.
|
|
123
153
|
for name in forward_env:
|
|
124
154
|
if os.environ.get(name):
|
|
125
|
-
|
|
155
|
+
options.extend(["--env", name])
|
|
126
156
|
for spec in volumes:
|
|
127
|
-
|
|
157
|
+
options.extend(["--volume", spec])
|
|
128
158
|
|
|
129
|
-
|
|
130
|
-
[
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"/opt/lemming",
|
|
135
|
-
"python",
|
|
136
|
-
"-m",
|
|
137
|
-
"lemming.evals.trial",
|
|
138
|
-
*trial_args,
|
|
139
|
-
]
|
|
159
|
+
return uv_command(
|
|
160
|
+
["python", "-m", "lemming.evals.trial", *trial_args],
|
|
161
|
+
options,
|
|
162
|
+
image=image,
|
|
163
|
+
docker=docker,
|
|
140
164
|
)
|
|
141
|
-
return command
|
|
142
165
|
|
|
143
166
|
|
|
144
167
|
def run_trial(
|
|
@@ -7,6 +7,7 @@ docker-based runner from the container module, tests use in-process fakes.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import concurrent.futures
|
|
10
|
+
import contextvars
|
|
10
11
|
import dataclasses
|
|
11
12
|
import json
|
|
12
13
|
import os
|
|
@@ -106,6 +107,10 @@ class HarnessConfig:
|
|
|
106
107
|
volumes: tuple[str, ...] = ()
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
# The running trial's config, for graders that execute workspace code.
|
|
111
|
+
TRIAL_CONFIG = contextvars.ContextVar("trial_config", default=HarnessConfig())
|
|
112
|
+
|
|
113
|
+
|
|
109
114
|
@dataclasses.dataclass
|
|
110
115
|
class TrialResult:
|
|
111
116
|
"""Outcome of a single graded trial.
|
|
@@ -356,6 +361,7 @@ def _execute_trial(
|
|
|
356
361
|
lemming_home = trial_dir / "home"
|
|
357
362
|
lemming_home.mkdir(parents=True, exist_ok=True)
|
|
358
363
|
|
|
364
|
+
TRIAL_CONFIG.set(config)
|
|
359
365
|
started = time.monotonic()
|
|
360
366
|
error = ""
|
|
361
367
|
try:
|
|
@@ -71,6 +71,27 @@ class TestRunSuite(HarnessTestCase):
|
|
|
71
71
|
|
|
72
72
|
self.assertEqual([r.passed for r in results], [True, True])
|
|
73
73
|
|
|
74
|
+
def test_graders_see_the_run_config(self):
|
|
75
|
+
# Graders that run workspace code must use the run's own image.
|
|
76
|
+
scenario = scenarios.Scenario(
|
|
77
|
+
name="config-demo",
|
|
78
|
+
summary="Reports the image it is graded with.",
|
|
79
|
+
build=lambda workspace: workspace.mkdir(parents=True),
|
|
80
|
+
grade=lambda workspace: [
|
|
81
|
+
scenarios.Check(
|
|
82
|
+
name=harness.TRIAL_CONFIG.get().image, passed=True
|
|
83
|
+
)
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
config = dataclasses.replace(self.config, image="custom-image")
|
|
87
|
+
|
|
88
|
+
results = harness.run_suite(
|
|
89
|
+
[scenario], self.run_dir, config, lambda *args: None
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
names = {check.name for r in results for check in r.checks}
|
|
93
|
+
self.assertEqual(names, {"custom-image"})
|
|
94
|
+
|
|
74
95
|
def test_infrastructure_errors_are_captured(self):
|
|
75
96
|
def broken_runner(scenario, workspace, lemming_home, config):
|
|
76
97
|
raise RuntimeError("docker daemon unreachable")
|
|
@@ -23,7 +23,7 @@ import subprocess
|
|
|
23
23
|
import sys
|
|
24
24
|
|
|
25
25
|
from .. import models
|
|
26
|
-
from . import container, fixtures, metrics, scenarios
|
|
26
|
+
from . import container, fixtures, harness, metrics, scenarios
|
|
27
27
|
|
|
28
28
|
_GOAL = (
|
|
29
29
|
"Build a small calculator CLI in calc/ with add, subtract, and "
|
|
@@ -95,26 +95,22 @@ def _save_finished_task(workspace: pathlib.Path, progress: list[str]) -> None:
|
|
|
95
95
|
|
|
96
96
|
def _check_tests_pass(workspace: pathlib.Path) -> scenarios.Check:
|
|
97
97
|
"""Checks that the fixture's unit tests still pass after the hook."""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"--
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"discover",
|
|
113
|
-
"-p",
|
|
114
|
-
"*_test.py",
|
|
115
|
-
]
|
|
98
|
+
unittest_args = ["-m", "unittest", "discover", "-p", "*_test.py"]
|
|
99
|
+
config = harness.TRIAL_CONFIG.get()
|
|
100
|
+
if shutil.which(config.docker):
|
|
101
|
+
# Offline, so uv must not sync the dev group the image lacks.
|
|
102
|
+
cmd = container.uv_command(
|
|
103
|
+
["--no-sync", "python", *unittest_args],
|
|
104
|
+
[
|
|
105
|
+
"--network=none",
|
|
106
|
+
"--volume",
|
|
107
|
+
f"{workspace}:{container.WORKSPACE_MOUNT}:ro",
|
|
108
|
+
],
|
|
109
|
+
image=config.image,
|
|
110
|
+
docker=config.docker,
|
|
111
|
+
)
|
|
116
112
|
else:
|
|
117
|
-
cmd = [sys.executable,
|
|
113
|
+
cmd = [sys.executable, *unittest_args]
|
|
118
114
|
result = subprocess.run(
|
|
119
115
|
cmd,
|
|
120
116
|
cwd=workspace,
|
|
@@ -5,7 +5,7 @@ import unittest
|
|
|
5
5
|
import unittest.mock
|
|
6
6
|
|
|
7
7
|
from lemming import models
|
|
8
|
-
from lemming.evals import fixtures, readability, scenarios, suites
|
|
8
|
+
from lemming.evals import fixtures, harness, readability, scenarios, suites
|
|
9
9
|
from lemming.tasks import operations, progress
|
|
10
10
|
|
|
11
11
|
|
|
@@ -380,12 +380,15 @@ class TestFixtureIntegrity(ScenarioTestCase):
|
|
|
380
380
|
check = readability._check_tests_pass(workspace)
|
|
381
381
|
self.assertTrue(check.passed, check.detail)
|
|
382
382
|
|
|
383
|
-
def
|
|
383
|
+
def test_check_tests_pass_runs_offline_in_the_run_image(self):
|
|
384
384
|
completed = unittest.mock.Mock(returncode=0, stderr="")
|
|
385
|
+
config = harness.HarnessConfig(image="custom-image", docker="podman")
|
|
386
|
+
token = harness.TRIAL_CONFIG.set(config)
|
|
387
|
+
self.addCleanup(harness.TRIAL_CONFIG.reset, token)
|
|
385
388
|
with (
|
|
386
389
|
unittest.mock.patch(
|
|
387
390
|
"lemming.evals.readability.shutil.which",
|
|
388
|
-
return_value="/usr/bin/
|
|
391
|
+
return_value="/usr/bin/podman",
|
|
389
392
|
),
|
|
390
393
|
unittest.mock.patch(
|
|
391
394
|
"lemming.evals.readability.subprocess.run",
|
|
@@ -396,19 +399,19 @@ class TestFixtureIntegrity(ScenarioTestCase):
|
|
|
396
399
|
|
|
397
400
|
self.assertTrue(check.passed)
|
|
398
401
|
cmd = mock_run.call_args[0][0]
|
|
402
|
+
self.assertEqual(cmd[0], "podman")
|
|
403
|
+
self.assertIn("--network=none", cmd)
|
|
404
|
+
self.assertIn(f"{self.workspace}:/workspace:ro", cmd)
|
|
405
|
+
self.assertEqual(cmd[cmd.index("--entrypoint") + 1], "uv")
|
|
406
|
+
# The image ships no dev dependencies, and offline uv cannot sync.
|
|
399
407
|
self.assertEqual(
|
|
400
|
-
cmd,
|
|
408
|
+
cmd[cmd.index("custom-image") + 1 :],
|
|
401
409
|
[
|
|
402
|
-
"docker",
|
|
403
410
|
"run",
|
|
404
|
-
"--
|
|
405
|
-
"
|
|
406
|
-
"-
|
|
407
|
-
|
|
408
|
-
"-w",
|
|
409
|
-
"/workspace",
|
|
410
|
-
"lemming-evals",
|
|
411
|
-
"python3",
|
|
411
|
+
"--project",
|
|
412
|
+
"/opt/lemming",
|
|
413
|
+
"--no-sync",
|
|
414
|
+
"python",
|
|
412
415
|
"-m",
|
|
413
416
|
"unittest",
|
|
414
417
|
"discover",
|
|
@@ -273,6 +273,7 @@ def update_task(
|
|
|
273
273
|
parent_tasks_file: str | None = None,
|
|
274
274
|
oneshot: bool | None = None,
|
|
275
275
|
hooks: list[str] | None = None,
|
|
276
|
+
project_hooks: bool = False,
|
|
276
277
|
force: bool = False,
|
|
277
278
|
) -> models.Task:
|
|
278
279
|
"""Updates an existing task.
|
|
@@ -291,6 +292,7 @@ def update_task(
|
|
|
291
292
|
parent_tasks_file: New parent tasks file path.
|
|
292
293
|
oneshot: New oneshot flag; None leaves it unchanged.
|
|
293
294
|
hooks: New reviews to run after the task; None leaves them unchanged.
|
|
295
|
+
project_hooks: If True, the task goes back to the project's reviews.
|
|
294
296
|
force: If True, force status transition even if task is in progress.
|
|
295
297
|
|
|
296
298
|
Returns:
|
|
@@ -326,7 +328,9 @@ def update_task(
|
|
|
326
328
|
target.model = model or None
|
|
327
329
|
if oneshot is not None:
|
|
328
330
|
target.oneshot = oneshot
|
|
329
|
-
if
|
|
331
|
+
if project_hooks:
|
|
332
|
+
target.hooks = None
|
|
333
|
+
elif hooks is not None:
|
|
330
334
|
target.hooks = hooks
|
|
331
335
|
if parent is not None:
|
|
332
336
|
if parent == "":
|
|
@@ -55,6 +55,9 @@ def test_update_task_hooks(tmp_path):
|
|
|
55
55
|
cleared = operations.update_task(tasks_file, task.id, hooks=[])
|
|
56
56
|
assert cleared.hooks == []
|
|
57
57
|
|
|
58
|
+
reset = operations.update_task(tasks_file, task.id, project_hooks=True)
|
|
59
|
+
assert reset.hooks is None
|
|
60
|
+
|
|
58
61
|
|
|
59
62
|
def test_add_task(tmp_path):
|
|
60
63
|
tasks_file = tmp_path / "tasks.yml"
|