@tarcisiopgs/lisa 1.7.7 → 1.8.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 +1 -0
- package/dist/chunk-YZKNBQN6.js +0 -0
- package/dist/index.js +235 -92
- package/dist/{kanban-5C3WZIKC.js → kanban-PD2F4KWT.js} +118 -29
- package/package.json +59 -61
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ Lisa follows a deterministic pipeline:
|
|
|
82
82
|
| Cursor Agent | `cursor` | `agent` / `cursor-agent` |
|
|
83
83
|
| Goose | `goose` | `goose` |
|
|
84
84
|
| Aider | `aider` | `aider` |
|
|
85
|
+
| OpenAI Codex | `codex` | `codex` |
|
|
85
86
|
|
|
86
87
|
At least one provider must be installed and available in your PATH.
|
|
87
88
|
|
package/dist/chunk-YZKNBQN6.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
} from "./chunk-YZKNBQN6.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
12
|
-
import { execSync as
|
|
12
|
+
import { execSync as execSync9 } from "child_process";
|
|
13
13
|
import { existsSync as existsSync7, readdirSync, readFileSync as readFileSync6 } from "fs";
|
|
14
|
-
import { tmpdir as
|
|
15
|
-
import { join as
|
|
14
|
+
import { tmpdir as tmpdir9 } from "os";
|
|
15
|
+
import { join as join13, resolve as resolvePath } from "path";
|
|
16
16
|
import * as clack from "@clack/prompts";
|
|
17
17
|
import { defineCommand, runMain } from "citty";
|
|
18
18
|
import pc2 from "picocolors";
|
|
@@ -171,6 +171,30 @@ function mergeWithFlags(config2, flags) {
|
|
|
171
171
|
|
|
172
172
|
// src/git/github.ts
|
|
173
173
|
import { execa } from "execa";
|
|
174
|
+
|
|
175
|
+
// src/git/pr-body.ts
|
|
176
|
+
var PROVIDER_ATTRIBUTION_RE = /claude\.ai|claude\s+code|gemini\s+cli|openai\s+codex|\bgoose\b|\baider\b|github\s+copilot|cursor\s+agent|\bopencode\b/i;
|
|
177
|
+
var AI_COAUTHOR_RE = /co-authored-by:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)/i;
|
|
178
|
+
function stripProviderAttribution(body) {
|
|
179
|
+
let result = body;
|
|
180
|
+
while (true) {
|
|
181
|
+
const sepIndex = result.lastIndexOf("\n---");
|
|
182
|
+
if (sepIndex === -1) break;
|
|
183
|
+
const section = result.slice(sepIndex);
|
|
184
|
+
if (PROVIDER_ATTRIBUTION_RE.test(section) || AI_COAUTHOR_RE.test(section)) {
|
|
185
|
+
result = result.slice(0, sepIndex).trimEnd();
|
|
186
|
+
} else {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
result = result.replace(
|
|
191
|
+
/\n+Co-Authored-By:[^\n]*(anthropic|claude|gemini|openai|codex|goose|aider|copilot|cursor|google)[^\n]*/gi,
|
|
192
|
+
""
|
|
193
|
+
);
|
|
194
|
+
return result.trimEnd();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/git/github.ts
|
|
174
198
|
async function isGhCliAvailable() {
|
|
175
199
|
try {
|
|
176
200
|
await execa("gh", ["auth", "status"]);
|
|
@@ -186,7 +210,8 @@ var PROVIDER_DISPLAY_NAMES = {
|
|
|
186
210
|
copilot: "GitHub Copilot CLI",
|
|
187
211
|
cursor: "Cursor Agent",
|
|
188
212
|
goose: "Goose",
|
|
189
|
-
aider: "Aider"
|
|
213
|
+
aider: "Aider",
|
|
214
|
+
codex: "OpenAI Codex"
|
|
190
215
|
};
|
|
191
216
|
function formatProviderName(providerUsed) {
|
|
192
217
|
const providerKey = providerUsed.split("/")[0] ?? providerUsed;
|
|
@@ -201,7 +226,7 @@ async function appendPrAttribution(prUrl, providerUsed) {
|
|
|
201
226
|
|
|
202
227
|
---
|
|
203
228
|
\u{1F916} Resolved by [lisa](https://github.com/tarcisiopgs/lisa) using **${providerName}**`;
|
|
204
|
-
const newBody = (body ?? "") + attribution;
|
|
229
|
+
const newBody = stripProviderAttribution(body ?? "") + attribution;
|
|
205
230
|
await execa("gh", ["pr", "edit", prUrl, "--body", newBody]);
|
|
206
231
|
} catch {
|
|
207
232
|
}
|
|
@@ -265,6 +290,28 @@ function ensureWorktreeGitignore(repoRoot) {
|
|
|
265
290
|
`);
|
|
266
291
|
}
|
|
267
292
|
}
|
|
293
|
+
var LOGS_GITIGNORE_ENTRY = ".lisa/logs/*";
|
|
294
|
+
function ensureLogsGitignore(repoRoot) {
|
|
295
|
+
if (!existsSync2(join(repoRoot, ".git"))) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
const gitignorePath = join(repoRoot, ".gitignore");
|
|
299
|
+
if (!existsSync2(gitignorePath)) {
|
|
300
|
+
appendFileSync(gitignorePath, `# Lisa
|
|
301
|
+
${LOGS_GITIGNORE_ENTRY}
|
|
302
|
+
`);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
const content = readFileSync2(gitignorePath, "utf-8");
|
|
306
|
+
if (content.split("\n").some((line) => line.trim() === LOGS_GITIGNORE_ENTRY)) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
310
|
+
appendFileSync(gitignorePath, `${separator}# Lisa
|
|
311
|
+
${LOGS_GITIGNORE_ENTRY}
|
|
312
|
+
`);
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
268
315
|
function determineRepoPath(repos, issue2, workspace) {
|
|
269
316
|
if (repos.length === 0) return void 0;
|
|
270
317
|
if (issue2.repo) {
|
|
@@ -281,8 +328,8 @@ function determineRepoPath(repos, issue2, workspace) {
|
|
|
281
328
|
}
|
|
282
329
|
|
|
283
330
|
// src/loop.ts
|
|
284
|
-
import { appendFileSync as
|
|
285
|
-
import { join as
|
|
331
|
+
import { appendFileSync as appendFileSync11, existsSync as existsSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync9 } from "fs";
|
|
332
|
+
import { join as join12, resolve as resolve5 } from "path";
|
|
286
333
|
import { execa as execa3 } from "execa";
|
|
287
334
|
|
|
288
335
|
// src/output/logger.ts
|
|
@@ -1129,16 +1176,16 @@ var ClaudeProvider = class {
|
|
|
1129
1176
|
}
|
|
1130
1177
|
};
|
|
1131
1178
|
|
|
1132
|
-
// src/providers/
|
|
1179
|
+
// src/providers/codex.ts
|
|
1133
1180
|
import { execSync as execSync3, spawn as spawn3 } from "child_process";
|
|
1134
1181
|
import { appendFileSync as appendFileSync5, mkdtempSync as mkdtempSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
1135
1182
|
import { tmpdir as tmpdir3 } from "os";
|
|
1136
1183
|
import { join as join6 } from "path";
|
|
1137
|
-
var
|
|
1138
|
-
name = "
|
|
1184
|
+
var CodexProvider = class {
|
|
1185
|
+
name = "codex";
|
|
1139
1186
|
async isAvailable() {
|
|
1140
1187
|
try {
|
|
1141
|
-
execSync3("
|
|
1188
|
+
execSync3("codex --version", { stdio: "ignore" });
|
|
1142
1189
|
return true;
|
|
1143
1190
|
} catch {
|
|
1144
1191
|
return false;
|
|
@@ -1150,9 +1197,12 @@ var CopilotProvider = class {
|
|
|
1150
1197
|
const promptFile = join6(tmpDir, "prompt.md");
|
|
1151
1198
|
writeFileSync6(promptFile, prompt, "utf-8");
|
|
1152
1199
|
try {
|
|
1153
|
-
const
|
|
1200
|
+
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1201
|
+
const cmd = `codex exec --dangerously-bypass-approvals-and-sandbox --ephemeral ${modelFlag} "$(cat '${promptFile}')"`;
|
|
1202
|
+
const proc = spawn3("sh", ["-c", cmd], {
|
|
1154
1203
|
cwd: opts.cwd,
|
|
1155
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1204
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1205
|
+
env: { ...process.env, CODEX_QUIET_MODE: "1" }
|
|
1156
1206
|
});
|
|
1157
1207
|
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
1158
1208
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
@@ -1206,15 +1256,92 @@ var CopilotProvider = class {
|
|
|
1206
1256
|
}
|
|
1207
1257
|
};
|
|
1208
1258
|
|
|
1209
|
-
// src/providers/
|
|
1259
|
+
// src/providers/copilot.ts
|
|
1210
1260
|
import { execSync as execSync4, spawn as spawn4 } from "child_process";
|
|
1211
1261
|
import { appendFileSync as appendFileSync6, mkdtempSync as mkdtempSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
1212
1262
|
import { tmpdir as tmpdir4 } from "os";
|
|
1213
1263
|
import { join as join7 } from "path";
|
|
1264
|
+
var CopilotProvider = class {
|
|
1265
|
+
name = "copilot";
|
|
1266
|
+
async isAvailable() {
|
|
1267
|
+
try {
|
|
1268
|
+
execSync4("copilot version", { stdio: "ignore" });
|
|
1269
|
+
return true;
|
|
1270
|
+
} catch {
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
async run(prompt, opts) {
|
|
1275
|
+
const start = Date.now();
|
|
1276
|
+
const tmpDir = mkdtempSync4(join7(tmpdir4(), "lisa-"));
|
|
1277
|
+
const promptFile = join7(tmpDir, "prompt.md");
|
|
1278
|
+
writeFileSync7(promptFile, prompt, "utf-8");
|
|
1279
|
+
try {
|
|
1280
|
+
const proc = spawn4("sh", ["-c", `copilot --allow-all -p "$(cat '${promptFile}')"`], {
|
|
1281
|
+
cwd: opts.cwd,
|
|
1282
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1283
|
+
});
|
|
1284
|
+
if (proc.pid) opts.onProcess?.(proc.pid);
|
|
1285
|
+
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
1286
|
+
const chunks = [];
|
|
1287
|
+
proc.stdout.on("data", (chunk) => {
|
|
1288
|
+
const text2 = chunk.toString();
|
|
1289
|
+
if (getOutputMode() !== "tui") process.stdout.write(text2);
|
|
1290
|
+
if (opts.issueId) {
|
|
1291
|
+
kanbanEmitter.emit("issue:output", opts.issueId, text2);
|
|
1292
|
+
}
|
|
1293
|
+
chunks.push(text2);
|
|
1294
|
+
try {
|
|
1295
|
+
appendFileSync6(opts.logFile, text2);
|
|
1296
|
+
} catch {
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
proc.stderr.on("data", (chunk) => {
|
|
1300
|
+
const text2 = chunk.toString();
|
|
1301
|
+
if (getOutputMode() !== "tui") process.stderr.write(text2);
|
|
1302
|
+
try {
|
|
1303
|
+
appendFileSync6(opts.logFile, text2);
|
|
1304
|
+
} catch {
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
const exitCode = await new Promise((resolve6) => {
|
|
1308
|
+
proc.on("close", (code) => {
|
|
1309
|
+
overseer?.stop();
|
|
1310
|
+
resolve6(code ?? 1);
|
|
1311
|
+
});
|
|
1312
|
+
});
|
|
1313
|
+
if (overseer?.wasKilled()) {
|
|
1314
|
+
chunks.push(STUCK_MESSAGE);
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
success: exitCode === 0 && !overseer?.wasKilled(),
|
|
1318
|
+
output: chunks.join(""),
|
|
1319
|
+
duration: Date.now() - start
|
|
1320
|
+
};
|
|
1321
|
+
} catch (err) {
|
|
1322
|
+
return {
|
|
1323
|
+
success: false,
|
|
1324
|
+
output: err instanceof Error ? err.message : String(err),
|
|
1325
|
+
duration: Date.now() - start
|
|
1326
|
+
};
|
|
1327
|
+
} finally {
|
|
1328
|
+
try {
|
|
1329
|
+
unlinkSync4(promptFile);
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1336
|
+
// src/providers/cursor.ts
|
|
1337
|
+
import { execSync as execSync5, spawn as spawn5 } from "child_process";
|
|
1338
|
+
import { appendFileSync as appendFileSync7, mkdtempSync as mkdtempSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
1339
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
1340
|
+
import { join as join8 } from "path";
|
|
1214
1341
|
function findCursorBinary() {
|
|
1215
1342
|
for (const bin of ["agent", "cursor-agent"]) {
|
|
1216
1343
|
try {
|
|
1217
|
-
|
|
1344
|
+
execSync5(`${bin} --version`, { stdio: "ignore" });
|
|
1218
1345
|
return bin;
|
|
1219
1346
|
} catch {
|
|
1220
1347
|
}
|
|
@@ -1236,12 +1363,12 @@ var CursorProvider = class {
|
|
|
1236
1363
|
duration: Date.now() - start
|
|
1237
1364
|
};
|
|
1238
1365
|
}
|
|
1239
|
-
const tmpDir =
|
|
1240
|
-
const promptFile =
|
|
1241
|
-
|
|
1366
|
+
const tmpDir = mkdtempSync5(join8(tmpdir5(), "lisa-"));
|
|
1367
|
+
const promptFile = join8(tmpDir, "prompt.md");
|
|
1368
|
+
writeFileSync8(promptFile, prompt, "utf-8");
|
|
1242
1369
|
try {
|
|
1243
1370
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1244
|
-
const proc =
|
|
1371
|
+
const proc = spawn5(
|
|
1245
1372
|
"sh",
|
|
1246
1373
|
["-c", `${bin} -p "$(cat '${promptFile}')" --output-format text --force ${modelFlag}`],
|
|
1247
1374
|
{
|
|
@@ -1260,7 +1387,7 @@ var CursorProvider = class {
|
|
|
1260
1387
|
}
|
|
1261
1388
|
chunks.push(text2);
|
|
1262
1389
|
try {
|
|
1263
|
-
|
|
1390
|
+
appendFileSync7(opts.logFile, text2);
|
|
1264
1391
|
} catch {
|
|
1265
1392
|
}
|
|
1266
1393
|
});
|
|
@@ -1268,7 +1395,7 @@ var CursorProvider = class {
|
|
|
1268
1395
|
const text2 = chunk.toString();
|
|
1269
1396
|
if (getOutputMode() !== "tui") process.stderr.write(text2);
|
|
1270
1397
|
try {
|
|
1271
|
-
|
|
1398
|
+
appendFileSync7(opts.logFile, text2);
|
|
1272
1399
|
} catch {
|
|
1273
1400
|
}
|
|
1274
1401
|
});
|
|
@@ -1294,7 +1421,7 @@ var CursorProvider = class {
|
|
|
1294
1421
|
};
|
|
1295
1422
|
} finally {
|
|
1296
1423
|
try {
|
|
1297
|
-
|
|
1424
|
+
unlinkSync5(promptFile);
|
|
1298
1425
|
} catch {
|
|
1299
1426
|
}
|
|
1300
1427
|
}
|
|
@@ -1302,15 +1429,15 @@ var CursorProvider = class {
|
|
|
1302
1429
|
};
|
|
1303
1430
|
|
|
1304
1431
|
// src/providers/gemini.ts
|
|
1305
|
-
import { execSync as
|
|
1306
|
-
import { appendFileSync as
|
|
1307
|
-
import { tmpdir as
|
|
1308
|
-
import { join as
|
|
1432
|
+
import { execSync as execSync6, spawn as spawn6 } from "child_process";
|
|
1433
|
+
import { appendFileSync as appendFileSync8, mkdtempSync as mkdtempSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
1434
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
1435
|
+
import { join as join9 } from "path";
|
|
1309
1436
|
var GeminiProvider = class {
|
|
1310
1437
|
name = "gemini";
|
|
1311
1438
|
async isAvailable() {
|
|
1312
1439
|
try {
|
|
1313
|
-
|
|
1440
|
+
execSync6("gemini --version", { stdio: "ignore" });
|
|
1314
1441
|
return true;
|
|
1315
1442
|
} catch {
|
|
1316
1443
|
return false;
|
|
@@ -1318,12 +1445,12 @@ var GeminiProvider = class {
|
|
|
1318
1445
|
}
|
|
1319
1446
|
async run(prompt, opts) {
|
|
1320
1447
|
const start = Date.now();
|
|
1321
|
-
const tmpDir =
|
|
1322
|
-
const promptFile =
|
|
1323
|
-
|
|
1448
|
+
const tmpDir = mkdtempSync6(join9(tmpdir6(), "lisa-"));
|
|
1449
|
+
const promptFile = join9(tmpDir, "prompt.md");
|
|
1450
|
+
writeFileSync9(promptFile, prompt, "utf-8");
|
|
1324
1451
|
try {
|
|
1325
1452
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1326
|
-
const proc =
|
|
1453
|
+
const proc = spawn6("sh", ["-c", `gemini --yolo ${modelFlag} -p "$(cat '${promptFile}')"`], {
|
|
1327
1454
|
cwd: opts.cwd,
|
|
1328
1455
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1329
1456
|
});
|
|
@@ -1338,7 +1465,7 @@ var GeminiProvider = class {
|
|
|
1338
1465
|
}
|
|
1339
1466
|
chunks.push(text2);
|
|
1340
1467
|
try {
|
|
1341
|
-
|
|
1468
|
+
appendFileSync8(opts.logFile, text2);
|
|
1342
1469
|
} catch {
|
|
1343
1470
|
}
|
|
1344
1471
|
});
|
|
@@ -1346,7 +1473,7 @@ var GeminiProvider = class {
|
|
|
1346
1473
|
const text2 = chunk.toString();
|
|
1347
1474
|
if (getOutputMode() !== "tui") process.stderr.write(text2);
|
|
1348
1475
|
try {
|
|
1349
|
-
|
|
1476
|
+
appendFileSync8(opts.logFile, text2);
|
|
1350
1477
|
} catch {
|
|
1351
1478
|
}
|
|
1352
1479
|
});
|
|
@@ -1372,7 +1499,7 @@ var GeminiProvider = class {
|
|
|
1372
1499
|
};
|
|
1373
1500
|
} finally {
|
|
1374
1501
|
try {
|
|
1375
|
-
|
|
1502
|
+
unlinkSync6(promptFile);
|
|
1376
1503
|
} catch {
|
|
1377
1504
|
}
|
|
1378
1505
|
}
|
|
@@ -1380,15 +1507,15 @@ var GeminiProvider = class {
|
|
|
1380
1507
|
};
|
|
1381
1508
|
|
|
1382
1509
|
// src/providers/goose.ts
|
|
1383
|
-
import { execSync as
|
|
1384
|
-
import { appendFileSync as
|
|
1385
|
-
import { tmpdir as
|
|
1386
|
-
import { join as
|
|
1510
|
+
import { execSync as execSync7, spawn as spawn7 } from "child_process";
|
|
1511
|
+
import { appendFileSync as appendFileSync9, mkdtempSync as mkdtempSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
1512
|
+
import { tmpdir as tmpdir7 } from "os";
|
|
1513
|
+
import { join as join10 } from "path";
|
|
1387
1514
|
var GooseProvider = class {
|
|
1388
1515
|
name = "goose";
|
|
1389
1516
|
async isAvailable() {
|
|
1390
1517
|
try {
|
|
1391
|
-
|
|
1518
|
+
execSync7("goose --version", { stdio: "ignore" });
|
|
1392
1519
|
return true;
|
|
1393
1520
|
} catch {
|
|
1394
1521
|
return false;
|
|
@@ -1396,12 +1523,12 @@ var GooseProvider = class {
|
|
|
1396
1523
|
}
|
|
1397
1524
|
async run(prompt, opts) {
|
|
1398
1525
|
const start = Date.now();
|
|
1399
|
-
const tmpDir =
|
|
1400
|
-
const promptFile =
|
|
1401
|
-
|
|
1526
|
+
const tmpDir = mkdtempSync7(join10(tmpdir7(), "lisa-"));
|
|
1527
|
+
const promptFile = join10(tmpDir, "prompt.md");
|
|
1528
|
+
writeFileSync10(promptFile, prompt, "utf-8");
|
|
1402
1529
|
try {
|
|
1403
1530
|
const modelFlag = opts.model ? `--model ${opts.model}` : "";
|
|
1404
|
-
const proc =
|
|
1531
|
+
const proc = spawn7("sh", ["-c", `goose run ${modelFlag} --text "$(cat '${promptFile}')"`], {
|
|
1405
1532
|
cwd: opts.cwd,
|
|
1406
1533
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1407
1534
|
});
|
|
@@ -1416,7 +1543,7 @@ var GooseProvider = class {
|
|
|
1416
1543
|
}
|
|
1417
1544
|
chunks.push(text2);
|
|
1418
1545
|
try {
|
|
1419
|
-
|
|
1546
|
+
appendFileSync9(opts.logFile, text2);
|
|
1420
1547
|
} catch {
|
|
1421
1548
|
}
|
|
1422
1549
|
});
|
|
@@ -1424,7 +1551,7 @@ var GooseProvider = class {
|
|
|
1424
1551
|
const text2 = chunk.toString();
|
|
1425
1552
|
if (getOutputMode() !== "tui") process.stderr.write(text2);
|
|
1426
1553
|
try {
|
|
1427
|
-
|
|
1554
|
+
appendFileSync9(opts.logFile, text2);
|
|
1428
1555
|
} catch {
|
|
1429
1556
|
}
|
|
1430
1557
|
});
|
|
@@ -1450,7 +1577,7 @@ var GooseProvider = class {
|
|
|
1450
1577
|
};
|
|
1451
1578
|
} finally {
|
|
1452
1579
|
try {
|
|
1453
|
-
|
|
1580
|
+
unlinkSync7(promptFile);
|
|
1454
1581
|
} catch {
|
|
1455
1582
|
}
|
|
1456
1583
|
}
|
|
@@ -1458,15 +1585,15 @@ var GooseProvider = class {
|
|
|
1458
1585
|
};
|
|
1459
1586
|
|
|
1460
1587
|
// src/providers/opencode.ts
|
|
1461
|
-
import { execSync as
|
|
1462
|
-
import { appendFileSync as
|
|
1463
|
-
import { tmpdir as
|
|
1464
|
-
import { join as
|
|
1588
|
+
import { execSync as execSync8, spawn as spawn8 } from "child_process";
|
|
1589
|
+
import { appendFileSync as appendFileSync10, mkdtempSync as mkdtempSync8, unlinkSync as unlinkSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
1590
|
+
import { tmpdir as tmpdir8 } from "os";
|
|
1591
|
+
import { join as join11 } from "path";
|
|
1465
1592
|
var OpenCodeProvider = class {
|
|
1466
1593
|
name = "opencode";
|
|
1467
1594
|
async isAvailable() {
|
|
1468
1595
|
try {
|
|
1469
|
-
|
|
1596
|
+
execSync8("opencode --version", { stdio: "ignore" });
|
|
1470
1597
|
return true;
|
|
1471
1598
|
} catch {
|
|
1472
1599
|
return false;
|
|
@@ -1474,11 +1601,11 @@ var OpenCodeProvider = class {
|
|
|
1474
1601
|
}
|
|
1475
1602
|
async run(prompt, opts) {
|
|
1476
1603
|
const start = Date.now();
|
|
1477
|
-
const tmpDir =
|
|
1478
|
-
const promptFile =
|
|
1479
|
-
|
|
1604
|
+
const tmpDir = mkdtempSync8(join11(tmpdir8(), "lisa-"));
|
|
1605
|
+
const promptFile = join11(tmpDir, "prompt.md");
|
|
1606
|
+
writeFileSync11(promptFile, prompt, "utf-8");
|
|
1480
1607
|
try {
|
|
1481
|
-
const proc =
|
|
1608
|
+
const proc = spawn8("sh", ["-c", `opencode run "$(cat '${promptFile}')"`], {
|
|
1482
1609
|
cwd: opts.cwd,
|
|
1483
1610
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1484
1611
|
});
|
|
@@ -1493,7 +1620,7 @@ var OpenCodeProvider = class {
|
|
|
1493
1620
|
}
|
|
1494
1621
|
chunks.push(text2);
|
|
1495
1622
|
try {
|
|
1496
|
-
|
|
1623
|
+
appendFileSync10(opts.logFile, text2);
|
|
1497
1624
|
} catch {
|
|
1498
1625
|
}
|
|
1499
1626
|
});
|
|
@@ -1501,7 +1628,7 @@ var OpenCodeProvider = class {
|
|
|
1501
1628
|
const text2 = chunk.toString();
|
|
1502
1629
|
if (getOutputMode() !== "tui") process.stderr.write(text2);
|
|
1503
1630
|
try {
|
|
1504
|
-
|
|
1631
|
+
appendFileSync10(opts.logFile, text2);
|
|
1505
1632
|
} catch {
|
|
1506
1633
|
}
|
|
1507
1634
|
});
|
|
@@ -1527,7 +1654,7 @@ var OpenCodeProvider = class {
|
|
|
1527
1654
|
};
|
|
1528
1655
|
} finally {
|
|
1529
1656
|
try {
|
|
1530
|
-
|
|
1657
|
+
unlinkSync8(promptFile);
|
|
1531
1658
|
} catch {
|
|
1532
1659
|
}
|
|
1533
1660
|
}
|
|
@@ -1542,7 +1669,8 @@ var providers = {
|
|
|
1542
1669
|
copilot: () => new CopilotProvider(),
|
|
1543
1670
|
cursor: () => new CursorProvider(),
|
|
1544
1671
|
goose: () => new GooseProvider(),
|
|
1545
|
-
aider: () => new AiderProvider()
|
|
1672
|
+
aider: () => new AiderProvider(),
|
|
1673
|
+
codex: () => new CodexProvider()
|
|
1546
1674
|
};
|
|
1547
1675
|
async function getAllProvidersWithAvailability() {
|
|
1548
1676
|
const all = Object.values(providers).map((f) => f());
|
|
@@ -1671,7 +1799,7 @@ function formatAttemptsReport(attempts) {
|
|
|
1671
1799
|
}
|
|
1672
1800
|
|
|
1673
1801
|
// src/session/lifecycle.ts
|
|
1674
|
-
import { spawn as
|
|
1802
|
+
import { spawn as spawn9 } from "child_process";
|
|
1675
1803
|
import { createConnection } from "net";
|
|
1676
1804
|
import { resolve as resolve4 } from "path";
|
|
1677
1805
|
var managedResources = [];
|
|
@@ -1709,7 +1837,7 @@ function waitForPort(port, timeoutMs) {
|
|
|
1709
1837
|
}
|
|
1710
1838
|
function spawnResource(config2, baseCwd) {
|
|
1711
1839
|
const cwd = config2.cwd ? resolve4(baseCwd, config2.cwd) : baseCwd;
|
|
1712
|
-
const child =
|
|
1840
|
+
const child = spawn9("sh", ["-c", config2.up], {
|
|
1713
1841
|
cwd,
|
|
1714
1842
|
stdio: "ignore",
|
|
1715
1843
|
detached: true
|
|
@@ -1719,7 +1847,7 @@ function spawnResource(config2, baseCwd) {
|
|
|
1719
1847
|
}
|
|
1720
1848
|
function runSetupCommand(command, cwd) {
|
|
1721
1849
|
return new Promise((resolve6, reject) => {
|
|
1722
|
-
const child =
|
|
1850
|
+
const child = spawn9("sh", ["-c", command], {
|
|
1723
1851
|
cwd,
|
|
1724
1852
|
stdio: "inherit"
|
|
1725
1853
|
});
|
|
@@ -1790,7 +1918,7 @@ async function stopResources() {
|
|
|
1790
1918
|
}
|
|
1791
1919
|
} else {
|
|
1792
1920
|
await new Promise((resolve6) => {
|
|
1793
|
-
const down =
|
|
1921
|
+
const down = spawn9("sh", ["-c", config2.down], {
|
|
1794
1922
|
stdio: "ignore"
|
|
1795
1923
|
});
|
|
1796
1924
|
down.on("close", () => resolve6());
|
|
@@ -3165,7 +3293,16 @@ function resolveModels(config2) {
|
|
|
3165
3293
|
if (!config2.models || config2.models.length === 0) {
|
|
3166
3294
|
return [{ provider: config2.provider }];
|
|
3167
3295
|
}
|
|
3168
|
-
const knownProviders = /* @__PURE__ */ new Set([
|
|
3296
|
+
const knownProviders = /* @__PURE__ */ new Set([
|
|
3297
|
+
"claude",
|
|
3298
|
+
"gemini",
|
|
3299
|
+
"opencode",
|
|
3300
|
+
"copilot",
|
|
3301
|
+
"cursor",
|
|
3302
|
+
"goose",
|
|
3303
|
+
"aider",
|
|
3304
|
+
"codex"
|
|
3305
|
+
]);
|
|
3169
3306
|
for (const m of config2.models) {
|
|
3170
3307
|
if (knownProviders.has(m) && m !== config2.provider) {
|
|
3171
3308
|
warn(
|
|
@@ -3189,7 +3326,7 @@ function resolveModels(config2) {
|
|
|
3189
3326
|
}
|
|
3190
3327
|
var PLAN_FILE = ".lisa-plan.json";
|
|
3191
3328
|
function readLisaPlan(dir) {
|
|
3192
|
-
const planPath =
|
|
3329
|
+
const planPath = join12(dir, PLAN_FILE);
|
|
3193
3330
|
if (!existsSync6(planPath)) return null;
|
|
3194
3331
|
try {
|
|
3195
3332
|
return JSON.parse(readFileSync5(planPath, "utf-8").trim());
|
|
@@ -3199,13 +3336,13 @@ function readLisaPlan(dir) {
|
|
|
3199
3336
|
}
|
|
3200
3337
|
function cleanupPlan(dir) {
|
|
3201
3338
|
try {
|
|
3202
|
-
|
|
3339
|
+
unlinkSync9(join12(dir, PLAN_FILE));
|
|
3203
3340
|
} catch {
|
|
3204
3341
|
}
|
|
3205
3342
|
}
|
|
3206
3343
|
var MANIFEST_FILE = ".lisa-manifest.json";
|
|
3207
3344
|
function readLisaManifest(dir) {
|
|
3208
|
-
const manifestPath =
|
|
3345
|
+
const manifestPath = join12(dir, MANIFEST_FILE);
|
|
3209
3346
|
if (!existsSync6(manifestPath)) return null;
|
|
3210
3347
|
try {
|
|
3211
3348
|
return JSON.parse(readFileSync5(manifestPath, "utf-8").trim());
|
|
@@ -3215,7 +3352,7 @@ function readLisaManifest(dir) {
|
|
|
3215
3352
|
}
|
|
3216
3353
|
function cleanupManifest(dir) {
|
|
3217
3354
|
try {
|
|
3218
|
-
|
|
3355
|
+
unlinkSync9(join12(dir, MANIFEST_FILE));
|
|
3219
3356
|
} catch {
|
|
3220
3357
|
}
|
|
3221
3358
|
}
|
|
@@ -3608,7 +3745,7 @@ async function runNativeWorktreeSession(config2, issue2, logFile, session, model
|
|
|
3608
3745
|
});
|
|
3609
3746
|
stopSpinner();
|
|
3610
3747
|
try {
|
|
3611
|
-
|
|
3748
|
+
appendFileSync11(
|
|
3612
3749
|
logFile,
|
|
3613
3750
|
`
|
|
3614
3751
|
${"=".repeat(80)}
|
|
@@ -3715,7 +3852,7 @@ async function runManualWorktreeSession(config2, issue2, logFile, session, model
|
|
|
3715
3852
|
});
|
|
3716
3853
|
stopSpinner();
|
|
3717
3854
|
try {
|
|
3718
|
-
|
|
3855
|
+
appendFileSync11(
|
|
3719
3856
|
logFile,
|
|
3720
3857
|
`
|
|
3721
3858
|
${"=".repeat(80)}
|
|
@@ -3774,7 +3911,7 @@ async function runWorktreeMultiRepoSession(config2, issue2, logFile, session, mo
|
|
|
3774
3911
|
});
|
|
3775
3912
|
stopSpinner();
|
|
3776
3913
|
try {
|
|
3777
|
-
|
|
3914
|
+
appendFileSync11(
|
|
3778
3915
|
logFile,
|
|
3779
3916
|
`
|
|
3780
3917
|
${"=".repeat(80)}
|
|
@@ -3910,7 +4047,7 @@ async function runMultiRepoStep(config2, issue2, step, previousResults, logFile,
|
|
|
3910
4047
|
stopSpinner();
|
|
3911
4048
|
if (repoConfig?.lifecycle) await stopResources();
|
|
3912
4049
|
try {
|
|
3913
|
-
|
|
4050
|
+
appendFileSync11(
|
|
3914
4051
|
logFile,
|
|
3915
4052
|
`
|
|
3916
4053
|
${"=".repeat(80)}
|
|
@@ -3989,7 +4126,7 @@ async function runBranchSession(config2, issue2, logFile, session, models) {
|
|
|
3989
4126
|
});
|
|
3990
4127
|
stopSpinner();
|
|
3991
4128
|
try {
|
|
3992
|
-
|
|
4129
|
+
appendFileSync11(
|
|
3993
4130
|
logFile,
|
|
3994
4131
|
`
|
|
3995
4132
|
${"=".repeat(80)}
|
|
@@ -4095,7 +4232,7 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
4095
4232
|
if (isTUI) {
|
|
4096
4233
|
const { render } = await import("ink");
|
|
4097
4234
|
const { createElement } = await import("react");
|
|
4098
|
-
const { KanbanApp } = await import("./kanban-
|
|
4235
|
+
const { KanbanApp } = await import("./kanban-PD2F4KWT.js");
|
|
4099
4236
|
render(createElement(KanbanApp, { config: merged }), { exitOnCtrlC: false });
|
|
4100
4237
|
}
|
|
4101
4238
|
await runLoop(merged, {
|
|
@@ -4195,21 +4332,21 @@ function getVersion() {
|
|
|
4195
4332
|
}
|
|
4196
4333
|
var CURSOR_FREE_PLAN_ERROR = "Free plans can only use Auto";
|
|
4197
4334
|
async function isCursorFreePlan() {
|
|
4198
|
-
const { mkdtempSync:
|
|
4199
|
-
const tmpDir =
|
|
4200
|
-
const promptFile =
|
|
4201
|
-
|
|
4335
|
+
const { mkdtempSync: mkdtempSync9, unlinkSync: unlinkSync10, writeFileSync: writeFileSync12 } = await import("fs");
|
|
4336
|
+
const tmpDir = mkdtempSync9(join13(tmpdir9(), "lisa-cursor-check-"));
|
|
4337
|
+
const promptFile = join13(tmpDir, "prompt.txt");
|
|
4338
|
+
writeFileSync12(promptFile, "test", "utf-8");
|
|
4202
4339
|
try {
|
|
4203
4340
|
const bin = ["agent", "cursor-agent"].find((b) => {
|
|
4204
4341
|
try {
|
|
4205
|
-
|
|
4342
|
+
execSync9(`${b} --version`, { stdio: "ignore" });
|
|
4206
4343
|
return true;
|
|
4207
4344
|
} catch {
|
|
4208
4345
|
return false;
|
|
4209
4346
|
}
|
|
4210
4347
|
});
|
|
4211
4348
|
if (!bin) return false;
|
|
4212
|
-
const output =
|
|
4349
|
+
const output = execSync9(`${bin} -p "$(cat '${promptFile}')" --output-format text`, {
|
|
4213
4350
|
cwd: process.cwd(),
|
|
4214
4351
|
encoding: "utf-8",
|
|
4215
4352
|
timeout: 3e4
|
|
@@ -4220,11 +4357,11 @@ async function isCursorFreePlan() {
|
|
|
4220
4357
|
return errorOutput.includes(CURSOR_FREE_PLAN_ERROR);
|
|
4221
4358
|
} finally {
|
|
4222
4359
|
try {
|
|
4223
|
-
|
|
4360
|
+
unlinkSync10(promptFile);
|
|
4224
4361
|
} catch {
|
|
4225
4362
|
}
|
|
4226
4363
|
try {
|
|
4227
|
-
|
|
4364
|
+
execSync9(`rm -rf ${tmpDir}`, { stdio: "ignore" });
|
|
4228
4365
|
} catch {
|
|
4229
4366
|
}
|
|
4230
4367
|
}
|
|
@@ -4311,14 +4448,14 @@ function fetchCursorModels() {
|
|
|
4311
4448
|
try {
|
|
4312
4449
|
const bin = ["agent", "cursor-agent"].find((b) => {
|
|
4313
4450
|
try {
|
|
4314
|
-
|
|
4451
|
+
execSync9(`${b} --version`, { stdio: "ignore" });
|
|
4315
4452
|
return true;
|
|
4316
4453
|
} catch {
|
|
4317
4454
|
return false;
|
|
4318
4455
|
}
|
|
4319
4456
|
});
|
|
4320
4457
|
if (!bin) return CURSOR_PREFERRED_MODELS;
|
|
4321
|
-
const raw =
|
|
4458
|
+
const raw = execSync9(`${bin} --list-models`, { encoding: "utf-8", timeout: 1e4 });
|
|
4322
4459
|
const clean = raw.replace(/\x1b\[[0-9;]*[mGKHFA-Z]/g, "");
|
|
4323
4460
|
const all = clean.split("\n").map((l) => l.trim()).filter((l) => l.includes(" - ")).map((l) => (l.split(" - ")[0] ?? "").trim()).filter(Boolean);
|
|
4324
4461
|
const filtered = CURSOR_PREFERRED_MODELS.filter((m) => all.includes(m));
|
|
@@ -4329,7 +4466,7 @@ function fetchCursorModels() {
|
|
|
4329
4466
|
}
|
|
4330
4467
|
function fetchOpenCodeModels() {
|
|
4331
4468
|
try {
|
|
4332
|
-
const raw =
|
|
4469
|
+
const raw = execSync9("opencode models", { encoding: "utf-8", timeout: 1e4 });
|
|
4333
4470
|
const hasAnthropic = Boolean(process.env.ANTHROPIC_API_KEY);
|
|
4334
4471
|
const hasGoogle = Boolean(
|
|
4335
4472
|
process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY
|
|
@@ -4373,7 +4510,8 @@ async function runConfigWizard(existing) {
|
|
|
4373
4510
|
copilot: "GitHub Copilot CLI",
|
|
4374
4511
|
cursor: "Cursor Agent",
|
|
4375
4512
|
goose: "Goose",
|
|
4376
|
-
aider: "Aider"
|
|
4513
|
+
aider: "Aider",
|
|
4514
|
+
codex: "OpenAI Codex"
|
|
4377
4515
|
};
|
|
4378
4516
|
const providerModels = {
|
|
4379
4517
|
claude: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5", "claude-sonnet-4-5"],
|
|
@@ -4381,7 +4519,8 @@ async function runConfigWizard(existing) {
|
|
|
4381
4519
|
// opencode: populated dynamically below (fetchOpenCodeModels)
|
|
4382
4520
|
copilot: ["claude-opus-4.6", "claude-sonnet-4.6", "claude-haiku-4.5", "gpt-5.2"],
|
|
4383
4521
|
goose: ["claude-sonnet-4-5", "claude-opus-4-5", "claude-haiku-4-5"],
|
|
4384
|
-
aider: ["claude-opus-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"]
|
|
4522
|
+
aider: ["claude-opus-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"],
|
|
4523
|
+
codex: ["gpt-5.1-codex-mini", "gpt-5.1-codex-max", "gpt-5.2-codex", "gpt-5.2", "gpt-5.3-codex"]
|
|
4385
4524
|
// cursor: populated dynamically below (fetchCursorModels)
|
|
4386
4525
|
};
|
|
4387
4526
|
const allProviders = await getAllProvidersWithAvailability();
|
|
@@ -4395,6 +4534,7 @@ async function runConfigWizard(existing) {
|
|
|
4395
4534
|
${pc2.bold("Gemini CLI")} ${pc2.dim("npm i -g @google/gemini-cli")}
|
|
4396
4535
|
${pc2.bold("OpenCode")} ${pc2.dim("npm i -g opencode")}
|
|
4397
4536
|
${pc2.bold("GitHub Copilot CLI")} ${pc2.dim("npm i -g @github/copilot-cli")}
|
|
4537
|
+
${pc2.bold("OpenAI Codex")} ${pc2.dim("npm i -g @openai/codex")}
|
|
4398
4538
|
${pc2.bold("Goose")} ${pc2.dim("https://block.github.io/goose")}
|
|
4399
4539
|
${pc2.bold("Aider")} ${pc2.dim("pip install aider-chat")}`
|
|
4400
4540
|
);
|
|
@@ -4651,6 +4791,9 @@ Then reload: ${pc2.cyan(`source ${shell}`)}`
|
|
|
4651
4791
|
logs: { dir: ".lisa/logs", format: "text" }
|
|
4652
4792
|
};
|
|
4653
4793
|
saveConfig(cfg);
|
|
4794
|
+
if (ensureLogsGitignore(process.cwd())) {
|
|
4795
|
+
clack.log.info("Added .lisa/logs/* to .gitignore");
|
|
4796
|
+
}
|
|
4654
4797
|
clack.outro(
|
|
4655
4798
|
`${pc2.green("All set!")} Config saved to ${pc2.cyan(".lisa/config.yaml")}
|
|
4656
4799
|
Run ${pc2.bold(pc2.cyan("lisa run"))} to start resolving issues.`
|
|
@@ -4682,12 +4825,12 @@ async function detectGitHubMethod() {
|
|
|
4682
4825
|
}
|
|
4683
4826
|
async function detectGitRepos() {
|
|
4684
4827
|
const cwd = process.cwd();
|
|
4685
|
-
if (existsSync7(
|
|
4828
|
+
if (existsSync7(join13(cwd, ".git"))) {
|
|
4686
4829
|
clack.log.info("Found a git repository in the current directory.");
|
|
4687
4830
|
return [];
|
|
4688
4831
|
}
|
|
4689
4832
|
const entries = readdirSync(cwd, { withFileTypes: true });
|
|
4690
|
-
const gitDirs = entries.filter((e) => e.isDirectory() && existsSync7(
|
|
4833
|
+
const gitDirs = entries.filter((e) => e.isDirectory() && existsSync7(join13(cwd, e.name, ".git"))).map((e) => e.name);
|
|
4691
4834
|
if (gitDirs.length === 0) {
|
|
4692
4835
|
return [];
|
|
4693
4836
|
}
|
|
@@ -4697,7 +4840,7 @@ async function detectGitRepos() {
|
|
|
4697
4840
|
});
|
|
4698
4841
|
if (clack.isCancel(selected)) return process.exit(0);
|
|
4699
4842
|
return selected.map((dir) => ({
|
|
4700
|
-
name: getGitRepoName(
|
|
4843
|
+
name: getGitRepoName(join13(cwd, dir)) ?? dir,
|
|
4701
4844
|
path: `./${dir}`,
|
|
4702
4845
|
match: "",
|
|
4703
4846
|
base_branch: ""
|
|
@@ -4705,7 +4848,7 @@ async function detectGitRepos() {
|
|
|
4705
4848
|
}
|
|
4706
4849
|
function detectDefaultBranch(repoPath) {
|
|
4707
4850
|
try {
|
|
4708
|
-
const ref =
|
|
4851
|
+
const ref = execSync9("git symbolic-ref refs/remotes/origin/HEAD --short", {
|
|
4709
4852
|
cwd: repoPath,
|
|
4710
4853
|
encoding: "utf-8"
|
|
4711
4854
|
}).trim();
|
|
@@ -4716,7 +4859,7 @@ function detectDefaultBranch(repoPath) {
|
|
|
4716
4859
|
}
|
|
4717
4860
|
function getGitRepoName(repoPath) {
|
|
4718
4861
|
try {
|
|
4719
|
-
const url =
|
|
4862
|
+
const url = execSync9("git remote get-url origin", { cwd: repoPath, encoding: "utf-8" }).trim();
|
|
4720
4863
|
const match = url.match(/\/([^/]+?)(?:\.git)?$/) ?? url.match(/:([^/]+?)(?:\.git)?$/);
|
|
4721
4864
|
return match?.[1] ?? null;
|
|
4722
4865
|
} catch {
|
|
@@ -29,7 +29,32 @@ function formatElapsed(ms) {
|
|
|
29
29
|
if (minutes > 0) return `${minutes}m ${remainingSeconds}s`;
|
|
30
30
|
return `${seconds}s`;
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function wrapTitle(title, maxWidth) {
|
|
33
|
+
if (title.length <= maxWidth) return [title, ""];
|
|
34
|
+
const words = title.split(" ");
|
|
35
|
+
let line1 = "";
|
|
36
|
+
let i = 0;
|
|
37
|
+
for (; i < words.length; i++) {
|
|
38
|
+
const word = words[i] ?? "";
|
|
39
|
+
const candidate = line1 ? `${line1} ${word}` : word;
|
|
40
|
+
if (candidate.length > maxWidth) break;
|
|
41
|
+
line1 = candidate;
|
|
42
|
+
}
|
|
43
|
+
if (!line1) {
|
|
44
|
+
line1 = title.slice(0, maxWidth);
|
|
45
|
+
const rest = title.slice(maxWidth);
|
|
46
|
+
const line22 = rest.length > maxWidth ? `${rest.slice(0, maxWidth - 1)}\u2026` : rest;
|
|
47
|
+
return [line1, line22];
|
|
48
|
+
}
|
|
49
|
+
const remaining = words.slice(i).join(" ");
|
|
50
|
+
const line2 = remaining.length > maxWidth ? `${remaining.slice(0, maxWidth - 1)}\u2026` : remaining;
|
|
51
|
+
return [line1, line2];
|
|
52
|
+
}
|
|
53
|
+
function Card({
|
|
54
|
+
card,
|
|
55
|
+
isSelected = false,
|
|
56
|
+
paused = false
|
|
57
|
+
}) {
|
|
33
58
|
const [now, setNow] = useState(Date.now());
|
|
34
59
|
useEffect(() => {
|
|
35
60
|
if (card.column !== "in_progress") return;
|
|
@@ -53,7 +78,9 @@ function Card({ card, isSelected = false }) {
|
|
|
53
78
|
}
|
|
54
79
|
const selectionBar = isSelected ? "\u2590" : " ";
|
|
55
80
|
const selectionColor = isSelected ? "yellow" : "white";
|
|
56
|
-
const
|
|
81
|
+
const CARD_TITLE_WIDTH = 28;
|
|
82
|
+
const [titleLine1, titleLine2] = wrapTitle(card.title, CARD_TITLE_WIDTH);
|
|
83
|
+
const isPausedInProgress = paused && card.column === "in_progress";
|
|
57
84
|
return /* @__PURE__ */ jsxs(
|
|
58
85
|
Box,
|
|
59
86
|
{
|
|
@@ -61,7 +88,7 @@ function Card({ card, isSelected = false }) {
|
|
|
61
88
|
paddingX: 0,
|
|
62
89
|
marginBottom: 0,
|
|
63
90
|
borderStyle: "single",
|
|
64
|
-
borderColor: card.hasError ? "red" : isSelected ? "yellow" : "gray",
|
|
91
|
+
borderColor: card.hasError ? "red" : isPausedInProgress ? "gray" : isSelected ? "yellow" : "gray",
|
|
65
92
|
children: [
|
|
66
93
|
/* @__PURE__ */ jsx(Text, { color: selectionColor, children: selectionBar }),
|
|
67
94
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
|
|
@@ -69,10 +96,11 @@ function Card({ card, isSelected = false }) {
|
|
|
69
96
|
/* @__PURE__ */ jsx(Text, { color: "yellow", bold: isSelected, children: card.id }),
|
|
70
97
|
/* @__PURE__ */ jsx(Text, { color: statusColor, children: statusGlyph })
|
|
71
98
|
] }),
|
|
72
|
-
/* @__PURE__ */ jsx(Text, { bold: isSelected, dimColor: !isSelected, children:
|
|
99
|
+
/* @__PURE__ */ jsx(Text, { bold: isSelected, dimColor: !isSelected, children: titleLine1 }),
|
|
100
|
+
/* @__PURE__ */ jsx(Text, { bold: isSelected, dimColor: !isSelected, children: titleLine2 }),
|
|
73
101
|
card.column === "in_progress" && card.startedAt !== void 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", marginTop: 0, children: [
|
|
74
|
-
/* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
75
|
-
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
102
|
+
isPausedInProgress ? /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u23F8" }) : /* @__PURE__ */ jsx(Text, { color: "yellow", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
103
|
+
/* @__PURE__ */ jsxs(Text, { color: isPausedInProgress ? "gray" : "yellow", dimColor: isPausedInProgress, children: [
|
|
76
104
|
" ",
|
|
77
105
|
formatElapsed(now - card.startedAt)
|
|
78
106
|
] })
|
|
@@ -90,9 +118,15 @@ function Card({ card, isSelected = false }) {
|
|
|
90
118
|
|
|
91
119
|
// src/ui/column.tsx
|
|
92
120
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
93
|
-
var CARD_HEIGHT =
|
|
121
|
+
var CARD_HEIGHT = 6;
|
|
94
122
|
var HEADER_ROWS = 4;
|
|
95
|
-
function Column({
|
|
123
|
+
function Column({
|
|
124
|
+
label,
|
|
125
|
+
cards,
|
|
126
|
+
isFocused = false,
|
|
127
|
+
activeCardIndex = 0,
|
|
128
|
+
paused = false
|
|
129
|
+
}) {
|
|
96
130
|
const terminalRows = process.stdout.rows ?? 24;
|
|
97
131
|
const visibleCount = Math.max(1, Math.floor((terminalRows - HEADER_ROWS) / CARD_HEIGHT));
|
|
98
132
|
let scrollOffset = 0;
|
|
@@ -134,7 +168,7 @@ function Column({ label, cards, isFocused = false, activeCardIndex = 0 }) {
|
|
|
134
168
|
visibleCards.map((card, idx) => {
|
|
135
169
|
const absoluteIdx = scrollOffset + idx;
|
|
136
170
|
const isSelected = isFocused && absoluteIdx === activeCardIndex;
|
|
137
|
-
return /* @__PURE__ */ jsx2(Card, { card, isSelected }, card.id);
|
|
171
|
+
return /* @__PURE__ */ jsx2(Card, { card, isSelected, paused }, card.id);
|
|
138
172
|
}),
|
|
139
173
|
cards.length === 0 && /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", paddingY: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", dimColor: true, children: "\u2014 empty \u2014" }) }),
|
|
140
174
|
hiddenBelow > 0 && /* @__PURE__ */ jsx2(Box2, { justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", dimColor: true, children: `\u2193 ${hiddenBelow} more` }) })
|
|
@@ -158,7 +192,8 @@ function Board({
|
|
|
158
192
|
isEmpty,
|
|
159
193
|
workComplete,
|
|
160
194
|
activeColIndex = 0,
|
|
161
|
-
activeCardIndex = 0
|
|
195
|
+
activeCardIndex = 0,
|
|
196
|
+
paused = false
|
|
162
197
|
}) {
|
|
163
198
|
const backlog = cards.filter((c) => c.column === "backlog");
|
|
164
199
|
const inProgress = cards.filter((c) => c.column === "in_progress");
|
|
@@ -197,7 +232,8 @@ function Board({
|
|
|
197
232
|
label: labels.backlog,
|
|
198
233
|
cards: backlog,
|
|
199
234
|
isFocused: activeColIndex === 0,
|
|
200
|
-
activeCardIndex: activeColIndex === 0 ? activeCardIndex : 0
|
|
235
|
+
activeCardIndex: activeColIndex === 0 ? activeCardIndex : 0,
|
|
236
|
+
paused
|
|
201
237
|
}
|
|
202
238
|
),
|
|
203
239
|
/* @__PURE__ */ jsx3(
|
|
@@ -206,7 +242,8 @@ function Board({
|
|
|
206
242
|
label: labels.inProgress,
|
|
207
243
|
cards: inProgress,
|
|
208
244
|
isFocused: activeColIndex === 1,
|
|
209
|
-
activeCardIndex: activeColIndex === 1 ? activeCardIndex : 0
|
|
245
|
+
activeCardIndex: activeColIndex === 1 ? activeCardIndex : 0,
|
|
246
|
+
paused
|
|
210
247
|
}
|
|
211
248
|
),
|
|
212
249
|
/* @__PURE__ */ jsx3(
|
|
@@ -215,7 +252,8 @@ function Board({
|
|
|
215
252
|
label: labels.done,
|
|
216
253
|
cards: done,
|
|
217
254
|
isFocused: activeColIndex === 2,
|
|
218
|
-
activeCardIndex: activeColIndex === 2 ? activeCardIndex : 0
|
|
255
|
+
activeCardIndex: activeColIndex === 2 ? activeCardIndex : 0,
|
|
256
|
+
paused
|
|
219
257
|
}
|
|
220
258
|
)
|
|
221
259
|
] })
|
|
@@ -234,6 +272,19 @@ function formatElapsed2(ms) {
|
|
|
234
272
|
if (minutes > 0) return `${minutes}m ${remainingSeconds}s`;
|
|
235
273
|
return `${seconds}s`;
|
|
236
274
|
}
|
|
275
|
+
function hyperlink(url, text) {
|
|
276
|
+
return `\x1B]8;;${url}\x07${text}\x1B]8;;\x07`;
|
|
277
|
+
}
|
|
278
|
+
function logLineColor(line) {
|
|
279
|
+
if (/\berror\b|✖/i.test(line)) return "red";
|
|
280
|
+
if (/\bwarn(ing)?\b/i.test(line)) return "yellow";
|
|
281
|
+
if (/✔|\bsuccess\b/i.test(line)) return "green";
|
|
282
|
+
return "white";
|
|
283
|
+
}
|
|
284
|
+
function scrollBar(pct, width = 8) {
|
|
285
|
+
const filled = Math.round(pct / 100 * width);
|
|
286
|
+
return "\u2593".repeat(filled) + "\u2591".repeat(width - filled);
|
|
287
|
+
}
|
|
237
288
|
function statusLabel(column, hasError) {
|
|
238
289
|
if (hasError) return { text: "FAILED", color: "red" };
|
|
239
290
|
if (column === "in_progress") return { text: "IN PROGRESS", color: "yellow" };
|
|
@@ -292,7 +343,7 @@ function IssueDetail({ card, onBack }) {
|
|
|
292
343
|
const separatorInner = Math.max(0, terminalCols - SIDEBAR_TOTAL_WIDTH - 4);
|
|
293
344
|
const separator = `\u2560${"\u2550".repeat(Math.max(0, separatorInner - 2))}\u2563`;
|
|
294
345
|
const totalLines = lines.length;
|
|
295
|
-
const
|
|
346
|
+
const scrollPctNum = totalLines <= bodyRows ? 100 : Math.round((startLine + bodyRows) / totalLines * 100);
|
|
296
347
|
return /* @__PURE__ */ jsxs4(
|
|
297
348
|
Box4,
|
|
298
349
|
{
|
|
@@ -323,21 +374,24 @@ function IssueDetail({ card, onBack }) {
|
|
|
323
374
|
/* @__PURE__ */ jsx4(Box4, { marginTop: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "white", bold: true, children: card.title }) }),
|
|
324
375
|
card.prUrl !== void 0 && card.prUrl.length > 0 && /* @__PURE__ */ jsxs4(Box4, { marginTop: 0, children: [
|
|
325
376
|
/* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: "PR: " }),
|
|
326
|
-
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: card.prUrl })
|
|
377
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: hyperlink(card.prUrl, card.prUrl) })
|
|
327
378
|
] }),
|
|
328
379
|
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: separator }) }),
|
|
329
380
|
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
330
381
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "PROVIDER OUTPUT" }),
|
|
331
|
-
userScrolled && /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children:
|
|
382
|
+
userScrolled && /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: scrollBar(scrollPctNum) }),
|
|
332
383
|
!userScrolled && totalLines > bodyRows && /* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "auto-scroll" })
|
|
333
384
|
] }),
|
|
334
385
|
/* @__PURE__ */ jsx4(Box4, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: card.outputLog.length === 0 ? /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", marginTop: 1, children: [
|
|
335
386
|
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: /* @__PURE__ */ jsx4(Spinner2, { type: "dots" }) }),
|
|
336
387
|
/* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: " Waiting for provider output..." })
|
|
337
|
-
] }) : visibleLines.map((line, i) =>
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
388
|
+
] }) : visibleLines.map((line, i) => {
|
|
389
|
+
const color = logLineColor(line);
|
|
390
|
+
return (
|
|
391
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: log lines have no stable key
|
|
392
|
+
/* @__PURE__ */ jsx4(Text4, { color, dimColor: color === "white", children: line }, i)
|
|
393
|
+
);
|
|
394
|
+
}) })
|
|
341
395
|
]
|
|
342
396
|
}
|
|
343
397
|
);
|
|
@@ -402,7 +456,7 @@ function Sidebar({ provider, source, cwd, activeView, paused = false }) {
|
|
|
402
456
|
/* @__PURE__ */ jsx5(Box5, { flexGrow: 1 }),
|
|
403
457
|
/* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
404
458
|
activeView === "board" ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
405
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[
|
|
459
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u2190\u2192] columns " }),
|
|
406
460
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u2191\u2193] navigate " }),
|
|
407
461
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "[\u21B5] view detail " }),
|
|
408
462
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: paused ? "[p] resume " : "[p] pause " }),
|
|
@@ -425,6 +479,7 @@ function KanbanApp({ config }) {
|
|
|
425
479
|
const [activeColIndex, setActiveColIndex] = useState3(0);
|
|
426
480
|
const [activeCardIndex, setActiveCardIndex] = useState3(0);
|
|
427
481
|
const [paused, setPaused] = useState3(false);
|
|
482
|
+
const [selectedCardId, setSelectedCardId] = useState3(null);
|
|
428
483
|
useEffect3(() => {
|
|
429
484
|
const onExit = () => exit();
|
|
430
485
|
kanbanEmitter.on("tui:exit", onExit);
|
|
@@ -447,13 +502,34 @@ function KanbanApp({ config }) {
|
|
|
447
502
|
}, [inProgress, workComplete]);
|
|
448
503
|
const done = cards.filter((c) => c.column === "done");
|
|
449
504
|
const columnCards = [backlog, inProgress, done];
|
|
505
|
+
useEffect3(() => {
|
|
506
|
+
if (!selectedCardId || activeView !== "detail") return;
|
|
507
|
+
const card = cards.find((c) => c.id === selectedCardId);
|
|
508
|
+
if (!card) return;
|
|
509
|
+
const colIndexMap = {
|
|
510
|
+
backlog: 0,
|
|
511
|
+
in_progress: 1,
|
|
512
|
+
done: 2
|
|
513
|
+
};
|
|
514
|
+
const newColIndex = colIndexMap[card.column];
|
|
515
|
+
const colCards = cards.filter((c) => c.column === card.column);
|
|
516
|
+
const newCardIndex = Math.max(
|
|
517
|
+
0,
|
|
518
|
+
colCards.findIndex((c) => c.id === selectedCardId)
|
|
519
|
+
);
|
|
520
|
+
setActiveColIndex(newColIndex);
|
|
521
|
+
setActiveCardIndex(newCardIndex);
|
|
522
|
+
}, [cards, selectedCardId, activeView]);
|
|
450
523
|
useInput2((input, key) => {
|
|
451
524
|
if (input === "q") {
|
|
452
525
|
process.emit("SIGINT");
|
|
453
526
|
return;
|
|
454
527
|
}
|
|
455
528
|
if (activeView === "detail") {
|
|
456
|
-
if (key.escape)
|
|
529
|
+
if (key.escape) {
|
|
530
|
+
setActiveView("board");
|
|
531
|
+
setSelectedCardId(null);
|
|
532
|
+
}
|
|
457
533
|
return;
|
|
458
534
|
}
|
|
459
535
|
if (input === "p") {
|
|
@@ -462,14 +538,14 @@ function KanbanApp({ config }) {
|
|
|
462
538
|
kanbanEmitter.emit(next ? "loop:pause" : "loop:resume");
|
|
463
539
|
return;
|
|
464
540
|
}
|
|
465
|
-
if (key.
|
|
541
|
+
if (key.rightArrow) {
|
|
466
542
|
const nextCol = (activeColIndex + 1) % 3;
|
|
467
543
|
setActiveColIndex(nextCol);
|
|
468
544
|
const colLen = columnCards[nextCol]?.length ?? 0;
|
|
469
545
|
setActiveCardIndex(Math.min(activeCardIndex, Math.max(0, colLen - 1)));
|
|
470
546
|
return;
|
|
471
547
|
}
|
|
472
|
-
if (key.
|
|
548
|
+
if (key.leftArrow) {
|
|
473
549
|
const prevCol = (activeColIndex + 2) % 3;
|
|
474
550
|
setActiveColIndex(prevCol);
|
|
475
551
|
const colLen = columnCards[prevCol]?.length ?? 0;
|
|
@@ -486,8 +562,11 @@ function KanbanApp({ config }) {
|
|
|
486
562
|
return;
|
|
487
563
|
}
|
|
488
564
|
if (key.return) {
|
|
489
|
-
const
|
|
490
|
-
if (
|
|
565
|
+
const card = columnCards[activeColIndex]?.[activeCardIndex];
|
|
566
|
+
if (card) {
|
|
567
|
+
setSelectedCardId(card.id);
|
|
568
|
+
setActiveView("detail");
|
|
569
|
+
}
|
|
491
570
|
}
|
|
492
571
|
});
|
|
493
572
|
const labels = {
|
|
@@ -495,7 +574,7 @@ function KanbanApp({ config }) {
|
|
|
495
574
|
inProgress: config.source_config.in_progress,
|
|
496
575
|
done: config.source_config.done
|
|
497
576
|
};
|
|
498
|
-
const selectedCard = activeView === "detail" ?
|
|
577
|
+
const selectedCard = activeView === "detail" && selectedCardId ? cards.find((c) => c.id === selectedCardId) ?? null : null;
|
|
499
578
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", height: process.stdout.rows, children: [
|
|
500
579
|
/* @__PURE__ */ jsx6(
|
|
501
580
|
Sidebar,
|
|
@@ -515,9 +594,19 @@ function KanbanApp({ config }) {
|
|
|
515
594
|
isEmpty,
|
|
516
595
|
workComplete,
|
|
517
596
|
activeColIndex,
|
|
518
|
-
activeCardIndex
|
|
597
|
+
activeCardIndex,
|
|
598
|
+
paused
|
|
599
|
+
}
|
|
600
|
+
) : /* @__PURE__ */ jsx6(
|
|
601
|
+
IssueDetail,
|
|
602
|
+
{
|
|
603
|
+
card: selectedCard,
|
|
604
|
+
onBack: () => {
|
|
605
|
+
setActiveView("board");
|
|
606
|
+
setSelectedCardId(null);
|
|
607
|
+
}
|
|
519
608
|
}
|
|
520
|
-
)
|
|
609
|
+
)
|
|
521
610
|
] });
|
|
522
611
|
}
|
|
523
612
|
export {
|
package/package.json
CHANGED
|
@@ -1,62 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
}
|
|
2
|
+
"name": "@tarcisiopgs/lisa",
|
|
3
|
+
"version": "1.8.1",
|
|
4
|
+
"description": "Autonomous issue resolver",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"lisa": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@clack/prompts": "^1.0.1",
|
|
12
|
+
"citty": "^0.2.1",
|
|
13
|
+
"execa": "^9.6.1",
|
|
14
|
+
"ink": "^6.8.0",
|
|
15
|
+
"ink-spinner": "^5.0.0",
|
|
16
|
+
"picocolors": "^1.1.1",
|
|
17
|
+
"react": "^19.2.4",
|
|
18
|
+
"yaml": "^2.8.2"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@biomejs/biome": "^2.4.3",
|
|
22
|
+
"@types/node": "^22.13.4",
|
|
23
|
+
"@types/react": "^19.2.14",
|
|
24
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
25
|
+
"concurrently": "^9.2.1",
|
|
26
|
+
"husky": "^9.1.7",
|
|
27
|
+
"lint-staged": "^16.2.7",
|
|
28
|
+
"tsup": "^8.4.0",
|
|
29
|
+
"tsx": "^4.19.3",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^4.0.18"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/tarcisiopgs/lisa.git"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist"
|
|
42
|
+
],
|
|
43
|
+
"lint-staged": {
|
|
44
|
+
"*.{ts,tsx}": [
|
|
45
|
+
"biome check --write"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"dev": "tsx src/index.ts",
|
|
51
|
+
"check": "biome check src/",
|
|
52
|
+
"format": "biome format --write src/",
|
|
53
|
+
"lint": "biome lint src/",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"test:coverage": "vitest run --coverage",
|
|
58
|
+
"ci": "concurrently -n lint,typecheck,test \"pnpm lint\" \"pnpm typecheck\" \"pnpm test\""
|
|
59
|
+
}
|
|
60
|
+
}
|