@tarcisiopgs/lisa 1.13.2 → 1.14.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 +150 -10
- package/dist/chunk-72CYGBT4.js +53 -0
- package/dist/{chunk-JSRHJYX2.js → chunk-Z6CNJAZF.js} +18 -60
- package/dist/index.js +5241 -4895
- package/dist/{kanban-KE4QMDQM.js → kanban-VSEZ7NUK.js} +137 -208
- package/dist/terminal-X7O55P6E.js +15 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img src="assets/demo.gif" alt="Lisa demo" width="800" />
|
|
12
|
+
<img src="assets/demo.gif?v=2" alt="Lisa demo" width="800" />
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
@@ -86,6 +86,98 @@ Lisa follows a deterministic pipeline:
|
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
89
|
+
## Writing Issues
|
|
90
|
+
|
|
91
|
+
Issue quality is the single biggest factor in PR quality. Lisa validates issues before accepting them — vague tickets without clear criteria are skipped automatically (labelled `needs-spec`). A well-structured issue gives the AI agent everything it needs to deliver a clean implementation on the first try.
|
|
92
|
+
|
|
93
|
+
### What Lisa looks for
|
|
94
|
+
|
|
95
|
+
At minimum, the issue description must contain **acceptance criteria**. Lisa detects them by looking for:
|
|
96
|
+
|
|
97
|
+
- Markdown checklists (`- [ ]`)
|
|
98
|
+
- Keywords: `acceptance criteria`, `expected`, `should`, `deve`, `critérios`
|
|
99
|
+
|
|
100
|
+
Issues without these are rejected. Beyond validation, the agent also consumes **relevant files**, **technical constraints**, and **stack information** when present in the description — so including them leads to better results.
|
|
101
|
+
|
|
102
|
+
### Feature
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
Title: Add rate limiting to /api/users endpoint
|
|
106
|
+
|
|
107
|
+
Description:
|
|
108
|
+
Implement rate limiting on the `/api/users` endpoint to prevent abuse.
|
|
109
|
+
|
|
110
|
+
Relevant files:
|
|
111
|
+
- src/routes/users.ts
|
|
112
|
+
- src/middleware/auth.ts
|
|
113
|
+
|
|
114
|
+
Acceptance criteria:
|
|
115
|
+
- [ ] Requests exceeding 100/min per IP return HTTP 429
|
|
116
|
+
- [ ] Rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) included in all responses
|
|
117
|
+
- [ ] Rate limit state stored in Redis (use existing connection from src/lib/redis.ts)
|
|
118
|
+
- [ ] Existing tests still pass
|
|
119
|
+
|
|
120
|
+
Stack: Express, Redis
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Bug fix
|
|
124
|
+
|
|
125
|
+
```markdown
|
|
126
|
+
Title: Login redirect loop when session expires
|
|
127
|
+
|
|
128
|
+
Description:
|
|
129
|
+
Users report being stuck in an infinite redirect loop after their session expires.
|
|
130
|
+
The issue is in the session middleware — it redirects to /login, but /login also
|
|
131
|
+
checks for a valid session and redirects back.
|
|
132
|
+
|
|
133
|
+
Steps to reproduce:
|
|
134
|
+
1. Log in
|
|
135
|
+
2. Wait for session to expire (or manually clear the session cookie)
|
|
136
|
+
3. Navigate to any protected route
|
|
137
|
+
|
|
138
|
+
Relevant files:
|
|
139
|
+
- src/middleware/session.ts
|
|
140
|
+
- src/routes/auth.ts
|
|
141
|
+
|
|
142
|
+
Expected behavior:
|
|
143
|
+
- [ ] Expired sessions redirect to /login without a loop
|
|
144
|
+
- [ ] /login route should not require an active session
|
|
145
|
+
- [ ] After login, user is redirected back to the originally requested page
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Refactor
|
|
149
|
+
|
|
150
|
+
```markdown
|
|
151
|
+
Title: Extract email sending into a dedicated service
|
|
152
|
+
|
|
153
|
+
Description:
|
|
154
|
+
Email sending logic is duplicated across 3 controllers. Extract it into a
|
|
155
|
+
shared service.
|
|
156
|
+
|
|
157
|
+
Relevant files:
|
|
158
|
+
- src/controllers/auth.ts (lines 45-67: welcome email)
|
|
159
|
+
- src/controllers/orders.ts (lines 120-145: order confirmation)
|
|
160
|
+
- src/controllers/support.ts (lines 30-55: ticket confirmation)
|
|
161
|
+
|
|
162
|
+
Acceptance criteria:
|
|
163
|
+
- [ ] New EmailService class in src/services/email.ts
|
|
164
|
+
- [ ] All 3 controllers use the shared service instead of inline email logic
|
|
165
|
+
- [ ] Existing tests still pass
|
|
166
|
+
- [ ] No new dependencies added
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### What makes a good issue
|
|
170
|
+
|
|
171
|
+
| Do | Don't |
|
|
172
|
+
|---|---|
|
|
173
|
+
| List specific files the agent should read | Say "fix the bug" with no context |
|
|
174
|
+
| Include acceptance criteria as a checklist | Describe the desired outcome vaguely |
|
|
175
|
+
| Mention the stack or libraries to use | Assume the agent knows your preferences |
|
|
176
|
+
| Describe expected behavior for bug fixes | Provide only steps to reproduce |
|
|
177
|
+
| Scope to a single deliverable | Bundle multiple unrelated changes |
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
89
181
|
## Issue Sources
|
|
90
182
|
|
|
91
183
|
Lisa integrates with **7 project trackers** out of the box:
|
|
@@ -205,10 +297,6 @@ export GITHUB_TOKEN="" # same token used for PR creation
|
|
|
205
297
|
export JIRA_BASE_URL="" # e.g. https://yourcompany.atlassian.net
|
|
206
298
|
export JIRA_EMAIL="" # Atlassian account email
|
|
207
299
|
export JIRA_API_TOKEN="" # Atlassian API token
|
|
208
|
-
|
|
209
|
-
# Optional — anonymous crash reporting (disabled by default)
|
|
210
|
-
export LISA_TELEMETRY=1 # enable anonymous crash/error reporting
|
|
211
|
-
export LISA_NO_TELEMETRY=1 # disable reporting (overrides LISA_TELEMETRY and config)
|
|
212
300
|
```
|
|
213
301
|
|
|
214
302
|
## Commands
|
|
@@ -228,6 +316,9 @@ export LISA_NO_TELEMETRY=1 # disable reporting (overrides LISA_TELEMETRY and
|
|
|
228
316
|
| `lisa run --label NAME` | Override label filter |
|
|
229
317
|
| `lisa run --platform METHOD` | Override PR platform (`cli`, `token`, `gitlab`, or `bitbucket`) |
|
|
230
318
|
| `lisa run --no-bell` | Disable terminal bell on issue completion/failure |
|
|
319
|
+
| `lisa run --lifecycle MODE` | Lifecycle mode: `auto` (default), `skip`, or `validate-only` |
|
|
320
|
+
| `lisa run --lifecycle-timeout N` | Startup timeout per resource in seconds (default: 30) |
|
|
321
|
+
| `lisa run --demo` | Run animated demo with fake issues to preview the TUI |
|
|
231
322
|
| `lisa init` | Create `.lisa/config.yaml` interactively (offers pre-defined templates) |
|
|
232
323
|
| `lisa config` | Edit config interactively |
|
|
233
324
|
| `lisa config --show` | Print current config as JSON |
|
|
@@ -253,7 +344,7 @@ When running in an interactive terminal, `lisa run` renders a real-time Kanban b
|
|
|
253
344
|
└──────────────────────────┘ └───────────────────────────┘ └───────────────────────────┘
|
|
254
345
|
```
|
|
255
346
|
|
|
256
|
-
In-progress cards show a live elapsed timer and the last action taken by the provider.
|
|
347
|
+
In-progress cards show a live elapsed timer and the last action taken by the provider. 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.
|
|
257
348
|
|
|
258
349
|
### Keyboard shortcuts
|
|
259
350
|
|
|
@@ -320,9 +411,10 @@ overseer:
|
|
|
320
411
|
validation:
|
|
321
412
|
require_acceptance_criteria: true # skip issues without detectable acceptance criteria (default: true)
|
|
322
413
|
|
|
323
|
-
# Optional —
|
|
324
|
-
|
|
325
|
-
|
|
414
|
+
# Optional — control infrastructure lifecycle
|
|
415
|
+
lifecycle:
|
|
416
|
+
mode: auto # "auto" (default), "skip", or "validate-only"
|
|
417
|
+
timeout: 30 # seconds to wait per resource (default: 30)
|
|
326
418
|
```
|
|
327
419
|
|
|
328
420
|
### Source-Specific Fields
|
|
@@ -467,7 +559,20 @@ validation:
|
|
|
467
559
|
|
|
468
560
|
### Infrastructure Auto-Discovery
|
|
469
561
|
|
|
470
|
-
Lisa auto-discovers Docker Compose services in each repository and starts them before handing control to the agent.
|
|
562
|
+
Lisa auto-discovers Docker Compose services and ORM/migration tools in each repository and starts them before handing control to the agent.
|
|
563
|
+
|
|
564
|
+
**Docker Compose** — If a `docker-compose.yml` / `compose.yml` is present, Lisa reads port mappings and starts the services, waiting for each port to become ready.
|
|
565
|
+
|
|
566
|
+
**ORM/Migration setup** — Lisa detects and runs migration commands automatically:
|
|
567
|
+
|
|
568
|
+
| Tool | Commands |
|
|
569
|
+
|------|----------|
|
|
570
|
+
| Prisma | `npx prisma generate`, `npx prisma db push` |
|
|
571
|
+
| Drizzle | `npx drizzle-kit push` |
|
|
572
|
+
| TypeORM | `npx typeorm migration:run` |
|
|
573
|
+
| Sequelize | `npx sequelize-cli db:migrate` |
|
|
574
|
+
|
|
575
|
+
**Environment files** — If `.env.example` exists but `.env` doesn't, Lisa copies it automatically before starting infrastructure.
|
|
471
576
|
|
|
472
577
|
**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:
|
|
473
578
|
|
|
@@ -488,10 +593,45 @@ When `port_range` is set, Lisa scans ports `check_port` through `check_port + po
|
|
|
488
593
|
|
|
489
594
|
If no free port is found within the range, Lisa logs a clear error and aborts the session.
|
|
490
595
|
|
|
596
|
+
**Lifecycle modes** control how Lisa handles infrastructure:
|
|
597
|
+
|
|
598
|
+
| Mode | Behavior |
|
|
599
|
+
|------|----------|
|
|
600
|
+
| `auto` (default) | Discover, start, and tear down infrastructure automatically |
|
|
601
|
+
| `skip` | Skip all infrastructure management — assume services are already running |
|
|
602
|
+
| `validate-only` | Check that required ports are available but don't start or stop services |
|
|
603
|
+
|
|
491
604
|
### Auto-Detection
|
|
492
605
|
|
|
493
606
|
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`).
|
|
494
607
|
|
|
608
|
+
### Project Context Analysis
|
|
609
|
+
|
|
610
|
+
Before building the prompt for the AI agent, Lisa automatically analyzes the project and injects rich context into the prompt:
|
|
611
|
+
|
|
612
|
+
- **Quality scripts** — detects `lint`, `typecheck`, `check`, `format`, `test`, `build`, `ci` scripts from `package.json`
|
|
613
|
+
- **Test patterns** — discovers test file location (colocated vs separate directory), style (describe/it vs top-level test), mocking libraries (`vi.mock`, `jest.mock`, fixtures), and includes example code from the first test file found
|
|
614
|
+
- **Code tools** — reads configuration from Biome (`biome.json`), ESLint (`.eslintrc.*`, `eslint.config.*`), and Prettier (`.prettierrc.*`, `prettier.config.*`)
|
|
615
|
+
- **Project structure** — generates a tree view of immediate subdirectories and their children
|
|
616
|
+
- **Environment type** — classifies the project as CLI, Mobile (React Native/Flutter), Web, Server, Library, or Unknown
|
|
617
|
+
|
|
618
|
+
This context is formatted as a `## Project Context` section in the implementation prompt, giving the agent immediate understanding of the codebase conventions without exploration.
|
|
619
|
+
|
|
620
|
+
### API Client Generator Detection
|
|
621
|
+
|
|
622
|
+
Lisa detects API client generators in the project and enriches the agent prompt with generation details — input spec location, output directory, and the command to regenerate clients:
|
|
623
|
+
|
|
624
|
+
| Generator | Config Files |
|
|
625
|
+
|-----------|-------------|
|
|
626
|
+
| Orval | `orval.config.ts/js`, `.orvalrc`, `.orvalrc.json/js/ts` |
|
|
627
|
+
| Kubb | `kubb.config.ts/js/mjs` |
|
|
628
|
+
| hey-api | `openapi-ts.config.ts/js/mjs` |
|
|
629
|
+
| openapi-generator | `openapitools.json`, `.openapi-generator/` |
|
|
630
|
+
| swagger-codegen | `swagger-codegen-config.json` |
|
|
631
|
+
| openapi-typescript | detected from `package.json` dependencies |
|
|
632
|
+
|
|
633
|
+
When a generator is detected, the agent receives the config file path, whether the spec is a live URL or local file, the output directory, and the `package.json` script (or `npx` command) to run the generator.
|
|
634
|
+
|
|
495
635
|
## License
|
|
496
636
|
|
|
497
637
|
[MIT](LICENSE)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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(count = 1) {
|
|
39
|
+
if (!isTTY()) return;
|
|
40
|
+
process.stdout.write("\x07".repeat(count));
|
|
41
|
+
}
|
|
42
|
+
function resetTitle() {
|
|
43
|
+
if (!isTTY()) return;
|
|
44
|
+
writeOSC("");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
setTitle,
|
|
49
|
+
startSpinner,
|
|
50
|
+
stopSpinner,
|
|
51
|
+
notify,
|
|
52
|
+
resetTitle
|
|
53
|
+
};
|
|
@@ -1,48 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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(count = 1) {
|
|
39
|
-
if (!isTTY()) return;
|
|
40
|
-
process.stdout.write("\x07".repeat(count));
|
|
41
|
-
}
|
|
42
|
-
function resetTitle() {
|
|
43
|
-
if (!isTTY()) return;
|
|
44
|
-
writeOSC("");
|
|
45
|
-
}
|
|
2
|
+
import {
|
|
3
|
+
notify
|
|
4
|
+
} from "./chunk-72CYGBT4.js";
|
|
46
5
|
|
|
47
6
|
// src/ui/state.ts
|
|
48
7
|
import { EventEmitter } from "events";
|
|
@@ -639,25 +598,25 @@ async function checkPrMergedByUrl(prUrl) {
|
|
|
639
598
|
}
|
|
640
599
|
return false;
|
|
641
600
|
}
|
|
642
|
-
function stopMergePolling(
|
|
643
|
-
const interval = activePolls.get(
|
|
601
|
+
function stopMergePolling(key) {
|
|
602
|
+
const interval = activePolls.get(key);
|
|
644
603
|
if (interval !== void 0) {
|
|
645
604
|
clearInterval(interval);
|
|
646
|
-
activePolls.delete(
|
|
605
|
+
activePolls.delete(key);
|
|
647
606
|
}
|
|
648
607
|
}
|
|
649
608
|
function startMergePolling(issueId, prUrl) {
|
|
650
|
-
if (activePolls.has(
|
|
609
|
+
if (activePolls.has(prUrl)) return;
|
|
651
610
|
const intervalId = setInterval(() => {
|
|
652
611
|
checkPrMergedByUrl(prUrl).then((merged) => {
|
|
653
612
|
if (merged) {
|
|
654
|
-
stopMergePolling(
|
|
613
|
+
stopMergePolling(prUrl);
|
|
655
614
|
kanbanEmitter.emit("issue:merged", issueId);
|
|
656
615
|
}
|
|
657
616
|
}).catch(() => {
|
|
658
617
|
});
|
|
659
618
|
}, MERGE_POLL_INTERVAL_MS);
|
|
660
|
-
activePolls.set(
|
|
619
|
+
activePolls.set(prUrl, intervalId);
|
|
661
620
|
}
|
|
662
621
|
var KanbanEmitter = class extends EventEmitter {
|
|
663
622
|
};
|
|
@@ -689,7 +648,10 @@ function useKanbanState(bellEnabled) {
|
|
|
689
648
|
const onQueued = (issue) => {
|
|
690
649
|
setCards((prev) => {
|
|
691
650
|
if (prev.some((c) => c.id === issue.id)) return prev;
|
|
692
|
-
return [
|
|
651
|
+
return [
|
|
652
|
+
...prev,
|
|
653
|
+
{ id: issue.id, title: issue.title, column: "backlog", prUrls: [], outputLog: "" }
|
|
654
|
+
];
|
|
693
655
|
});
|
|
694
656
|
};
|
|
695
657
|
const onStarted = (issueId) => {
|
|
@@ -702,20 +664,21 @@ function useKanbanState(bellEnabled) {
|
|
|
702
664
|
hasError: false,
|
|
703
665
|
skipped: false,
|
|
704
666
|
killed: false,
|
|
667
|
+
prUrls: [],
|
|
705
668
|
pausedAt: void 0,
|
|
706
669
|
pauseAccumulated: 0
|
|
707
670
|
} : c
|
|
708
671
|
)
|
|
709
672
|
);
|
|
710
673
|
};
|
|
711
|
-
const onDone = (issueId,
|
|
674
|
+
const onDone = (issueId, prUrls) => {
|
|
712
675
|
setCards(
|
|
713
676
|
(prev) => prev.map(
|
|
714
|
-
(c) => c.id === issueId ? { ...c, column: "done",
|
|
677
|
+
(c) => c.id === issueId ? { ...c, column: "done", prUrls, finishedAt: Date.now() } : c
|
|
715
678
|
)
|
|
716
679
|
);
|
|
717
|
-
|
|
718
|
-
startMergePolling(issueId,
|
|
680
|
+
for (const url of prUrls) {
|
|
681
|
+
startMergePolling(issueId, url);
|
|
719
682
|
}
|
|
720
683
|
};
|
|
721
684
|
const onMerged = (issueId) => {
|
|
@@ -840,11 +803,6 @@ export {
|
|
|
840
803
|
ok,
|
|
841
804
|
divider,
|
|
842
805
|
banner,
|
|
843
|
-
setTitle,
|
|
844
|
-
startSpinner,
|
|
845
|
-
stopSpinner,
|
|
846
|
-
notify,
|
|
847
|
-
resetTitle,
|
|
848
806
|
GitHubIssuesSource,
|
|
849
807
|
GitLabIssuesSource,
|
|
850
808
|
kanbanEmitter,
|