@tarcisiopgs/lisa 1.17.3 → 1.18.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
@@ -193,6 +193,11 @@ overseer:
193
193
  check_interval: 30 # seconds between git status checks
194
194
  stuck_threshold: 300 # seconds without changes before killing
195
195
 
196
+ # Optional — Docker infrastructure management (disabled by default)
197
+ lifecycle:
198
+ mode: auto # "auto" = start/stop services, "skip" = disabled (default), "validate-only" = fail if not running
199
+ timeout: 30 # seconds per service to wait on startup
200
+
196
201
  # Optional — skip issues without acceptance criteria
197
202
  validation:
198
203
  require_acceptance_criteria: true
@@ -7,6 +7,9 @@ import {
7
7
  import { EventEmitter } from "events";
8
8
  import { useEffect, useState } from "react";
9
9
 
10
+ // src/sources/github-issues.ts
11
+ import { execa } from "execa";
12
+
10
13
  // src/output/logger.ts
11
14
  import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
12
15
  import { dirname } from "path";
@@ -83,9 +86,17 @@ var API_URL = "https://api.github.com";
83
86
  var REQUEST_TIMEOUT_MS = 3e4;
84
87
  var PRIORITY_LABELS = ["p1", "p2", "p3"];
85
88
  var DEPENDENCY_PATTERN = /(?:depends\s+on|blocked\s+by)\s+#(\d+)/gi;
86
- function getAuthHeaders() {
87
- const token = process.env.GITHUB_TOKEN;
88
- if (!token) throw new Error("GITHUB_TOKEN must be set");
89
+ async function getToken() {
90
+ if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
91
+ try {
92
+ const { stdout } = await execa("gh", ["auth", "token"]);
93
+ if (stdout.trim()) return stdout.trim();
94
+ } catch {
95
+ }
96
+ throw new Error("GitHub authentication required: set GITHUB_TOKEN or run `gh auth login`");
97
+ }
98
+ async function getAuthHeaders() {
99
+ const token = await getToken();
89
100
  return {
90
101
  Authorization: `Bearer ${token}`,
91
102
  Accept: "application/vnd.github+json",
@@ -95,7 +106,7 @@ function getAuthHeaders() {
95
106
  async function githubFetch(method, path, body) {
96
107
  const url = `${API_URL}${path}`;
97
108
  const headers = {
98
- ...getAuthHeaders(),
109
+ ...await getAuthHeaders(),
99
110
  "Content-Type": "application/json"
100
111
  };
101
112
  const res = await fetch(url, {
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  ok,
33
33
  setOutputMode,
34
34
  warn
35
- } from "./chunk-WDGLMZB7.js";
35
+ } from "./chunk-KDKCASVH.js";
36
36
  import {
37
37
  notify,
38
38
  resetTitle,
@@ -257,10 +257,103 @@ import { resolve as resolvePath2 } from "path";
257
257
  import * as clack2 from "@clack/prompts";
258
258
  import pc from "picocolors";
259
259
 
260
+ // src/git/github.ts
261
+ import { execa } from "execa";
262
+
263
+ // src/git/pr-body.ts
264
+ var PROVIDER_ATTRIBUTION_RE = /claude\.ai|claude\s+code|gemini\s+cli|openai\s+codex|\bgoose\b|\baider\b|github\s+copilot|cursor\s+agent|\bopencode\b/i;
265
+ var AI_COAUTHOR_RE = /co-authored-by:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)/i;
266
+ function stripProviderAttribution(body) {
267
+ let result = body;
268
+ while (true) {
269
+ const sepIndex = result.lastIndexOf("\n---");
270
+ if (sepIndex === -1) break;
271
+ const section = result.slice(sepIndex);
272
+ if (PROVIDER_ATTRIBUTION_RE.test(section) || AI_COAUTHOR_RE.test(section)) {
273
+ result = result.slice(0, sepIndex).trimEnd();
274
+ } else {
275
+ break;
276
+ }
277
+ }
278
+ result = result.replace(
279
+ /\n+Co-Authored-By:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)[^\n]*/gi,
280
+ ""
281
+ );
282
+ return result.trimEnd();
283
+ }
284
+
285
+ // src/git/github.ts
286
+ async function isGhCliAvailable() {
287
+ try {
288
+ await execa("gh", ["auth", "status"]);
289
+ return true;
290
+ } catch {
291
+ return false;
292
+ }
293
+ }
294
+ var PROVIDER_DISPLAY_NAMES = {
295
+ claude: "Claude Code",
296
+ gemini: "Gemini CLI",
297
+ opencode: "OpenCode",
298
+ copilot: "GitHub Copilot CLI",
299
+ cursor: "Cursor Agent",
300
+ goose: "Goose",
301
+ aider: "Aider",
302
+ codex: "OpenAI Codex"
303
+ };
304
+ function formatProviderName(providerUsed) {
305
+ const providerKey = providerUsed.split("/")[0] ?? providerUsed;
306
+ return PROVIDER_DISPLAY_NAMES[providerKey] ?? providerKey;
307
+ }
308
+ async function deleteProviderComments(prUrl) {
309
+ try {
310
+ const match = prUrl.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
311
+ if (!match) return;
312
+ const [, owner, repo, prNumber] = match;
313
+ const { stdout } = await execa("gh", [
314
+ "api",
315
+ "--paginate",
316
+ "--jq",
317
+ ".[]",
318
+ `/repos/${owner}/${repo}/issues/${prNumber}/comments`
319
+ ]);
320
+ const comments = stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
321
+ for (const comment of comments) {
322
+ if (PROVIDER_ATTRIBUTION_RE.test(comment.body)) {
323
+ try {
324
+ await execa("gh", [
325
+ "api",
326
+ "--method",
327
+ "DELETE",
328
+ `/repos/${owner}/${repo}/issues/comments/${comment.id}`
329
+ ]);
330
+ } catch {
331
+ }
332
+ }
333
+ }
334
+ } catch {
335
+ }
336
+ }
337
+ async function appendPrAttribution(prUrl, providerUsed) {
338
+ await deleteProviderComments(prUrl);
339
+ try {
340
+ const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
341
+ const { body } = JSON.parse(bodyJson);
342
+ const providerName = formatProviderName(providerUsed);
343
+ const attribution = `
344
+
345
+ ---
346
+ \u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
347
+ const newBody = stripProviderAttribution(body ?? "") + attribution;
348
+ await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
349
+ } catch {
350
+ }
351
+ }
352
+
260
353
  // src/git/worktree.ts
261
354
  import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
262
355
  import { join, resolve as resolve2 } from "path";
263
- import { execa } from "execa";
356
+ import { execa as execa2 } from "execa";
264
357
  var WORKTREES_DIR = ".worktrees";
265
358
  function generateBranchName(issueId, title) {
266
359
  const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").substring(0, 40).replace(/^-|-$/g, "");
@@ -268,7 +361,7 @@ function generateBranchName(issueId, title) {
268
361
  return `feat/${safeId}-${slug}`;
269
362
  }
270
363
  async function cleanupOrphanedWorktree(repoRoot, branchName) {
271
- const { stdout: branchList } = await execa("git", ["branch", "--list", branchName], {
364
+ const { stdout: branchList } = await execa2("git", ["branch", "--list", branchName], {
272
365
  cwd: repoRoot,
273
366
  reject: false
274
367
  });
@@ -276,41 +369,41 @@ async function cleanupOrphanedWorktree(repoRoot, branchName) {
276
369
  return false;
277
370
  }
278
371
  const worktreePath = join(repoRoot, WORKTREES_DIR, branchName);
279
- const { stdout: worktreeList } = await execa("git", ["worktree", "list", "--porcelain"], {
372
+ const { stdout: worktreeList } = await execa2("git", ["worktree", "list", "--porcelain"], {
280
373
  cwd: repoRoot,
281
374
  reject: false
282
375
  });
283
376
  if (worktreeList.includes(worktreePath)) {
284
- await execa("git", ["worktree", "remove", worktreePath, "--force"], { cwd: repoRoot });
285
- await execa("git", ["worktree", "prune"], { cwd: repoRoot });
377
+ await execa2("git", ["worktree", "remove", worktreePath, "--force"], { cwd: repoRoot });
378
+ await execa2("git", ["worktree", "prune"], { cwd: repoRoot });
286
379
  }
287
- await execa("git", ["branch", "-D", branchName], { cwd: repoRoot });
380
+ await execa2("git", ["branch", "-D", branchName], { cwd: repoRoot });
288
381
  return true;
289
382
  }
290
383
  async function createWorktree(repoRoot, branchName, baseBranch) {
291
384
  const worktreePath = join(repoRoot, WORKTREES_DIR, branchName);
292
385
  await cleanupOrphanedWorktree(repoRoot, branchName);
293
386
  if (existsSync2(worktreePath)) {
294
- await execa("git", ["worktree", "remove", worktreePath, "--force"], {
387
+ await execa2("git", ["worktree", "remove", worktreePath, "--force"], {
295
388
  cwd: repoRoot,
296
389
  reject: false
297
390
  });
298
- await execa("git", ["worktree", "prune"], { cwd: repoRoot, reject: false });
391
+ await execa2("git", ["worktree", "prune"], { cwd: repoRoot, reject: false });
299
392
  if (existsSync2(worktreePath)) {
300
393
  rmSync(worktreePath, { recursive: true, force: true });
301
394
  }
302
395
  }
303
- await execa("git", ["fetch", "origin", baseBranch], { cwd: repoRoot });
304
- await execa("git", ["worktree", "add", "-b", branchName, worktreePath, `origin/${baseBranch}`], {
396
+ await execa2("git", ["fetch", "origin", baseBranch], { cwd: repoRoot });
397
+ await execa2("git", ["worktree", "add", "-b", branchName, worktreePath, `origin/${baseBranch}`], {
305
398
  cwd: repoRoot
306
399
  });
307
400
  return worktreePath;
308
401
  }
309
402
  async function removeWorktree(repoRoot, worktreePath) {
310
- await execa("git", ["worktree", "remove", worktreePath, "--force"], {
403
+ await execa2("git", ["worktree", "remove", worktreePath, "--force"], {
311
404
  cwd: repoRoot
312
405
  });
313
- await execa("git", ["worktree", "prune"], { cwd: repoRoot });
406
+ await execa2("git", ["worktree", "prune"], { cwd: repoRoot });
314
407
  }
315
408
  function ensureWorktreeGitignore(repoRoot) {
316
409
  const gitignorePath = join(repoRoot, ".gitignore");
@@ -328,21 +421,21 @@ function ensureWorktreeGitignore(repoRoot) {
328
421
  }
329
422
  async function findBranchByIssueId(repoRoot, issueId) {
330
423
  const needle = issueId.toLowerCase();
331
- const { stdout: local } = await execa(
424
+ const { stdout: local } = await execa2(
332
425
  "git",
333
426
  ["for-each-ref", "--sort=-committerdate", "--format=%(refname:short)", "refs/heads/"],
334
427
  { cwd: repoRoot }
335
428
  );
336
429
  const localMatch = local.split("\n").map((b) => b.trim()).filter(Boolean).find((b) => b.toLowerCase().includes(needle));
337
430
  if (localMatch) return localMatch;
338
- const { stdout: remote } = await execa(
431
+ const { stdout: remote } = await execa2(
339
432
  "git",
340
433
  ["for-each-ref", "--sort=-committerdate", "--format=%(refname:short)", "refs/remotes/origin/"],
341
434
  { cwd: repoRoot }
342
435
  );
343
436
  const remoteMatch = remote.split("\n").map((b) => b.trim()).filter(Boolean).find((b) => b.toLowerCase().includes(needle));
344
437
  if (remoteMatch) return remoteMatch.replace("origin/", "");
345
- const { stdout: lsRemote } = await execa("git", ["ls-remote", "--heads", "origin"], {
438
+ const { stdout: lsRemote } = await execa2("git", ["ls-remote", "--heads", "origin"], {
346
439
  cwd: repoRoot
347
440
  });
348
441
  const lsMatch = lsRemote.split("\n").map((l) => l.trim()).filter(Boolean).map((l) => l.split(" ")[1]?.replace("refs/heads/", "") ?? "").find((b) => b.toLowerCase().includes(needle));
@@ -365,7 +458,7 @@ function determineRepoPath(repos, issue2, workspace) {
365
458
  }
366
459
  async function hasCodeChanges(repoPath, baseBranch) {
367
460
  try {
368
- const { stdout } = await execa("git", ["diff", "--stat", `${baseBranch}..HEAD`], {
461
+ const { stdout } = await execa2("git", ["diff", "--stat", `${baseBranch}..HEAD`], {
369
462
  cwd: repoPath,
370
463
  reject: false
371
464
  });
@@ -1624,101 +1717,6 @@ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 }
1624
1717
  import { tmpdir as tmpdir9 } from "os";
1625
1718
  import { join as join10, resolve as resolvePath } from "path";
1626
1719
  import * as clack from "@clack/prompts";
1627
-
1628
- // src/git/github.ts
1629
- import { execa as execa2 } from "execa";
1630
-
1631
- // src/git/pr-body.ts
1632
- var PROVIDER_ATTRIBUTION_RE = /claude\.ai|claude\s+code|gemini\s+cli|openai\s+codex|\bgoose\b|\baider\b|github\s+copilot|cursor\s+agent|\bopencode\b/i;
1633
- var AI_COAUTHOR_RE = /co-authored-by:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)/i;
1634
- function stripProviderAttribution(body) {
1635
- let result = body;
1636
- while (true) {
1637
- const sepIndex = result.lastIndexOf("\n---");
1638
- if (sepIndex === -1) break;
1639
- const section = result.slice(sepIndex);
1640
- if (PROVIDER_ATTRIBUTION_RE.test(section) || AI_COAUTHOR_RE.test(section)) {
1641
- result = result.slice(0, sepIndex).trimEnd();
1642
- } else {
1643
- break;
1644
- }
1645
- }
1646
- result = result.replace(
1647
- /\n+Co-Authored-By:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)[^\n]*/gi,
1648
- ""
1649
- );
1650
- return result.trimEnd();
1651
- }
1652
-
1653
- // src/git/github.ts
1654
- async function isGhCliAvailable() {
1655
- try {
1656
- await execa2("gh", ["auth", "status"]);
1657
- return true;
1658
- } catch {
1659
- return false;
1660
- }
1661
- }
1662
- var PROVIDER_DISPLAY_NAMES = {
1663
- claude: "Claude Code",
1664
- gemini: "Gemini CLI",
1665
- opencode: "OpenCode",
1666
- copilot: "GitHub Copilot CLI",
1667
- cursor: "Cursor Agent",
1668
- goose: "Goose",
1669
- aider: "Aider",
1670
- codex: "OpenAI Codex"
1671
- };
1672
- function formatProviderName(providerUsed) {
1673
- const providerKey = providerUsed.split("/")[0] ?? providerUsed;
1674
- return PROVIDER_DISPLAY_NAMES[providerKey] ?? providerKey;
1675
- }
1676
- async function deleteProviderComments(prUrl) {
1677
- try {
1678
- const match = prUrl.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
1679
- if (!match) return;
1680
- const [, owner, repo, prNumber] = match;
1681
- const { stdout } = await execa2("gh", [
1682
- "api",
1683
- "--paginate",
1684
- "--jq",
1685
- ".[]",
1686
- `/repos/${owner}/${repo}/issues/${prNumber}/comments`
1687
- ]);
1688
- const comments = stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
1689
- for (const comment of comments) {
1690
- if (PROVIDER_ATTRIBUTION_RE.test(comment.body)) {
1691
- try {
1692
- await execa2("gh", [
1693
- "api",
1694
- "--method",
1695
- "DELETE",
1696
- `/repos/${owner}/${repo}/issues/comments/${comment.id}`
1697
- ]);
1698
- } catch {
1699
- }
1700
- }
1701
- }
1702
- } catch {
1703
- }
1704
- }
1705
- async function appendPrAttribution(prUrl, providerUsed) {
1706
- await deleteProviderComments(prUrl);
1707
- try {
1708
- const { stdout: bodyJson } = await execa2("gh", ["pr", "view", prUrl, "--json", "body"]);
1709
- const { body } = JSON.parse(bodyJson);
1710
- const providerName = formatProviderName(providerUsed);
1711
- const attribution = `
1712
-
1713
- ---
1714
- \u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
1715
- const newBody = stripProviderAttribution(body ?? "") + attribution;
1716
- await execa2("gh", ["pr", "edit", prUrl, "--body", newBody]);
1717
- } catch {
1718
- }
1719
- }
1720
-
1721
- // src/cli/detection.ts
1722
1720
  function getVersion() {
1723
1721
  try {
1724
1722
  const pkgPath = resolvePath(new URL(".", import.meta.url).pathname, "../package.json");
@@ -1874,7 +1872,7 @@ async function verifyPlatformCredential(platform2) {
1874
1872
  return;
1875
1873
  }
1876
1874
  }
1877
- async function detectGitRepos() {
1875
+ async function detectGitRepos(existingRepos = []) {
1878
1876
  const cwd = process.cwd();
1879
1877
  if (existsSync3(join10(cwd, ".git"))) {
1880
1878
  clack.log.info("Found a git repository in the current directory.");
@@ -1882,19 +1880,32 @@ async function detectGitRepos() {
1882
1880
  }
1883
1881
  const entries = readdirSync(cwd, { withFileTypes: true });
1884
1882
  const gitDirs = entries.filter((e) => e.isDirectory() && existsSync3(join10(cwd, e.name, ".git"))).map((e) => e.name);
1885
- if (gitDirs.length === 0) {
1883
+ const existingDirs = existingRepos.map((r) => r.path.replace(/^\.\//, ""));
1884
+ const missingDirs = existingDirs.filter((d) => !gitDirs.includes(d));
1885
+ if (gitDirs.length === 0 && missingDirs.length === 0) {
1886
1886
  return [];
1887
1887
  }
1888
+ const options = [
1889
+ ...gitDirs.map((dir) => ({ value: dir, label: dir, disabled: false })),
1890
+ ...missingDirs.map((dir) => ({
1891
+ value: dir,
1892
+ label: dir,
1893
+ hint: "(not found on disk)",
1894
+ disabled: true
1895
+ }))
1896
+ ];
1897
+ const initialValues = existingDirs.filter((d) => gitDirs.includes(d));
1888
1898
  const selected = await clack.multiselect({
1889
1899
  message: "Multiple git repositories found \u2014 which ones should Lisa work on?",
1890
- options: gitDirs.map((dir) => ({ value: dir, label: dir }))
1900
+ options,
1901
+ initialValues
1891
1902
  });
1892
1903
  if (clack.isCancel(selected)) return process.exit(0);
1893
1904
  return selected.map((dir) => ({
1894
1905
  name: getGitRepoName(join10(cwd, dir)) ?? dir,
1895
1906
  path: `./${dir}`,
1896
- match: "",
1897
- base_branch: ""
1907
+ match: existingRepos.find((r) => r.path === `./${dir}`)?.match ?? "",
1908
+ base_branch: existingRepos.find((r) => r.path === `./${dir}`)?.base_branch ?? ""
1898
1909
  }));
1899
1910
  }
1900
1911
  function detectDefaultBranch(repoPath) {
@@ -2118,48 +2129,70 @@ remove them or set the file to ${pc.cyan("{}")} \u2014 MCP tools can cause OpenC
2118
2129
  if (clack2.isCancel(modelSelection)) return process.exit(0);
2119
2130
  selectedModels = modelSelection ?? [];
2120
2131
  }
2132
+ const ghCliAvailable = await isGhCliAvailable();
2121
2133
  const source = await clack2.select({
2122
2134
  message: "Where do your issues come from?",
2123
2135
  initialValue: initial?.source,
2124
2136
  options: [
2125
- { value: "linear", label: "Linear", apiHint: "GraphQL API", envVars: ["LINEAR_API_KEY"] },
2137
+ {
2138
+ value: "linear",
2139
+ label: "Linear",
2140
+ apiHint: "GraphQL API",
2141
+ envVars: ["LINEAR_API_KEY"],
2142
+ ghCliFallback: false
2143
+ },
2126
2144
  {
2127
2145
  value: "trello",
2128
2146
  label: "Trello",
2129
2147
  apiHint: "REST API",
2130
- envVars: ["TRELLO_API_KEY", "TRELLO_TOKEN"]
2148
+ envVars: ["TRELLO_API_KEY", "TRELLO_TOKEN"],
2149
+ ghCliFallback: false
2131
2150
  },
2132
2151
  {
2133
2152
  value: "github-issues",
2134
2153
  label: "GitHub Issues",
2135
2154
  apiHint: "REST API",
2136
- envVars: ["GITHUB_TOKEN"]
2155
+ envVars: ["GITHUB_TOKEN"],
2156
+ ghCliFallback: true
2137
2157
  },
2138
2158
  {
2139
2159
  value: "gitlab-issues",
2140
2160
  label: "GitLab Issues",
2141
2161
  apiHint: "REST API",
2142
- envVars: ["GITLAB_TOKEN"]
2162
+ envVars: ["GITLAB_TOKEN"],
2163
+ ghCliFallback: false
2164
+ },
2165
+ {
2166
+ value: "plane",
2167
+ label: "Plane",
2168
+ apiHint: "REST API",
2169
+ envVars: ["PLANE_API_TOKEN"],
2170
+ ghCliFallback: false
2143
2171
  },
2144
- { value: "plane", label: "Plane", apiHint: "REST API", envVars: ["PLANE_API_TOKEN"] },
2145
2172
  {
2146
2173
  value: "shortcut",
2147
2174
  label: "Shortcut",
2148
2175
  apiHint: "REST API",
2149
- envVars: ["SHORTCUT_API_TOKEN"]
2176
+ envVars: ["SHORTCUT_API_TOKEN"],
2177
+ ghCliFallback: false
2150
2178
  },
2151
2179
  {
2152
2180
  value: "jira",
2153
2181
  label: "Jira",
2154
2182
  apiHint: "REST API",
2155
- envVars: ["JIRA_BASE_URL", "JIRA_EMAIL", "JIRA_API_TOKEN"]
2183
+ envVars: ["JIRA_BASE_URL", "JIRA_EMAIL", "JIRA_API_TOKEN"],
2184
+ ghCliFallback: false
2185
+ }
2186
+ ].map(({ value, label: label2, apiHint, envVars, ghCliFallback }) => {
2187
+ let missing2 = envVars.filter((v) => !process.env[v]);
2188
+ if (ghCliFallback && ghCliAvailable) {
2189
+ missing2 = missing2.filter((v) => v !== "GITHUB_TOKEN");
2156
2190
  }
2157
- ].map(({ value, label: label2, apiHint, envVars }) => {
2158
- const missing2 = envVars.filter((v) => !process.env[v]);
2191
+ const usingGhCli = ghCliFallback && ghCliAvailable && !process.env.GITHUB_TOKEN;
2159
2192
  return {
2160
2193
  value,
2161
2194
  label: label2,
2162
- hint: missing2.length > 0 ? `missing: ${missing2.join(", ")}` : apiHint,
2195
+ hint: missing2.length > 0 ? `missing: ${missing2.join(", ")}` : usingGhCli ? "via gh CLI" : apiHint,
2163
2196
  disabled: missing2.length > 0
2164
2197
  };
2165
2198
  })
@@ -2284,7 +2317,7 @@ This will cause Lisa to re-pick the issue on recovery. Consider using a differen
2284
2317
  });
2285
2318
  if (clack2.isCancel(workflowAnswer)) return process.exit(0);
2286
2319
  const workflow = workflowAnswer;
2287
- const repos = await detectGitRepos();
2320
+ const repos = await detectGitRepos(initial?.repos ?? []);
2288
2321
  let baseBranch = "main";
2289
2322
  const cwd = process.cwd();
2290
2323
  if (repos.length === 0) {
@@ -5538,20 +5571,11 @@ function discoverDockerCompose(cwd) {
5538
5571
  resources.push({
5539
5572
  name: serviceName,
5540
5573
  check_port: hostPort,
5541
- up: "docker compose up -d",
5542
- down: "docker compose down",
5574
+ up: `docker compose up -d ${serviceName}`,
5575
+ down: `docker compose stop ${serviceName}`,
5543
5576
  startup_timeout: 30
5544
5577
  });
5545
5578
  }
5546
- if (resources.length > 1) {
5547
- for (let i = 1; i < resources.length; i++) {
5548
- const r = resources[i];
5549
- if (r) {
5550
- r.up = "true";
5551
- r.down = "true";
5552
- }
5553
- }
5554
- }
5555
5579
  return resources;
5556
5580
  }
5557
5581
  function discoverSetupCommands(cwd) {
@@ -5735,7 +5759,7 @@ async function startResources(infra, baseCwd) {
5735
5759
  return { success: true, env: allocatedEnv };
5736
5760
  }
5737
5761
  async function runLifecycle(infra, lifecycle, baseCwd) {
5738
- const mode = lifecycle?.mode ?? "auto";
5762
+ const mode = lifecycle?.mode ?? "skip";
5739
5763
  if (mode === "skip") {
5740
5764
  return { success: true, env: {} };
5741
5765
  }
@@ -7248,7 +7272,7 @@ var run = defineCommand5({
7248
7272
  if (isTTY) {
7249
7273
  const { render } = await import("ink");
7250
7274
  const { createElement } = await import("react");
7251
- const { KanbanApp } = await import("./kanban-LG26AUFK.js");
7275
+ const { KanbanApp } = await import("./kanban-PTPW3HO2.js");
7252
7276
  const demoConfig = {
7253
7277
  provider: "claude",
7254
7278
  source: "linear",
@@ -7323,7 +7347,7 @@ Add them to your ${shell} and run: source ${shell}`));
7323
7347
  if (isTTY) {
7324
7348
  const { render } = await import("ink");
7325
7349
  const { createElement } = await import("react");
7326
- const { KanbanApp } = await import("./kanban-LG26AUFK.js");
7350
+ const { KanbanApp } = await import("./kanban-PTPW3HO2.js");
7327
7351
  render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
7328
7352
  }
7329
7353
  await runLoop(merged, {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  kanbanEmitter,
4
4
  useKanbanState
5
- } from "./chunk-WDGLMZB7.js";
5
+ } from "./chunk-KDKCASVH.js";
6
6
  import {
7
7
  resetTitle,
8
8
  startSpinner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarcisiopgs/lisa",
3
- "version": "1.17.3",
3
+ "version": "1.18.1",
4
4
  "description": "Autonomous issue resolver",
5
5
  "keywords": [
6
6
  "loop",