@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
|
|
87
|
-
|
|
88
|
-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
285
|
-
await
|
|
377
|
+
await execa2("git", ["worktree", "remove", worktreePath, "--force"], { cwd: repoRoot });
|
|
378
|
+
await execa2("git", ["worktree", "prune"], { cwd: repoRoot });
|
|
286
379
|
}
|
|
287
|
-
await
|
|
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
|
|
387
|
+
await execa2("git", ["worktree", "remove", worktreePath, "--force"], {
|
|
295
388
|
cwd: repoRoot,
|
|
296
389
|
reject: false
|
|
297
390
|
});
|
|
298
|
-
await
|
|
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
|
|
304
|
-
await
|
|
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
|
|
403
|
+
await execa2("git", ["worktree", "remove", worktreePath, "--force"], {
|
|
311
404
|
cwd: repoRoot
|
|
312
405
|
});
|
|
313
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
{
|
|
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
|
-
|
|
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:
|
|
5542
|
-
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 ?? "
|
|
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-
|
|
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-
|
|
7350
|
+
const { KanbanApp } = await import("./kanban-PTPW3HO2.js");
|
|
7327
7351
|
render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
|
|
7328
7352
|
}
|
|
7329
7353
|
await runLoop(merged, {
|