@tarcisiopgs/lisa 1.36.0 → 1.37.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 +2 -1
- package/dist/chunk-4MZ2565Y.js +32 -0
- package/dist/chunk-HPWL5JRW.js +113 -0
- package/dist/{chunk-AFKXWCAM.js → chunk-I5HJELVZ.js} +9 -4
- package/dist/{chunk-V44FTYWZ.js → chunk-LR2GREZS.js} +145 -146
- package/dist/{chunk-R6D5VH65.js → chunk-MC4SAOF2.js} +23 -11
- package/dist/{chunk-YMV4CBQE.js → chunk-YBM6JNRO.js} +90 -0
- package/dist/{chunk-6VIN5PMW.js → chunk-YRKJONH5.js} +1103 -1301
- package/dist/{chunk-PGIXWLQT.js → chunk-ZERJ7TNX.js} +123 -62
- package/dist/chunk-ZOVVFU7B.js +970 -0
- package/dist/{detection-H5QJR5XI.js → detection-2Z6TXYHU.js} +4 -3
- package/dist/index.js +82 -50
- package/dist/{kanban-CRHTDRBU.js → kanban-6WPOGFK5.js} +101 -14
- package/dist/{loop-732CLNLZ.js → loop-FKUQEJVU.js} +9 -5
- package/dist/{merge-CFQO7VU4.js → merge-NWSEV3FR.js} +1 -1
- package/dist/platform-ZDLHR264.js +24 -0
- package/dist/{tui-bridge-TSGCSJV4.js → tui-bridge-MMP6OGHK.js} +12 -9
- package/package.json +1 -1
- package/dist/chunk-7JT7DTSS.js +0 -10
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ If something fails — pre-push hooks, quota limits, stuck processes — Lisa ha
|
|
|
53
53
|
- **Smart activity detection** — reads agent session logs to prevent false stuck kills during analysis phases
|
|
54
54
|
- **Progress comments** — posts real-time status updates on issues as Lisa works through stages
|
|
55
55
|
- **Context enrichment** — greps for issue-related files and surfaces them in the agent prompt
|
|
56
|
-
- **PR reviewers & assignees** — auto-request reviews and assign PRs via config; `self` keyword resolves to the authenticated user
|
|
56
|
+
- **PR reviewers & assignees** — auto-request reviews and assign PRs via config; `self` keyword resolves to the authenticated user. Manage reviewers interactively from the TUI detail view (`r`)
|
|
57
57
|
- **Self-healing** — orphan recovery on startup, push failure retry, stuck process detection
|
|
58
58
|
- **Guardrails** — past failures are injected into future prompts to avoid repeating mistakes
|
|
59
59
|
- **Lineage context** — plan-decomposed issues get sibling task awareness, preventing duplicate work in concurrent mode
|
|
@@ -360,6 +360,7 @@ The real-time Kanban board shows issue progress, streams provider output, and de
|
|
|
360
360
|
|-----|--------|
|
|
361
361
|
| `↑` `↓` | Scroll output log |
|
|
362
362
|
| `o` | Open PR in browser |
|
|
363
|
+
| `r` | Toggle reviewer picker (add/remove reviewers on the PR) |
|
|
363
364
|
| `m` | Merge PR (warns if CI not passed) |
|
|
364
365
|
| `Esc` | Back to board |
|
|
365
366
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
function formatError(err) {
|
|
5
|
+
if (err instanceof Error) {
|
|
6
|
+
const cause = err.cause ? ` (caused by: ${formatError(err.cause)})` : "";
|
|
7
|
+
return `${err.message}${cause}`;
|
|
8
|
+
}
|
|
9
|
+
return String(err);
|
|
10
|
+
}
|
|
11
|
+
var LisaError = class extends Error {
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, options);
|
|
14
|
+
this.name = "LisaError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var SourceError = class extends LisaError {
|
|
18
|
+
constructor(message, source, statusCode, options) {
|
|
19
|
+
super(message, options);
|
|
20
|
+
this.source = source;
|
|
21
|
+
this.statusCode = statusCode;
|
|
22
|
+
this.name = "SourceError";
|
|
23
|
+
}
|
|
24
|
+
source;
|
|
25
|
+
statusCode;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
formatError,
|
|
30
|
+
LisaError,
|
|
31
|
+
SourceError
|
|
32
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/output/logger.ts
|
|
4
|
+
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
|
|
5
|
+
import { dirname } from "path";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
var logFilePath = null;
|
|
8
|
+
var outputMode = "default";
|
|
9
|
+
var logLevel = "default";
|
|
10
|
+
function setOutputMode(mode) {
|
|
11
|
+
outputMode = mode;
|
|
12
|
+
}
|
|
13
|
+
function getOutputMode() {
|
|
14
|
+
return outputMode;
|
|
15
|
+
}
|
|
16
|
+
function setLogLevel(level) {
|
|
17
|
+
logLevel = level;
|
|
18
|
+
}
|
|
19
|
+
function shouldPrintToConsole() {
|
|
20
|
+
return outputMode !== "tui" && logLevel !== "quiet";
|
|
21
|
+
}
|
|
22
|
+
function initLogFile(path) {
|
|
23
|
+
const dir = dirname(path);
|
|
24
|
+
if (!existsSync(dir)) {
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
writeFileSync(path, `[${timestamp()}] Log started
|
|
28
|
+
`);
|
|
29
|
+
logFilePath = path;
|
|
30
|
+
}
|
|
31
|
+
function timestamp() {
|
|
32
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
33
|
+
}
|
|
34
|
+
function writeToFile(level, message) {
|
|
35
|
+
if (logFilePath) {
|
|
36
|
+
appendFileSync(logFilePath, `[${timestamp()}] [${level}] ${message}
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function log(message) {
|
|
41
|
+
if (shouldPrintToConsole()) {
|
|
42
|
+
console.error(`${pc.cyan("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
43
|
+
}
|
|
44
|
+
writeToFile("info", message);
|
|
45
|
+
}
|
|
46
|
+
function warn(message) {
|
|
47
|
+
if (shouldPrintToConsole()) {
|
|
48
|
+
console.error(`${pc.yellow("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
49
|
+
}
|
|
50
|
+
writeToFile("warn", message);
|
|
51
|
+
}
|
|
52
|
+
function error(message) {
|
|
53
|
+
if (shouldPrintToConsole()) {
|
|
54
|
+
console.error(`${pc.red("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
55
|
+
}
|
|
56
|
+
writeToFile("error", message);
|
|
57
|
+
}
|
|
58
|
+
function ok(message) {
|
|
59
|
+
if (shouldPrintToConsole()) {
|
|
60
|
+
console.error(`${pc.green("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
61
|
+
}
|
|
62
|
+
writeToFile("ok", message);
|
|
63
|
+
}
|
|
64
|
+
function verbose(message) {
|
|
65
|
+
if (logLevel !== "verbose") return;
|
|
66
|
+
if (shouldPrintToConsole()) {
|
|
67
|
+
console.error(`${pc.dim("[lisa]")} ${pc.dim(timestamp())} ${pc.dim(message)}`);
|
|
68
|
+
}
|
|
69
|
+
writeToFile("verbose", message);
|
|
70
|
+
}
|
|
71
|
+
function divider(session) {
|
|
72
|
+
log(`${"\u2501".repeat(3)} Session ${session} ${"\u2501".repeat(3)}`);
|
|
73
|
+
}
|
|
74
|
+
function banner() {
|
|
75
|
+
if (outputMode !== "default" || logLevel === "quiet") return;
|
|
76
|
+
const title = " lisa \u266A autonomous issue resolver ";
|
|
77
|
+
const border = "\u2500".repeat(title.length);
|
|
78
|
+
console.error(pc.yellow(`
|
|
79
|
+
\u250C${border}\u2510`));
|
|
80
|
+
console.error(pc.yellow(` \u2502`) + pc.bold(pc.white(title)) + pc.yellow("\u2502"));
|
|
81
|
+
console.error(pc.yellow(` \u2514${border}\u2518
|
|
82
|
+
`));
|
|
83
|
+
}
|
|
84
|
+
function updateNotice(update) {
|
|
85
|
+
if (outputMode !== "default" || logLevel === "quiet") return;
|
|
86
|
+
const msg = `Update available ${pc.dim(update.currentVersion)} \u2192 ${pc.green(pc.bold(update.latestVersion))}`;
|
|
87
|
+
const cmd = `Run ${pc.cyan("npm i -g @tarcisiopgs/lisa")} to update`;
|
|
88
|
+
const lines = [msg, cmd];
|
|
89
|
+
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
90
|
+
const maxLen = Math.max(...lines.map((l) => strip(l).length));
|
|
91
|
+
const pad = (s) => s + " ".repeat(maxLen - strip(s).length);
|
|
92
|
+
console.error(pc.yellow(` \u250C${"\u2500".repeat(maxLen + 2)}\u2510`));
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
console.error(pc.yellow(" \u2502 ") + pad(line) + pc.yellow(" \u2502"));
|
|
95
|
+
}
|
|
96
|
+
console.error(pc.yellow(` \u2514${"\u2500".repeat(maxLen + 2)}\u2518
|
|
97
|
+
`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
setOutputMode,
|
|
102
|
+
getOutputMode,
|
|
103
|
+
setLogLevel,
|
|
104
|
+
initLogFile,
|
|
105
|
+
log,
|
|
106
|
+
warn,
|
|
107
|
+
error,
|
|
108
|
+
ok,
|
|
109
|
+
verbose,
|
|
110
|
+
divider,
|
|
111
|
+
banner,
|
|
112
|
+
updateNotice
|
|
113
|
+
};
|
|
@@ -5,17 +5,22 @@ import {
|
|
|
5
5
|
resolveModels,
|
|
6
6
|
runWithFallback,
|
|
7
7
|
saveLineage
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YRKJONH5.js";
|
|
9
|
+
import {
|
|
10
|
+
normalizeLabels
|
|
11
|
+
} from "./chunk-LR2GREZS.js";
|
|
9
12
|
import {
|
|
10
13
|
error,
|
|
11
14
|
log,
|
|
12
|
-
normalizeLabels,
|
|
13
15
|
ok,
|
|
14
16
|
warn
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HPWL5JRW.js";
|
|
18
|
+
import {
|
|
19
|
+
LisaError
|
|
20
|
+
} from "./chunk-4MZ2565Y.js";
|
|
16
21
|
|
|
17
22
|
// src/cli/error.ts
|
|
18
|
-
var CliError = class extends
|
|
23
|
+
var CliError = class extends LisaError {
|
|
19
24
|
exitCode;
|
|
20
25
|
constructor(message, exitCode = 1) {
|
|
21
26
|
super(message);
|
|
@@ -2,96 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
notify
|
|
4
4
|
} from "./chunk-72CYGBT4.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var outputMode = "default";
|
|
12
|
-
var logLevel = "default";
|
|
13
|
-
function setOutputMode(mode) {
|
|
14
|
-
outputMode = mode;
|
|
15
|
-
}
|
|
16
|
-
function getOutputMode() {
|
|
17
|
-
return outputMode;
|
|
18
|
-
}
|
|
19
|
-
function setLogLevel(level) {
|
|
20
|
-
logLevel = level;
|
|
21
|
-
}
|
|
22
|
-
function shouldPrintToConsole() {
|
|
23
|
-
return outputMode !== "tui" && logLevel !== "quiet";
|
|
24
|
-
}
|
|
25
|
-
function initLogFile(path) {
|
|
26
|
-
const dir = dirname(path);
|
|
27
|
-
if (!existsSync(dir)) {
|
|
28
|
-
mkdirSync(dir, { recursive: true });
|
|
29
|
-
}
|
|
30
|
-
writeFileSync(path, `[${timestamp()}] Log started
|
|
31
|
-
`);
|
|
32
|
-
logFilePath = path;
|
|
33
|
-
}
|
|
34
|
-
function timestamp() {
|
|
35
|
-
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
36
|
-
}
|
|
37
|
-
function writeToFile(level, message) {
|
|
38
|
-
if (logFilePath) {
|
|
39
|
-
appendFileSync(logFilePath, `[${timestamp()}] [${level}] ${message}
|
|
40
|
-
`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function log(message) {
|
|
44
|
-
if (shouldPrintToConsole()) {
|
|
45
|
-
console.error(`${pc.cyan("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
46
|
-
}
|
|
47
|
-
writeToFile("info", message);
|
|
48
|
-
}
|
|
49
|
-
function warn(message) {
|
|
50
|
-
if (shouldPrintToConsole()) {
|
|
51
|
-
console.error(`${pc.yellow("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
52
|
-
}
|
|
53
|
-
writeToFile("warn", message);
|
|
54
|
-
}
|
|
55
|
-
function error(message) {
|
|
56
|
-
if (shouldPrintToConsole()) {
|
|
57
|
-
console.error(`${pc.red("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
58
|
-
}
|
|
59
|
-
writeToFile("error", message);
|
|
60
|
-
}
|
|
61
|
-
function ok(message) {
|
|
62
|
-
if (shouldPrintToConsole()) {
|
|
63
|
-
console.error(`${pc.green("[lisa]")} ${pc.dim(timestamp())} ${message}`);
|
|
64
|
-
}
|
|
65
|
-
writeToFile("ok", message);
|
|
66
|
-
}
|
|
67
|
-
function divider(session) {
|
|
68
|
-
log(`${"\u2501".repeat(3)} Session ${session} ${"\u2501".repeat(3)}`);
|
|
69
|
-
}
|
|
70
|
-
function banner() {
|
|
71
|
-
if (outputMode !== "default" || logLevel === "quiet") return;
|
|
72
|
-
const title = " lisa \u266A autonomous issue resolver ";
|
|
73
|
-
const border = "\u2500".repeat(title.length);
|
|
74
|
-
console.error(pc.yellow(`
|
|
75
|
-
\u250C${border}\u2510`));
|
|
76
|
-
console.error(pc.yellow(` \u2502`) + pc.bold(pc.white(title)) + pc.yellow("\u2502"));
|
|
77
|
-
console.error(pc.yellow(` \u2514${border}\u2518
|
|
78
|
-
`));
|
|
79
|
-
}
|
|
80
|
-
function updateNotice(update) {
|
|
81
|
-
if (outputMode !== "default" || logLevel === "quiet") return;
|
|
82
|
-
const msg = `Update available ${pc.dim(update.currentVersion)} \u2192 ${pc.green(pc.bold(update.latestVersion))}`;
|
|
83
|
-
const cmd = `Run ${pc.cyan("npm i -g @tarcisiopgs/lisa")} to update`;
|
|
84
|
-
const lines = [msg, cmd];
|
|
85
|
-
const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
86
|
-
const maxLen = Math.max(...lines.map((l) => strip(l).length));
|
|
87
|
-
const pad = (s) => s + " ".repeat(maxLen - strip(s).length);
|
|
88
|
-
console.error(pc.yellow(` \u250C${"\u2500".repeat(maxLen + 2)}\u2510`));
|
|
89
|
-
for (const line of lines) {
|
|
90
|
-
console.error(pc.yellow(" \u2502 ") + pad(line) + pc.yellow(" \u2502"));
|
|
91
|
-
}
|
|
92
|
-
console.error(pc.yellow(` \u2514${"\u2500".repeat(maxLen + 2)}\u2518
|
|
93
|
-
`));
|
|
94
|
-
}
|
|
5
|
+
import {
|
|
6
|
+
warn
|
|
7
|
+
} from "./chunk-HPWL5JRW.js";
|
|
8
|
+
import {
|
|
9
|
+
SourceError
|
|
10
|
+
} from "./chunk-4MZ2565Y.js";
|
|
95
11
|
|
|
96
12
|
// src/ui/state.ts
|
|
97
13
|
import { EventEmitter } from "events";
|
|
@@ -102,51 +18,101 @@ import { execa } from "execa";
|
|
|
102
18
|
|
|
103
19
|
// src/sources/base.ts
|
|
104
20
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
21
|
+
var MAX_ATTEMPTS = 3;
|
|
22
|
+
var BASE_BACKOFF_MS = 1e3;
|
|
105
23
|
function normalizeLabels(config) {
|
|
106
24
|
return Array.isArray(config.label) ? config.label : config.label ? [config.label] : [];
|
|
107
25
|
}
|
|
108
26
|
function createApiClient(baseUrl, getHeaders, name) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!res.ok) {
|
|
122
|
-
const text = await res.text();
|
|
123
|
-
throw new Error(`${name} API error (${res.status}): ${text}`);
|
|
27
|
+
function isRetryable(error) {
|
|
28
|
+
if (error instanceof Error) {
|
|
29
|
+
if (error.name === "AbortError") return true;
|
|
30
|
+
if (error instanceof TypeError) return true;
|
|
31
|
+
if (error instanceof SourceError && error.statusCode) {
|
|
32
|
+
return error.statusCode >= 500;
|
|
33
|
+
}
|
|
34
|
+
const match = error.message.match(/API error \((\d+)\)/);
|
|
35
|
+
if (match?.[1]) {
|
|
36
|
+
const status = Number.parseInt(match[1], 10);
|
|
37
|
+
return status >= 500;
|
|
38
|
+
}
|
|
124
39
|
}
|
|
125
|
-
|
|
126
|
-
return await res.json();
|
|
40
|
+
return false;
|
|
127
41
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
42
|
+
async function retryableRequest(operation) {
|
|
43
|
+
let lastError;
|
|
44
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
return await operation();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
lastError = error;
|
|
49
|
+
if (!isRetryable(error) || attempt === MAX_ATTEMPTS - 1) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
const delay = BASE_BACKOFF_MS * 2 ** attempt;
|
|
53
|
+
warn(
|
|
54
|
+
`${name} API request failed (attempt ${attempt + 1}/${MAX_ATTEMPTS}), retrying in ${delay}ms...`
|
|
55
|
+
);
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw lastError;
|
|
60
|
+
}
|
|
61
|
+
async function request(method, path, body, schema) {
|
|
62
|
+
return retryableRequest(async () => {
|
|
136
63
|
const url = `${baseUrl}${path}`;
|
|
137
|
-
const headers =
|
|
64
|
+
const headers = {
|
|
65
|
+
...await getHeaders(),
|
|
66
|
+
"Content-Type": "application/json"
|
|
67
|
+
};
|
|
138
68
|
const res = await fetch(url, {
|
|
139
69
|
method,
|
|
140
|
-
headers
|
|
141
|
-
body:
|
|
70
|
+
headers,
|
|
71
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
142
72
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
143
73
|
});
|
|
144
74
|
if (!res.ok) {
|
|
145
75
|
const text = await res.text();
|
|
146
|
-
throw new
|
|
76
|
+
throw new SourceError(`${name} API error (${res.status}): ${text}`, name, res.status);
|
|
147
77
|
}
|
|
148
78
|
if (method === "DELETE" || res.status === 204) return void 0;
|
|
149
|
-
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
if (schema) {
|
|
81
|
+
const result = schema.safeParse(data);
|
|
82
|
+
if (!result.success) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`${name} API response validation failed for ${method} ${path}: ${result.error.message}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return result.data;
|
|
88
|
+
}
|
|
89
|
+
return data;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
get: (path, schema) => request("GET", path, void 0, schema),
|
|
94
|
+
post: (path, body, schema) => request("POST", path, body, schema),
|
|
95
|
+
put: (path, body, schema) => request("PUT", path, body, schema),
|
|
96
|
+
patch: (path, body, schema) => request("PATCH", path, body, schema),
|
|
97
|
+
delete: (path) => request("DELETE", path),
|
|
98
|
+
/** Raw request for non-JSON bodies (e.g. Trello form-encoded). */
|
|
99
|
+
raw: async (method, path, init) => {
|
|
100
|
+
return retryableRequest(async () => {
|
|
101
|
+
const url = `${baseUrl}${path}`;
|
|
102
|
+
const headers = await getHeaders();
|
|
103
|
+
const res = await fetch(url, {
|
|
104
|
+
method,
|
|
105
|
+
headers: { ...headers, ...init?.headers },
|
|
106
|
+
body: init?.body,
|
|
107
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
const text = await res.text();
|
|
111
|
+
throw new SourceError(`${name} API error (${res.status}): ${text}`, name, res.status);
|
|
112
|
+
}
|
|
113
|
+
if (method === "DELETE" || res.status === 204) return void 0;
|
|
114
|
+
return await res.json();
|
|
115
|
+
});
|
|
150
116
|
}
|
|
151
117
|
};
|
|
152
118
|
}
|
|
@@ -161,7 +127,10 @@ async function getToken() {
|
|
|
161
127
|
if (stdout.trim()) return stdout.trim();
|
|
162
128
|
} catch {
|
|
163
129
|
}
|
|
164
|
-
throw new
|
|
130
|
+
throw new SourceError(
|
|
131
|
+
"GitHub authentication required: set GITHUB_TOKEN or run `gh auth login`",
|
|
132
|
+
"github-issues"
|
|
133
|
+
);
|
|
165
134
|
}
|
|
166
135
|
async function getAuthHeaders() {
|
|
167
136
|
const token = await getToken();
|
|
@@ -247,8 +216,15 @@ var GitHubIssuesSource = class {
|
|
|
247
216
|
const isOrphanDetection = !!config.pick_from && !validStates.includes(config.pick_from);
|
|
248
217
|
const filterLabels = isOrphanDetection ? [config.pick_from] : normalizeLabels(config);
|
|
249
218
|
const label = filterLabels.map((l) => encodeURIComponent(l)).join(",");
|
|
250
|
-
const
|
|
251
|
-
|
|
219
|
+
const issues = [];
|
|
220
|
+
let page = 1;
|
|
221
|
+
while (true) {
|
|
222
|
+
const path = `/repos/${owner}/${repo}/issues?labels=${label}&state=open&sort=created&direction=asc&per_page=100&page=${page}`;
|
|
223
|
+
const batch = (await api().get(path)).filter((i) => !i.pull_request);
|
|
224
|
+
issues.push(...batch);
|
|
225
|
+
if (batch.length < 100) break;
|
|
226
|
+
page++;
|
|
227
|
+
}
|
|
252
228
|
if (issues.length === 0) return null;
|
|
253
229
|
const unblocked = [];
|
|
254
230
|
const blocked = [];
|
|
@@ -388,9 +364,16 @@ var GitHubIssuesSource = class {
|
|
|
388
364
|
const { owner, repo } = parseOwnerRepo(config.scope);
|
|
389
365
|
const labels = normalizeLabels(config);
|
|
390
366
|
const label = labels.map((l) => encodeURIComponent(l)).join(",");
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
367
|
+
const allIssues = [];
|
|
368
|
+
let page = 1;
|
|
369
|
+
while (true) {
|
|
370
|
+
const path = `/repos/${owner}/${repo}/issues?labels=${label}&state=open&sort=created&direction=asc&per_page=100&page=${page}`;
|
|
371
|
+
const batch = (await api().get(path)).filter((i) => !i.pull_request);
|
|
372
|
+
allIssues.push(...batch);
|
|
373
|
+
if (batch.length < 100) break;
|
|
374
|
+
page++;
|
|
375
|
+
}
|
|
376
|
+
return allIssues.map((issue) => ({
|
|
394
377
|
id: makeIssueId(owner, repo, issue.number),
|
|
395
378
|
title: issue.title,
|
|
396
379
|
description: issue.body ?? "",
|
|
@@ -457,7 +440,7 @@ function getBaseUrl() {
|
|
|
457
440
|
}
|
|
458
441
|
function getAuthHeaders2() {
|
|
459
442
|
const token = process.env.GITLAB_TOKEN;
|
|
460
|
-
if (!token) throw new
|
|
443
|
+
if (!token) throw new SourceError("GITLAB_TOKEN must be set", "gitlab-issues");
|
|
461
444
|
return { "PRIVATE-TOKEN": token };
|
|
462
445
|
}
|
|
463
446
|
var _api2;
|
|
@@ -512,8 +495,15 @@ var GitLabIssuesSource = class {
|
|
|
512
495
|
const isOrphanDetection = !!config.pick_from && !validStates.includes(config.pick_from);
|
|
513
496
|
const filterLabels = isOrphanDetection ? [config.pick_from] : normalizeLabels(config);
|
|
514
497
|
const label = filterLabels.map((l) => encodeURIComponent(l)).join(",");
|
|
515
|
-
const
|
|
516
|
-
|
|
498
|
+
const issues = [];
|
|
499
|
+
let page = 1;
|
|
500
|
+
while (true) {
|
|
501
|
+
const path = `/projects/${project}/issues?labels=${label}&state=opened&per_page=100&page=${page}`;
|
|
502
|
+
const batch = await api2().get(path);
|
|
503
|
+
issues.push(...batch);
|
|
504
|
+
if (batch.length < 100) break;
|
|
505
|
+
page++;
|
|
506
|
+
}
|
|
517
507
|
if (issues.length === 0) return null;
|
|
518
508
|
const unblocked = [];
|
|
519
509
|
const blocked = [];
|
|
@@ -626,9 +616,16 @@ var GitLabIssuesSource = class {
|
|
|
626
616
|
const project = parseGitLabProject(config.scope);
|
|
627
617
|
const labelsArr = normalizeLabels(config);
|
|
628
618
|
const label = labelsArr.map((l) => encodeURIComponent(l)).join(",");
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
619
|
+
const allIssues = [];
|
|
620
|
+
let page = 1;
|
|
621
|
+
while (true) {
|
|
622
|
+
const path = `/projects/${project}/issues?labels=${label}&state=opened&per_page=100&page=${page}`;
|
|
623
|
+
const batch = await api2().get(path);
|
|
624
|
+
allIssues.push(...batch);
|
|
625
|
+
if (batch.length < 100) break;
|
|
626
|
+
page++;
|
|
627
|
+
}
|
|
628
|
+
return allIssues.map((issue) => ({
|
|
632
629
|
id: makeIssueId2(config.scope, issue.iid),
|
|
633
630
|
title: issue.title,
|
|
634
631
|
description: issue.description ?? "",
|
|
@@ -739,17 +736,18 @@ function stopMergePolling(key) {
|
|
|
739
736
|
}
|
|
740
737
|
}
|
|
741
738
|
function startMergePolling(issueId, prUrl) {
|
|
742
|
-
|
|
739
|
+
const key = `${issueId}:${prUrl}`;
|
|
740
|
+
if (activePolls.has(key)) return;
|
|
743
741
|
const intervalId = setInterval(() => {
|
|
744
742
|
checkPrMergedByUrl(prUrl).then((merged) => {
|
|
745
743
|
if (merged) {
|
|
746
|
-
stopMergePolling(
|
|
744
|
+
stopMergePolling(key);
|
|
747
745
|
kanbanEmitter.emit("issue:merged", issueId);
|
|
748
746
|
}
|
|
749
747
|
}).catch(() => {
|
|
750
748
|
});
|
|
751
749
|
}, MERGE_POLL_INTERVAL_MS);
|
|
752
|
-
activePolls.set(
|
|
750
|
+
activePolls.set(key, intervalId);
|
|
753
751
|
}
|
|
754
752
|
var KanbanEmitter = class extends EventEmitter {
|
|
755
753
|
};
|
|
@@ -893,6 +891,14 @@ function useKanbanState(bellEnabled, initialCards = []) {
|
|
|
893
891
|
const onSubstatus = (issueId, substatus) => {
|
|
894
892
|
setCards((prev) => prev.map((c) => c.id === issueId ? { ...c, substatus } : c));
|
|
895
893
|
};
|
|
894
|
+
const onReviewersUpdated = (issueId, reviewers) => {
|
|
895
|
+
setCards((prev) => prev.map((c) => c.id === issueId ? { ...c, reviewers } : c));
|
|
896
|
+
};
|
|
897
|
+
const onAvailableReviewers = (issueId, available) => {
|
|
898
|
+
setCards(
|
|
899
|
+
(prev) => prev.map((c) => c.id === issueId ? { ...c, availableReviewers: available } : c)
|
|
900
|
+
);
|
|
901
|
+
};
|
|
896
902
|
const MAX_OUTPUT_SIZE = 2e5;
|
|
897
903
|
const outputBuffer = /* @__PURE__ */ new Map();
|
|
898
904
|
let flushTimer = null;
|
|
@@ -941,6 +947,8 @@ function useKanbanState(bellEnabled, initialCards = []) {
|
|
|
941
947
|
kanbanEmitter.on("provider:resumed", onProviderResumed);
|
|
942
948
|
kanbanEmitter.on("issue:log-file", onLogFile);
|
|
943
949
|
kanbanEmitter.on("issue:substatus", onSubstatus);
|
|
950
|
+
kanbanEmitter.on("issue:reviewers-updated", onReviewersUpdated);
|
|
951
|
+
kanbanEmitter.on("issue:available-reviewers", onAvailableReviewers);
|
|
944
952
|
kanbanEmitter.on("issue:output", onOutput);
|
|
945
953
|
const onModelChanged = (model) => setModelInUse(model);
|
|
946
954
|
kanbanEmitter.on("provider:model-changed", onModelChanged);
|
|
@@ -987,6 +995,8 @@ function useKanbanState(bellEnabled, initialCards = []) {
|
|
|
987
995
|
kanbanEmitter.off("provider:resumed", onProviderResumed);
|
|
988
996
|
kanbanEmitter.off("issue:log-file", onLogFile);
|
|
989
997
|
kanbanEmitter.off("issue:substatus", onSubstatus);
|
|
998
|
+
kanbanEmitter.off("issue:reviewers-updated", onReviewersUpdated);
|
|
999
|
+
kanbanEmitter.off("issue:available-reviewers", onAvailableReviewers);
|
|
990
1000
|
kanbanEmitter.off("issue:output", onOutput);
|
|
991
1001
|
kanbanEmitter.off("provider:model-changed", onModelChanged);
|
|
992
1002
|
kanbanEmitter.off("work:empty", onEmpty);
|
|
@@ -1015,17 +1025,6 @@ function useKanbanState(bellEnabled, initialCards = []) {
|
|
|
1015
1025
|
}
|
|
1016
1026
|
|
|
1017
1027
|
export {
|
|
1018
|
-
setOutputMode,
|
|
1019
|
-
getOutputMode,
|
|
1020
|
-
setLogLevel,
|
|
1021
|
-
initLogFile,
|
|
1022
|
-
log,
|
|
1023
|
-
warn,
|
|
1024
|
-
error,
|
|
1025
|
-
ok,
|
|
1026
|
-
divider,
|
|
1027
|
-
banner,
|
|
1028
|
-
updateNotice,
|
|
1029
1028
|
REQUEST_TIMEOUT_MS,
|
|
1030
1029
|
normalizeLabels,
|
|
1031
1030
|
createApiClient,
|