@tarcisiopgs/lisa 1.27.0 → 1.28.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 +37 -23
- 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-KDAXGOFF.js +3067 -0
- package/dist/chunk-UXVSQQID.js +3924 -0
- package/dist/{detection-FUU6FUZZ.js → detection-JT7HSKSX.js} +2 -1
- package/dist/index.js +1420 -7826
- package/dist/{kanban-MHKZIHS3.js → kanban-HFUBTOF4.js} +536 -103
- package/dist/loop-SXI4PQOI.js +21 -0
- package/dist/tui-bridge-DCC4JAPM.js +164 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Lisa
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<strong>
|
|
4
|
+
<strong>Plan issues. Run agents. Get PRs.</strong>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -15,23 +15,23 @@
|
|
|
15
15
|
<img src="assets/demo.gif" alt="Lisa demo" />
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
Lisa connects your issue tracker to an AI coding agent and delivers pull requests — autonomously.
|
|
18
|
+
Lisa connects your issue tracker to an AI coding agent and delivers pull requests — autonomously. Describe a goal, Lisa decomposes it into issues, picks them up, implements each one, opens PRs, and updates your board. No babysitting.
|
|
19
19
|
|
|
20
20
|
## Quickstart
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
23
|
npm install -g @tarcisiopgs/lisa
|
|
24
24
|
lisa init # interactive setup wizard
|
|
25
|
-
lisa
|
|
25
|
+
lisa # start the agent loop
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## How It Works
|
|
29
29
|
|
|
30
30
|
```
|
|
31
|
-
|
|
31
|
+
Plan → Create issues → Fetch → Implement → Push → Open PR → Update board → Next
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Lisa picks the highest-priority labeled issue, moves it to "In Progress", sends a structured prompt to the AI agent, and monitors execution. The agent works in an isolated git worktree, implements the change, runs tests, and commits. Lisa pushes, opens a PR, moves the ticket to "In Review", and picks up the next one.
|
|
34
|
+
Lisa starts and shows a Kanban board. If the queue is empty, press `n` to plan — describe a goal and the AI decomposes it into atomic issues, created directly in your tracker. Press `r` to start processing. Lisa picks the highest-priority labeled issue, moves it to "In Progress", sends a structured prompt to the AI agent, and monitors execution. The agent works in an isolated git worktree, implements the change, runs tests, and commits. Lisa pushes, opens a PR, moves the ticket to "In Review", and picks up the next one.
|
|
35
35
|
|
|
36
36
|
If something fails — pre-push hooks, quota limits, stuck processes — Lisa handles it: retries with error context, falls back to the next model, or kills and moves on.
|
|
37
37
|
|
|
@@ -39,10 +39,11 @@ If something fails — pre-push hooks, quota limits, stuck processes — Lisa ha
|
|
|
39
39
|
|
|
40
40
|
- **7 issue trackers** — Linear, GitHub Issues, GitLab Issues, Jira, Trello, Plane, Shortcut
|
|
41
41
|
- **8 AI agents** — Claude Code, Gemini CLI, GitHub Copilot CLI, Cursor Agent, Aider, Goose, OpenCode, Codex
|
|
42
|
+
- **AI planning** — describe a goal, the AI decomposes it into issues with dependencies, created in your tracker
|
|
42
43
|
- **Concurrent execution** — process multiple issues in parallel, each in its own worktree
|
|
43
44
|
- **Multi-repo** — plans across repos, creates one PR per repo in the correct order
|
|
44
45
|
- **Model fallback** — chain models; transient errors (429, quota, timeout) auto-switch to the next
|
|
45
|
-
- **Real-time TUI** — Kanban board with live provider output,
|
|
46
|
+
- **Real-time TUI** — Kanban board with live provider output, plan mode, PR merge detection
|
|
46
47
|
- **Self-healing** — orphan recovery on startup, push failure retry, stuck process detection
|
|
47
48
|
- **Guardrails** — past failures are injected into future prompts to avoid repeating mistakes
|
|
48
49
|
- **Project context** — auto-generates `.lisa/context.md` with your stack, conventions, and constraints
|
|
@@ -82,18 +83,21 @@ provider_options:
|
|
|
82
83
|
## Commands
|
|
83
84
|
|
|
84
85
|
```bash
|
|
85
|
-
lisa
|
|
86
|
-
lisa
|
|
87
|
-
lisa
|
|
88
|
-
lisa
|
|
89
|
-
lisa
|
|
90
|
-
lisa
|
|
91
|
-
lisa
|
|
92
|
-
lisa
|
|
93
|
-
lisa
|
|
94
|
-
lisa
|
|
95
|
-
lisa
|
|
96
|
-
lisa
|
|
86
|
+
lisa # start the agent loop (Kanban TUI)
|
|
87
|
+
lisa --once # process a single issue
|
|
88
|
+
lisa --once --dry-run # preview config without executing
|
|
89
|
+
lisa --watch # poll for new issues after queue empties
|
|
90
|
+
lisa -c 3 # process 3 issues in parallel
|
|
91
|
+
lisa --issue INT-42 # process a specific issue
|
|
92
|
+
lisa --limit 5 # stop after 5 issues
|
|
93
|
+
lisa plan "Add rate limiting" # decompose goal into issues via AI (CLI mode)
|
|
94
|
+
lisa plan --issue EPIC-123 # decompose existing issue into sub-issues
|
|
95
|
+
lisa plan --continue # resume interrupted plan
|
|
96
|
+
lisa init # create .lisa/config.yaml interactively
|
|
97
|
+
lisa status # show session stats
|
|
98
|
+
lisa doctor # diagnose setup issues (config, provider, env, git)
|
|
99
|
+
lisa context refresh # regenerate project context
|
|
100
|
+
lisa feedback --pr URL # inject PR review feedback into guardrails
|
|
97
101
|
```
|
|
98
102
|
|
|
99
103
|
Append `--json` to any command for machine-readable output. Use `--verbose` / `--quiet` to control log verbosity.
|
|
@@ -238,16 +242,16 @@ Acceptance criteria:
|
|
|
238
242
|
|
|
239
243
|
## TUI
|
|
240
244
|
|
|
241
|
-
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. When the queue is empty, Lisa enters idle mode — plan new issues with `n`, then start processing with `r`.
|
|
242
246
|
|
|
243
247
|
**Board view**
|
|
244
248
|
|
|
245
249
|
| Key | Action | Key | Action |
|
|
246
250
|
|-----|--------|-----|--------|
|
|
247
|
-
| `←` `→` | Switch columns | `
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
251
|
+
| `←` `→` | Switch columns | `k` | Kill current issue |
|
|
252
|
+
| `↑` `↓` | Navigate cards | `n` | Open plan mode |
|
|
253
|
+
| `↵` | Open detail view | `r` | Run (from idle) |
|
|
254
|
+
| `p` | Pause / resume | `q` | Quit |
|
|
251
255
|
|
|
252
256
|
**Detail view**
|
|
253
257
|
|
|
@@ -257,6 +261,16 @@ The real-time Kanban board shows issue progress, streams provider output, and de
|
|
|
257
261
|
| `o` | Open PR in browser |
|
|
258
262
|
| `Esc` | Back to board |
|
|
259
263
|
|
|
264
|
+
**Plan mode**
|
|
265
|
+
|
|
266
|
+
| Key | Action |
|
|
267
|
+
|-----|--------|
|
|
268
|
+
| `↵` | Send message / view detail |
|
|
269
|
+
| `e` | Edit issue in $EDITOR |
|
|
270
|
+
| `d` | Delete issue |
|
|
271
|
+
| `a` | Approve and create issues |
|
|
272
|
+
| `Esc` | Cancel / back |
|
|
273
|
+
|
|
260
274
|
## License
|
|
261
275
|
|
|
262
276
|
[MIT](LICENSE)
|
|
@@ -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);
|