@tarcisiopgs/lisa 1.27.0 → 1.28.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 +4 -0
- package/dist/chunk-2TW2MJXF.js +116 -0
- package/dist/{chunk-JRCAT5PI.js → chunk-42NKASE3.js} +4 -114
- package/dist/{chunk-SSSFEMF4.js → chunk-5N4BWHIT.js} +44 -7
- package/dist/chunk-FCEUJ7VK.js +407 -0
- package/dist/chunk-UXVSQQID.js +3924 -0
- package/dist/chunk-W73XGHD4.js +3059 -0
- package/dist/{detection-FUU6FUZZ.js → detection-JT7HSKSX.js} +2 -1
- package/dist/index.js +1420 -7826
- package/dist/{kanban-MHKZIHS3.js → kanban-2WMA5VPQ.js} +523 -103
- package/dist/loop-KNJIRK7T.js +21 -0
- package/dist/tui-bridge-DCC4JAPM.js +164 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ If something fails — pre-push hooks, quota limits, stuck processes — Lisa ha
|
|
|
45
45
|
- **Real-time TUI** — Kanban board with live provider output, keyboard controls, PR merge detection
|
|
46
46
|
- **Self-healing** — orphan recovery on startup, push failure retry, stuck process detection
|
|
47
47
|
- **Guardrails** — past failures are injected into future prompts to avoid repeating mistakes
|
|
48
|
+
- **AI planning** — `lisa plan` decomposes goals into atomic issues with dependencies, creates them in your tracker
|
|
48
49
|
- **Project context** — auto-generates `.lisa/context.md` with your stack, conventions, and constraints
|
|
49
50
|
|
|
50
51
|
## Providers
|
|
@@ -89,6 +90,9 @@ lisa run --watch # poll for new issues after queue empties
|
|
|
89
90
|
lisa run --concurrency 3 # process 3 issues in parallel
|
|
90
91
|
lisa run --issue INT-42 # process a specific issue
|
|
91
92
|
lisa run --limit 5 # stop after 5 issues
|
|
93
|
+
lisa plan "Add rate limiting" # decompose goal into issues via AI
|
|
94
|
+
lisa plan --issue EPIC-123 # decompose existing issue into sub-issues
|
|
95
|
+
lisa plan --continue # resume interrupted plan
|
|
92
96
|
lisa init # create .lisa/config.yaml interactively
|
|
93
97
|
lisa status # show session stats
|
|
94
98
|
lisa doctor # diagnose setup issues (config, provider, env, git)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/git/github.ts
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
|
|
6
|
+
// src/git/pr-body.ts
|
|
7
|
+
var PROVIDER_ATTRIBUTION_RE = /claude\.ai|claude\s+code|\banthropic\b|gemini\s+cli|\bgoogle\s+gemini\b|openai\s+codex|\bopenai\b|\bgoose\b|\baider\b|github\s+copilot|cursor\s+agent|\bopencode\b|\blisa\b/i;
|
|
8
|
+
var AI_COAUTHOR_RE = /co-authored-by:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)/i;
|
|
9
|
+
function stripProviderAttribution(body) {
|
|
10
|
+
let result = body;
|
|
11
|
+
while (true) {
|
|
12
|
+
const sepIndex = result.lastIndexOf("\n---");
|
|
13
|
+
if (sepIndex === -1) break;
|
|
14
|
+
const section = result.slice(sepIndex);
|
|
15
|
+
if (PROVIDER_ATTRIBUTION_RE.test(section) || AI_COAUTHOR_RE.test(section)) {
|
|
16
|
+
result = result.slice(0, sepIndex).trimEnd();
|
|
17
|
+
} else {
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
result = result.replace(
|
|
22
|
+
/\n+Co-Authored-By:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)[^\n]*/gi,
|
|
23
|
+
""
|
|
24
|
+
);
|
|
25
|
+
return result.trimEnd();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/git/github.ts
|
|
29
|
+
async function isGhCliAvailable() {
|
|
30
|
+
try {
|
|
31
|
+
await execa("gh", ["auth", "status"]);
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
var PROVIDER_DISPLAY_NAMES = {
|
|
38
|
+
claude: "Claude Code",
|
|
39
|
+
gemini: "Gemini CLI",
|
|
40
|
+
opencode: "OpenCode",
|
|
41
|
+
copilot: "GitHub Copilot CLI",
|
|
42
|
+
cursor: "Cursor Agent",
|
|
43
|
+
goose: "Goose",
|
|
44
|
+
aider: "Aider",
|
|
45
|
+
codex: "OpenAI Codex"
|
|
46
|
+
};
|
|
47
|
+
function formatProviderName(providerUsed) {
|
|
48
|
+
const providerKey = providerUsed.split("/")[0] ?? providerUsed;
|
|
49
|
+
return PROVIDER_DISPLAY_NAMES[providerKey] ?? providerKey;
|
|
50
|
+
}
|
|
51
|
+
async function deleteProviderComments(prUrl) {
|
|
52
|
+
try {
|
|
53
|
+
const match = prUrl.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
54
|
+
if (!match) return;
|
|
55
|
+
const [, owner, repo, prNumber] = match;
|
|
56
|
+
const { stdout } = await execa("gh", [
|
|
57
|
+
"api",
|
|
58
|
+
"--paginate",
|
|
59
|
+
"--jq",
|
|
60
|
+
".[]",
|
|
61
|
+
`/repos/${owner}/${repo}/issues/${prNumber}/comments`
|
|
62
|
+
]);
|
|
63
|
+
const comments = stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
64
|
+
for (const comment of comments) {
|
|
65
|
+
if (PROVIDER_ATTRIBUTION_RE.test(comment.body)) {
|
|
66
|
+
try {
|
|
67
|
+
await execa("gh", [
|
|
68
|
+
"api",
|
|
69
|
+
"--method",
|
|
70
|
+
"DELETE",
|
|
71
|
+
`/repos/${owner}/${repo}/issues/comments/${comment.id}`
|
|
72
|
+
]);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function appendPrAttribution(prUrl, providerUsed) {
|
|
81
|
+
await deleteProviderComments(prUrl);
|
|
82
|
+
try {
|
|
83
|
+
const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
|
|
84
|
+
const { body } = JSON.parse(bodyJson);
|
|
85
|
+
const providerName = formatProviderName(providerUsed);
|
|
86
|
+
const attribution = `
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
\u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
|
|
90
|
+
const newBody = stripProviderAttribution(body ?? "") + attribution;
|
|
91
|
+
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function appendPrBody(prUrl, content) {
|
|
96
|
+
try {
|
|
97
|
+
const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
|
|
98
|
+
const { body } = JSON.parse(bodyJson);
|
|
99
|
+
const newBody = (body ?? "") + content;
|
|
100
|
+
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/errors.ts
|
|
106
|
+
function formatError(err) {
|
|
107
|
+
return err instanceof Error ? err.message : String(err);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
stripProviderAttribution,
|
|
112
|
+
isGhCliAvailable,
|
|
113
|
+
appendPrAttribution,
|
|
114
|
+
appendPrBody,
|
|
115
|
+
formatError
|
|
116
|
+
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
formatError,
|
|
4
|
+
isGhCliAvailable
|
|
5
|
+
} from "./chunk-2TW2MJXF.js";
|
|
2
6
|
|
|
3
7
|
// src/cli/detection.ts
|
|
4
8
|
import { execSync } from "child_process";
|
|
@@ -6,115 +10,6 @@ import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
|
6
10
|
import { tmpdir } from "os";
|
|
7
11
|
import { join, resolve as resolvePath } from "path";
|
|
8
12
|
import * as clack from "@clack/prompts";
|
|
9
|
-
|
|
10
|
-
// src/errors.ts
|
|
11
|
-
function formatError(err) {
|
|
12
|
-
return err instanceof Error ? err.message : String(err);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// src/git/github.ts
|
|
16
|
-
import { execa } from "execa";
|
|
17
|
-
|
|
18
|
-
// src/git/pr-body.ts
|
|
19
|
-
var PROVIDER_ATTRIBUTION_RE = /claude\.ai|claude\s+code|\banthropic\b|gemini\s+cli|\bgoogle\s+gemini\b|openai\s+codex|\bopenai\b|\bgoose\b|\baider\b|github\s+copilot|cursor\s+agent|\bopencode\b|\blisa\b/i;
|
|
20
|
-
var AI_COAUTHOR_RE = /co-authored-by:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)/i;
|
|
21
|
-
function stripProviderAttribution(body) {
|
|
22
|
-
let result = body;
|
|
23
|
-
while (true) {
|
|
24
|
-
const sepIndex = result.lastIndexOf("\n---");
|
|
25
|
-
if (sepIndex === -1) break;
|
|
26
|
-
const section = result.slice(sepIndex);
|
|
27
|
-
if (PROVIDER_ATTRIBUTION_RE.test(section) || AI_COAUTHOR_RE.test(section)) {
|
|
28
|
-
result = result.slice(0, sepIndex).trimEnd();
|
|
29
|
-
} else {
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
result = result.replace(
|
|
34
|
-
/\n+Co-Authored-By:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)[^\n]*/gi,
|
|
35
|
-
""
|
|
36
|
-
);
|
|
37
|
-
return result.trimEnd();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/git/github.ts
|
|
41
|
-
async function isGhCliAvailable() {
|
|
42
|
-
try {
|
|
43
|
-
await execa("gh", ["auth", "status"]);
|
|
44
|
-
return true;
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
var PROVIDER_DISPLAY_NAMES = {
|
|
50
|
-
claude: "Claude Code",
|
|
51
|
-
gemini: "Gemini CLI",
|
|
52
|
-
opencode: "OpenCode",
|
|
53
|
-
copilot: "GitHub Copilot CLI",
|
|
54
|
-
cursor: "Cursor Agent",
|
|
55
|
-
goose: "Goose",
|
|
56
|
-
aider: "Aider",
|
|
57
|
-
codex: "OpenAI Codex"
|
|
58
|
-
};
|
|
59
|
-
function formatProviderName(providerUsed) {
|
|
60
|
-
const providerKey = providerUsed.split("/")[0] ?? providerUsed;
|
|
61
|
-
return PROVIDER_DISPLAY_NAMES[providerKey] ?? providerKey;
|
|
62
|
-
}
|
|
63
|
-
async function deleteProviderComments(prUrl) {
|
|
64
|
-
try {
|
|
65
|
-
const match = prUrl.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
66
|
-
if (!match) return;
|
|
67
|
-
const [, owner, repo, prNumber] = match;
|
|
68
|
-
const { stdout } = await execa("gh", [
|
|
69
|
-
"api",
|
|
70
|
-
"--paginate",
|
|
71
|
-
"--jq",
|
|
72
|
-
".[]",
|
|
73
|
-
`/repos/${owner}/${repo}/issues/${prNumber}/comments`
|
|
74
|
-
]);
|
|
75
|
-
const comments = stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
76
|
-
for (const comment of comments) {
|
|
77
|
-
if (PROVIDER_ATTRIBUTION_RE.test(comment.body)) {
|
|
78
|
-
try {
|
|
79
|
-
await execa("gh", [
|
|
80
|
-
"api",
|
|
81
|
-
"--method",
|
|
82
|
-
"DELETE",
|
|
83
|
-
`/repos/${owner}/${repo}/issues/comments/${comment.id}`
|
|
84
|
-
]);
|
|
85
|
-
} catch {
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async function appendPrAttribution(prUrl, providerUsed) {
|
|
93
|
-
await deleteProviderComments(prUrl);
|
|
94
|
-
try {
|
|
95
|
-
const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
|
|
96
|
-
const { body } = JSON.parse(bodyJson);
|
|
97
|
-
const providerName = formatProviderName(providerUsed);
|
|
98
|
-
const attribution = `
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
\u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
|
|
102
|
-
const newBody = stripProviderAttribution(body ?? "") + attribution;
|
|
103
|
-
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
104
|
-
} catch {
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
async function appendPrBody(prUrl, content) {
|
|
108
|
-
try {
|
|
109
|
-
const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
|
|
110
|
-
const { body } = JSON.parse(bodyJson);
|
|
111
|
-
const newBody = (body ?? "") + content;
|
|
112
|
-
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
113
|
-
} catch {
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/cli/detection.ts
|
|
118
13
|
function getVersion() {
|
|
119
14
|
try {
|
|
120
15
|
const pkgPath = resolvePath(new URL(".", import.meta.url).pathname, "../package.json");
|
|
@@ -353,11 +248,6 @@ async function getMissingEnvVars(source) {
|
|
|
353
248
|
}
|
|
354
249
|
|
|
355
250
|
export {
|
|
356
|
-
stripProviderAttribution,
|
|
357
|
-
isGhCliAvailable,
|
|
358
|
-
appendPrAttribution,
|
|
359
|
-
appendPrBody,
|
|
360
|
-
formatError,
|
|
361
251
|
getVersion,
|
|
362
252
|
isCursorFreePlan,
|
|
363
253
|
fetchCursorModels,
|
|
@@ -3,13 +3,6 @@ import {
|
|
|
3
3
|
notify
|
|
4
4
|
} from "./chunk-72CYGBT4.js";
|
|
5
5
|
|
|
6
|
-
// src/ui/state.ts
|
|
7
|
-
import { EventEmitter } from "events";
|
|
8
|
-
import { useEffect, useState } from "react";
|
|
9
|
-
|
|
10
|
-
// src/sources/github-issues.ts
|
|
11
|
-
import { execa } from "execa";
|
|
12
|
-
|
|
13
6
|
// src/output/logger.ts
|
|
14
7
|
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
|
|
15
8
|
import { dirname } from "path";
|
|
@@ -100,6 +93,13 @@ function updateNotice(update) {
|
|
|
100
93
|
`));
|
|
101
94
|
}
|
|
102
95
|
|
|
96
|
+
// src/ui/state.ts
|
|
97
|
+
import { EventEmitter } from "events";
|
|
98
|
+
import { useEffect, useState } from "react";
|
|
99
|
+
|
|
100
|
+
// src/sources/github-issues.ts
|
|
101
|
+
import { execa } from "execa";
|
|
102
|
+
|
|
103
103
|
// src/sources/base.ts
|
|
104
104
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
105
105
|
function normalizeLabels(config) {
|
|
@@ -425,6 +425,16 @@ var GitHubIssuesSource = class {
|
|
|
425
425
|
} catch {
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
|
+
async createIssue(opts, config) {
|
|
429
|
+
const { owner, repo } = parseOwnerRepo(config.scope);
|
|
430
|
+
const labels = Array.isArray(opts.label) ? opts.label : [opts.label];
|
|
431
|
+
const issue = await api().post(`/repos/${owner}/${repo}/issues`, {
|
|
432
|
+
title: opts.title,
|
|
433
|
+
body: opts.description,
|
|
434
|
+
labels
|
|
435
|
+
});
|
|
436
|
+
return makeIssueId(owner, repo, issue.number);
|
|
437
|
+
}
|
|
428
438
|
};
|
|
429
439
|
|
|
430
440
|
// src/sources/gitlab-issues.ts
|
|
@@ -642,6 +652,30 @@ var GitLabIssuesSource = class {
|
|
|
642
652
|
labels: filtered.join(",")
|
|
643
653
|
});
|
|
644
654
|
}
|
|
655
|
+
async createIssue(opts, config) {
|
|
656
|
+
const encodedProject = parseGitLabProject(config.scope);
|
|
657
|
+
const labels = Array.isArray(opts.label) ? opts.label : [opts.label];
|
|
658
|
+
const issue = await api2().post(`/projects/${encodedProject}/issues`, {
|
|
659
|
+
title: opts.title,
|
|
660
|
+
description: opts.description,
|
|
661
|
+
labels: labels.join(","),
|
|
662
|
+
...opts.order !== void 0 && { weight: opts.order }
|
|
663
|
+
});
|
|
664
|
+
return makeIssueId2(config.scope, issue.iid);
|
|
665
|
+
}
|
|
666
|
+
async linkDependency(issueId, dependsOnId) {
|
|
667
|
+
const source = splitIssueId(issueId);
|
|
668
|
+
const target = splitIssueId(dependsOnId);
|
|
669
|
+
const encodedProject = parseGitLabProject(source.project);
|
|
670
|
+
const projectInfo = await api2().get(
|
|
671
|
+
`/projects/${parseGitLabProject(target.project)}`
|
|
672
|
+
);
|
|
673
|
+
await api2().post(`/projects/${encodedProject}/issues/${source.iid}/links`, {
|
|
674
|
+
target_project_id: projectInfo.id,
|
|
675
|
+
target_issue_iid: Number(target.iid),
|
|
676
|
+
link_type: "is_blocked_by"
|
|
677
|
+
});
|
|
678
|
+
}
|
|
645
679
|
};
|
|
646
680
|
function parseGitLabProject(input) {
|
|
647
681
|
if (/^\d+$/.test(input)) return input;
|
|
@@ -874,6 +908,7 @@ function useKanbanState(bellEnabled, initialCards = []) {
|
|
|
874
908
|
const onModelChanged = (model) => setModelInUse(model);
|
|
875
909
|
kanbanEmitter.on("provider:model-changed", onModelChanged);
|
|
876
910
|
const onEmpty = () => setIsEmpty(true);
|
|
911
|
+
const onResumed = () => setIsEmpty(false);
|
|
877
912
|
const onComplete = (data) => setWorkComplete(data);
|
|
878
913
|
const onWatching = () => setIsWatching(true);
|
|
879
914
|
const onWatchResume = () => setIsWatching(false);
|
|
@@ -883,6 +918,7 @@ function useKanbanState(bellEnabled, initialCards = []) {
|
|
|
883
918
|
};
|
|
884
919
|
const onWatchPromptResolved = () => setIsWatchPrompt(false);
|
|
885
920
|
kanbanEmitter.on("work:empty", onEmpty);
|
|
921
|
+
kanbanEmitter.on("work:resumed", onResumed);
|
|
886
922
|
kanbanEmitter.on("work:complete", onComplete);
|
|
887
923
|
kanbanEmitter.on("work:watching", onWatching);
|
|
888
924
|
kanbanEmitter.on("work:watch-resume", onWatchResume);
|
|
@@ -909,6 +945,7 @@ function useKanbanState(bellEnabled, initialCards = []) {
|
|
|
909
945
|
kanbanEmitter.off("issue:output", onOutput);
|
|
910
946
|
kanbanEmitter.off("provider:model-changed", onModelChanged);
|
|
911
947
|
kanbanEmitter.off("work:empty", onEmpty);
|
|
948
|
+
kanbanEmitter.off("work:resumed", onResumed);
|
|
912
949
|
kanbanEmitter.off("work:complete", onComplete);
|
|
913
950
|
kanbanEmitter.off("work:watching", onWatching);
|
|
914
951
|
kanbanEmitter.off("work:watch-resume", onWatchResume);
|