lemming-cli 0.8.0__tar.gz → 0.8.1__tar.gz

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