@tarcisiopgs/lisa 1.8.2 → 1.10.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 +58 -23
- package/dist/{chunk-YZKNBQN6.js → chunk-H6W3UZ3N.js} +95 -6
- package/dist/index.js +2005 -849
- package/dist/{kanban-PD2F4KWT.js → kanban-GAGJGLPA.js} +308 -119
- package/package.json +7 -6
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 --
|
|
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,
|
|
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
|
-
| `
|
|
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
|
-
###
|
|
339
|
+
### Concurrent Execution
|
|
324
340
|
|
|
325
|
-
|
|
341
|
+
Process multiple issues in parallel with `--concurrency`:
|
|
326
342
|
|
|
327
|
-
```
|
|
328
|
-
|
|
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
|
-
|
|
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,25 @@ 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
|
+
|
|
357
392
|
### Auto-Detection
|
|
358
393
|
|
|
359
394
|
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
|
|
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) => {
|
|
@@ -66,7 +82,16 @@ function useKanbanState() {
|
|
|
66
82
|
const onStarted = (issueId) => {
|
|
67
83
|
setCards(
|
|
68
84
|
(prev) => prev.map(
|
|
69
|
-
(c) => c.id === issueId ? {
|
|
85
|
+
(c) => c.id === issueId ? {
|
|
86
|
+
...c,
|
|
87
|
+
column: "in_progress",
|
|
88
|
+
startedAt: Date.now(),
|
|
89
|
+
hasError: false,
|
|
90
|
+
skipped: false,
|
|
91
|
+
killed: false,
|
|
92
|
+
pausedAt: void 0,
|
|
93
|
+
pauseAccumulated: 0
|
|
94
|
+
} : c
|
|
70
95
|
)
|
|
71
96
|
);
|
|
72
97
|
};
|
|
@@ -84,6 +109,57 @@ function useKanbanState() {
|
|
|
84
109
|
)
|
|
85
110
|
);
|
|
86
111
|
};
|
|
112
|
+
const onSkipped = (issueId) => {
|
|
113
|
+
setCards(
|
|
114
|
+
(prev) => prev.map(
|
|
115
|
+
(c) => c.id === issueId ? {
|
|
116
|
+
...c,
|
|
117
|
+
column: "backlog",
|
|
118
|
+
startedAt: void 0,
|
|
119
|
+
skipped: true,
|
|
120
|
+
hasError: false,
|
|
121
|
+
pausedAt: void 0
|
|
122
|
+
} : c
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
const onKilled = (issueId) => {
|
|
127
|
+
setCards(
|
|
128
|
+
(prev) => prev.map(
|
|
129
|
+
(c) => c.id === issueId ? {
|
|
130
|
+
...c,
|
|
131
|
+
column: "backlog",
|
|
132
|
+
startedAt: void 0,
|
|
133
|
+
killed: true,
|
|
134
|
+
hasError: false,
|
|
135
|
+
pausedAt: void 0
|
|
136
|
+
} : c
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
const onProviderPaused = (issueId) => {
|
|
141
|
+
setCards(
|
|
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
|
+
})
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
const onProviderResumed = (issueId) => {
|
|
150
|
+
setCards(
|
|
151
|
+
(prev) => prev.map((c) => {
|
|
152
|
+
if (c.column !== "in_progress" || !c.pausedAt) return c;
|
|
153
|
+
if (issueId && c.id !== issueId) return c;
|
|
154
|
+
const pauseDuration = Date.now() - c.pausedAt;
|
|
155
|
+
return {
|
|
156
|
+
...c,
|
|
157
|
+
pausedAt: void 0,
|
|
158
|
+
pauseAccumulated: (c.pauseAccumulated ?? 0) + pauseDuration
|
|
159
|
+
};
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
};
|
|
87
163
|
const onOutput = (issueId, text) => {
|
|
88
164
|
setCards(
|
|
89
165
|
(prev) => prev.map((c) => c.id === issueId ? { ...c, outputLog: c.outputLog + text } : c)
|
|
@@ -93,22 +169,35 @@ function useKanbanState() {
|
|
|
93
169
|
kanbanEmitter.on("issue:started", onStarted);
|
|
94
170
|
kanbanEmitter.on("issue:done", onDone);
|
|
95
171
|
kanbanEmitter.on("issue:reverted", onReverted);
|
|
172
|
+
kanbanEmitter.on("issue:skipped", onSkipped);
|
|
173
|
+
kanbanEmitter.on("issue:killed", onKilled);
|
|
174
|
+
kanbanEmitter.on("provider:paused", onProviderPaused);
|
|
175
|
+
kanbanEmitter.on("provider:resumed", onProviderResumed);
|
|
96
176
|
kanbanEmitter.on("issue:output", onOutput);
|
|
177
|
+
const onModelChanged = (model) => setModelInUse(model);
|
|
178
|
+
kanbanEmitter.on("provider:model-changed", onModelChanged);
|
|
97
179
|
const onEmpty = () => setIsEmpty(true);
|
|
98
180
|
const onComplete = (data) => setWorkComplete(data);
|
|
99
181
|
kanbanEmitter.on("work:empty", onEmpty);
|
|
100
182
|
kanbanEmitter.on("work:complete", onComplete);
|
|
183
|
+
const cleanupBell = registerBellListeners(bellEnabled);
|
|
101
184
|
return () => {
|
|
102
185
|
kanbanEmitter.off("issue:queued", onQueued);
|
|
103
186
|
kanbanEmitter.off("issue:started", onStarted);
|
|
104
187
|
kanbanEmitter.off("issue:done", onDone);
|
|
105
188
|
kanbanEmitter.off("issue:reverted", onReverted);
|
|
189
|
+
kanbanEmitter.off("issue:skipped", onSkipped);
|
|
190
|
+
kanbanEmitter.off("issue:killed", onKilled);
|
|
191
|
+
kanbanEmitter.off("provider:paused", onProviderPaused);
|
|
192
|
+
kanbanEmitter.off("provider:resumed", onProviderResumed);
|
|
106
193
|
kanbanEmitter.off("issue:output", onOutput);
|
|
194
|
+
kanbanEmitter.off("provider:model-changed", onModelChanged);
|
|
107
195
|
kanbanEmitter.off("work:empty", onEmpty);
|
|
108
196
|
kanbanEmitter.off("work:complete", onComplete);
|
|
197
|
+
cleanupBell();
|
|
109
198
|
};
|
|
110
|
-
}, []);
|
|
111
|
-
return { cards, isEmpty, workComplete };
|
|
199
|
+
}, [bellEnabled]);
|
|
200
|
+
return { cards, isEmpty, workComplete, modelInUse };
|
|
112
201
|
}
|
|
113
202
|
|
|
114
203
|
export {
|