@locusai/cli 0.20.0 → 0.20.2
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/bin/locus.js +667 -265
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -1280,6 +1280,177 @@ var init_version_check = __esm(() => {
|
|
|
1280
1280
|
CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
1281
1281
|
});
|
|
1282
1282
|
|
|
1283
|
+
// src/core/ecosystem.ts
|
|
1284
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1285
|
+
import { join as join5 } from "node:path";
|
|
1286
|
+
function detectProjectEcosystem(projectRoot) {
|
|
1287
|
+
for (const signal of ECOSYSTEM_SIGNALS) {
|
|
1288
|
+
for (const marker of signal.markers) {
|
|
1289
|
+
if (marker.startsWith("*")) {
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (existsSync5(join5(projectRoot, marker))) {
|
|
1293
|
+
return signal.ecosystem;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return "unknown";
|
|
1298
|
+
}
|
|
1299
|
+
function isJavaScriptEcosystem(ecosystem) {
|
|
1300
|
+
return ecosystem === "javascript";
|
|
1301
|
+
}
|
|
1302
|
+
function generateSandboxSetupTemplate(ecosystem) {
|
|
1303
|
+
switch (ecosystem) {
|
|
1304
|
+
case "javascript":
|
|
1305
|
+
return null;
|
|
1306
|
+
case "rust":
|
|
1307
|
+
return `#!/bin/sh
|
|
1308
|
+
# Sandbox setup for Rust projects
|
|
1309
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1310
|
+
# Uncomment or modify the commands below for your project.
|
|
1311
|
+
|
|
1312
|
+
# Install Rust toolchain (if not pre-installed)
|
|
1313
|
+
# curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
1314
|
+
# . "$HOME/.cargo/env"
|
|
1315
|
+
|
|
1316
|
+
# Build the project
|
|
1317
|
+
# cargo build
|
|
1318
|
+
|
|
1319
|
+
echo "Rust sandbox setup complete"
|
|
1320
|
+
`;
|
|
1321
|
+
case "go":
|
|
1322
|
+
return `#!/bin/sh
|
|
1323
|
+
# Sandbox setup for Go projects
|
|
1324
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1325
|
+
|
|
1326
|
+
# Download dependencies
|
|
1327
|
+
# go mod download
|
|
1328
|
+
|
|
1329
|
+
# Build the project
|
|
1330
|
+
# go build ./...
|
|
1331
|
+
|
|
1332
|
+
echo "Go sandbox setup complete"
|
|
1333
|
+
`;
|
|
1334
|
+
case "python":
|
|
1335
|
+
return `#!/bin/sh
|
|
1336
|
+
# Sandbox setup for Python projects
|
|
1337
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1338
|
+
|
|
1339
|
+
# Create virtual environment and install dependencies
|
|
1340
|
+
# python3 -m venv .venv
|
|
1341
|
+
# . .venv/bin/activate
|
|
1342
|
+
# pip install -r requirements.txt
|
|
1343
|
+
# OR: pip install -e .
|
|
1344
|
+
|
|
1345
|
+
echo "Python sandbox setup complete"
|
|
1346
|
+
`;
|
|
1347
|
+
case "java":
|
|
1348
|
+
return `#!/bin/sh
|
|
1349
|
+
# Sandbox setup for Java/JVM projects
|
|
1350
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1351
|
+
|
|
1352
|
+
# Maven
|
|
1353
|
+
# mvn install -DskipTests
|
|
1354
|
+
|
|
1355
|
+
# Gradle
|
|
1356
|
+
# ./gradlew build -x test
|
|
1357
|
+
|
|
1358
|
+
echo "Java sandbox setup complete"
|
|
1359
|
+
`;
|
|
1360
|
+
case "ruby":
|
|
1361
|
+
return `#!/bin/sh
|
|
1362
|
+
# Sandbox setup for Ruby projects
|
|
1363
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1364
|
+
|
|
1365
|
+
# Install dependencies
|
|
1366
|
+
# bundle install
|
|
1367
|
+
|
|
1368
|
+
echo "Ruby sandbox setup complete"
|
|
1369
|
+
`;
|
|
1370
|
+
case "elixir":
|
|
1371
|
+
return `#!/bin/sh
|
|
1372
|
+
# Sandbox setup for Elixir projects
|
|
1373
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1374
|
+
|
|
1375
|
+
# Install dependencies
|
|
1376
|
+
# mix deps.get
|
|
1377
|
+
# mix compile
|
|
1378
|
+
|
|
1379
|
+
echo "Elixir sandbox setup complete"
|
|
1380
|
+
`;
|
|
1381
|
+
case "dotnet":
|
|
1382
|
+
return `#!/bin/sh
|
|
1383
|
+
# Sandbox setup for .NET projects
|
|
1384
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1385
|
+
|
|
1386
|
+
# Restore packages
|
|
1387
|
+
# dotnet restore
|
|
1388
|
+
|
|
1389
|
+
# Build
|
|
1390
|
+
# dotnet build
|
|
1391
|
+
|
|
1392
|
+
echo ".NET sandbox setup complete"
|
|
1393
|
+
`;
|
|
1394
|
+
case "unknown":
|
|
1395
|
+
return `#!/bin/sh
|
|
1396
|
+
# Sandbox setup script
|
|
1397
|
+
# This script runs inside the Docker sandbox after creation.
|
|
1398
|
+
# Add any commands needed to prepare the build environment.
|
|
1399
|
+
|
|
1400
|
+
# Example: install system dependencies
|
|
1401
|
+
# apt-get update && apt-get install -y <packages>
|
|
1402
|
+
|
|
1403
|
+
# Example: install project dependencies
|
|
1404
|
+
# <your-package-manager> install
|
|
1405
|
+
|
|
1406
|
+
echo "Sandbox setup complete"
|
|
1407
|
+
`;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
var ECOSYSTEM_SIGNALS;
|
|
1411
|
+
var init_ecosystem = __esm(() => {
|
|
1412
|
+
ECOSYSTEM_SIGNALS = [
|
|
1413
|
+
{
|
|
1414
|
+
ecosystem: "javascript",
|
|
1415
|
+
markers: ["package.json"]
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
ecosystem: "rust",
|
|
1419
|
+
markers: ["Cargo.toml"]
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
ecosystem: "go",
|
|
1423
|
+
markers: ["go.mod"]
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
ecosystem: "python",
|
|
1427
|
+
markers: [
|
|
1428
|
+
"pyproject.toml",
|
|
1429
|
+
"setup.py",
|
|
1430
|
+
"setup.cfg",
|
|
1431
|
+
"Pipfile",
|
|
1432
|
+
"requirements.txt"
|
|
1433
|
+
]
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
ecosystem: "java",
|
|
1437
|
+
markers: ["pom.xml", "build.gradle", "build.gradle.kts"]
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
ecosystem: "ruby",
|
|
1441
|
+
markers: ["Gemfile"]
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
ecosystem: "elixir",
|
|
1445
|
+
markers: ["mix.exs"]
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
ecosystem: "dotnet",
|
|
1449
|
+
markers: ["*.csproj", "*.fsproj", "*.sln"]
|
|
1450
|
+
}
|
|
1451
|
+
];
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1283
1454
|
// src/core/github.ts
|
|
1284
1455
|
import {
|
|
1285
1456
|
execFileSync,
|
|
@@ -1476,7 +1647,18 @@ function reopenMilestone(owner, repo, milestoneNumber, options = {}) {
|
|
|
1476
1647
|
gh(`api repos/${owner}/${repo}/milestones/${milestoneNumber} -X PATCH -f state=open`, options);
|
|
1477
1648
|
}
|
|
1478
1649
|
function createPR(title, body, head, base, options = {}) {
|
|
1479
|
-
const result = ghExec([
|
|
1650
|
+
const result = ghExec([
|
|
1651
|
+
"pr",
|
|
1652
|
+
"create",
|
|
1653
|
+
"--title",
|
|
1654
|
+
title,
|
|
1655
|
+
"--body",
|
|
1656
|
+
body,
|
|
1657
|
+
"--head",
|
|
1658
|
+
head,
|
|
1659
|
+
"--base",
|
|
1660
|
+
base
|
|
1661
|
+
], options);
|
|
1480
1662
|
const match = result.match(/\/pull\/(\d+)/);
|
|
1481
1663
|
if (!match) {
|
|
1482
1664
|
throw new Error(`Could not extract PR number from: ${result}`);
|
|
@@ -1630,8 +1812,8 @@ var exports_init = {};
|
|
|
1630
1812
|
__export(exports_init, {
|
|
1631
1813
|
initCommand: () => initCommand
|
|
1632
1814
|
});
|
|
1633
|
-
import { existsSync as
|
|
1634
|
-
import { join as
|
|
1815
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1816
|
+
import { join as join6 } from "node:path";
|
|
1635
1817
|
async function initCommand(cwd) {
|
|
1636
1818
|
const log = getLogger();
|
|
1637
1819
|
process.stderr.write(`
|
|
@@ -1675,17 +1857,17 @@ ${bold("Initializing Locus...")}
|
|
|
1675
1857
|
}
|
|
1676
1858
|
process.stderr.write(`${green("✓")} Repository: ${bold(`${context.owner}/${context.repo}`)} (branch: ${context.defaultBranch})
|
|
1677
1859
|
`);
|
|
1678
|
-
const locusDir =
|
|
1860
|
+
const locusDir = join6(cwd, ".locus");
|
|
1679
1861
|
const dirs = [
|
|
1680
1862
|
locusDir,
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1863
|
+
join6(locusDir, "sessions"),
|
|
1864
|
+
join6(locusDir, "discussions"),
|
|
1865
|
+
join6(locusDir, "artifacts"),
|
|
1866
|
+
join6(locusDir, "plans"),
|
|
1867
|
+
join6(locusDir, "logs")
|
|
1686
1868
|
];
|
|
1687
1869
|
for (const dir of dirs) {
|
|
1688
|
-
if (!
|
|
1870
|
+
if (!existsSync6(dir)) {
|
|
1689
1871
|
mkdirSync5(dir, { recursive: true });
|
|
1690
1872
|
}
|
|
1691
1873
|
}
|
|
@@ -1706,7 +1888,7 @@ ${bold("Initializing Locus...")}
|
|
|
1706
1888
|
};
|
|
1707
1889
|
if (isReInit) {
|
|
1708
1890
|
try {
|
|
1709
|
-
const existing = JSON.parse(readFileSync4(
|
|
1891
|
+
const existing = JSON.parse(readFileSync4(join6(locusDir, "config.json"), "utf-8"));
|
|
1710
1892
|
if (existing.ai)
|
|
1711
1893
|
config.ai = { ...config.ai, ...existing.ai };
|
|
1712
1894
|
if (existing.agent)
|
|
@@ -1725,8 +1907,8 @@ ${bold("Initializing Locus...")}
|
|
|
1725
1907
|
`);
|
|
1726
1908
|
}
|
|
1727
1909
|
saveConfig(cwd, config);
|
|
1728
|
-
const locusMdPath =
|
|
1729
|
-
if (!
|
|
1910
|
+
const locusMdPath = join6(locusDir, "LOCUS.md");
|
|
1911
|
+
if (!existsSync6(locusMdPath)) {
|
|
1730
1912
|
writeFileSync4(locusMdPath, LOCUS_MD_TEMPLATE, "utf-8");
|
|
1731
1913
|
process.stderr.write(`${green("✓")} Generated LOCUS.md (edit to add project context)
|
|
1732
1914
|
`);
|
|
@@ -1734,8 +1916,8 @@ ${bold("Initializing Locus...")}
|
|
|
1734
1916
|
process.stderr.write(`${dim("○")} LOCUS.md already exists (preserved)
|
|
1735
1917
|
`);
|
|
1736
1918
|
}
|
|
1737
|
-
const learningsMdPath =
|
|
1738
|
-
if (!
|
|
1919
|
+
const learningsMdPath = join6(locusDir, "LEARNINGS.md");
|
|
1920
|
+
if (!existsSync6(learningsMdPath)) {
|
|
1739
1921
|
writeFileSync4(learningsMdPath, LEARNINGS_MD_TEMPLATE, "utf-8");
|
|
1740
1922
|
process.stderr.write(`${green("✓")} Generated LEARNINGS.md
|
|
1741
1923
|
`);
|
|
@@ -1743,13 +1925,29 @@ ${bold("Initializing Locus...")}
|
|
|
1743
1925
|
process.stderr.write(`${dim("○")} LEARNINGS.md already exists (preserved)
|
|
1744
1926
|
`);
|
|
1745
1927
|
}
|
|
1746
|
-
const sandboxIgnorePath =
|
|
1747
|
-
if (!
|
|
1928
|
+
const sandboxIgnorePath = join6(cwd, ".sandboxignore");
|
|
1929
|
+
if (!existsSync6(sandboxIgnorePath)) {
|
|
1748
1930
|
writeFileSync4(sandboxIgnorePath, SANDBOXIGNORE_TEMPLATE, "utf-8");
|
|
1749
1931
|
process.stderr.write(`${green("✓")} Generated .sandboxignore
|
|
1750
1932
|
`);
|
|
1751
1933
|
} else {
|
|
1752
1934
|
process.stderr.write(`${dim("○")} .sandboxignore already exists (preserved)
|
|
1935
|
+
`);
|
|
1936
|
+
}
|
|
1937
|
+
const ecosystem = detectProjectEcosystem(cwd);
|
|
1938
|
+
const sandboxSetupPath = join6(locusDir, "sandbox-setup.sh");
|
|
1939
|
+
if (!existsSync6(sandboxSetupPath)) {
|
|
1940
|
+
const template = generateSandboxSetupTemplate(ecosystem);
|
|
1941
|
+
if (template) {
|
|
1942
|
+
writeFileSync4(sandboxSetupPath, template, {
|
|
1943
|
+
encoding: "utf-8",
|
|
1944
|
+
mode: 493
|
|
1945
|
+
});
|
|
1946
|
+
process.stderr.write(`${green("✓")} Generated .locus/sandbox-setup.sh (${ecosystem} project detected)
|
|
1947
|
+
`);
|
|
1948
|
+
}
|
|
1949
|
+
} else {
|
|
1950
|
+
process.stderr.write(`${dim("○")} sandbox-setup.sh already exists (preserved)
|
|
1753
1951
|
`);
|
|
1754
1952
|
}
|
|
1755
1953
|
process.stderr.write(`${cyan("●")} Creating GitHub labels...`);
|
|
@@ -1761,9 +1959,9 @@ ${bold("Initializing Locus...")}
|
|
|
1761
1959
|
process.stderr.write(`\r${yellow("⚠")} Some labels could not be created: ${e.message}
|
|
1762
1960
|
`);
|
|
1763
1961
|
}
|
|
1764
|
-
const gitignorePath =
|
|
1962
|
+
const gitignorePath = join6(cwd, ".gitignore");
|
|
1765
1963
|
let gitignoreContent = "";
|
|
1766
|
-
if (
|
|
1964
|
+
if (existsSync6(gitignorePath)) {
|
|
1767
1965
|
gitignoreContent = readFileSync4(gitignorePath, "utf-8");
|
|
1768
1966
|
}
|
|
1769
1967
|
const entriesToAdd = GITIGNORE_ENTRIES.filter((entry) => entry && !gitignoreContent.includes(entry.trim()));
|
|
@@ -1996,6 +2194,7 @@ It is read by AI agents before every task to avoid repeating mistakes and to fol
|
|
|
1996
2194
|
var init_init = __esm(() => {
|
|
1997
2195
|
init_config();
|
|
1998
2196
|
init_context();
|
|
2197
|
+
init_ecosystem();
|
|
1999
2198
|
init_github();
|
|
2000
2199
|
init_logger();
|
|
2001
2200
|
init_terminal();
|
|
@@ -2017,33 +2216,33 @@ var init_init = __esm(() => {
|
|
|
2017
2216
|
|
|
2018
2217
|
// src/packages/registry.ts
|
|
2019
2218
|
import {
|
|
2020
|
-
existsSync as
|
|
2219
|
+
existsSync as existsSync7,
|
|
2021
2220
|
mkdirSync as mkdirSync6,
|
|
2022
2221
|
readFileSync as readFileSync5,
|
|
2023
2222
|
renameSync,
|
|
2024
2223
|
writeFileSync as writeFileSync5
|
|
2025
2224
|
} from "node:fs";
|
|
2026
2225
|
import { homedir as homedir2 } from "node:os";
|
|
2027
|
-
import { join as
|
|
2226
|
+
import { join as join7 } from "node:path";
|
|
2028
2227
|
function getPackagesDir() {
|
|
2029
2228
|
const home = process.env.HOME || homedir2();
|
|
2030
|
-
const dir =
|
|
2031
|
-
if (!
|
|
2229
|
+
const dir = join7(home, ".locus", "packages");
|
|
2230
|
+
if (!existsSync7(dir)) {
|
|
2032
2231
|
mkdirSync6(dir, { recursive: true });
|
|
2033
2232
|
}
|
|
2034
|
-
const pkgJson =
|
|
2035
|
-
if (!
|
|
2233
|
+
const pkgJson = join7(dir, "package.json");
|
|
2234
|
+
if (!existsSync7(pkgJson)) {
|
|
2036
2235
|
writeFileSync5(pkgJson, `${JSON.stringify({ private: true }, null, 2)}
|
|
2037
2236
|
`, "utf-8");
|
|
2038
2237
|
}
|
|
2039
2238
|
return dir;
|
|
2040
2239
|
}
|
|
2041
2240
|
function getRegistryPath() {
|
|
2042
|
-
return
|
|
2241
|
+
return join7(getPackagesDir(), "registry.json");
|
|
2043
2242
|
}
|
|
2044
2243
|
function loadRegistry() {
|
|
2045
2244
|
const registryPath = getRegistryPath();
|
|
2046
|
-
if (!
|
|
2245
|
+
if (!existsSync7(registryPath)) {
|
|
2047
2246
|
return { packages: {} };
|
|
2048
2247
|
}
|
|
2049
2248
|
try {
|
|
@@ -2066,8 +2265,8 @@ function saveRegistry(registry) {
|
|
|
2066
2265
|
}
|
|
2067
2266
|
function resolvePackageBinary(packageName) {
|
|
2068
2267
|
const fullName = normalizePackageName(packageName);
|
|
2069
|
-
const binPath =
|
|
2070
|
-
return
|
|
2268
|
+
const binPath = join7(getPackagesDir(), "node_modules", ".bin", fullName);
|
|
2269
|
+
return existsSync7(binPath) ? binPath : null;
|
|
2071
2270
|
}
|
|
2072
2271
|
function normalizePackageName(input) {
|
|
2073
2272
|
if (input.startsWith("@")) {
|
|
@@ -2087,7 +2286,7 @@ __export(exports_pkg, {
|
|
|
2087
2286
|
listInstalledPackages: () => listInstalledPackages
|
|
2088
2287
|
});
|
|
2089
2288
|
import { spawn } from "node:child_process";
|
|
2090
|
-
import { existsSync as
|
|
2289
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
2091
2290
|
function listInstalledPackages() {
|
|
2092
2291
|
const registry = loadRegistry();
|
|
2093
2292
|
const entries = Object.values(registry.packages);
|
|
@@ -2165,7 +2364,7 @@ async function pkgCommand(args, _flags) {
|
|
|
2165
2364
|
return;
|
|
2166
2365
|
}
|
|
2167
2366
|
const binaryPath = entry.binaryPath;
|
|
2168
|
-
if (!binaryPath || !
|
|
2367
|
+
if (!binaryPath || !existsSync8(binaryPath)) {
|
|
2169
2368
|
process.stderr.write(`${red("✗")} Binary for ${bold(packageName)} not found on disk.
|
|
2170
2369
|
`);
|
|
2171
2370
|
if (binaryPath) {
|
|
@@ -2291,8 +2490,8 @@ __export(exports_install, {
|
|
|
2291
2490
|
installCommand: () => installCommand
|
|
2292
2491
|
});
|
|
2293
2492
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2294
|
-
import { existsSync as
|
|
2295
|
-
import { join as
|
|
2493
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
|
|
2494
|
+
import { join as join8 } from "node:path";
|
|
2296
2495
|
function parsePackageArg(raw) {
|
|
2297
2496
|
if (raw.startsWith("@")) {
|
|
2298
2497
|
const slashIdx = raw.indexOf("/");
|
|
@@ -2369,8 +2568,8 @@ ${red("✗")} Failed to install ${bold(packageSpec)}.
|
|
|
2369
2568
|
process.exit(1);
|
|
2370
2569
|
return;
|
|
2371
2570
|
}
|
|
2372
|
-
const installedPkgJsonPath =
|
|
2373
|
-
if (!
|
|
2571
|
+
const installedPkgJsonPath = join8(packagesDir, "node_modules", packageName, "package.json");
|
|
2572
|
+
if (!existsSync9(installedPkgJsonPath)) {
|
|
2374
2573
|
process.stderr.write(`
|
|
2375
2574
|
${red("✗")} Package installed but package.json not found at:
|
|
2376
2575
|
`);
|
|
@@ -2645,16 +2844,16 @@ __export(exports_logs, {
|
|
|
2645
2844
|
logsCommand: () => logsCommand
|
|
2646
2845
|
});
|
|
2647
2846
|
import {
|
|
2648
|
-
existsSync as
|
|
2847
|
+
existsSync as existsSync10,
|
|
2649
2848
|
readdirSync as readdirSync2,
|
|
2650
2849
|
readFileSync as readFileSync7,
|
|
2651
2850
|
statSync as statSync2,
|
|
2652
2851
|
unlinkSync as unlinkSync2
|
|
2653
2852
|
} from "node:fs";
|
|
2654
|
-
import { join as
|
|
2853
|
+
import { join as join9 } from "node:path";
|
|
2655
2854
|
async function logsCommand(cwd, options) {
|
|
2656
|
-
const logsDir =
|
|
2657
|
-
if (!
|
|
2855
|
+
const logsDir = join9(cwd, ".locus", "logs");
|
|
2856
|
+
if (!existsSync10(logsDir)) {
|
|
2658
2857
|
process.stderr.write(`${dim("No logs found.")}
|
|
2659
2858
|
`);
|
|
2660
2859
|
return;
|
|
@@ -2709,8 +2908,8 @@ async function tailLog(logFile, levelFilter) {
|
|
|
2709
2908
|
process.stderr.write(`${bold("Tailing:")} ${dim(logFile)} ${dim("(Ctrl+C to stop)")}
|
|
2710
2909
|
|
|
2711
2910
|
`);
|
|
2712
|
-
let lastSize =
|
|
2713
|
-
if (
|
|
2911
|
+
let lastSize = existsSync10(logFile) ? statSync2(logFile).size : 0;
|
|
2912
|
+
if (existsSync10(logFile)) {
|
|
2714
2913
|
const content = readFileSync7(logFile, "utf-8");
|
|
2715
2914
|
const lines = content.trim().split(`
|
|
2716
2915
|
`).filter(Boolean);
|
|
@@ -2729,7 +2928,7 @@ async function tailLog(logFile, levelFilter) {
|
|
|
2729
2928
|
}
|
|
2730
2929
|
return new Promise((resolve) => {
|
|
2731
2930
|
const interval = setInterval(() => {
|
|
2732
|
-
if (!
|
|
2931
|
+
if (!existsSync10(logFile))
|
|
2733
2932
|
return;
|
|
2734
2933
|
const currentSize = statSync2(logFile).size;
|
|
2735
2934
|
if (currentSize <= lastSize)
|
|
@@ -2789,7 +2988,7 @@ function cleanLogs(logsDir) {
|
|
|
2789
2988
|
`);
|
|
2790
2989
|
}
|
|
2791
2990
|
function getLogFiles(logsDir) {
|
|
2792
|
-
return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) =>
|
|
2991
|
+
return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) => join9(logsDir, f)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs);
|
|
2793
2992
|
}
|
|
2794
2993
|
function formatEntry(entry) {
|
|
2795
2994
|
const time = dim(new Date(entry.ts).toLocaleTimeString());
|
|
@@ -3099,9 +3298,9 @@ var init_stream_renderer = __esm(() => {
|
|
|
3099
3298
|
|
|
3100
3299
|
// src/repl/clipboard.ts
|
|
3101
3300
|
import { execSync as execSync4 } from "node:child_process";
|
|
3102
|
-
import { existsSync as
|
|
3301
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7 } from "node:fs";
|
|
3103
3302
|
import { tmpdir } from "node:os";
|
|
3104
|
-
import { join as
|
|
3303
|
+
import { join as join10 } from "node:path";
|
|
3105
3304
|
function readClipboardImage() {
|
|
3106
3305
|
if (process.platform === "darwin") {
|
|
3107
3306
|
return readMacOSClipboardImage();
|
|
@@ -3112,14 +3311,14 @@ function readClipboardImage() {
|
|
|
3112
3311
|
return null;
|
|
3113
3312
|
}
|
|
3114
3313
|
function ensureStableDir() {
|
|
3115
|
-
if (!
|
|
3314
|
+
if (!existsSync11(STABLE_DIR)) {
|
|
3116
3315
|
mkdirSync7(STABLE_DIR, { recursive: true });
|
|
3117
3316
|
}
|
|
3118
3317
|
}
|
|
3119
3318
|
function readMacOSClipboardImage() {
|
|
3120
3319
|
try {
|
|
3121
3320
|
ensureStableDir();
|
|
3122
|
-
const destPath =
|
|
3321
|
+
const destPath = join10(STABLE_DIR, `clipboard-${Date.now()}.png`);
|
|
3123
3322
|
const script = [
|
|
3124
3323
|
`set destPath to POSIX file "${destPath}"`,
|
|
3125
3324
|
"try",
|
|
@@ -3143,7 +3342,7 @@ function readMacOSClipboardImage() {
|
|
|
3143
3342
|
timeout: 5000,
|
|
3144
3343
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3145
3344
|
}).trim();
|
|
3146
|
-
if (result === "ok" &&
|
|
3345
|
+
if (result === "ok" && existsSync11(destPath)) {
|
|
3147
3346
|
return destPath;
|
|
3148
3347
|
}
|
|
3149
3348
|
} catch {}
|
|
@@ -3156,9 +3355,9 @@ function readLinuxClipboardImage() {
|
|
|
3156
3355
|
return null;
|
|
3157
3356
|
}
|
|
3158
3357
|
ensureStableDir();
|
|
3159
|
-
const destPath =
|
|
3358
|
+
const destPath = join10(STABLE_DIR, `clipboard-${Date.now()}.png`);
|
|
3160
3359
|
execSync4(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
|
|
3161
|
-
if (
|
|
3360
|
+
if (existsSync11(destPath)) {
|
|
3162
3361
|
return destPath;
|
|
3163
3362
|
}
|
|
3164
3363
|
} catch {}
|
|
@@ -3166,13 +3365,13 @@ function readLinuxClipboardImage() {
|
|
|
3166
3365
|
}
|
|
3167
3366
|
var STABLE_DIR;
|
|
3168
3367
|
var init_clipboard = __esm(() => {
|
|
3169
|
-
STABLE_DIR =
|
|
3368
|
+
STABLE_DIR = join10(tmpdir(), "locus-images");
|
|
3170
3369
|
});
|
|
3171
3370
|
|
|
3172
3371
|
// src/repl/image-detect.ts
|
|
3173
|
-
import { copyFileSync, existsSync as
|
|
3372
|
+
import { copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3174
3373
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
3175
|
-
import { basename, extname, join as
|
|
3374
|
+
import { basename, extname, join as join11, resolve } from "node:path";
|
|
3176
3375
|
function detectImages(input) {
|
|
3177
3376
|
const detected = [];
|
|
3178
3377
|
const byResolved = new Map;
|
|
@@ -3266,15 +3465,15 @@ function collectReferencedAttachments(input, attachments) {
|
|
|
3266
3465
|
return dedupeByResolvedPath(selected);
|
|
3267
3466
|
}
|
|
3268
3467
|
function relocateImages(images, projectRoot) {
|
|
3269
|
-
const targetDir =
|
|
3468
|
+
const targetDir = join11(projectRoot, ".locus", "tmp", "images");
|
|
3270
3469
|
for (const img of images) {
|
|
3271
3470
|
if (!img.exists)
|
|
3272
3471
|
continue;
|
|
3273
3472
|
try {
|
|
3274
|
-
if (!
|
|
3473
|
+
if (!existsSync12(targetDir)) {
|
|
3275
3474
|
mkdirSync8(targetDir, { recursive: true });
|
|
3276
3475
|
}
|
|
3277
|
-
const dest =
|
|
3476
|
+
const dest = join11(targetDir, basename(img.stablePath));
|
|
3278
3477
|
copyFileSync(img.stablePath, dest);
|
|
3279
3478
|
img.stablePath = dest;
|
|
3280
3479
|
} catch {}
|
|
@@ -3286,7 +3485,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
|
|
|
3286
3485
|
return;
|
|
3287
3486
|
let resolved = stripQuotes(rawPath).replace(/\\ /g, " ");
|
|
3288
3487
|
if (resolved.startsWith("~/")) {
|
|
3289
|
-
resolved =
|
|
3488
|
+
resolved = join11(homedir3(), resolved.slice(2));
|
|
3290
3489
|
}
|
|
3291
3490
|
resolved = resolve(resolved);
|
|
3292
3491
|
const existing = byResolved.get(resolved);
|
|
@@ -3299,7 +3498,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
|
|
|
3299
3498
|
]);
|
|
3300
3499
|
return;
|
|
3301
3500
|
}
|
|
3302
|
-
const exists =
|
|
3501
|
+
const exists = existsSync12(resolved);
|
|
3303
3502
|
let stablePath = resolved;
|
|
3304
3503
|
if (exists) {
|
|
3305
3504
|
stablePath = copyToStable(resolved);
|
|
@@ -3353,10 +3552,10 @@ function dedupeByResolvedPath(images) {
|
|
|
3353
3552
|
}
|
|
3354
3553
|
function copyToStable(sourcePath) {
|
|
3355
3554
|
try {
|
|
3356
|
-
if (!
|
|
3555
|
+
if (!existsSync12(STABLE_DIR2)) {
|
|
3357
3556
|
mkdirSync8(STABLE_DIR2, { recursive: true });
|
|
3358
3557
|
}
|
|
3359
|
-
const dest =
|
|
3558
|
+
const dest = join11(STABLE_DIR2, `${Date.now()}-${basename(sourcePath)}`);
|
|
3360
3559
|
copyFileSync(sourcePath, dest);
|
|
3361
3560
|
return dest;
|
|
3362
3561
|
} catch {
|
|
@@ -3376,7 +3575,7 @@ var init_image_detect = __esm(() => {
|
|
|
3376
3575
|
".tif",
|
|
3377
3576
|
".tiff"
|
|
3378
3577
|
]);
|
|
3379
|
-
STABLE_DIR2 =
|
|
3578
|
+
STABLE_DIR2 = join11(tmpdir2(), "locus-images");
|
|
3380
3579
|
PLACEHOLDER_ID_PATTERN = /\(locus:\/\/screenshot-(\d+)\)/g;
|
|
3381
3580
|
});
|
|
3382
3581
|
|
|
@@ -4160,10 +4359,7 @@ __export(exports_claude, {
|
|
|
4160
4359
|
});
|
|
4161
4360
|
import { execSync as execSync5, spawn as spawn2 } from "node:child_process";
|
|
4162
4361
|
function buildClaudeArgs(options) {
|
|
4163
|
-
const args = [
|
|
4164
|
-
"--dangerously-skip-permissions",
|
|
4165
|
-
"--no-session-persistence"
|
|
4166
|
-
];
|
|
4362
|
+
const args = ["--dangerously-skip-permissions", "--no-session-persistence"];
|
|
4167
4363
|
if (options.model) {
|
|
4168
4364
|
args.push("--model", options.model);
|
|
4169
4365
|
}
|
|
@@ -4355,11 +4551,11 @@ var init_claude = __esm(() => {
|
|
|
4355
4551
|
|
|
4356
4552
|
// src/core/sandbox-ignore.ts
|
|
4357
4553
|
import { exec } from "node:child_process";
|
|
4358
|
-
import { existsSync as
|
|
4359
|
-
import { join as
|
|
4554
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "node:fs";
|
|
4555
|
+
import { join as join12 } from "node:path";
|
|
4360
4556
|
import { promisify } from "node:util";
|
|
4361
4557
|
function parseIgnoreFile(filePath) {
|
|
4362
|
-
if (!
|
|
4558
|
+
if (!existsSync13(filePath))
|
|
4363
4559
|
return [];
|
|
4364
4560
|
const content = readFileSync8(filePath, "utf-8");
|
|
4365
4561
|
const rules = [];
|
|
@@ -4406,7 +4602,7 @@ function buildCleanupScript(rules, workspacePath) {
|
|
|
4406
4602
|
}
|
|
4407
4603
|
async function enforceSandboxIgnore(sandboxName, projectRoot) {
|
|
4408
4604
|
const log = getLogger();
|
|
4409
|
-
const ignorePath =
|
|
4605
|
+
const ignorePath = join12(projectRoot, ".sandboxignore");
|
|
4410
4606
|
const rules = parseIgnoreFile(ignorePath);
|
|
4411
4607
|
if (rules.length === 0)
|
|
4412
4608
|
return;
|
|
@@ -4534,7 +4730,6 @@ class SandboxedClaudeRunner {
|
|
|
4534
4730
|
const text = chunk.toString();
|
|
4535
4731
|
errorOutput += text;
|
|
4536
4732
|
log.debug("sandboxed claude stderr", { text: text.slice(0, 500) });
|
|
4537
|
-
options.onOutput?.(text);
|
|
4538
4733
|
});
|
|
4539
4734
|
this.process.on("close", (code) => {
|
|
4540
4735
|
this.process = null;
|
|
@@ -6686,8 +6881,8 @@ var init_sprint = __esm(() => {
|
|
|
6686
6881
|
|
|
6687
6882
|
// src/core/prompt-builder.ts
|
|
6688
6883
|
import { execSync as execSync7 } from "node:child_process";
|
|
6689
|
-
import { existsSync as
|
|
6690
|
-
import { join as
|
|
6884
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
|
|
6885
|
+
import { join as join13 } from "node:path";
|
|
6691
6886
|
function buildExecutionPrompt(ctx) {
|
|
6692
6887
|
const sections = [];
|
|
6693
6888
|
sections.push(buildSystemContext(ctx.projectRoot));
|
|
@@ -6717,13 +6912,13 @@ function buildFeedbackPrompt(ctx) {
|
|
|
6717
6912
|
}
|
|
6718
6913
|
function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
|
|
6719
6914
|
const sections = [];
|
|
6720
|
-
const locusmd = readFileSafe(
|
|
6915
|
+
const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
|
|
6721
6916
|
if (locusmd) {
|
|
6722
6917
|
sections.push(`<project-instructions>
|
|
6723
6918
|
${locusmd}
|
|
6724
6919
|
</project-instructions>`);
|
|
6725
6920
|
}
|
|
6726
|
-
const learnings = readFileSafe(
|
|
6921
|
+
const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
|
|
6727
6922
|
if (learnings) {
|
|
6728
6923
|
sections.push(`<past-learnings>
|
|
6729
6924
|
${learnings}
|
|
@@ -6749,24 +6944,24 @@ ${userMessage}
|
|
|
6749
6944
|
}
|
|
6750
6945
|
function buildSystemContext(projectRoot) {
|
|
6751
6946
|
const parts = [];
|
|
6752
|
-
const locusmd = readFileSafe(
|
|
6947
|
+
const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
|
|
6753
6948
|
if (locusmd) {
|
|
6754
6949
|
parts.push(`<project-instructions>
|
|
6755
6950
|
${locusmd}
|
|
6756
6951
|
</project-instructions>`);
|
|
6757
6952
|
}
|
|
6758
|
-
const learnings = readFileSafe(
|
|
6953
|
+
const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
|
|
6759
6954
|
if (learnings) {
|
|
6760
6955
|
parts.push(`<past-learnings>
|
|
6761
6956
|
${learnings}
|
|
6762
6957
|
</past-learnings>`);
|
|
6763
6958
|
}
|
|
6764
|
-
const discussionsDir =
|
|
6765
|
-
if (
|
|
6959
|
+
const discussionsDir = join13(projectRoot, ".locus", "discussions");
|
|
6960
|
+
if (existsSync14(discussionsDir)) {
|
|
6766
6961
|
try {
|
|
6767
6962
|
const files = readdirSync3(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
|
|
6768
6963
|
for (const file of files) {
|
|
6769
|
-
const content = readFileSafe(
|
|
6964
|
+
const content = readFileSafe(join13(discussionsDir, file));
|
|
6770
6965
|
if (content) {
|
|
6771
6966
|
const name = file.replace(".md", "");
|
|
6772
6967
|
parts.push(`<discussion name="${name}">
|
|
@@ -6917,7 +7112,7 @@ function buildFeedbackInstructions() {
|
|
|
6917
7112
|
}
|
|
6918
7113
|
function readFileSafe(path) {
|
|
6919
7114
|
try {
|
|
6920
|
-
if (!
|
|
7115
|
+
if (!existsSync14(path))
|
|
6921
7116
|
return null;
|
|
6922
7117
|
return readFileSync9(path, "utf-8");
|
|
6923
7118
|
} catch {
|
|
@@ -7383,7 +7578,7 @@ var init_commands = __esm(() => {
|
|
|
7383
7578
|
|
|
7384
7579
|
// src/repl/completions.ts
|
|
7385
7580
|
import { readdirSync as readdirSync4 } from "node:fs";
|
|
7386
|
-
import { basename as basename2, dirname as dirname3, join as
|
|
7581
|
+
import { basename as basename2, dirname as dirname3, join as join14 } from "node:path";
|
|
7387
7582
|
|
|
7388
7583
|
class SlashCommandCompletion {
|
|
7389
7584
|
commands;
|
|
@@ -7438,7 +7633,7 @@ class FilePathCompletion {
|
|
|
7438
7633
|
}
|
|
7439
7634
|
findMatches(partial) {
|
|
7440
7635
|
try {
|
|
7441
|
-
const dir = partial.includes("/") ?
|
|
7636
|
+
const dir = partial.includes("/") ? join14(this.projectRoot, dirname3(partial)) : this.projectRoot;
|
|
7442
7637
|
const prefix = basename2(partial);
|
|
7443
7638
|
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
7444
7639
|
return entries.filter((e) => {
|
|
@@ -7474,14 +7669,14 @@ class CombinedCompletion {
|
|
|
7474
7669
|
var init_completions = () => {};
|
|
7475
7670
|
|
|
7476
7671
|
// src/repl/input-history.ts
|
|
7477
|
-
import { existsSync as
|
|
7478
|
-
import { dirname as dirname4, join as
|
|
7672
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7673
|
+
import { dirname as dirname4, join as join15 } from "node:path";
|
|
7479
7674
|
|
|
7480
7675
|
class InputHistory {
|
|
7481
7676
|
entries = [];
|
|
7482
7677
|
filePath;
|
|
7483
7678
|
constructor(projectRoot) {
|
|
7484
|
-
this.filePath =
|
|
7679
|
+
this.filePath = join15(projectRoot, ".locus", "sessions", ".input-history");
|
|
7485
7680
|
this.load();
|
|
7486
7681
|
}
|
|
7487
7682
|
add(text) {
|
|
@@ -7520,7 +7715,7 @@ class InputHistory {
|
|
|
7520
7715
|
}
|
|
7521
7716
|
load() {
|
|
7522
7717
|
try {
|
|
7523
|
-
if (!
|
|
7718
|
+
if (!existsSync15(this.filePath))
|
|
7524
7719
|
return;
|
|
7525
7720
|
const content = readFileSync10(this.filePath, "utf-8");
|
|
7526
7721
|
this.entries = content.split(`
|
|
@@ -7530,7 +7725,7 @@ class InputHistory {
|
|
|
7530
7725
|
save() {
|
|
7531
7726
|
try {
|
|
7532
7727
|
const dir = dirname4(this.filePath);
|
|
7533
|
-
if (!
|
|
7728
|
+
if (!existsSync15(dir)) {
|
|
7534
7729
|
mkdirSync9(dir, { recursive: true });
|
|
7535
7730
|
}
|
|
7536
7731
|
const content = this.entries.map((e) => this.escape(e)).join(`
|
|
@@ -7561,20 +7756,20 @@ var init_model_config = __esm(() => {
|
|
|
7561
7756
|
|
|
7562
7757
|
// src/repl/session-manager.ts
|
|
7563
7758
|
import {
|
|
7564
|
-
existsSync as
|
|
7759
|
+
existsSync as existsSync16,
|
|
7565
7760
|
mkdirSync as mkdirSync10,
|
|
7566
7761
|
readdirSync as readdirSync5,
|
|
7567
7762
|
readFileSync as readFileSync11,
|
|
7568
7763
|
unlinkSync as unlinkSync3,
|
|
7569
7764
|
writeFileSync as writeFileSync7
|
|
7570
7765
|
} from "node:fs";
|
|
7571
|
-
import { basename as basename3, join as
|
|
7766
|
+
import { basename as basename3, join as join16 } from "node:path";
|
|
7572
7767
|
|
|
7573
7768
|
class SessionManager {
|
|
7574
7769
|
sessionsDir;
|
|
7575
7770
|
constructor(projectRoot) {
|
|
7576
|
-
this.sessionsDir =
|
|
7577
|
-
if (!
|
|
7771
|
+
this.sessionsDir = join16(projectRoot, ".locus", "sessions");
|
|
7772
|
+
if (!existsSync16(this.sessionsDir)) {
|
|
7578
7773
|
mkdirSync10(this.sessionsDir, { recursive: true });
|
|
7579
7774
|
}
|
|
7580
7775
|
}
|
|
@@ -7600,12 +7795,12 @@ class SessionManager {
|
|
|
7600
7795
|
}
|
|
7601
7796
|
isPersisted(sessionOrId) {
|
|
7602
7797
|
const sessionId = typeof sessionOrId === "string" ? sessionOrId : sessionOrId.id;
|
|
7603
|
-
return
|
|
7798
|
+
return existsSync16(this.getSessionPath(sessionId));
|
|
7604
7799
|
}
|
|
7605
7800
|
load(idOrPrefix) {
|
|
7606
7801
|
const files = this.listSessionFiles();
|
|
7607
7802
|
const exactPath = this.getSessionPath(idOrPrefix);
|
|
7608
|
-
if (
|
|
7803
|
+
if (existsSync16(exactPath)) {
|
|
7609
7804
|
try {
|
|
7610
7805
|
return JSON.parse(readFileSync11(exactPath, "utf-8"));
|
|
7611
7806
|
} catch {
|
|
@@ -7655,7 +7850,7 @@ class SessionManager {
|
|
|
7655
7850
|
}
|
|
7656
7851
|
delete(sessionId) {
|
|
7657
7852
|
const path = this.getSessionPath(sessionId);
|
|
7658
|
-
if (
|
|
7853
|
+
if (existsSync16(path)) {
|
|
7659
7854
|
unlinkSync3(path);
|
|
7660
7855
|
return true;
|
|
7661
7856
|
}
|
|
@@ -7685,7 +7880,7 @@ class SessionManager {
|
|
|
7685
7880
|
const remaining = withStats.length - pruned;
|
|
7686
7881
|
if (remaining > MAX_SESSIONS) {
|
|
7687
7882
|
const toRemove = remaining - MAX_SESSIONS;
|
|
7688
|
-
const alive = withStats.filter((e) =>
|
|
7883
|
+
const alive = withStats.filter((e) => existsSync16(e.path));
|
|
7689
7884
|
for (let i = 0;i < toRemove && i < alive.length; i++) {
|
|
7690
7885
|
try {
|
|
7691
7886
|
unlinkSync3(alive[i].path);
|
|
@@ -7700,7 +7895,7 @@ class SessionManager {
|
|
|
7700
7895
|
}
|
|
7701
7896
|
listSessionFiles() {
|
|
7702
7897
|
try {
|
|
7703
|
-
return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) =>
|
|
7898
|
+
return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
|
|
7704
7899
|
} catch {
|
|
7705
7900
|
return [];
|
|
7706
7901
|
}
|
|
@@ -7709,7 +7904,7 @@ class SessionManager {
|
|
|
7709
7904
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
7710
7905
|
}
|
|
7711
7906
|
getSessionPath(sessionId) {
|
|
7712
|
-
return
|
|
7907
|
+
return join16(this.sessionsDir, `${sessionId}.json`);
|
|
7713
7908
|
}
|
|
7714
7909
|
}
|
|
7715
7910
|
var MAX_SESSIONS = 50, SESSION_MAX_AGE_MS;
|
|
@@ -8186,8 +8381,167 @@ var init_exec = __esm(() => {
|
|
|
8186
8381
|
init_session_manager();
|
|
8187
8382
|
});
|
|
8188
8383
|
|
|
8189
|
-
// src/core/
|
|
8384
|
+
// src/core/submodule.ts
|
|
8190
8385
|
import { execSync as execSync10 } from "node:child_process";
|
|
8386
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
8387
|
+
import { join as join17 } from "node:path";
|
|
8388
|
+
function git2(args, cwd) {
|
|
8389
|
+
return execSync10(`git ${args}`, {
|
|
8390
|
+
cwd,
|
|
8391
|
+
encoding: "utf-8",
|
|
8392
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
8393
|
+
});
|
|
8394
|
+
}
|
|
8395
|
+
function gitSafe(args, cwd) {
|
|
8396
|
+
try {
|
|
8397
|
+
return git2(args, cwd);
|
|
8398
|
+
} catch {
|
|
8399
|
+
return null;
|
|
8400
|
+
}
|
|
8401
|
+
}
|
|
8402
|
+
function hasSubmodules(cwd) {
|
|
8403
|
+
return existsSync17(join17(cwd, ".gitmodules"));
|
|
8404
|
+
}
|
|
8405
|
+
function listSubmodules(cwd) {
|
|
8406
|
+
if (!hasSubmodules(cwd))
|
|
8407
|
+
return [];
|
|
8408
|
+
const log = getLogger();
|
|
8409
|
+
const submodules = [];
|
|
8410
|
+
try {
|
|
8411
|
+
const output = git2("submodule status", cwd);
|
|
8412
|
+
for (const line of output.trim().split(`
|
|
8413
|
+
`)) {
|
|
8414
|
+
if (!line.trim())
|
|
8415
|
+
continue;
|
|
8416
|
+
const dirty = line.startsWith("+");
|
|
8417
|
+
const parts = line.trim().replace(/^[+-]/, "").split(/\s+/);
|
|
8418
|
+
const path = parts[1];
|
|
8419
|
+
if (!path)
|
|
8420
|
+
continue;
|
|
8421
|
+
submodules.push({
|
|
8422
|
+
path,
|
|
8423
|
+
absolutePath: join17(cwd, path),
|
|
8424
|
+
dirty
|
|
8425
|
+
});
|
|
8426
|
+
}
|
|
8427
|
+
} catch (e) {
|
|
8428
|
+
log.warn(`Failed to list submodules: ${e}`);
|
|
8429
|
+
}
|
|
8430
|
+
return submodules;
|
|
8431
|
+
}
|
|
8432
|
+
function getDirtySubmodules(cwd) {
|
|
8433
|
+
const submodules = listSubmodules(cwd);
|
|
8434
|
+
const dirty = [];
|
|
8435
|
+
for (const sub of submodules) {
|
|
8436
|
+
if (!existsSync17(sub.absolutePath))
|
|
8437
|
+
continue;
|
|
8438
|
+
const status = gitSafe("status --porcelain", sub.absolutePath);
|
|
8439
|
+
if (status && status.trim().length > 0) {
|
|
8440
|
+
dirty.push({ ...sub, dirty: true });
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
return dirty;
|
|
8444
|
+
}
|
|
8445
|
+
function commitDirtySubmodules(cwd, issueNumber, issueTitle) {
|
|
8446
|
+
const log = getLogger();
|
|
8447
|
+
const dirtySubmodules = getDirtySubmodules(cwd);
|
|
8448
|
+
if (dirtySubmodules.length === 0)
|
|
8449
|
+
return [];
|
|
8450
|
+
const committed = [];
|
|
8451
|
+
for (const sub of dirtySubmodules) {
|
|
8452
|
+
try {
|
|
8453
|
+
git2("add -A", sub.absolutePath);
|
|
8454
|
+
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
8455
|
+
|
|
8456
|
+
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
8457
|
+
execSync10("git commit -F -", {
|
|
8458
|
+
input: message,
|
|
8459
|
+
cwd: sub.absolutePath,
|
|
8460
|
+
encoding: "utf-8",
|
|
8461
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
8462
|
+
});
|
|
8463
|
+
committed.push(sub.path);
|
|
8464
|
+
log.info(`Committed submodule changes: ${sub.path} for #${issueNumber}`);
|
|
8465
|
+
} catch {
|
|
8466
|
+
log.verbose(`No committable changes in submodule ${sub.path}`);
|
|
8467
|
+
}
|
|
8468
|
+
}
|
|
8469
|
+
if (committed.length > 0) {
|
|
8470
|
+
for (const subPath of committed) {
|
|
8471
|
+
gitSafe(`add ${subPath}`, cwd);
|
|
8472
|
+
}
|
|
8473
|
+
}
|
|
8474
|
+
return committed;
|
|
8475
|
+
}
|
|
8476
|
+
function initSubmodules(cwd) {
|
|
8477
|
+
if (!hasSubmodules(cwd))
|
|
8478
|
+
return;
|
|
8479
|
+
const log = getLogger();
|
|
8480
|
+
try {
|
|
8481
|
+
git2("submodule update --init --recursive", cwd);
|
|
8482
|
+
log.info("Initialized submodules");
|
|
8483
|
+
} catch (e) {
|
|
8484
|
+
log.warn(`Failed to initialize submodules: ${e}`);
|
|
8485
|
+
}
|
|
8486
|
+
}
|
|
8487
|
+
function updateSubmodulesAfterRebase(cwd) {
|
|
8488
|
+
if (!hasSubmodules(cwd))
|
|
8489
|
+
return;
|
|
8490
|
+
const log = getLogger();
|
|
8491
|
+
try {
|
|
8492
|
+
git2("submodule update --recursive", cwd);
|
|
8493
|
+
log.info("Updated submodules after rebase");
|
|
8494
|
+
} catch (e) {
|
|
8495
|
+
log.warn(`Failed to update submodules after rebase: ${e}`);
|
|
8496
|
+
}
|
|
8497
|
+
}
|
|
8498
|
+
function getSubmoduleChangeSummary(cwd, baseBranch) {
|
|
8499
|
+
if (!hasSubmodules(cwd))
|
|
8500
|
+
return null;
|
|
8501
|
+
const diff = gitSafe(`diff origin/${baseBranch}..HEAD --submodule=short`, cwd);
|
|
8502
|
+
if (!diff || !diff.trim())
|
|
8503
|
+
return null;
|
|
8504
|
+
const submoduleChanges = [];
|
|
8505
|
+
for (const line of diff.split(`
|
|
8506
|
+
`)) {
|
|
8507
|
+
if (line.startsWith("Submodule ")) {
|
|
8508
|
+
submoduleChanges.push(line.trim());
|
|
8509
|
+
}
|
|
8510
|
+
}
|
|
8511
|
+
if (submoduleChanges.length === 0)
|
|
8512
|
+
return null;
|
|
8513
|
+
return `### Submodule Changes
|
|
8514
|
+
${submoduleChanges.map((c) => `- ${c}`).join(`
|
|
8515
|
+
`)}`;
|
|
8516
|
+
}
|
|
8517
|
+
function pushSubmoduleBranches(cwd) {
|
|
8518
|
+
if (!hasSubmodules(cwd))
|
|
8519
|
+
return;
|
|
8520
|
+
const log = getLogger();
|
|
8521
|
+
const submodules = listSubmodules(cwd);
|
|
8522
|
+
for (const sub of submodules) {
|
|
8523
|
+
if (!existsSync17(sub.absolutePath))
|
|
8524
|
+
continue;
|
|
8525
|
+
const branch = gitSafe("rev-parse --abbrev-ref HEAD", sub.absolutePath)?.trim();
|
|
8526
|
+
if (!branch || branch === "HEAD")
|
|
8527
|
+
continue;
|
|
8528
|
+
const unpushed = gitSafe(`log origin/${branch}..HEAD --oneline`, sub.absolutePath)?.trim();
|
|
8529
|
+
if (unpushed) {
|
|
8530
|
+
try {
|
|
8531
|
+
git2(`push origin ${branch}`, sub.absolutePath);
|
|
8532
|
+
log.info(`Pushed submodule ${sub.path} branch ${branch}`);
|
|
8533
|
+
} catch (e) {
|
|
8534
|
+
log.warn(`Failed to push submodule ${sub.path}: ${e}`);
|
|
8535
|
+
}
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
}
|
|
8539
|
+
var init_submodule = __esm(() => {
|
|
8540
|
+
init_logger();
|
|
8541
|
+
});
|
|
8542
|
+
|
|
8543
|
+
// src/core/agent.ts
|
|
8544
|
+
import { execSync as execSync11 } from "node:child_process";
|
|
8191
8545
|
async function executeIssue(projectRoot, options) {
|
|
8192
8546
|
const log = getLogger();
|
|
8193
8547
|
const timer = createTimer();
|
|
@@ -8216,7 +8570,7 @@ ${cyan("●")} ${bold(`#${issueNumber}`)} ${issue.title}
|
|
|
8216
8570
|
}
|
|
8217
8571
|
let issueComments = [];
|
|
8218
8572
|
try {
|
|
8219
|
-
const commentsRaw =
|
|
8573
|
+
const commentsRaw = execSync11(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
8220
8574
|
if (commentsRaw) {
|
|
8221
8575
|
issueComments = commentsRaw.split(`
|
|
8222
8576
|
`).filter(Boolean);
|
|
@@ -8380,12 +8734,12 @@ ${aiResult.success ? green("✓") : red("✗")} Iteration ${aiResult.success ? "
|
|
|
8380
8734
|
}
|
|
8381
8735
|
async function createIssuePR(projectRoot, config, issue) {
|
|
8382
8736
|
try {
|
|
8383
|
-
const currentBranch =
|
|
8737
|
+
const currentBranch = execSync11("git rev-parse --abbrev-ref HEAD", {
|
|
8384
8738
|
cwd: projectRoot,
|
|
8385
8739
|
encoding: "utf-8",
|
|
8386
8740
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8387
8741
|
}).trim();
|
|
8388
|
-
const diff =
|
|
8742
|
+
const diff = execSync11(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
8389
8743
|
cwd: projectRoot,
|
|
8390
8744
|
encoding: "utf-8",
|
|
8391
8745
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8394,17 +8748,25 @@ async function createIssuePR(projectRoot, config, issue) {
|
|
|
8394
8748
|
getLogger().verbose("No changes to create PR for");
|
|
8395
8749
|
return;
|
|
8396
8750
|
}
|
|
8397
|
-
|
|
8751
|
+
pushSubmoduleBranches(projectRoot);
|
|
8752
|
+
execSync11(`git push -u origin ${currentBranch}`, {
|
|
8398
8753
|
cwd: projectRoot,
|
|
8399
8754
|
encoding: "utf-8",
|
|
8400
8755
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8401
8756
|
});
|
|
8402
|
-
const
|
|
8403
|
-
|
|
8757
|
+
const submoduleSummary = getSubmoduleChangeSummary(projectRoot, config.agent.baseBranch);
|
|
8758
|
+
let prBody = `Closes #${issue.number}`;
|
|
8759
|
+
if (submoduleSummary) {
|
|
8760
|
+
prBody += `
|
|
8761
|
+
|
|
8762
|
+
${submoduleSummary}`;
|
|
8763
|
+
}
|
|
8764
|
+
prBody += `
|
|
8404
8765
|
|
|
8405
8766
|
---
|
|
8406
8767
|
|
|
8407
8768
|
\uD83E\uDD16 Automated by [Locus](https://github.com/locusai/locus)`;
|
|
8769
|
+
const prTitle = `${issue.title} (#${issue.number})`;
|
|
8408
8770
|
const prNumber = createPR(prTitle, prBody, currentBranch, config.agent.baseBranch, { cwd: projectRoot });
|
|
8409
8771
|
process.stderr.write(` ${green("✓")} Created PR #${prNumber}
|
|
8410
8772
|
`);
|
|
@@ -8438,20 +8800,21 @@ var init_agent = __esm(() => {
|
|
|
8438
8800
|
init_logger();
|
|
8439
8801
|
init_prompt_builder();
|
|
8440
8802
|
init_sandbox();
|
|
8803
|
+
init_submodule();
|
|
8441
8804
|
});
|
|
8442
8805
|
|
|
8443
8806
|
// src/core/conflict.ts
|
|
8444
|
-
import { execSync as
|
|
8445
|
-
function
|
|
8446
|
-
return
|
|
8807
|
+
import { execSync as execSync12 } from "node:child_process";
|
|
8808
|
+
function git3(args, cwd) {
|
|
8809
|
+
return execSync12(`git ${args}`, {
|
|
8447
8810
|
cwd,
|
|
8448
8811
|
encoding: "utf-8",
|
|
8449
8812
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8450
8813
|
});
|
|
8451
8814
|
}
|
|
8452
|
-
function
|
|
8815
|
+
function gitSafe2(args, cwd) {
|
|
8453
8816
|
try {
|
|
8454
|
-
return
|
|
8817
|
+
return git3(args, cwd);
|
|
8455
8818
|
} catch {
|
|
8456
8819
|
return null;
|
|
8457
8820
|
}
|
|
@@ -8459,7 +8822,7 @@ function gitSafe(args, cwd) {
|
|
|
8459
8822
|
function checkForConflicts(cwd, baseBranch) {
|
|
8460
8823
|
const log = getLogger();
|
|
8461
8824
|
try {
|
|
8462
|
-
|
|
8825
|
+
git3(`fetch origin ${baseBranch}`, cwd);
|
|
8463
8826
|
log.debug("Fetched latest from origin", { baseBranch });
|
|
8464
8827
|
} catch (e) {
|
|
8465
8828
|
log.warn(`Could not fetch origin/${baseBranch}: ${e}`);
|
|
@@ -8470,8 +8833,8 @@ function checkForConflicts(cwd, baseBranch) {
|
|
|
8470
8833
|
newCommits: 0
|
|
8471
8834
|
};
|
|
8472
8835
|
}
|
|
8473
|
-
const currentBranch =
|
|
8474
|
-
const mergeBase =
|
|
8836
|
+
const currentBranch = gitSafe2("rev-parse --abbrev-ref HEAD", cwd)?.trim() ?? "";
|
|
8837
|
+
const mergeBase = gitSafe2(`merge-base ${currentBranch} origin/${baseBranch}`, cwd)?.trim();
|
|
8475
8838
|
if (!mergeBase) {
|
|
8476
8839
|
log.debug("Could not find merge base — branches may be unrelated");
|
|
8477
8840
|
return {
|
|
@@ -8481,7 +8844,7 @@ function checkForConflicts(cwd, baseBranch) {
|
|
|
8481
8844
|
newCommits: 0
|
|
8482
8845
|
};
|
|
8483
8846
|
}
|
|
8484
|
-
const remoteTip =
|
|
8847
|
+
const remoteTip = gitSafe2(`rev-parse origin/${baseBranch}`, cwd)?.trim();
|
|
8485
8848
|
if (!remoteTip || remoteTip === mergeBase) {
|
|
8486
8849
|
log.debug("Base branch has not advanced");
|
|
8487
8850
|
return {
|
|
@@ -8491,16 +8854,16 @@ function checkForConflicts(cwd, baseBranch) {
|
|
|
8491
8854
|
newCommits: 0
|
|
8492
8855
|
};
|
|
8493
8856
|
}
|
|
8494
|
-
const newCommitsOutput =
|
|
8857
|
+
const newCommitsOutput = gitSafe2(`rev-list --count ${mergeBase}..origin/${baseBranch}`, cwd)?.trim() ?? "0";
|
|
8495
8858
|
const newCommits = Number.parseInt(newCommitsOutput, 10);
|
|
8496
8859
|
log.verbose(`Base branch has ${newCommits} new commits`, {
|
|
8497
8860
|
baseBranch,
|
|
8498
8861
|
mergeBase: mergeBase.slice(0, 8),
|
|
8499
8862
|
remoteTip: remoteTip.slice(0, 8)
|
|
8500
8863
|
});
|
|
8501
|
-
const ourChanges =
|
|
8864
|
+
const ourChanges = gitSafe2(`diff --name-only ${mergeBase}..HEAD`, cwd)?.trim().split(`
|
|
8502
8865
|
`).filter(Boolean) ?? [];
|
|
8503
|
-
const theirChanges =
|
|
8866
|
+
const theirChanges = gitSafe2(`diff --name-only ${mergeBase}..origin/${baseBranch}`, cwd)?.trim().split(`
|
|
8504
8867
|
`).filter(Boolean) ?? [];
|
|
8505
8868
|
const overlapping = ourChanges.filter((f) => theirChanges.includes(f));
|
|
8506
8869
|
return {
|
|
@@ -8513,18 +8876,19 @@ function checkForConflicts(cwd, baseBranch) {
|
|
|
8513
8876
|
function attemptRebase(cwd, baseBranch) {
|
|
8514
8877
|
const log = getLogger();
|
|
8515
8878
|
try {
|
|
8516
|
-
|
|
8879
|
+
git3(`rebase origin/${baseBranch}`, cwd);
|
|
8517
8880
|
log.info(`Successfully rebased onto origin/${baseBranch}`);
|
|
8881
|
+
updateSubmodulesAfterRebase(cwd);
|
|
8518
8882
|
return { success: true };
|
|
8519
8883
|
} catch (_e) {
|
|
8520
8884
|
log.warn("Rebase failed, aborting");
|
|
8521
8885
|
const conflicts = [];
|
|
8522
8886
|
try {
|
|
8523
|
-
const status =
|
|
8887
|
+
const status = git3("diff --name-only --diff-filter=U", cwd);
|
|
8524
8888
|
conflicts.push(...status.trim().split(`
|
|
8525
8889
|
`).filter(Boolean));
|
|
8526
8890
|
} catch {}
|
|
8527
|
-
|
|
8891
|
+
gitSafe2("rebase --abort", cwd);
|
|
8528
8892
|
return { success: false, conflicts };
|
|
8529
8893
|
}
|
|
8530
8894
|
}
|
|
@@ -8566,23 +8930,24 @@ ${bold(yellow("⚠"))} Base branch has ${result.newCommits} new commit${result.n
|
|
|
8566
8930
|
var init_conflict = __esm(() => {
|
|
8567
8931
|
init_terminal();
|
|
8568
8932
|
init_logger();
|
|
8933
|
+
init_submodule();
|
|
8569
8934
|
});
|
|
8570
8935
|
|
|
8571
8936
|
// src/core/run-state.ts
|
|
8572
8937
|
import {
|
|
8573
|
-
existsSync as
|
|
8938
|
+
existsSync as existsSync18,
|
|
8574
8939
|
mkdirSync as mkdirSync11,
|
|
8575
8940
|
readFileSync as readFileSync12,
|
|
8576
8941
|
unlinkSync as unlinkSync4,
|
|
8577
8942
|
writeFileSync as writeFileSync8
|
|
8578
8943
|
} from "node:fs";
|
|
8579
|
-
import { dirname as dirname5, join as
|
|
8944
|
+
import { dirname as dirname5, join as join18 } from "node:path";
|
|
8580
8945
|
function getRunStatePath(projectRoot) {
|
|
8581
|
-
return
|
|
8946
|
+
return join18(projectRoot, ".locus", "run-state.json");
|
|
8582
8947
|
}
|
|
8583
8948
|
function loadRunState(projectRoot) {
|
|
8584
8949
|
const path = getRunStatePath(projectRoot);
|
|
8585
|
-
if (!
|
|
8950
|
+
if (!existsSync18(path))
|
|
8586
8951
|
return null;
|
|
8587
8952
|
try {
|
|
8588
8953
|
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
@@ -8594,7 +8959,7 @@ function loadRunState(projectRoot) {
|
|
|
8594
8959
|
function saveRunState(projectRoot, state) {
|
|
8595
8960
|
const path = getRunStatePath(projectRoot);
|
|
8596
8961
|
const dir = dirname5(path);
|
|
8597
|
-
if (!
|
|
8962
|
+
if (!existsSync18(dir)) {
|
|
8598
8963
|
mkdirSync11(dir, { recursive: true });
|
|
8599
8964
|
}
|
|
8600
8965
|
writeFileSync8(path, `${JSON.stringify(state, null, 2)}
|
|
@@ -8602,7 +8967,7 @@ function saveRunState(projectRoot, state) {
|
|
|
8602
8967
|
}
|
|
8603
8968
|
function clearRunState(projectRoot) {
|
|
8604
8969
|
const path = getRunStatePath(projectRoot);
|
|
8605
|
-
if (
|
|
8970
|
+
if (existsSync18(path)) {
|
|
8606
8971
|
unlinkSync4(path);
|
|
8607
8972
|
}
|
|
8608
8973
|
}
|
|
@@ -8742,28 +9107,28 @@ var init_shutdown = __esm(() => {
|
|
|
8742
9107
|
});
|
|
8743
9108
|
|
|
8744
9109
|
// src/core/worktree.ts
|
|
8745
|
-
import { execSync as
|
|
8746
|
-
import { existsSync as
|
|
8747
|
-
import { join as
|
|
8748
|
-
function
|
|
8749
|
-
return
|
|
9110
|
+
import { execSync as execSync13 } from "node:child_process";
|
|
9111
|
+
import { existsSync as existsSync19, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
|
|
9112
|
+
import { join as join19 } from "node:path";
|
|
9113
|
+
function git4(args, cwd) {
|
|
9114
|
+
return execSync13(`git ${args}`, {
|
|
8750
9115
|
cwd,
|
|
8751
9116
|
encoding: "utf-8",
|
|
8752
9117
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8753
9118
|
});
|
|
8754
9119
|
}
|
|
8755
|
-
function
|
|
9120
|
+
function gitSafe3(args, cwd) {
|
|
8756
9121
|
try {
|
|
8757
|
-
return
|
|
9122
|
+
return git4(args, cwd);
|
|
8758
9123
|
} catch {
|
|
8759
9124
|
return null;
|
|
8760
9125
|
}
|
|
8761
9126
|
}
|
|
8762
9127
|
function getWorktreeDir(projectRoot) {
|
|
8763
|
-
return
|
|
9128
|
+
return join19(projectRoot, ".locus", "worktrees");
|
|
8764
9129
|
}
|
|
8765
9130
|
function getWorktreePath(projectRoot, issueNumber) {
|
|
8766
|
-
return
|
|
9131
|
+
return join19(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
|
|
8767
9132
|
}
|
|
8768
9133
|
function generateBranchName(issueNumber) {
|
|
8769
9134
|
const randomSuffix = Math.random().toString(36).slice(2, 8);
|
|
@@ -8771,7 +9136,7 @@ function generateBranchName(issueNumber) {
|
|
|
8771
9136
|
}
|
|
8772
9137
|
function getWorktreeBranch(worktreePath) {
|
|
8773
9138
|
try {
|
|
8774
|
-
return
|
|
9139
|
+
return execSync13("git branch --show-current", {
|
|
8775
9140
|
cwd: worktreePath,
|
|
8776
9141
|
encoding: "utf-8",
|
|
8777
9142
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8783,7 +9148,7 @@ function getWorktreeBranch(worktreePath) {
|
|
|
8783
9148
|
function createWorktree(projectRoot, issueNumber, baseBranch) {
|
|
8784
9149
|
const log = getLogger();
|
|
8785
9150
|
const worktreePath = getWorktreePath(projectRoot, issueNumber);
|
|
8786
|
-
if (
|
|
9151
|
+
if (existsSync19(worktreePath)) {
|
|
8787
9152
|
log.verbose(`Worktree already exists for issue #${issueNumber}`);
|
|
8788
9153
|
const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/issue-${issueNumber}`;
|
|
8789
9154
|
return {
|
|
@@ -8794,7 +9159,8 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
|
|
|
8794
9159
|
};
|
|
8795
9160
|
}
|
|
8796
9161
|
const branch = generateBranchName(issueNumber);
|
|
8797
|
-
|
|
9162
|
+
git4(`worktree add ${JSON.stringify(worktreePath)} -b ${branch} ${baseBranch}`, projectRoot);
|
|
9163
|
+
initSubmodules(worktreePath);
|
|
8798
9164
|
log.info(`Created worktree for issue #${issueNumber}`, {
|
|
8799
9165
|
path: worktreePath,
|
|
8800
9166
|
branch,
|
|
@@ -8810,30 +9176,30 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
|
|
|
8810
9176
|
function removeWorktree(projectRoot, issueNumber) {
|
|
8811
9177
|
const log = getLogger();
|
|
8812
9178
|
const worktreePath = getWorktreePath(projectRoot, issueNumber);
|
|
8813
|
-
if (!
|
|
9179
|
+
if (!existsSync19(worktreePath)) {
|
|
8814
9180
|
log.verbose(`Worktree for issue #${issueNumber} does not exist`);
|
|
8815
9181
|
return;
|
|
8816
9182
|
}
|
|
8817
9183
|
const branch = getWorktreeBranch(worktreePath);
|
|
8818
9184
|
try {
|
|
8819
|
-
|
|
9185
|
+
git4(`worktree remove ${JSON.stringify(worktreePath)} --force`, projectRoot);
|
|
8820
9186
|
log.info(`Removed worktree for issue #${issueNumber}`);
|
|
8821
9187
|
} catch (e) {
|
|
8822
9188
|
log.warn(`Failed to remove worktree: ${e}`);
|
|
8823
|
-
|
|
9189
|
+
gitSafe3(`worktree remove ${JSON.stringify(worktreePath)} --force`, projectRoot);
|
|
8824
9190
|
}
|
|
8825
9191
|
if (branch) {
|
|
8826
|
-
|
|
9192
|
+
gitSafe3(`branch -D ${branch}`, projectRoot);
|
|
8827
9193
|
}
|
|
8828
9194
|
}
|
|
8829
9195
|
function listWorktrees(projectRoot) {
|
|
8830
9196
|
const log = getLogger();
|
|
8831
9197
|
const worktreeDir = getWorktreeDir(projectRoot);
|
|
8832
|
-
if (!
|
|
9198
|
+
if (!existsSync19(worktreeDir)) {
|
|
8833
9199
|
return [];
|
|
8834
9200
|
}
|
|
8835
9201
|
const entries = readdirSync6(worktreeDir).filter((entry) => entry.startsWith("issue-"));
|
|
8836
|
-
const gitWorktreeList =
|
|
9202
|
+
const gitWorktreeList = gitSafe3("worktree list --porcelain", projectRoot);
|
|
8837
9203
|
const activeWorktrees = new Set;
|
|
8838
9204
|
if (gitWorktreeList) {
|
|
8839
9205
|
for (const line of gitWorktreeList.split(`
|
|
@@ -8849,7 +9215,7 @@ function listWorktrees(projectRoot) {
|
|
|
8849
9215
|
if (!match)
|
|
8850
9216
|
continue;
|
|
8851
9217
|
const issueNumber = Number.parseInt(match[1], 10);
|
|
8852
|
-
const path =
|
|
9218
|
+
const path = join19(worktreeDir, entry);
|
|
8853
9219
|
const branch = getWorktreeBranch(path) ?? `locus/issue-${issueNumber}`;
|
|
8854
9220
|
let resolvedPath;
|
|
8855
9221
|
try {
|
|
@@ -8883,12 +9249,13 @@ function cleanupStaleWorktrees(projectRoot) {
|
|
|
8883
9249
|
}
|
|
8884
9250
|
}
|
|
8885
9251
|
if (cleaned > 0) {
|
|
8886
|
-
|
|
9252
|
+
gitSafe3("worktree prune", projectRoot);
|
|
8887
9253
|
}
|
|
8888
9254
|
return cleaned;
|
|
8889
9255
|
}
|
|
8890
9256
|
var init_worktree = __esm(() => {
|
|
8891
9257
|
init_logger();
|
|
9258
|
+
init_submodule();
|
|
8892
9259
|
});
|
|
8893
9260
|
|
|
8894
9261
|
// src/commands/run.ts
|
|
@@ -8896,7 +9263,7 @@ var exports_run = {};
|
|
|
8896
9263
|
__export(exports_run, {
|
|
8897
9264
|
runCommand: () => runCommand
|
|
8898
9265
|
});
|
|
8899
|
-
import { execSync as
|
|
9266
|
+
import { execSync as execSync14 } from "node:child_process";
|
|
8900
9267
|
function resolveExecutionContext(config, modelOverride) {
|
|
8901
9268
|
const model = modelOverride ?? config.ai.model;
|
|
8902
9269
|
const provider = inferProviderFromModel(model) ?? config.ai.provider;
|
|
@@ -9047,7 +9414,7 @@ ${yellow("⚠")} A sprint run is already in progress.
|
|
|
9047
9414
|
}
|
|
9048
9415
|
if (!flags.dryRun) {
|
|
9049
9416
|
try {
|
|
9050
|
-
|
|
9417
|
+
execSync14(`git checkout -B ${branchName}`, {
|
|
9051
9418
|
cwd: projectRoot,
|
|
9052
9419
|
encoding: "utf-8",
|
|
9053
9420
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9097,7 +9464,7 @@ ${red("✗")} Auto-rebase failed. Resolve manually.
|
|
|
9097
9464
|
let sprintContext;
|
|
9098
9465
|
if (i > 0 && !flags.dryRun) {
|
|
9099
9466
|
try {
|
|
9100
|
-
sprintContext =
|
|
9467
|
+
sprintContext = execSync14(`git diff origin/${config.agent.baseBranch}..HEAD`, {
|
|
9101
9468
|
cwd: projectRoot,
|
|
9102
9469
|
encoding: "utf-8",
|
|
9103
9470
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9162,7 +9529,7 @@ ${bold("Summary:")}
|
|
|
9162
9529
|
const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
9163
9530
|
if (prNumber !== undefined) {
|
|
9164
9531
|
try {
|
|
9165
|
-
|
|
9532
|
+
execSync14(`git checkout ${config.agent.baseBranch}`, {
|
|
9166
9533
|
cwd: projectRoot,
|
|
9167
9534
|
encoding: "utf-8",
|
|
9168
9535
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9177,6 +9544,7 @@ ${bold("Summary:")}
|
|
|
9177
9544
|
}
|
|
9178
9545
|
}
|
|
9179
9546
|
async function handleSingleIssue(projectRoot, config, issueNumber, flags, sandboxed) {
|
|
9547
|
+
const log = getLogger();
|
|
9180
9548
|
const execution = resolveExecutionContext(config, flags.model);
|
|
9181
9549
|
let isSprintIssue = false;
|
|
9182
9550
|
try {
|
|
@@ -9185,7 +9553,7 @@ async function handleSingleIssue(projectRoot, config, issueNumber, flags, sandbo
|
|
|
9185
9553
|
} catch {}
|
|
9186
9554
|
if (isSprintIssue) {
|
|
9187
9555
|
process.stderr.write(`
|
|
9188
|
-
${bold("Running sprint issue")} ${cyan(`#${issueNumber}`)} ${dim("(sequential
|
|
9556
|
+
${bold("Running sprint issue")} ${cyan(`#${issueNumber}`)} ${dim("(sequential)")}
|
|
9189
9557
|
|
|
9190
9558
|
`);
|
|
9191
9559
|
await executeIssue(projectRoot, {
|
|
@@ -9198,42 +9566,48 @@ ${bold("Running sprint issue")} ${cyan(`#${issueNumber}`)} ${dim("(sequential, n
|
|
|
9198
9566
|
});
|
|
9199
9567
|
return;
|
|
9200
9568
|
}
|
|
9569
|
+
const randomSuffix = Math.random().toString(36).slice(2, 8);
|
|
9570
|
+
const branchName = `locus/issue-${issueNumber}-${randomSuffix}`;
|
|
9201
9571
|
process.stderr.write(`
|
|
9202
|
-
${bold("Running issue")} ${cyan(`#${issueNumber}`)} ${dim(
|
|
9572
|
+
${bold("Running issue")} ${cyan(`#${issueNumber}`)} ${dim(`(branch: ${branchName})`)}
|
|
9203
9573
|
|
|
9204
9574
|
`);
|
|
9205
|
-
let worktreePath;
|
|
9206
9575
|
if (!flags.dryRun) {
|
|
9207
9576
|
try {
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9577
|
+
execSync14(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
|
|
9578
|
+
cwd: projectRoot,
|
|
9579
|
+
encoding: "utf-8",
|
|
9580
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9581
|
+
});
|
|
9582
|
+
log.info(`Checked out branch ${branchName}`);
|
|
9213
9583
|
} catch (e) {
|
|
9214
|
-
process.stderr.write(`${yellow("⚠")} Could not create
|
|
9584
|
+
process.stderr.write(`${yellow("⚠")} Could not create branch: ${e}
|
|
9215
9585
|
`);
|
|
9216
|
-
process.stderr.write(` ${dim("
|
|
9586
|
+
process.stderr.write(` ${dim("Running on current branch instead.")}
|
|
9217
9587
|
|
|
9218
9588
|
`);
|
|
9219
9589
|
}
|
|
9220
9590
|
}
|
|
9221
9591
|
const result = await executeIssue(projectRoot, {
|
|
9222
9592
|
issueNumber,
|
|
9223
|
-
worktreePath,
|
|
9224
9593
|
provider: execution.provider,
|
|
9225
9594
|
model: execution.model,
|
|
9226
9595
|
dryRun: flags.dryRun,
|
|
9227
9596
|
sandboxed,
|
|
9228
9597
|
sandboxName: execution.sandboxName
|
|
9229
9598
|
});
|
|
9230
|
-
if (
|
|
9599
|
+
if (!flags.dryRun) {
|
|
9231
9600
|
if (result.success) {
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9601
|
+
try {
|
|
9602
|
+
execSync14(`git checkout ${config.agent.baseBranch}`, {
|
|
9603
|
+
cwd: projectRoot,
|
|
9604
|
+
encoding: "utf-8",
|
|
9605
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9606
|
+
});
|
|
9607
|
+
log.info(`Checked out ${config.agent.baseBranch}`);
|
|
9608
|
+
} catch {}
|
|
9235
9609
|
} else {
|
|
9236
|
-
process.stderr.write(` ${yellow("⚠")}
|
|
9610
|
+
process.stderr.write(` ${yellow("⚠")} Branch ${dim(branchName)} preserved for debugging.
|
|
9237
9611
|
`);
|
|
9238
9612
|
}
|
|
9239
9613
|
}
|
|
@@ -9362,13 +9736,13 @@ ${bold("Resuming")} ${state.type} run ${dim(state.runId)}
|
|
|
9362
9736
|
`);
|
|
9363
9737
|
if (state.type === "sprint" && state.branch) {
|
|
9364
9738
|
try {
|
|
9365
|
-
const currentBranch =
|
|
9739
|
+
const currentBranch = execSync14("git rev-parse --abbrev-ref HEAD", {
|
|
9366
9740
|
cwd: projectRoot,
|
|
9367
9741
|
encoding: "utf-8",
|
|
9368
9742
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9369
9743
|
}).trim();
|
|
9370
9744
|
if (currentBranch !== state.branch) {
|
|
9371
|
-
|
|
9745
|
+
execSync14(`git checkout ${state.branch}`, {
|
|
9372
9746
|
cwd: projectRoot,
|
|
9373
9747
|
encoding: "utf-8",
|
|
9374
9748
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9435,7 +9809,7 @@ ${bold("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fail
|
|
|
9435
9809
|
const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
9436
9810
|
if (prNumber !== undefined) {
|
|
9437
9811
|
try {
|
|
9438
|
-
|
|
9812
|
+
execSync14(`git checkout ${config.agent.baseBranch}`, {
|
|
9439
9813
|
cwd: projectRoot,
|
|
9440
9814
|
encoding: "utf-8",
|
|
9441
9815
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9466,14 +9840,19 @@ function getOrder2(issue) {
|
|
|
9466
9840
|
}
|
|
9467
9841
|
function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
9468
9842
|
try {
|
|
9469
|
-
const
|
|
9843
|
+
const committedSubmodules = commitDirtySubmodules(projectRoot, issueNumber, issueTitle);
|
|
9844
|
+
if (committedSubmodules.length > 0) {
|
|
9845
|
+
process.stderr.write(` ${dim(`Committed submodule changes: ${committedSubmodules.join(", ")}`)}
|
|
9846
|
+
`);
|
|
9847
|
+
}
|
|
9848
|
+
const status = execSync14("git status --porcelain", {
|
|
9470
9849
|
cwd: projectRoot,
|
|
9471
9850
|
encoding: "utf-8",
|
|
9472
9851
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9473
9852
|
}).trim();
|
|
9474
9853
|
if (!status)
|
|
9475
9854
|
return;
|
|
9476
|
-
|
|
9855
|
+
execSync14("git add -A", {
|
|
9477
9856
|
cwd: projectRoot,
|
|
9478
9857
|
encoding: "utf-8",
|
|
9479
9858
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9481,7 +9860,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
9481
9860
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
9482
9861
|
|
|
9483
9862
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
9484
|
-
|
|
9863
|
+
execSync14(`git commit -F -`, {
|
|
9485
9864
|
input: message,
|
|
9486
9865
|
cwd: projectRoot,
|
|
9487
9866
|
encoding: "utf-8",
|
|
@@ -9495,7 +9874,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
9495
9874
|
if (!config.agent.autoPR)
|
|
9496
9875
|
return;
|
|
9497
9876
|
try {
|
|
9498
|
-
const diff =
|
|
9877
|
+
const diff = execSync14(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
9499
9878
|
cwd: projectRoot,
|
|
9500
9879
|
encoding: "utf-8",
|
|
9501
9880
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9505,16 +9884,24 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
9505
9884
|
`);
|
|
9506
9885
|
return;
|
|
9507
9886
|
}
|
|
9508
|
-
|
|
9887
|
+
pushSubmoduleBranches(projectRoot);
|
|
9888
|
+
execSync14(`git push -u origin ${branchName}`, {
|
|
9509
9889
|
cwd: projectRoot,
|
|
9510
9890
|
encoding: "utf-8",
|
|
9511
9891
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9512
9892
|
});
|
|
9513
9893
|
const taskLines = tasks.map((t) => `- Closes #${t.issue}${t.title ? `: ${t.title}` : ""}`).join(`
|
|
9514
9894
|
`);
|
|
9515
|
-
const
|
|
9895
|
+
const submoduleSummary = getSubmoduleChangeSummary(projectRoot, config.agent.baseBranch);
|
|
9896
|
+
let prBody = `## Sprint: ${sprintName}
|
|
9516
9897
|
|
|
9517
|
-
${taskLines}
|
|
9898
|
+
${taskLines}`;
|
|
9899
|
+
if (submoduleSummary) {
|
|
9900
|
+
prBody += `
|
|
9901
|
+
|
|
9902
|
+
${submoduleSummary}`;
|
|
9903
|
+
}
|
|
9904
|
+
prBody += `
|
|
9518
9905
|
|
|
9519
9906
|
---
|
|
9520
9907
|
|
|
@@ -9531,8 +9918,8 @@ ${taskLines}
|
|
|
9531
9918
|
}
|
|
9532
9919
|
}
|
|
9533
9920
|
var init_run = __esm(() => {
|
|
9534
|
-
init_ai_models();
|
|
9535
9921
|
init_agent();
|
|
9922
|
+
init_ai_models();
|
|
9536
9923
|
init_config();
|
|
9537
9924
|
init_conflict();
|
|
9538
9925
|
init_github();
|
|
@@ -9541,6 +9928,7 @@ var init_run = __esm(() => {
|
|
|
9541
9928
|
init_run_state();
|
|
9542
9929
|
init_sandbox();
|
|
9543
9930
|
init_shutdown();
|
|
9931
|
+
init_submodule();
|
|
9544
9932
|
init_worktree();
|
|
9545
9933
|
init_progress();
|
|
9546
9934
|
init_terminal();
|
|
@@ -9655,13 +10043,13 @@ __export(exports_plan, {
|
|
|
9655
10043
|
parsePlanArgs: () => parsePlanArgs
|
|
9656
10044
|
});
|
|
9657
10045
|
import {
|
|
9658
|
-
existsSync as
|
|
10046
|
+
existsSync as existsSync20,
|
|
9659
10047
|
mkdirSync as mkdirSync12,
|
|
9660
10048
|
readdirSync as readdirSync7,
|
|
9661
10049
|
readFileSync as readFileSync13,
|
|
9662
10050
|
writeFileSync as writeFileSync9
|
|
9663
10051
|
} from "node:fs";
|
|
9664
|
-
import { join as
|
|
10052
|
+
import { join as join20 } from "node:path";
|
|
9665
10053
|
function printHelp() {
|
|
9666
10054
|
process.stderr.write(`
|
|
9667
10055
|
${bold("locus plan")} — AI-powered sprint planning
|
|
@@ -9692,11 +10080,11 @@ function normalizeSprintName(name) {
|
|
|
9692
10080
|
return name.trim().toLowerCase();
|
|
9693
10081
|
}
|
|
9694
10082
|
function getPlansDir(projectRoot) {
|
|
9695
|
-
return
|
|
10083
|
+
return join20(projectRoot, ".locus", "plans");
|
|
9696
10084
|
}
|
|
9697
10085
|
function ensurePlansDir(projectRoot) {
|
|
9698
10086
|
const dir = getPlansDir(projectRoot);
|
|
9699
|
-
if (!
|
|
10087
|
+
if (!existsSync20(dir)) {
|
|
9700
10088
|
mkdirSync12(dir, { recursive: true });
|
|
9701
10089
|
}
|
|
9702
10090
|
return dir;
|
|
@@ -9706,14 +10094,14 @@ function generateId() {
|
|
|
9706
10094
|
}
|
|
9707
10095
|
function loadPlanFile(projectRoot, id) {
|
|
9708
10096
|
const dir = getPlansDir(projectRoot);
|
|
9709
|
-
if (!
|
|
10097
|
+
if (!existsSync20(dir))
|
|
9710
10098
|
return null;
|
|
9711
10099
|
const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
|
|
9712
10100
|
const match = files.find((f) => f.startsWith(id));
|
|
9713
10101
|
if (!match)
|
|
9714
10102
|
return null;
|
|
9715
10103
|
try {
|
|
9716
|
-
const content = readFileSync13(
|
|
10104
|
+
const content = readFileSync13(join20(dir, match), "utf-8");
|
|
9717
10105
|
return JSON.parse(content);
|
|
9718
10106
|
} catch {
|
|
9719
10107
|
return null;
|
|
@@ -9759,7 +10147,7 @@ async function planCommand(projectRoot, args, flags = {}) {
|
|
|
9759
10147
|
}
|
|
9760
10148
|
function handleListPlans(projectRoot) {
|
|
9761
10149
|
const dir = getPlansDir(projectRoot);
|
|
9762
|
-
if (!
|
|
10150
|
+
if (!existsSync20(dir)) {
|
|
9763
10151
|
process.stderr.write(`${dim("No saved plans yet.")}
|
|
9764
10152
|
`);
|
|
9765
10153
|
return;
|
|
@@ -9777,7 +10165,7 @@ ${bold("Saved Plans:")}
|
|
|
9777
10165
|
for (const file of files) {
|
|
9778
10166
|
const id = file.replace(".json", "");
|
|
9779
10167
|
try {
|
|
9780
|
-
const content = readFileSync13(
|
|
10168
|
+
const content = readFileSync13(join20(dir, file), "utf-8");
|
|
9781
10169
|
const plan = JSON.parse(content);
|
|
9782
10170
|
const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
|
|
9783
10171
|
const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
|
|
@@ -9888,7 +10276,7 @@ ${bold("Approving plan:")}
|
|
|
9888
10276
|
async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
|
|
9889
10277
|
const id = generateId();
|
|
9890
10278
|
const plansDir = ensurePlansDir(projectRoot);
|
|
9891
|
-
const planPath =
|
|
10279
|
+
const planPath = join20(plansDir, `${id}.json`);
|
|
9892
10280
|
const planPathRelative = `.locus/plans/${id}.json`;
|
|
9893
10281
|
const displayDirective = directive;
|
|
9894
10282
|
process.stderr.write(`
|
|
@@ -9922,7 +10310,7 @@ ${red("✗")} Planning failed: ${aiResult.error}
|
|
|
9922
10310
|
`);
|
|
9923
10311
|
return;
|
|
9924
10312
|
}
|
|
9925
|
-
if (!
|
|
10313
|
+
if (!existsSync20(planPath)) {
|
|
9926
10314
|
process.stderr.write(`
|
|
9927
10315
|
${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
|
|
9928
10316
|
`);
|
|
@@ -10095,15 +10483,15 @@ ${directive}${sprintName ? `
|
|
|
10095
10483
|
|
|
10096
10484
|
**Sprint:** ${sprintName}` : ""}
|
|
10097
10485
|
</directive>`);
|
|
10098
|
-
const locusPath =
|
|
10099
|
-
if (
|
|
10486
|
+
const locusPath = join20(projectRoot, ".locus", "LOCUS.md");
|
|
10487
|
+
if (existsSync20(locusPath)) {
|
|
10100
10488
|
const content = readFileSync13(locusPath, "utf-8");
|
|
10101
10489
|
parts.push(`<project-context>
|
|
10102
10490
|
${content.slice(0, 3000)}
|
|
10103
10491
|
</project-context>`);
|
|
10104
10492
|
}
|
|
10105
|
-
const learningsPath =
|
|
10106
|
-
if (
|
|
10493
|
+
const learningsPath = join20(projectRoot, ".locus", "LEARNINGS.md");
|
|
10494
|
+
if (existsSync20(learningsPath)) {
|
|
10107
10495
|
const content = readFileSync13(learningsPath, "utf-8");
|
|
10108
10496
|
parts.push(`<past-learnings>
|
|
10109
10497
|
${content.slice(0, 2000)}
|
|
@@ -10274,8 +10662,8 @@ var init_plan = __esm(() => {
|
|
|
10274
10662
|
init_run_ai();
|
|
10275
10663
|
init_config();
|
|
10276
10664
|
init_github();
|
|
10277
|
-
init_terminal();
|
|
10278
10665
|
init_sandbox();
|
|
10666
|
+
init_terminal();
|
|
10279
10667
|
});
|
|
10280
10668
|
|
|
10281
10669
|
// src/commands/review.ts
|
|
@@ -10283,9 +10671,9 @@ var exports_review = {};
|
|
|
10283
10671
|
__export(exports_review, {
|
|
10284
10672
|
reviewCommand: () => reviewCommand
|
|
10285
10673
|
});
|
|
10286
|
-
import { execSync as
|
|
10287
|
-
import { existsSync as
|
|
10288
|
-
import { join as
|
|
10674
|
+
import { execSync as execSync15 } from "node:child_process";
|
|
10675
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14 } from "node:fs";
|
|
10676
|
+
import { join as join21 } from "node:path";
|
|
10289
10677
|
function printHelp2() {
|
|
10290
10678
|
process.stderr.write(`
|
|
10291
10679
|
${bold("locus review")} — AI-powered code review
|
|
@@ -10361,7 +10749,7 @@ ${bold("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red(`
|
|
|
10361
10749
|
async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
|
|
10362
10750
|
let prInfo;
|
|
10363
10751
|
try {
|
|
10364
|
-
const result =
|
|
10752
|
+
const result = execSync15(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10365
10753
|
const raw = JSON.parse(result);
|
|
10366
10754
|
prInfo = {
|
|
10367
10755
|
number: raw.number,
|
|
@@ -10427,7 +10815,7 @@ ${output.slice(0, 60000)}
|
|
|
10427
10815
|
|
|
10428
10816
|
---
|
|
10429
10817
|
_Reviewed by Locus AI (${config.ai.provider}/${flags.model ?? config.ai.model})_`;
|
|
10430
|
-
|
|
10818
|
+
execSync15(`gh pr comment ${pr.number} --body ${JSON.stringify(reviewBody)}`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10431
10819
|
process.stderr.write(` ${green("✓")} Review posted ${dim(`(${timer.formatted()})`)}
|
|
10432
10820
|
`);
|
|
10433
10821
|
} catch (e) {
|
|
@@ -10445,8 +10833,8 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
|
|
|
10445
10833
|
parts.push(`<role>
|
|
10446
10834
|
You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
|
|
10447
10835
|
</role>`);
|
|
10448
|
-
const locusPath =
|
|
10449
|
-
if (
|
|
10836
|
+
const locusPath = join21(projectRoot, ".locus", "LOCUS.md");
|
|
10837
|
+
if (existsSync21(locusPath)) {
|
|
10450
10838
|
const content = readFileSync14(locusPath, "utf-8");
|
|
10451
10839
|
parts.push(`<project-context>
|
|
10452
10840
|
${content.slice(0, 2000)}
|
|
@@ -10507,7 +10895,7 @@ var exports_iterate = {};
|
|
|
10507
10895
|
__export(exports_iterate, {
|
|
10508
10896
|
iterateCommand: () => iterateCommand
|
|
10509
10897
|
});
|
|
10510
|
-
import { execSync as
|
|
10898
|
+
import { execSync as execSync16 } from "node:child_process";
|
|
10511
10899
|
function printHelp3() {
|
|
10512
10900
|
process.stderr.write(`
|
|
10513
10901
|
${bold("locus iterate")} — Re-execute tasks with PR feedback
|
|
@@ -10717,12 +11105,12 @@ ${bold("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red(`✗ ${fa
|
|
|
10717
11105
|
}
|
|
10718
11106
|
function findPRForIssue(projectRoot, issueNumber) {
|
|
10719
11107
|
try {
|
|
10720
|
-
const result =
|
|
11108
|
+
const result = execSync16(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10721
11109
|
const parsed = JSON.parse(result);
|
|
10722
11110
|
if (parsed.length > 0) {
|
|
10723
11111
|
return parsed[0].number;
|
|
10724
11112
|
}
|
|
10725
|
-
const branchResult =
|
|
11113
|
+
const branchResult = execSync16(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
10726
11114
|
const branchParsed = JSON.parse(branchResult);
|
|
10727
11115
|
if (branchParsed.length > 0) {
|
|
10728
11116
|
return branchParsed[0].number;
|
|
@@ -10757,14 +11145,14 @@ __export(exports_discuss, {
|
|
|
10757
11145
|
discussCommand: () => discussCommand
|
|
10758
11146
|
});
|
|
10759
11147
|
import {
|
|
10760
|
-
existsSync as
|
|
11148
|
+
existsSync as existsSync22,
|
|
10761
11149
|
mkdirSync as mkdirSync13,
|
|
10762
11150
|
readdirSync as readdirSync8,
|
|
10763
11151
|
readFileSync as readFileSync15,
|
|
10764
11152
|
unlinkSync as unlinkSync5,
|
|
10765
11153
|
writeFileSync as writeFileSync10
|
|
10766
11154
|
} from "node:fs";
|
|
10767
|
-
import { join as
|
|
11155
|
+
import { join as join22 } from "node:path";
|
|
10768
11156
|
function printHelp4() {
|
|
10769
11157
|
process.stderr.write(`
|
|
10770
11158
|
${bold("locus discuss")} — AI-powered architectural discussions
|
|
@@ -10786,11 +11174,11 @@ ${bold("Examples:")}
|
|
|
10786
11174
|
`);
|
|
10787
11175
|
}
|
|
10788
11176
|
function getDiscussionsDir(projectRoot) {
|
|
10789
|
-
return
|
|
11177
|
+
return join22(projectRoot, ".locus", "discussions");
|
|
10790
11178
|
}
|
|
10791
11179
|
function ensureDiscussionsDir(projectRoot) {
|
|
10792
11180
|
const dir = getDiscussionsDir(projectRoot);
|
|
10793
|
-
if (!
|
|
11181
|
+
if (!existsSync22(dir)) {
|
|
10794
11182
|
mkdirSync13(dir, { recursive: true });
|
|
10795
11183
|
}
|
|
10796
11184
|
return dir;
|
|
@@ -10825,7 +11213,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
|
|
|
10825
11213
|
}
|
|
10826
11214
|
function listDiscussions(projectRoot) {
|
|
10827
11215
|
const dir = getDiscussionsDir(projectRoot);
|
|
10828
|
-
if (!
|
|
11216
|
+
if (!existsSync22(dir)) {
|
|
10829
11217
|
process.stderr.write(`${dim("No discussions yet.")}
|
|
10830
11218
|
`);
|
|
10831
11219
|
return;
|
|
@@ -10842,7 +11230,7 @@ ${bold("Discussions:")}
|
|
|
10842
11230
|
`);
|
|
10843
11231
|
for (const file of files) {
|
|
10844
11232
|
const id = file.replace(".md", "");
|
|
10845
|
-
const content = readFileSync15(
|
|
11233
|
+
const content = readFileSync15(join22(dir, file), "utf-8");
|
|
10846
11234
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
10847
11235
|
const title = titleMatch ? titleMatch[1] : id;
|
|
10848
11236
|
const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
|
|
@@ -10860,7 +11248,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
10860
11248
|
return;
|
|
10861
11249
|
}
|
|
10862
11250
|
const dir = getDiscussionsDir(projectRoot);
|
|
10863
|
-
if (!
|
|
11251
|
+
if (!existsSync22(dir)) {
|
|
10864
11252
|
process.stderr.write(`${red("✗")} No discussions found.
|
|
10865
11253
|
`);
|
|
10866
11254
|
return;
|
|
@@ -10872,7 +11260,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
10872
11260
|
`);
|
|
10873
11261
|
return;
|
|
10874
11262
|
}
|
|
10875
|
-
const content = readFileSync15(
|
|
11263
|
+
const content = readFileSync15(join22(dir, match), "utf-8");
|
|
10876
11264
|
process.stdout.write(`${content}
|
|
10877
11265
|
`);
|
|
10878
11266
|
}
|
|
@@ -10883,7 +11271,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
10883
11271
|
return;
|
|
10884
11272
|
}
|
|
10885
11273
|
const dir = getDiscussionsDir(projectRoot);
|
|
10886
|
-
if (!
|
|
11274
|
+
if (!existsSync22(dir)) {
|
|
10887
11275
|
process.stderr.write(`${red("✗")} No discussions found.
|
|
10888
11276
|
`);
|
|
10889
11277
|
return;
|
|
@@ -10895,7 +11283,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
10895
11283
|
`);
|
|
10896
11284
|
return;
|
|
10897
11285
|
}
|
|
10898
|
-
unlinkSync5(
|
|
11286
|
+
unlinkSync5(join22(dir, match));
|
|
10899
11287
|
process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
|
|
10900
11288
|
`);
|
|
10901
11289
|
}
|
|
@@ -10908,7 +11296,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
10908
11296
|
return;
|
|
10909
11297
|
}
|
|
10910
11298
|
const dir = getDiscussionsDir(projectRoot);
|
|
10911
|
-
if (!
|
|
11299
|
+
if (!existsSync22(dir)) {
|
|
10912
11300
|
process.stderr.write(`${red("✗")} No discussions found.
|
|
10913
11301
|
`);
|
|
10914
11302
|
return;
|
|
@@ -10920,7 +11308,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
10920
11308
|
`);
|
|
10921
11309
|
return;
|
|
10922
11310
|
}
|
|
10923
|
-
const content = readFileSync15(
|
|
11311
|
+
const content = readFileSync15(join22(dir, match), "utf-8");
|
|
10924
11312
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
10925
11313
|
const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
|
|
10926
11314
|
await planCommand(projectRoot, [
|
|
@@ -11034,7 +11422,7 @@ ${turn.content}`;
|
|
|
11034
11422
|
...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
|
|
11035
11423
|
].join(`
|
|
11036
11424
|
`);
|
|
11037
|
-
writeFileSync10(
|
|
11425
|
+
writeFileSync10(join22(dir, `${id}.md`), markdown, "utf-8");
|
|
11038
11426
|
process.stderr.write(`
|
|
11039
11427
|
${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
|
|
11040
11428
|
`);
|
|
@@ -11049,15 +11437,15 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
|
|
|
11049
11437
|
parts.push(`<role>
|
|
11050
11438
|
You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
|
|
11051
11439
|
</role>`);
|
|
11052
|
-
const locusPath =
|
|
11053
|
-
if (
|
|
11440
|
+
const locusPath = join22(projectRoot, ".locus", "LOCUS.md");
|
|
11441
|
+
if (existsSync22(locusPath)) {
|
|
11054
11442
|
const content = readFileSync15(locusPath, "utf-8");
|
|
11055
11443
|
parts.push(`<project-context>
|
|
11056
11444
|
${content.slice(0, 3000)}
|
|
11057
11445
|
</project-context>`);
|
|
11058
11446
|
}
|
|
11059
|
-
const learningsPath =
|
|
11060
|
-
if (
|
|
11447
|
+
const learningsPath = join22(projectRoot, ".locus", "LEARNINGS.md");
|
|
11448
|
+
if (existsSync22(learningsPath)) {
|
|
11061
11449
|
const content = readFileSync15(learningsPath, "utf-8");
|
|
11062
11450
|
parts.push(`<past-learnings>
|
|
11063
11451
|
${content.slice(0, 2000)}
|
|
@@ -11129,8 +11517,8 @@ __export(exports_artifacts, {
|
|
|
11129
11517
|
formatDate: () => formatDate2,
|
|
11130
11518
|
artifactsCommand: () => artifactsCommand
|
|
11131
11519
|
});
|
|
11132
|
-
import { existsSync as
|
|
11133
|
-
import { join as
|
|
11520
|
+
import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync16, statSync as statSync4 } from "node:fs";
|
|
11521
|
+
import { join as join23 } from "node:path";
|
|
11134
11522
|
function printHelp5() {
|
|
11135
11523
|
process.stderr.write(`
|
|
11136
11524
|
${bold("locus artifacts")} — View and manage AI-generated artifacts
|
|
@@ -11150,14 +11538,14 @@ ${dim("Artifact names support partial matching.")}
|
|
|
11150
11538
|
`);
|
|
11151
11539
|
}
|
|
11152
11540
|
function getArtifactsDir(projectRoot) {
|
|
11153
|
-
return
|
|
11541
|
+
return join23(projectRoot, ".locus", "artifacts");
|
|
11154
11542
|
}
|
|
11155
11543
|
function listArtifacts(projectRoot) {
|
|
11156
11544
|
const dir = getArtifactsDir(projectRoot);
|
|
11157
|
-
if (!
|
|
11545
|
+
if (!existsSync23(dir))
|
|
11158
11546
|
return [];
|
|
11159
11547
|
return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
11160
|
-
const filePath =
|
|
11548
|
+
const filePath = join23(dir, fileName);
|
|
11161
11549
|
const stat = statSync4(filePath);
|
|
11162
11550
|
return {
|
|
11163
11551
|
name: fileName.replace(/\.md$/, ""),
|
|
@@ -11170,8 +11558,8 @@ function listArtifacts(projectRoot) {
|
|
|
11170
11558
|
function readArtifact(projectRoot, name) {
|
|
11171
11559
|
const dir = getArtifactsDir(projectRoot);
|
|
11172
11560
|
const fileName = name.endsWith(".md") ? name : `${name}.md`;
|
|
11173
|
-
const filePath =
|
|
11174
|
-
if (!
|
|
11561
|
+
const filePath = join23(dir, fileName);
|
|
11562
|
+
if (!existsSync23(filePath))
|
|
11175
11563
|
return null;
|
|
11176
11564
|
const stat = statSync4(filePath);
|
|
11177
11565
|
return {
|
|
@@ -11341,10 +11729,10 @@ __export(exports_sandbox2, {
|
|
|
11341
11729
|
parseSandboxInstallArgs: () => parseSandboxInstallArgs,
|
|
11342
11730
|
parseSandboxExecArgs: () => parseSandboxExecArgs
|
|
11343
11731
|
});
|
|
11344
|
-
import { execSync as
|
|
11732
|
+
import { execSync as execSync17, spawn as spawn6 } from "node:child_process";
|
|
11345
11733
|
import { createHash } from "node:crypto";
|
|
11346
|
-
import { existsSync as
|
|
11347
|
-
import { basename as basename4, join as
|
|
11734
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17 } from "node:fs";
|
|
11735
|
+
import { basename as basename4, join as join24 } from "node:path";
|
|
11348
11736
|
function printSandboxHelp() {
|
|
11349
11737
|
process.stderr.write(`
|
|
11350
11738
|
${bold("locus sandbox")} — Manage Docker sandbox lifecycle
|
|
@@ -11520,7 +11908,7 @@ function handleRemove(projectRoot) {
|
|
|
11520
11908
|
process.stderr.write(`Removing sandbox ${bold(sandboxName)}...
|
|
11521
11909
|
`);
|
|
11522
11910
|
try {
|
|
11523
|
-
|
|
11911
|
+
execSync17(`docker sandbox rm ${sandboxName}`, {
|
|
11524
11912
|
encoding: "utf-8",
|
|
11525
11913
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11526
11914
|
timeout: 15000
|
|
@@ -11794,7 +12182,7 @@ async function handleLogs(projectRoot, args) {
|
|
|
11794
12182
|
}
|
|
11795
12183
|
function detectPackageManager(projectRoot) {
|
|
11796
12184
|
try {
|
|
11797
|
-
const raw = readFileSync17(
|
|
12185
|
+
const raw = readFileSync17(join24(projectRoot, "package.json"), "utf-8");
|
|
11798
12186
|
const pkgJson = JSON.parse(raw);
|
|
11799
12187
|
if (typeof pkgJson.packageManager === "string") {
|
|
11800
12188
|
const name = pkgJson.packageManager.split("@")[0];
|
|
@@ -11803,13 +12191,13 @@ function detectPackageManager(projectRoot) {
|
|
|
11803
12191
|
}
|
|
11804
12192
|
}
|
|
11805
12193
|
} catch {}
|
|
11806
|
-
if (
|
|
12194
|
+
if (existsSync24(join24(projectRoot, "bun.lock")) || existsSync24(join24(projectRoot, "bun.lockb"))) {
|
|
11807
12195
|
return "bun";
|
|
11808
12196
|
}
|
|
11809
|
-
if (
|
|
12197
|
+
if (existsSync24(join24(projectRoot, "yarn.lock"))) {
|
|
11810
12198
|
return "yarn";
|
|
11811
12199
|
}
|
|
11812
|
-
if (
|
|
12200
|
+
if (existsSync24(join24(projectRoot, "pnpm-lock.yaml"))) {
|
|
11813
12201
|
return "pnpm";
|
|
11814
12202
|
}
|
|
11815
12203
|
return "npm";
|
|
@@ -11827,31 +12215,39 @@ function getInstallCommand(pm) {
|
|
|
11827
12215
|
}
|
|
11828
12216
|
}
|
|
11829
12217
|
async function runSandboxSetup(sandboxName, projectRoot) {
|
|
11830
|
-
const
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
12218
|
+
const ecosystem = detectProjectEcosystem(projectRoot);
|
|
12219
|
+
const isJS = isJavaScriptEcosystem(ecosystem);
|
|
12220
|
+
if (isJS) {
|
|
12221
|
+
const pm = detectPackageManager(projectRoot);
|
|
12222
|
+
if (pm !== "npm") {
|
|
12223
|
+
await ensurePackageManagerInSandbox(sandboxName, pm);
|
|
12224
|
+
}
|
|
12225
|
+
const installCmd = getInstallCommand(pm);
|
|
12226
|
+
process.stderr.write(`
|
|
11836
12227
|
Installing dependencies (${bold(installCmd.join(" "))}) in sandbox ${dim(sandboxName)}...
|
|
11837
12228
|
`);
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
12229
|
+
const installOk = await runInteractiveCommand("docker", [
|
|
12230
|
+
"sandbox",
|
|
12231
|
+
"exec",
|
|
12232
|
+
"-w",
|
|
12233
|
+
projectRoot,
|
|
12234
|
+
sandboxName,
|
|
12235
|
+
...installCmd
|
|
12236
|
+
]);
|
|
12237
|
+
if (!installOk) {
|
|
12238
|
+
process.stderr.write(`${red("✗")} Dependency install failed in sandbox ${dim(sandboxName)}.
|
|
11848
12239
|
`);
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
12240
|
+
return false;
|
|
12241
|
+
}
|
|
12242
|
+
process.stderr.write(`${green("✓")} Dependencies installed in sandbox ${dim(sandboxName)}.
|
|
12243
|
+
`);
|
|
12244
|
+
} else {
|
|
12245
|
+
process.stderr.write(`
|
|
12246
|
+
${dim(`Detected ${ecosystem} project — skipping JS package install.`)}
|
|
11852
12247
|
`);
|
|
11853
|
-
|
|
11854
|
-
|
|
12248
|
+
}
|
|
12249
|
+
const setupScript = join24(projectRoot, ".locus", "sandbox-setup.sh");
|
|
12250
|
+
if (existsSync24(setupScript)) {
|
|
11855
12251
|
process.stderr.write(`Running ${bold(".locus/sandbox-setup.sh")} in sandbox ${dim(sandboxName)}...
|
|
11856
12252
|
`);
|
|
11857
12253
|
const hookOk = await runInteractiveCommand("docker", [
|
|
@@ -11867,6 +12263,11 @@ Installing dependencies (${bold(installCmd.join(" "))}) in sandbox ${dim(sandbox
|
|
|
11867
12263
|
process.stderr.write(`${yellow("⚠")} Setup hook failed in sandbox ${dim(sandboxName)}.
|
|
11868
12264
|
`);
|
|
11869
12265
|
}
|
|
12266
|
+
} else if (!isJS) {
|
|
12267
|
+
process.stderr.write(`${yellow("⚠")} No ${bold(".locus/sandbox-setup.sh")} found. Create one to install ${ecosystem} toolchain in the sandbox.
|
|
12268
|
+
`);
|
|
12269
|
+
process.stderr.write(` Re-run ${cyan("locus init")} to auto-generate a template, or create it manually.
|
|
12270
|
+
`);
|
|
11870
12271
|
}
|
|
11871
12272
|
return true;
|
|
11872
12273
|
}
|
|
@@ -11934,7 +12335,7 @@ function runInteractiveCommand(command, args) {
|
|
|
11934
12335
|
}
|
|
11935
12336
|
async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
11936
12337
|
try {
|
|
11937
|
-
|
|
12338
|
+
execSync17(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
|
|
11938
12339
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11939
12340
|
timeout: 120000
|
|
11940
12341
|
});
|
|
@@ -11950,7 +12351,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
11950
12351
|
}
|
|
11951
12352
|
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
11952
12353
|
try {
|
|
11953
|
-
|
|
12354
|
+
execSync17(`docker sandbox exec ${sandboxName} which ${pm}`, {
|
|
11954
12355
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11955
12356
|
timeout: 5000
|
|
11956
12357
|
});
|
|
@@ -11959,7 +12360,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
11959
12360
|
process.stderr.write(`Installing ${bold(pm)} in sandbox...
|
|
11960
12361
|
`);
|
|
11961
12362
|
try {
|
|
11962
|
-
|
|
12363
|
+
execSync17(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
|
|
11963
12364
|
stdio: "inherit",
|
|
11964
12365
|
timeout: 120000
|
|
11965
12366
|
});
|
|
@@ -11971,7 +12372,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
11971
12372
|
}
|
|
11972
12373
|
async function ensureCodexInSandbox(sandboxName) {
|
|
11973
12374
|
try {
|
|
11974
|
-
|
|
12375
|
+
execSync17(`docker sandbox exec ${sandboxName} which codex`, {
|
|
11975
12376
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11976
12377
|
timeout: 5000
|
|
11977
12378
|
});
|
|
@@ -11979,7 +12380,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
11979
12380
|
process.stderr.write(`Installing codex in sandbox...
|
|
11980
12381
|
`);
|
|
11981
12382
|
try {
|
|
11982
|
-
|
|
12383
|
+
execSync17(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
|
|
11983
12384
|
} catch {
|
|
11984
12385
|
process.stderr.write(`${red("✗")} Failed to install codex in sandbox.
|
|
11985
12386
|
`);
|
|
@@ -11988,7 +12389,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
11988
12389
|
}
|
|
11989
12390
|
function isSandboxAlive(name) {
|
|
11990
12391
|
try {
|
|
11991
|
-
const output =
|
|
12392
|
+
const output = execSync17("docker sandbox ls", {
|
|
11992
12393
|
encoding: "utf-8",
|
|
11993
12394
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11994
12395
|
timeout: 5000
|
|
@@ -12001,6 +12402,7 @@ function isSandboxAlive(name) {
|
|
|
12001
12402
|
var PROVIDERS;
|
|
12002
12403
|
var init_sandbox2 = __esm(() => {
|
|
12003
12404
|
init_config();
|
|
12405
|
+
init_ecosystem();
|
|
12004
12406
|
init_sandbox();
|
|
12005
12407
|
init_sandbox_ignore();
|
|
12006
12408
|
init_terminal();
|
|
@@ -12013,13 +12415,13 @@ init_context();
|
|
|
12013
12415
|
init_logger();
|
|
12014
12416
|
init_rate_limiter();
|
|
12015
12417
|
init_terminal();
|
|
12016
|
-
import { existsSync as
|
|
12017
|
-
import { join as
|
|
12418
|
+
import { existsSync as existsSync25, readFileSync as readFileSync18 } from "node:fs";
|
|
12419
|
+
import { join as join25 } from "node:path";
|
|
12018
12420
|
import { fileURLToPath } from "node:url";
|
|
12019
12421
|
function getCliVersion() {
|
|
12020
12422
|
const fallbackVersion = "0.0.0";
|
|
12021
|
-
const packageJsonPath =
|
|
12022
|
-
if (!
|
|
12423
|
+
const packageJsonPath = join25(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
|
|
12424
|
+
if (!existsSync25(packageJsonPath)) {
|
|
12023
12425
|
return fallbackVersion;
|
|
12024
12426
|
}
|
|
12025
12427
|
try {
|
|
@@ -12284,7 +12686,7 @@ async function main() {
|
|
|
12284
12686
|
try {
|
|
12285
12687
|
const root = getGitRoot(cwd);
|
|
12286
12688
|
if (isInitialized(root)) {
|
|
12287
|
-
logDir =
|
|
12689
|
+
logDir = join25(root, ".locus", "logs");
|
|
12288
12690
|
getRateLimiter(root);
|
|
12289
12691
|
}
|
|
12290
12692
|
} catch {}
|