@tarcisiopgs/lisa 1.26.0 → 1.26.2
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 +39 -5
- package/dist/index.js +37 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,13 +56,27 @@ If something fails — pre-push hooks, quota limits, stuck processes — Lisa ha
|
|
|
56
56
|
| GitHub Copilot CLI | `copilot` | Aider | `aider` |
|
|
57
57
|
| OpenCode | `opencode` | OpenAI Codex | `codex` |
|
|
58
58
|
|
|
59
|
-
Configure
|
|
59
|
+
Configure models and provider-specific options:
|
|
60
60
|
|
|
61
61
|
```yaml
|
|
62
62
|
provider: claude
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
provider_options:
|
|
64
|
+
claude:
|
|
65
|
+
models:
|
|
66
|
+
- claude-sonnet-4-6 # primary
|
|
67
|
+
- claude-opus-4-6 # fallback
|
|
68
|
+
effort: high # optional: low, medium, high, max
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Goose requires a backend selection:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
provider: goose
|
|
75
|
+
provider_options:
|
|
76
|
+
goose:
|
|
77
|
+
goose_provider: gemini-cli # gemini-cli, anthropic, openai, google, ollama
|
|
78
|
+
models:
|
|
79
|
+
- gemini-2.5-pro
|
|
66
80
|
```
|
|
67
81
|
|
|
68
82
|
## Commands
|
|
@@ -93,11 +107,14 @@ workflow: worktree # "worktree" (isolated) or "branch" (in-place)
|
|
|
93
107
|
source_config:
|
|
94
108
|
scope: Engineering
|
|
95
109
|
project: Web App
|
|
96
|
-
label: ready
|
|
110
|
+
label: ready # or array: [ready, urgent]
|
|
111
|
+
remove_label: ready # label to remove on completion (defaults to label)
|
|
97
112
|
pick_from: Backlog
|
|
98
113
|
in_progress: In Progress
|
|
99
114
|
done: In Review
|
|
100
115
|
|
|
116
|
+
bell: true # terminal bell on issue completion
|
|
117
|
+
|
|
101
118
|
platform: cli # "cli" (gh), "token" (GITHUB_TOKEN), "gitlab", "bitbucket"
|
|
102
119
|
base_branch: main
|
|
103
120
|
```
|
|
@@ -120,6 +137,21 @@ SHORTCUT_API_TOKEN=""
|
|
|
120
137
|
GITLAB_TOKEN=""
|
|
121
138
|
GITHUB_TOKEN=""
|
|
122
139
|
JIRA_BASE_URL="" && JIRA_EMAIL="" && JIRA_API_TOKEN=""
|
|
140
|
+
|
|
141
|
+
# Self-hosted instances (optional)
|
|
142
|
+
PLANE_BASE_URL="" # default: https://api.plane.so
|
|
143
|
+
GITLAB_BASE_URL="" # default: https://gitlab.com
|
|
144
|
+
PLANE_WORKSPACE="" # fallback for source_config.scope
|
|
145
|
+
|
|
146
|
+
# Goose backend (required when provider: goose)
|
|
147
|
+
GOOSE_PROVIDER="" # gemini-cli, anthropic, openai, google, ollama
|
|
148
|
+
GOOSE_MODEL="" # model name for the selected backend
|
|
149
|
+
|
|
150
|
+
# AI provider API keys (used by Aider / Goose / wizard auto-detection)
|
|
151
|
+
ANTHROPIC_API_KEY=""
|
|
152
|
+
OPENAI_API_KEY=""
|
|
153
|
+
GEMINI_API_KEY=""
|
|
154
|
+
GOOGLE_API_KEY="" # for Goose with goose_provider: google
|
|
123
155
|
```
|
|
124
156
|
|
|
125
157
|
</details>
|
|
@@ -133,6 +165,7 @@ JIRA_BASE_URL="" && JIRA_EMAIL="" && JIRA_API_TOKEN=""
|
|
|
133
165
|
| `project` | Project name | — | Project ID | — | — | — | — |
|
|
134
166
|
| `pick_from` | Status name | List name | State name | Workflow state | — | — | Status name |
|
|
135
167
|
| `label` | Label | Label | Label | Label | Label | Label | Label |
|
|
168
|
+
| `remove_label` | Label | Label | Label | Label | — | — | — |
|
|
136
169
|
| `in_progress` | Status | Column | State | Workflow state | Label | Label | Status |
|
|
137
170
|
| `done` | Status | Column | State | Workflow state | Closes issue | Closes issue | Status |
|
|
138
171
|
|
|
@@ -163,6 +196,7 @@ Lisa runs a planning phase, then executes steps sequentially — one worktree an
|
|
|
163
196
|
loop:
|
|
164
197
|
cooldown: 10 # seconds between issues
|
|
165
198
|
session_timeout: 0 # max seconds per provider run (0 = disabled)
|
|
199
|
+
output_stall_timeout: 120 # seconds without stdout before killing provider (0 = disabled)
|
|
166
200
|
|
|
167
201
|
overseer:
|
|
168
202
|
enabled: true
|
package/dist/index.js
CHANGED
|
@@ -1406,6 +1406,9 @@ async function jiraPost(path, body) {
|
|
|
1406
1406
|
async function jiraPut(path, body) {
|
|
1407
1407
|
return jiraFetch("PUT", path, body);
|
|
1408
1408
|
}
|
|
1409
|
+
async function jiraSearchJql(jql, fields, maxResults) {
|
|
1410
|
+
return jiraPost("/search/jql", { jql, fields: fields.split(","), maxResults });
|
|
1411
|
+
}
|
|
1409
1412
|
function priorityRank(issue2) {
|
|
1410
1413
|
const name = issue2.fields.priority?.name?.toLowerCase() ?? "";
|
|
1411
1414
|
return PRIORITY_RANK[name] ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -1432,6 +1435,21 @@ function extractAdfText(node) {
|
|
|
1432
1435
|
function escapeJql(value) {
|
|
1433
1436
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1434
1437
|
}
|
|
1438
|
+
async function resolveStatusId(scope, statusName) {
|
|
1439
|
+
try {
|
|
1440
|
+
const data = await jiraGet(
|
|
1441
|
+
`/project/${encodeURIComponent(scope)}/statuses`
|
|
1442
|
+
);
|
|
1443
|
+
for (const issueType of data) {
|
|
1444
|
+
const match = issueType.statuses.find(
|
|
1445
|
+
(s) => s.name.toLowerCase() === statusName.toLowerCase()
|
|
1446
|
+
);
|
|
1447
|
+
if (match) return match.id;
|
|
1448
|
+
}
|
|
1449
|
+
} catch {
|
|
1450
|
+
}
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1435
1453
|
function issueUrl(baseUrl, key) {
|
|
1436
1454
|
return `${baseUrl}/browse/${key}`;
|
|
1437
1455
|
}
|
|
@@ -1440,13 +1458,11 @@ var JiraSource = class {
|
|
|
1440
1458
|
async fetchNextIssue(config2) {
|
|
1441
1459
|
const labels = Array.isArray(config2.label) ? config2.label : [config2.label];
|
|
1442
1460
|
const labelClause = labels.map((l) => `labels = "${escapeJql(l)}"`).join(" AND ");
|
|
1443
|
-
const
|
|
1444
|
-
|
|
1445
|
-
)
|
|
1461
|
+
const statusId = await resolveStatusId(config2.scope, config2.pick_from);
|
|
1462
|
+
const statusClause = statusId ? `status = ${statusId}` : `status = "${escapeJql(config2.pick_from)}"`;
|
|
1463
|
+
const jql = `project = "${escapeJql(config2.scope)}" AND ${labelClause} AND ${statusClause} ORDER BY priority ASC, created ASC`;
|
|
1446
1464
|
const fields = "summary,description,priority,status,labels,issuelinks";
|
|
1447
|
-
const data = await
|
|
1448
|
-
`/search/jql?jql=${jql}&fields=${fields}&maxResults=50`
|
|
1449
|
-
);
|
|
1465
|
+
const data = await jiraSearchJql(jql, fields, 50);
|
|
1450
1466
|
const issues = data.issues ?? [];
|
|
1451
1467
|
if (issues.length === 0) return null;
|
|
1452
1468
|
const unblocked = [];
|
|
@@ -1504,11 +1520,10 @@ var JiraSource = class {
|
|
|
1504
1520
|
async updateStatus(issueId, statusName) {
|
|
1505
1521
|
const key = parseJiraIdentifier(issueId);
|
|
1506
1522
|
const data = await jiraGet(`/issue/${key}/transitions`);
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
);
|
|
1523
|
+
const lowerName = statusName.toLowerCase();
|
|
1524
|
+
const transition = data.transitions.find((t) => t.to.name.toLowerCase() === lowerName) ?? data.transitions.find((t) => t.name.toLowerCase() === lowerName);
|
|
1510
1525
|
if (!transition) {
|
|
1511
|
-
const available = data.transitions.map((t) => t.name).join(", ");
|
|
1526
|
+
const available = data.transitions.map((t) => `${t.name} \u2192 ${t.to.name}`).join(", ");
|
|
1512
1527
|
throw new Error(`Jira transition "${statusName}" not found. Available: ${available}`);
|
|
1513
1528
|
}
|
|
1514
1529
|
await jiraPost(`/issue/${key}/transitions`, { transition: { id: transition.id } });
|
|
@@ -1535,13 +1550,11 @@ var JiraSource = class {
|
|
|
1535
1550
|
async listIssues(config2) {
|
|
1536
1551
|
const labels = Array.isArray(config2.label) ? config2.label : [config2.label];
|
|
1537
1552
|
const labelClause = labels.map((l) => `labels = "${escapeJql(l)}"`).join(" AND ");
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
)
|
|
1553
|
+
const statusId = await resolveStatusId(config2.scope, config2.pick_from);
|
|
1554
|
+
const statusClause = statusId ? `status = ${statusId}` : `status = "${escapeJql(config2.pick_from)}"`;
|
|
1555
|
+
const jql = `project = "${escapeJql(config2.scope)}" AND ${labelClause} AND ${statusClause} ORDER BY priority ASC, created ASC`;
|
|
1541
1556
|
const fields = "summary,description,priority,status,labels";
|
|
1542
|
-
const data = await
|
|
1543
|
-
`/search/jql?jql=${jql}&fields=${fields}&maxResults=100`
|
|
1544
|
-
);
|
|
1557
|
+
const data = await jiraSearchJql(jql, fields, 100);
|
|
1545
1558
|
const baseUrl = getBaseUrl();
|
|
1546
1559
|
return (data.issues ?? []).map((issue2) => ({
|
|
1547
1560
|
id: issue2.key,
|
|
@@ -2552,14 +2565,15 @@ var ShortcutSource = class {
|
|
|
2552
2565
|
}
|
|
2553
2566
|
async removeLabel(storyId, labelName) {
|
|
2554
2567
|
const story = await shortcutGet(`/api/v3/stories/${storyId}`);
|
|
2555
|
-
const
|
|
2556
|
-
const
|
|
2568
|
+
const allLabels = await shortcutGet("/api/v3/labels");
|
|
2569
|
+
const labelToRemove = allLabels.find(
|
|
2557
2570
|
(l) => l.name.toLowerCase() === labelName.toLowerCase() && !l.archived
|
|
2558
2571
|
);
|
|
2559
|
-
if (!
|
|
2560
|
-
const
|
|
2572
|
+
if (!labelToRemove || !story.label_ids.includes(labelToRemove.id)) return;
|
|
2573
|
+
const remainingIds = story.label_ids.filter((lid) => lid !== labelToRemove.id);
|
|
2574
|
+
const labelNames = remainingIds.map((lid) => allLabels.find((l) => l.id === lid)).filter((l) => l !== void 0).map((l) => ({ name: l.name }));
|
|
2561
2575
|
await shortcutPut(`/api/v3/stories/${storyId}`, {
|
|
2562
|
-
|
|
2576
|
+
labels: labelNames
|
|
2563
2577
|
});
|
|
2564
2578
|
}
|
|
2565
2579
|
};
|
|
@@ -4663,17 +4677,14 @@ function formatProviderName(providerUsed) {
|
|
|
4663
4677
|
function getAuthHeader2() {
|
|
4664
4678
|
const token = process.env.BITBUCKET_TOKEN;
|
|
4665
4679
|
if (!token) throw new Error("BITBUCKET_TOKEN is not set");
|
|
4666
|
-
|
|
4667
|
-
if (!username) throw new Error("BITBUCKET_USERNAME is not set");
|
|
4668
|
-
const credentials = Buffer.from(`${username}:${token}`).toString("base64");
|
|
4669
|
-
return `Basic ${credentials}`;
|
|
4680
|
+
return `Bearer ${token}`;
|
|
4670
4681
|
}
|
|
4671
4682
|
async function appendPrAttribution2(prUrl, providerUsed) {
|
|
4672
4683
|
try {
|
|
4673
4684
|
const match = prUrl.match(/bitbucket\.org\/([^/]+)\/([^/]+)\/pull-requests\/(\d+)/);
|
|
4674
4685
|
if (!match) return;
|
|
4675
4686
|
const [, workspace, repoSlug, prId] = match;
|
|
4676
|
-
if (!process.env.BITBUCKET_TOKEN
|
|
4687
|
+
if (!process.env.BITBUCKET_TOKEN) return;
|
|
4677
4688
|
const authHeader = getAuthHeader2();
|
|
4678
4689
|
const getRes = await fetch(
|
|
4679
4690
|
`${API_URL3}/repositories/${workspace}/${repoSlug}/pullrequests/${prId}`,
|