@tarcisiopgs/lisa 1.14.2 → 1.16.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 +24 -3
- package/dist/{chunk-OYQ6TOAG.js → chunk-GZ2ZAQO4.js} +9 -2
- package/dist/{chunk-Z6CNJAZF.js → chunk-ITQEGO5A.js} +15 -5
- package/dist/{chunk-NXGXGHS3.js → chunk-UQPR5OXK.js} +1 -1
- package/dist/{guardrails-KI5NEJVE.js → guardrails-I5ACG5LQ.js} +2 -2
- package/dist/index.js +317 -99
- package/dist/{kanban-VSEZ7NUK.js → kanban-QZ5NRPJ5.js} +48 -24
- package/dist/{paths-HQQDKACV.js → paths-WQN4NBC6.js} +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ Set the tokens for your chosen source and PR platform:
|
|
|
112
112
|
# PR platform
|
|
113
113
|
GITHUB_TOKEN # GitHub (platform: cli or token)
|
|
114
114
|
GITLAB_TOKEN # GitLab (platform: gitlab)
|
|
115
|
-
BITBUCKET_TOKEN # Bitbucket (platform: bitbucket)
|
|
115
|
+
BITBUCKET_TOKEN # Bitbucket app password (platform: bitbucket)
|
|
116
116
|
BITBUCKET_USERNAME # Bitbucket username
|
|
117
117
|
|
|
118
118
|
# Issue sources
|
|
@@ -125,9 +125,14 @@ PLANE_BASE_URL # optional, defaults to https://api.plane.so
|
|
|
125
125
|
GITLAB_TOKEN # source: gitlab-issues
|
|
126
126
|
GITLAB_BASE_URL # optional, defaults to https://gitlab.com
|
|
127
127
|
GITHUB_TOKEN # source: github-issues
|
|
128
|
-
JIRA_BASE_URL # source: jira
|
|
128
|
+
JIRA_BASE_URL # source: jira (e.g. https://yourorg.atlassian.net)
|
|
129
129
|
JIRA_EMAIL
|
|
130
|
-
JIRA_API_TOKEN
|
|
130
|
+
JIRA_API_TOKEN # generate at id.atlassian.com — expires, regenerate if 401
|
|
131
|
+
|
|
132
|
+
# Aider provider (one of)
|
|
133
|
+
GEMINI_API_KEY
|
|
134
|
+
OPENAI_API_KEY
|
|
135
|
+
ANTHROPIC_API_KEY
|
|
131
136
|
```
|
|
132
137
|
|
|
133
138
|
---
|
|
@@ -196,6 +201,22 @@ validation:
|
|
|
196
201
|
require_acceptance_criteria: true
|
|
197
202
|
```
|
|
198
203
|
|
|
204
|
+
### Source-specific notes
|
|
205
|
+
|
|
206
|
+
**GitHub Issues / GitLab Issues** — `pick_from`, `in_progress`, and `done` are **labels**, not statuses. Make sure `in_progress` differs from `pick_from`; using the same value causes Lisa to re-pick issues that are already being worked on.
|
|
207
|
+
|
|
208
|
+
**Trello** — `team`, `pick_from`, `in_progress`, and `done` are list **names** (not IDs).
|
|
209
|
+
|
|
210
|
+
**Jira** — `team` is your project **key** (e.g. `ENG`). `JIRA_API_TOKEN` is generated at [id.atlassian.com](https://id.atlassian.com) and expires — regenerate if you get 401 errors.
|
|
211
|
+
|
|
212
|
+
**Goose** — `lisa init` asks which backend to use (gemini-cli, anthropic, openai, etc.) and saves it to config. No env vars needed. You can also set `GOOSE_PROVIDER` manually — it takes precedence over the config value.
|
|
213
|
+
|
|
214
|
+
**Aider** — requires a direct LLM API key (`GEMINI_API_KEY`, `OPENAI_API_KEY`, or `ANTHROPIC_API_KEY`). Does not support OAuth or cached credentials.
|
|
215
|
+
|
|
216
|
+
**OpenCode** — if `~/.config/opencode/config.json` contains MCP entries, remove them or set the file to `{}` to prevent OpenCode from hanging on startup.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
199
220
|
### Workflow Modes
|
|
200
221
|
|
|
201
222
|
**Branch** — The agent creates a branch in your current checkout. Simple setup, works everywhere.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/paths.ts
|
|
4
4
|
import { createHash } from "crypto";
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "fs";
|
|
6
|
-
import { homedir } from "os";
|
|
6
|
+
import { homedir, platform } from "os";
|
|
7
7
|
import { join, resolve } from "path";
|
|
8
8
|
var MAX_LOG_FILES = 20;
|
|
9
9
|
function projectHash(cwd) {
|
|
@@ -11,7 +11,14 @@ function projectHash(cwd) {
|
|
|
11
11
|
return createHash("sha256").update(absolute).digest("hex").slice(0, 12);
|
|
12
12
|
}
|
|
13
13
|
function getCacheDir(cwd) {
|
|
14
|
-
|
|
14
|
+
let base;
|
|
15
|
+
if (process.env.XDG_CACHE_HOME) {
|
|
16
|
+
base = process.env.XDG_CACHE_HOME;
|
|
17
|
+
} else if (platform() === "darwin") {
|
|
18
|
+
base = join(homedir(), "Library", "Caches");
|
|
19
|
+
} else {
|
|
20
|
+
base = join(homedir(), ".cache");
|
|
21
|
+
}
|
|
15
22
|
return join(base, "lisa", projectHash(cwd));
|
|
16
23
|
}
|
|
17
24
|
function getLogsDir(cwd) {
|
|
@@ -193,7 +193,7 @@ var GitHubIssuesSource = class {
|
|
|
193
193
|
const filterLabels = isOrphanDetection ? [config.pick_from] : Array.isArray(config.label) ? config.label : [config.label];
|
|
194
194
|
const label = filterLabels.map((l) => encodeURIComponent(l)).join(",");
|
|
195
195
|
const path = `/repos/${owner}/${repo}/issues?labels=${label}&state=open&sort=created&direction=asc&per_page=100`;
|
|
196
|
-
const issues = await githubGet(path);
|
|
196
|
+
const issues = (await githubGet(path)).filter((i) => !i.pull_request);
|
|
197
197
|
if (issues.length === 0) return null;
|
|
198
198
|
const unblocked = [];
|
|
199
199
|
const blocked = [];
|
|
@@ -333,7 +333,7 @@ var GitHubIssuesSource = class {
|
|
|
333
333
|
const labels = Array.isArray(config.label) ? config.label : [config.label];
|
|
334
334
|
const label = labels.map((l) => encodeURIComponent(l)).join(",");
|
|
335
335
|
const path = `/repos/${owner}/${repo}/issues?labels=${label}&state=open&sort=created&direction=asc&per_page=100`;
|
|
336
|
-
const issues = await githubGet(path);
|
|
336
|
+
const issues = (await githubGet(path)).filter((i) => !i.pull_request);
|
|
337
337
|
return issues.map((issue) => ({
|
|
338
338
|
id: makeIssueId(owner, repo, issue.number),
|
|
339
339
|
title: issue.title,
|
|
@@ -448,10 +448,10 @@ var GitLabIssuesSource = class {
|
|
|
448
448
|
);
|
|
449
449
|
const activeBlockers = links.filter((link) => {
|
|
450
450
|
if (link.link_type === "is_blocked_by") {
|
|
451
|
-
return link.
|
|
451
|
+
return link.state !== "closed";
|
|
452
452
|
}
|
|
453
453
|
return false;
|
|
454
|
-
}).map((link) => link.
|
|
454
|
+
}).map((link) => link.iid);
|
|
455
455
|
if (activeBlockers.length === 0) {
|
|
456
456
|
unblocked.push(issue2);
|
|
457
457
|
} else {
|
|
@@ -640,6 +640,7 @@ function useKanbanState(bellEnabled) {
|
|
|
640
640
|
const [cards, setCards] = useState([]);
|
|
641
641
|
const [isEmpty, setIsEmpty] = useState(false);
|
|
642
642
|
const [isWatching, setIsWatching] = useState(false);
|
|
643
|
+
const [isWatchPrompt, setIsWatchPrompt] = useState(false);
|
|
643
644
|
const [workComplete, setWorkComplete] = useState(
|
|
644
645
|
null
|
|
645
646
|
);
|
|
@@ -763,10 +764,17 @@ function useKanbanState(bellEnabled) {
|
|
|
763
764
|
const onComplete = (data) => setWorkComplete(data);
|
|
764
765
|
const onWatching = () => setIsWatching(true);
|
|
765
766
|
const onWatchResume = () => setIsWatching(false);
|
|
767
|
+
const onWatchPrompt = () => {
|
|
768
|
+
setIsWatchPrompt(true);
|
|
769
|
+
setIsWatching(false);
|
|
770
|
+
};
|
|
771
|
+
const onWatchPromptResolved = () => setIsWatchPrompt(false);
|
|
766
772
|
kanbanEmitter.on("work:empty", onEmpty);
|
|
767
773
|
kanbanEmitter.on("work:complete", onComplete);
|
|
768
774
|
kanbanEmitter.on("work:watching", onWatching);
|
|
769
775
|
kanbanEmitter.on("work:watch-resume", onWatchResume);
|
|
776
|
+
kanbanEmitter.on("work:watch-prompt", onWatchPrompt);
|
|
777
|
+
kanbanEmitter.on("work:watch-prompt-resolved", onWatchPromptResolved);
|
|
770
778
|
const cleanupBell = registerBellListeners(bellEnabled);
|
|
771
779
|
return () => {
|
|
772
780
|
kanbanEmitter.off("issue:queued", onQueued);
|
|
@@ -784,13 +792,15 @@ function useKanbanState(bellEnabled) {
|
|
|
784
792
|
kanbanEmitter.off("work:complete", onComplete);
|
|
785
793
|
kanbanEmitter.off("work:watching", onWatching);
|
|
786
794
|
kanbanEmitter.off("work:watch-resume", onWatchResume);
|
|
795
|
+
kanbanEmitter.off("work:watch-prompt", onWatchPrompt);
|
|
796
|
+
kanbanEmitter.off("work:watch-prompt-resolved", onWatchPromptResolved);
|
|
787
797
|
cleanupBell();
|
|
788
798
|
for (const issueId of activePolls.keys()) {
|
|
789
799
|
stopMergePolling(issueId);
|
|
790
800
|
}
|
|
791
801
|
};
|
|
792
802
|
}, [bellEnabled]);
|
|
793
|
-
return { cards, isEmpty, isWatching, workComplete, modelInUse };
|
|
803
|
+
return { cards, isEmpty, isWatching, isWatchPrompt, workComplete, modelInUse };
|
|
794
804
|
}
|
|
795
805
|
|
|
796
806
|
export {
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
extractContext,
|
|
7
7
|
extractErrorType,
|
|
8
8
|
migrateGuardrails
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UQPR5OXK.js";
|
|
10
10
|
import {
|
|
11
11
|
ensureCacheDir,
|
|
12
12
|
getLogsDir,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getPlanPath,
|
|
15
15
|
getPrCachePath,
|
|
16
16
|
rotateLogFiles
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-GZ2ZAQO4.js";
|
|
18
18
|
import {
|
|
19
19
|
fetchPrFeedback,
|
|
20
20
|
formatPrFeedbackEntry
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
ok,
|
|
33
33
|
setOutputMode,
|
|
34
34
|
warn
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-ITQEGO5A.js";
|
|
36
36
|
import {
|
|
37
37
|
notify,
|
|
38
38
|
resetTitle,
|
|
@@ -53,7 +53,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
53
53
|
import { resolve } from "path";
|
|
54
54
|
import { parse, stringify } from "yaml";
|
|
55
55
|
var DEFAULT_OVERSEER_CONFIG = {
|
|
56
|
-
enabled:
|
|
56
|
+
enabled: true,
|
|
57
57
|
check_interval: 30,
|
|
58
58
|
stuck_threshold: 300
|
|
59
59
|
};
|
|
@@ -363,10 +363,22 @@ function determineRepoPath(repos, issue2, workspace) {
|
|
|
363
363
|
const first = repos[0];
|
|
364
364
|
return first ? join(workspace, first.path) : void 0;
|
|
365
365
|
}
|
|
366
|
+
async function hasCodeChanges(repoPath, baseBranch) {
|
|
367
|
+
try {
|
|
368
|
+
const { stdout } = await execa("git", ["diff", "--stat", `${baseBranch}..HEAD`], {
|
|
369
|
+
cwd: repoPath,
|
|
370
|
+
reject: false
|
|
371
|
+
});
|
|
372
|
+
const trimmed = stdout.trim();
|
|
373
|
+
return trimmed.length > 0;
|
|
374
|
+
} catch {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
366
378
|
|
|
367
379
|
// src/providers/aider.ts
|
|
368
380
|
import { execSync } from "child_process";
|
|
369
|
-
import { appendFileSync as appendFileSync2, mkdtempSync,
|
|
381
|
+
import { appendFileSync as appendFileSync2, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
370
382
|
import { tmpdir } from "os";
|
|
371
383
|
import { join as join2 } from "path";
|
|
372
384
|
|
|
@@ -520,7 +532,8 @@ var AIDER_API_KEY_ENV_VARS = [
|
|
|
520
532
|
"COHERE_API_KEY",
|
|
521
533
|
"MISTRAL_API_KEY",
|
|
522
534
|
"DEEPSEEK_API_KEY",
|
|
523
|
-
"AZURE_API_KEY"
|
|
535
|
+
"AZURE_API_KEY",
|
|
536
|
+
"XAI_API_KEY"
|
|
524
537
|
];
|
|
525
538
|
var AiderProvider = class {
|
|
526
539
|
name = "aider";
|
|
@@ -547,7 +560,7 @@ var AiderProvider = class {
|
|
|
547
560
|
writeFileSync2(promptFile, prompt, "utf-8");
|
|
548
561
|
try {
|
|
549
562
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
550
|
-
const command = `aider --message
|
|
563
|
+
const command = `aider --message-file '${promptFile}' --yes-always ${modelFlag}`;
|
|
551
564
|
const { proc, isPty } = spawnWithPty(command, {
|
|
552
565
|
cwd: opts.cwd,
|
|
553
566
|
env: { ...process.env, ...opts.env }
|
|
@@ -601,7 +614,7 @@ var AiderProvider = class {
|
|
|
601
614
|
};
|
|
602
615
|
} finally {
|
|
603
616
|
try {
|
|
604
|
-
|
|
617
|
+
rmSync2(tmpDir, { recursive: true, force: true });
|
|
605
618
|
} catch {
|
|
606
619
|
}
|
|
607
620
|
}
|
|
@@ -610,7 +623,7 @@ var AiderProvider = class {
|
|
|
610
623
|
|
|
611
624
|
// src/providers/claude.ts
|
|
612
625
|
import { execSync as execSync2, spawn as spawn2 } from "child_process";
|
|
613
|
-
import { appendFileSync as appendFileSync3, mkdtempSync as mkdtempSync2,
|
|
626
|
+
import { appendFileSync as appendFileSync3, mkdtempSync as mkdtempSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
614
627
|
import { tmpdir as tmpdir2 } from "os";
|
|
615
628
|
import { join as join3 } from "path";
|
|
616
629
|
var ClaudeProvider = class {
|
|
@@ -699,7 +712,7 @@ var ClaudeProvider = class {
|
|
|
699
712
|
};
|
|
700
713
|
} finally {
|
|
701
714
|
try {
|
|
702
|
-
|
|
715
|
+
rmSync3(tmpDir, { recursive: true, force: true });
|
|
703
716
|
} catch {
|
|
704
717
|
}
|
|
705
718
|
}
|
|
@@ -708,14 +721,14 @@ var ClaudeProvider = class {
|
|
|
708
721
|
|
|
709
722
|
// src/providers/codex.ts
|
|
710
723
|
import { execSync as execSync3 } from "child_process";
|
|
711
|
-
import { appendFileSync as appendFileSync4, mkdtempSync as mkdtempSync3,
|
|
724
|
+
import { appendFileSync as appendFileSync4, mkdtempSync as mkdtempSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
712
725
|
import { tmpdir as tmpdir3 } from "os";
|
|
713
726
|
import { join as join4 } from "path";
|
|
714
727
|
var CodexProvider = class {
|
|
715
728
|
name = "codex";
|
|
716
729
|
async isAvailable() {
|
|
717
730
|
try {
|
|
718
|
-
execSync3("codex
|
|
731
|
+
execSync3("which codex", { stdio: "ignore" });
|
|
719
732
|
return true;
|
|
720
733
|
} catch {
|
|
721
734
|
return false;
|
|
@@ -782,7 +795,7 @@ var CodexProvider = class {
|
|
|
782
795
|
};
|
|
783
796
|
} finally {
|
|
784
797
|
try {
|
|
785
|
-
|
|
798
|
+
rmSync4(tmpDir, { recursive: true, force: true });
|
|
786
799
|
} catch {
|
|
787
800
|
}
|
|
788
801
|
}
|
|
@@ -791,7 +804,7 @@ var CodexProvider = class {
|
|
|
791
804
|
|
|
792
805
|
// src/providers/copilot.ts
|
|
793
806
|
import { execSync as execSync4 } from "child_process";
|
|
794
|
-
import { appendFileSync as appendFileSync5, mkdtempSync as mkdtempSync4,
|
|
807
|
+
import { appendFileSync as appendFileSync5, mkdtempSync as mkdtempSync4, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
795
808
|
import { tmpdir as tmpdir4 } from "os";
|
|
796
809
|
import { join as join5 } from "path";
|
|
797
810
|
var CopilotProvider = class {
|
|
@@ -810,7 +823,8 @@ var CopilotProvider = class {
|
|
|
810
823
|
const promptFile = join5(tmpDir, "prompt.md");
|
|
811
824
|
writeFileSync5(promptFile, prompt, "utf-8");
|
|
812
825
|
try {
|
|
813
|
-
const
|
|
826
|
+
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
827
|
+
const command = `copilot --allow-all ${modelFlag} -p "$(cat '${promptFile}')"`;
|
|
814
828
|
const { proc, isPty } = spawnWithPty(command, {
|
|
815
829
|
cwd: opts.cwd,
|
|
816
830
|
env: { ...process.env, ...opts.env }
|
|
@@ -864,7 +878,7 @@ var CopilotProvider = class {
|
|
|
864
878
|
};
|
|
865
879
|
} finally {
|
|
866
880
|
try {
|
|
867
|
-
|
|
881
|
+
rmSync5(tmpDir, { recursive: true, force: true });
|
|
868
882
|
} catch {
|
|
869
883
|
}
|
|
870
884
|
}
|
|
@@ -873,13 +887,13 @@ var CopilotProvider = class {
|
|
|
873
887
|
|
|
874
888
|
// src/providers/cursor.ts
|
|
875
889
|
import { execSync as execSync5 } from "child_process";
|
|
876
|
-
import { appendFileSync as appendFileSync6, mkdtempSync as mkdtempSync5,
|
|
890
|
+
import { appendFileSync as appendFileSync6, mkdtempSync as mkdtempSync5, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
877
891
|
import { tmpdir as tmpdir5 } from "os";
|
|
878
892
|
import { join as join6 } from "path";
|
|
879
893
|
function findCursorBinary() {
|
|
880
894
|
for (const bin of ["agent", "cursor-agent"]) {
|
|
881
895
|
try {
|
|
882
|
-
execSync5(
|
|
896
|
+
execSync5(`which ${bin}`, { stdio: "ignore" });
|
|
883
897
|
return bin;
|
|
884
898
|
} catch {
|
|
885
899
|
}
|
|
@@ -888,12 +902,17 @@ function findCursorBinary() {
|
|
|
888
902
|
}
|
|
889
903
|
var CursorProvider = class {
|
|
890
904
|
name = "cursor";
|
|
905
|
+
_bin = void 0;
|
|
906
|
+
resolveBin() {
|
|
907
|
+
if (this._bin === void 0) this._bin = findCursorBinary();
|
|
908
|
+
return this._bin;
|
|
909
|
+
}
|
|
891
910
|
async isAvailable() {
|
|
892
|
-
return
|
|
911
|
+
return this.resolveBin() !== null;
|
|
893
912
|
}
|
|
894
913
|
async run(prompt, opts) {
|
|
895
914
|
const start = Date.now();
|
|
896
|
-
const bin =
|
|
915
|
+
const bin = this.resolveBin();
|
|
897
916
|
if (!bin) {
|
|
898
917
|
return {
|
|
899
918
|
success: false,
|
|
@@ -960,7 +979,7 @@ var CursorProvider = class {
|
|
|
960
979
|
};
|
|
961
980
|
} finally {
|
|
962
981
|
try {
|
|
963
|
-
|
|
982
|
+
rmSync6(tmpDir, { recursive: true, force: true });
|
|
964
983
|
} catch {
|
|
965
984
|
}
|
|
966
985
|
}
|
|
@@ -969,7 +988,7 @@ var CursorProvider = class {
|
|
|
969
988
|
|
|
970
989
|
// src/providers/gemini.ts
|
|
971
990
|
import { execSync as execSync6 } from "child_process";
|
|
972
|
-
import { appendFileSync as appendFileSync7, mkdtempSync as mkdtempSync6,
|
|
991
|
+
import { appendFileSync as appendFileSync7, mkdtempSync as mkdtempSync6, rmSync as rmSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
973
992
|
import { tmpdir as tmpdir6 } from "os";
|
|
974
993
|
import { join as join7 } from "path";
|
|
975
994
|
var GEMINI_ERROR_PATTERN = /^Error (executing tool|generating content)/;
|
|
@@ -1044,7 +1063,7 @@ var GeminiProvider = class {
|
|
|
1044
1063
|
};
|
|
1045
1064
|
} finally {
|
|
1046
1065
|
try {
|
|
1047
|
-
|
|
1066
|
+
rmSync7(tmpDir, { recursive: true, force: true });
|
|
1048
1067
|
} catch {
|
|
1049
1068
|
}
|
|
1050
1069
|
}
|
|
@@ -1053,7 +1072,7 @@ var GeminiProvider = class {
|
|
|
1053
1072
|
|
|
1054
1073
|
// src/providers/goose.ts
|
|
1055
1074
|
import { execSync as execSync7 } from "child_process";
|
|
1056
|
-
import { appendFileSync as appendFileSync8, mkdtempSync as mkdtempSync7,
|
|
1075
|
+
import { appendFileSync as appendFileSync8, mkdtempSync as mkdtempSync7, rmSync as rmSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
1057
1076
|
import { tmpdir as tmpdir7 } from "os";
|
|
1058
1077
|
import { join as join8 } from "path";
|
|
1059
1078
|
var GooseProvider = class {
|
|
@@ -1128,7 +1147,7 @@ var GooseProvider = class {
|
|
|
1128
1147
|
};
|
|
1129
1148
|
} finally {
|
|
1130
1149
|
try {
|
|
1131
|
-
|
|
1150
|
+
rmSync8(tmpDir, { recursive: true, force: true });
|
|
1132
1151
|
} catch {
|
|
1133
1152
|
}
|
|
1134
1153
|
}
|
|
@@ -1137,7 +1156,7 @@ var GooseProvider = class {
|
|
|
1137
1156
|
|
|
1138
1157
|
// src/providers/opencode.ts
|
|
1139
1158
|
import { execSync as execSync8 } from "child_process";
|
|
1140
|
-
import { appendFileSync as appendFileSync9, mkdtempSync as mkdtempSync8,
|
|
1159
|
+
import { appendFileSync as appendFileSync9, mkdtempSync as mkdtempSync8, rmSync as rmSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
1141
1160
|
import { tmpdir as tmpdir8 } from "os";
|
|
1142
1161
|
import { join as join9 } from "path";
|
|
1143
1162
|
var OpenCodeProvider = class {
|
|
@@ -1156,7 +1175,8 @@ var OpenCodeProvider = class {
|
|
|
1156
1175
|
const promptFile = join9(tmpDir, "prompt.md");
|
|
1157
1176
|
writeFileSync9(promptFile, prompt, "utf-8");
|
|
1158
1177
|
try {
|
|
1159
|
-
const
|
|
1178
|
+
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1179
|
+
const command = `opencode run ${modelFlag} "$(cat '${promptFile}')"`;
|
|
1160
1180
|
const { proc, isPty } = spawnWithPty(command, {
|
|
1161
1181
|
cwd: opts.cwd,
|
|
1162
1182
|
env: { ...process.env, ...opts.env }
|
|
@@ -1210,7 +1230,7 @@ var OpenCodeProvider = class {
|
|
|
1210
1230
|
};
|
|
1211
1231
|
} finally {
|
|
1212
1232
|
try {
|
|
1213
|
-
|
|
1233
|
+
rmSync9(tmpDir, { recursive: true, force: true });
|
|
1214
1234
|
} catch {
|
|
1215
1235
|
}
|
|
1216
1236
|
}
|
|
@@ -1255,15 +1275,16 @@ var ELIGIBLE_ERROR_PATTERNS = [
|
|
|
1255
1275
|
/ECONNRESET/,
|
|
1256
1276
|
/ENOTFOUND/,
|
|
1257
1277
|
/fetch failed/i,
|
|
1258
|
-
/
|
|
1259
|
-
|
|
1278
|
+
/\btimeout\b/i,
|
|
1279
|
+
/\btimed?\s*out\b/i,
|
|
1260
1280
|
/network.?error/i,
|
|
1261
1281
|
/not installed/i,
|
|
1262
1282
|
/not in PATH/i,
|
|
1263
1283
|
/command not found/i,
|
|
1264
1284
|
/lisa-overseer/i,
|
|
1265
1285
|
/named models unavailable/i,
|
|
1266
|
-
/free plans can only use/i
|
|
1286
|
+
/free plans can only use/i,
|
|
1287
|
+
/empty commit/i
|
|
1267
1288
|
];
|
|
1268
1289
|
function isEligibleForFallback(output) {
|
|
1269
1290
|
return ELIGIBLE_ERROR_PATTERNS.some((pattern) => pattern.test(output));
|
|
@@ -1521,9 +1542,12 @@ async function deleteProviderComments(prUrl) {
|
|
|
1521
1542
|
const [, owner, repo, prNumber] = match;
|
|
1522
1543
|
const { stdout } = await execa2("gh", [
|
|
1523
1544
|
"api",
|
|
1545
|
+
"--paginate",
|
|
1546
|
+
"--jq",
|
|
1547
|
+
".[]",
|
|
1524
1548
|
`/repos/${owner}/${repo}/issues/${prNumber}/comments`
|
|
1525
1549
|
]);
|
|
1526
|
-
const comments = JSON.parse(
|
|
1550
|
+
const comments = stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
1527
1551
|
for (const comment of comments) {
|
|
1528
1552
|
if (PROVIDER_ATTRIBUTION_RE.test(comment.body)) {
|
|
1529
1553
|
try {
|
|
@@ -1559,7 +1583,7 @@ async function appendPrAttribution(prUrl, providerUsed) {
|
|
|
1559
1583
|
// src/cli/detection.ts
|
|
1560
1584
|
function getVersion() {
|
|
1561
1585
|
try {
|
|
1562
|
-
const pkgPath = resolvePath(new URL(".", import.meta.url).pathname, "
|
|
1586
|
+
const pkgPath = resolvePath(new URL(".", import.meta.url).pathname, "../package.json");
|
|
1563
1587
|
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1564
1588
|
return pkg.version;
|
|
1565
1589
|
} catch {
|
|
@@ -1568,7 +1592,7 @@ function getVersion() {
|
|
|
1568
1592
|
}
|
|
1569
1593
|
var CURSOR_FREE_PLAN_ERROR = "Free plans can only use Auto";
|
|
1570
1594
|
async function isCursorFreePlan() {
|
|
1571
|
-
const { mkdtempSync: mkdtempSync9, unlinkSync:
|
|
1595
|
+
const { mkdtempSync: mkdtempSync9, unlinkSync: unlinkSync4, writeFileSync: writeFileSync11 } = await import("fs");
|
|
1572
1596
|
const tmpDir = mkdtempSync9(join10(tmpdir9(), "lisa-cursor-check-"));
|
|
1573
1597
|
const promptFile = join10(tmpDir, "prompt.txt");
|
|
1574
1598
|
writeFileSync11(promptFile, "test", "utf-8");
|
|
@@ -1593,7 +1617,7 @@ async function isCursorFreePlan() {
|
|
|
1593
1617
|
return errorOutput.includes(CURSOR_FREE_PLAN_ERROR);
|
|
1594
1618
|
} finally {
|
|
1595
1619
|
try {
|
|
1596
|
-
|
|
1620
|
+
unlinkSync4(promptFile);
|
|
1597
1621
|
} catch {
|
|
1598
1622
|
}
|
|
1599
1623
|
try {
|
|
@@ -1899,9 +1923,50 @@ async function runConfigWizard(existing) {
|
|
|
1899
1923
|
if (clack2.isCancel(selected)) return process.exit(0);
|
|
1900
1924
|
providerName = selected;
|
|
1901
1925
|
}
|
|
1926
|
+
let gooseProvider;
|
|
1927
|
+
if (providerName === "goose") {
|
|
1928
|
+
const gooseProviderAnswer = await clack2.select({
|
|
1929
|
+
message: "Which backend should Goose use?",
|
|
1930
|
+
initialValue: initial?.provider_options?.goose?.goose_provider ?? process.env.GOOSE_PROVIDER ?? "gemini-cli",
|
|
1931
|
+
options: [
|
|
1932
|
+
{ value: "gemini-cli", label: "Gemini CLI", hint: "requires Gemini CLI installed" },
|
|
1933
|
+
{ value: "anthropic", label: "Anthropic", hint: "requires ANTHROPIC_API_KEY" },
|
|
1934
|
+
{ value: "openai", label: "OpenAI", hint: "requires OPENAI_API_KEY" },
|
|
1935
|
+
{ value: "google", label: "Google (direct)", hint: "requires GOOGLE_API_KEY" },
|
|
1936
|
+
{ value: "ollama", label: "Ollama", hint: "local models" }
|
|
1937
|
+
]
|
|
1938
|
+
});
|
|
1939
|
+
if (clack2.isCancel(gooseProviderAnswer)) return process.exit(0);
|
|
1940
|
+
gooseProvider = gooseProviderAnswer;
|
|
1941
|
+
} else if (providerName === "aider") {
|
|
1942
|
+
clack2.log.info(
|
|
1943
|
+
`Aider requires a direct LLM API key in your environment.
|
|
1944
|
+
Set one of: ${pc.bold("GEMINI_API_KEY")}, ${pc.bold("OPENAI_API_KEY")}, ${pc.bold("ANTHROPIC_API_KEY")}, etc.
|
|
1945
|
+
Aider does not use OAuth or cached credentials.`
|
|
1946
|
+
);
|
|
1947
|
+
} else if (providerName === "opencode") {
|
|
1948
|
+
clack2.log.info(
|
|
1949
|
+
`OpenCode tip: if you have MCP entries in ${pc.cyan("~/.config/opencode/config.json")},
|
|
1950
|
+
remove them or set the file to ${pc.cyan("{}")} \u2014 MCP tools can cause OpenCode to hang.`
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1902
1953
|
let selectedModels = [];
|
|
1903
1954
|
let availableModels = providerModels[providerName];
|
|
1904
|
-
if (providerName === "
|
|
1955
|
+
if (providerName === "goose" && gooseProvider) {
|
|
1956
|
+
const gooseModelsByBackend = {
|
|
1957
|
+
"gemini-cli": [
|
|
1958
|
+
"gemini-2.5-pro",
|
|
1959
|
+
"gemini-2.5-flash",
|
|
1960
|
+
"gemini-2.0-flash",
|
|
1961
|
+
"gemini-2.5-flash-lite"
|
|
1962
|
+
],
|
|
1963
|
+
anthropic: ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5", "claude-sonnet-4-5"],
|
|
1964
|
+
openai: ["gpt-4o", "gpt-4o-mini", "o3", "o4-mini"],
|
|
1965
|
+
google: ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash"],
|
|
1966
|
+
ollama: ["llama3.3", "qwen2.5-coder", "mistral"]
|
|
1967
|
+
};
|
|
1968
|
+
availableModels = gooseModelsByBackend[gooseProvider] ?? [];
|
|
1969
|
+
} else if (providerName === "cursor") {
|
|
1905
1970
|
const isFree = await isCursorFreePlan();
|
|
1906
1971
|
if (isFree) {
|
|
1907
1972
|
availableModels = ["auto"];
|
|
@@ -2056,21 +2121,29 @@ Then reload: ${pc.cyan(`source ${shell}`)}`
|
|
|
2056
2121
|
});
|
|
2057
2122
|
if (clack2.isCancel(projectAnswer)) return process.exit(0);
|
|
2058
2123
|
project = projectAnswer;
|
|
2124
|
+
const isLabelBasedSource = source === "github-issues" || source === "gitlab-issues";
|
|
2059
2125
|
const pickFromAnswer = await clack2.text({
|
|
2060
|
-
message: "Pick up issues in which status?",
|
|
2061
|
-
initialValue: initial?.source_config.pick_from ?? "Backlog",
|
|
2062
|
-
placeholder: "e.g. Backlog, Todo"
|
|
2126
|
+
message: isLabelBasedSource ? "Pick up issues in which state? (open, closed, or a label name)" : "Pick up issues in which status?",
|
|
2127
|
+
initialValue: initial?.source_config.pick_from ?? (isLabelBasedSource ? "open" : "Backlog"),
|
|
2128
|
+
placeholder: isLabelBasedSource ? "e.g. open" : "e.g. Backlog, Todo"
|
|
2063
2129
|
});
|
|
2064
2130
|
if (clack2.isCancel(pickFromAnswer)) return process.exit(0);
|
|
2065
2131
|
pickFrom = pickFromAnswer;
|
|
2066
2132
|
const inProgressAnswer = await clack2.text({
|
|
2067
|
-
message: "Move to which status while the agent is working?",
|
|
2068
|
-
initialValue: initial?.source_config.in_progress ?? "In Progress"
|
|
2133
|
+
message: isLabelBasedSource ? "Which label to apply while the agent is working? (must differ from pick_from label)" : "Move to which status while the agent is working?",
|
|
2134
|
+
initialValue: initial?.source_config.in_progress ?? "In Progress",
|
|
2135
|
+
placeholder: isLabelBasedSource ? "e.g. in-progress" : void 0
|
|
2069
2136
|
});
|
|
2070
2137
|
if (clack2.isCancel(inProgressAnswer)) return process.exit(0);
|
|
2071
2138
|
inProgress = inProgressAnswer;
|
|
2139
|
+
if (isLabelBasedSource && inProgress === pickFrom) {
|
|
2140
|
+
clack2.log.warning(
|
|
2141
|
+
`"in_progress" label is the same as "pick_from" label ("${pickFrom}").
|
|
2142
|
+
This will cause Lisa to re-pick the issue on recovery. Consider using a different label.`
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2072
2145
|
const doneAnswer = await clack2.text({
|
|
2073
|
-
message: "Move to which status after the PR is created?",
|
|
2146
|
+
message: isLabelBasedSource ? "Which label to apply after the PR is created?" : "Move to which status after the PR is created?",
|
|
2074
2147
|
initialValue: initial?.source_config.done ?? "In Review"
|
|
2075
2148
|
});
|
|
2076
2149
|
if (clack2.isCancel(doneAnswer)) return process.exit(0);
|
|
@@ -2131,7 +2204,10 @@ Then reload: ${pc.cyan(`source ${shell}`)}`
|
|
|
2131
2204
|
provider: providerName,
|
|
2132
2205
|
provider_options: {
|
|
2133
2206
|
...initial?.provider_options || {},
|
|
2134
|
-
[providerName]: {
|
|
2207
|
+
[providerName]: {
|
|
2208
|
+
models: selectedModels,
|
|
2209
|
+
...gooseProvider ? { goose_provider: gooseProvider } : {}
|
|
2210
|
+
}
|
|
2135
2211
|
},
|
|
2136
2212
|
source,
|
|
2137
2213
|
source_config: {
|
|
@@ -2204,8 +2280,8 @@ var feedback = defineCommand2({
|
|
|
2204
2280
|
},
|
|
2205
2281
|
async run({ args }) {
|
|
2206
2282
|
const { fetchPrFeedback: fetchPrFeedback2, formatPrFeedbackEntry: formatPrFeedbackEntry2 } = await import("./pr-feedback-DGHNP3E7.js");
|
|
2207
|
-
const { appendRawEntrySync } = await import("./guardrails-
|
|
2208
|
-
const { ensureCacheDir: ensureCacheDir2 } = await import("./paths-
|
|
2283
|
+
const { appendRawEntrySync } = await import("./guardrails-I5ACG5LQ.js");
|
|
2284
|
+
const { ensureCacheDir: ensureCacheDir2 } = await import("./paths-WQN4NBC6.js");
|
|
2209
2285
|
const prUrl = args.pr;
|
|
2210
2286
|
const issueId = args.issue ?? "unknown";
|
|
2211
2287
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -2768,14 +2844,14 @@ var LinearSource = class {
|
|
|
2768
2844
|
`mutation($teamId: String!, $name: String!) {
|
|
2769
2845
|
issueLabelCreate(input: { teamId: $teamId, name: $name }) {
|
|
2770
2846
|
success
|
|
2771
|
-
|
|
2847
|
+
issueLabel { id name }
|
|
2772
2848
|
}
|
|
2773
2849
|
}`,
|
|
2774
2850
|
{ teamId: issueData.issue.team.id, name: labelName }
|
|
2775
2851
|
);
|
|
2776
|
-
if (created.issueLabelCreate.success && created.issueLabelCreate.
|
|
2852
|
+
if (created.issueLabelCreate.success && created.issueLabelCreate.issueLabel) {
|
|
2777
2853
|
log(`Label "${labelName}" created automatically in team ${issueData.issue.team.id}`);
|
|
2778
|
-
teamLabel = created.issueLabelCreate.
|
|
2854
|
+
teamLabel = created.issueLabelCreate.issueLabel;
|
|
2779
2855
|
} else {
|
|
2780
2856
|
const refetch = await gql(
|
|
2781
2857
|
`query($identifier: String!) {
|
|
@@ -2976,7 +3052,7 @@ var PlaneSource = class {
|
|
|
2976
3052
|
labelNames.map((name) => resolveLabelId(workspaceSlug, projectId, name))
|
|
2977
3053
|
);
|
|
2978
3054
|
const data = await planeGet(
|
|
2979
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3055
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/?state=${stateId}&per_page=100`
|
|
2980
3056
|
);
|
|
2981
3057
|
const matching = data.results.filter((i) => i.state === stateId).filter((i) => labelIds.every((lid) => i.labels.includes(lid)));
|
|
2982
3058
|
if (matching.length === 0) return null;
|
|
@@ -2989,14 +3065,14 @@ var PlaneSource = class {
|
|
|
2989
3065
|
const blocked = [];
|
|
2990
3066
|
for (const issue3 of matching) {
|
|
2991
3067
|
const relations = await fetchAll(
|
|
2992
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3068
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${issue3.id}/relations/`
|
|
2993
3069
|
);
|
|
2994
3070
|
const blockerIds = relations.filter((r) => r.relation_type === "blocked_by").map((r) => r.related_issue);
|
|
2995
3071
|
const activeBlockers = [];
|
|
2996
3072
|
for (const blockerId of blockerIds) {
|
|
2997
3073
|
try {
|
|
2998
3074
|
const blocker = await planeGet(
|
|
2999
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3075
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${blockerId}/`
|
|
3000
3076
|
);
|
|
3001
3077
|
if (!doneStateIds.has(blocker.state)) {
|
|
3002
3078
|
activeBlockers.push(blockerId);
|
|
@@ -3037,7 +3113,7 @@ var PlaneSource = class {
|
|
|
3037
3113
|
try {
|
|
3038
3114
|
const { workspaceSlug, projectId, issueId } = parseIssueId(id);
|
|
3039
3115
|
const issue2 = await planeGet(
|
|
3040
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3116
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${issueId}/`
|
|
3041
3117
|
);
|
|
3042
3118
|
const webUrl = `${getAppUrl()}/${workspaceSlug}/projects/${projectId}/issues/${issue2.id}`;
|
|
3043
3119
|
return {
|
|
@@ -3054,14 +3130,14 @@ var PlaneSource = class {
|
|
|
3054
3130
|
const { workspaceSlug, projectId, issueId: planeIssueId } = parseIssueId(issueId);
|
|
3055
3131
|
const stateId = await resolveStateId(workspaceSlug, projectId, stateName);
|
|
3056
3132
|
await planePatch(
|
|
3057
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3133
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${planeIssueId}/`,
|
|
3058
3134
|
{ state: stateId }
|
|
3059
3135
|
);
|
|
3060
3136
|
}
|
|
3061
3137
|
async attachPullRequest(issueId, prUrl) {
|
|
3062
3138
|
const { workspaceSlug, projectId, issueId: planeIssueId } = parseIssueId(issueId);
|
|
3063
3139
|
await planePost(
|
|
3064
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3140
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${planeIssueId}/comments/`,
|
|
3065
3141
|
{ comment_html: `<p>Pull request: <a href="${prUrl}">${prUrl}</a></p>` }
|
|
3066
3142
|
);
|
|
3067
3143
|
}
|
|
@@ -3080,7 +3156,7 @@ var PlaneSource = class {
|
|
|
3080
3156
|
labelNames.map((name) => resolveLabelId(workspaceSlug, projectId, name))
|
|
3081
3157
|
);
|
|
3082
3158
|
const data = await planeGet(
|
|
3083
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3159
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/?state=${stateId}&per_page=100`
|
|
3084
3160
|
);
|
|
3085
3161
|
return data.results.filter((i) => i.state === stateId).filter((i) => labelIds.every((lid) => i.labels.includes(lid))).map((i) => {
|
|
3086
3162
|
const webUrl = `${getAppUrl()}/${workspaceSlug}/projects/${projectId}/issues/${i.id}`;
|
|
@@ -3095,14 +3171,14 @@ var PlaneSource = class {
|
|
|
3095
3171
|
async removeLabel(issueId, labelName) {
|
|
3096
3172
|
const { workspaceSlug, projectId, issueId: planeIssueId } = parseIssueId(issueId);
|
|
3097
3173
|
const issue2 = await planeGet(
|
|
3098
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3174
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${planeIssueId}/`
|
|
3099
3175
|
);
|
|
3100
3176
|
const labels = await fetchLabels(workspaceSlug, projectId);
|
|
3101
3177
|
const labelObj = labels.find((l) => l.name.toLowerCase() === labelName.toLowerCase());
|
|
3102
3178
|
if (!labelObj || !issue2.labels.includes(labelObj.id)) return;
|
|
3103
3179
|
const updatedLabels = issue2.labels.filter((lid) => lid !== labelObj.id);
|
|
3104
3180
|
await planePatch(
|
|
3105
|
-
`/workspaces/${workspaceSlug}/projects/${projectId}/
|
|
3181
|
+
`/workspaces/${workspaceSlug}/projects/${projectId}/work-items/${planeIssueId}/`,
|
|
3106
3182
|
{ labels: updatedLabels }
|
|
3107
3183
|
);
|
|
3108
3184
|
}
|
|
@@ -3144,6 +3220,24 @@ function extractStories(result) {
|
|
|
3144
3220
|
if (Array.isArray(result)) return result;
|
|
3145
3221
|
return result.data ?? [];
|
|
3146
3222
|
}
|
|
3223
|
+
function extractNext(result) {
|
|
3224
|
+
if (Array.isArray(result)) return null;
|
|
3225
|
+
return result.next ?? null;
|
|
3226
|
+
}
|
|
3227
|
+
async function searchStoriesAll(body) {
|
|
3228
|
+
const all = [];
|
|
3229
|
+
let next = null;
|
|
3230
|
+
do {
|
|
3231
|
+
const req = next ? { ...body, next } : body;
|
|
3232
|
+
const result = await shortcutPost(
|
|
3233
|
+
"/api/v3/stories/search",
|
|
3234
|
+
req
|
|
3235
|
+
);
|
|
3236
|
+
all.push(...extractStories(result));
|
|
3237
|
+
next = extractNext(result);
|
|
3238
|
+
} while (next);
|
|
3239
|
+
return all;
|
|
3240
|
+
}
|
|
3147
3241
|
async function resolveWorkflowStateId(stateName) {
|
|
3148
3242
|
const workflows = await shortcutGet("/api/v3/workflows");
|
|
3149
3243
|
for (const workflow of workflows) {
|
|
@@ -3205,15 +3299,11 @@ var ShortcutSource = class {
|
|
|
3205
3299
|
const seen = /* @__PURE__ */ new Set();
|
|
3206
3300
|
const allStories = [];
|
|
3207
3301
|
for (const stateId of stateIds) {
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
archived: false
|
|
3214
|
-
}
|
|
3215
|
-
);
|
|
3216
|
-
for (const story2 of extractStories(searchResult)) {
|
|
3302
|
+
for (const story2 of await searchStoriesAll({
|
|
3303
|
+
workflow_state_id: stateId,
|
|
3304
|
+
label_name: primaryLabel,
|
|
3305
|
+
archived: false
|
|
3306
|
+
})) {
|
|
3217
3307
|
if (!seen.has(story2.id)) {
|
|
3218
3308
|
seen.add(story2.id);
|
|
3219
3309
|
allStories.push(story2);
|
|
@@ -3309,15 +3399,11 @@ var ShortcutSource = class {
|
|
|
3309
3399
|
const seen = /* @__PURE__ */ new Set();
|
|
3310
3400
|
const allStories = [];
|
|
3311
3401
|
for (const stateId of stateIds) {
|
|
3312
|
-
const
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
archived: false
|
|
3318
|
-
}
|
|
3319
|
-
);
|
|
3320
|
-
for (const story of extractStories(searchResult)) {
|
|
3402
|
+
for (const story of await searchStoriesAll({
|
|
3403
|
+
workflow_state_id: stateId,
|
|
3404
|
+
label_name: primaryLabel,
|
|
3405
|
+
archived: false
|
|
3406
|
+
})) {
|
|
3321
3407
|
if (!seen.has(story.id)) {
|
|
3322
3408
|
seen.add(story.id);
|
|
3323
3409
|
allStories.push(story);
|
|
@@ -3686,6 +3772,7 @@ var userKilledSet = /* @__PURE__ */ new Set();
|
|
|
3686
3772
|
var userSkippedSet = /* @__PURE__ */ new Set();
|
|
3687
3773
|
var _shuttingDown = false;
|
|
3688
3774
|
var _loopPaused = false;
|
|
3775
|
+
var _userQuitFromWatchPrompt = false;
|
|
3689
3776
|
function isShuttingDown() {
|
|
3690
3777
|
return _shuttingDown;
|
|
3691
3778
|
}
|
|
@@ -3695,6 +3782,9 @@ function setShuttingDown(value) {
|
|
|
3695
3782
|
function isLoopPaused() {
|
|
3696
3783
|
return _loopPaused;
|
|
3697
3784
|
}
|
|
3785
|
+
function hasUserQuitFromWatchPrompt() {
|
|
3786
|
+
return _userQuitFromWatchPrompt;
|
|
3787
|
+
}
|
|
3698
3788
|
function killProviderForIssue(issueId) {
|
|
3699
3789
|
const pid = activeProviderPids.get(issueId);
|
|
3700
3790
|
if (!pid) return;
|
|
@@ -3792,6 +3882,10 @@ function setupEventListeners() {
|
|
|
3792
3882
|
}
|
|
3793
3883
|
}
|
|
3794
3884
|
});
|
|
3885
|
+
kanbanEmitter.on("loop:quit", () => {
|
|
3886
|
+
_userQuitFromWatchPrompt = true;
|
|
3887
|
+
setShuttingDown(true);
|
|
3888
|
+
});
|
|
3795
3889
|
}
|
|
3796
3890
|
|
|
3797
3891
|
// src/loop/helpers.ts
|
|
@@ -3888,6 +3982,7 @@ async function injectRejectedPrFeedback(workspace, issueId, prUrls) {
|
|
|
3888
3982
|
clearPrUrl(workspace, issueId);
|
|
3889
3983
|
}
|
|
3890
3984
|
async function recoverOrphanIssues(source, config2) {
|
|
3985
|
+
if (!config2.source_config.in_progress) return;
|
|
3891
3986
|
if (config2.source_config.in_progress === config2.source_config.pick_from) return;
|
|
3892
3987
|
const orphanConfig = {
|
|
3893
3988
|
...config2.source_config,
|
|
@@ -4588,6 +4683,7 @@ async function appendPrAttribution2(prUrl, providerUsed) {
|
|
|
4588
4683
|
if (!getRes.ok) return;
|
|
4589
4684
|
const prData = await getRes.json();
|
|
4590
4685
|
const currentDescription = prData.description ?? "";
|
|
4686
|
+
const currentTitle = prData.title ?? "";
|
|
4591
4687
|
const providerName = formatProviderName2(providerUsed);
|
|
4592
4688
|
const attribution = `
|
|
4593
4689
|
|
|
@@ -4600,7 +4696,7 @@ async function appendPrAttribution2(prUrl, providerUsed) {
|
|
|
4600
4696
|
Authorization: authHeader,
|
|
4601
4697
|
"Content-Type": "application/json"
|
|
4602
4698
|
},
|
|
4603
|
-
body: JSON.stringify({ description: newDescription }),
|
|
4699
|
+
body: JSON.stringify({ description: newDescription, title: currentTitle }),
|
|
4604
4700
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS6)
|
|
4605
4701
|
});
|
|
4606
4702
|
} catch {
|
|
@@ -4701,7 +4797,7 @@ function buildPrCreateInstruction(platform2, targetBranch) {
|
|
|
4701
4797
|
REPO=$(git remote get-url origin | sed 's/.*bitbucket\\.org[:/][^/]*\\///;s/\\.git$//')
|
|
4702
4798
|
BRANCH=$(git branch --show-current)
|
|
4703
4799
|
curl -X POST \\
|
|
4704
|
-
-H "Authorization:
|
|
4800
|
+
-H "Authorization: Basic $(printf '%s:%s' "$BITBUCKET_USERNAME" "$BITBUCKET_TOKEN" | base64)" \\
|
|
4705
4801
|
-H "Content-Type: application/json" \\
|
|
4706
4802
|
"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO/pullrequests" \\
|
|
4707
4803
|
--data "{\\"title\\":\\"<conventional-commit-title>\\",\\"description\\":\\"<markdown-summary>\\",\\"source\\":{\\"branch\\":{\\"name\\":\\"$BRANCH\\"}},\\"destination\\":{\\"branch\\":{\\"name\\":\\"${dest}\\"}}}"
|
|
@@ -4820,6 +4916,7 @@ function buildRulesSection(env, variant = "issue", extraRules = "") {
|
|
|
4820
4916
|
- **ALL git commits, branch names, PR titles, and PR descriptions MUST be in English.**
|
|
4821
4917
|
- The issue description may be in any language \u2014 read it for context but write all code artifacts in English.
|
|
4822
4918
|
- Do NOT install new dependencies unless the issue explicitly requires it.
|
|
4919
|
+
- Do NOT use documentation lookup MCP tools (e.g., Context7, codesearch, Exa) \u2014 they have free-tier rate limits that will block your execution. Read files directly from the repository. Web search is allowed only when strictly necessary (e.g., looking up an external API format not available in the codebase).
|
|
4823
4920
|
${envRule}${extraRules}- If you get stuck or the issue is unclear, STOP and explain why.
|
|
4824
4921
|
${scopeRule}
|
|
4825
4922
|
- If the repo has a CLAUDE.md, read it first and follow its conventions.`;
|
|
@@ -5151,14 +5248,17 @@ ${generatorBlock}
|
|
|
5151
5248
|
|
|
5152
5249
|
2. **Determine execution order**: If multiple repos are affected, decide the order. Repos that produce APIs, schemas, or shared libraries should come first. Repos that consume them should come later.
|
|
5153
5250
|
|
|
5154
|
-
3. **Write the plan**:
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5251
|
+
3. **Write the plan file to disk**: Use a bash command or file-write tool to write the plan to \`${resolvedPlanPath}\`.
|
|
5252
|
+
**You MUST write the file to disk. Do NOT print the JSON to stdout or in a code block.**
|
|
5253
|
+
|
|
5254
|
+
The file must be valid JSON with this structure (replace angle-bracket placeholders with real values):
|
|
5255
|
+
- \`repoPath\`: absolute path to the affected repository
|
|
5256
|
+
- \`scope\`: concise English description of what to implement in that repo
|
|
5257
|
+
- \`order\`: integer starting at 1 (lower = executes first)
|
|
5258
|
+
|
|
5259
|
+
Use your write_file tool, or a bash command such as:
|
|
5260
|
+
\`\`\`bash
|
|
5261
|
+
printf '%s' '{"steps":[{"repoPath":"/absolute/path","scope":"description of work","order":1}]}' > '${resolvedPlanPath}'
|
|
5162
5262
|
\`\`\`
|
|
5163
5263
|
|
|
5164
5264
|
## Rules
|
|
@@ -5597,19 +5697,20 @@ function registerCleanup() {
|
|
|
5597
5697
|
}
|
|
5598
5698
|
|
|
5599
5699
|
// src/loop/manifest.ts
|
|
5600
|
-
import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync
|
|
5700
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync } from "fs";
|
|
5601
5701
|
function readLisaManifest(cwd, issueId) {
|
|
5602
5702
|
const manifestPath = getManifestPath(cwd, issueId);
|
|
5603
5703
|
if (!existsSync8(manifestPath)) return null;
|
|
5604
5704
|
try {
|
|
5605
5705
|
return JSON.parse(readFileSync8(manifestPath, "utf-8").trim());
|
|
5606
5706
|
} catch {
|
|
5707
|
+
warn(`Failed to parse manifest at ${manifestPath} \u2014 agent may not have written it correctly`);
|
|
5607
5708
|
return null;
|
|
5608
5709
|
}
|
|
5609
5710
|
}
|
|
5610
5711
|
function cleanupManifest(cwd, issueId) {
|
|
5611
5712
|
try {
|
|
5612
|
-
|
|
5713
|
+
unlinkSync(getManifestPath(cwd, issueId));
|
|
5613
5714
|
} catch {
|
|
5614
5715
|
}
|
|
5615
5716
|
}
|
|
@@ -5643,14 +5744,13 @@ function readPlanFile(filePath) {
|
|
|
5643
5744
|
}
|
|
5644
5745
|
|
|
5645
5746
|
// src/loop/multi-repo-session.ts
|
|
5646
|
-
import { appendFileSync as appendFileSync10, unlinkSync as
|
|
5747
|
+
import { appendFileSync as appendFileSync10, unlinkSync as unlinkSync2 } from "fs";
|
|
5647
5748
|
import { join as join14, resolve as resolve7 } from "path";
|
|
5648
5749
|
async function runWorktreeMultiRepoSession(config2, issue2, logFile, session, models) {
|
|
5649
5750
|
const workspace = resolve7(config2.workspace);
|
|
5650
|
-
const
|
|
5651
|
-
const planPath = join14(workspace, `.lisa-plan-${safeId}.json`);
|
|
5751
|
+
const planPath = getPlanPath(workspace, issue2.id);
|
|
5652
5752
|
try {
|
|
5653
|
-
|
|
5753
|
+
unlinkSync2(planPath);
|
|
5654
5754
|
} catch {
|
|
5655
5755
|
}
|
|
5656
5756
|
initLogFile(logFile);
|
|
@@ -5689,7 +5789,7 @@ ${planResult.output}
|
|
|
5689
5789
|
if (!planResult.success) {
|
|
5690
5790
|
error(`Planning phase failed for ${issue2.id}. Check ${logFile}`);
|
|
5691
5791
|
try {
|
|
5692
|
-
|
|
5792
|
+
unlinkSync2(planPath);
|
|
5693
5793
|
} catch {
|
|
5694
5794
|
}
|
|
5695
5795
|
activeProviderPids.delete(issue2.id);
|
|
@@ -5704,7 +5804,7 @@ ${planResult.output}
|
|
|
5704
5804
|
if (!plan?.steps || plan.steps.length === 0) {
|
|
5705
5805
|
error(`Agent did not produce a valid execution plan for ${issue2.id}. Aborting.`);
|
|
5706
5806
|
try {
|
|
5707
|
-
|
|
5807
|
+
unlinkSync2(planPath);
|
|
5708
5808
|
} catch {
|
|
5709
5809
|
}
|
|
5710
5810
|
activeProviderPids.delete(issue2.id);
|
|
@@ -5720,7 +5820,7 @@ ${planResult.output}
|
|
|
5720
5820
|
`Plan produced ${sortedSteps.length} step(s): ${sortedSteps.map((s) => s.repoPath).join(" \u2192 ")}`
|
|
5721
5821
|
);
|
|
5722
5822
|
try {
|
|
5723
|
-
|
|
5823
|
+
unlinkSync2(planPath);
|
|
5724
5824
|
} catch {
|
|
5725
5825
|
}
|
|
5726
5826
|
const prUrls = [];
|
|
@@ -6003,6 +6103,34 @@ ${result.output}
|
|
|
6003
6103
|
cleanupManifest(workspace, issue2.id);
|
|
6004
6104
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
6005
6105
|
}
|
|
6106
|
+
const hasChanges = await hasCodeChanges(repoPath, _defaultBranch);
|
|
6107
|
+
if (!hasChanges) {
|
|
6108
|
+
error(
|
|
6109
|
+
`Provider reported success but no code changes detected. Treating as failure for ${issue2.id}.`
|
|
6110
|
+
);
|
|
6111
|
+
cleanupManifest(workspace, issue2.id);
|
|
6112
|
+
const emptyCommitResult = {
|
|
6113
|
+
success: false,
|
|
6114
|
+
output: "Provider reported success but no code changes detected",
|
|
6115
|
+
duration: result.duration,
|
|
6116
|
+
providerUsed: result.providerUsed,
|
|
6117
|
+
attempts: [
|
|
6118
|
+
{
|
|
6119
|
+
provider: result.providerUsed,
|
|
6120
|
+
model: "",
|
|
6121
|
+
success: false,
|
|
6122
|
+
error: "Eligible error (empty commit)",
|
|
6123
|
+
duration: result.duration
|
|
6124
|
+
}
|
|
6125
|
+
]
|
|
6126
|
+
};
|
|
6127
|
+
return {
|
|
6128
|
+
success: false,
|
|
6129
|
+
providerUsed: result.providerUsed,
|
|
6130
|
+
prUrls: [],
|
|
6131
|
+
fallback: emptyCommitResult
|
|
6132
|
+
};
|
|
6133
|
+
}
|
|
6006
6134
|
const manifest = readLisaManifest(workspace, issue2.id);
|
|
6007
6135
|
cleanupManifest(workspace, issue2.id);
|
|
6008
6136
|
let prUrl = manifest?.prUrl;
|
|
@@ -6131,6 +6259,34 @@ ${result.output}
|
|
|
6131
6259
|
await cleanupWorktree(repoPath, worktreePath);
|
|
6132
6260
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
6133
6261
|
}
|
|
6262
|
+
const hasChanges = await hasCodeChanges(worktreePath, baseBranch);
|
|
6263
|
+
if (!hasChanges) {
|
|
6264
|
+
error(
|
|
6265
|
+
`Provider reported success but no code changes detected. Treating as failure for ${issue2.id}.`
|
|
6266
|
+
);
|
|
6267
|
+
await cleanupWorktree(repoPath, worktreePath);
|
|
6268
|
+
const emptyCommitResult = {
|
|
6269
|
+
success: false,
|
|
6270
|
+
output: "Provider reported success but no code changes detected",
|
|
6271
|
+
duration: result.duration,
|
|
6272
|
+
providerUsed: result.providerUsed,
|
|
6273
|
+
attempts: [
|
|
6274
|
+
{
|
|
6275
|
+
provider: result.providerUsed,
|
|
6276
|
+
model: "",
|
|
6277
|
+
success: false,
|
|
6278
|
+
error: "Eligible error (empty commit)",
|
|
6279
|
+
duration: result.duration
|
|
6280
|
+
}
|
|
6281
|
+
]
|
|
6282
|
+
};
|
|
6283
|
+
return {
|
|
6284
|
+
success: false,
|
|
6285
|
+
providerUsed: result.providerUsed,
|
|
6286
|
+
prUrls: [],
|
|
6287
|
+
fallback: emptyCommitResult
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6134
6290
|
const manifest = readManifestFile(manifestPath);
|
|
6135
6291
|
let prUrl = manifest?.prUrl;
|
|
6136
6292
|
if (!prUrl) {
|
|
@@ -6269,6 +6425,18 @@ async function runConcurrentLoop(config2, source, models, workspace, opts) {
|
|
|
6269
6425
|
if (!issue2) {
|
|
6270
6426
|
if (opts.watch) {
|
|
6271
6427
|
if (activeWorkers.size === 0) {
|
|
6428
|
+
if (completedCount > 0) {
|
|
6429
|
+
ok(`All issues resolved. Prompting user to continue watching...`);
|
|
6430
|
+
kanbanEmitter.emit("work:watch-prompt");
|
|
6431
|
+
setTitle("Lisa \u2014 all resolved");
|
|
6432
|
+
await waitIfPaused();
|
|
6433
|
+
if (hasUserQuitFromWatchPrompt() || isShuttingDown()) {
|
|
6434
|
+
noMoreIssues = true;
|
|
6435
|
+
break;
|
|
6436
|
+
}
|
|
6437
|
+
kanbanEmitter.emit("work:watch-prompt-resumed");
|
|
6438
|
+
ok(`Resuming watch mode (polling every ${WATCH_POLL_INTERVAL_MS / 1e3}s)...`);
|
|
6439
|
+
}
|
|
6272
6440
|
ok(
|
|
6273
6441
|
`No issues ready. Watching for new issues (polling every ${WATCH_POLL_INTERVAL_MS / 1e3}s)...`
|
|
6274
6442
|
);
|
|
@@ -6381,13 +6549,13 @@ async function getChangedFiles(repoPath, baseBranch, dependencyBranch) {
|
|
|
6381
6549
|
}
|
|
6382
6550
|
|
|
6383
6551
|
// src/loop/branch-session.ts
|
|
6384
|
-
import { appendFileSync as appendFileSync12, unlinkSync as
|
|
6552
|
+
import { appendFileSync as appendFileSync12, unlinkSync as unlinkSync3 } from "fs";
|
|
6385
6553
|
import { join as join16, resolve as resolve10 } from "path";
|
|
6386
6554
|
async function runBranchSession(config2, issue2, logFile, session, models) {
|
|
6387
6555
|
const workspace = resolve10(config2.workspace);
|
|
6388
6556
|
const manifestPath = join16(workspace, ".lisa-manifest.json");
|
|
6389
6557
|
try {
|
|
6390
|
-
|
|
6558
|
+
unlinkSync3(manifestPath);
|
|
6391
6559
|
} catch {
|
|
6392
6560
|
}
|
|
6393
6561
|
const testRunner = detectTestRunner(workspace);
|
|
@@ -6461,9 +6629,36 @@ ${result.output}
|
|
|
6461
6629
|
error(`Session ${session} failed for ${issue2.id}. Check ${logFile}`);
|
|
6462
6630
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
6463
6631
|
}
|
|
6632
|
+
const hasChanges = await hasCodeChanges(workspace, config2.base_branch);
|
|
6633
|
+
if (!hasChanges) {
|
|
6634
|
+
error(
|
|
6635
|
+
`Provider reported success but no code changes detected. Treating as failure for ${issue2.id}.`
|
|
6636
|
+
);
|
|
6637
|
+
const emptyCommitResult = {
|
|
6638
|
+
success: false,
|
|
6639
|
+
output: "Provider reported success but no code changes detected",
|
|
6640
|
+
duration: result.duration,
|
|
6641
|
+
providerUsed: result.providerUsed,
|
|
6642
|
+
attempts: [
|
|
6643
|
+
{
|
|
6644
|
+
provider: result.providerUsed,
|
|
6645
|
+
model: "",
|
|
6646
|
+
success: false,
|
|
6647
|
+
error: "Eligible error (empty commit)",
|
|
6648
|
+
duration: result.duration
|
|
6649
|
+
}
|
|
6650
|
+
]
|
|
6651
|
+
};
|
|
6652
|
+
return {
|
|
6653
|
+
success: false,
|
|
6654
|
+
providerUsed: result.providerUsed,
|
|
6655
|
+
prUrls: [],
|
|
6656
|
+
fallback: emptyCommitResult
|
|
6657
|
+
};
|
|
6658
|
+
}
|
|
6464
6659
|
const manifest = readManifestFile(manifestPath);
|
|
6465
6660
|
try {
|
|
6466
|
-
|
|
6661
|
+
unlinkSync3(manifestPath);
|
|
6467
6662
|
} catch {
|
|
6468
6663
|
}
|
|
6469
6664
|
let prUrl = manifest?.prUrl;
|
|
@@ -6556,6 +6751,23 @@ async function runSequentialLoop(config2, source, models, workspace, opts) {
|
|
|
6556
6751
|
break;
|
|
6557
6752
|
}
|
|
6558
6753
|
if (opts.watch) {
|
|
6754
|
+
if (completedCount > 0) {
|
|
6755
|
+
ok(`All issues resolved. Prompting user to continue watching...`);
|
|
6756
|
+
kanbanEmitter.emit("work:watch-prompt");
|
|
6757
|
+
setTitle("Lisa \u2014 all resolved");
|
|
6758
|
+
await waitIfPaused();
|
|
6759
|
+
if (hasUserQuitFromWatchPrompt() || isShuttingDown()) {
|
|
6760
|
+
break;
|
|
6761
|
+
}
|
|
6762
|
+
kanbanEmitter.emit("work:watch-prompt-resumed");
|
|
6763
|
+
ok(`Resuming watch mode (polling every ${WATCH_POLL_INTERVAL_MS / 1e3}s)...`);
|
|
6764
|
+
kanbanEmitter.emit("work:watching");
|
|
6765
|
+
setTitle("Lisa \u2014 watching...");
|
|
6766
|
+
await sleep2(WATCH_POLL_INTERVAL_MS);
|
|
6767
|
+
kanbanEmitter.emit("work:watch-resume");
|
|
6768
|
+
session--;
|
|
6769
|
+
continue;
|
|
6770
|
+
}
|
|
6559
6771
|
ok(
|
|
6560
6772
|
`No issues ready. Watching for new issues (polling every ${WATCH_POLL_INTERVAL_MS / 1e3}s)...`
|
|
6561
6773
|
);
|
|
@@ -6903,7 +7115,7 @@ var run = defineCommand5({
|
|
|
6903
7115
|
if (isTTY) {
|
|
6904
7116
|
const { render } = await import("ink");
|
|
6905
7117
|
const { createElement } = await import("react");
|
|
6906
|
-
const { KanbanApp } = await import("./kanban-
|
|
7118
|
+
const { KanbanApp } = await import("./kanban-QZ5NRPJ5.js");
|
|
6907
7119
|
const demoConfig = {
|
|
6908
7120
|
provider: "claude",
|
|
6909
7121
|
source: "linear",
|
|
@@ -6940,6 +7152,12 @@ var run = defineCommand5({
|
|
|
6940
7152
|
label: args.label,
|
|
6941
7153
|
bell: args.bell
|
|
6942
7154
|
});
|
|
7155
|
+
if (merged.provider === "goose") {
|
|
7156
|
+
const gooseProvider = merged.provider_options?.goose?.goose_provider;
|
|
7157
|
+
if (gooseProvider && !process.env.GOOSE_PROVIDER) {
|
|
7158
|
+
process.env.GOOSE_PROVIDER = gooseProvider;
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
6943
7161
|
if (args.lifecycle || args["lifecycle-timeout"]) {
|
|
6944
7162
|
const lifecycleTimeout = args["lifecycle-timeout"] ? Number.parseInt(args["lifecycle-timeout"], 10) : void 0;
|
|
6945
7163
|
merged.lifecycle = {
|
|
@@ -6972,7 +7190,7 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
6972
7190
|
if (isTTY) {
|
|
6973
7191
|
const { render } = await import("ink");
|
|
6974
7192
|
const { createElement } = await import("react");
|
|
6975
|
-
const { KanbanApp } = await import("./kanban-
|
|
7193
|
+
const { KanbanApp } = await import("./kanban-QZ5NRPJ5.js");
|
|
6976
7194
|
render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
|
|
6977
7195
|
}
|
|
6978
7196
|
await runLoop(merged, {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
kanbanEmitter,
|
|
4
4
|
useKanbanState
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ITQEGO5A.js";
|
|
6
6
|
import {
|
|
7
7
|
resetTitle,
|
|
8
8
|
startSpinner,
|
|
@@ -56,21 +56,6 @@ function wrapTitle(title, maxWidth) {
|
|
|
56
56
|
const line2 = remaining.length > maxWidth ? `${remaining.slice(0, maxWidth - 1)}\u2026` : remaining;
|
|
57
57
|
return [line1, line2];
|
|
58
58
|
}
|
|
59
|
-
function getLastOutputLine(outputLog, maxWidth) {
|
|
60
|
-
if (!outputLog) return "";
|
|
61
|
-
const ansiPattern = /\x1B(?:\[[0-?]*[ -/]*[@-~]|\].*?(?:\x07|\x1B\\))/g;
|
|
62
|
-
const stripped = outputLog.replace(ansiPattern, "");
|
|
63
|
-
const lines = stripped.split(/\r?\n/).map((line) => {
|
|
64
|
-
const parts = line.split("\r");
|
|
65
|
-
return (parts[parts.length - 1] ?? "").trim();
|
|
66
|
-
}).filter((line) => line.length > 0);
|
|
67
|
-
if (lines.length === 0) return "";
|
|
68
|
-
const lastLine = lines[lines.length - 1] ?? "";
|
|
69
|
-
if (lastLine.length > maxWidth) {
|
|
70
|
-
return `${lastLine.slice(0, maxWidth - 1)}\u2026`;
|
|
71
|
-
}
|
|
72
|
-
return lastLine;
|
|
73
|
-
}
|
|
74
59
|
function Card({
|
|
75
60
|
card,
|
|
76
61
|
isSelected = false,
|
|
@@ -134,9 +119,7 @@ function Card({
|
|
|
134
119
|
] }),
|
|
135
120
|
/* @__PURE__ */ jsx(Text, { bold: isSelected, dimColor: !isSelected, children: stripDoubleWidth(titleLine1).padEnd(cardWidth) }),
|
|
136
121
|
/* @__PURE__ */ jsx(Text, { bold: isSelected, dimColor: !isSelected, children: stripDoubleWidth(titleLine2).padEnd(cardWidth) }),
|
|
137
|
-
/* @__PURE__ */ jsx(Text, {
|
|
138
|
-
card.column === "in_progress" ? getLastOutputLine(card.outputLog, cardWidth) : ""
|
|
139
|
-
).padEnd(cardWidth) }),
|
|
122
|
+
/* @__PURE__ */ jsx(Text, { children: " ".repeat(cardWidth) }),
|
|
140
123
|
card.column === "in_progress" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", marginTop: 0, children: [
|
|
141
124
|
isPausedInProgress ? /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u23F8" }) : /* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
142
125
|
/* @__PURE__ */ jsx(Text, { color: isPausedInProgress ? "gray" : "yellow", dimColor: isPausedInProgress, children: elapsedMs !== null ? ` ${formatElapsed(elapsedMs)}` : "" })
|
|
@@ -220,7 +203,6 @@ function Column({
|
|
|
220
203
|
const hiddenBelow = Math.max(0, sortedCards.length - scrollOffset - visibleCount);
|
|
221
204
|
const borderColor = isFocused ? "yellow" : "gray";
|
|
222
205
|
const headerColor = isFocused ? "yellow" : "white";
|
|
223
|
-
const runningCount = cards.filter((c) => c.column === "in_progress").length;
|
|
224
206
|
const errorCount = cards.filter((c) => c.hasError).length;
|
|
225
207
|
return /* @__PURE__ */ jsxs2(
|
|
226
208
|
Box2,
|
|
@@ -241,7 +223,6 @@ function Column({
|
|
|
241
223
|
] }),
|
|
242
224
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "row", children: [
|
|
243
225
|
errorCount > 0 && /* @__PURE__ */ jsx2(Text2, { color: "red", bold: true, children: `\u2716${errorCount} ` }),
|
|
244
|
-
runningCount > 0 && /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: `\u25CF${runningCount} ` }),
|
|
245
226
|
/* @__PURE__ */ jsx2(Text2, { color: headerColor, children: `[${cards.length}]` })
|
|
246
227
|
] })
|
|
247
228
|
] }),
|
|
@@ -268,7 +249,7 @@ function Column({
|
|
|
268
249
|
}
|
|
269
250
|
|
|
270
251
|
// src/ui/board.tsx
|
|
271
|
-
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
252
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
272
253
|
function formatDuration(ms) {
|
|
273
254
|
const totalSeconds = Math.floor(ms / 1e3);
|
|
274
255
|
const minutes = Math.floor(totalSeconds / 60);
|
|
@@ -281,6 +262,7 @@ function Board({
|
|
|
281
262
|
labels,
|
|
282
263
|
isEmpty,
|
|
283
264
|
isWatching = false,
|
|
265
|
+
isWatchPrompt = false,
|
|
284
266
|
workComplete,
|
|
285
267
|
activeColIndex = 0,
|
|
286
268
|
activeCardIndex = 0,
|
|
@@ -303,7 +285,36 @@ function Board({
|
|
|
303
285
|
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
304
286
|
/* @__PURE__ */ jsx3(Text3, { color: "white", dimColor: true, children: "Polling every 60s for new issues with the ready label." }),
|
|
305
287
|
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
306
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: "Press
|
|
288
|
+
/* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: "Press [q] to quit" })
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
) });
|
|
292
|
+
}
|
|
293
|
+
if (isWatchPrompt) {
|
|
294
|
+
return /* @__PURE__ */ jsx3(Box3, { flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs3(
|
|
295
|
+
Box3,
|
|
296
|
+
{
|
|
297
|
+
flexDirection: "column",
|
|
298
|
+
borderStyle: "single",
|
|
299
|
+
borderColor: "green",
|
|
300
|
+
paddingX: 3,
|
|
301
|
+
paddingY: 1,
|
|
302
|
+
children: [
|
|
303
|
+
workComplete && /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
304
|
+
/* @__PURE__ */ jsx3(Text3, { color: "green", bold: true, children: `\u25C8 ${workComplete.total} issue${workComplete.total !== 1 ? "s" : ""} resolved` }),
|
|
305
|
+
/* @__PURE__ */ jsx3(Box3, { height: 1 })
|
|
306
|
+
] }),
|
|
307
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "\u25CE CONTINUE WATCHING?" }),
|
|
308
|
+
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
309
|
+
/* @__PURE__ */ jsx3(Text3, { color: "white", dimColor: true, children: "All issues have been processed." }),
|
|
310
|
+
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
311
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "gray", dimColor: true, children: [
|
|
312
|
+
"[",
|
|
313
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "w" }),
|
|
314
|
+
"] Watch / [",
|
|
315
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "q" }),
|
|
316
|
+
"] Quit"
|
|
317
|
+
] })
|
|
307
318
|
]
|
|
308
319
|
}
|
|
309
320
|
) });
|
|
@@ -655,7 +666,7 @@ function Sidebar({
|
|
|
655
666
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
656
667
|
function KanbanApp({ config }) {
|
|
657
668
|
const { exit } = useApp();
|
|
658
|
-
const { cards, isEmpty, isWatching, workComplete, modelInUse } = useKanbanState(
|
|
669
|
+
const { cards, isEmpty, isWatching, isWatchPrompt, workComplete, modelInUse } = useKanbanState(
|
|
659
670
|
config.bell ?? true
|
|
660
671
|
);
|
|
661
672
|
const { rows } = useTerminalSize();
|
|
@@ -712,6 +723,18 @@ function KanbanApp({ config }) {
|
|
|
712
723
|
setActiveCardIndex(newCardIndex);
|
|
713
724
|
}, [cards, selectedCardId, activeView]);
|
|
714
725
|
useInput2((input, key) => {
|
|
726
|
+
if (isWatchPrompt) {
|
|
727
|
+
if (input === "w") {
|
|
728
|
+
kanbanEmitter.emit("work:watch-prompt-resolved");
|
|
729
|
+
kanbanEmitter.emit("loop:resume");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (input === "q") {
|
|
733
|
+
kanbanEmitter.emit("loop:quit");
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
715
738
|
if (input === "q") {
|
|
716
739
|
process.emit("SIGINT");
|
|
717
740
|
return;
|
|
@@ -810,6 +833,7 @@ function KanbanApp({ config }) {
|
|
|
810
833
|
labels,
|
|
811
834
|
isEmpty,
|
|
812
835
|
isWatching,
|
|
836
|
+
isWatchPrompt,
|
|
813
837
|
workComplete,
|
|
814
838
|
activeColIndex,
|
|
815
839
|
activeCardIndex,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tarcisiopgs/lisa",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Autonomous issue resolver",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"loop",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"lisa": "dist/index.js"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@clack/prompts": "^1.0
|
|
30
|
+
"@clack/prompts": "^1.1.0",
|
|
31
31
|
"citty": "^0.2.1",
|
|
32
32
|
"execa": "^9.6.1",
|
|
33
33
|
"ink": "^6.8.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@vitest/coverage-v8": "^4.0.18",
|
|
44
44
|
"concurrently": "^9.2.1",
|
|
45
45
|
"husky": "^9.1.7",
|
|
46
|
-
"lint-staged": "^16.3.
|
|
46
|
+
"lint-staged": "^16.3.2",
|
|
47
47
|
"tsup": "^8.5.1",
|
|
48
48
|
"tsx": "^4.21.0",
|
|
49
49
|
"typescript": "^5.9.3",
|