@tarcisiopgs/lisa 1.28.0 → 1.28.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Lisa
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<strong>
|
|
4
|
+
<strong>Plan issues. Run agents. Get PRs.</strong>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -15,23 +15,23 @@
|
|
|
15
15
|
<img src="assets/demo.gif" alt="Lisa demo" />
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
Lisa connects your issue tracker to an AI coding agent and delivers pull requests — autonomously.
|
|
18
|
+
Lisa connects your issue tracker to an AI coding agent and delivers pull requests — autonomously. Describe a goal, Lisa decomposes it into issues, picks them up, implements each one, opens PRs, and updates your board. No babysitting.
|
|
19
19
|
|
|
20
20
|
## Quickstart
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
npm install -g @tarcisiopgs/lisa
|
|
24
24
|
lisa init # interactive setup wizard
|
|
25
|
-
lisa
|
|
25
|
+
lisa # start the agent loop
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## How It Works
|
|
29
29
|
|
|
30
30
|
```
|
|
31
|
-
|
|
31
|
+
Plan → Create issues → Fetch → Implement → Push → Open PR → Update board → Next
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Lisa picks the highest-priority labeled issue, moves it to "In Progress", sends a structured prompt to the AI agent, and monitors execution. The agent works in an isolated git worktree, implements the change, runs tests, and commits. Lisa pushes, opens a PR, moves the ticket to "In Review", and picks up the next one.
|
|
34
|
+
Lisa starts and shows a Kanban board. If the queue is empty, press `n` to plan — describe a goal and the AI decomposes it into atomic issues, created directly in your tracker. Press `r` to start processing. Lisa picks the highest-priority labeled issue, moves it to "In Progress", sends a structured prompt to the AI agent, and monitors execution. The agent works in an isolated git worktree, implements the change, runs tests, and commits. Lisa pushes, opens a PR, moves the ticket to "In Review", and picks up the next one.
|
|
35
35
|
|
|
36
36
|
If something fails — pre-push hooks, quota limits, stuck processes — Lisa handles it: retries with error context, falls back to the next model, or kills and moves on.
|
|
37
37
|
|
|
@@ -39,13 +39,13 @@ If something fails — pre-push hooks, quota limits, stuck processes — Lisa ha
|
|
|
39
39
|
|
|
40
40
|
- **7 issue trackers** — Linear, GitHub Issues, GitLab Issues, Jira, Trello, Plane, Shortcut
|
|
41
41
|
- **8 AI agents** — Claude Code, Gemini CLI, GitHub Copilot CLI, Cursor Agent, Aider, Goose, OpenCode, Codex
|
|
42
|
+
- **AI planning** — describe a goal, the AI decomposes it into issues with dependencies, created in your tracker
|
|
42
43
|
- **Concurrent execution** — process multiple issues in parallel, each in its own worktree
|
|
43
44
|
- **Multi-repo** — plans across repos, creates one PR per repo in the correct order
|
|
44
45
|
- **Model fallback** — chain models; transient errors (429, quota, timeout) auto-switch to the next
|
|
45
|
-
- **Real-time TUI** — Kanban board with live provider output,
|
|
46
|
+
- **Real-time TUI** — Kanban board with live provider output, plan mode, PR merge detection
|
|
46
47
|
- **Self-healing** — orphan recovery on startup, push failure retry, stuck process detection
|
|
47
48
|
- **Guardrails** — past failures are injected into future prompts to avoid repeating mistakes
|
|
48
|
-
- **AI planning** — `lisa plan` decomposes goals into atomic issues with dependencies, creates them in your tracker
|
|
49
49
|
- **Project context** — auto-generates `.lisa/context.md` with your stack, conventions, and constraints
|
|
50
50
|
|
|
51
51
|
## Providers
|
|
@@ -83,21 +83,21 @@ provider_options:
|
|
|
83
83
|
## Commands
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
lisa
|
|
87
|
-
lisa
|
|
88
|
-
lisa
|
|
89
|
-
lisa
|
|
90
|
-
lisa
|
|
91
|
-
lisa
|
|
92
|
-
lisa
|
|
93
|
-
lisa plan "Add rate limiting" # decompose goal into issues via AI
|
|
94
|
-
lisa plan --issue EPIC-123
|
|
95
|
-
lisa plan --continue
|
|
96
|
-
lisa init
|
|
97
|
-
lisa status
|
|
98
|
-
lisa doctor
|
|
99
|
-
lisa context refresh
|
|
100
|
-
lisa feedback --pr URL
|
|
86
|
+
lisa # start the agent loop (Kanban TUI)
|
|
87
|
+
lisa --once # process a single issue
|
|
88
|
+
lisa --once --dry-run # preview config without executing
|
|
89
|
+
lisa --watch # poll for new issues after queue empties
|
|
90
|
+
lisa -c 3 # process 3 issues in parallel
|
|
91
|
+
lisa --issue INT-42 # process a specific issue
|
|
92
|
+
lisa --limit 5 # stop after 5 issues
|
|
93
|
+
lisa plan "Add rate limiting" # decompose goal into issues via AI (CLI mode)
|
|
94
|
+
lisa plan --issue EPIC-123 # decompose existing issue into sub-issues
|
|
95
|
+
lisa plan --continue # resume interrupted plan
|
|
96
|
+
lisa init # create .lisa/config.yaml interactively
|
|
97
|
+
lisa status # show session stats
|
|
98
|
+
lisa doctor # diagnose setup issues (config, provider, env, git)
|
|
99
|
+
lisa context refresh # regenerate project context
|
|
100
|
+
lisa feedback --pr URL # inject PR review feedback into guardrails
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Append `--json` to any command for machine-readable output. Use `--verbose` / `--quiet` to control log verbosity.
|
|
@@ -242,16 +242,16 @@ Acceptance criteria:
|
|
|
242
242
|
|
|
243
243
|
## TUI
|
|
244
244
|
|
|
245
|
-
The real-time Kanban board shows issue progress, streams provider output, and detects PR merges.
|
|
245
|
+
The real-time Kanban board shows issue progress, streams provider output, and detects PR merges. When the queue is empty, Lisa enters idle mode — plan new issues with `n`, then start processing with `r`.
|
|
246
246
|
|
|
247
247
|
**Board view**
|
|
248
248
|
|
|
249
249
|
| Key | Action | Key | Action |
|
|
250
250
|
|-----|--------|-----|--------|
|
|
251
|
-
| `←` `→` | Switch columns | `
|
|
252
|
-
|
|
|
253
|
-
|
|
|
254
|
-
|
|
|
251
|
+
| `←` `→` | Switch columns | `k` | Kill current issue |
|
|
252
|
+
| `↑` `↓` | Navigate cards | `n` | Open plan mode |
|
|
253
|
+
| `↵` | Open detail view | `r` | Run (from idle) |
|
|
254
|
+
| `p` | Pause / resume | `q` | Quit |
|
|
255
255
|
|
|
256
256
|
**Detail view**
|
|
257
257
|
|
|
@@ -261,6 +261,16 @@ The real-time Kanban board shows issue progress, streams provider output, and de
|
|
|
261
261
|
| `o` | Open PR in browser |
|
|
262
262
|
| `Esc` | Back to board |
|
|
263
263
|
|
|
264
|
+
**Plan mode**
|
|
265
|
+
|
|
266
|
+
| Key | Action |
|
|
267
|
+
|-----|--------|
|
|
268
|
+
| `↵` | Send message / view detail |
|
|
269
|
+
| `e` | Edit issue in $EDITOR |
|
|
270
|
+
| `d` | Delete issue |
|
|
271
|
+
| `a` | Approve and create issues |
|
|
272
|
+
| `Esc` | Cancel / back |
|
|
273
|
+
|
|
264
274
|
## License
|
|
265
275
|
|
|
266
276
|
[MIT](LICENSE)
|
|
@@ -2917,18 +2917,56 @@ function installSignalHandlers(onBeforeExit) {
|
|
|
2917
2917
|
|
|
2918
2918
|
// src/loop/demo.ts
|
|
2919
2919
|
async function runDemoLoop() {
|
|
2920
|
-
const demoIssues = [
|
|
2921
|
-
{ id: "INT-514", title: "Dark mode UI" },
|
|
2922
|
-
{ id: "INT-513", title: "WebSocket leak fix" },
|
|
2923
|
-
{ id: "INT-512", title: "Rate limiter middleware" },
|
|
2924
|
-
{ id: "INT-511", title: "Sidebar navigation icons" },
|
|
2925
|
-
{ id: "INT-510", title: "Blog post CRUD" },
|
|
2926
|
-
{ id: "INT-509", title: "Admin FAQ management" },
|
|
2927
|
-
{ id: "INT-508", title: "Changelog CRUD" }
|
|
2928
|
-
];
|
|
2929
2920
|
await sleep(3e3);
|
|
2930
2921
|
kanbanEmitter.emit("provider:model-changed", "claude-sonnet-4-6");
|
|
2931
|
-
|
|
2922
|
+
kanbanEmitter.emit("work:empty");
|
|
2923
|
+
await sleep(2e3);
|
|
2924
|
+
kanbanEmitter.emit("demo:open-plan", "Add a FAQ section to the web app");
|
|
2925
|
+
await sleep(2500);
|
|
2926
|
+
const plannedIssues = [
|
|
2927
|
+
{
|
|
2928
|
+
title: "Add FAQ shared types to @playground/shared",
|
|
2929
|
+
description: "Create TypeScript interfaces for FAQ data models",
|
|
2930
|
+
order: 1,
|
|
2931
|
+
dependsOn: [],
|
|
2932
|
+
relevantFiles: ["packages/shared/src/types/faq.ts"],
|
|
2933
|
+
acceptanceCriteria: ["FAQ type exported", "Tests pass"]
|
|
2934
|
+
},
|
|
2935
|
+
{
|
|
2936
|
+
title: "Create GET /faq API route returning FAQ data",
|
|
2937
|
+
description: "Add Fastify route that returns FAQ entries as JSON",
|
|
2938
|
+
order: 2,
|
|
2939
|
+
dependsOn: [1],
|
|
2940
|
+
relevantFiles: ["apps/api/src/routes/faq.ts"],
|
|
2941
|
+
acceptanceCriteria: ["GET /faq returns 200", "Response matches schema"]
|
|
2942
|
+
},
|
|
2943
|
+
{
|
|
2944
|
+
title: "Create FAQ page with accordion UI",
|
|
2945
|
+
description: "Build the FAQ page component with expandable sections",
|
|
2946
|
+
order: 3,
|
|
2947
|
+
dependsOn: [1, 2],
|
|
2948
|
+
relevantFiles: ["apps/web/src/pages/faq.tsx"],
|
|
2949
|
+
acceptanceCriteria: ["Page renders FAQ items", "Accordion expands/collapses"]
|
|
2950
|
+
},
|
|
2951
|
+
{
|
|
2952
|
+
title: "Add FAQ link to the navigation menu",
|
|
2953
|
+
description: "Add a navigation entry pointing to the FAQ page",
|
|
2954
|
+
order: 4,
|
|
2955
|
+
dependsOn: [3],
|
|
2956
|
+
relevantFiles: ["apps/web/src/components/nav.tsx"],
|
|
2957
|
+
acceptanceCriteria: ["Link visible in nav", "Navigates to /faq"]
|
|
2958
|
+
}
|
|
2959
|
+
];
|
|
2960
|
+
kanbanEmitter.emit("plan:issues-ready", plannedIssues);
|
|
2961
|
+
await sleep(3e3);
|
|
2962
|
+
kanbanEmitter.emit("demo:approve-plan");
|
|
2963
|
+
await sleep(500);
|
|
2964
|
+
const demoIssues = [
|
|
2965
|
+
{ id: "INT-601", title: "Add FAQ shared types to @playground/shared" },
|
|
2966
|
+
{ id: "INT-602", title: "Create GET /faq API route returning FAQ data" },
|
|
2967
|
+
{ id: "INT-603", title: "Create FAQ page with accordion UI" },
|
|
2968
|
+
{ id: "INT-604", title: "Add FAQ link to the navigation menu" }
|
|
2969
|
+
];
|
|
2932
2970
|
for (const issue of demoIssues) {
|
|
2933
2971
|
kanbanEmitter.emit("issue:queued", {
|
|
2934
2972
|
id: issue.id,
|
|
@@ -2938,57 +2976,27 @@ async function runDemoLoop() {
|
|
|
2938
2976
|
});
|
|
2939
2977
|
await sleep(200);
|
|
2940
2978
|
}
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
"
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
kanbanEmitter.emit("issue:started", issue2.id);
|
|
2961
|
-
const outputs2 = [
|
|
2962
|
-
"Reading issue description...\n",
|
|
2963
|
-
"Locating WebSocket connection handler...\n",
|
|
2964
|
-
"Patching connection lifecycle in src/ws/handler.ts...\n",
|
|
2965
|
-
"Adding cleanup in disconnect callback...\n",
|
|
2966
|
-
"Running tests... all passing \u2713\n",
|
|
2967
|
-
"Pushing branch int-513-fix-ws-leak...\n"
|
|
2968
|
-
];
|
|
2969
|
-
for (const line of outputs2) {
|
|
2970
|
-
kanbanEmitter.emit("issue:output", issue2.id, line);
|
|
2971
|
-
await sleep(500);
|
|
2972
|
-
}
|
|
2973
|
-
kanbanEmitter.emit("issue:done", issue2.id, ["https://github.com/acme/webapp/pull/92"]);
|
|
2974
|
-
await sleep(1e3);
|
|
2975
|
-
const issue3 = demoIssues[2];
|
|
2976
|
-
kanbanEmitter.emit("issue:started", issue3.id);
|
|
2977
|
-
const outputs3 = [
|
|
2978
|
-
"Reading issue description...\n",
|
|
2979
|
-
"Creating src/middleware/rateLimiter.ts...\n",
|
|
2980
|
-
"Writing sliding window rate limiter...\n",
|
|
2981
|
-
"Adding tests in rateLimiter.test.ts...\n",
|
|
2982
|
-
"Running tests... all passing \u2713\n",
|
|
2983
|
-
"Pushing branch int-512-rate-limiting...\n"
|
|
2984
|
-
];
|
|
2985
|
-
for (const line of outputs3) {
|
|
2986
|
-
kanbanEmitter.emit("issue:output", issue3.id, line);
|
|
2987
|
-
await sleep(500);
|
|
2979
|
+
kanbanEmitter.emit("work:resumed");
|
|
2980
|
+
await sleep(1500);
|
|
2981
|
+
for (let i = 0; i < demoIssues.length; i++) {
|
|
2982
|
+
const issue = demoIssues[i];
|
|
2983
|
+
kanbanEmitter.emit("issue:started", issue.id);
|
|
2984
|
+
const steps = [
|
|
2985
|
+
"Reading issue description...\n",
|
|
2986
|
+
"Analyzing codebase...\n",
|
|
2987
|
+
"Implementing changes...\n",
|
|
2988
|
+
"Running tests... all passing \u2713\n",
|
|
2989
|
+
`Pushing branch feat/${issue.id.toLowerCase()}...
|
|
2990
|
+
`
|
|
2991
|
+
];
|
|
2992
|
+
for (const step of steps) {
|
|
2993
|
+
kanbanEmitter.emit("issue:output", issue.id, step);
|
|
2994
|
+
await sleep(400);
|
|
2995
|
+
}
|
|
2996
|
+
kanbanEmitter.emit("issue:done", issue.id, [`https://github.com/acme/webapp/pull/${90 + i}`]);
|
|
2997
|
+
await sleep(800);
|
|
2988
2998
|
}
|
|
2989
|
-
kanbanEmitter.emit("
|
|
2990
|
-
await sleep(1e3);
|
|
2991
|
-
kanbanEmitter.emit("work:complete", { total: 3, duration: 127e3 });
|
|
2999
|
+
kanbanEmitter.emit("work:complete", { total: 4, duration: 185e3 });
|
|
2992
3000
|
await sleep(4e3);
|
|
2993
3001
|
kanbanEmitter.emit("tui:exit");
|
|
2994
3002
|
}
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
runLoop,
|
|
37
37
|
saveConfig,
|
|
38
38
|
validateConfig
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-KDAXGOFF.js";
|
|
40
40
|
import {
|
|
41
41
|
createProvider,
|
|
42
42
|
createSource,
|
|
@@ -1286,7 +1286,7 @@ async function reviewAndCreate(plan2, planPath, opts) {
|
|
|
1286
1286
|
log("Run `lisa run` when ready.");
|
|
1287
1287
|
return;
|
|
1288
1288
|
}
|
|
1289
|
-
const { runLoop: runLoop2 } = await import("./loop-
|
|
1289
|
+
const { runLoop: runLoop2 } = await import("./loop-SXI4PQOI.js");
|
|
1290
1290
|
await runLoop2(config2, {
|
|
1291
1291
|
once: false,
|
|
1292
1292
|
watch: false,
|
|
@@ -1637,7 +1637,7 @@ async function executeRun(args) {
|
|
|
1637
1637
|
if (isTTY) {
|
|
1638
1638
|
const { render } = await import("ink");
|
|
1639
1639
|
const { createElement } = await import("react");
|
|
1640
|
-
const { KanbanApp } = await import("./kanban-
|
|
1640
|
+
const { KanbanApp } = await import("./kanban-HFUBTOF4.js");
|
|
1641
1641
|
const demoConfig = {
|
|
1642
1642
|
provider: "claude",
|
|
1643
1643
|
source: "linear",
|
|
@@ -1745,7 +1745,7 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
1745
1745
|
};
|
|
1746
1746
|
const { render } = await import("ink");
|
|
1747
1747
|
const { createElement } = await import("react");
|
|
1748
|
-
const { KanbanApp } = await import("./kanban-
|
|
1748
|
+
const { KanbanApp } = await import("./kanban-HFUBTOF4.js");
|
|
1749
1749
|
render(createElement(KanbanApp, { config: merged, initialCards }), { exitOnCtrlC: false });
|
|
1750
1750
|
}
|
|
1751
1751
|
await runLoop(merged, {
|
|
@@ -1016,15 +1016,28 @@ function KanbanApp({ config, initialCards = [] }) {
|
|
|
1016
1016
|
return next;
|
|
1017
1017
|
});
|
|
1018
1018
|
};
|
|
1019
|
+
const onDemoOpenPlan = (userMessage) => {
|
|
1020
|
+
setActiveView("plan-chat");
|
|
1021
|
+
setPlanMessages([{ role: "user", content: userMessage }]);
|
|
1022
|
+
setPlanGoal(userMessage);
|
|
1023
|
+
setPlanThinking(true);
|
|
1024
|
+
};
|
|
1025
|
+
const onDemoApprovePlan = () => {
|
|
1026
|
+
setActiveView("board");
|
|
1027
|
+
};
|
|
1019
1028
|
kanbanEmitter.on("plan:ai-message", onAiMessage);
|
|
1020
1029
|
kanbanEmitter.on("plan:thinking", onThinking);
|
|
1021
1030
|
kanbanEmitter.on("plan:issues-ready", onIssuesReady);
|
|
1022
1031
|
kanbanEmitter.on("plan:edit-result", onEditResult);
|
|
1032
|
+
kanbanEmitter.on("demo:open-plan", onDemoOpenPlan);
|
|
1033
|
+
kanbanEmitter.on("demo:approve-plan", onDemoApprovePlan);
|
|
1023
1034
|
return () => {
|
|
1024
1035
|
kanbanEmitter.off("plan:ai-message", onAiMessage);
|
|
1025
1036
|
kanbanEmitter.off("plan:thinking", onThinking);
|
|
1026
1037
|
kanbanEmitter.off("plan:issues-ready", onIssuesReady);
|
|
1027
1038
|
kanbanEmitter.off("plan:edit-result", onEditResult);
|
|
1039
|
+
kanbanEmitter.off("demo:open-plan", onDemoOpenPlan);
|
|
1040
|
+
kanbanEmitter.off("demo:approve-plan", onDemoApprovePlan);
|
|
1028
1041
|
};
|
|
1029
1042
|
}, []);
|
|
1030
1043
|
const backlog = [...cards.filter((c) => c.column === "backlog")].sort((a, b) => {
|