lemming-cli 0.7.2__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.7.2 → lemming_cli-0.8.1}/.github/workflows/ci.yml +1 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/PKG-INFO +27 -24
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/README.md +26 -23
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/SKILL.md +8 -8
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/docs/EVALS.md +3 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/docs/HOOKS.md +31 -17
- 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.7.2 → lemming_cli-0.8.1}/pyproject.toml +1 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/config.py +5 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/config_test.py +20 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/hooks.py +4 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/hooks_test.py +13 -33
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/tasks.py +18 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/tasks_test.py +47 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/config.py +2 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/config_test.py +2 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/exec_cli.py +8 -37
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/exec_cli_test.py +2 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/hooks.py +20 -19
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/hooks_test.py +26 -48
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/tasks.py +47 -3
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/tasks_test.py +53 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/container.py +45 -22
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/harness.py +6 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/harness_test.py +21 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/readability.py +16 -20
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/readability_test.py +16 -13
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/roadmap.py +96 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/roadmap_test.py +35 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/hooks.py +110 -95
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/hooks_test.py +129 -76
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/models.py +4 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/orchestrator.py +21 -17
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/orchestrator_test.py +41 -4
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/50-readability.md +21 -13
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/70-ux.md +1 -1
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/90-roadmap.md +12 -5
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/prompts/taskrunner.md +17 -6
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/runner.py +25 -9
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/runner_test.py +46 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/operations.py +17 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/operations_test.py +30 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/dashboard.spec.js +104 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/dashboard.test.js +3 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/favicon.spec.js +1 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/index.html +77 -2
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/index.js +43 -3
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/screenshots.spec.js +3 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/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.1}/.dockerignore +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/.github/workflows/publish.yml +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/.github/workflows/screenshots.yml +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/.gitignore +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/.prettierignore +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/.prettierrc +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/Dockerfile +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/LICENSE +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/biome.json +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/docker-compose.yml +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/docs/WORKSPACE.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/docs/screenshots/task-log-desktop.png +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/docs/screenshots/task-log-mobile.png +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/package-lock.json +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/package.json +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/playwright.config.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/auth.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/auth_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/conftest.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/context.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/context_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/directories.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/directories_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/files.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/files_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/logging.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/logging_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/loop.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/loop_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/main.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/api/main_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/artifact_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/brief_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/artifact.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/brief.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/goal.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/goal_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/machine_output_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/main.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/main_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/operations.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/operations_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/progress.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/progress_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/readability_cli.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/readability_cli_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/skill.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/skill_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/cli/stop_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/conftest.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/__main__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/cli.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/cli_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/container_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/fixtures.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/fixtures_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/metrics.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/metrics_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/legacy.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/base/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/clean/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/clean/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/dead-code/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/dead-code/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/false-reuse/calc/limits.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/false-reuse/calc/limits_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/lint-debt/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/readability/lint-debt/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/add-only/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/full-ops/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/hidden/stats/summary_hidden_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/README.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/stats/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/stats/summary.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/projects/task/summary/stats/summary_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/report.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/report_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/scenarios.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/scenarios_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/suites.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/task.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/task_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/trial.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/evals/trial_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/integration_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/main.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/main_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/models_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/paths.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/paths_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/persistence.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/persistence_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/prompts/hooks/60-testing.md +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/prompts.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/prompts_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/providers.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/providers_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/scope.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/scope_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/shutdown.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/shutdown_integration_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/shutdown_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/__init__.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/lifecycle.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/lifecycle_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/limits.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/progress.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/progress_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/queries.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/tasks/queries_test.py +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/favicon.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/files.html +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/files.spec.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/logs.html +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/src/lemming/web/logs.test.js +0 -0
- {lemming_cli-0.7.2 → lemming_cli-0.8.1}/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.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
|
|
@@ -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,
|
|
@@ -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
|
|
|
@@ -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,11 @@ 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`;
|
|
391
|
+
`--project-hooks` restores the project's).
|
|
386
392
|
- **`brief <id> [text]`**: View or set a task's long-form brief. Unlike the
|
|
387
393
|
description it has no length cap, and it is appended to the runner prompt
|
|
388
394
|
automatically — the right home for measured timings, exact failing selectors,
|
|
@@ -410,12 +416,9 @@ These come before the subcommand and apply to all of them.
|
|
|
410
416
|
- `set <key> <value>`: Update a setting. `set model default` clears a pinned
|
|
411
417
|
model without touching the runner.
|
|
412
418
|
- **`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).
|
|
419
|
+
- `list`: View available hooks and the ones enabled for the project.
|
|
420
|
+
- `enable <name>...`: Run one or more hooks after every task.
|
|
421
|
+
- `disable <name>...`: Stop running one or more hooks by default.
|
|
419
422
|
- **`readability`**: Code quality tool for style guide adherence, wrapping the
|
|
420
423
|
[readability](https://github.com/owahltinez/readability) package. Ruff and
|
|
421
424
|
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,
|
|
@@ -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
|
|
|
@@ -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,11 @@ 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`;
|
|
371
|
+
`--project-hooks` restores the project's).
|
|
366
372
|
- **`brief <id> [text]`**: View or set a task's long-form brief. Unlike the
|
|
367
373
|
description it has no length cap, and it is appended to the runner prompt
|
|
368
374
|
automatically — the right home for measured timings, exact failing selectors,
|
|
@@ -390,12 +396,9 @@ These come before the subcommand and apply to all of them.
|
|
|
390
396
|
- `set <key> <value>`: Update a setting. `set model default` clears a pinned
|
|
391
397
|
model without touching the runner.
|
|
392
398
|
- **`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).
|
|
399
|
+
- `list`: View available hooks and the ones enabled for the project.
|
|
400
|
+
- `enable <name>...`: Run one or more hooks after every task.
|
|
401
|
+
- `disable <name>...`: Stop running one or more hooks by default.
|
|
399
402
|
- **`readability`**: Code quality tool for style guide adherence, wrapping the
|
|
400
403
|
[readability](https://github.com/owahltinez/readability) package. Ruff and
|
|
401
404
|
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,36 @@ 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
|
|
198
|
+
lemming edit <id> --project-hooks # back to the project's reviews
|
|
189
199
|
```
|
|
190
200
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
|
|
204
|
+
`--hooks` accepts only reviews (priority below 90). Hooks at 90 and above, such
|
|
205
|
+
as `roadmap`, orchestrate the queue and always follow the project's list. A task
|
|
206
|
+
added with `--parent` and no `--hooks` inherits its parent's choice, so a split
|
|
207
|
+
keeps it. The same rules apply to built-in, global, and project hooks, and a
|
|
208
|
+
masked or missing hook never runs whatever the lists say.
|
|
195
209
|
|
|
196
|
-
|
|
210
|
+
The selection is re-read every time hooks run, so changes are picked up
|
|
197
211
|
dynamically by a running orchestrator loop.
|
|
198
212
|
|
|
199
213
|
### Available Variables
|
|
@@ -227,7 +241,7 @@ audit" describes the kind of review, not its extent.
|
|
|
227
241
|
### Listing Hooks
|
|
228
242
|
|
|
229
243
|
You can see all hooks in execution order, with their priority, source layer, and
|
|
230
|
-
status (
|
|
244
|
+
status (enabled for the project, masked, runs on failure) using the CLI:
|
|
231
245
|
|
|
232
246
|
```bash
|
|
233
247
|
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"
|
|
@@ -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
|