@tarcisiopgs/lisa 1.7.5 → 1.7.7

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** — Runs the project's test suite. If tests fail, the session is aborted and the issue reverts.
62
- 5. **PR** — Pushes the branch and creates a pull request referencing the original issue. If pre-push hooks fail, Lisa re-invokes the agent to fix the errors and retries.
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,18 +73,20 @@ Lisa follows a deterministic pipeline:
75
73
 
76
74
  ## Providers
77
75
 
78
- | Provider | CLI | Auto-approve Flag |
79
- |----------|-----|-------------------|
80
- | Claude Code | `claude` | `--dangerously-skip-permissions` |
81
- | Gemini CLI | `gemini` | `--yolo` |
82
- | OpenCode | `opencode` | implicit in `run` |
83
- | GitHub Copilot CLI | `copilot` | `--allow-all` |
84
- | Cursor Agent | `agent` | `--force` |
85
- | Goose | `goose` | implicit in `run` |
86
- | Aider | `aider` | `--yes-always` |
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
 
88
+ > **Cursor Free plan** — `lisa init` automatically detects Free accounts and restricts model selection to `auto` only. On paid plans, a curated list of top-tier models is shown (`composer-1.5`, `opus-4.6`, `sonnet-4.6`, `gpt-5.3-codex`, etc.).
89
+
90
90
  ### Fallback Chain
91
91
 
92
92
  Configure multiple models — Lisa tries each in order. Transient errors (429, quota, timeout, network) trigger the next model; non-transient errors stop the chain.
@@ -108,7 +108,7 @@ npm install -g @tarcisiopgs/lisa
108
108
  ## Environment Variables
109
109
 
110
110
  ```bash
111
- # Required (at least one)
111
+ # Required for PR creation (at least one)
112
112
  export GITHUB_TOKEN="" # or have `gh` CLI authenticated
113
113
 
114
114
  # Required when source = linear
@@ -150,16 +150,48 @@ export JIRA_API_TOKEN="" # Atlassian API token
150
150
  | `lisa run --limit N` | Process up to N issues |
151
151
  | `lisa run --dry-run` | Preview without executing |
152
152
  | `lisa run --provider NAME` | Override AI provider |
153
- | `lisa run --source NAME` | Override issue source (linear, trello, plane, shortcut, gitlab-issues, github-issues, jira) |
153
+ | `lisa run --source NAME` | Override issue source |
154
154
  | `lisa run --label NAME` | Override label filter |
155
- | `lisa run --github METHOD` | Override GitHub method (cli, token) |
155
+ | `lisa run --github METHOD` | Override GitHub method (`cli` or `token`) |
156
156
  | `lisa run --json` | Output as JSON lines |
157
157
  | `lisa run --quiet` | Suppress non-essential output |
158
- | `lisa config` | Interactive config wizard |
159
- | `lisa config --show` | Show current config |
160
- | `lisa config --set key=value` | Set a config value |
161
- | `lisa init` | Create `.lisa/config.yaml` |
158
+ | `lisa init` | Create `.lisa/config.yaml` interactively |
159
+ | `lisa config` | Edit config interactively |
160
+ | `lisa config --show` | Print current config as JSON |
161
+ | `lisa config --set key=value` | Set a single config value |
162
162
  | `lisa status` | Show session stats |
163
+ | `lisa issue get <id>` | Fetch full issue details as JSON (for use inside worktrees) |
164
+ | `lisa issue done <id> --pr-url <url>` | Complete issue, attach PR, update status, remove label |
165
+
166
+ ## TUI
167
+
168
+ When running in an interactive terminal, `lisa run` renders a real-time Kanban board:
169
+
170
+ ```
171
+ ┌──────────────────────────┐ ┌───────────────────────────┐ ┌───────────────────────────┐
172
+ │ ▶ BACKLOG [3] │ │ ▶ IN PROGRESS [1] │ │ ▶ IN REVIEW [2] │
173
+ │ │ │ │ │ │
174
+ │ ┌────────────────────┐ │ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │
175
+ │ │ ENG-42 │ │ │ │ ● ENG-38 │ │ │ │ ✓ ENG-35 │ │
176
+ │ │ Add dark mode │ │ │ │ Fix login redirect │ │ │ │ Update dependencies │ │
177
+ │ │ ready │ │ │ │ ~1 running │ │ │ │ PR created │ │
178
+ │ └────────────────────┘ │ │ └─────────────────────┘ │ │ └─────────────────────┘ │
179
+ └──────────────────────────┘ └───────────────────────────┘ └───────────────────────────┘
180
+ ```
181
+
182
+ ### Keyboard shortcuts
183
+
184
+ | Key | Action |
185
+ |-----|--------|
186
+ | `Tab` | Move to next column |
187
+ | `Shift+Tab` | Move to previous column |
188
+ | `↑` / `↓` | Navigate cards / scroll output |
189
+ | `Enter` | Open issue detail view (streams provider output) |
190
+ | `Esc` | Close detail view, return to board |
191
+ | `p` | Pause / resume — loop finishes the current issue then waits |
192
+ | `q` | Quit |
193
+
194
+ The sidebar legend updates contextually: board shortcuts when browsing the Kanban, scroll and back hints when viewing issue detail. 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
195
 
164
196
  ## Configuration
165
197
 
package/dist/index.js CHANGED
@@ -179,6 +179,33 @@ async function isGhCliAvailable() {
179
179
  return false;
180
180
  }
181
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
+ }
182
209
 
183
210
  // src/git/worktree.ts
184
211
  import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
@@ -3127,6 +3154,13 @@ function createSource(name) {
3127
3154
  var activeCleanup = null;
3128
3155
  var activeProviderPid = null;
3129
3156
  var shuttingDown = false;
3157
+ var loopPaused = false;
3158
+ kanbanEmitter.on("loop:pause", () => {
3159
+ loopPaused = true;
3160
+ });
3161
+ kanbanEmitter.on("loop:resume", () => {
3162
+ loopPaused = false;
3163
+ });
3130
3164
  function resolveModels(config2) {
3131
3165
  if (!config2.models || config2.models.length === 0) {
3132
3166
  return [{ provider: config2.provider }];
@@ -3293,6 +3327,7 @@ async function runLoop(config2, opts) {
3293
3327
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
3294
3328
  const logFile = resolve5(config2.logs.dir, `session_${session}_${timestamp2}.log`);
3295
3329
  divider(session);
3330
+ await waitIfPaused();
3296
3331
  startSpinner("fetching issue...");
3297
3332
  if (opts.issueId) {
3298
3333
  log(`Fetching issue '${opts.issueId}' from ${config2.source}...`);
@@ -3602,6 +3637,7 @@ ${result.output}
3602
3637
  }
3603
3638
  const worktreePath = await findWorktreeForBranch(repoPath, manifest.branch ?? "");
3604
3639
  ok(`PR created by provider: ${manifest.prUrl}`);
3640
+ await appendPrAttribution(manifest.prUrl, result.providerUsed);
3605
3641
  if (worktreePath) await cleanupWorktree(repoPath, worktreePath);
3606
3642
  ok(`Session ${session} complete for ${issue2.id}`);
3607
3643
  return {
@@ -3708,6 +3744,7 @@ ${result.output}
3708
3744
  return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
3709
3745
  }
3710
3746
  ok(`PR created by provider: ${manifest.prUrl}`);
3747
+ await appendPrAttribution(manifest.prUrl, result.providerUsed);
3711
3748
  await cleanupWorktree(repoPath, worktreePath);
3712
3749
  ok(`Session ${session} complete for ${issue2.id}`);
3713
3750
  return {
@@ -3896,6 +3933,7 @@ ${result.output}
3896
3933
  return { ...failResult(result.providerUsed, result), branch: branchName };
3897
3934
  }
3898
3935
  await cleanupWorktree(repoPath, worktreePath);
3936
+ await appendPrAttribution(manifest.prUrl, result.providerUsed);
3899
3937
  ok(`Step ${stepNum} complete: ${repoPath} \u2014 PR: ${manifest.prUrl}`);
3900
3938
  return {
3901
3939
  success: true,
@@ -3976,6 +4014,7 @@ ${result.output}
3976
4014
  return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
3977
4015
  }
3978
4016
  ok(`PR created by provider: ${manifest.prUrl}`);
4017
+ await appendPrAttribution(manifest.prUrl, result.providerUsed);
3979
4018
  ok(`Session ${session} complete for ${issue2.id}`);
3980
4019
  return {
3981
4020
  success: true,
@@ -3995,6 +4034,11 @@ async function cleanupWorktree(repoRoot, worktreePath) {
3995
4034
  function sleep(ms) {
3996
4035
  return new Promise((resolve6) => setTimeout(resolve6, ms));
3997
4036
  }
4037
+ async function waitIfPaused() {
4038
+ while (loopPaused) {
4039
+ await sleep(500);
4040
+ }
4041
+ }
3998
4042
 
3999
4043
  // src/cli.ts
4000
4044
  function sleep2(ms) {
@@ -4051,7 +4095,7 @@ Add them to your ${shell} and run: source ${shell}`));
4051
4095
  if (isTUI) {
4052
4096
  const { render } = await import("ink");
4053
4097
  const { createElement } = await import("react");
4054
- const { KanbanApp } = await import("./kanban-YP3TJJUT.js");
4098
+ const { KanbanApp } = await import("./kanban-5C3WZIKC.js");
4055
4099
  render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
4056
4100
  }
4057
4101
  await runLoop(merged, {
@@ -4247,15 +4291,21 @@ var issue = defineCommand({
4247
4291
  meta: { name: "issue", description: "Issue tracker operations for use inside worktrees" },
4248
4292
  subCommands: { get: issueGet, done: issueDone }
4249
4293
  });
4250
- var CURSOR_MODELS_FALLBACK = [
4294
+ var CURSOR_PREFERRED_MODELS = [
4251
4295
  "auto",
4252
4296
  "composer-1.5",
4297
+ "composer-1",
4298
+ "gpt-5.3-codex",
4299
+ "gpt-5.2",
4300
+ "gpt-5.1-codex-max",
4253
4301
  "opus-4.6-thinking",
4254
4302
  "opus-4.6",
4303
+ "sonnet-4.6-thinking",
4255
4304
  "sonnet-4.6",
4256
- "gpt-5.3-codex",
4257
- "gpt-5.2",
4258
- "gemini-3.1-pro"
4305
+ "gemini-3.1-pro",
4306
+ "gemini-3-pro",
4307
+ "grok",
4308
+ "kimi-k2.5"
4259
4309
  ];
4260
4310
  function fetchCursorModels() {
4261
4311
  try {
@@ -4267,13 +4317,14 @@ function fetchCursorModels() {
4267
4317
  return false;
4268
4318
  }
4269
4319
  });
4270
- if (!bin) return CURSOR_MODELS_FALLBACK;
4320
+ if (!bin) return CURSOR_PREFERRED_MODELS;
4271
4321
  const raw = execSync8(`${bin} --list-models`, { encoding: "utf-8", timeout: 1e4 });
4272
4322
  const clean = raw.replace(/\x1b\[[0-9;]*[mGKHFA-Z]/g, "");
4273
- const models = clean.split("\n").map((l) => l.trim()).filter((l) => l.includes(" - ")).map((l) => (l.split(" - ")[0] ?? "").trim()).filter(Boolean);
4274
- return models.length > 0 ? models : CURSOR_MODELS_FALLBACK;
4323
+ const all = clean.split("\n").map((l) => l.trim()).filter((l) => l.includes(" - ")).map((l) => (l.split(" - ")[0] ?? "").trim()).filter(Boolean);
4324
+ const filtered = CURSOR_PREFERRED_MODELS.filter((m) => all.includes(m));
4325
+ return filtered.length > 0 ? filtered : CURSOR_PREFERRED_MODELS;
4275
4326
  } catch {
4276
- return CURSOR_MODELS_FALLBACK;
4327
+ return CURSOR_PREFERRED_MODELS;
4277
4328
  }
4278
4329
  }
4279
4330
  function fetchOpenCodeModels() {
@@ -337,11 +337,7 @@ function IssueDetail({ card, onBack }) {
337
337
  ] }) : visibleLines.map((line, i) => (
338
338
  // biome-ignore lint/suspicious/noArrayIndexKey: log lines have no stable key
339
339
  /* @__PURE__ */ jsx4(Text4, { color: "white", dimColor: true, children: line }, i)
340
- )) }),
341
- /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
342
- /* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "[\u2191\u2193] scroll" }),
343
- /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: "[Esc] back to board" })
344
- ] })
340
+ )) })
345
341
  ]
346
342
  }
347
343
  );
@@ -352,14 +348,15 @@ import { existsSync } from "fs";
352
348
  import { basename, join } from "path";
353
349
  import { Box as Box5, Text as Text5 } from "ink";
354
350
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
355
- function Sidebar({ provider, source, cwd }) {
356
- const dir = basename(cwd);
351
+ function Sidebar({ provider, source, cwd, activeView, paused = false }) {
352
+ const dir = basename(cwd).toUpperCase();
357
353
  const cwdLabel = existsSync(join(cwd, ".git")) ? "REPOSITORY" : "WORKSPACE";
358
354
  return /* @__PURE__ */ jsxs5(
359
355
  Box5,
360
356
  {
361
357
  flexDirection: "column",
362
358
  width: 28,
359
+ flexShrink: 0,
363
360
  borderStyle: "single",
364
361
  borderColor: "yellow",
365
362
  paddingX: 1,
@@ -377,8 +374,8 @@ function Sidebar({ provider, source, cwd }) {
377
374
  /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D" })
378
375
  ] }),
379
376
  /* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, children: [
380
- /* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u25B6 " }),
381
- /* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "RUNNING" })
377
+ /* @__PURE__ */ jsx5(Text5, { color: paused ? "yellow" : "green", children: paused ? "\u23F8 " : "\u25B6 " }),
378
+ /* @__PURE__ */ jsx5(Text5, { color: paused ? "yellow" : "green", bold: true, children: paused ? "PAUSED" : "RUNNING" })
382
379
  ] }),
383
380
  /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
384
381
  /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
@@ -404,11 +401,15 @@ function Sidebar({ provider, source, cwd }) {
404
401
  ] }),
405
402
  /* @__PURE__ */ jsx5(Box5, { flexGrow: 1 }),
406
403
  /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
407
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
404
+ activeView === "board" ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
408
405
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[Tab] next column" }),
409
406
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u2191\u2193] navigate " }),
410
407
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u21B5] view detail " }),
408
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: paused ? "[p] resume " : "[p] pause " }),
411
409
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[q] quit " })
410
+ ] }) : /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
411
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u2191\u2193] scroll " }),
412
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[Esc] back to board" })
412
413
  ] })
413
414
  ]
414
415
  }
@@ -423,6 +424,7 @@ function KanbanApp({ config }) {
423
424
  const [activeView, setActiveView] = useState3("board");
424
425
  const [activeColIndex, setActiveColIndex] = useState3(0);
425
426
  const [activeCardIndex, setActiveCardIndex] = useState3(0);
427
+ const [paused, setPaused] = useState3(false);
426
428
  useEffect3(() => {
427
429
  const onExit = () => exit();
428
430
  kanbanEmitter.on("tui:exit", onExit);
@@ -454,6 +456,12 @@ function KanbanApp({ config }) {
454
456
  if (key.escape) setActiveView("board");
455
457
  return;
456
458
  }
459
+ if (input === "p") {
460
+ const next = !paused;
461
+ setPaused(next);
462
+ kanbanEmitter.emit(next ? "loop:pause" : "loop:resume");
463
+ return;
464
+ }
457
465
  if (key.tab && !key.shift) {
458
466
  const nextCol = (activeColIndex + 1) % 3;
459
467
  setActiveColIndex(nextCol);
@@ -489,7 +497,16 @@ function KanbanApp({ config }) {
489
497
  };
490
498
  const selectedCard = activeView === "detail" ? columnCards[activeColIndex]?.[activeCardIndex] ?? null : null;
491
499
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", height: process.stdout.rows, children: [
492
- /* @__PURE__ */ jsx6(Sidebar, { provider: config.provider, source: config.source, cwd: process.cwd() }),
500
+ /* @__PURE__ */ jsx6(
501
+ Sidebar,
502
+ {
503
+ provider: config.provider,
504
+ source: config.source,
505
+ cwd: process.cwd(),
506
+ activeView,
507
+ paused
508
+ }
509
+ ),
493
510
  activeView === "board" || !selectedCard ? /* @__PURE__ */ jsx6(
494
511
  Board,
495
512
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarcisiopgs/lisa",
3
- "version": "1.7.5",
3
+ "version": "1.7.7",
4
4
  "description": "Autonomous issue resolver",
5
5
  "license": "MIT",
6
6
  "type": "module",