@tarcisiopgs/lisa 1.26.2 → 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 +21 -5
- package/dist/chunk-2TW2MJXF.js +116 -0
- package/dist/{chunk-CDI22S63.js → chunk-3EOEDL3T.js} +2 -2
- package/dist/{chunk-AGYOJQBR.js → chunk-42NKASE3.js} +8 -106
- package/dist/{chunk-235DMOXJ.js → chunk-5N4BWHIT.js} +186 -116
- package/dist/chunk-FCEUJ7VK.js +407 -0
- package/dist/chunk-UXVSQQID.js +3924 -0
- package/dist/chunk-W73XGHD4.js +3059 -0
- package/dist/{detection-OWEDBW7B.js → detection-JT7HSKSX.js} +2 -1
- package/dist/{guardrails-6IG2C4KJ.js → guardrails-IX3VVUO5.js} +1 -1
- package/dist/index.js +1474 -7340
- package/dist/{kanban-PSOTFEQI.js → kanban-2WMA5VPQ.js} +551 -116
- package/dist/loop-KNJIRK7T.js +21 -0
- package/dist/tui-bridge-DCC4JAPM.js +164 -0
- package/package.json +5 -4
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,12 +90,18 @@ 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
|
|
98
|
+
lisa doctor # diagnose setup issues (config, provider, env, git)
|
|
94
99
|
lisa context refresh # regenerate project context
|
|
95
100
|
lisa feedback --pr URL # inject PR review feedback into guardrails
|
|
96
101
|
```
|
|
97
102
|
|
|
103
|
+
Append `--json` to any command for machine-readable output. Use `--verbose` / `--quiet` to control log verbosity.
|
|
104
|
+
|
|
98
105
|
## Configuration
|
|
99
106
|
|
|
100
107
|
Config lives in `.lisa/config.yaml`. Run `lisa init` to create it interactively.
|
|
@@ -235,15 +242,24 @@ Acceptance criteria:
|
|
|
235
242
|
|
|
236
243
|
## TUI
|
|
237
244
|
|
|
238
|
-
The real-time Kanban board shows issue progress, streams provider output, and detects PR merges.
|
|
245
|
+
The real-time Kanban board shows issue progress, streams provider output, and detects PR merges. The sidebar legend updates contextually — only the shortcuts active in the current view are shown.
|
|
246
|
+
|
|
247
|
+
**Board view**
|
|
239
248
|
|
|
240
249
|
| Key | Action | Key | Action |
|
|
241
250
|
|-----|--------|-----|--------|
|
|
242
251
|
| `←` `→` | Switch columns | `p` | Pause / resume provider |
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
252
|
+
| `1` `2` `3` | Jump to column | `k` | Kill current issue |
|
|
253
|
+
| `↑` `↓` | Navigate cards | `s` | Skip current issue |
|
|
254
|
+
| `↵` | Open detail view | `q` | Quit |
|
|
255
|
+
|
|
256
|
+
**Detail view**
|
|
257
|
+
|
|
258
|
+
| Key | Action |
|
|
259
|
+
|-----|--------|
|
|
260
|
+
| `↑` `↓` | Scroll output log |
|
|
261
|
+
| `o` | Open PR in browser |
|
|
262
|
+
| `Esc` | Back to board |
|
|
247
263
|
|
|
248
264
|
## License
|
|
249
265
|
|
|
@@ -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
|
+
};
|
|
@@ -81,7 +81,7 @@ ${newEntryText}`;
|
|
|
81
81
|
|
|
82
82
|
${rotated.join("\n\n")}`;
|
|
83
83
|
}
|
|
84
|
-
writeFileSync(path, content, "utf-8");
|
|
84
|
+
writeFileSync(path, content, { encoding: "utf-8", mode: 384 });
|
|
85
85
|
}
|
|
86
86
|
function appendRawEntry(dir, entryText) {
|
|
87
87
|
writeLock = writeLock.then(() => appendRawEntrySync(dir, entryText)).catch(() => {
|
|
@@ -108,7 +108,7 @@ ${entryText}`;
|
|
|
108
108
|
|
|
109
109
|
${rotated.join("\n\n")}`;
|
|
110
110
|
}
|
|
111
|
-
writeFileSync(path, content, "utf-8");
|
|
111
|
+
writeFileSync(path, content, { encoding: "utf-8", mode: 384 });
|
|
112
112
|
}
|
|
113
113
|
function formatEntry(entry) {
|
|
114
114
|
return [
|
|
@@ -1,106 +1,15 @@
|
|
|
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";
|
|
5
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
9
|
+
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/git/github.ts
|
|
11
|
-
import { execa } from "execa";
|
|
12
|
-
|
|
13
|
-
// src/git/pr-body.ts
|
|
14
|
-
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;
|
|
15
|
-
var AI_COAUTHOR_RE = /co-authored-by:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)/i;
|
|
16
|
-
function stripProviderAttribution(body) {
|
|
17
|
-
let result = body;
|
|
18
|
-
while (true) {
|
|
19
|
-
const sepIndex = result.lastIndexOf("\n---");
|
|
20
|
-
if (sepIndex === -1) break;
|
|
21
|
-
const section = result.slice(sepIndex);
|
|
22
|
-
if (PROVIDER_ATTRIBUTION_RE.test(section) || AI_COAUTHOR_RE.test(section)) {
|
|
23
|
-
result = result.slice(0, sepIndex).trimEnd();
|
|
24
|
-
} else {
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
result = result.replace(
|
|
29
|
-
/\n+Co-Authored-By:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)[^\n]*/gi,
|
|
30
|
-
""
|
|
31
|
-
);
|
|
32
|
-
return result.trimEnd();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/git/github.ts
|
|
36
|
-
async function isGhCliAvailable() {
|
|
37
|
-
try {
|
|
38
|
-
await execa("gh", ["auth", "status"]);
|
|
39
|
-
return true;
|
|
40
|
-
} catch {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
var PROVIDER_DISPLAY_NAMES = {
|
|
45
|
-
claude: "Claude Code",
|
|
46
|
-
gemini: "Gemini CLI",
|
|
47
|
-
opencode: "OpenCode",
|
|
48
|
-
copilot: "GitHub Copilot CLI",
|
|
49
|
-
cursor: "Cursor Agent",
|
|
50
|
-
goose: "Goose",
|
|
51
|
-
aider: "Aider",
|
|
52
|
-
codex: "OpenAI Codex"
|
|
53
|
-
};
|
|
54
|
-
function formatProviderName(providerUsed) {
|
|
55
|
-
const providerKey = providerUsed.split("/")[0] ?? providerUsed;
|
|
56
|
-
return PROVIDER_DISPLAY_NAMES[providerKey] ?? providerKey;
|
|
57
|
-
}
|
|
58
|
-
async function deleteProviderComments(prUrl) {
|
|
59
|
-
try {
|
|
60
|
-
const match = prUrl.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
61
|
-
if (!match) return;
|
|
62
|
-
const [, owner, repo, prNumber] = match;
|
|
63
|
-
const { stdout } = await execa("gh", [
|
|
64
|
-
"api",
|
|
65
|
-
"--paginate",
|
|
66
|
-
"--jq",
|
|
67
|
-
".[]",
|
|
68
|
-
`/repos/${owner}/${repo}/issues/${prNumber}/comments`
|
|
69
|
-
]);
|
|
70
|
-
const comments = stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
71
|
-
for (const comment of comments) {
|
|
72
|
-
if (PROVIDER_ATTRIBUTION_RE.test(comment.body)) {
|
|
73
|
-
try {
|
|
74
|
-
await execa("gh", [
|
|
75
|
-
"api",
|
|
76
|
-
"--method",
|
|
77
|
-
"DELETE",
|
|
78
|
-
`/repos/${owner}/${repo}/issues/comments/${comment.id}`
|
|
79
|
-
]);
|
|
80
|
-
} catch {
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function appendPrAttribution(prUrl, providerUsed) {
|
|
88
|
-
await deleteProviderComments(prUrl);
|
|
89
|
-
try {
|
|
90
|
-
const { stdout: bodyJson } = await execa("gh", ["pr", "view", prUrl, "--json", "body"]);
|
|
91
|
-
const { body } = JSON.parse(bodyJson);
|
|
92
|
-
const providerName = formatProviderName(providerUsed);
|
|
93
|
-
const attribution = `
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
\u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
|
|
97
|
-
const newBody = stripProviderAttribution(body ?? "") + attribution;
|
|
98
|
-
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
99
|
-
} catch {
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// src/cli/detection.ts
|
|
104
13
|
function getVersion() {
|
|
105
14
|
try {
|
|
106
15
|
const pkgPath = resolvePath(new URL(".", import.meta.url).pathname, "../package.json");
|
|
@@ -112,7 +21,7 @@ function getVersion() {
|
|
|
112
21
|
}
|
|
113
22
|
var CURSOR_FREE_PLAN_ERROR = "Free plans can only use Auto";
|
|
114
23
|
async function isCursorFreePlan() {
|
|
115
|
-
const { mkdtempSync,
|
|
24
|
+
const { mkdtempSync, writeFileSync } = await import("fs");
|
|
116
25
|
const tmpDir = mkdtempSync(join(tmpdir(), "lisa-cursor-check-"));
|
|
117
26
|
const promptFile = join(tmpDir, "prompt.txt");
|
|
118
27
|
writeFileSync(promptFile, "test", "utf-8");
|
|
@@ -133,15 +42,11 @@ async function isCursorFreePlan() {
|
|
|
133
42
|
});
|
|
134
43
|
return output.includes(CURSOR_FREE_PLAN_ERROR);
|
|
135
44
|
} catch (err) {
|
|
136
|
-
const errorOutput =
|
|
45
|
+
const errorOutput = formatError(err);
|
|
137
46
|
return errorOutput.includes(CURSOR_FREE_PLAN_ERROR);
|
|
138
47
|
} finally {
|
|
139
48
|
try {
|
|
140
|
-
|
|
141
|
-
} catch {
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
execSync(`rm -rf ${tmpDir}`, { stdio: "ignore" });
|
|
49
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
145
50
|
} catch {
|
|
146
51
|
}
|
|
147
52
|
}
|
|
@@ -343,9 +248,6 @@ async function getMissingEnvVars(source) {
|
|
|
343
248
|
}
|
|
344
249
|
|
|
345
250
|
export {
|
|
346
|
-
stripProviderAttribution,
|
|
347
|
-
isGhCliAvailable,
|
|
348
|
-
appendPrAttribution,
|
|
349
251
|
getVersion,
|
|
350
252
|
isCursorFreePlan,
|
|
351
253
|
fetchCursorModels,
|