@tarcisiopgs/lisa 1.13.1 → 1.14.0

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
@@ -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:
@@ -150,14 +242,14 @@ Lisa delivers pull requests and merge requests to **4 platforms**:
150
242
  | GitLab | `gitlab` | `GITLAB_TOKEN` | Merge Request |
151
243
  | Bitbucket | `bitbucket` | `BITBUCKET_TOKEN` + `BITBUCKET_USERNAME` | Pull Request |
152
244
 
153
- Set `github` in your config (or pass `--github` at runtime) to select the delivery method:
245
+ Set `platform` in your config (or pass `--platform` at runtime) to select the delivery method:
154
246
 
155
247
  ```yaml
156
- github: gitlab # or: cli, token, bitbucket
248
+ platform: gitlab # or: cli, token, bitbucket
157
249
  ```
158
250
 
159
251
  ```bash
160
- lisa run --github bitbucket # override at runtime
252
+ lisa run --platform bitbucket # override at runtime
161
253
  ```
162
254
 
163
255
  Each platform appends a `🤖 Resolved by lisa` attribution comment to the PR/MR after creation.
@@ -174,9 +266,9 @@ npm install -g @tarcisiopgs/lisa
174
266
 
175
267
  ```bash
176
268
  # PR/MR delivery — set one based on your platform
177
- export GITHUB_TOKEN="" # GitHub — or have `gh` CLI authenticated (github: cli or token)
178
- export GITLAB_TOKEN="" # GitLab MR creation (github: gitlab)
179
- export BITBUCKET_TOKEN="" # Bitbucket PR creation (github: bitbucket)
269
+ export GITHUB_TOKEN="" # GitHub — or have `gh` CLI authenticated (platform: cli or token)
270
+ export GITLAB_TOKEN="" # GitLab MR creation (platform: gitlab)
271
+ export BITBUCKET_TOKEN="" # Bitbucket PR creation (platform: bitbucket)
180
272
  export BITBUCKET_USERNAME="" # Bitbucket username (required with BITBUCKET_TOKEN)
181
273
 
182
274
  # Required when source = linear
@@ -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
@@ -226,8 +314,11 @@ export LISA_NO_TELEMETRY=1 # disable reporting (overrides LISA_TELEMETRY and
226
314
  | `lisa run --provider NAME` | Override AI provider |
227
315
  | `lisa run --source NAME` | Override issue source |
228
316
  | `lisa run --label NAME` | Override label filter |
229
- | `lisa run --github METHOD` | Override PR platform (`cli`, `token`, `gitlab`, or `bitbucket`) |
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
 
@@ -289,7 +380,7 @@ source_config:
289
380
  in_progress: In Progress
290
381
  done: In Review
291
382
 
292
- github: cli # "cli" (gh), "token" (GITHUB_TOKEN), "gitlab" (GITLAB_TOKEN), or "bitbucket" (BITBUCKET_TOKEN)
383
+ platform: cli # "cli" (gh), "token" (GITHUB_TOKEN), "gitlab" (GITLAB_TOKEN), or "bitbucket" (BITBUCKET_TOKEN)
293
384
  workspace: .
294
385
  base_branch: main
295
386
 
@@ -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
@@ -463,11 +555,24 @@ validation:
463
555
  require_acceptance_criteria: false
464
556
  ```
465
557
 
466
- > **Note:** The `needs-spec` label must exist in your issue tracker before Lisa can apply it. Create it manually if it does not exist — Lisa will log a warning if the label is missing.
558
+ > **Note:** For Linear, Lisa auto-creates the `needs-spec` label if it does not exist. For other trackers, the label must exist before Lisa can apply it — Lisa will log a warning if it is missing.
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,