@tarcisiopgs/lisa 1.35.0 → 1.37.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/dist/{chunk-BWND35E5.js → chunk-555PDPCW.js} +94 -49
- package/dist/chunk-CTMZ666K.js +30 -0
- package/dist/{chunk-R6D5VH65.js → chunk-FUIWWBDX.js} +22 -10
- package/dist/chunk-HPWL5JRW.js +113 -0
- package/dist/{chunk-6SNT7TND.js → chunk-MOQR4OY5.js} +149 -26
- package/dist/{chunk-V44FTYWZ.js → chunk-VS6R5KBO.js} +133 -146
- package/dist/{chunk-6VIN5PMW.js → chunk-XXVTKBC5.js} +1093 -542
- package/dist/{detection-H5QJR5XI.js → detection-MHQPM7O6.js} +3 -2
- package/dist/index.js +302 -33
- package/dist/{kanban-CRHTDRBU.js → kanban-ZQ67DJHP.js} +5 -3
- package/dist/{loop-N5D27JQX.js → loop-UN2MO6JN.js} +7 -4
- package/dist/{merge-CFQO7VU4.js → merge-Q3P65FEA.js} +1 -1
- package/dist/{tui-bridge-LQDYRWHY.js → tui-bridge-A5XQUJQ7.js} +11 -9
- package/package.json +1 -1
- package/dist/chunk-7JT7DTSS.js +0 -10
|
@@ -32,16 +32,10 @@ import {
|
|
|
32
32
|
resolveModels,
|
|
33
33
|
runValidationCommands,
|
|
34
34
|
runWithFallback
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-XXVTKBC5.js";
|
|
36
36
|
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
initLogFile,
|
|
40
|
-
kanbanEmitter,
|
|
41
|
-
log,
|
|
42
|
-
ok,
|
|
43
|
-
warn
|
|
44
|
-
} from "./chunk-V44FTYWZ.js";
|
|
37
|
+
kanbanEmitter
|
|
38
|
+
} from "./chunk-VS6R5KBO.js";
|
|
45
39
|
import {
|
|
46
40
|
appendRawEntry,
|
|
47
41
|
migrateGuardrails
|
|
@@ -62,8 +56,17 @@ import {
|
|
|
62
56
|
stopSpinner
|
|
63
57
|
} from "./chunk-72CYGBT4.js";
|
|
64
58
|
import {
|
|
59
|
+
divider,
|
|
60
|
+
error,
|
|
61
|
+
initLogFile,
|
|
62
|
+
log,
|
|
63
|
+
ok,
|
|
64
|
+
warn
|
|
65
|
+
} from "./chunk-HPWL5JRW.js";
|
|
66
|
+
import {
|
|
67
|
+
LisaError,
|
|
65
68
|
formatError
|
|
66
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-CTMZ666K.js";
|
|
67
70
|
import {
|
|
68
71
|
fetchPrFeedback,
|
|
69
72
|
formatPrFeedbackEntry
|
|
@@ -111,7 +114,7 @@ var configSchema = object({
|
|
|
111
114
|
base_branch: string().optional(),
|
|
112
115
|
workspace: string().optional()
|
|
113
116
|
}).passthrough();
|
|
114
|
-
var ConfigValidationError = class extends
|
|
117
|
+
var ConfigValidationError = class extends LisaError {
|
|
115
118
|
constructor(message) {
|
|
116
119
|
super(message);
|
|
117
120
|
this.name = "ConfigValidationError";
|
|
@@ -255,6 +258,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
255
258
|
const rawReviewMonitor = parsed.review_monitor;
|
|
256
259
|
const rawReactions = parsed.reactions;
|
|
257
260
|
const rawSpecCompliance = parsed.spec_compliance;
|
|
261
|
+
const rawPlanValidation = parsed.plan_validation;
|
|
258
262
|
const rawProgress = parsed.progress_comments;
|
|
259
263
|
const rawPr = parsed.pr;
|
|
260
264
|
const config = {
|
|
@@ -309,6 +313,10 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
309
313
|
max_retries: rawSpecCompliance.max_retries,
|
|
310
314
|
block_on_failure: rawSpecCompliance.block_on_failure
|
|
311
315
|
} : void 0,
|
|
316
|
+
plan_validation: rawPlanValidation ? {
|
|
317
|
+
enabled: rawPlanValidation.enabled ?? false,
|
|
318
|
+
max_iterations: rawPlanValidation.max_iterations
|
|
319
|
+
} : void 0,
|
|
312
320
|
progress_comments: rawProgress ? { enabled: rawProgress.enabled ?? false } : void 0,
|
|
313
321
|
pr: parsePrConfig(rawPr),
|
|
314
322
|
provider_options: {
|
|
@@ -852,13 +860,13 @@ function killProviderForIssue(issueId) {
|
|
|
852
860
|
}, 5e3);
|
|
853
861
|
}
|
|
854
862
|
function setupEventListeners() {
|
|
855
|
-
|
|
863
|
+
const onPause = () => {
|
|
856
864
|
_loopPaused = true;
|
|
857
|
-
}
|
|
858
|
-
|
|
865
|
+
};
|
|
866
|
+
const onResume = () => {
|
|
859
867
|
_loopPaused = false;
|
|
860
|
-
}
|
|
861
|
-
|
|
868
|
+
};
|
|
869
|
+
const onPauseProvider = (issueId) => {
|
|
862
870
|
if (issueId) {
|
|
863
871
|
const pid = activeProviderPids.get(issueId);
|
|
864
872
|
if (pid) {
|
|
@@ -878,8 +886,8 @@ function setupEventListeners() {
|
|
|
878
886
|
}
|
|
879
887
|
}
|
|
880
888
|
kanbanEmitter.emit("provider:paused", issueId);
|
|
881
|
-
}
|
|
882
|
-
|
|
889
|
+
};
|
|
890
|
+
const onResumeProvider = (issueId) => {
|
|
883
891
|
if (issueId) {
|
|
884
892
|
const pid = activeProviderPids.get(issueId);
|
|
885
893
|
if (pid && providerPausedSet.has(issueId)) {
|
|
@@ -902,8 +910,8 @@ function setupEventListeners() {
|
|
|
902
910
|
providerPausedSet.clear();
|
|
903
911
|
}
|
|
904
912
|
kanbanEmitter.emit("provider:resumed", issueId);
|
|
905
|
-
}
|
|
906
|
-
|
|
913
|
+
};
|
|
914
|
+
const onKill = (issueId) => {
|
|
907
915
|
if (issueId) {
|
|
908
916
|
userKilledSet.add(issueId);
|
|
909
917
|
killProviderForIssue(issueId);
|
|
@@ -914,8 +922,8 @@ function setupEventListeners() {
|
|
|
914
922
|
killProviderForIssue(firstId);
|
|
915
923
|
}
|
|
916
924
|
}
|
|
917
|
-
}
|
|
918
|
-
|
|
925
|
+
};
|
|
926
|
+
const onSkip = (issueId) => {
|
|
919
927
|
if (issueId) {
|
|
920
928
|
userSkippedSet.add(issueId);
|
|
921
929
|
killProviderForIssue(issueId);
|
|
@@ -926,15 +934,33 @@ function setupEventListeners() {
|
|
|
926
934
|
killProviderForIssue(firstId);
|
|
927
935
|
}
|
|
928
936
|
}
|
|
929
|
-
}
|
|
930
|
-
|
|
937
|
+
};
|
|
938
|
+
const onRun = () => {
|
|
931
939
|
resolveIdle();
|
|
932
|
-
}
|
|
933
|
-
|
|
940
|
+
};
|
|
941
|
+
const onQuit = () => {
|
|
934
942
|
_userQuitFromWatchPrompt = true;
|
|
935
943
|
setShuttingDown(true);
|
|
936
944
|
resolveIdle();
|
|
937
|
-
}
|
|
945
|
+
};
|
|
946
|
+
kanbanEmitter.on("loop:pause", onPause);
|
|
947
|
+
kanbanEmitter.on("loop:resume", onResume);
|
|
948
|
+
kanbanEmitter.on("loop:pause-provider", onPauseProvider);
|
|
949
|
+
kanbanEmitter.on("loop:resume-provider", onResumeProvider);
|
|
950
|
+
kanbanEmitter.on("loop:kill", onKill);
|
|
951
|
+
kanbanEmitter.on("loop:skip", onSkip);
|
|
952
|
+
kanbanEmitter.on("loop:run", onRun);
|
|
953
|
+
kanbanEmitter.on("loop:quit", onQuit);
|
|
954
|
+
return () => {
|
|
955
|
+
kanbanEmitter.off("loop:pause", onPause);
|
|
956
|
+
kanbanEmitter.off("loop:resume", onResume);
|
|
957
|
+
kanbanEmitter.off("loop:pause-provider", onPauseProvider);
|
|
958
|
+
kanbanEmitter.off("loop:resume-provider", onResumeProvider);
|
|
959
|
+
kanbanEmitter.off("loop:kill", onKill);
|
|
960
|
+
kanbanEmitter.off("loop:skip", onSkip);
|
|
961
|
+
kanbanEmitter.off("loop:run", onRun);
|
|
962
|
+
kanbanEmitter.off("loop:quit", onQuit);
|
|
963
|
+
};
|
|
938
964
|
}
|
|
939
965
|
|
|
940
966
|
// src/session/reconciliation.ts
|
|
@@ -1798,7 +1824,14 @@ var WORKTREES_DIR = ".worktrees";
|
|
|
1798
1824
|
function generateBranchName(issueId, title) {
|
|
1799
1825
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").substring(0, 40).replace(/^-|-$/g, "");
|
|
1800
1826
|
const safeId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
1801
|
-
|
|
1827
|
+
let branch = `feat/${safeId}-${slug}`;
|
|
1828
|
+
branch = branch.replace(/\.\./g, "");
|
|
1829
|
+
branch = branch.replace(/@\{/g, "");
|
|
1830
|
+
branch = branch.replace(/^[./]+/, "").replace(/[./]+$/, "");
|
|
1831
|
+
if (!branch || branch === "feat/" || branch === "feat") {
|
|
1832
|
+
branch = `feat/${safeId}-${Date.now()}`;
|
|
1833
|
+
}
|
|
1834
|
+
return branch;
|
|
1802
1835
|
}
|
|
1803
1836
|
async function cleanupOrphanedWorktree(repoRoot, branchName) {
|
|
1804
1837
|
const { stdout: branchList } = await execa2("git", ["branch", "--list", branchName], {
|
|
@@ -2078,28 +2111,11 @@ async function monitorCi(branch, config, issue, models, cwd, logFile, workspace,
|
|
|
2078
2111
|
// src/session/hooks.ts
|
|
2079
2112
|
import { spawn as spawn2 } from "child_process";
|
|
2080
2113
|
var DEFAULT_HOOK_TIMEOUT = 6e4;
|
|
2081
|
-
var
|
|
2082
|
-
/^GITHUB_TOKEN$/,
|
|
2083
|
-
/^GH_TOKEN$/,
|
|
2084
|
-
/^GITLAB_TOKEN$/,
|
|
2085
|
-
/^BITBUCKET_.*(TOKEN|PASSWORD|SECRET)/i,
|
|
2086
|
-
/^LINEAR_API_KEY$/,
|
|
2087
|
-
/^TRELLO_(API_KEY|TOKEN)$/,
|
|
2088
|
-
/^PLANE_API_TOKEN$/,
|
|
2089
|
-
/^SHORTCUT_API_TOKEN$/,
|
|
2090
|
-
/^JIRA_(API_TOKEN|TOKEN)$/,
|
|
2091
|
-
/^AWS_(SECRET|SESSION).*KEY/i,
|
|
2092
|
-
/^ANTHROPIC_API_KEY$/,
|
|
2093
|
-
/^OPENAI_API_KEY$/,
|
|
2094
|
-
/^GOOGLE_API_KEY$/,
|
|
2095
|
-
/^GEMINI_API_KEY$/,
|
|
2096
|
-
/^NPM_TOKEN$/,
|
|
2097
|
-
/^PYPI_TOKEN$/
|
|
2098
|
-
];
|
|
2114
|
+
var SAFE_ENV_PATTERN = /^(PATH|HOME|USER|LANG|LANGUAGE|LC_.+|SHELL|TERM|TERM_PROGRAM|PWD|OLDPWD|TMPDIR|TMP|TEMP|EDITOR|VISUAL|CI|LISA_.+|NODE_ENV|NODE_PATH|NPM_.+|PNPM_.+|YARN_.+|XDG_.+|COLORTERM|FORCE_COLOR|NO_COLOR|COLUMNS|LINES|HOSTNAME|LOGNAME|SHLVL)$/;
|
|
2099
2115
|
function sanitizeEnv(env) {
|
|
2100
2116
|
const result = {};
|
|
2101
2117
|
for (const [key, value] of Object.entries(env)) {
|
|
2102
|
-
if (value !== void 0 &&
|
|
2118
|
+
if (value !== void 0 && SAFE_ENV_PATTERN.test(key)) {
|
|
2103
2119
|
result[key] = value;
|
|
2104
2120
|
}
|
|
2105
2121
|
}
|
|
@@ -3428,6 +3444,8 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
|
|
|
3428
3444
|
let consecutiveExhaustions = 0;
|
|
3429
3445
|
const MAX_CONSECUTIVE_EXHAUSTIONS = 3;
|
|
3430
3446
|
const slotPool = Array.from({ length: concurrency }, (_, i) => i);
|
|
3447
|
+
let watchStartTime = null;
|
|
3448
|
+
const watchTimeout = config.loop.watch_timeout ?? 0;
|
|
3431
3449
|
const processIssue = async (issue, session, slotIndex) => {
|
|
3432
3450
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
3433
3451
|
const logFile = resolve7(getLogsDir(workspace), `session_${session}_${timestamp}.log`);
|
|
@@ -3456,8 +3474,12 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
|
|
|
3456
3474
|
} catch (err) {
|
|
3457
3475
|
error(`Unhandled error in session for ${issue.id}: ${formatError(err)}`);
|
|
3458
3476
|
await revertIssueStatus(issue, source, config);
|
|
3459
|
-
|
|
3477
|
+
userKilledSet.delete(issue.id);
|
|
3478
|
+
userSkippedSet.delete(issue.id);
|
|
3479
|
+
providerPausedSet.delete(issue.id);
|
|
3460
3480
|
activeProviderPids.delete(issue.id);
|
|
3481
|
+
activeCleanups.delete(issue.id);
|
|
3482
|
+
claimedIssueIds.delete(issue.id);
|
|
3461
3483
|
if (config.bell !== false) notify(2);
|
|
3462
3484
|
return;
|
|
3463
3485
|
}
|
|
@@ -3520,10 +3542,20 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
|
|
|
3520
3542
|
}
|
|
3521
3543
|
if (issue && claimedIssueIds.has(issue.id)) {
|
|
3522
3544
|
log(`Issue ${issue.id} already claimed by another worker. Skipping.`);
|
|
3545
|
+
await sleep(Math.max(config.loop.cooldown * 1e3, 2e3));
|
|
3523
3546
|
break;
|
|
3524
3547
|
}
|
|
3525
3548
|
if (!issue) {
|
|
3526
3549
|
if (opts.watch) {
|
|
3550
|
+
if (watchStartTime === null) watchStartTime = Date.now();
|
|
3551
|
+
if (watchTimeout > 0) {
|
|
3552
|
+
const elapsed = (Date.now() - watchStartTime) / 1e3;
|
|
3553
|
+
if (elapsed >= watchTimeout) {
|
|
3554
|
+
ok(`Watch mode timeout reached (${watchTimeout}s). Stopping.`);
|
|
3555
|
+
noMoreIssues = true;
|
|
3556
|
+
break;
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3527
3559
|
if (activeWorkers.size === 0) {
|
|
3528
3560
|
if (completedCount > 0) {
|
|
3529
3561
|
ok(`All issues resolved. Prompting user to continue watching...`);
|
|
@@ -3561,6 +3593,7 @@ async function runConcurrentLoop(config, source, models, workspace, opts) {
|
|
|
3561
3593
|
break;
|
|
3562
3594
|
}
|
|
3563
3595
|
kanbanEmitter.emit("work:resumed");
|
|
3596
|
+
watchStartTime = null;
|
|
3564
3597
|
sessionCounter = tentativeSession;
|
|
3565
3598
|
const session = sessionCounter;
|
|
3566
3599
|
claimedIssueIds.add(issue.id);
|
|
@@ -3924,6 +3957,8 @@ async function runSequentialLoop(config, source, models, workspace, opts) {
|
|
|
3924
3957
|
const MAX_CONSECUTIVE_FETCH_ERRORS = 3;
|
|
3925
3958
|
let consecutiveExhaustions = 0;
|
|
3926
3959
|
const MAX_CONSECUTIVE_EXHAUSTIONS = 3;
|
|
3960
|
+
let watchStartTime = null;
|
|
3961
|
+
const watchTimeout = config.loop.watch_timeout ?? 0;
|
|
3927
3962
|
while (true) {
|
|
3928
3963
|
session++;
|
|
3929
3964
|
if (opts.limit > 0 && session > opts.limit) {
|
|
@@ -3985,6 +4020,14 @@ async function runSequentialLoop(config, source, models, workspace, opts) {
|
|
|
3985
4020
|
break;
|
|
3986
4021
|
}
|
|
3987
4022
|
if (opts.watch) {
|
|
4023
|
+
if (watchStartTime === null) watchStartTime = Date.now();
|
|
4024
|
+
if (watchTimeout > 0) {
|
|
4025
|
+
const elapsed = (Date.now() - watchStartTime) / 1e3;
|
|
4026
|
+
if (elapsed >= watchTimeout) {
|
|
4027
|
+
ok(`Watch mode timeout reached (${watchTimeout}s). Stopping.`);
|
|
4028
|
+
break;
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
3988
4031
|
if (completedCount > 0) {
|
|
3989
4032
|
ok(`All issues resolved. Prompting user to continue watching...`);
|
|
3990
4033
|
kanbanEmitter.emit("work:watch-prompt");
|
|
@@ -4024,6 +4067,7 @@ async function runSequentialLoop(config, source, models, workspace, opts) {
|
|
|
4024
4067
|
continue;
|
|
4025
4068
|
}
|
|
4026
4069
|
kanbanEmitter.emit("work:resumed");
|
|
4070
|
+
watchStartTime = null;
|
|
4027
4071
|
ok(`Picked up: ${issue.id} \u2014 ${issue.title}`);
|
|
4028
4072
|
setTitle(`Lisa \u2014 ${issue.id}`);
|
|
4029
4073
|
const cachedPrUrls = loadPrUrls(workspace, issue.id);
|
|
@@ -4246,7 +4290,7 @@ async function runDemoLoop() {
|
|
|
4246
4290
|
}
|
|
4247
4291
|
|
|
4248
4292
|
// src/loop/index.ts
|
|
4249
|
-
setupEventListeners();
|
|
4293
|
+
var cleanupEventListeners = setupEventListeners();
|
|
4250
4294
|
async function runLoop(config, opts) {
|
|
4251
4295
|
const source = createSource(config.source);
|
|
4252
4296
|
const models = resolveModels(config);
|
|
@@ -4318,5 +4362,6 @@ export {
|
|
|
4318
4362
|
checkoutBaseBranches,
|
|
4319
4363
|
listSessionRecords,
|
|
4320
4364
|
runDemoLoop,
|
|
4365
|
+
cleanupEventListeners,
|
|
4321
4366
|
runLoop
|
|
4322
4367
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
};
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
formatError,
|
|
28
|
+
LisaError,
|
|
29
|
+
SourceError
|
|
30
|
+
};
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
isGhCliAvailable
|
|
4
4
|
} from "./chunk-YMV4CBQE.js";
|
|
5
|
+
import {
|
|
6
|
+
verbose
|
|
7
|
+
} from "./chunk-HPWL5JRW.js";
|
|
5
8
|
import {
|
|
6
9
|
formatError
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-CTMZ666K.js";
|
|
8
11
|
|
|
9
12
|
// src/cli/detection.ts
|
|
10
13
|
import { execSync } from "child_process";
|
|
@@ -17,7 +20,8 @@ function getVersion() {
|
|
|
17
20
|
const pkgPath = resolvePath(new URL(".", import.meta.url).pathname, "../package.json");
|
|
18
21
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
19
22
|
return pkg.version;
|
|
20
|
-
} catch {
|
|
23
|
+
} catch (err) {
|
|
24
|
+
verbose(`Failed to read package.json version: ${formatError(err)}`);
|
|
21
25
|
return "0.0.0";
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -32,7 +36,8 @@ async function isCursorFreePlan() {
|
|
|
32
36
|
try {
|
|
33
37
|
execSync(`${b} --version`, { stdio: "ignore" });
|
|
34
38
|
return true;
|
|
35
|
-
} catch {
|
|
39
|
+
} catch (err) {
|
|
40
|
+
verbose(`Cursor binary "${b}" not found: ${formatError(err)}`);
|
|
36
41
|
return false;
|
|
37
42
|
}
|
|
38
43
|
});
|
|
@@ -49,7 +54,8 @@ async function isCursorFreePlan() {
|
|
|
49
54
|
} finally {
|
|
50
55
|
try {
|
|
51
56
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
52
|
-
} catch {
|
|
57
|
+
} catch (err) {
|
|
58
|
+
verbose(`Failed to clean up temp dir ${tmpDir}: ${formatError(err)}`);
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
}
|
|
@@ -75,7 +81,8 @@ function fetchCursorModels() {
|
|
|
75
81
|
try {
|
|
76
82
|
execSync(`${b} --version`, { stdio: "ignore" });
|
|
77
83
|
return true;
|
|
78
|
-
} catch {
|
|
84
|
+
} catch (err) {
|
|
85
|
+
verbose(`Cursor binary "${b}" not available: ${formatError(err)}`);
|
|
79
86
|
return false;
|
|
80
87
|
}
|
|
81
88
|
});
|
|
@@ -85,7 +92,8 @@ function fetchCursorModels() {
|
|
|
85
92
|
const all = clean.split("\n").map((l) => l.trim()).filter((l) => l.includes(" - ")).map((l) => (l.split(" - ")[0] ?? "").trim()).filter(Boolean);
|
|
86
93
|
const filtered = CURSOR_PREFERRED_MODELS.filter((m) => all.includes(m));
|
|
87
94
|
return filtered.length > 0 ? filtered : CURSOR_PREFERRED_MODELS;
|
|
88
|
-
} catch {
|
|
95
|
+
} catch (err) {
|
|
96
|
+
verbose(`Failed to fetch Cursor models: ${formatError(err)}`);
|
|
89
97
|
return CURSOR_PREFERRED_MODELS;
|
|
90
98
|
}
|
|
91
99
|
}
|
|
@@ -98,7 +106,8 @@ function fetchOpenCodeModels() {
|
|
|
98
106
|
const raw = execSync("opencode models", { encoding: "utf-8", timeout: 1e4 });
|
|
99
107
|
const clean = raw.replace(/\x1b\[[0-9;]*[mGKHFA-Z]/g, "");
|
|
100
108
|
return clean.split("\n").map((l) => l.trim()).filter((m) => /^[a-z0-9][\w.-]*\/.+/i.test(m));
|
|
101
|
-
} catch {
|
|
109
|
+
} catch (err) {
|
|
110
|
+
verbose(`Failed to fetch OpenCode models: ${formatError(err)}`);
|
|
102
111
|
return [];
|
|
103
112
|
}
|
|
104
113
|
}
|
|
@@ -120,7 +129,8 @@ async function detectPlatform() {
|
|
|
120
129
|
const platformLabel = detectedPlatform === "cli" || detectedPlatform === "token" ? "GitHub" : detectedPlatform === "gitlab" ? "GitLab" : "Bitbucket";
|
|
121
130
|
clack.log.info(`Detected ${platformLabel} remote`);
|
|
122
131
|
}
|
|
123
|
-
} catch {
|
|
132
|
+
} catch (err) {
|
|
133
|
+
verbose(`Platform detection from git remote failed: ${formatError(err)}`);
|
|
124
134
|
}
|
|
125
135
|
const initialValue = detectedPlatform ?? "cli";
|
|
126
136
|
const selected = await clack.select({
|
|
@@ -210,7 +220,8 @@ function detectDefaultBranch(repoPath) {
|
|
|
210
220
|
encoding: "utf-8"
|
|
211
221
|
}).trim();
|
|
212
222
|
return ref.replace("origin/", "");
|
|
213
|
-
} catch {
|
|
223
|
+
} catch (err) {
|
|
224
|
+
verbose(`Failed to detect default branch, falling back to "main": ${formatError(err)}`);
|
|
214
225
|
return "main";
|
|
215
226
|
}
|
|
216
227
|
}
|
|
@@ -219,7 +230,8 @@ function getGitRepoName(repoPath) {
|
|
|
219
230
|
const url = execSync("git remote get-url origin", { cwd: repoPath, encoding: "utf-8" }).trim();
|
|
220
231
|
const match = url.match(/\/([^/]+?)(?:\.git)?$/) ?? url.match(/:([^/]+?)(?:\.git)?$/);
|
|
221
232
|
return match?.[1] ?? null;
|
|
222
|
-
} catch {
|
|
233
|
+
} catch (err) {
|
|
234
|
+
verbose(`Failed to get git repo name for ${repoPath}: ${formatError(err)}`);
|
|
223
235
|
return null;
|
|
224
236
|
}
|
|
225
237
|
}
|
|
@@ -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
|
+
};
|