lemming-cli 0.4.0__tar.gz → 0.4.2__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 (147) hide show
  1. lemming_cli-0.4.2/.dockerignore +30 -0
  2. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/.github/workflows/ci.yml +1 -1
  3. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/Dockerfile +8 -2
  4. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/PKG-INFO +18 -8
  5. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/README.md +17 -7
  6. lemming_cli-0.4.2/SKILL.md +120 -0
  7. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/pyproject.toml +1 -1
  8. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/exec_cli.py +6 -0
  9. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/exec_cli_test.py +18 -0
  10. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/cli.py +4 -0
  11. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/container.py +35 -1
  12. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/container_test.py +31 -0
  13. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/runner.py +14 -3
  14. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/runner_test.py +4 -2
  15. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/uv.lock +1 -1
  16. lemming_cli-0.4.0/SKILL.md +0 -86
  17. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/.github/workflows/publish.yml +0 -0
  18. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/.github/workflows/screenshots.yml +0 -0
  19. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/.gitignore +0 -0
  20. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/.prettierignore +0 -0
  21. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/.prettierrc +0 -0
  22. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/LICENSE +0 -0
  23. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/biome.json +0 -0
  24. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/docker-compose.yml +0 -0
  25. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/docs/EVALS.md +0 -0
  26. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/docs/HOOKS.md +0 -0
  27. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/docs/screenshots/dashboard-desktop.png +0 -0
  28. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/docs/screenshots/dashboard-mobile.png +0 -0
  29. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/docs/screenshots/task-log-desktop.png +0 -0
  30. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/docs/screenshots/task-log-mobile.png +0 -0
  31. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/package-lock.json +0 -0
  32. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/package.json +0 -0
  33. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/playwright.config.js +0 -0
  34. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/__init__.py +0 -0
  35. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/__init__.py +0 -0
  36. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/auth.py +0 -0
  37. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/auth_test.py +0 -0
  38. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/config.py +0 -0
  39. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/config_test.py +0 -0
  40. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/conftest.py +0 -0
  41. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/context.py +0 -0
  42. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/context_test.py +0 -0
  43. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/directories.py +0 -0
  44. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/directories_test.py +0 -0
  45. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/files.py +0 -0
  46. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/files_test.py +0 -0
  47. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/hooks.py +0 -0
  48. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/hooks_test.py +0 -0
  49. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/logging.py +0 -0
  50. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/logging_test.py +0 -0
  51. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/loop.py +0 -0
  52. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/loop_test.py +0 -0
  53. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/main.py +0 -0
  54. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/main_test.py +0 -0
  55. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/tasks.py +0 -0
  56. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api/tasks_test.py +0 -0
  57. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/api_test.py +0 -0
  58. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/brief_test.py +0 -0
  59. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/__init__.py +0 -0
  60. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/brief.py +0 -0
  61. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/config.py +0 -0
  62. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/config_test.py +0 -0
  63. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/goal.py +0 -0
  64. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/goal_test.py +0 -0
  65. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/hooks.py +0 -0
  66. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/hooks_test.py +0 -0
  67. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/machine_output_test.py +0 -0
  68. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/main.py +0 -0
  69. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/main_test.py +0 -0
  70. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/operations.py +0 -0
  71. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/operations_test.py +0 -0
  72. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/progress.py +0 -0
  73. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/progress_test.py +0 -0
  74. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/readability_cli.py +0 -0
  75. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/readability_cli_test.py +0 -0
  76. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/skill.py +0 -0
  77. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/skill_test.py +0 -0
  78. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/stop_test.py +0 -0
  79. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/tasks.py +0 -0
  80. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli/tasks_test.py +0 -0
  81. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/cli_test.py +0 -0
  82. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/conftest.py +0 -0
  83. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/__init__.py +0 -0
  84. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/__main__.py +0 -0
  85. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/cli_test.py +0 -0
  86. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/fixtures.py +0 -0
  87. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/fixtures_test.py +0 -0
  88. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/harness.py +0 -0
  89. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/harness_test.py +0 -0
  90. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/readability.py +0 -0
  91. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/readability_test.py +0 -0
  92. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/roadmap.py +0 -0
  93. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/roadmap_test.py +0 -0
  94. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/scenarios.py +0 -0
  95. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/scenarios_test.py +0 -0
  96. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/suites.py +0 -0
  97. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/trial.py +0 -0
  98. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/evals/trial_test.py +0 -0
  99. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/hooks.py +0 -0
  100. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/hooks_test.py +0 -0
  101. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/integration_test.py +0 -0
  102. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/main.py +0 -0
  103. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/main_test.py +0 -0
  104. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/models.py +0 -0
  105. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/models_test.py +0 -0
  106. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/orchestrator.py +0 -0
  107. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/orchestrator_test.py +0 -0
  108. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/paths.py +0 -0
  109. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/paths_test.py +0 -0
  110. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/persistence.py +0 -0
  111. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/persistence_test.py +0 -0
  112. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/prompts/hooks/50-readability.md +0 -0
  113. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/prompts/hooks/60-testing.md +0 -0
  114. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/prompts/hooks/70-ux.md +0 -0
  115. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/prompts/hooks/90-roadmap.md +0 -0
  116. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/prompts/taskrunner.md +0 -0
  117. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/prompts.py +0 -0
  118. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/prompts_test.py +0 -0
  119. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/providers.py +0 -0
  120. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/providers_test.py +0 -0
  121. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/scope.py +0 -0
  122. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/scope_test.py +0 -0
  123. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/shutdown.py +0 -0
  124. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/shutdown_integration_test.py +0 -0
  125. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/shutdown_test.py +0 -0
  126. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/__init__.py +0 -0
  127. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/lifecycle.py +0 -0
  128. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/lifecycle_test.py +0 -0
  129. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/limits.py +0 -0
  130. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/operations.py +0 -0
  131. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/operations_test.py +0 -0
  132. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/progress.py +0 -0
  133. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/progress_test.py +0 -0
  134. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/queries.py +0 -0
  135. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/tasks/queries_test.py +0 -0
  136. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/dashboard.spec.js +0 -0
  137. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/dashboard.test.js +0 -0
  138. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/favicon.js +0 -0
  139. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/favicon.spec.js +0 -0
  140. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/files.html +0 -0
  141. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/files.spec.js +0 -0
  142. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/index.html +0 -0
  143. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/index.js +0 -0
  144. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/logs.html +0 -0
  145. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/logs.test.js +0 -0
  146. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/mancha.js +0 -0
  147. {lemming_cli-0.4.0 → lemming_cli-0.4.2}/src/lemming/web/screenshots.spec.js +0 -0
@@ -0,0 +1,30 @@
1
+ # The image builds its own environments, so host-built ones are not just
2
+ # wasted bytes in the context — they are the wrong platform's binaries
3
+ # landing on top of what `uv sync` and `npm install` are about to create.
4
+ .venv
5
+ node_modules
6
+
7
+ # History and build products the image never reads.
8
+ .git
9
+ .github
10
+ dist
11
+ test-results
12
+ docs/screenshots
13
+
14
+ # Agent worktrees are whole checkouts of this repo, each with its own
15
+ # dependencies installed, and they dwarf the source they sit beside.
16
+ .claude
17
+
18
+ # Local state and caches.
19
+ .pytest_cache
20
+ .ruff_cache
21
+ __pycache__
22
+ *.pyc
23
+ .DS_Store
24
+
25
+ # Roadmaps and their locks belong to whoever is running lemming, not to
26
+ # the image; a stale one baked in would be read as that project's state.
27
+ tasks.yml
28
+ *.lock
29
+ !uv.lock
30
+ !package-lock.json
@@ -19,7 +19,7 @@ jobs:
19
19
  python-version: '3.14'
20
20
 
21
21
  - name: Install Python dependencies
22
- run: uv sync
22
+ run: uv sync --locked
23
23
 
24
24
  - name: Run Python linting
25
25
  run: uv run ruff check .
@@ -22,8 +22,14 @@ WORKDIR /opt/lemming
22
22
  COPY . .
23
23
  RUN uv sync --no-dev --frozen
24
24
 
25
- # Install frontend dependencies and Playwright browsers
26
- RUN npm install && npx playwright install --with-deps
25
+ # Frontend dependencies are always needed: postinstall vendors mancha into
26
+ # the web assets. The browsers are not, and they are more than half the
27
+ # image, so anything that will never drive one builds without them.
28
+ ARG INSTALL_BROWSERS=true
29
+ RUN npm install \
30
+ && if [ "$INSTALL_BROWSERS" = "true" ]; then \
31
+ npx playwright install --with-deps; \
32
+ fi
27
33
 
28
34
  # Pre-create the lemming home directory so users can bind-mount a .env file into it
29
35
  RUN mkdir -p /root/.local/lemming
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lemming-cli
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -140,7 +140,10 @@ removed unless it failed — in which case it is kept, and its path printed, so
140
140
  the log can be read. Kept directories live in `~/.local/lemming/exec-*` and
141
141
  are retired automatically a week later, so a recent failure is always still
142
142
  there to inspect. Stdout carries the agent's message alone and everything
143
- else goes to stderr, so the output can be consumed directly.
143
+ else goes to stderr, so the output can be consumed directly. Stderr reports
144
+ the ephemeral tasks file and task ID at startup; pass those to `lemming
145
+ --tasks-file <path> status <id>` or `logs <id>` to inspect an active run. Use
146
+ the global verbose flag (`lemming -v exec ...`) to stream runner activity.
144
147
 
145
148
  Note that the agent runs unattended (`--yolo` by default), so it does not
146
149
  inherit the permission prompts of whatever launched it.
@@ -395,8 +398,9 @@ These come before the subcommand and apply to all of them.
395
398
  per-task `--runner`/`--model` overrides anything passed here.
396
399
  - **`exec [<description>]`**: Run one task, or one set of reviews, outside any
397
400
  roadmap. Prints the agent's closing message to stdout and everything else to
398
- stderr. Exits non-zero if the task did not complete. See
399
- [One-off tasks](#one-off-tasks-without-a-roadmap).
401
+ stderr, including its tasks file and task ID at startup. Use `lemming -v
402
+ exec ...` to stream runner activity. Exits non-zero if the task did not
403
+ complete. See [One-off tasks](#one-off-tasks-without-a-roadmap).
400
404
  - `-f/--file`: Read the description from a file, or `-` for stdin. Unlike
401
405
  `add`, there is no length cap.
402
406
  - `--review <names>`: Reviews to run after the task, comma-separated or
@@ -533,10 +537,16 @@ pushing a `v*` tag triggers the `publish.yml` GitHub Actions workflow, which
533
537
  builds the package with `uv build` and uploads it.
534
538
 
535
539
  ```bash
536
- # 1. Bump the version in pyproject.toml, commit, and push
537
- # 2. Tag the release and push the tag
538
- git tag v0.1.1
539
- git push origin v0.1.1
540
+ # 1. Bump pyproject.toml and uv.lock together, commit, and push
541
+ uv version --bump patch --no-sync
542
+ version="$(uv version --short)"
543
+ git add pyproject.toml uv.lock
544
+ git commit -m "Bump version to $version"
545
+ git push origin main
546
+
547
+ # 2. After CI passes, tag the release and push the tag
548
+ git tag "v$version"
549
+ git push origin "v$version"
540
550
  ```
541
551
 
542
552
  ---
@@ -118,7 +118,10 @@ removed unless it failed — in which case it is kept, and its path printed, so
118
118
  the log can be read. Kept directories live in `~/.local/lemming/exec-*` and
119
119
  are retired automatically a week later, so a recent failure is always still
120
120
  there to inspect. Stdout carries the agent's message alone and everything
121
- else goes to stderr, so the output can be consumed directly.
121
+ else goes to stderr, so the output can be consumed directly. Stderr reports
122
+ the ephemeral tasks file and task ID at startup; pass those to `lemming
123
+ --tasks-file <path> status <id>` or `logs <id>` to inspect an active run. Use
124
+ the global verbose flag (`lemming -v exec ...`) to stream runner activity.
122
125
 
123
126
  Note that the agent runs unattended (`--yolo` by default), so it does not
124
127
  inherit the permission prompts of whatever launched it.
@@ -373,8 +376,9 @@ These come before the subcommand and apply to all of them.
373
376
  per-task `--runner`/`--model` overrides anything passed here.
374
377
  - **`exec [<description>]`**: Run one task, or one set of reviews, outside any
375
378
  roadmap. Prints the agent's closing message to stdout and everything else to
376
- stderr. Exits non-zero if the task did not complete. See
377
- [One-off tasks](#one-off-tasks-without-a-roadmap).
379
+ stderr, including its tasks file and task ID at startup. Use `lemming -v
380
+ exec ...` to stream runner activity. Exits non-zero if the task did not
381
+ complete. See [One-off tasks](#one-off-tasks-without-a-roadmap).
378
382
  - `-f/--file`: Read the description from a file, or `-` for stdin. Unlike
379
383
  `add`, there is no length cap.
380
384
  - `--review <names>`: Reviews to run after the task, comma-separated or
@@ -511,10 +515,16 @@ pushing a `v*` tag triggers the `publish.yml` GitHub Actions workflow, which
511
515
  builds the package with `uv build` and uploads it.
512
516
 
513
517
  ```bash
514
- # 1. Bump the version in pyproject.toml, commit, and push
515
- # 2. Tag the release and push the tag
516
- git tag v0.1.1
517
- git push origin v0.1.1
518
+ # 1. Bump pyproject.toml and uv.lock together, commit, and push
519
+ uv version --bump patch --no-sync
520
+ version="$(uv version --short)"
521
+ git add pyproject.toml uv.lock
522
+ git commit -m "Bump version to $version"
523
+ git push origin main
524
+
525
+ # 2. After CI passes, tag the release and push the tag
526
+ git tag "v$version"
527
+ git push origin "v$version"
518
528
  ```
519
529
 
520
530
  ---
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: lemming
3
+ description: One interface over the AI coding agent CLIs (agy, claude, codex). Delegate a single task to another agent to spare a quota or play to its strengths; run a readability, testing, or UX review over changed files, a branch, or a path; or drive a multi-task project through a roadmap that survives context limits. Use whenever work should run under a different agent than the current one, when reviewing a diff before opening a pull request, or when a job is too large for one agent run.
4
+ license: MIT
5
+ ---
6
+
7
+ # lemming
8
+
9
+ One interface over several agent CLIs. You do not need to know how `agy`,
10
+ `claude`, or `codex` spell their flags — only `lemming`.
11
+
12
+ Use `exec` for one unit of work, and a roadmap for anything larger than a
13
+ single agent run can hold.
14
+
15
+ ## Delegate one task
16
+
17
+ ```sh
18
+ lemming -v exec "Fix the flaky heartbeat test in src/runner_test.py" --runner codex
19
+
20
+ # Pipe a longer handoff rather than fighting shell quoting; no length limit
21
+ cat handoff.md | lemming -v exec -f - --runner agy
22
+ ```
23
+
24
+ The agent's closing message comes back on **stdout**, everything else on
25
+ stderr, and the exit code is 0 only if the task completed. Read that message
26
+ instead of hunting through logs.
27
+
28
+ When you launch `exec` as a subprocess, use the global `-v` flag (before
29
+ `exec`) and continue monitoring its output until it exits. Verbose mode streams
30
+ the runner's activity on stderr. At startup, stderr also reports the exact
31
+ `Tasks file` and `Task ID`; use them from another process to inspect the
32
+ agent's durable findings or full log without guessing which `exec-*` directory
33
+ belongs to the run:
34
+
35
+ ```sh
36
+ lemming --tasks-file <reported-path> status <reported-task-id>
37
+ lemming --tasks-file <reported-path> logs <reported-task-id>
38
+ ```
39
+
40
+ The task runner is required to record its approach first and concise findings
41
+ as it works. These entries are checkpoints rather than a continuous trace, so
42
+ use verbose output or the log to tell whether it is actively making progress.
43
+
44
+ The task starts with an empty context and sees nothing of your conversation,
45
+ so give it everything it needs.
46
+
47
+ ## Run a review
48
+
49
+ With no description there is nothing for a task runner to do, so only the
50
+ reviews run — against work that already exists.
51
+
52
+ ```sh
53
+ lemming exec --review readability # uncommitted work
54
+ lemming exec --review testing --scope main...HEAD
55
+ lemming exec --review all --scope src/api/
56
+ lemming exec "Add pagination" --review all # do the work, then gate it
57
+
58
+ # Someone else's branch, in a worktree so your own tree is untouched
59
+ git worktree add /tmp/pr-123 && (cd /tmp/pr-123 && gh pr checkout 123)
60
+ lemming -C /tmp/pr-123 exec --review testing --scope main...HEAD
61
+ ```
62
+
63
+ `--scope` takes paths, which pass through untouched, or a git revision range,
64
+ which is resolved to the files it changed. It defaults to uncommitted work,
65
+ and a clean tree stops the run rather than reviewing everything.
66
+
67
+ Reviews **edit the workspace** — readability applies fixes and reruns checks.
68
+ That is the point, but it means a review is not read-only.
69
+
70
+ ## Drive a roadmap
71
+
72
+ For work that outlasts one agent run — a migration, a multi-step feature —
73
+ break it into tasks. Each runs with a fresh context seeded only by the goal
74
+ and recorded progress, so the project survives context limits.
75
+
76
+ ```sh
77
+ lemming goal "Add offline support with a service worker and a sync queue"
78
+ lemming add "Register the service worker and cache the app shell"
79
+ lemming add "Queue writes in IndexedDB while offline"
80
+ lemming run # runs until the queue drains
81
+ ```
82
+
83
+ Check on it, and read what an agent actually did:
84
+
85
+ ```sh
86
+ lemming status # queue, attempts, and recorded progress
87
+ lemming status <id> # one task in detail, including its runner command
88
+ lemming logs # the active task's log, or logs <id> for one task
89
+ ```
90
+
91
+ Adjust it while it runs — new tasks are picked up automatically:
92
+
93
+ ```sh
94
+ lemming add "Add a retry backoff to the sync queue" --index 0 # jump the queue
95
+ lemming stop --after-current-task # drain, then stop
96
+ ```
97
+
98
+ After each task, review hooks run automatically and a roadmap hook may revise
99
+ the queue. `lemming hooks list` shows them; `lemming hooks disable <name>`
100
+ turns one off for the project.
101
+
102
+ ## What to know before running any of it
103
+
104
+ - **The agent runs unattended.** `--yolo` is the default, so it does not
105
+ inherit the permission prompts of whatever launched it. Treat this as
106
+ granting an agent unsupervised write access to the working directory.
107
+ - **`exec` makes one attempt, no retry.** A failure is final. A roadmap task
108
+ retries (3 by default) with its progress carried forward.
109
+ - **Interrupting leaves partial edits.** Nothing is restored, so there is no
110
+ atomicity to rely on.
111
+ - **Concurrent runs in one checkout interleave their edits.** Give each
112
+ writing run its own worktree and address it with `-C`.
113
+ - **Every run reports its state.** The tasks file and task ID are printed on
114
+ stderr at startup. Successful state is removed on exit unless `--keep` is
115
+ used; failures keep their directory and print it again for recovery. Kept
116
+ failure state is retired automatically after a week.
117
+ - **`--tasks-file` and `-C` are group options.** They go before the
118
+ subcommand — `lemming -C ../other exec ...`, never `exec -C ../other`.
119
+
120
+ `lemming --help`, or `--help` on any subcommand, has the full flag list.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lemming-cli"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "An autonomous, iterative task runner for AI coding agents."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -324,6 +324,12 @@ def exec_command(
324
324
  else _create_review_task(exec_dir, reviews, config)
325
325
  )
326
326
 
327
+ # A supervising process needs an exact, race-free handle to the ephemeral
328
+ # roadmap while the run is active. Keep it on stderr so stdout remains the
329
+ # agent's closing-message interface.
330
+ click.echo(f"Tasks file: {tasks_file}", err=True)
331
+ click.echo(f"Task ID: {task_id}", err=True)
332
+
327
333
  shutdown.clear_drain()
328
334
  shutdown.install_handlers()
329
335
 
@@ -80,6 +80,24 @@ def test_exec_keeps_progress_chatter_off_stdout(workspace):
80
80
  assert "Attempt" in result.stderr
81
81
 
82
82
 
83
+ def test_exec_announces_monitoring_coordinates_on_stderr(workspace):
84
+ """A supervisor can inspect an active exec without guessing its state."""
85
+ seen = {}
86
+
87
+ def finish(cmd, tasks_file, task_id, *args, **kwargs):
88
+ seen["tasks_file"] = tasks_file
89
+ seen["task_id"] = task_id
90
+ return _finish("Done.")(cmd, tasks_file, task_id, *args, **kwargs)
91
+
92
+ with mock.patch("lemming.runner.run_with_heartbeat", finish):
93
+ result = CliRunner().invoke(cli, ["exec", "Fix the flaky test"])
94
+
95
+ assert result.exit_code == 0, result.stderr
96
+ assert result.stdout.strip() == "Done."
97
+ assert f"Tasks file: {seen['tasks_file']}" in result.stderr
98
+ assert f"Task ID: {seen['task_id']}" in result.stderr
99
+
100
+
83
101
  def test_exec_removes_its_state_directory_on_success(workspace):
84
102
  """A one-shot leaves nothing behind when it worked."""
85
103
  with mock.patch("lemming.runner.run_with_heartbeat", _finish("Done.")):
@@ -178,6 +178,10 @@ def run(
178
178
  )
179
179
  click.echo(f"Building eval image '{image}'...")
180
180
  container.build_image(context, image=image, docker=docker)
181
+ # The image is what the trials need; the cache that produced it is
182
+ # dead weight, and it outgrows the image several times over across
183
+ # repeated runs.
184
+ container.prune_build_cache(docker=docker)
181
185
 
182
186
  if run_dir is None:
183
187
  run_dir = pathlib.Path(tempfile.mkdtemp(prefix="lemming-evals-"))
@@ -41,7 +41,41 @@ def build_image(
41
41
  image: Tag for the built image.
42
42
  docker: Docker-compatible CLI binary to invoke.
43
43
  """
44
- subprocess.run([docker, "build", "--tag", image, str(context)], check=True)
44
+ # No scenario drives a browser, and they are more than half the image.
45
+ subprocess.run(
46
+ [
47
+ docker,
48
+ "build",
49
+ "--build-arg",
50
+ "INSTALL_BROWSERS=false",
51
+ "--tag",
52
+ image,
53
+ str(context),
54
+ ],
55
+ check=True,
56
+ )
57
+
58
+
59
+ def prune_build_cache(docker: str = "docker") -> None:
60
+ """Discards the build cache left behind by an eval image build.
61
+
62
+ Cache accumulates faster than images do — a run rebuilding this image a
63
+ few times leaves tens of gigabytes behind — and nothing reuses it once
64
+ the trials have finished.
65
+
66
+ Args:
67
+ docker: Docker-compatible CLI binary to invoke.
68
+ """
69
+ try:
70
+ subprocess.run(
71
+ [docker, "builder", "prune", "--force", "--all"],
72
+ check=False,
73
+ capture_output=True,
74
+ )
75
+ except OSError:
76
+ # This runs after the results are in; failing to tidy up must never
77
+ # be the reason a completed eval run reports an error.
78
+ pass
45
79
 
46
80
 
47
81
  def trial_command(
@@ -84,3 +84,34 @@ class TestRunTrial(unittest.TestCase):
84
84
 
85
85
  if __name__ == "__main__":
86
86
  unittest.main()
87
+
88
+
89
+ class BuildImageTest(unittest.TestCase):
90
+ """The build must not carry what the eval trials never use."""
91
+
92
+ @unittest.mock.patch("subprocess.run")
93
+ def test_builds_without_browsers(
94
+ self, run: unittest.mock.MagicMock
95
+ ) -> None:
96
+ """Browsers are over half the image and no trial drives one."""
97
+ container.build_image(pathlib.Path("/repo"))
98
+
99
+ argv = run.call_args[0][0]
100
+ self.assertIn("--build-arg", argv)
101
+ self.assertIn("INSTALL_BROWSERS=false", argv)
102
+
103
+ @unittest.mock.patch("subprocess.run")
104
+ def test_prunes_the_build_cache(self, run: unittest.mock.MagicMock) -> None:
105
+ """Repeated eval runs accumulate cache faster than images."""
106
+ container.prune_build_cache()
107
+
108
+ argv = run.call_args[0][0]
109
+ self.assertEqual(argv[:3], ["docker", "builder", "prune"])
110
+ self.assertIn("--force", argv)
111
+
112
+ @unittest.mock.patch("subprocess.run", side_effect=OSError("no docker"))
113
+ def test_pruning_never_fails_a_run(
114
+ self, run: unittest.mock.MagicMock
115
+ ) -> None:
116
+ """Cleanup runs after the results are in; it must not lose them."""
117
+ container.prune_build_cache()
@@ -29,6 +29,10 @@ KILL_GRACE_SECONDS = 5
29
29
  # inherit the pipe and hold it open indefinitely.
30
30
  OUTPUT_DRAIN_SECONDS = 5
31
31
 
32
+ # Headroom between the task time limit and a runner's own print-mode timeout,
33
+ # so the two never race to end the same run.
34
+ PRINT_TIMEOUT_GRACE_MINUTES = 5
35
+
32
36
 
33
37
  def _pretty_quote(s: str) -> str:
34
38
  """Quotes a string for shell execution, preferring readable quoting.
@@ -234,9 +238,16 @@ def build_runner_command(
234
238
  # payload, with none of claude's top-level "type".
235
239
  cmd.extend(["--output-format", "stream-json"])
236
240
 
237
- # agy caps print mode at 5 minutes by default; extend it to the
238
- # task time limit (or effectively unlimited when there is none).
239
- print_timeout = f"{time_limit}m" if time_limit > 0 else "24h"
241
+ # agy caps print mode at 5 minutes by default; extend it past the
242
+ # task time limit (or make it effectively unlimited when there is
243
+ # none). The headroom leaves Lemming's own kill to end an
244
+ # overrunning task, which records why it stopped; agy's expiry
245
+ # reports an empty response instead, losing the run's outcome.
246
+ print_timeout = (
247
+ f"{time_limit + PRINT_TIMEOUT_GRACE_MINUTES}m"
248
+ if time_limit > 0
249
+ else "24h"
250
+ )
240
251
  cmd.extend(["--print-timeout", print_timeout])
241
252
 
242
253
  prompt_arg = "--prompt"
@@ -48,11 +48,13 @@ def test_build_runner_command_agy_streams_json_events():
48
48
  assert "--log-file" not in cmd
49
49
 
50
50
 
51
- def test_build_runner_command_agy_print_timeout_matches_time_limit():
51
+ def test_build_runner_command_agy_print_timeout_clears_time_limit():
52
+ # The runner's own timeout must not race Lemming's kill: Lemming records
53
+ # why it stopped the task, whereas agy's expiry yields an empty response.
52
54
  cmd = runner.build_runner_command(
53
55
  "agy", "my prompt", yolo=True, time_limit=45
54
56
  )
55
- assert cmd[cmd.index("--print-timeout") + 1] == "45m"
57
+ assert cmd[cmd.index("--print-timeout") + 1] == "50m"
56
58
 
57
59
 
58
60
  def test_build_runner_command_agy_print_timeout_without_time_limit():
@@ -222,7 +222,7 @@ wheels = [
222
222
 
223
223
  [[package]]
224
224
  name = "lemming-cli"
225
- version = "0.3.4"
225
+ version = "0.4.2"
226
226
  source = { editable = "." }
227
227
  dependencies = [
228
228
  { name = "beautifulsoup4" },
@@ -1,86 +0,0 @@
1
- ---
2
- name: lemming
3
- description: Delegate a single coding task to another AI agent CLI (agy, claude, codex) through one interface, or run a code review — readability, testing, UX — over changed files, a PR branch, or a path. Use when handing work to a different agent to spare a quota or play to its strengths, or when reviewing a diff before opening a pull request. Not for driving a multi-task project; that is a roadmap.
4
- license: MIT
5
- ---
6
-
7
- # lemming
8
-
9
- One interface over several agent CLIs. You do not need to know how `agy`,
10
- `claude`, or `codex` spell their flags — only `lemming exec`.
11
-
12
- ## Delegate one task
13
-
14
- ```sh
15
- lemming exec "Fix the flaky heartbeat test in src/runner_test.py" --runner codex
16
- ```
17
-
18
- The agent's closing message comes back on **stdout**; progress and the event
19
- trace go to stderr. Exit code is 0 only if the task completed. That message is
20
- the return value — read it instead of hunting through logs.
21
-
22
- Pipe a longer handoff rather than fighting shell quoting. There is no length
23
- limit, unlike a roadmap task description:
24
-
25
- ```sh
26
- cat handoff.md | lemming exec -f - --runner agy
27
- ```
28
-
29
- Delegate when another agent's quota, price, or strengths suit the work better
30
- than yours. Give the task everything it needs: it starts with an empty context
31
- and sees nothing of your conversation.
32
-
33
- ## Run a review
34
-
35
- With no description there is nothing for a task runner to do, so only the
36
- reviews run — against work that already exists.
37
-
38
- ```sh
39
- lemming exec --review readability # uncommitted work
40
- lemming exec --review testing --scope main...HEAD
41
- lemming exec --review all --scope src/api/
42
- lemming exec "Add pagination" --review all # do the work, then gate it
43
- ```
44
-
45
- `--scope` takes paths, which pass through untouched, or a git revision range,
46
- which is resolved to the files it changed. It defaults to uncommitted work.
47
- A clean tree stops the run rather than reviewing everything.
48
-
49
- Reviews **edit the workspace**: readability applies fixes and reruns checks.
50
- That is the point — but it means a review is not read-only.
51
-
52
- ## Reviewing someone else's branch
53
-
54
- Check it out in a worktree so your own tree is untouched, and point `-C` at it:
55
-
56
- ```sh
57
- git worktree add /tmp/pr-123 && (cd /tmp/pr-123 && gh pr checkout 123)
58
- lemming -C /tmp/pr-123 exec --review testing --scope main...HEAD
59
- ```
60
-
61
- ## What to know before running it
62
-
63
- - **The agent runs unattended.** `--yolo` is the default, so it does not
64
- inherit the permission prompts of whatever launched it. Treat `lemming exec`
65
- as granting an agent unsupervised write access to the working directory.
66
- - **One agent run, no retry.** A failure is final; it does not silently try
67
- again. `--time-limit` caps the wall clock (default 60 minutes).
68
- - **Interrupting leaves partial edits.** The workspace is not restored, so
69
- there is no atomicity to rely on.
70
- - **Failures keep their log.** The state directory path is printed on stderr;
71
- read it with `lemming logs` pointed at that tasks file.
72
-
73
- ## Running several at once
74
-
75
- Concurrent runs in one checkout will interleave their edits. Give each write
76
- task its own worktree and address it with `-C`. Read-only work parallelizes
77
- safely as-is.
78
-
79
- ## When not to use it
80
-
81
- `exec` is for one unit of work. Anything needing more context than a single
82
- agent run can hold — a migration across a large codebase, a multi-step feature
83
- — belongs on a roadmap: `lemming add`, then `lemming run`. In particular,
84
- `--scope .` over a large repository *samples*; it does not cover.
85
-
86
- Run `lemming exec --help` for the full flag list.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes