@tarcisiopgs/lisa 0.9.6 → 1.0.1
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 +106 -66
- package/dist/index.js +62 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
While the Ralphs of the world flooded GitHub with mindless agent loops — brute-forcing their way through issues with no context, no workflow awareness, and no regard for the mess they leave behind — Lisa takes a different approach. She reads the issue, understands the workspace, picks the right repo, creates the branch, validates her work, and opens the PR. Then she moves on to the next one. When there's nothing left to do, she stops.
|
|
8
8
|
|
|
9
|
-
Named after the smartest Simpson, Lisa is an autonomous issue resolver that connects your project tracker (Linear or Trello) to an AI coding agent (Claude Code, Gemini CLI, or OpenCode) and delivers pull requests via
|
|
9
|
+
Named after the smartest Simpson, Lisa is an autonomous issue resolver that connects your project tracker (Linear or Trello) to an AI coding agent (Claude Code, Gemini CLI, or OpenCode) and delivers pull requests via GitHub. No MCP servers. No prompt chains. No blind retries. Just structured, end-to-end execution.
|
|
10
10
|
|
|
11
11
|
## Why Lisa?
|
|
12
12
|
|
|
@@ -14,10 +14,12 @@ Most AI agent loops work like Ralph — they grab an issue, throw it at a model,
|
|
|
14
14
|
|
|
15
15
|
Lisa is deterministic. She follows a structured pipeline with clear stages (fetch, activate, implement, validate, PR, update) and stops when the work is done. This means:
|
|
16
16
|
|
|
17
|
-
- **Token efficiency** — Each issue gets one focused prompt with full context
|
|
18
|
-
- **Multi-repo awareness** — Lisa detects which repos the agent
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
17
|
+
- **Token efficiency** — Each issue gets one focused prompt with full context. No wasted retries, no speculative exploration, no idle polling.
|
|
18
|
+
- **Multi-repo awareness** — Lisa detects which repos the agent touched and creates one PR per repo. Monorepos with multiple packages just work.
|
|
19
|
+
- **Model fallback** — Configure a chain of models (`claude → gemini → opencode`). Transient errors (429, quota, timeout) trigger the next model; non-transient errors stop the chain.
|
|
20
|
+
- **Workflow integration** — Issues move through your board in real time (Backlog → In Progress → In Review). Your team always knows what's being worked on.
|
|
21
|
+
- **Self-healing** — Orphan issues (stuck in "In Progress" from interrupted runs) are automatically recovered on startup. Pre-push hook failures trigger the agent to fix and retry.
|
|
22
|
+
- **Guardrails** — Past failures are logged and injected into future prompts so the agent avoids repeating mistakes.
|
|
21
23
|
|
|
22
24
|
## Install
|
|
23
25
|
|
|
@@ -27,11 +29,9 @@ npm install -g @tarcisiopgs/lisa
|
|
|
27
29
|
|
|
28
30
|
## Environment Variables
|
|
29
31
|
|
|
30
|
-
Lisa calls external APIs directly. Set these in your shell profile (`~/.zshrc` or `~/.bashrc`):
|
|
31
|
-
|
|
32
32
|
```bash
|
|
33
|
-
# Required (
|
|
34
|
-
export GITHUB_TOKEN=""
|
|
33
|
+
# Required (at least one)
|
|
34
|
+
export GITHUB_TOKEN="" # or have `gh` CLI authenticated
|
|
35
35
|
|
|
36
36
|
# Required when source = linear
|
|
37
37
|
export LINEAR_API_KEY=""
|
|
@@ -41,24 +41,28 @@ export TRELLO_API_KEY=""
|
|
|
41
41
|
export TRELLO_TOKEN=""
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
The CLI will warn you if any required variable is missing.
|
|
45
|
-
|
|
46
44
|
## Quick Start
|
|
47
45
|
|
|
48
46
|
```bash
|
|
49
47
|
# Interactive setup
|
|
50
48
|
lisa init
|
|
51
49
|
|
|
52
|
-
# Run continuously
|
|
50
|
+
# Run continuously until all labeled issues are done
|
|
53
51
|
lisa run
|
|
54
52
|
|
|
55
53
|
# Single issue
|
|
56
54
|
lisa run --once
|
|
57
55
|
|
|
56
|
+
# Specific issue by identifier or URL
|
|
57
|
+
lisa run --issue INT-150
|
|
58
|
+
|
|
59
|
+
# Process up to N issues
|
|
60
|
+
lisa run --limit 5
|
|
61
|
+
|
|
58
62
|
# Preview without executing
|
|
59
63
|
lisa run --dry-run
|
|
60
64
|
|
|
61
|
-
# Override provider
|
|
65
|
+
# Override provider for a single run
|
|
62
66
|
lisa run --provider gemini --once
|
|
63
67
|
```
|
|
64
68
|
|
|
@@ -68,8 +72,13 @@ lisa run --provider gemini --once
|
|
|
68
72
|
|---------|-------------|
|
|
69
73
|
| `lisa run` | Run the agent loop |
|
|
70
74
|
| `lisa run --once` | Process a single issue |
|
|
75
|
+
| `lisa run --issue ID` | Process a specific issue by identifier or URL |
|
|
71
76
|
| `lisa run --limit N` | Process up to N issues |
|
|
72
77
|
| `lisa run --dry-run` | Preview without executing |
|
|
78
|
+
| `lisa run --provider NAME` | Override AI provider |
|
|
79
|
+
| `lisa run --label NAME` | Override label filter |
|
|
80
|
+
| `lisa run --json` | Output as JSON lines |
|
|
81
|
+
| `lisa run --quiet` | Suppress non-essential output |
|
|
73
82
|
| `lisa config` | Interactive config wizard |
|
|
74
83
|
| `lisa config --show` | Show current config |
|
|
75
84
|
| `lisa config --set key=value` | Set a config value |
|
|
@@ -86,106 +95,137 @@ lisa run --provider gemini --once
|
|
|
86
95
|
|
|
87
96
|
At least one provider must be installed and available in your PATH.
|
|
88
97
|
|
|
89
|
-
All providers
|
|
98
|
+
All providers use `child_process.spawn` with `sh -c`. Prompts are written to a temp file and passed via `$(cat file)` to avoid argument length limits. Output streams to both stdout and the session log file in real time.
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
### Fallback Chain
|
|
101
|
+
|
|
102
|
+
Configure multiple models in the `models` array. Lisa tries them in order — transient errors (429, quota, timeout, network) trigger the next model. Non-transient errors stop the chain immediately.
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
models:
|
|
106
|
+
- claude
|
|
107
|
+
- gemini
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If `models` is not set, Lisa uses the single `provider` field.
|
|
92
111
|
|
|
93
|
-
|
|
112
|
+
## Workflow Modes
|
|
94
113
|
|
|
95
|
-
### Branch
|
|
114
|
+
### Branch
|
|
96
115
|
|
|
97
116
|
The AI agent creates a branch directly in your current checkout, implements the changes, and pushes. Simple setup, works everywhere.
|
|
98
117
|
|
|
99
118
|
### Worktree
|
|
100
119
|
|
|
101
|
-
Lisa creates an isolated [git worktree](https://git-scm.com/docs/git-worktree) for each issue under `.worktrees/`. The
|
|
120
|
+
Lisa creates an isolated [git worktree](https://git-scm.com/docs/git-worktree) for each issue under `.worktrees/`. The agent works inside the worktree without touching your main checkout. After the PR is created, the worktree is cleaned up automatically.
|
|
121
|
+
|
|
122
|
+
In multi-repo workspaces, the agent selects the correct repository, creates the worktree, implements, and writes a `.lisa-manifest.json` with the repo path, branch name, and PR title. Lisa reads the manifest to push and create the PR.
|
|
102
123
|
|
|
103
124
|
Worktree mode is ideal when you want to keep working in the repo while Lisa resolves issues in the background.
|
|
104
125
|
|
|
105
126
|
## Configuration
|
|
106
127
|
|
|
107
|
-
Config lives in `.lisa/config.yaml
|
|
128
|
+
Config lives in `.lisa/config.yaml`. Run `lisa init` to create it interactively.
|
|
108
129
|
|
|
109
|
-
**Linear:**
|
|
110
130
|
```yaml
|
|
111
131
|
provider: claude
|
|
132
|
+
models:
|
|
133
|
+
- claude
|
|
134
|
+
- gemini
|
|
112
135
|
source: linear
|
|
113
|
-
workflow:
|
|
136
|
+
workflow: worktree
|
|
114
137
|
|
|
115
138
|
source_config:
|
|
116
139
|
team: Engineering
|
|
117
140
|
project: Web App
|
|
118
141
|
label: ready
|
|
119
|
-
pick_from:
|
|
142
|
+
pick_from: Backlog
|
|
120
143
|
in_progress: In Progress
|
|
121
144
|
done: In Review
|
|
122
145
|
|
|
123
|
-
github: cli
|
|
146
|
+
github: cli # "cli" (gh) or "token" (GITHUB_TOKEN)
|
|
124
147
|
workspace: .
|
|
148
|
+
base_branch: main
|
|
149
|
+
|
|
125
150
|
repos:
|
|
126
|
-
- name:
|
|
151
|
+
- name: my-api
|
|
152
|
+
path: ./api
|
|
153
|
+
base_branch: main
|
|
154
|
+
- name: my-app
|
|
127
155
|
path: ./app
|
|
128
|
-
|
|
156
|
+
base_branch: main
|
|
129
157
|
|
|
130
158
|
loop:
|
|
131
|
-
cooldown: 10
|
|
132
|
-
max_sessions: 0
|
|
159
|
+
cooldown: 10 # seconds between issues
|
|
160
|
+
max_sessions: 0 # 0 = unlimited
|
|
133
161
|
|
|
134
162
|
logs:
|
|
135
163
|
dir: .lisa/logs
|
|
136
|
-
format: text
|
|
137
|
-
```
|
|
164
|
+
format: text # "text" or "json"
|
|
138
165
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
source_config:
|
|
146
|
-
board: Product
|
|
147
|
-
pick_from: Backlog
|
|
148
|
-
label: ready
|
|
149
|
-
in_progress: In Progress
|
|
150
|
-
done: Code Review
|
|
151
|
-
|
|
152
|
-
github: cli
|
|
153
|
-
workspace: .
|
|
154
|
-
|
|
155
|
-
loop:
|
|
156
|
-
cooldown: 10
|
|
157
|
-
max_sessions: 0
|
|
158
|
-
|
|
159
|
-
logs:
|
|
160
|
-
dir: .lisa/logs
|
|
161
|
-
format: text
|
|
166
|
+
# Optional — kill stuck providers
|
|
167
|
+
overseer:
|
|
168
|
+
enabled: true
|
|
169
|
+
check_interval: 30 # seconds between git status checks
|
|
170
|
+
stuck_threshold: 300 # seconds without git changes before killing
|
|
162
171
|
```
|
|
163
172
|
|
|
164
|
-
### Source-
|
|
173
|
+
### Source-Specific Fields
|
|
165
174
|
|
|
166
175
|
| Field | Linear | Trello |
|
|
167
176
|
|-------|--------|--------|
|
|
168
|
-
| `team`
|
|
177
|
+
| `team` | Team name | Board name |
|
|
169
178
|
| `project` | Project name | — |
|
|
170
|
-
| `pick_from` | Status to pick issues from
|
|
179
|
+
| `pick_from` | Status to pick issues from | List to pick cards from |
|
|
171
180
|
| `label` | Label to filter issues | Label to filter cards |
|
|
172
|
-
| `in_progress` | In-progress status
|
|
173
|
-
| `done` | Destination status
|
|
181
|
+
| `in_progress` | In-progress status | In-progress column |
|
|
182
|
+
| `done` | Destination status after PR | Destination column after PR |
|
|
174
183
|
|
|
175
|
-
|
|
184
|
+
### Lifecycle Resources
|
|
176
185
|
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
For repos that need services running during implementation (databases, dev servers):
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
repos:
|
|
190
|
+
- name: my-api
|
|
191
|
+
path: ./api
|
|
192
|
+
base_branch: main
|
|
193
|
+
lifecycle:
|
|
194
|
+
resources:
|
|
195
|
+
- name: postgres
|
|
196
|
+
check_port: 5432
|
|
197
|
+
up: "docker compose up -d postgres"
|
|
198
|
+
down: "docker compose down"
|
|
199
|
+
startup_timeout: 30
|
|
200
|
+
setup:
|
|
201
|
+
- "npx prisma generate"
|
|
202
|
+
- "npx prisma db push"
|
|
179
203
|
```
|
|
180
204
|
|
|
205
|
+
Lisa starts resources before the agent runs, waits for the port to be ready, runs setup commands, then stops everything after the session.
|
|
206
|
+
|
|
181
207
|
## How It Works
|
|
182
208
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
```
|
|
210
|
+
┌─────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────┐ ┌────────┐
|
|
211
|
+
│ Fetch │───▶│ Activate │───▶│ Implement │───▶│ Validate │───▶│ PR │───▶│ Update │
|
|
212
|
+
└─────────┘ └──────────┘ └───────────┘ └──────────┘ └────┘ └────────┘
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
1. **Fetch** — Pulls the next issue from Linear or Trello matching the configured label, team, and project. Issues are sorted by priority. Blocked issues (with unresolved dependencies) are skipped.
|
|
216
|
+
2. **Activate** — Moves the issue to `in_progress` so your team knows it's being worked on.
|
|
217
|
+
3. **Implement** — Builds a structured prompt with full issue context and sends it to the AI agent. The agent works in a worktree or branch, implements the change, runs validation, and commits.
|
|
218
|
+
4. **Validate** — Runs the project's test suite. If tests fail, the session is aborted and the issue reverts.
|
|
219
|
+
5. **PR** — Pushes the branch and creates a pull request referencing the original issue. If pre-push hooks fail, Lisa re-invokes the agent to fix the errors and retries (up to 2 recovery attempts).
|
|
220
|
+
6. **Update** — Moves the issue to the `done` status and removes the pickup label in a single atomic operation.
|
|
221
|
+
7. **Next** — Picks the next issue. When there are no more matching issues, Lisa stops.
|
|
222
|
+
|
|
223
|
+
### Recovery Mechanisms
|
|
224
|
+
|
|
225
|
+
- **Orphan recovery** — On startup, Lisa scans for issues stuck in `in_progress` from previous interrupted runs and reverts them to `pick_from`.
|
|
226
|
+
- **Push recovery** — If `git push` fails due to pre-push hooks (linter, typecheck, tests), Lisa re-invokes the agent with the error output and retries the push.
|
|
227
|
+
- **Signal handling** — SIGINT/SIGTERM gracefully revert the active issue to its previous status before exiting.
|
|
228
|
+
- **Guardrails** — Failed sessions are logged to `.lisa/guardrails.md` and injected into future prompts so the agent avoids repeating the same mistakes.
|
|
189
229
|
|
|
190
230
|
## License
|
|
191
231
|
|
package/dist/index.js
CHANGED
|
@@ -1488,6 +1488,56 @@ var LinearSource = class {
|
|
|
1488
1488
|
}
|
|
1489
1489
|
async attachPullRequest(_issueId, _prUrl) {
|
|
1490
1490
|
}
|
|
1491
|
+
async completeIssue(issueId, statusName, labelToRemove) {
|
|
1492
|
+
const issueData = await gql(
|
|
1493
|
+
`query($identifier: String!) {
|
|
1494
|
+
issue(id: $identifier) {
|
|
1495
|
+
id
|
|
1496
|
+
team { id }
|
|
1497
|
+
labels { nodes { id name } }
|
|
1498
|
+
}
|
|
1499
|
+
}`,
|
|
1500
|
+
{ identifier: issueId }
|
|
1501
|
+
);
|
|
1502
|
+
const statesData = await gql(
|
|
1503
|
+
`query($teamId: ID!) {
|
|
1504
|
+
workflowStates(filter: { team: { id: { eq: $teamId } } }) {
|
|
1505
|
+
nodes { id name }
|
|
1506
|
+
}
|
|
1507
|
+
}`,
|
|
1508
|
+
{ teamId: issueData.issue.team.id }
|
|
1509
|
+
);
|
|
1510
|
+
const state = statesData.workflowStates.nodes.find(
|
|
1511
|
+
(s) => s.name.toLowerCase() === statusName.toLowerCase()
|
|
1512
|
+
);
|
|
1513
|
+
if (!state) {
|
|
1514
|
+
const available = statesData.workflowStates.nodes.map((s) => s.name).join(", ");
|
|
1515
|
+
throw new Error(`Status "${statusName}" not found. Available: ${available}`);
|
|
1516
|
+
}
|
|
1517
|
+
const input = { stateId: state.id };
|
|
1518
|
+
if (labelToRemove) {
|
|
1519
|
+
const currentLabels = issueData.issue.labels.nodes;
|
|
1520
|
+
const filtered = currentLabels.filter(
|
|
1521
|
+
(l) => l.name.toLowerCase() !== labelToRemove.toLowerCase()
|
|
1522
|
+
);
|
|
1523
|
+
if (filtered.length !== currentLabels.length) {
|
|
1524
|
+
input.labelIds = filtered.map((l) => l.id);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
const mutationResult = await gql(
|
|
1528
|
+
`mutation($issueId: String!, $input: IssueUpdateInput!) {
|
|
1529
|
+
issueUpdate(id: $issueId, input: $input) {
|
|
1530
|
+
success
|
|
1531
|
+
}
|
|
1532
|
+
}`,
|
|
1533
|
+
{ issueId: issueData.issue.id, input }
|
|
1534
|
+
);
|
|
1535
|
+
if (!mutationResult.issueUpdate.success) {
|
|
1536
|
+
throw new Error(
|
|
1537
|
+
`issueUpdate returned success=false for ${issueId} (stateId: ${state.id}, stateName: ${state.name})`
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1491
1541
|
async removeLabel(issueId, labelName) {
|
|
1492
1542
|
const issueData = await gql(
|
|
1493
1543
|
`query($identifier: String!) {
|
|
@@ -1630,6 +1680,12 @@ var TrelloSource = class {
|
|
|
1630
1680
|
async attachPullRequest(cardId, prUrl) {
|
|
1631
1681
|
await trelloPost(`/cards/${cardId}/attachments`, `url=${encodeURIComponent(prUrl)}`);
|
|
1632
1682
|
}
|
|
1683
|
+
async completeIssue(cardId, listName, labelToRemove) {
|
|
1684
|
+
await this.updateStatus(cardId, listName);
|
|
1685
|
+
if (labelToRemove) {
|
|
1686
|
+
await this.removeLabel(cardId, labelToRemove);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1633
1689
|
async removeLabel(cardId, labelName) {
|
|
1634
1690
|
const card = await trelloGet(
|
|
1635
1691
|
`/cards/${cardId}`,
|
|
@@ -2082,22 +2138,16 @@ async function runLoop(config2, opts) {
|
|
|
2082
2138
|
warn(`Failed to attach PR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2083
2139
|
}
|
|
2084
2140
|
}
|
|
2085
|
-
let statusUpdated = false;
|
|
2086
2141
|
try {
|
|
2087
2142
|
const doneStatus = config2.source_config.done;
|
|
2088
|
-
|
|
2143
|
+
const labelToRemove = opts.issueId ? void 0 : config2.source_config.label;
|
|
2144
|
+
await source.completeIssue(issue.id, doneStatus, labelToRemove);
|
|
2089
2145
|
ok(`Updated ${issue.id} status to "${doneStatus}"`);
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
error(`Failed to update status: ${err instanceof Error ? err.message : String(err)}`);
|
|
2093
|
-
}
|
|
2094
|
-
if (statusUpdated && !opts.issueId) {
|
|
2095
|
-
try {
|
|
2096
|
-
await source.removeLabel(issue.id, config2.source_config.label);
|
|
2097
|
-
ok(`Removed label "${config2.source_config.label}" from ${issue.id}`);
|
|
2098
|
-
} catch (err) {
|
|
2099
|
-
error(`Failed to remove label: ${err instanceof Error ? err.message : String(err)}`);
|
|
2146
|
+
if (labelToRemove) {
|
|
2147
|
+
ok(`Removed label "${labelToRemove}" from ${issue.id}`);
|
|
2100
2148
|
}
|
|
2149
|
+
} catch (err) {
|
|
2150
|
+
error(`Failed to complete issue: ${err instanceof Error ? err.message : String(err)}`);
|
|
2101
2151
|
}
|
|
2102
2152
|
activeCleanup = null;
|
|
2103
2153
|
if (opts.once) {
|