lemming-cli 0.7.2__tar.gz → 0.8.0__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.7.2 → lemming_cli-0.8.0}/.github/workflows/ci.yml +1 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/PKG-INFO +24 -22
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/README.md +23 -21
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/SKILL.md +8 -8
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/docs/EVALS.md +3 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/docs/HOOKS.md +27 -17
- 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.7.2 → lemming_cli-0.8.0}/pyproject.toml +1 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/config.py +5 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/config_test.py +20 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/hooks.py +4 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/hooks_test.py +13 -33
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/config.py +2 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/config_test.py +2 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/exec_cli.py +8 -37
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/exec_cli_test.py +2 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/hooks.py +20 -19
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/hooks_test.py +26 -48
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/tasks.py +34 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/tasks_test.py +29 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/roadmap.py +96 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/roadmap_test.py +35 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/hooks.py +110 -95
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/hooks_test.py +129 -76
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/models.py +4 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/orchestrator.py +21 -17
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/orchestrator_test.py +41 -4
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/50-readability.md +21 -13
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/70-ux.md +1 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/90-roadmap.md +12 -5
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/prompts/taskrunner.md +17 -6
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/runner.py +25 -9
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/runner_test.py +46 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/operations.py +13 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/operations_test.py +27 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/dashboard.spec.js +1 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/favicon.spec.js +1 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/index.html +1 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/index.js +2 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/screenshots.spec.js +3 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/uv.lock +1 -1
- lemming_cli-0.7.2/docs/screenshots/dashboard-desktop.png +0 -0
- lemming_cli-0.7.2/docs/screenshots/dashboard-mobile.png +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/.dockerignore +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/.github/workflows/publish.yml +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/.github/workflows/screenshots.yml +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/.gitignore +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/.prettierignore +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/.prettierrc +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/Dockerfile +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/LICENSE +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/biome.json +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/docker-compose.yml +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/docs/WORKSPACE.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/docs/screenshots/task-log-desktop.png +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/docs/screenshots/task-log-mobile.png +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/package-lock.json +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/package.json +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/playwright.config.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/auth.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/auth_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/conftest.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/context.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/context_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/directories.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/directories_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/files.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/files_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/logging.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/logging_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/loop.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/loop_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/main.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/main_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/tasks.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/api/tasks_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/artifact_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/brief_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/artifact.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/brief.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/goal.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/goal_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/machine_output_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/main.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/main_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/operations.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/operations_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/progress.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/progress_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/readability_cli.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/readability_cli_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/skill.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/skill_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/cli/stop_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/conftest.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/__main__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/cli.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/cli_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/container.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/container_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/fixtures.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/fixtures_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/harness.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/harness_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/metrics.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/metrics_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/legacy.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/clean/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/clean/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/dead-code/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/dead-code/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/false-reuse/calc/limits.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/false-reuse/calc/limits_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/lint-debt/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/lint-debt/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/hidden/stats/summary_hidden_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/summary.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/summary_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/readability.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/readability_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/report.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/report_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/scenarios.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/scenarios_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/suites.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/task.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/task_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/trial.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/evals/trial_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/integration_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/main.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/main_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/models_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/paths.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/paths_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/persistence.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/persistence_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/60-testing.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/prompts.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/prompts_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/providers.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/providers_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/scope.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/scope_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/shutdown.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/shutdown_integration_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/shutdown_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/lifecycle.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/lifecycle_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/limits.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/progress.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/progress_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/queries.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/tasks/queries_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/dashboard.test.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/favicon.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/files.html +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/files.spec.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/logs.html +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/logs.test.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.0}/src/lemming/web/mancha.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lemming-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
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
|
|
@@ -139,7 +139,7 @@ lemming exec --review readability
|
|
|
139
139
|
lemming -C ../review-worktree exec --review testing --scope main...HEAD
|
|
140
140
|
|
|
141
141
|
# Do the work, then gate it
|
|
142
|
-
lemming exec "Add pagination to the tasks API" --review
|
|
142
|
+
lemming exec "Add pagination to the tasks API" --review '*'
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
`--scope` takes paths, which pass through untouched, or a git revision range,
|
|
@@ -265,9 +265,9 @@ tasks may not fully achieve the stated goal. **Orchestrator Hooks** address this
|
|
|
265
265
|
by running custom agents or scripts after each task execution to evaluate
|
|
266
266
|
results and adapt the roadmap.
|
|
267
267
|
|
|
268
|
-
Lemming runs
|
|
269
|
-
`roadmap` hook
|
|
270
|
-
conventions control their behavior:
|
|
268
|
+
Lemming runs the hooks enabled for the project, which by default is only the
|
|
269
|
+
built-in `roadmap` hook; review hooks are opt-in. Hooks are plain Markdown
|
|
270
|
+
files, and udev-style filename conventions control their behavior:
|
|
271
271
|
|
|
272
272
|
- **Ordering**: A numeric prefix sets the execution order (e.g. `10-lint.md`
|
|
273
273
|
runs before `90-roadmap.md`); files without a prefix default to priority 50.
|
|
@@ -276,15 +276,15 @@ conventions control their behavior:
|
|
|
276
276
|
- **Oneshot tasks**: A task added with `--oneshot` skips all hooks once it
|
|
277
277
|
completes, so mechanical work (a typo fix, a version bump) does not pay for
|
|
278
278
|
the full review lifecycle. Failure hooks still run if it fails.
|
|
279
|
-
- **
|
|
280
|
-
lower-precedence layer.
|
|
279
|
+
- **Masking**: An empty file hides the hook of the same name from a
|
|
280
|
+
lower-precedence layer, even where it is enabled.
|
|
281
281
|
|
|
282
282
|
```bash
|
|
283
|
-
#
|
|
284
|
-
lemming hooks
|
|
283
|
+
# Run the testing review after every task in this project
|
|
284
|
+
lemming hooks enable testing
|
|
285
285
|
|
|
286
|
-
#
|
|
287
|
-
lemming hooks
|
|
286
|
+
# Or pick the reviews for one task, replacing the project's ('' for none)
|
|
287
|
+
lemming add "Redesign the settings page" --hooks ux,testing
|
|
288
288
|
```
|
|
289
289
|
|
|
290
290
|
### Built-in Hooks ⚓️
|
|
@@ -329,10 +329,14 @@ See [docs/HOOKS.md](docs/HOOKS.md) for more details.
|
|
|
329
329
|
Use the `config` and `hooks` commands to manage your project's execution loop:
|
|
330
330
|
|
|
331
331
|
```bash
|
|
332
|
-
# List all hooks in execution order,
|
|
332
|
+
# List all hooks in execution order, marking the ones enabled by default
|
|
333
333
|
lemming hooks list
|
|
334
334
|
|
|
335
|
-
#
|
|
335
|
+
# Enable or disable hooks for the project (stored in tasks.yml)
|
|
336
|
+
lemming hooks enable readability
|
|
337
|
+
lemming hooks disable readability
|
|
338
|
+
|
|
339
|
+
# View current project configuration (includes the enabled hooks)
|
|
336
340
|
lemming config list
|
|
337
341
|
|
|
338
342
|
# Persist configuration to tasks.yml
|
|
@@ -380,9 +384,10 @@ These come before the subcommand and apply to all of them.
|
|
|
380
384
|
- **`goal [<text>]`**: Set or view the long-term goal shared by all tasks.
|
|
381
385
|
Supports `-f/--file`.
|
|
382
386
|
- **`add <desc>`**: Append a new task. Supports `--index`, `--runner`,
|
|
383
|
-
`--model`, and `--
|
|
384
|
-
|
|
385
|
-
|
|
387
|
+
`--model`, `--oneshot`, and `--hooks` (the task's reviews, comma-separated;
|
|
388
|
+
inherited from `--parent` when omitted).
|
|
389
|
+
- **`edit <id>`**: Modify a task's description, runner, model, position,
|
|
390
|
+
oneshot flag (`--oneshot/--no-oneshot`), or reviews (`--hooks`).
|
|
386
391
|
- **`brief <id> [text]`**: View or set a task's long-form brief. Unlike the
|
|
387
392
|
description it has no length cap, and it is appended to the runner prompt
|
|
388
393
|
automatically — the right home for measured timings, exact failing selectors,
|
|
@@ -410,12 +415,9 @@ These come before the subcommand and apply to all of them.
|
|
|
410
415
|
- `set <key> <value>`: Update a setting. `set model default` clears a pinned
|
|
411
416
|
model without touching the runner.
|
|
412
417
|
- **`hooks`**: Manage orchestrator hooks.
|
|
413
|
-
- `list`: View available and
|
|
414
|
-
- `
|
|
415
|
-
- `
|
|
416
|
-
- `disable <name>...`: Deactivate one or more hooks.
|
|
417
|
-
- `set <name>...`: Set the exact list of active hooks.
|
|
418
|
-
- `reset`: Restore default hooks (run all available).
|
|
418
|
+
- `list`: View available hooks and the ones enabled for the project.
|
|
419
|
+
- `enable <name>...`: Run one or more hooks after every task.
|
|
420
|
+
- `disable <name>...`: Stop running one or more hooks by default.
|
|
419
421
|
- **`readability`**: Code quality tool for style guide adherence, wrapping the
|
|
420
422
|
[readability](https://github.com/owahltinez/readability) package. Ruff and
|
|
421
423
|
Pyrefly run with bundled Google-style defaults unless the target project
|
|
@@ -119,7 +119,7 @@ lemming exec --review readability
|
|
|
119
119
|
lemming -C ../review-worktree exec --review testing --scope main...HEAD
|
|
120
120
|
|
|
121
121
|
# Do the work, then gate it
|
|
122
|
-
lemming exec "Add pagination to the tasks API" --review
|
|
122
|
+
lemming exec "Add pagination to the tasks API" --review '*'
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
`--scope` takes paths, which pass through untouched, or a git revision range,
|
|
@@ -245,9 +245,9 @@ tasks may not fully achieve the stated goal. **Orchestrator Hooks** address this
|
|
|
245
245
|
by running custom agents or scripts after each task execution to evaluate
|
|
246
246
|
results and adapt the roadmap.
|
|
247
247
|
|
|
248
|
-
Lemming runs
|
|
249
|
-
`roadmap` hook
|
|
250
|
-
conventions control their behavior:
|
|
248
|
+
Lemming runs the hooks enabled for the project, which by default is only the
|
|
249
|
+
built-in `roadmap` hook; review hooks are opt-in. Hooks are plain Markdown
|
|
250
|
+
files, and udev-style filename conventions control their behavior:
|
|
251
251
|
|
|
252
252
|
- **Ordering**: A numeric prefix sets the execution order (e.g. `10-lint.md`
|
|
253
253
|
runs before `90-roadmap.md`); files without a prefix default to priority 50.
|
|
@@ -256,15 +256,15 @@ conventions control their behavior:
|
|
|
256
256
|
- **Oneshot tasks**: A task added with `--oneshot` skips all hooks once it
|
|
257
257
|
completes, so mechanical work (a typo fix, a version bump) does not pay for
|
|
258
258
|
the full review lifecycle. Failure hooks still run if it fails.
|
|
259
|
-
- **
|
|
260
|
-
lower-precedence layer.
|
|
259
|
+
- **Masking**: An empty file hides the hook of the same name from a
|
|
260
|
+
lower-precedence layer, even where it is enabled.
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
|
-
#
|
|
264
|
-
lemming hooks
|
|
263
|
+
# Run the testing review after every task in this project
|
|
264
|
+
lemming hooks enable testing
|
|
265
265
|
|
|
266
|
-
#
|
|
267
|
-
lemming hooks
|
|
266
|
+
# Or pick the reviews for one task, replacing the project's ('' for none)
|
|
267
|
+
lemming add "Redesign the settings page" --hooks ux,testing
|
|
268
268
|
```
|
|
269
269
|
|
|
270
270
|
### Built-in Hooks ⚓️
|
|
@@ -309,10 +309,14 @@ See [docs/HOOKS.md](docs/HOOKS.md) for more details.
|
|
|
309
309
|
Use the `config` and `hooks` commands to manage your project's execution loop:
|
|
310
310
|
|
|
311
311
|
```bash
|
|
312
|
-
# List all hooks in execution order,
|
|
312
|
+
# List all hooks in execution order, marking the ones enabled by default
|
|
313
313
|
lemming hooks list
|
|
314
314
|
|
|
315
|
-
#
|
|
315
|
+
# Enable or disable hooks for the project (stored in tasks.yml)
|
|
316
|
+
lemming hooks enable readability
|
|
317
|
+
lemming hooks disable readability
|
|
318
|
+
|
|
319
|
+
# View current project configuration (includes the enabled hooks)
|
|
316
320
|
lemming config list
|
|
317
321
|
|
|
318
322
|
# Persist configuration to tasks.yml
|
|
@@ -360,9 +364,10 @@ These come before the subcommand and apply to all of them.
|
|
|
360
364
|
- **`goal [<text>]`**: Set or view the long-term goal shared by all tasks.
|
|
361
365
|
Supports `-f/--file`.
|
|
362
366
|
- **`add <desc>`**: Append a new task. Supports `--index`, `--runner`,
|
|
363
|
-
`--model`, and `--
|
|
364
|
-
|
|
365
|
-
|
|
367
|
+
`--model`, `--oneshot`, and `--hooks` (the task's reviews, comma-separated;
|
|
368
|
+
inherited from `--parent` when omitted).
|
|
369
|
+
- **`edit <id>`**: Modify a task's description, runner, model, position,
|
|
370
|
+
oneshot flag (`--oneshot/--no-oneshot`), or reviews (`--hooks`).
|
|
366
371
|
- **`brief <id> [text]`**: View or set a task's long-form brief. Unlike the
|
|
367
372
|
description it has no length cap, and it is appended to the runner prompt
|
|
368
373
|
automatically — the right home for measured timings, exact failing selectors,
|
|
@@ -390,12 +395,9 @@ These come before the subcommand and apply to all of them.
|
|
|
390
395
|
- `set <key> <value>`: Update a setting. `set model default` clears a pinned
|
|
391
396
|
model without touching the runner.
|
|
392
397
|
- **`hooks`**: Manage orchestrator hooks.
|
|
393
|
-
- `list`: View available and
|
|
394
|
-
- `
|
|
395
|
-
- `
|
|
396
|
-
- `disable <name>...`: Deactivate one or more hooks.
|
|
397
|
-
- `set <name>...`: Set the exact list of active hooks.
|
|
398
|
-
- `reset`: Restore default hooks (run all available).
|
|
398
|
+
- `list`: View available hooks and the ones enabled for the project.
|
|
399
|
+
- `enable <name>...`: Run one or more hooks after every task.
|
|
400
|
+
- `disable <name>...`: Stop running one or more hooks by default.
|
|
399
401
|
- **`readability`**: Code quality tool for style guide adherence, wrapping the
|
|
400
402
|
[readability](https://github.com/owahltinez/readability) package. Ruff and
|
|
401
403
|
Pyrefly run with bundled Google-style defaults unless the target project
|
|
@@ -124,8 +124,8 @@ reviews run — against work that already exists.
|
|
|
124
124
|
```sh
|
|
125
125
|
lemming exec --review readability # uncommitted work
|
|
126
126
|
lemming exec --review testing --scope main...HEAD
|
|
127
|
-
lemming exec --review
|
|
128
|
-
lemming exec "Add pagination" --review
|
|
127
|
+
lemming exec --review '*' --scope src/api/
|
|
128
|
+
lemming exec "Add pagination" --review '*' # do the work, then gate it
|
|
129
129
|
|
|
130
130
|
# Someone else's branch, in a worktree so your own tree is untouched
|
|
131
131
|
git worktree add /tmp/pr-123 && (cd /tmp/pr-123 && gh pr checkout 123)
|
|
@@ -162,12 +162,12 @@ The loop shares one workspace across all its tasks, so isolate the **whole run**
|
|
|
162
162
|
— one branch or worktree created before `lemming run`, driven with
|
|
163
163
|
`lemming -C <worktree> run` — never one per task.
|
|
164
164
|
|
|
165
|
-
**Mark mechanical tasks `--oneshot`.** After every other task,
|
|
166
|
-
|
|
167
|
-
typo fix, rename, version bump, or cleanup without `--oneshot` takes
|
|
168
|
-
times longer than the work itself.
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
**Mark mechanical tasks `--oneshot`.** After every other task, a roadmap hook
|
|
166
|
+
may revise the queue and any enabled reviews run; each hook is a full agent
|
|
167
|
+
run, so a typo fix, rename, version bump, or cleanup without `--oneshot` takes
|
|
168
|
+
several times longer than the work itself. Reviews are opt-in: pick them for
|
|
169
|
+
one task with `lemming add ... --hooks testing,ux`, or for every task with
|
|
170
|
+
`lemming hooks enable <name>`. `lemming hooks list` shows which are enabled.
|
|
171
171
|
|
|
172
172
|
### Supervise in milestones
|
|
173
173
|
|
|
@@ -153,6 +153,9 @@ running a constant policy in either direction should score 50% across the pair.
|
|
|
153
153
|
`false-reuse-restraint` exists for exactly this reason: it is the inverse of
|
|
154
154
|
`consolidate-or-report-live-duplication`, with two functions that share a shape
|
|
155
155
|
but not a concept, and folding them together is the failure.
|
|
156
|
+
`finding-is-not-a-task` is the roadmap suite's inverse of
|
|
157
|
+
`follow-up-without-code-changes`: the finished task logs optional polish ideas
|
|
158
|
+
rather than a bug, and scheduling them is the failure.
|
|
156
159
|
|
|
157
160
|
The `task` suite carries the same idea into the code an agent writes rather than
|
|
158
161
|
reviews. `minimal-change-restraint` hands it one well-specified function to add
|
|
@@ -36,10 +36,10 @@ conventions control their behavior:
|
|
|
36
36
|
failure hooks, so recovery is never skipped. Note that `roadmap` is skipped
|
|
37
37
|
too: if a oneshot task is the last one in the queue, the run ends without a
|
|
38
38
|
goal audit, so nothing is queued to carry the goal further.
|
|
39
|
-
- **Masking**: An empty file
|
|
40
|
-
lower-precedence layer (see below)
|
|
41
|
-
`.lemming/hooks/readability.md`
|
|
42
|
-
that project.
|
|
39
|
+
- **Masking**: An empty file hides the hook of the same name from a
|
|
40
|
+
lower-precedence layer (see below), even where a project or task enables it.
|
|
41
|
+
For example, an empty `.lemming/hooks/readability.md` hides the built-in
|
|
42
|
+
`readability` hook from that project.
|
|
43
43
|
- **Overriding**: A non-empty file replaces the hook of the same name from a
|
|
44
44
|
lower-precedence layer. The winning filename also determines the priority, so
|
|
45
45
|
keep the `9x-` prefix when overriding a failure hook (e.g. `90-roadmap.md`),
|
|
@@ -47,6 +47,9 @@ conventions control their behavior:
|
|
|
47
47
|
|
|
48
48
|
## Built-in Hooks
|
|
49
49
|
|
|
50
|
+
Only `roadmap` runs by default; the review hooks are opt-in per project or task
|
|
51
|
+
(see [Enabling and Disabling](#enabling-and-disabling)).
|
|
52
|
+
|
|
50
53
|
### `roadmap` (Default)
|
|
51
54
|
|
|
52
55
|
The primary mechanism for autonomous project management. It analyzes the results
|
|
@@ -175,25 +178,32 @@ are issues, add a new task to fix them.
|
|
|
175
178
|
|
|
176
179
|
### Enabling and Disabling
|
|
177
180
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
The `hooks` command group provides shortcuts for managing project masks:
|
|
181
|
+
Each project lists the hooks it runs in `tasks.yml` under `config.hooks`, which
|
|
182
|
+
defaults to `[roadmap]`: review hooks are opt-in. Manage the list with:
|
|
182
183
|
|
|
183
184
|
```bash
|
|
184
|
-
#
|
|
185
|
+
# Run the lint review after every task in this project
|
|
186
|
+
lemming hooks enable lint
|
|
187
|
+
|
|
188
|
+
# Stop running it by default
|
|
185
189
|
lemming hooks disable lint
|
|
190
|
+
```
|
|
186
191
|
|
|
187
|
-
|
|
188
|
-
|
|
192
|
+
A task can pick its own reviews, which replace the project's for that task:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
lemming add "Redesign the settings page" --hooks ux,testing
|
|
196
|
+
lemming edit <id> --hooks '' # no reviews for this task
|
|
197
|
+
lemming edit <id> --hooks '*' # every review available now
|
|
189
198
|
```
|
|
190
199
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
`--hooks` accepts only reviews (priority below 90). Hooks at 90 and above, such
|
|
201
|
+
as `roadmap`, orchestrate the queue and always follow the project's list. A task
|
|
202
|
+
added with `--parent` and no `--hooks` inherits its parent's choice, so a split
|
|
203
|
+
keeps it. The same rules apply to built-in, global, and project hooks, and a
|
|
204
|
+
masked or missing hook never runs whatever the lists say.
|
|
195
205
|
|
|
196
|
-
|
|
206
|
+
The selection is re-read every time hooks run, so changes are picked up
|
|
197
207
|
dynamically by a running orchestrator loop.
|
|
198
208
|
|
|
199
209
|
### Available Variables
|
|
@@ -227,7 +237,7 @@ audit" describes the kind of review, not its extent.
|
|
|
227
237
|
### Listing Hooks
|
|
228
238
|
|
|
229
239
|
You can see all hooks in execution order, with their priority, source layer, and
|
|
230
|
-
status (
|
|
240
|
+
status (enabled for the project, masked, runs on failure) using the CLI:
|
|
231
241
|
|
|
232
242
|
```bash
|
|
233
243
|
lemming hooks list
|
|
Binary file
|
|
Binary file
|
|
@@ -36,11 +36,14 @@ def update_config(
|
|
|
36
36
|
config: models.RoadmapConfig,
|
|
37
37
|
project: str | None = None,
|
|
38
38
|
):
|
|
39
|
-
"""
|
|
39
|
+
"""Update the posted roadmap configuration fields and return the result."""
|
|
40
40
|
tasks_file = context.resolve_tasks_file(request.app.state, project)
|
|
41
41
|
with persistence.lock_tasks(tasks_file):
|
|
42
42
|
data = persistence.load_tasks(tasks_file)
|
|
43
|
-
|
|
43
|
+
# Omitted fields keep their value; the dashboard posts a subset
|
|
44
|
+
data.config = data.config.model_copy(
|
|
45
|
+
update=config.model_dump(exclude_unset=True)
|
|
46
|
+
)
|
|
44
47
|
persistence.save_tasks(tasks_file, data)
|
|
45
48
|
return data.config
|
|
46
49
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from unittest.mock import patch
|
|
2
2
|
|
|
3
|
+
from lemming import persistence
|
|
3
4
|
from lemming.api import main
|
|
4
5
|
|
|
5
6
|
|
|
@@ -17,6 +18,25 @@ def test_run_loop(client, test_tasks):
|
|
|
17
18
|
assert "--hook" not in args
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def test_update_config_keeps_omitted_fields(client, test_tasks):
|
|
22
|
+
"""The dashboard posts a partial config; it must not reset the rest."""
|
|
23
|
+
data = persistence.load_tasks(test_tasks)
|
|
24
|
+
data.config.hooks = ["ux", "roadmap"]
|
|
25
|
+
data.config.model = "fast-model"
|
|
26
|
+
persistence.save_tasks(test_tasks, data)
|
|
27
|
+
|
|
28
|
+
response = client.post(
|
|
29
|
+
"/api/config", json={"runner": "codex", "retries": 5, "time_limit": 0}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
assert response.status_code == 200
|
|
33
|
+
config = persistence.load_tasks(test_tasks).config
|
|
34
|
+
assert config.runner == "codex"
|
|
35
|
+
assert config.retries == 5
|
|
36
|
+
assert config.hooks == ["ux", "roadmap"]
|
|
37
|
+
assert config.model == "fast-model"
|
|
38
|
+
|
|
39
|
+
|
|
20
40
|
def test_get_runners(client):
|
|
21
41
|
response = client.get("/api/runners")
|
|
22
42
|
assert response.status_code == 200
|
|
@@ -5,7 +5,7 @@ import pathlib
|
|
|
5
5
|
import fastapi
|
|
6
6
|
import pydantic
|
|
7
7
|
|
|
8
|
-
from .. import hooks
|
|
8
|
+
from .. import hooks, persistence
|
|
9
9
|
from . import context
|
|
10
10
|
|
|
11
11
|
router = fastapi.APIRouter()
|
|
@@ -13,12 +13,14 @@ router = fastapi.APIRouter()
|
|
|
13
13
|
|
|
14
14
|
def _hook_payload(tasks_file: pathlib.Path) -> list[dict]:
|
|
15
15
|
"""Serializes resolved hooks for API responses."""
|
|
16
|
+
enabled = persistence.load_tasks(tasks_file).config.hooks
|
|
16
17
|
return [
|
|
17
18
|
{
|
|
18
19
|
"name": h.name,
|
|
19
20
|
"priority": h.priority,
|
|
20
21
|
"source": h.source,
|
|
21
22
|
"masked": h.masked,
|
|
23
|
+
"enabled": not h.masked and h.name in enabled,
|
|
22
24
|
"runs_on_failure": h.priority >= hooks.FAILURE_HOOK_PRIORITY,
|
|
23
25
|
}
|
|
24
26
|
for h in hooks.resolve_hooks(tasks_file)
|
|
@@ -45,7 +47,7 @@ def toggle_hook(
|
|
|
45
47
|
toggle: HookToggle,
|
|
46
48
|
project: str | None = None,
|
|
47
49
|
):
|
|
48
|
-
"""
|
|
50
|
+
"""Add or remove a hook from those enabled for the project."""
|
|
49
51
|
tasks_file = context.resolve_tasks_file(request.app.state, project)
|
|
50
52
|
try:
|
|
51
53
|
if toggle.enabled:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from lemming import persistence
|
|
1
2
|
from lemming.api import main
|
|
2
3
|
|
|
3
4
|
|
|
@@ -30,8 +31,6 @@ def test_list_hooks_with_project(client, test_tasks, monkeypatch, tmp_path):
|
|
|
30
31
|
root = main.app.state.root
|
|
31
32
|
project_dir = root / "project1"
|
|
32
33
|
project_dir.mkdir()
|
|
33
|
-
tasks_file = project_dir / "tasks.yml"
|
|
34
|
-
tasks_file.touch()
|
|
35
34
|
|
|
36
35
|
response = client.get("/api/hooks", params={"project": "project1"})
|
|
37
36
|
|
|
@@ -40,29 +39,28 @@ def test_list_hooks_with_project(client, test_tasks, monkeypatch, tmp_path):
|
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
def test_toggle_hook(client, test_tasks, monkeypatch, tmp_path):
|
|
43
|
-
"""
|
|
42
|
+
"""Toggling a hook edits the project's default set, not mask files."""
|
|
44
43
|
monkeypatch.setenv("LEMMING_HOME", str(tmp_path / "lemming_home"))
|
|
45
|
-
# The mask filename keeps the hook's priority (roadmap is 90)
|
|
46
|
-
mask = test_tasks.parent / ".lemming" / "hooks" / "90-roadmap.md"
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
response = client.get("/api/hooks")
|
|
46
|
+
hooks = {h["name"]: h for h in response.json()}
|
|
47
|
+
assert hooks["roadmap"]["enabled"] is True
|
|
48
|
+
assert hooks["ux"]["enabled"] is False
|
|
49
|
+
|
|
50
|
+
response = client.post("/api/hooks", json={"name": "ux", "enabled": True})
|
|
52
51
|
assert response.status_code == 200
|
|
53
52
|
hooks = {h["name"]: h for h in response.json()}
|
|
54
|
-
assert hooks["
|
|
55
|
-
assert mask.exists()
|
|
56
|
-
assert mask.read_text(encoding="utf-8") == ""
|
|
53
|
+
assert hooks["ux"]["enabled"] is True
|
|
57
54
|
|
|
58
|
-
# Enable: the mask is removed
|
|
59
55
|
response = client.post(
|
|
60
|
-
"/api/hooks", json={"name": "roadmap", "enabled":
|
|
56
|
+
"/api/hooks", json={"name": "roadmap", "enabled": False}
|
|
61
57
|
)
|
|
62
58
|
assert response.status_code == 200
|
|
63
59
|
hooks = {h["name"]: h for h in response.json()}
|
|
60
|
+
assert hooks["roadmap"]["enabled"] is False
|
|
64
61
|
assert hooks["roadmap"]["masked"] is False
|
|
65
|
-
assert
|
|
62
|
+
assert persistence.load_tasks(test_tasks).config.hooks == ["ux"]
|
|
63
|
+
assert not (test_tasks.parent / ".lemming").exists()
|
|
66
64
|
|
|
67
65
|
|
|
68
66
|
def test_toggle_unknown_hook(client, test_tasks, monkeypatch, tmp_path):
|
|
@@ -75,21 +73,3 @@ def test_toggle_unknown_hook(client, test_tasks, monkeypatch, tmp_path):
|
|
|
75
73
|
|
|
76
74
|
assert response.status_code == 400
|
|
77
75
|
assert "not found" in response.json()["detail"]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_toggle_hook_refuses_project_override(
|
|
81
|
-
client, test_tasks, monkeypatch, tmp_path
|
|
82
|
-
):
|
|
83
|
-
"""Disabling a hook with a project override must not clobber it."""
|
|
84
|
-
monkeypatch.setenv("LEMMING_HOME", str(tmp_path / "lemming_home"))
|
|
85
|
-
local_hooks_dir = test_tasks.parent / ".lemming" / "hooks"
|
|
86
|
-
local_hooks_dir.mkdir(parents=True)
|
|
87
|
-
override = local_hooks_dir / "roadmap.md"
|
|
88
|
-
override.write_text("custom prompt", encoding="utf-8")
|
|
89
|
-
|
|
90
|
-
response = client.post(
|
|
91
|
-
"/api/hooks", json={"name": "roadmap", "enabled": False}
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
assert response.status_code == 400
|
|
95
|
-
assert override.read_text(encoding="utf-8") == "custom prompt"
|
|
@@ -27,8 +27,8 @@ def config_list(ctx: click.Context):
|
|
|
27
27
|
click.echo(f" Retries: {c.retries}")
|
|
28
28
|
click.echo(f" Time limit: {format_duration(c.time_limit)}")
|
|
29
29
|
|
|
30
|
-
#
|
|
31
|
-
active_hooks = hooks.
|
|
30
|
+
# Enabled hooks that resolve to an unmasked file, in execution order
|
|
31
|
+
active_hooks = hooks.select_hooks(tasks_file, c.hooks, None)
|
|
32
32
|
click.echo(
|
|
33
33
|
" Hooks: "
|
|
34
34
|
f"{', '.join(active_hooks) if active_hooks else '(none)'}"
|
|
@@ -36,9 +36,10 @@ class TestCLIConfig(unittest.TestCase):
|
|
|
36
36
|
)
|
|
37
37
|
self.assertEqual(result.exit_code, 0)
|
|
38
38
|
self.assertIn("Runner:", result.output)
|
|
39
|
-
#
|
|
39
|
+
# Only the project's enabled hooks are listed; reviews are opt-in
|
|
40
40
|
self.assertIn("Hooks:", result.output)
|
|
41
41
|
self.assertIn("roadmap", result.output)
|
|
42
|
+
self.assertNotIn("readability", result.output)
|
|
42
43
|
|
|
43
44
|
def test_config_set(self):
|
|
44
45
|
result = self.cli_runner.invoke(
|
|
@@ -17,15 +17,11 @@ import typing
|
|
|
17
17
|
|
|
18
18
|
import click
|
|
19
19
|
|
|
20
|
-
from .. import models, paths, persistence, runner, scope, shutdown
|
|
21
|
-
from ..hooks import FAILURE_HOOK_PRIORITY, get_hook_priority, list_hooks
|
|
20
|
+
from .. import hooks, models, paths, persistence, runner, scope, shutdown
|
|
22
21
|
from ..orchestrator import run_loop
|
|
23
22
|
from ..tasks import lifecycle, limits, operations
|
|
24
23
|
from .main import cli
|
|
25
24
|
|
|
26
|
-
# Requests every review rather than making the caller name each one.
|
|
27
|
-
ALL_REVIEWS = "all"
|
|
28
|
-
|
|
29
25
|
# Ceiling on how much of the runner log is scanned for the closing message.
|
|
30
26
|
# The message is at the end, and a log carrying every tool call can be
|
|
31
27
|
# large enough that reading it whole is wasteful.
|
|
@@ -150,7 +146,7 @@ def _resolve_reviews(values: tuple[str, ...]) -> list[str]:
|
|
|
150
146
|
|
|
151
147
|
Args:
|
|
152
148
|
values: Requested reviews, each possibly comma-separated. The value
|
|
153
|
-
"
|
|
149
|
+
"*" stands for every review.
|
|
154
150
|
|
|
155
151
|
Returns:
|
|
156
152
|
Review names ordered by hook priority.
|
|
@@ -158,38 +154,13 @@ def _resolve_reviews(values: tuple[str, ...]) -> list[str]:
|
|
|
158
154
|
Raises:
|
|
159
155
|
click.UsageError: If a name is unknown or orchestrates the queue.
|
|
160
156
|
"""
|
|
161
|
-
requested = [
|
|
162
|
-
name.strip()
|
|
163
|
-
for value in values
|
|
164
|
-
for name in value.split(",")
|
|
165
|
-
if name.strip()
|
|
166
|
-
]
|
|
167
|
-
available = list_hooks()
|
|
168
|
-
|
|
169
157
|
# The 9x band orchestrates the queue: it revises the roadmap. Here the
|
|
170
158
|
# roadmap holds one task and is deleted afterwards, so a hook that adds
|
|
171
159
|
# tasks to it would set unbounded work running from a one-shot.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
]
|
|
177
|
-
|
|
178
|
-
if ALL_REVIEWS in requested:
|
|
179
|
-
return reviews
|
|
180
|
-
|
|
181
|
-
for name in requested:
|
|
182
|
-
if name in reviews:
|
|
183
|
-
continue
|
|
184
|
-
if name in available:
|
|
185
|
-
raise click.UsageError(
|
|
186
|
-
f"'{name}' orchestrates the roadmap and cannot run as a "
|
|
187
|
-
"review. Use 'lemming run' for roadmap revision."
|
|
188
|
-
)
|
|
189
|
-
raise click.UsageError(
|
|
190
|
-
f"Unknown review '{name}'. Available: {', '.join(reviews)}."
|
|
191
|
-
)
|
|
192
|
-
return [name for name in reviews if name in requested]
|
|
160
|
+
try:
|
|
161
|
+
return hooks.parse_reviews(values)
|
|
162
|
+
except ValueError as e:
|
|
163
|
+
raise click.UsageError(str(e)) from e
|
|
193
164
|
|
|
194
165
|
|
|
195
166
|
def _task_status(
|
|
@@ -225,7 +196,7 @@ def _task_status(
|
|
|
225
196
|
multiple=True,
|
|
226
197
|
help=(
|
|
227
198
|
"Reviews to run after the task, comma-separated or repeated. "
|
|
228
|
-
"Use '
|
|
199
|
+
"Use '*' for every review. With no description, only the "
|
|
229
200
|
"reviews run. Reviews may edit the working directory."
|
|
230
201
|
),
|
|
231
202
|
)
|
|
@@ -291,7 +262,7 @@ def exec_command(
|
|
|
291
262
|
lemming exec "Fix the flaky test in runner_test.py" --runner codex
|
|
292
263
|
lemming exec --review readability
|
|
293
264
|
lemming exec --review testing --scope main...HEAD
|
|
294
|
-
lemming exec "Add pagination" --review
|
|
265
|
+
lemming exec "Add pagination" --review '*'
|
|
295
266
|
"""
|
|
296
267
|
verbose = ctx.obj["VERBOSE"]
|
|
297
268
|
reviews = _resolve_reviews(review_values)
|
|
@@ -526,7 +526,7 @@ def test_review_refuses_the_orchestration_hook(repo):
|
|
|
526
526
|
|
|
527
527
|
|
|
528
528
|
def test_review_all_excludes_the_orchestration_band(repo):
|
|
529
|
-
"""--review
|
|
529
|
+
"""--review '*' must not require naming every review by hand."""
|
|
530
530
|
(repo / "committed.py").write_text("x = 2\n")
|
|
531
531
|
headers = []
|
|
532
532
|
|
|
@@ -535,7 +535,7 @@ def test_review_all_excludes_the_orchestration_band(repo):
|
|
|
535
535
|
return _finish("Reviewed.")(cmd, tasks_file, task_id, *args, **kwargs)
|
|
536
536
|
|
|
537
537
|
with mock.patch("lemming.runner.run_with_heartbeat", record):
|
|
538
|
-
CliRunner().invoke(main.cli, ["exec", "--review", "
|
|
538
|
+
CliRunner().invoke(main.cli, ["exec", "--review", "*"])
|
|
539
539
|
|
|
540
540
|
assert "Hook: roadmap" not in headers
|
|
541
541
|
assert "Hook: readability" in headers
|