@tarcisiopgs/lisa 1.9.0 → 1.11.0

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.
package/README.md CHANGED
@@ -151,13 +151,13 @@ export JIRA_API_TOKEN="" # Atlassian API token
151
151
  | `lisa run --once --dry-run` | **Recommended first step** — preview config without executing |
152
152
  | `lisa run --issue ID` | Process a specific issue by identifier or URL |
153
153
  | `lisa run --limit N` | Process up to N issues |
154
+ | `lisa run --concurrency N` / `-c N` | Process up to N issues in parallel (default: 1) |
154
155
  | `lisa run --dry-run` | Preview without executing |
155
156
  | `lisa run --provider NAME` | Override AI provider |
156
157
  | `lisa run --source NAME` | Override issue source |
157
158
  | `lisa run --label NAME` | Override label filter |
158
159
  | `lisa run --github METHOD` | Override GitHub method (`cli` or `token`) |
159
- | `lisa run --json` | Output as JSON lines |
160
- | `lisa run --quiet` | Suppress non-essential output |
160
+ | `lisa run --no-bell` | Disable terminal bell on issue completion/failure |
161
161
  | `lisa init` | Create `.lisa/config.yaml` interactively |
162
162
  | `lisa config` | Edit config interactively |
163
163
  | `lisa config --show` | Print current config as JSON |
@@ -182,7 +182,7 @@ When running in an interactive terminal, `lisa run` renders a real-time Kanban b
182
182
  └──────────────────────────┘ └───────────────────────────┘ └───────────────────────────┘
183
183
  ```
184
184
 
185
- In-progress cards show a live elapsed timer. When the loop is paused, the active card displays a visual pause indicator. The detail view includes a scroll bar when output overflows.
185
+ In-progress cards show a live elapsed timer and the last action taken by the provider. A global progress bar in the header tracks overall session progress. When the loop is paused, active cards display a visual pause indicator. The detail view includes a scroll bar when output overflows and shows the active model name in the sidebar.
186
186
 
187
187
  ### Keyboard shortcuts
188
188
 
@@ -192,8 +192,11 @@ In-progress cards show a live elapsed timer. When the loop is paused, the active
192
192
  | `Tab` / `Shift+Tab` | Move between columns (alternative) |
193
193
  | `↑` / `↓` | Navigate cards / scroll output |
194
194
  | `Enter` | Open issue detail view (streams provider output) |
195
+ | `o` | Open PR in browser (when viewing a completed card with a PR) |
195
196
  | `Esc` | Close detail view, return to board |
196
- | `p` | Pause / resume loop finishes the current issue then waits |
197
+ | `k` | Kill the selected in-progress issue |
198
+ | `s` | Skip the selected in-progress issue |
199
+ | `p` | Pause / resume all active providers |
197
200
  | `q` | Quit |
198
201
 
199
202
  The sidebar legend updates contextually: board shortcuts when browsing the Kanban, scroll and back hints when viewing issue detail. The terminal tab title also updates in real time: it shows a spinner with the active issue ID while work is in progress, and a checkmark when done.
@@ -231,16 +234,20 @@ repos:
231
234
  loop:
232
235
  cooldown: 10 # seconds between issues
233
236
  max_sessions: 0 # 0 = unlimited
237
+ concurrency: 1 # issues processed in parallel (default: 1; >1 forces worktree mode)
234
238
 
235
239
  logs:
236
240
  dir: .lisa/logs
237
- format: text # "text" or "json"
238
241
 
239
242
  # Optional — kill stuck providers
240
243
  overseer:
241
244
  enabled: true
242
245
  check_interval: 30 # seconds between git status checks
243
246
  stuck_threshold: 300 # seconds without git changes before killing
247
+
248
+ # Optional — validate issue spec before accepting
249
+ validation:
250
+ require_acceptance_criteria: true # skip issues without detectable acceptance criteria (default: true)
244
251
  ```
245
252
 
246
253
  ### Source-Specific Fields
@@ -251,9 +258,18 @@ overseer:
251
258
  | `project` | Project name | — | Project identifier or UUID | — | — | — | — |
252
259
  | `pick_from` | Status to pick issues from | List to pick cards from | State name to pick issues from | Workflow state to pick stories from | — | — | Status to pick issues from |
253
260
  | `label` | Label to filter issues | Label to filter cards | Label to filter issues | Label to filter stories | Label to filter issues | Label to filter issues | Label to filter issues |
261
+ | `remove_label` | Label removed after pickup (optional; defaults to `label`) | Same | Same | Same | Same | Same | Same |
254
262
  | `in_progress` | In-progress status | In-progress column | In-progress state name | In-progress workflow state | Label to apply on activate | Label to apply on activate | In-progress status name |
255
263
  | `done` | Destination status after PR | Destination column after PR | Done state name | Done workflow state | Closes the issue | Closes the issue | Destination status after PR |
256
264
 
265
+ `label` can also be a list — Lisa picks any issue that has **all** of the specified labels. `remove_label` controls which label is removed after pickup, useful when you want to keep some labels intact. Example:
266
+
267
+ ```yaml
268
+ source_config:
269
+ label: [ready, api] # pick issues labelled both "ready" and "api"
270
+ remove_label: ready # only remove "ready" after pickup; "api" is preserved
271
+ ```
272
+
257
273
  Plane example:
258
274
 
259
275
  ```yaml
@@ -320,28 +336,28 @@ source_config:
320
336
 
321
337
  **Multi-repo worktree** — When multiple repos are configured, Lisa runs a two-phase flow: a planning agent produces a `.lisa-plan.json` with ordered steps, then Lisa executes each step sequentially — one worktree and one PR per repo. Cross-repo context (branch names, PR URLs) is passed to each subsequent step.
322
338
 
323
- ### Lifecycle Resources
339
+ ### Concurrent Execution
324
340
 
325
- For repos that need services running during implementation (databases, dev servers):
341
+ Process multiple issues in parallel with `--concurrency`:
326
342
 
327
- ```yaml
328
- repos:
329
- - name: my-api
330
- path: ./api
331
- base_branch: main
332
- lifecycle:
333
- resources:
334
- - name: postgres
335
- check_port: 5432
336
- up: "docker compose up -d postgres"
337
- down: "docker compose down"
338
- startup_timeout: 30
339
- setup:
340
- - "npx prisma generate"
341
- - "npx prisma db push"
343
+ ```bash
344
+ lisa run --concurrency 3 # or -c 3
342
345
  ```
343
346
 
344
- Lisa starts resources before the agent runs, waits for the port to be ready, runs setup commands, then stops everything after the session.
347
+ Each issue runs in its own isolated worktree with an independent provider process. When `--concurrency` is greater than 1, worktree mode is enforced automatically. The default is 1 (sequential, backward-compatible).
348
+
349
+ - **Isolation** — Each issue gets its own worktree, log file, and manifest. No shared state between concurrent sessions.
350
+ - **Shared infrastructure** — Docker containers and lifecycle resources are started once and shared across worktrees.
351
+ - **Guardrails** — Writes to the guardrails file are serialized to prevent concurrent corruption.
352
+ - **TUI** — Multiple cards appear in the In Progress column simultaneously. `[k]` and `[s]` target the selected card; `[p]` pauses all active providers.
353
+ - **Fallback** — Each issue has its own independent fallback chain.
354
+ - **Slot filling** — When an issue completes, the freed slot is immediately filled with the next available issue.
355
+
356
+ ### PR Stacking
357
+
358
+ When an issue depends on another issue that is still in progress, Lisa passes the dependency context to the agent automatically. The agent receives the parent issue ID, the branch name, the open PR URL, and the list of changed files — so it can build on top of the parent branch cleanly rather than branching from `main`.
359
+
360
+ Lisa detects blocked issues using the `completedBlockerIds` from your issue tracker (Linear blockers, GitHub milestone links, etc.) and skips issues whose blockers are not yet completed. Once a blocker is resolved, the dependent issue becomes eligible and will be picked up in a future loop.
345
361
 
346
362
  ### Recovery Mechanisms
347
363
 
@@ -354,6 +370,48 @@ Lisa starts resources before the agent runs, waits for the port to be ready, run
354
370
 
355
371
  When enabled, the overseer periodically checks `git status` in the working directory. If no changes are detected within `stuck_threshold` seconds, the provider process is killed and the error is eligible for fallback to the next model.
356
372
 
373
+ ### Issue Spec Validation
374
+
375
+ Before starting work on an issue, Lisa validates that it has a minimum spec. Issues that fail validation are skipped, labelled `needs-spec`, and have their `ready` label removed — so they never block the queue.
376
+
377
+ An issue passes validation if its description is non-empty and contains at least one of:
378
+
379
+ - A Markdown checklist item (`- [ ]`)
380
+ - The phrase `acceptance criteria` or `critérios`
381
+ - The words `expected`, `should`, or `deve`
382
+
383
+ To disable spec validation (e.g. for quick one-off issues):
384
+
385
+ ```yaml
386
+ validation:
387
+ require_acceptance_criteria: false
388
+ ```
389
+
390
+ > **Note:** The `needs-spec` label must exist in your issue tracker before Lisa can apply it. Create it manually if it does not exist — Lisa will log a warning if the label is missing.
391
+
392
+ ### Infrastructure Auto-Discovery
393
+
394
+ Lisa auto-discovers Docker Compose services in each repository and starts them before handing control to the agent. If a `docker-compose.yml` / `compose.yml` is present, Lisa reads port mappings and starts the services, waiting for each port to become ready.
395
+
396
+ **Dynamic port allocation** prevents collisions when multiple Lisa instances run in parallel. Add `port_range` and `port_env_var` to any discovered or manually configured resource:
397
+
398
+ ```yaml
399
+ repos:
400
+ - path: api
401
+ # lifecycle config is resolved by auto-discovery (docker-compose.yml)
402
+ # port_range and port_env_var can be set via manual config override
403
+ ```
404
+
405
+ When `port_range` is set, Lisa scans ports `check_port` through `check_port + port_range - 1` and picks the first free one. The chosen port is injected into the resource process via `port_env_var` and is available to `setup` commands and the agent environment.
406
+
407
+ | Field | Type | Description |
408
+ |---|---|---|
409
+ | `check_port` | `number` | Preferred (base) port |
410
+ | `port_range` | `number` (optional) | How many ports to scan from `check_port` |
411
+ | `port_env_var` | `string` (optional) | Env var name to inject the allocated port (e.g. `DATABASE_PORT`) |
412
+
413
+ If no free port is found within the range, Lisa logs a clear error and aborts the session.
414
+
357
415
  ### Auto-Detection
358
416
 
359
417
  Lisa auto-detects `vitest` or `jest` from `package.json` dependencies and injects the correct test command into the agent prompt. It also detects the package manager from lockfiles (`bun.lockb`/`bun.lock` → `bun`, `pnpm-lock.yaml` → `pnpm`, `yarn.lock` → `yarn`, otherwise `npm`).
@@ -35,9 +35,9 @@ function stopSpinner(message) {
35
35
  writeOSC(message);
36
36
  }
37
37
  }
38
- function notify() {
38
+ function notify(count = 1) {
39
39
  if (!isTTY()) return;
40
- process.stdout.write("\x07");
40
+ process.stdout.write("\x07".repeat(count));
41
41
  }
42
42
  function resetTitle() {
43
43
  if (!isTTY()) return;
@@ -50,12 +50,28 @@ import { useEffect, useState } from "react";
50
50
  var KanbanEmitter = class extends EventEmitter {
51
51
  };
52
52
  var kanbanEmitter = new KanbanEmitter();
53
- function useKanbanState() {
53
+ function registerBellListeners(bellEnabled) {
54
+ if (!bellEnabled) return () => {
55
+ };
56
+ const onDone = () => notify(1);
57
+ const onReverted = () => notify(2);
58
+ const onComplete = () => notify(1);
59
+ kanbanEmitter.on("issue:done", onDone);
60
+ kanbanEmitter.on("issue:reverted", onReverted);
61
+ kanbanEmitter.on("work:complete", onComplete);
62
+ return () => {
63
+ kanbanEmitter.off("issue:done", onDone);
64
+ kanbanEmitter.off("issue:reverted", onReverted);
65
+ kanbanEmitter.off("work:complete", onComplete);
66
+ };
67
+ }
68
+ function useKanbanState(bellEnabled) {
54
69
  const [cards, setCards] = useState([]);
55
70
  const [isEmpty, setIsEmpty] = useState(false);
56
71
  const [workComplete, setWorkComplete] = useState(
57
72
  null
58
73
  );
74
+ const [modelInUse, setModelInUse] = useState(null);
59
75
  useEffect(() => {
60
76
  const onQueued = (issue) => {
61
77
  setCards((prev) => {
@@ -121,15 +137,20 @@ function useKanbanState() {
121
137
  )
122
138
  );
123
139
  };
124
- const onProviderPaused = () => {
140
+ const onProviderPaused = (issueId) => {
125
141
  setCards(
126
- (prev) => prev.map((c) => c.column === "in_progress" ? { ...c, pausedAt: Date.now() } : c)
142
+ (prev) => prev.map((c) => {
143
+ if (c.column !== "in_progress") return c;
144
+ if (issueId && c.id !== issueId) return c;
145
+ return { ...c, pausedAt: Date.now() };
146
+ })
127
147
  );
128
148
  };
129
- const onProviderResumed = () => {
149
+ const onProviderResumed = (issueId) => {
130
150
  setCards(
131
151
  (prev) => prev.map((c) => {
132
152
  if (c.column !== "in_progress" || !c.pausedAt) return c;
153
+ if (issueId && c.id !== issueId) return c;
133
154
  const pauseDuration = Date.now() - c.pausedAt;
134
155
  return {
135
156
  ...c,
@@ -153,10 +174,13 @@ function useKanbanState() {
153
174
  kanbanEmitter.on("provider:paused", onProviderPaused);
154
175
  kanbanEmitter.on("provider:resumed", onProviderResumed);
155
176
  kanbanEmitter.on("issue:output", onOutput);
177
+ const onModelChanged = (model) => setModelInUse(model);
178
+ kanbanEmitter.on("provider:model-changed", onModelChanged);
156
179
  const onEmpty = () => setIsEmpty(true);
157
180
  const onComplete = (data) => setWorkComplete(data);
158
181
  kanbanEmitter.on("work:empty", onEmpty);
159
182
  kanbanEmitter.on("work:complete", onComplete);
183
+ const cleanupBell = registerBellListeners(bellEnabled);
160
184
  return () => {
161
185
  kanbanEmitter.off("issue:queued", onQueued);
162
186
  kanbanEmitter.off("issue:started", onStarted);
@@ -167,11 +191,13 @@ function useKanbanState() {
167
191
  kanbanEmitter.off("provider:paused", onProviderPaused);
168
192
  kanbanEmitter.off("provider:resumed", onProviderResumed);
169
193
  kanbanEmitter.off("issue:output", onOutput);
194
+ kanbanEmitter.off("provider:model-changed", onModelChanged);
170
195
  kanbanEmitter.off("work:empty", onEmpty);
171
196
  kanbanEmitter.off("work:complete", onComplete);
197
+ cleanupBell();
172
198
  };
173
- }, []);
174
- return { cards, isEmpty, workComplete };
199
+ }, [bellEnabled]);
200
+ return { cards, isEmpty, workComplete, modelInUse };
175
201
  }
176
202
 
177
203
  export {