@tarcisiopgs/lisa 1.7.4 → 1.7.6
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
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
<!-- Add a GIF or screen recording of the full pipeline here (fetch → implement → PR) -->
|
|
18
|
-
|
|
19
17
|
## Quickstart
|
|
20
18
|
|
|
21
19
|
```bash
|
|
@@ -57,9 +55,9 @@ Lisa follows a deterministic pipeline:
|
|
|
57
55
|
|
|
58
56
|
1. **Fetch** — Pulls the next issue from Linear, Trello, Plane, Shortcut, GitLab Issues, GitHub Issues, or Jira matching the configured label, team, and project. Issues are sorted by priority. Blocked issues are skipped.
|
|
59
57
|
2. **Activate** — Moves the issue to `in_progress` so your team knows it's being worked on.
|
|
60
|
-
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, and commits.
|
|
61
|
-
4. **Validate** —
|
|
62
|
-
5. **PR** — Pushes the branch and creates a pull request referencing the original issue.
|
|
58
|
+
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 tests, and commits.
|
|
59
|
+
4. **Validate** — If the agent's tests pass and pre-push hooks succeed, the branch is pushed. If hooks fail, Lisa re-invokes the agent with the error output and retries.
|
|
60
|
+
5. **PR** — Pushes the branch and creates a pull request referencing the original issue. The PR body includes a footer crediting the provider that resolved it.
|
|
63
61
|
6. **Update** — Moves the issue to the `done` status and removes the pickup label.
|
|
64
62
|
7. **Next** — Picks the next issue. When there are no more matching issues, Lisa stops.
|
|
65
63
|
|
|
@@ -75,15 +73,15 @@ Lisa follows a deterministic pipeline:
|
|
|
75
73
|
|
|
76
74
|
## Providers
|
|
77
75
|
|
|
78
|
-
| Provider |
|
|
79
|
-
|
|
80
|
-
| Claude Code | `claude` |
|
|
81
|
-
| Gemini CLI | `gemini` |
|
|
82
|
-
| OpenCode | `opencode` |
|
|
83
|
-
| GitHub Copilot CLI | `copilot` |
|
|
84
|
-
| Cursor Agent | `
|
|
85
|
-
| Goose | `goose` |
|
|
86
|
-
| Aider | `aider` |
|
|
76
|
+
| Provider | Key | Command |
|
|
77
|
+
|----------|-----|---------|
|
|
78
|
+
| Claude Code | `claude` | `claude` |
|
|
79
|
+
| Gemini CLI | `gemini` | `gemini` |
|
|
80
|
+
| OpenCode | `opencode` | `opencode` |
|
|
81
|
+
| GitHub Copilot CLI | `copilot` | `copilot` |
|
|
82
|
+
| Cursor Agent | `cursor` | `agent` / `cursor-agent` |
|
|
83
|
+
| Goose | `goose` | `goose` |
|
|
84
|
+
| Aider | `aider` | `aider` |
|
|
87
85
|
|
|
88
86
|
At least one provider must be installed and available in your PATH.
|
|
89
87
|
|
|
@@ -108,7 +106,7 @@ npm install -g @tarcisiopgs/lisa
|
|
|
108
106
|
## Environment Variables
|
|
109
107
|
|
|
110
108
|
```bash
|
|
111
|
-
# Required (at least one)
|
|
109
|
+
# Required for PR creation (at least one)
|
|
112
110
|
export GITHUB_TOKEN="" # or have `gh` CLI authenticated
|
|
113
111
|
|
|
114
112
|
# Required when source = linear
|
|
@@ -150,16 +148,47 @@ export JIRA_API_TOKEN="" # Atlassian API token
|
|
|
150
148
|
| `lisa run --limit N` | Process up to N issues |
|
|
151
149
|
| `lisa run --dry-run` | Preview without executing |
|
|
152
150
|
| `lisa run --provider NAME` | Override AI provider |
|
|
153
|
-
| `lisa run --source NAME` | Override issue source
|
|
151
|
+
| `lisa run --source NAME` | Override issue source |
|
|
154
152
|
| `lisa run --label NAME` | Override label filter |
|
|
155
|
-
| `lisa run --github METHOD` | Override GitHub method (cli
|
|
153
|
+
| `lisa run --github METHOD` | Override GitHub method (`cli` or `token`) |
|
|
156
154
|
| `lisa run --json` | Output as JSON lines |
|
|
157
155
|
| `lisa run --quiet` | Suppress non-essential output |
|
|
158
|
-
| `lisa
|
|
159
|
-
| `lisa config
|
|
160
|
-
| `lisa config --
|
|
161
|
-
| `lisa
|
|
156
|
+
| `lisa init` | Create `.lisa/config.yaml` interactively |
|
|
157
|
+
| `lisa config` | Edit config interactively |
|
|
158
|
+
| `lisa config --show` | Print current config as JSON |
|
|
159
|
+
| `lisa config --set key=value` | Set a single config value |
|
|
162
160
|
| `lisa status` | Show session stats |
|
|
161
|
+
| `lisa issue get <id>` | Fetch full issue details as JSON (for use inside worktrees) |
|
|
162
|
+
| `lisa issue done <id> --pr-url <url>` | Complete issue, attach PR, update status, remove label |
|
|
163
|
+
|
|
164
|
+
## TUI
|
|
165
|
+
|
|
166
|
+
When running in an interactive terminal, `lisa run` renders a real-time Kanban board:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
┌──────────────────────────┐ ┌───────────────────────────┐ ┌───────────────────────────┐
|
|
170
|
+
│ ▶ BACKLOG [3] │ │ ▶ IN PROGRESS [1] │ │ ▶ IN REVIEW [2] │
|
|
171
|
+
│ │ │ │ │ │
|
|
172
|
+
│ ┌────────────────────┐ │ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │
|
|
173
|
+
│ │ ENG-42 │ │ │ │ ● ENG-38 │ │ │ │ ✓ ENG-35 │ │
|
|
174
|
+
│ │ Add dark mode │ │ │ │ Fix login redirect │ │ │ │ Update dependencies │ │
|
|
175
|
+
│ │ ready │ │ │ │ ~1 running │ │ │ │ PR created │ │
|
|
176
|
+
│ └────────────────────┘ │ │ └─────────────────────┘ │ │ └─────────────────────┘ │
|
|
177
|
+
└──────────────────────────┘ └───────────────────────────┘ └───────────────────────────┘
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Keyboard shortcuts
|
|
181
|
+
|
|
182
|
+
| Key | Action |
|
|
183
|
+
|-----|--------|
|
|
184
|
+
| `Tab` | Move to next column |
|
|
185
|
+
| `Shift+Tab` | Move to previous column |
|
|
186
|
+
| `↑` / `↓` | Navigate cards within a column |
|
|
187
|
+
| `Enter` | Open issue detail view (streams provider output) |
|
|
188
|
+
| `Esc` | Close detail view, return to board |
|
|
189
|
+
| `q` | Quit |
|
|
190
|
+
|
|
191
|
+
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.
|
|
163
192
|
|
|
164
193
|
## Configuration
|
|
165
194
|
|
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/output/terminal.ts
|
|
4
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
5
|
+
var SPINNER_INTERVAL_MS = 80;
|
|
6
|
+
var spinnerTimer = null;
|
|
7
|
+
var spinnerFrame = 0;
|
|
8
|
+
function isTTY() {
|
|
9
|
+
return process.stdout.isTTY === true;
|
|
10
|
+
}
|
|
11
|
+
function writeOSC(title) {
|
|
12
|
+
process.stdout.write(`\x1B]0;${title}\x07`);
|
|
13
|
+
}
|
|
14
|
+
function setTitle(title) {
|
|
15
|
+
if (!isTTY()) return;
|
|
16
|
+
writeOSC(title);
|
|
17
|
+
}
|
|
18
|
+
function startSpinner(message) {
|
|
19
|
+
if (!isTTY()) return;
|
|
20
|
+
stopSpinner();
|
|
21
|
+
spinnerFrame = 0;
|
|
22
|
+
writeOSC(`${SPINNER_FRAMES[0]} Lisa \u2014 ${message}`);
|
|
23
|
+
spinnerTimer = setInterval(() => {
|
|
24
|
+
spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
25
|
+
writeOSC(`${SPINNER_FRAMES[spinnerFrame]} Lisa \u2014 ${message}`);
|
|
26
|
+
}, SPINNER_INTERVAL_MS);
|
|
27
|
+
}
|
|
28
|
+
function stopSpinner(message) {
|
|
29
|
+
if (spinnerTimer) {
|
|
30
|
+
clearInterval(spinnerTimer);
|
|
31
|
+
spinnerTimer = null;
|
|
32
|
+
}
|
|
33
|
+
if (!isTTY()) return;
|
|
34
|
+
if (message) {
|
|
35
|
+
writeOSC(message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function notify() {
|
|
39
|
+
if (!isTTY()) return;
|
|
40
|
+
process.stdout.write("\x07");
|
|
41
|
+
}
|
|
42
|
+
function resetTitle() {
|
|
43
|
+
if (!isTTY()) return;
|
|
44
|
+
writeOSC("");
|
|
45
|
+
}
|
|
46
|
+
|
|
3
47
|
// src/ui/state.ts
|
|
4
48
|
import { EventEmitter } from "events";
|
|
5
49
|
import { useEffect, useState } from "react";
|
|
@@ -68,6 +112,11 @@ function useKanbanState() {
|
|
|
68
112
|
}
|
|
69
113
|
|
|
70
114
|
export {
|
|
115
|
+
setTitle,
|
|
116
|
+
startSpinner,
|
|
117
|
+
stopSpinner,
|
|
118
|
+
notify,
|
|
119
|
+
resetTitle,
|
|
71
120
|
kanbanEmitter,
|
|
72
121
|
useKanbanState
|
|
73
122
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
kanbanEmitter
|
|
4
|
-
|
|
3
|
+
kanbanEmitter,
|
|
4
|
+
notify,
|
|
5
|
+
resetTitle,
|
|
6
|
+
setTitle,
|
|
7
|
+
startSpinner,
|
|
8
|
+
stopSpinner
|
|
9
|
+
} from "./chunk-YZKNBQN6.js";
|
|
5
10
|
|
|
6
11
|
// src/cli.ts
|
|
7
12
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -174,6 +179,33 @@ async function isGhCliAvailable() {
|
|
|
174
179
|
return false;
|
|
175
180
|
}
|
|
176
181
|
}
|
|
182
|
+
var PROVIDER_DISPLAY_NAMES = {
|
|
183
|
+
claude: "Claude Code",
|
|
184
|
+
gemini: "Gemini CLI",
|
|
185
|
+
opencode: "OpenCode",
|
|
186
|
+
copilot: "GitHub Copilot CLI",
|
|
187
|
+
cursor: "Cursor Agent",
|
|
188
|
+
goose: "Goose",
|
|
189
|
+
aider: "Aider"
|
|
190
|
+
};
|
|
191
|
+
function formatProviderName(providerUsed) {
|
|
192
|
+
const providerKey = providerUsed.split("/")[0] ?? providerUsed;
|
|
193
|
+
return PROVIDER_DISPLAY_NAMES[providerKey] ?? providerKey;
|
|
194
|
+
}
|
|
195
|
+
async function appendPrAttribution(prUrl, providerUsed) {
|
|
196
|
+
try {
|
|
197
|
+
const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
|
|
198
|
+
const { body } = JSON.parse(bodyJson);
|
|
199
|
+
const providerName = formatProviderName(providerUsed);
|
|
200
|
+
const attribution = `
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
\u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
|
|
204
|
+
const newBody = (body ?? "") + attribution;
|
|
205
|
+
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
177
209
|
|
|
178
210
|
// src/git/worktree.ts
|
|
179
211
|
import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
@@ -346,52 +378,6 @@ function banner() {
|
|
|
346
378
|
`));
|
|
347
379
|
}
|
|
348
380
|
|
|
349
|
-
// src/output/terminal.ts
|
|
350
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
351
|
-
var SPINNER_INTERVAL_MS = 80;
|
|
352
|
-
var spinnerTimer = null;
|
|
353
|
-
var spinnerFrame = 0;
|
|
354
|
-
function isTTY() {
|
|
355
|
-
return process.stdout.isTTY === true;
|
|
356
|
-
}
|
|
357
|
-
function writeOSC(title) {
|
|
358
|
-
process.stdout.write(`\x1B]0;${title}\x07`);
|
|
359
|
-
}
|
|
360
|
-
function setTitle(title) {
|
|
361
|
-
if (!isTTY()) return;
|
|
362
|
-
writeOSC(title);
|
|
363
|
-
}
|
|
364
|
-
function startSpinner(message) {
|
|
365
|
-
if (!isTTY()) return;
|
|
366
|
-
if (getOutputMode() === "tui") return;
|
|
367
|
-
stopSpinner();
|
|
368
|
-
spinnerFrame = 0;
|
|
369
|
-
writeOSC(`${SPINNER_FRAMES[0]} Lisa \u2014 ${message}`);
|
|
370
|
-
spinnerTimer = setInterval(() => {
|
|
371
|
-
spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
372
|
-
writeOSC(`${SPINNER_FRAMES[spinnerFrame]} Lisa \u2014 ${message}`);
|
|
373
|
-
}, SPINNER_INTERVAL_MS);
|
|
374
|
-
}
|
|
375
|
-
function stopSpinner(message) {
|
|
376
|
-
if (spinnerTimer) {
|
|
377
|
-
clearInterval(spinnerTimer);
|
|
378
|
-
spinnerTimer = null;
|
|
379
|
-
}
|
|
380
|
-
if (!isTTY()) return;
|
|
381
|
-
if (getOutputMode() === "tui") return;
|
|
382
|
-
if (message) {
|
|
383
|
-
writeOSC(message);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
function notify() {
|
|
387
|
-
if (!isTTY()) return;
|
|
388
|
-
process.stdout.write("\x07");
|
|
389
|
-
}
|
|
390
|
-
function resetTitle() {
|
|
391
|
-
if (!isTTY()) return;
|
|
392
|
-
writeOSC("");
|
|
393
|
-
}
|
|
394
|
-
|
|
395
381
|
// src/prompt.ts
|
|
396
382
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
397
383
|
import { join as join2, resolve as resolve3 } from "path";
|
|
@@ -3643,6 +3629,7 @@ ${result.output}
|
|
|
3643
3629
|
}
|
|
3644
3630
|
const worktreePath = await findWorktreeForBranch(repoPath, manifest.branch ?? "");
|
|
3645
3631
|
ok(`PR created by provider: ${manifest.prUrl}`);
|
|
3632
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
3646
3633
|
if (worktreePath) await cleanupWorktree(repoPath, worktreePath);
|
|
3647
3634
|
ok(`Session ${session} complete for ${issue2.id}`);
|
|
3648
3635
|
return {
|
|
@@ -3749,6 +3736,7 @@ ${result.output}
|
|
|
3749
3736
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
3750
3737
|
}
|
|
3751
3738
|
ok(`PR created by provider: ${manifest.prUrl}`);
|
|
3739
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
3752
3740
|
await cleanupWorktree(repoPath, worktreePath);
|
|
3753
3741
|
ok(`Session ${session} complete for ${issue2.id}`);
|
|
3754
3742
|
return {
|
|
@@ -3937,6 +3925,7 @@ ${result.output}
|
|
|
3937
3925
|
return { ...failResult(result.providerUsed, result), branch: branchName };
|
|
3938
3926
|
}
|
|
3939
3927
|
await cleanupWorktree(repoPath, worktreePath);
|
|
3928
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
3940
3929
|
ok(`Step ${stepNum} complete: ${repoPath} \u2014 PR: ${manifest.prUrl}`);
|
|
3941
3930
|
return {
|
|
3942
3931
|
success: true,
|
|
@@ -4017,6 +4006,7 @@ ${result.output}
|
|
|
4017
4006
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
4018
4007
|
}
|
|
4019
4008
|
ok(`PR created by provider: ${manifest.prUrl}`);
|
|
4009
|
+
await appendPrAttribution(manifest.prUrl, result.providerUsed);
|
|
4020
4010
|
ok(`Session ${session} complete for ${issue2.id}`);
|
|
4021
4011
|
return {
|
|
4022
4012
|
success: true,
|
|
@@ -4092,7 +4082,7 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
4092
4082
|
if (isTUI) {
|
|
4093
4083
|
const { render } = await import("ink");
|
|
4094
4084
|
const { createElement } = await import("react");
|
|
4095
|
-
const { KanbanApp } = await import("./kanban-
|
|
4085
|
+
const { KanbanApp } = await import("./kanban-YP3TJJUT.js");
|
|
4096
4086
|
render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
|
|
4097
4087
|
}
|
|
4098
4088
|
await runLoop(merged, {
|
|
@@ -4141,15 +4131,14 @@ var init = defineCommand({
|
|
|
4141
4131
|
process.exit(1);
|
|
4142
4132
|
}
|
|
4143
4133
|
if (configExists()) {
|
|
4144
|
-
const
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4134
|
+
const existing = loadConfig();
|
|
4135
|
+
clack.log.info(
|
|
4136
|
+
`Existing config found \u2014 current values will be pre-filled. Edit what you need, keep the rest.`
|
|
4137
|
+
);
|
|
4138
|
+
await runConfigWizard(existing);
|
|
4139
|
+
} else {
|
|
4140
|
+
await runConfigWizard();
|
|
4151
4141
|
}
|
|
4152
|
-
await runConfigWizard();
|
|
4153
4142
|
}
|
|
4154
4143
|
});
|
|
4155
4144
|
var status = defineCommand({
|
|
@@ -4321,14 +4310,26 @@ function fetchCursorModels() {
|
|
|
4321
4310
|
function fetchOpenCodeModels() {
|
|
4322
4311
|
try {
|
|
4323
4312
|
const raw = execSync8("opencode models", { encoding: "utf-8", timeout: 1e4 });
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
/^anthropic\/claude-(opus|sonnet|haiku)-4-\d+$/.test(m) || // Google Gemini 2.5 stable (no preview/tts suffixes)
|
|
4328
|
-
/^google\/gemini-2\.5-(pro|flash|flash-lite)$/.test(m) || // OpenCode proprietary models
|
|
4329
|
-
/^opencode\//.test(m)
|
|
4330
|
-
)
|
|
4313
|
+
const hasAnthropic = Boolean(process.env.ANTHROPIC_API_KEY);
|
|
4314
|
+
const hasGoogle = Boolean(
|
|
4315
|
+
process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY
|
|
4331
4316
|
);
|
|
4317
|
+
const hasOpenAI = Boolean(process.env.OPENAI_API_KEY);
|
|
4318
|
+
const hasCopilot = Boolean(process.env.GITHUB_COPILOT_API_KEY || process.env.GITHUB_TOKEN);
|
|
4319
|
+
const hasGroq = Boolean(process.env.GROQ_API_KEY);
|
|
4320
|
+
const hasMistral = Boolean(process.env.MISTRAL_API_KEY);
|
|
4321
|
+
const hasDeepSeek = Boolean(process.env.DEEPSEEK_API_KEY);
|
|
4322
|
+
return raw.split("\n").map((l) => l.trim()).filter((m) => {
|
|
4323
|
+
if (/^opencode\//.test(m)) return true;
|
|
4324
|
+
if (/^anthropic\/claude-(opus|sonnet|haiku)-4-\d+$/.test(m)) return hasAnthropic;
|
|
4325
|
+
if (/^google\/gemini-2\.5-(pro|flash|flash-lite)$/.test(m)) return hasGoogle;
|
|
4326
|
+
if (/^openai\//.test(m)) return hasOpenAI;
|
|
4327
|
+
if (/^github-copilot\//.test(m)) return hasCopilot;
|
|
4328
|
+
if (/^groq\//.test(m)) return hasGroq;
|
|
4329
|
+
if (/^mistral\//.test(m)) return hasMistral;
|
|
4330
|
+
if (/^deepseek\//.test(m)) return hasDeepSeek;
|
|
4331
|
+
return false;
|
|
4332
|
+
});
|
|
4332
4333
|
} catch {
|
|
4333
4334
|
return [];
|
|
4334
4335
|
}
|
|
@@ -4341,8 +4342,10 @@ var main = defineCommand({
|
|
|
4341
4342
|
},
|
|
4342
4343
|
subCommands: { run, config, init, status, issue }
|
|
4343
4344
|
});
|
|
4344
|
-
async function runConfigWizard() {
|
|
4345
|
-
clack.intro(
|
|
4345
|
+
async function runConfigWizard(existing) {
|
|
4346
|
+
clack.intro(
|
|
4347
|
+
pc2.cyan(existing ? " lisa \u266A editing config " : " lisa \u266A autonomous issue resolver ")
|
|
4348
|
+
);
|
|
4346
4349
|
const providerLabels = {
|
|
4347
4350
|
claude: "Claude Code",
|
|
4348
4351
|
gemini: "Gemini CLI",
|
|
@@ -4378,12 +4381,13 @@ async function runConfigWizard() {
|
|
|
4378
4381
|
return process.exit(1);
|
|
4379
4382
|
}
|
|
4380
4383
|
let providerName;
|
|
4381
|
-
if (available.length === 1 && available[0]) {
|
|
4384
|
+
if (available.length === 1 && available[0] && !existing) {
|
|
4382
4385
|
providerName = available[0].name;
|
|
4383
4386
|
clack.log.info(`Auto-detected ${pc2.bold(providerLabels[providerName])} as your AI provider.`);
|
|
4384
4387
|
} else {
|
|
4385
4388
|
const selected = await clack.select({
|
|
4386
4389
|
message: "Which AI provider should resolve your issues?",
|
|
4390
|
+
initialValue: existing?.provider,
|
|
4387
4391
|
options: allProviders.map(({ provider, available: isAvailable }) => ({
|
|
4388
4392
|
value: provider.name,
|
|
4389
4393
|
label: providerLabels[provider.name],
|
|
@@ -4417,6 +4421,7 @@ async function runConfigWizard() {
|
|
|
4417
4421
|
if (availableModels && availableModels.length > 0) {
|
|
4418
4422
|
const modelSelection = await clack.multiselect({
|
|
4419
4423
|
message: "Which models should Lisa use? Select in order \u2014 first = primary, rest = fallbacks",
|
|
4424
|
+
initialValues: existing?.models?.filter((m) => availableModels.includes(m)) ?? [],
|
|
4420
4425
|
options: availableModels.map((m) => ({
|
|
4421
4426
|
value: m,
|
|
4422
4427
|
label: m
|
|
@@ -4428,6 +4433,7 @@ async function runConfigWizard() {
|
|
|
4428
4433
|
}
|
|
4429
4434
|
const source = await clack.select({
|
|
4430
4435
|
message: "Where do your issues come from?",
|
|
4436
|
+
initialValue: existing?.source,
|
|
4431
4437
|
options: [
|
|
4432
4438
|
{ value: "linear", label: "Linear", apiHint: "GraphQL API", envVars: ["LINEAR_API_KEY"] },
|
|
4433
4439
|
{
|
|
@@ -4489,13 +4495,14 @@ Then reload: ${pc2.cyan(`source ${shell}`)}`
|
|
|
4489
4495
|
const githubMethod = await detectGitHubMethod();
|
|
4490
4496
|
const teamAnswer = await clack.text({
|
|
4491
4497
|
message: source === "linear" ? "What is your Linear team name?" : source === "trello" ? "What is your Trello board name?" : source === "jira" ? "What is your Jira project key?" : "What is your team or project name?",
|
|
4498
|
+
initialValue: existing?.source_config.team ?? "",
|
|
4492
4499
|
placeholder: source === "linear" ? "e.g. Engineering" : void 0
|
|
4493
4500
|
});
|
|
4494
4501
|
if (clack.isCancel(teamAnswer)) return process.exit(0);
|
|
4495
4502
|
const team = teamAnswer;
|
|
4496
4503
|
const labelAnswer = await clack.text({
|
|
4497
4504
|
message: "Which label marks issues as ready for the agent to pick up?",
|
|
4498
|
-
initialValue: "ready",
|
|
4505
|
+
initialValue: existing?.source_config.label ?? "ready",
|
|
4499
4506
|
placeholder: "e.g. ready, ai, lisa"
|
|
4500
4507
|
});
|
|
4501
4508
|
if (clack.isCancel(labelAnswer)) return process.exit(0);
|
|
@@ -4507,52 +4514,54 @@ Then reload: ${pc2.cyan(`source ${shell}`)}`
|
|
|
4507
4514
|
if (source === "trello") {
|
|
4508
4515
|
const pickFromAnswer = await clack.text({
|
|
4509
4516
|
message: "Pick up cards from which list?",
|
|
4510
|
-
initialValue: "Backlog"
|
|
4517
|
+
initialValue: existing?.source_config.pick_from ?? "Backlog"
|
|
4511
4518
|
});
|
|
4512
4519
|
if (clack.isCancel(pickFromAnswer)) return process.exit(0);
|
|
4513
4520
|
pickFrom = pickFromAnswer;
|
|
4514
4521
|
project = pickFrom;
|
|
4515
4522
|
const inProgressAnswer = await clack.text({
|
|
4516
4523
|
message: "Move the card to which list while the agent is working?",
|
|
4517
|
-
initialValue: "In Progress"
|
|
4524
|
+
initialValue: existing?.source_config.in_progress ?? "In Progress"
|
|
4518
4525
|
});
|
|
4519
4526
|
if (clack.isCancel(inProgressAnswer)) return process.exit(0);
|
|
4520
4527
|
inProgress = inProgressAnswer;
|
|
4521
4528
|
const doneAnswer = await clack.text({
|
|
4522
4529
|
message: "Move the card to which list after the PR is created?",
|
|
4523
|
-
initialValue: "Code Review"
|
|
4530
|
+
initialValue: existing?.source_config.done ?? "Code Review"
|
|
4524
4531
|
});
|
|
4525
4532
|
if (clack.isCancel(doneAnswer)) return process.exit(0);
|
|
4526
4533
|
done = doneAnswer;
|
|
4527
4534
|
} else {
|
|
4528
4535
|
const projectAnswer = await clack.text({
|
|
4529
4536
|
message: source === "linear" ? "Which Linear project should Lisa work on? (leave empty for all team issues)" : "Which project should Lisa work on?",
|
|
4537
|
+
initialValue: existing?.source_config.project ?? "",
|
|
4530
4538
|
placeholder: source === "linear" ? "e.g. Q1 Roadmap (optional)" : void 0
|
|
4531
4539
|
});
|
|
4532
4540
|
if (clack.isCancel(projectAnswer)) return process.exit(0);
|
|
4533
4541
|
project = projectAnswer;
|
|
4534
4542
|
const pickFromAnswer = await clack.text({
|
|
4535
4543
|
message: "Pick up issues in which status?",
|
|
4536
|
-
initialValue: "Backlog",
|
|
4544
|
+
initialValue: existing?.source_config.pick_from ?? "Backlog",
|
|
4537
4545
|
placeholder: "e.g. Backlog, Todo"
|
|
4538
4546
|
});
|
|
4539
4547
|
if (clack.isCancel(pickFromAnswer)) return process.exit(0);
|
|
4540
4548
|
pickFrom = pickFromAnswer;
|
|
4541
4549
|
const inProgressAnswer = await clack.text({
|
|
4542
4550
|
message: "Move to which status while the agent is working?",
|
|
4543
|
-
initialValue: "In Progress"
|
|
4551
|
+
initialValue: existing?.source_config.in_progress ?? "In Progress"
|
|
4544
4552
|
});
|
|
4545
4553
|
if (clack.isCancel(inProgressAnswer)) return process.exit(0);
|
|
4546
4554
|
inProgress = inProgressAnswer;
|
|
4547
4555
|
const doneAnswer = await clack.text({
|
|
4548
4556
|
message: "Move to which status after the PR is created?",
|
|
4549
|
-
initialValue: "In Review"
|
|
4557
|
+
initialValue: existing?.source_config.done ?? "In Review"
|
|
4550
4558
|
});
|
|
4551
4559
|
if (clack.isCancel(doneAnswer)) return process.exit(0);
|
|
4552
4560
|
done = doneAnswer;
|
|
4553
4561
|
}
|
|
4554
4562
|
const workflowAnswer = await clack.select({
|
|
4555
4563
|
message: "How should Lisa check out code for each issue?",
|
|
4564
|
+
initialValue: existing?.workflow,
|
|
4556
4565
|
options: [
|
|
4557
4566
|
{
|
|
4558
4567
|
value: "worktree",
|
|
@@ -4572,7 +4581,7 @@ Then reload: ${pc2.cyan(`source ${shell}`)}`
|
|
|
4572
4581
|
let baseBranch = "main";
|
|
4573
4582
|
const cwd = process.cwd();
|
|
4574
4583
|
if (repos.length === 0) {
|
|
4575
|
-
const detected = detectDefaultBranch(cwd);
|
|
4584
|
+
const detected = existing?.base_branch ?? detectDefaultBranch(cwd);
|
|
4576
4585
|
const branchAnswer = await clack.text({
|
|
4577
4586
|
message: "What is the base branch to branch off from?",
|
|
4578
4587
|
initialValue: detected
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
kanbanEmitter,
|
|
4
|
+
resetTitle,
|
|
5
|
+
startSpinner,
|
|
6
|
+
stopSpinner,
|
|
4
7
|
useKanbanState
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YZKNBQN6.js";
|
|
6
9
|
|
|
7
10
|
// src/ui/kanban.tsx
|
|
8
11
|
import { Box as Box6, useApp, useInput as useInput2 } from "ink";
|
|
@@ -109,6 +112,7 @@ function Column({ label, cards, isFocused = false, activeCardIndex = 0 }) {
|
|
|
109
112
|
{
|
|
110
113
|
flexDirection: "column",
|
|
111
114
|
flexGrow: 1,
|
|
115
|
+
flexBasis: 0,
|
|
112
116
|
borderStyle: "single",
|
|
113
117
|
borderColor,
|
|
114
118
|
paddingX: 1,
|
|
@@ -344,11 +348,13 @@ function IssueDetail({ card, onBack }) {
|
|
|
344
348
|
}
|
|
345
349
|
|
|
346
350
|
// src/ui/sidebar.tsx
|
|
347
|
-
import {
|
|
351
|
+
import { existsSync } from "fs";
|
|
352
|
+
import { basename, join } from "path";
|
|
348
353
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
349
354
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
350
355
|
function Sidebar({ provider, source, cwd }) {
|
|
351
356
|
const dir = basename(cwd);
|
|
357
|
+
const cwdLabel = existsSync(join(cwd, ".git")) ? "REPOSITORY" : "WORKSPACE";
|
|
352
358
|
return /* @__PURE__ */ jsxs5(
|
|
353
359
|
Box5,
|
|
354
360
|
{
|
|
@@ -390,7 +396,7 @@ function Sidebar({ provider, source, cwd }) {
|
|
|
390
396
|
] })
|
|
391
397
|
] }),
|
|
392
398
|
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
393
|
-
/* @__PURE__ */ jsx5(Text5, { color: "white", dimColor: true, children:
|
|
399
|
+
/* @__PURE__ */ jsx5(Text5, { color: "white", dimColor: true, children: cwdLabel }),
|
|
394
400
|
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
395
401
|
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u25B8 " }),
|
|
396
402
|
/* @__PURE__ */ jsx5(Text5, { color: "white", bold: true, children: dir.length > 18 ? `${dir.slice(0, 15)}\u2026` : dir })
|
|
@@ -426,6 +432,17 @@ function KanbanApp({ config }) {
|
|
|
426
432
|
}, [exit]);
|
|
427
433
|
const backlog = cards.filter((c) => c.column === "backlog");
|
|
428
434
|
const inProgress = cards.filter((c) => c.column === "in_progress");
|
|
435
|
+
useEffect3(() => {
|
|
436
|
+
if (workComplete) {
|
|
437
|
+
stopSpinner("Lisa \u2014 done \u2713");
|
|
438
|
+
} else if (inProgress.length > 0) {
|
|
439
|
+
const ids = inProgress.map((c) => c.id).join(", ");
|
|
440
|
+
startSpinner(ids);
|
|
441
|
+
} else {
|
|
442
|
+
stopSpinner("Lisa \u266A");
|
|
443
|
+
}
|
|
444
|
+
return () => resetTitle();
|
|
445
|
+
}, [inProgress, workComplete]);
|
|
429
446
|
const done = cards.filter((c) => c.column === "done");
|
|
430
447
|
const columnCards = [backlog, inProgress, done];
|
|
431
448
|
useInput2((input, key) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tarcisiopgs/lisa",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.7.6",
|
|
4
|
+
"description": "Autonomous issue resolver",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|