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.
Files changed (193) hide show
  1. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.github/workflows/ci.yml +5 -2
  2. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.github/workflows/screenshots.yml +1 -1
  3. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/Dockerfile +1 -1
  4. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/PKG-INFO +25 -23
  5. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/README.md +23 -21
  6. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/SKILL.md +8 -8
  7. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docker-compose.yml +1 -1
  8. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/EVALS.md +3 -0
  9. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/HOOKS.md +27 -17
  10. lemming_cli-0.8.0/docs/screenshots/dashboard-desktop.png +0 -0
  11. lemming_cli-0.8.0/docs/screenshots/dashboard-mobile.png +0 -0
  12. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/pyproject.toml +2 -2
  13. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/auth.py +18 -5
  14. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/auth_test.py +10 -2
  15. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/config.py +26 -2
  16. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/config_test.py +35 -0
  17. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/hooks.py +4 -2
  18. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/hooks_test.py +13 -33
  19. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/tasks.py +18 -4
  20. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/tasks_test.py +18 -0
  21. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/config.py +2 -2
  22. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/config_test.py +2 -1
  23. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/exec_cli.py +8 -37
  24. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/exec_cli_test.py +2 -2
  25. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/hooks.py +20 -19
  26. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/hooks_test.py +26 -48
  27. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/operations.py +10 -3
  28. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/operations_test.py +17 -0
  29. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/tasks.py +34 -2
  30. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/tasks_test.py +29 -0
  31. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/readability.py +23 -2
  32. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/readability_test.py +47 -0
  33. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/roadmap.py +96 -1
  34. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/roadmap_test.py +35 -1
  35. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/hooks.py +110 -95
  36. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/hooks_test.py +129 -76
  37. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/models.py +4 -0
  38. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/orchestrator.py +21 -17
  39. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/orchestrator_test.py +41 -4
  40. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/paths.py +31 -8
  41. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/paths_test.py +34 -6
  42. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/50-readability.md +21 -13
  43. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/70-ux.md +1 -1
  44. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/90-roadmap.md +12 -5
  45. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/taskrunner.md +17 -6
  46. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/runner.py +25 -9
  47. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/runner_test.py +46 -0
  48. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/scope.py +4 -0
  49. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/scope_test.py +8 -0
  50. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/operations.py +13 -0
  51. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/operations_test.py +27 -0
  52. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/dashboard.spec.js +1 -0
  53. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/favicon.spec.js +1 -0
  54. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/index.html +1 -1
  55. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/index.js +2 -2
  56. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/screenshots.spec.js +3 -0
  57. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/uv.lock +6 -6
  58. lemming_cli-0.7.1/docs/screenshots/dashboard-desktop.png +0 -0
  59. lemming_cli-0.7.1/docs/screenshots/dashboard-mobile.png +0 -0
  60. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.dockerignore +0 -0
  61. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.github/workflows/publish.yml +0 -0
  62. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.gitignore +0 -0
  63. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.prettierignore +0 -0
  64. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/.prettierrc +0 -0
  65. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/LICENSE +0 -0
  66. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/biome.json +0 -0
  67. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/WORKSPACE.md +0 -0
  68. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/screenshots/task-log-desktop.png +0 -0
  69. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/docs/screenshots/task-log-mobile.png +0 -0
  70. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/package-lock.json +0 -0
  71. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/package.json +0 -0
  72. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/playwright.config.js +0 -0
  73. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/__init__.py +0 -0
  74. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/__init__.py +0 -0
  75. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/conftest.py +0 -0
  76. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/context.py +0 -0
  77. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/context_test.py +0 -0
  78. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/directories.py +0 -0
  79. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/directories_test.py +0 -0
  80. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/files.py +0 -0
  81. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/files_test.py +0 -0
  82. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/logging.py +0 -0
  83. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/logging_test.py +0 -0
  84. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/loop.py +0 -0
  85. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/loop_test.py +0 -0
  86. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/main.py +0 -0
  87. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/api/main_test.py +0 -0
  88. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/artifact_test.py +0 -0
  89. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/brief_test.py +0 -0
  90. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/__init__.py +0 -0
  91. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/artifact.py +0 -0
  92. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/brief.py +0 -0
  93. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/goal.py +0 -0
  94. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/goal_test.py +0 -0
  95. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/machine_output_test.py +0 -0
  96. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/main.py +0 -0
  97. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/main_test.py +0 -0
  98. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/progress.py +0 -0
  99. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/progress_test.py +0 -0
  100. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/readability_cli.py +0 -0
  101. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/readability_cli_test.py +0 -0
  102. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/skill.py +0 -0
  103. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/skill_test.py +0 -0
  104. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/cli/stop_test.py +0 -0
  105. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/conftest.py +0 -0
  106. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/__init__.py +0 -0
  107. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/__main__.py +0 -0
  108. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/cli.py +0 -0
  109. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/cli_test.py +0 -0
  110. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/container.py +0 -0
  111. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/container_test.py +0 -0
  112. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/fixtures.py +0 -0
  113. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/fixtures_test.py +0 -0
  114. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/harness.py +0 -0
  115. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/harness_test.py +0 -0
  116. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/metrics.py +0 -0
  117. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/metrics_test.py +0 -0
  118. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/README.md +0 -0
  119. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/__init__.py +0 -0
  120. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/legacy.py +0 -0
  121. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/ops.py +0 -0
  122. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/base/calc/ops_test.py +0 -0
  123. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/clean/calc/ops.py +0 -0
  124. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/clean/calc/ops_test.py +0 -0
  125. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/dead-code/calc/ops.py +0 -0
  126. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/dead-code/calc/ops_test.py +0 -0
  127. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops.py +0 -0
  128. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/duplicated-behavior/calc/ops_test.py +0 -0
  129. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/false-reuse/calc/limits.py +0 -0
  130. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/false-reuse/calc/limits_test.py +0 -0
  131. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/lint-debt/calc/ops.py +0 -0
  132. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/readability/lint-debt/calc/ops_test.py +0 -0
  133. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/README.md +0 -0
  134. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/__init__.py +0 -0
  135. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/ops.py +0 -0
  136. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/add-only/calc/ops_test.py +0 -0
  137. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/README.md +0 -0
  138. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/__init__.py +0 -0
  139. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops.py +0 -0
  140. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/buggy-subtract/calc/ops_test.py +0 -0
  141. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/README.md +0 -0
  142. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/__init__.py +0 -0
  143. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/ops.py +0 -0
  144. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/full-ops/calc/ops_test.py +0 -0
  145. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/README.md +0 -0
  146. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/__init__.py +0 -0
  147. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli.py +0 -0
  148. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/cli_test.py +0 -0
  149. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops.py +0 -0
  150. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/roadmap/unwired-cli/calc/ops_test.py +0 -0
  151. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/hidden/stats/summary_hidden_test.py +0 -0
  152. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/README.md +0 -0
  153. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/__init__.py +0 -0
  154. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/summary.py +0 -0
  155. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/projects/task/summary/stats/summary_test.py +0 -0
  156. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/report.py +0 -0
  157. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/report_test.py +0 -0
  158. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/scenarios.py +0 -0
  159. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/scenarios_test.py +0 -0
  160. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/suites.py +0 -0
  161. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/task.py +0 -0
  162. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/task_test.py +0 -0
  163. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/trial.py +0 -0
  164. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/evals/trial_test.py +0 -0
  165. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/integration_test.py +0 -0
  166. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/main.py +0 -0
  167. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/main_test.py +0 -0
  168. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/models_test.py +0 -0
  169. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/persistence.py +0 -0
  170. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/persistence_test.py +0 -0
  171. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts/hooks/60-testing.md +0 -0
  172. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts.py +0 -0
  173. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/prompts_test.py +0 -0
  174. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/providers.py +0 -0
  175. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/providers_test.py +0 -0
  176. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/shutdown.py +0 -0
  177. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/shutdown_integration_test.py +0 -0
  178. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/shutdown_test.py +0 -0
  179. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/__init__.py +0 -0
  180. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/lifecycle.py +0 -0
  181. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/lifecycle_test.py +0 -0
  182. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/limits.py +0 -0
  183. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/progress.py +0 -0
  184. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/progress_test.py +0 -0
  185. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/queries.py +0 -0
  186. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/tasks/queries_test.py +0 -0
  187. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/dashboard.test.js +0 -0
  188. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/favicon.js +0 -0
  189. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/files.html +0 -0
  190. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/files.spec.js +0 -0
  191. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/logs.html +0 -0
  192. {lemming_cli-0.7.1 → lemming_cli-0.8.0}/src/lemming/web/logs.test.js +0 -0
  193. {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 install
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
@@ -29,7 +29,7 @@ jobs:
29
29
  node-version: '20'
30
30
 
31
31
  - name: Install Node.js dependencies
32
- run: npm install
32
+ run: npm ci
33
33
 
34
34
  - name: Install Playwright browser
35
35
  run: npx playwright install --with-deps chromium
@@ -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 install \
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.7.1
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.4
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 all
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 every hook it discovers on the filesystem (including the built-in
269
- `roadmap` hook). Hooks are plain Markdown files, and udev-style filename
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
- - **Disabling**: An empty file masks (disables) the hook of the same name from a
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
- # Disable a hook for this project (writes an empty .lemming/hooks/50-lint.md)
284
- lemming hooks disable lint
283
+ # Run the testing review after every task in this project
284
+ lemming hooks enable testing
285
285
 
286
- # Re-enable it (removes the mask file)
287
- lemming hooks enable lint
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, with source and status
332
+ # List all hooks in execution order, marking the ones enabled by default
333
333
  lemming hooks list
334
334
 
335
- # View current project configuration (includes the active hooks)
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 `--oneshot`.
384
- - **`edit <id>`**: Modify a task's description, runner, model, position, or
385
- oneshot flag (`--oneshot/--no-oneshot`).
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 active hooks.
414
- - `install`: Install built-in hooks to the global directory.
415
- - `enable <name>...`: Activate one or more hooks.
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 all
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 every hook it discovers on the filesystem (including the built-in
249
- `roadmap` hook). Hooks are plain Markdown files, and udev-style filename
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
- - **Disabling**: An empty file masks (disables) the hook of the same name from a
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
- # Disable a hook for this project (writes an empty .lemming/hooks/50-lint.md)
264
- lemming hooks disable lint
263
+ # Run the testing review after every task in this project
264
+ lemming hooks enable testing
265
265
 
266
- # Re-enable it (removes the mask file)
267
- lemming hooks enable lint
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, with source and status
312
+ # List all hooks in execution order, marking the ones enabled by default
313
313
  lemming hooks list
314
314
 
315
- # View current project configuration (includes the active hooks)
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 `--oneshot`.
364
- - **`edit <id>`**: Modify a task's description, runner, model, position, or
365
- oneshot flag (`--oneshot/--no-oneshot`).
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 active hooks.
394
- - `install`: Install built-in hooks to the global directory.
395
- - `enable <name>...`: Activate one or more hooks.
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 all --scope src/api/
128
- lemming exec "Add pagination" --review all # do the work, then gate it
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, review hooks run
166
- and a roadmap hook may revise the queue; each hook is a full agent run, so a
167
- typo fix, rename, version bump, or cleanup without `--oneshot` takes several
168
- times longer than the work itself. Reserve hooked tasks for changes in
169
- behavior. `lemming hooks list` shows the hooks; `lemming hooks disable <name>`
170
- turns one off for the project.
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
 
@@ -2,7 +2,7 @@ services:
2
2
  lemming:
3
3
  build: .
4
4
  ports:
5
- - '${LEMMING_PORT:-8999}:${LEMMING_PORT:-8999}'
5
+ - '127.0.0.1:${LEMMING_PORT:-8999}:${LEMMING_PORT:-8999}'
6
6
  command: ['--port', '${LEMMING_PORT:-8999}']
7
7
  volumes:
8
8
  - lemming-data:/data
@@ -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 disables the hook of the same name from a
40
- lower-precedence layer (see below). For example, an empty
41
- `.lemming/hooks/readability.md` disables the built-in `readability` hook for
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
- Lemming runs every hook it discovers that is not masked. There is no hook
179
- configuration in `tasks.yml`; the filesystem is the single source of truth.
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
- # Disable a hook for this project (writes an empty .lemming/hooks/50-lint.md)
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
- # Re-enable it (removes the mask file)
188
- lemming hooks enable lint
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
- These commands validate the hook names before changing anything and refuse to
192
- touch files with content (a non-empty file is an override, not a mask). An empty
193
- file created by hand works just as well; the commands additionally keep the
194
- hook's priority in the mask filename so listings stay accurate.
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
- Hooks are re-discovered on every task execution, so changes are picked up
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 (disabled, runs on failure) using the CLI:
240
+ status (enabled for the project, masked, runs on failure) using the CLI:
231
241
 
232
242
  ```bash
233
243
  lemming hooks list
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lemming-cli"
7
- version = "0.7.1"
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.4",
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", value=token, httponly=True
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 fastapi.Response("Unauthorized", status_code=401)
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
- assert "lemming_share_token=secret123" in response.headers.get(
24
- "set-cookie", ""
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
- """Replace the roadmap configuration and return the saved value."""
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
- data.config = config
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
- """Enable or disable a hook by removing or creating its project mask."""
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: