lemming-cli 0.7.1__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.1 → lemming_cli-0.8.0}/.github/workflows/ci.yml +5 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.github/workflows/screenshots.yml +1 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/Dockerfile +1 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/PKG-INFO +25 -23
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/README.md +23 -21
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/SKILL.md +8 -8
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docker-compose.yml +1 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/EVALS.md +3 -0
- {lemming_cli-0.7.1 → 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.1 → lemming_cli-0.8.0}/pyproject.toml +2 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/auth.py +18 -5
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/auth_test.py +10 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/config.py +26 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/config_test.py +35 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/hooks.py +4 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/hooks_test.py +13 -33
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/tasks.py +18 -4
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/tasks_test.py +18 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/config.py +2 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/config_test.py +2 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/exec_cli.py +8 -37
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/exec_cli_test.py +2 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/hooks.py +20 -19
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/hooks_test.py +26 -48
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/operations.py +10 -3
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/operations_test.py +17 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/tasks.py +34 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/tasks_test.py +29 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/readability.py +23 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/readability_test.py +47 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/roadmap.py +96 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/roadmap_test.py +35 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/hooks.py +110 -95
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/hooks_test.py +129 -76
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/models.py +4 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/orchestrator.py +21 -17
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/orchestrator_test.py +41 -4
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/paths.py +31 -8
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/paths_test.py +34 -6
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/50-readability.md +21 -13
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/70-ux.md +1 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/90-roadmap.md +12 -5
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/taskrunner.md +17 -6
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/runner.py +25 -9
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/runner_test.py +46 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/scope.py +4 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/scope_test.py +8 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/operations.py +13 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/operations_test.py +27 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/dashboard.spec.js +1 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/favicon.spec.js +1 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/index.html +1 -1
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/index.js +2 -2
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/screenshots.spec.js +3 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/uv.lock +6 -6
- lemming_cli-0.7.1/docs/screenshots/dashboard-desktop.png +0 -0
- lemming_cli-0.7.1/docs/screenshots/dashboard-mobile.png +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.dockerignore +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.github/workflows/publish.yml +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.gitignore +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.prettierignore +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.prettierrc +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/LICENSE +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/biome.json +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/WORKSPACE.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/screenshots/task-log-desktop.png +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/screenshots/task-log-mobile.png +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/package-lock.json +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/package.json +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/playwright.config.js +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/conftest.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/context.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/context_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/directories.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/directories_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/files.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/files_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/logging.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/logging_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/loop.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/loop_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/main.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/main_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/artifact_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/brief_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/artifact.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/brief.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/goal.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/goal_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/machine_output_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/main.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/main_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/progress.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/progress_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/readability_cli.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/readability_cli_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/skill.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/skill_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/stop_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/conftest.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/__main__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/cli.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/cli_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/container.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/container_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/fixtures.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/fixtures_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/harness.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/harness_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/metrics.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/metrics_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/README.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/legacy.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/clean/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/clean/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/dead-code/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/dead-code/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/false-reuse/calc/limits.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/false-reuse/calc/limits_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/lint-debt/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/lint-debt/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/README.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/README.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/README.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/README.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/hidden/stats/summary_hidden_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/README.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/summary.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/summary_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/report.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/report_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/scenarios.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/scenarios_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/suites.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/task.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/task_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/trial.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/trial_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/integration_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/main.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/main_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/models_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/persistence.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/persistence_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/60-testing.md +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/providers.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/providers_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/shutdown.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/shutdown_integration_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/shutdown_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/__init__.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/lifecycle.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/lifecycle_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/limits.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/progress.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/progress_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/queries.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/queries_test.py +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/dashboard.test.js +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/favicon.js +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/files.html +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/files.spec.js +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/logs.html +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/logs.test.js +0 -0
- {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/mancha.js +0 -0
|
@@ -6,6 +6,9 @@ on:
|
|
|
6
6
|
pull_request:
|
|
7
7
|
branches: [main]
|
|
8
8
|
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
9
12
|
jobs:
|
|
10
13
|
lint-and-test:
|
|
11
14
|
runs-on: ubuntu-latest
|
|
@@ -28,7 +31,7 @@ jobs:
|
|
|
28
31
|
|
|
29
32
|
# Before the Python tests: they invoke readability, which runs this.
|
|
30
33
|
- name: Install Node.js dependencies
|
|
31
|
-
run: npm
|
|
34
|
+
run: npm ci
|
|
32
35
|
|
|
33
36
|
# Runs ruff and ruff format as before, and adds the type and prose
|
|
34
37
|
# checks the separate steps never covered.
|
|
@@ -46,7 +49,7 @@ jobs:
|
|
|
46
49
|
|
|
47
50
|
- name: Run Web tests
|
|
48
51
|
run: |
|
|
49
|
-
npx playwright install --with-deps
|
|
52
|
+
npx playwright install --with-deps chromium
|
|
50
53
|
npm test
|
|
51
54
|
|
|
52
55
|
- name: Build container image
|
|
@@ -28,7 +28,7 @@ RUN uv sync --no-dev --group evals --frozen
|
|
|
28
28
|
# the web assets. The browsers are not, and they are more than half the
|
|
29
29
|
# image, so anything that will never drive one builds without them.
|
|
30
30
|
ARG INSTALL_BROWSERS=true
|
|
31
|
-
RUN npm
|
|
31
|
+
RUN npm ci \
|
|
32
32
|
&& if [ "$INSTALL_BROWSERS" = "true" ]; then \
|
|
33
33
|
npx playwright install --with-deps; \
|
|
34
34
|
fi
|
|
@@ -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
|
|
@@ -14,7 +14,7 @@ Requires-Dist: click>=8.0.0
|
|
|
14
14
|
Requires-Dist: fastapi>=0.124.4
|
|
15
15
|
Requires-Dist: pydantic>=2.12.5
|
|
16
16
|
Requires-Dist: pyyaml>=6.0.3
|
|
17
|
-
Requires-Dist: readability-cli>=0.10.
|
|
17
|
+
Requires-Dist: readability-cli>=0.10.6
|
|
18
18
|
Requires-Dist: uvicorn>=0.33.0
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
@@ -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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lemming-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "An autonomous, iterative task runner for AI coding agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
"fastapi>=0.124.4",
|
|
20
20
|
"pydantic>=2.12.5",
|
|
21
21
|
"pyyaml>=6.0.3",
|
|
22
|
-
"readability-cli>=0.10.
|
|
22
|
+
"readability-cli>=0.10.6",
|
|
23
23
|
"uvicorn>=0.33.0",
|
|
24
24
|
]
|
|
25
25
|
|
|
@@ -23,6 +23,13 @@ def _token_matches(candidate: str | None, share_token: str) -> bool:
|
|
|
23
23
|
return secrets.compare_digest(candidate.encode(), share_token.encode())
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _with_security_headers(response: fastapi.Response) -> fastapi.Response:
|
|
27
|
+
"""Attaches baseline security headers to an outgoing response."""
|
|
28
|
+
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
|
29
|
+
response.headers.setdefault("Referrer-Policy", "no-referrer")
|
|
30
|
+
return response
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
async def share_token_middleware(request: fastapi.Request, call_next):
|
|
27
34
|
"""Require a share token for every request when one is configured.
|
|
28
35
|
|
|
@@ -40,18 +47,24 @@ async def share_token_middleware(request: fastapi.Request, call_next):
|
|
|
40
47
|
"""
|
|
41
48
|
share_token = getattr(request.app.state, "share_token", None)
|
|
42
49
|
if not share_token:
|
|
43
|
-
return await call_next(request)
|
|
50
|
+
return _with_security_headers(await call_next(request))
|
|
44
51
|
|
|
45
52
|
token = request.query_params.get("token")
|
|
46
53
|
if _token_matches(token, share_token):
|
|
47
54
|
response = await call_next(request)
|
|
48
55
|
response.set_cookie(
|
|
49
|
-
key="lemming_share_token",
|
|
56
|
+
key="lemming_share_token",
|
|
57
|
+
value=token,
|
|
58
|
+
httponly=True,
|
|
59
|
+
samesite="strict",
|
|
60
|
+
secure=request.url.scheme == "https",
|
|
50
61
|
)
|
|
51
|
-
return response
|
|
62
|
+
return _with_security_headers(response)
|
|
52
63
|
|
|
53
64
|
cookie_token = request.cookies.get("lemming_share_token")
|
|
54
65
|
if _token_matches(cookie_token, share_token):
|
|
55
|
-
return await call_next(request)
|
|
66
|
+
return _with_security_headers(await call_next(request))
|
|
56
67
|
|
|
57
|
-
return
|
|
68
|
+
return _with_security_headers(
|
|
69
|
+
fastapi.Response("Unauthorized", status_code=401)
|
|
70
|
+
)
|
|
@@ -20,9 +20,17 @@ def test_share_token_middleware():
|
|
|
20
20
|
# Valid token via query
|
|
21
21
|
response = client.get("/api/data?token=secret123")
|
|
22
22
|
assert response.status_code == 200
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
set_cookie = response.headers.get("set-cookie", "")
|
|
24
|
+
assert "lemming_share_token=secret123" in set_cookie
|
|
25
|
+
assert "samesite=strict" in set_cookie.lower()
|
|
26
|
+
assert response.headers.get("x-content-type-options") == "nosniff"
|
|
27
|
+
assert response.headers.get("referrer-policy") == "no-referrer"
|
|
28
|
+
|
|
29
|
+
https_client = fastapi.testclient.TestClient(
|
|
30
|
+
main.app, base_url="https://testserver"
|
|
25
31
|
)
|
|
32
|
+
https_resp = https_client.get("/api/data?token=secret123")
|
|
33
|
+
assert "secure" in https_resp.headers.get("set-cookie", "").lower()
|
|
26
34
|
|
|
27
35
|
# Valid token via cookie
|
|
28
36
|
client.cookies.set("lemming_share_token", "secret123")
|
|
@@ -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
|
|
|
@@ -51,6 +54,18 @@ class RunRequest(pydantic.BaseModel):
|
|
|
51
54
|
env: dict[str, str] | None = None
|
|
52
55
|
|
|
53
56
|
|
|
57
|
+
_BLOCKED_ENV_VARS = frozenset(
|
|
58
|
+
{
|
|
59
|
+
"LD_PRELOAD",
|
|
60
|
+
"LD_LIBRARY_PATH",
|
|
61
|
+
"DYLD_INSERT_LIBRARIES",
|
|
62
|
+
"PYTHONSTARTUP",
|
|
63
|
+
"PYTHONPATH",
|
|
64
|
+
"NODE_OPTIONS",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
54
69
|
@router.post("/api/run")
|
|
55
70
|
def run_loop(
|
|
56
71
|
request: fastapi.Request,
|
|
@@ -58,6 +73,15 @@ def run_loop(
|
|
|
58
73
|
project: str | None = None,
|
|
59
74
|
):
|
|
60
75
|
"""Start the orchestrator loop as a detached background process."""
|
|
76
|
+
if run_request.env:
|
|
77
|
+
blocked = sorted(
|
|
78
|
+
key for key in run_request.env if key.upper() in _BLOCKED_ENV_VARS
|
|
79
|
+
)
|
|
80
|
+
if blocked:
|
|
81
|
+
raise fastapi.HTTPException(
|
|
82
|
+
400, f"Forbidden environment variable(s): {', '.join(blocked)}"
|
|
83
|
+
)
|
|
84
|
+
|
|
61
85
|
tasks_file = context.resolve_tasks_file(request.app.state, project)
|
|
62
86
|
project_dir = context.resolve_project_dir(request.app.state, project)
|
|
63
87
|
|
|
@@ -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
|
|
@@ -82,3 +102,18 @@ def test_run_loop_with_project_cwd(test_workspace, client):
|
|
|
82
102
|
# Verify Popen was called with the correct cwd
|
|
83
103
|
_, kwargs = mock_popen.call_args
|
|
84
104
|
assert str(kwargs["cwd"]) == str(subproject_dir)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_run_loop_blocks_dangerous_env_vars(client, test_tasks):
|
|
108
|
+
for var in (
|
|
109
|
+
"LD_PRELOAD",
|
|
110
|
+
"LD_LIBRARY_PATH",
|
|
111
|
+
"DYLD_INSERT_LIBRARIES",
|
|
112
|
+
"PYTHONSTARTUP",
|
|
113
|
+
"PYTHONPATH",
|
|
114
|
+
"NODE_OPTIONS",
|
|
115
|
+
):
|
|
116
|
+
with patch("subprocess.Popen") as mock_popen:
|
|
117
|
+
response = client.post("/api/run", json={"env": {var: "evil"}})
|
|
118
|
+
assert response.status_code == 400, var
|
|
119
|
+
mock_popen.assert_not_called()
|
|
@@ -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:
|