@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 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. 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.
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 — anonymous crash/error reporting (disabled by default)
324
- telemetry:
325
- enabled: true # set via `lisa init` prompt or LISA_TELEMETRY=1; override with LISA_NO_TELEMETRY=1
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. If a `docker-compose.yml` / `compose.yml` is present, Lisa reads port mappings and starts the services, waiting for each port to become ready.
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
- // 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
- }
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(issueId) {
643
- const interval = activePolls.get(issueId);
601
+ function stopMergePolling(key) {
602
+ const interval = activePolls.get(key);
644
603
  if (interval !== void 0) {
645
604
  clearInterval(interval);
646
- activePolls.delete(issueId);
605
+ activePolls.delete(key);
647
606
  }
648
607
  }
649
608
  function startMergePolling(issueId, prUrl) {
650
- if (activePolls.has(issueId)) return;
609
+ if (activePolls.has(prUrl)) return;
651
610
  const intervalId = setInterval(() => {
652
611
  checkPrMergedByUrl(prUrl).then((merged) => {
653
612
  if (merged) {
654
- stopMergePolling(issueId);
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(issueId, intervalId);
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 [...prev, { id: issue.id, title: issue.title, column: "backlog", outputLog: "" }];
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, prUrl) => {
674
+ const onDone = (issueId, prUrls) => {
712
675
  setCards(
713
676
  (prev) => prev.map(
714
- (c) => c.id === issueId ? { ...c, column: "done", prUrl, finishedAt: Date.now() } : c
677
+ (c) => c.id === issueId ? { ...c, column: "done", prUrls, finishedAt: Date.now() } : c
715
678
  )
716
679
  );
717
- if (prUrl) {
718
- startMergePolling(issueId, prUrl);
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,