lemming-cli 0.3.1__tar.gz → 0.3.3__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 (133) hide show
  1. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/PKG-INFO +3 -2
  2. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/README.md +2 -1
  3. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/docs/HOOKS.md +7 -1
  4. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/pyproject.toml +1 -1
  5. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/tasks_test.py +13 -0
  6. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/progress_test.py +20 -0
  7. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/tasks.py +2 -2
  8. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/tasks_test.py +12 -0
  9. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/orchestrator.py +9 -1
  10. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/orchestrator_test.py +43 -2
  11. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/prompts/hooks/90-roadmap.md +14 -9
  12. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/prompts/taskrunner.md +21 -21
  13. lemming_cli-0.3.3/src/lemming/prompts.py +525 -0
  14. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/prompts_test.py +106 -3
  15. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/runner.py +13 -0
  16. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/runner_test.py +84 -3
  17. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/__init__.py +6 -0
  18. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/lifecycle.py +6 -11
  19. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/lifecycle_test.py +57 -13
  20. lemming_cli-0.3.3/src/lemming/tasks/limits.py +45 -0
  21. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/operations.py +6 -1
  22. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/operations_test.py +67 -1
  23. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/progress.py +2 -0
  24. lemming_cli-0.3.3/src/lemming/tasks/progress_test.py +54 -0
  25. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/uv.lock +1 -1
  26. lemming_cli-0.3.1/src/lemming/prompts.py +0 -267
  27. lemming_cli-0.3.1/src/lemming/tasks/progress_test.py +0 -22
  28. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/.github/workflows/ci.yml +0 -0
  29. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/.github/workflows/publish.yml +0 -0
  30. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/.github/workflows/screenshots.yml +0 -0
  31. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/.gitignore +0 -0
  32. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/.prettierignore +0 -0
  33. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/.prettierrc +0 -0
  34. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/Dockerfile +0 -0
  35. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/LICENSE +0 -0
  36. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/biome.json +0 -0
  37. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/docker-compose.yml +0 -0
  38. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/docs/EVALS.md +0 -0
  39. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/docs/screenshots/dashboard-desktop.png +0 -0
  40. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/docs/screenshots/dashboard-mobile.png +0 -0
  41. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/docs/screenshots/task-log-desktop.png +0 -0
  42. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/docs/screenshots/task-log-mobile.png +0 -0
  43. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/package-lock.json +0 -0
  44. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/package.json +0 -0
  45. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/playwright.config.js +0 -0
  46. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/__init__.py +0 -0
  47. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/__init__.py +0 -0
  48. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/auth.py +0 -0
  49. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/auth_test.py +0 -0
  50. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/config.py +0 -0
  51. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/config_test.py +0 -0
  52. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/conftest.py +0 -0
  53. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/context.py +0 -0
  54. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/context_test.py +0 -0
  55. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/directories.py +0 -0
  56. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/directories_test.py +0 -0
  57. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/files.py +0 -0
  58. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/files_test.py +0 -0
  59. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/hooks.py +0 -0
  60. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/hooks_test.py +0 -0
  61. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/logging.py +0 -0
  62. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/logging_test.py +0 -0
  63. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/loop.py +0 -0
  64. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/loop_test.py +0 -0
  65. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/main.py +0 -0
  66. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/main_test.py +0 -0
  67. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api/tasks.py +0 -0
  68. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/api_test.py +0 -0
  69. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/__init__.py +0 -0
  70. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/config.py +0 -0
  71. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/config_test.py +0 -0
  72. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/goal.py +0 -0
  73. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/goal_test.py +0 -0
  74. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/hooks.py +0 -0
  75. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/hooks_test.py +0 -0
  76. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/main.py +0 -0
  77. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/main_test.py +0 -0
  78. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/operations.py +0 -0
  79. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/operations_test.py +0 -0
  80. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/progress.py +0 -0
  81. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/readability_cli.py +0 -0
  82. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli/readability_cli_test.py +0 -0
  83. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/cli_test.py +0 -0
  84. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/conftest.py +0 -0
  85. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/__init__.py +0 -0
  86. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/__main__.py +0 -0
  87. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/cli.py +0 -0
  88. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/cli_test.py +0 -0
  89. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/container.py +0 -0
  90. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/container_test.py +0 -0
  91. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/fixtures.py +0 -0
  92. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/fixtures_test.py +0 -0
  93. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/harness.py +0 -0
  94. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/harness_test.py +0 -0
  95. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/readability.py +0 -0
  96. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/readability_test.py +0 -0
  97. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/roadmap.py +0 -0
  98. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/roadmap_test.py +0 -0
  99. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/scenarios.py +0 -0
  100. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/scenarios_test.py +0 -0
  101. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/suites.py +0 -0
  102. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/trial.py +0 -0
  103. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/evals/trial_test.py +0 -0
  104. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/hooks.py +0 -0
  105. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/hooks_test.py +0 -0
  106. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/integration_test.py +0 -0
  107. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/main.py +0 -0
  108. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/main_test.py +0 -0
  109. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/models.py +0 -0
  110. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/models_test.py +0 -0
  111. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/paths.py +0 -0
  112. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/paths_test.py +0 -0
  113. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/persistence.py +0 -0
  114. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/persistence_test.py +0 -0
  115. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/prompts/hooks/50-readability.md +0 -0
  116. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/prompts/hooks/60-testing.md +0 -0
  117. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/prompts/hooks/70-ux.md +0 -0
  118. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/providers.py +0 -0
  119. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/providers_test.py +0 -0
  120. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/queries.py +0 -0
  121. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/tasks/queries_test.py +0 -0
  122. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/dashboard.spec.js +0 -0
  123. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/dashboard.test.js +0 -0
  124. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/favicon.js +0 -0
  125. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/favicon.spec.js +0 -0
  126. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/files.html +0 -0
  127. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/files.spec.js +0 -0
  128. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/index.html +0 -0
  129. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/index.js +0 -0
  130. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/logs.html +0 -0
  131. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/logs.test.js +0 -0
  132. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/mancha.js +0 -0
  133. {lemming_cli-0.3.1 → lemming_cli-0.3.3}/src/lemming/web/screenshots.spec.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lemming-cli
3
- Version: 0.3.1
3
+ Version: 0.3.3
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
@@ -319,7 +319,8 @@ See [docs/EVALS.md](docs/EVALS.md) for details.
319
319
  Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
320
320
  (auto-approve) and "Quiet" flags for popular tools:
321
321
 
322
- - **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions`
322
+ - **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
323
+ the project workspace with `--add-dir`
323
324
  - **Aider**: Adds `--yes --quiet`
324
325
  - **Claude**: Adds `--dangerously-skip-permissions`
325
326
  - **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
@@ -297,7 +297,8 @@ See [docs/EVALS.md](docs/EVALS.md) for details.
297
297
  Lemming uses **fuzzy matching** to automatically inject the correct "YOLO"
298
298
  (auto-approve) and "Quiet" flags for popular tools:
299
299
 
300
- - **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions`
300
+ - **Antigravity (`agy`)**: Adds `--dangerously-skip-permissions` and exposes
301
+ the project workspace with `--add-dir`
301
302
  - **Aider**: Adds `--yes --quiet`
302
303
  - **Claude**: Adds `--dangerously-skip-permissions`
303
304
  - **Codex**: Runs non-interactively via `codex exec`, adds `--json`, and, in
@@ -155,11 +155,17 @@ Your hook template can use the following placeholders:
155
155
 
156
156
  - `{{roadmap}}`: A structured summary of the long-term goal and all tasks.
157
157
  - `{{finished_task}}`: Details about the task that just finished (ID,
158
- description, progress, and the last 100 lines of its execution log).
158
+ description, recent progress, and a recent execution-log excerpt capped at
159
+ 16 KiB).
159
160
  - `{{finished_task_id}}`: The ID of the task that just finished.
160
161
  - `{{tasks_file_name}}`: The filename of the tasks YAML file.
161
162
  - `{{tasks_file_path}}`: The full path to the tasks YAML file.
162
163
 
164
+ Roadmap context is size-bounded and prioritizes current and actionable tasks
165
+ over older completed history. Readability, testing, and UX hooks receive a
166
+ smaller roadmap summary because their primary context is the finished task and
167
+ the workspace.
168
+
163
169
  ## Developer Ergonomics
164
170
 
165
171
  ### Listing Hooks
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lemming-cli"
7
- version = "0.3.1"
7
+ version = "0.3.3"
8
8
  description = "An autonomous, iterative task runner for AI coding agents."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -30,6 +30,19 @@ def test_add_task(client, test_tasks):
30
30
  assert any(t.description == "New task from test" for t in roadmap.tasks)
31
31
 
32
32
 
33
+ def test_add_task_rejects_oversized_description(client, test_tasks):
34
+ response = client.post(
35
+ "/api/tasks",
36
+ json={
37
+ "description": "x" * (tasks.MAX_TASK_DESCRIPTION_CHARS + 1),
38
+ },
39
+ )
40
+
41
+ assert response.status_code == 400
42
+ assert "2,001 characters (limit 2,000)" in response.json()["detail"]
43
+ assert len(tasks.load_tasks(test_tasks).tasks) == 3
44
+
45
+
33
46
  def test_add_task_with_runner(client, test_tasks):
34
47
  response = client.post(
35
48
  "/api/tasks", json={"description": "Runner task", "runner": "claude"}
@@ -38,3 +38,23 @@ def test_progress(setup_env):
38
38
 
39
39
  data = tasks.load_tasks(tasks_file)
40
40
  assert "Observed behavior X" in data.tasks[0].progress
41
+
42
+
43
+ def test_progress_reports_actionable_size_error(setup_env):
44
+ runner, base_args, tasks_file = setup_env
45
+
46
+ result = runner.invoke(
47
+ cli.cli,
48
+ base_args
49
+ + [
50
+ "progress",
51
+ "12345678",
52
+ "x" * (tasks.MAX_PROGRESS_ENTRY_CHARS + 1),
53
+ ],
54
+ )
55
+
56
+ assert result.exit_code == 1
57
+ assert "281 characters (limit 280)" in result.output
58
+ assert "Write detailed evidence or verbose command output" in result.output
59
+ assert str(tasks_file.parent) not in result.output
60
+ assert tasks.load_tasks(tasks_file).tasks[0].progress == []
@@ -645,11 +645,11 @@ def fail(ctx: click.Context, task_id: str):
645
645
  ctx.exit(1)
646
646
 
647
647
 
648
- @cli.command(short_help="<taskid> Stop an in-progress task")
648
+ @cli.command(short_help="<taskid> Cancel a pending or active task")
649
649
  @click.argument("task_id")
650
650
  @click.pass_context
651
651
  def cancel(ctx: click.Context, task_id: str):
652
- """Kills the runner and cancels an in-progress task."""
652
+ """Cancels a task, stopping its runner if it is active."""
653
653
  tasks_file = ctx.obj["TASKS_FILE"]
654
654
  if tasks.cancel_task(tasks_file, task_id):
655
655
  click.echo(f"Task {task_id} cancelled.")
@@ -51,6 +51,18 @@ class TestCLITasks(unittest.TestCase):
51
51
  task_descs = [t.description for t in data.tasks]
52
52
  self.assertIn("New Task", task_descs)
53
53
 
54
+ def test_add_task_reports_actionable_size_error(self):
55
+ result = self.cli_runner.invoke(
56
+ cli.cli,
57
+ self.base_args
58
+ + ["add", "x" * (tasks.MAX_TASK_DESCRIPTION_CHARS + 1)],
59
+ )
60
+
61
+ self.assertEqual(result.exit_code, 1)
62
+ self.assertIn("2,001 characters (limit 2,000)", result.output)
63
+ self.assertIn("Keep the brief task-specific", result.output)
64
+ self.assertEqual(len(tasks.load_tasks(self.test_tasks_file).tasks), 1)
65
+
54
66
  def test_edit_task_description(self):
55
67
  result = self.cli_runner.invoke(
56
68
  cli.cli,
@@ -111,6 +111,7 @@ def run_hooks(
111
111
  no_defaults,
112
112
  verbose=verbose,
113
113
  time_limit=time_limit,
114
+ working_dir=working_dir,
114
115
  )
115
116
 
116
117
  try:
@@ -357,8 +358,14 @@ def _handle_runner_exit(
357
358
  click.echo(stderr, err=True)
358
359
 
359
360
  if returncode == -15:
361
+ if post_task.status == tasks.TaskStatus.CANCELLED:
362
+ if verbose:
363
+ click.echo("Task was cancelled. Continuing orchestrator.")
364
+ return False
360
365
  if verbose:
361
- click.echo("Task was cancelled. Stopping orchestrator loop.")
366
+ click.echo(
367
+ "Runner exited after SIGTERM. Stopping orchestrator loop."
368
+ )
362
369
  return True
363
370
 
364
371
  if runner_failed and not post_task.requested_status:
@@ -536,6 +543,7 @@ def run_loop(
536
543
  no_defaults,
537
544
  verbose=verbose,
538
545
  time_limit=time_limit,
546
+ working_dir=working_dir,
539
547
  )
540
548
 
541
549
  returncode = 0
@@ -209,6 +209,7 @@ def test_synchronous_hooks_execution_timing(setup_env):
209
209
  @mock.patch("lemming.runner.run_with_heartbeat")
210
210
  def test_run_loop_calls_runner_with_header(mock_run, setup_env):
211
211
  test_tasks_file, initial_data = setup_env
212
+ working_dir = test_tasks_file.parent / "project with spaces"
212
213
  # Setup runner mock
213
214
  mock_run.return_value = (0, "output", "")
214
215
 
@@ -225,11 +226,14 @@ def test_run_loop_calls_runner_with_header(mock_run, setup_env):
225
226
  yolo=True,
226
227
  no_defaults=False,
227
228
  runner_args=(),
229
+ working_dir=working_dir,
228
230
  )
229
231
 
230
232
  # Verify run_with_heartbeat was called with header="Task Runner"
231
233
  args, kwargs = mock_run.call_args
232
234
  assert kwargs.get("header") == "Task Runner"
235
+ cmd = args[0]
236
+ assert cmd[cmd.index("--add-dir") + 1] == str(working_dir)
233
237
 
234
238
 
235
239
  @mock.patch("lemming.runner.run_with_heartbeat")
@@ -483,7 +487,9 @@ def test_handle_runner_exit_completes(mock_sleep, mock_run_hooks, setup_env):
483
487
 
484
488
 
485
489
  @mock.patch("lemming.orchestrator.run_hooks")
486
- def test_handle_runner_exit_cancelled(mock_run_hooks, setup_env):
490
+ def test_handle_runner_exit_stops_on_unexpected_sigterm(
491
+ mock_run_hooks, setup_env
492
+ ):
487
493
  test_tasks_file, initial_data = setup_env
488
494
  data = tasks.load_tasks(test_tasks_file)
489
495
  task = data.tasks[0]
@@ -507,7 +513,38 @@ def test_handle_runner_exit_cancelled(mock_run_hooks, setup_env):
507
513
  working_dir=None,
508
514
  time_limit=1,
509
515
  )
510
- assert should_abort, "Cancellation (-15) should abort the loop"
516
+ assert should_abort
517
+
518
+
519
+ @mock.patch("lemming.orchestrator.run_hooks")
520
+ def test_handle_runner_exit_continues_after_cancellation(
521
+ mock_run_hooks, setup_env
522
+ ):
523
+ test_tasks_file, initial_data = setup_env
524
+ data = tasks.load_tasks(test_tasks_file)
525
+ task = data.tasks[0]
526
+ task.status = tasks.TaskStatus.CANCELLED
527
+ tasks.save_tasks(test_tasks_file, data)
528
+
529
+ should_abort = _handle_runner_exit(
530
+ test_tasks_file,
531
+ task.id,
532
+ returncode=-15,
533
+ stdout="",
534
+ stderr="",
535
+ retries=3,
536
+ retry_delay=5,
537
+ runner_name="agy",
538
+ yolo=True,
539
+ runner_args=(),
540
+ no_defaults=False,
541
+ verbose=False,
542
+ active_hooks=[],
543
+ working_dir=None,
544
+ time_limit=1,
545
+ )
546
+
547
+ assert not should_abort
511
548
 
512
549
 
513
550
  @mock.patch("lemming.orchestrator.time.sleep")
@@ -576,6 +613,7 @@ def test_run_hooks_success(mock_prepare, mock_list, mock_run, hooks_env):
576
613
  mock_list.return_value = ["roadmap"]
577
614
  mock_prepare.return_value = "Hook Prompt"
578
615
  mock_run.return_value = (0, "stdout", "")
616
+ working_dir = hooks_env.parent / "project with spaces"
579
617
 
580
618
  # Task must be IN_PROGRESS for finalization to apply
581
619
  tasks.update_task(
@@ -590,10 +628,13 @@ def test_run_hooks_success(mock_prepare, mock_list, mock_run, hooks_env):
590
628
  runner_args=(),
591
629
  no_defaults=False,
592
630
  verbose=True,
631
+ working_dir=working_dir,
593
632
  final_status=tasks.TaskStatus.COMPLETED,
594
633
  )
595
634
 
596
635
  assert mock_run.called
636
+ cmd = mock_run.call_args.args[0]
637
+ assert cmd[cmd.index("--add-dir") + 1] == str(working_dir)
597
638
  data = tasks.load_tasks(hooks_env)
598
639
  assert data.tasks[0].status == tasks.TaskStatus.COMPLETED
599
640
 
@@ -49,16 +49,21 @@ the plan up-to-date with minimal friction.
49
49
  forgotten teardowns, missing tests reported by the testing hook, or
50
50
  formatting issues reported by the readability hook), add new tasks to
51
51
  address them.
52
- 7. **No Code Changes**: Your only persistent changes may be to the roadmap via
52
+ 7. **Task-Specific Briefs**: Keep new or rewritten task descriptions concise,
53
+ self-contained, and no more than {{max_task_description_chars}} characters.
54
+ Include the concrete files, symbols, motivation, and acceptance criteria the
55
+ task needs, but do not repeat project-wide rules already present in the
56
+ long-term goal.
57
+ 8. **No Code Changes**: Your only persistent changes may be to the roadmap via
53
58
  the `lemming` CLI. Do NOT edit source or configuration files. Reading the
54
- workspace and running existing build, test, and entry-point commands for the
55
- queue-drain audit is explicitly allowed.
56
- 8. **Fast Exit**: If the roadmap is accurate and well-structured, AND there are
57
- no failed tasks that have reached their maximum attempts, you may exit
58
- immediately without running any commands. The queue-drain audit in directive
59
- 4 must finish before this clause applies. If a task is marked as FAILED and
60
- has reached its maximum attempts, a Fast Exit will result in the entire
61
- project ABORTING. In that case, you MUST repair it.
59
+ workspace and running existing build, test, and entry-point commands for
60
+ the queue-drain audit is explicitly allowed.
61
+ 9. **Fast Exit**: If the roadmap is accurate and well-structured, AND there
62
+ are no failed tasks that have reached their maximum attempts, you may exit
63
+ immediately without running any commands. The queue-drain audit in
64
+ directive 4 must finish before this clause applies. If a task is marked as
65
+ FAILED and has reached its maximum attempts, a Fast Exit will result in the
66
+ entire project ABORTING. In that case, you MUST repair it.
62
67
 
63
68
  ## Commands
64
69
 
@@ -36,33 +36,33 @@ Your CURRENT, EXCLUSIVE task is: **{{description}}**
36
36
  a fresh, empty conversation history. Their only context is the global
37
37
  roadmap, previously recorded progress from completed tasks, and the state
38
38
  of the file system.
39
- - **Self-Contained Descriptions:** Because of this isolation, you MUST write
40
- extremely thorough, self-contained descriptions for any new tasks you
41
- schedule. Reference specific task IDs, file paths, and exact symbols so the
42
- new task knows exactly what to do.
43
- - **Complex Handoffs:** If you need to pass extensive context (like detailed
44
- architectural plans, specific error traces, or large code snippets) to a
45
- downstream task, do not rely on the brief progress messages alone. Instead,
46
- create a dedicated file in the workspace to hold this context, and
47
- explicitly mention its path in the new task's description. Use this
48
- technique judiciously to avoid creating a mess of orphaned files in the
49
- project.
39
+ - **Task-Specific Descriptions:** New task descriptions must be concise,
40
+ self-contained, and no more than {{max_task_description_chars}} characters.
41
+ Include the file paths, symbols, motivation, and acceptance criteria that
42
+ task needs, but do not restate project-wide rules already present in the
43
+ long-term goal.
44
+ - **Detailed Evidence:** Keep verbose gate output, transcripts, error traces,
45
+ and other ephemeral evidence in `{{tasks_dir}}`, which is Lemming's
46
+ per-project scratch directory outside the workspace. Reference the file
47
+ from a task description or progress entry when another attempt needs it.
48
+ Put conclusions that must outlive the task in the commit message or a
49
+ decision document in the repository.
50
50
 
51
- 4. **Progress:** Your first action should be to record a brief progress entry
52
- describing how you intend to approach this task e.g. which files you will
53
- modify, what strategy you will use. This lets the operator verify your
54
- direction early. Then continue recording progress as you work: each time you
55
- complete a meaningful step, discover something relevant, or hit a problem,
56
- record it immediately. If your process is killed, recorded progress carries
57
- over to the next attempt. At least one progress entry is required before
51
+ 4. **Progress:** Your first action should be to record a one-line progress entry
52
+ describing your approach. Continue recording concise findings as you work,
53
+ but keep each entry to one line and no more than
54
+ {{max_progress_entry_chars}} characters. Do not paste command output or
55
+ detailed evidence into progress; write it under `{{tasks_dir}}` and reference
56
+ the file instead. If your process is killed, recorded progress carries over
57
+ to the next attempt. At least one progress entry is required before
58
58
  completing or failing a task:
59
59
  `lemming --tasks-file {{tasks_file_path}} progress {{task_id}} '<what you did or found>'`
60
60
  5. **Success:** When you have completely finished and verified the task, and
61
- recorded ALL relevant progress (at least one is required), run:
61
+ recorded relevant progress (at least one entry is required), run:
62
62
  `lemming --tasks-file {{tasks_file_path}} complete {{task_id}}`
63
63
  6. **Failure/Blocker:** If you hit a technical roadblock, cannot fix a bug, or
64
- are unable to complete the task, after recording ALL relevant progress (at
65
- least one is required), run:
64
+ are unable to complete the task, after recording relevant progress (at least
65
+ one entry is required), run:
66
66
  `lemming --tasks-file {{tasks_file_path}} fail {{task_id}}`
67
67
 
68
68
  7. Stop and exit after running either the complete or fail command.