@locusai/cli 0.22.0 → 0.22.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.
Files changed (2) hide show
  1. package/bin/locus.js +656 -299
  2. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -29,7 +29,7 @@ var init_dist = __esm(() => {
29
29
  },
30
30
  ai: {
31
31
  provider: "claude",
32
- model: "claude-sonnet-4-6"
32
+ model: "opus"
33
33
  },
34
34
  agent: {
35
35
  maxParallel: 3,
@@ -2226,35 +2226,384 @@ var init_init = __esm(() => {
2226
2226
  GITIGNORE_ENTRIES = ["", "# Locus", ".locus/", "!.locus/LEARNINGS.md"];
2227
2227
  });
2228
2228
 
2229
+ // src/commands/create.ts
2230
+ var exports_create = {};
2231
+ __export(exports_create, {
2232
+ createCommand: () => createCommand
2233
+ });
2234
+ import { execSync as execSync4 } from "node:child_process";
2235
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
2236
+ import { join as join7 } from "node:path";
2237
+ function validateName(name) {
2238
+ if (!name)
2239
+ return "Package name is required.";
2240
+ if (!NAME_PATTERN.test(name))
2241
+ return "Name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens.";
2242
+ if (name.startsWith("locus-"))
2243
+ return `Don't include the "locus-" prefix — it's added automatically.`;
2244
+ if (name.length > 50)
2245
+ return "Name must be 50 characters or fewer.";
2246
+ return null;
2247
+ }
2248
+ function capitalize(str) {
2249
+ return str.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2250
+ }
2251
+ function checkNpmExists(fullName) {
2252
+ try {
2253
+ execSync4(`npm view ${fullName} version 2>/dev/null`, {
2254
+ stdio: "pipe",
2255
+ timeout: 1e4
2256
+ });
2257
+ return true;
2258
+ } catch {
2259
+ return false;
2260
+ }
2261
+ }
2262
+ function parseCreateArgs(args) {
2263
+ let name = "";
2264
+ let description = "";
2265
+ let i = 0;
2266
+ while (i < args.length) {
2267
+ const arg = args[i];
2268
+ if (arg === "--description" || arg === "-D") {
2269
+ description = args[++i] ?? "";
2270
+ } else if (arg === "help" || arg === "--help" || arg === "-h") {
2271
+ return null;
2272
+ } else if (!arg.startsWith("-")) {
2273
+ name = arg;
2274
+ }
2275
+ i++;
2276
+ }
2277
+ return { name, description };
2278
+ }
2279
+ function generatePackageJson(name, displayName, description, sdkVersion) {
2280
+ const pkg = {
2281
+ name: `@locusai/locus-${name}`,
2282
+ version: "0.1.0",
2283
+ description,
2284
+ type: "module",
2285
+ bin: {
2286
+ [`locus-${name}`]: `./bin/locus-${name}.js`
2287
+ },
2288
+ files: ["bin", "package.json", "README.md"],
2289
+ locus: {
2290
+ displayName,
2291
+ description,
2292
+ commands: [name],
2293
+ version: "0.1.0"
2294
+ },
2295
+ scripts: {
2296
+ build: `bun build src/cli.ts --outfile bin/locus-${name}.js --target node`,
2297
+ typecheck: "tsc --noEmit",
2298
+ lint: "biome lint .",
2299
+ format: "biome format --write ."
2300
+ },
2301
+ dependencies: {
2302
+ "@locusai/sdk": `^${sdkVersion}`
2303
+ },
2304
+ devDependencies: {
2305
+ typescript: "^5.8.3"
2306
+ },
2307
+ keywords: ["locusai-package", "locus", name],
2308
+ engines: {
2309
+ node: ">=18"
2310
+ },
2311
+ license: "MIT"
2312
+ };
2313
+ return `${JSON.stringify(pkg, null, 2)}
2314
+ `;
2315
+ }
2316
+ function generateTsconfig() {
2317
+ const config = {
2318
+ compilerOptions: {
2319
+ target: "ES2022",
2320
+ module: "ESNext",
2321
+ moduleResolution: "bundler",
2322
+ strict: true,
2323
+ skipLibCheck: true,
2324
+ esModuleInterop: true,
2325
+ isolatedModules: true,
2326
+ resolveJsonModule: true,
2327
+ noEmit: true,
2328
+ rootDir: "./src"
2329
+ },
2330
+ include: ["src/**/*"],
2331
+ exclude: ["node_modules", "dist", "bin"]
2332
+ };
2333
+ return `${JSON.stringify(config, null, 2)}
2334
+ `;
2335
+ }
2336
+ function generateCliTs() {
2337
+ return `#!/usr/bin/env node
2338
+
2339
+ import { main } from "./index.js";
2340
+
2341
+ main(process.argv.slice(2)).catch((error) => {
2342
+ console.error(\`Fatal error: \${error.message}\`);
2343
+ process.exit(1);
2344
+ });
2345
+ `;
2346
+ }
2347
+ function generateIndexTs(name) {
2348
+ return `import { createLogger, readLocusConfig } from "@locusai/sdk";
2349
+
2350
+ const logger = createLogger("${name}");
2351
+
2352
+ export async function main(args: string[]): Promise<void> {
2353
+ const command = args[0] ?? "help";
2354
+
2355
+ switch (command) {
2356
+ case "start":
2357
+ return handleStart();
2358
+ case "help":
2359
+ case "--help":
2360
+ case "-h":
2361
+ return printHelp();
2362
+ default:
2363
+ console.error(\`Unknown command: \${command}\`);
2364
+ printHelp();
2365
+ process.exit(1);
2366
+ }
2367
+ }
2368
+
2369
+ // ─── Commands ────────────────────────────────────────────────────────────────
2370
+
2371
+ function handleStart(): void {
2372
+ const config = readLocusConfig();
2373
+ logger.info(\`Hello from locus-${name}! Repo: \${config.github.owner}/\${config.github.repo}\`);
2374
+ // TODO: Implement your package logic here
2375
+ }
2376
+
2377
+ // ─── Help ────────────────────────────────────────────────────────────────────
2378
+
2379
+ function printHelp(): void {
2380
+ console.log(\`
2381
+ locus-${name}
2382
+
2383
+ Usage:
2384
+ locus pkg ${name} <command>
2385
+
2386
+ Commands:
2387
+ start Start the ${name} integration
2388
+ help Show this help message
2389
+ \`);
2390
+ }
2391
+ `;
2392
+ }
2393
+ function generateReadme(name, displayName, description) {
2394
+ return `# @locusai/locus-${name}
2395
+
2396
+ ${description}
2397
+
2398
+ ## Installation
2399
+
2400
+ \`\`\`bash
2401
+ locus install ${name}
2402
+ \`\`\`
2403
+
2404
+ ## Usage
2405
+
2406
+ \`\`\`bash
2407
+ locus pkg ${name} start # Start the integration
2408
+ locus pkg ${name} help # Show help
2409
+ \`\`\`
2410
+
2411
+ ## Configuration
2412
+
2413
+ Configure via \`locus config\`:
2414
+
2415
+ \`\`\`bash
2416
+ locus config set packages.${name}.apiKey "your-api-key"
2417
+ \`\`\`
2418
+
2419
+ ## Development
2420
+
2421
+ \`\`\`bash
2422
+ # Build
2423
+ bun run build
2424
+
2425
+ # Type check
2426
+ bun run typecheck
2427
+
2428
+ # Lint
2429
+ bun run lint
2430
+
2431
+ # Test locally
2432
+ locus pkg ${name}
2433
+ \`\`\`
2434
+
2435
+ ## License
2436
+
2437
+ MIT
2438
+ `;
2439
+ }
2440
+ function printHelp() {
2441
+ process.stderr.write(`
2442
+ ${bold2("locus create")} — Scaffold a new Locus community package
2443
+
2444
+ ${bold2("Usage:")}
2445
+ locus create <name> [options]
2446
+
2447
+ ${bold2("Arguments:")}
2448
+ ${cyan2("<name>")} Package short name (e.g. slack, discord, jira)
2449
+
2450
+ ${bold2("Options:")}
2451
+ ${dim2("--description, -D")} Package description (default: auto-generated)
2452
+ ${dim2("--help, -h")} Show this help
2453
+
2454
+ ${bold2("Examples:")}
2455
+ locus create slack ${dim2("# Create packages/slack/")}
2456
+ locus create discord -D "Control Locus via Discord" ${dim2("# With custom description")}
2457
+
2458
+ ${bold2("What gets created:")}
2459
+ packages/<name>/
2460
+ ├── src/
2461
+ │ ├── cli.ts ${dim2("# Entry point")}
2462
+ │ └── index.ts ${dim2("# Main logic with command dispatch")}
2463
+ ├── package.json ${dim2("# Full config with locus manifest")}
2464
+ ├── tsconfig.json ${dim2("# TypeScript configuration")}
2465
+ └── README.md ${dim2("# Package documentation")}
2466
+
2467
+ ${bold2("Next steps after creation:")}
2468
+ ${gray2("1.")} cd packages/<name>
2469
+ ${gray2("2.")} Implement your logic in src/index.ts
2470
+ ${gray2("3.")} bun install && bun run build
2471
+ ${gray2("4.")} Test locally with: locus pkg <name>
2472
+ ${gray2("5.")} Submit a pull request
2473
+
2474
+ `);
2475
+ }
2476
+ async function createCommand(args) {
2477
+ const parsed = parseCreateArgs(args);
2478
+ if (!parsed || !parsed.name) {
2479
+ printHelp();
2480
+ if (parsed && !parsed.name) {
2481
+ process.stderr.write(`${red2("✗")} Package name is required.
2482
+
2483
+ `);
2484
+ process.stderr.write(` Usage: ${bold2("locus create <name>")}
2485
+
2486
+ `);
2487
+ process.exit(1);
2488
+ }
2489
+ return;
2490
+ }
2491
+ const { name } = parsed;
2492
+ const displayName = capitalize(name);
2493
+ const description = parsed.description || `${displayName} integration for Locus`;
2494
+ const fullNpmName = `@locusai/locus-${name}`;
2495
+ process.stderr.write(`
2496
+ ${bold2("Creating package:")} ${cyan2(fullNpmName)}
2497
+
2498
+ `);
2499
+ const nameError = validateName(name);
2500
+ if (nameError) {
2501
+ process.stderr.write(`${red2("✗")} ${nameError}
2502
+ `);
2503
+ process.exit(1);
2504
+ }
2505
+ process.stderr.write(`${green("✓")} Name is valid: ${bold2(name)}
2506
+ `);
2507
+ const packagesDir = join7(process.cwd(), "packages", name);
2508
+ if (existsSync7(packagesDir)) {
2509
+ process.stderr.write(`${red2("✗")} Directory already exists: ${bold2(`packages/${name}/`)}
2510
+ `);
2511
+ process.exit(1);
2512
+ }
2513
+ process.stderr.write(`${cyan2("●")} Checking npm registry...`);
2514
+ const existsOnNpm = checkNpmExists(fullNpmName);
2515
+ if (existsOnNpm) {
2516
+ process.stderr.write(`\r${yellow2("⚠")} Package ${bold2(fullNpmName)} already exists on npm. You may want to choose a different name.
2517
+ `);
2518
+ } else {
2519
+ process.stderr.write(`\r${green("✓")} Name is available on npm
2520
+ `);
2521
+ }
2522
+ let sdkVersion = "0.22.0";
2523
+ try {
2524
+ const sdkPkgPath = join7(process.cwd(), "packages", "sdk", "package.json");
2525
+ if (existsSync7(sdkPkgPath)) {
2526
+ const { readFileSync: readFileSync5 } = await import("node:fs");
2527
+ const sdkPkg = JSON.parse(readFileSync5(sdkPkgPath, "utf-8"));
2528
+ if (sdkPkg.version)
2529
+ sdkVersion = sdkPkg.version;
2530
+ }
2531
+ } catch {}
2532
+ mkdirSync6(join7(packagesDir, "src"), { recursive: true });
2533
+ mkdirSync6(join7(packagesDir, "bin"), { recursive: true });
2534
+ process.stderr.write(`${green("✓")} Created directory structure
2535
+ `);
2536
+ writeFileSync5(join7(packagesDir, "package.json"), generatePackageJson(name, displayName, description, sdkVersion), "utf-8");
2537
+ process.stderr.write(`${green("✓")} Generated package.json
2538
+ `);
2539
+ writeFileSync5(join7(packagesDir, "tsconfig.json"), generateTsconfig(), "utf-8");
2540
+ process.stderr.write(`${green("✓")} Generated tsconfig.json
2541
+ `);
2542
+ writeFileSync5(join7(packagesDir, "src", "cli.ts"), generateCliTs(), "utf-8");
2543
+ process.stderr.write(`${green("✓")} Generated src/cli.ts
2544
+ `);
2545
+ writeFileSync5(join7(packagesDir, "src", "index.ts"), generateIndexTs(name), "utf-8");
2546
+ process.stderr.write(`${green("✓")} Generated src/index.ts
2547
+ `);
2548
+ writeFileSync5(join7(packagesDir, "README.md"), generateReadme(name, displayName, description), "utf-8");
2549
+ process.stderr.write(`${green("✓")} Generated README.md
2550
+ `);
2551
+ process.stderr.write(`
2552
+ ${bold2(green("Package created!"))}
2553
+
2554
+ `);
2555
+ process.stderr.write(`${bold2("Next steps:")}
2556
+ `);
2557
+ process.stderr.write(` ${gray2("1.")} Implement your logic in ${bold2(`packages/${name}/src/index.ts`)}
2558
+ `);
2559
+ process.stderr.write(` ${gray2("2.")} Install dependencies: ${bold2("bun install")}
2560
+ `);
2561
+ process.stderr.write(` ${gray2("3.")} Build your package: ${bold2(`cd packages/${name} && bun run build`)}
2562
+ `);
2563
+ process.stderr.write(` ${gray2("4.")} Test locally: ${bold2(`locus pkg ${name}`)}
2564
+ `);
2565
+ process.stderr.write(` ${gray2("5.")} Submit a pull request
2566
+ `);
2567
+ process.stderr.write(`
2568
+ ${dim2("See the full guide:")} ${cyan2("packages/sdk/PACKAGE_GUIDE.md")}
2569
+
2570
+ `);
2571
+ }
2572
+ var NAME_PATTERN;
2573
+ var init_create = __esm(() => {
2574
+ init_terminal();
2575
+ NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
2576
+ });
2577
+
2229
2578
  // src/packages/registry.ts
2230
2579
  import {
2231
- existsSync as existsSync7,
2232
- mkdirSync as mkdirSync6,
2580
+ existsSync as existsSync8,
2581
+ mkdirSync as mkdirSync7,
2233
2582
  readFileSync as readFileSync5,
2234
2583
  renameSync,
2235
- writeFileSync as writeFileSync5
2584
+ writeFileSync as writeFileSync6
2236
2585
  } from "node:fs";
2237
2586
  import { homedir as homedir2 } from "node:os";
2238
- import { join as join7 } from "node:path";
2587
+ import { join as join8 } from "node:path";
2239
2588
  function getPackagesDir() {
2240
2589
  const home = process.env.HOME || homedir2();
2241
- const dir = join7(home, ".locus", "packages");
2242
- if (!existsSync7(dir)) {
2243
- mkdirSync6(dir, { recursive: true });
2590
+ const dir = join8(home, ".locus", "packages");
2591
+ if (!existsSync8(dir)) {
2592
+ mkdirSync7(dir, { recursive: true });
2244
2593
  }
2245
- const pkgJson = join7(dir, "package.json");
2246
- if (!existsSync7(pkgJson)) {
2247
- writeFileSync5(pkgJson, `${JSON.stringify({ private: true }, null, 2)}
2594
+ const pkgJson = join8(dir, "package.json");
2595
+ if (!existsSync8(pkgJson)) {
2596
+ writeFileSync6(pkgJson, `${JSON.stringify({ private: true }, null, 2)}
2248
2597
  `, "utf-8");
2249
2598
  }
2250
2599
  return dir;
2251
2600
  }
2252
2601
  function getRegistryPath() {
2253
- return join7(getPackagesDir(), "registry.json");
2602
+ return join8(getPackagesDir(), "registry.json");
2254
2603
  }
2255
2604
  function loadRegistry() {
2256
2605
  const registryPath = getRegistryPath();
2257
- if (!existsSync7(registryPath)) {
2606
+ if (!existsSync8(registryPath)) {
2258
2607
  return { packages: {} };
2259
2608
  }
2260
2609
  try {
@@ -2271,7 +2620,7 @@ function loadRegistry() {
2271
2620
  }
2272
2621
  if (pruned) {
2273
2622
  const tmp = `${registryPath}.tmp`;
2274
- writeFileSync5(tmp, `${JSON.stringify(registry, null, 2)}
2623
+ writeFileSync6(tmp, `${JSON.stringify(registry, null, 2)}
2275
2624
  `, "utf-8");
2276
2625
  renameSync(tmp, registryPath);
2277
2626
  }
@@ -2285,15 +2634,15 @@ function loadRegistry() {
2285
2634
  function saveRegistry(registry) {
2286
2635
  const registryPath = getRegistryPath();
2287
2636
  const tmp = `${registryPath}.tmp`;
2288
- writeFileSync5(tmp, `${JSON.stringify(registry, null, 2)}
2637
+ writeFileSync6(tmp, `${JSON.stringify(registry, null, 2)}
2289
2638
  `, "utf-8");
2290
2639
  renameSync(tmp, registryPath);
2291
2640
  }
2292
2641
  function resolvePackageBinary(packageName) {
2293
2642
  const fullName = normalizePackageName(packageName);
2294
2643
  const binName = fullName.includes("/") ? fullName.split("/").pop() : fullName;
2295
- const binPath = join7(getPackagesDir(), "node_modules", ".bin", binName);
2296
- return existsSync7(binPath) ? binPath : null;
2644
+ const binPath = join8(getPackagesDir(), "node_modules", ".bin", binName);
2645
+ return existsSync8(binPath) ? binPath : null;
2297
2646
  }
2298
2647
  function normalizePackageName(input) {
2299
2648
  if (input.startsWith(SCOPED_PREFIX)) {
@@ -2319,7 +2668,7 @@ __export(exports_pkg, {
2319
2668
  listInstalledPackages: () => listInstalledPackages
2320
2669
  });
2321
2670
  import { spawn } from "node:child_process";
2322
- import { existsSync as existsSync8 } from "node:fs";
2671
+ import { existsSync as existsSync9 } from "node:fs";
2323
2672
  function listInstalledPackages() {
2324
2673
  const registry = loadRegistry();
2325
2674
  const entries = Object.values(registry.packages);
@@ -2398,7 +2747,7 @@ async function pkgCommand(args, _flags) {
2398
2747
  return;
2399
2748
  }
2400
2749
  let binaryPath = entry.binaryPath;
2401
- if (!binaryPath || !existsSync8(binaryPath)) {
2750
+ if (!binaryPath || !existsSync9(binaryPath)) {
2402
2751
  const resolved = resolvePackageBinary(packageName);
2403
2752
  if (resolved) {
2404
2753
  binaryPath = resolved;
@@ -2531,8 +2880,8 @@ __export(exports_install, {
2531
2880
  installCommand: () => installCommand
2532
2881
  });
2533
2882
  import { spawnSync as spawnSync2 } from "node:child_process";
2534
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
2535
- import { join as join8 } from "node:path";
2883
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "node:fs";
2884
+ import { join as join9 } from "node:path";
2536
2885
  function parsePackageArg(raw) {
2537
2886
  if (raw.startsWith("@")) {
2538
2887
  const slashIdx = raw.indexOf("/");
@@ -2609,8 +2958,8 @@ ${red2("✗")} Failed to install ${bold2(packageSpec)}.
2609
2958
  process.exit(1);
2610
2959
  return;
2611
2960
  }
2612
- const installedPkgJsonPath = join8(packagesDir, "node_modules", packageName, "package.json");
2613
- if (!existsSync9(installedPkgJsonPath)) {
2961
+ const installedPkgJsonPath = join9(packagesDir, "node_modules", packageName, "package.json");
2962
+ if (!existsSync10(installedPkgJsonPath)) {
2614
2963
  process.stderr.write(`
2615
2964
  ${red2("✗")} Package installed but package.json not found at:
2616
2965
  `);
@@ -2888,16 +3237,16 @@ __export(exports_logs, {
2888
3237
  logsCommand: () => logsCommand
2889
3238
  });
2890
3239
  import {
2891
- existsSync as existsSync10,
3240
+ existsSync as existsSync11,
2892
3241
  readdirSync as readdirSync2,
2893
3242
  readFileSync as readFileSync7,
2894
3243
  statSync as statSync2,
2895
3244
  unlinkSync as unlinkSync2
2896
3245
  } from "node:fs";
2897
- import { join as join9 } from "node:path";
3246
+ import { join as join10 } from "node:path";
2898
3247
  async function logsCommand(cwd, options) {
2899
- const logsDir = join9(cwd, ".locus", "logs");
2900
- if (!existsSync10(logsDir)) {
3248
+ const logsDir = join10(cwd, ".locus", "logs");
3249
+ if (!existsSync11(logsDir)) {
2901
3250
  process.stderr.write(`${dim2("No logs found.")}
2902
3251
  `);
2903
3252
  return;
@@ -2952,8 +3301,8 @@ async function tailLog(logFile, levelFilter) {
2952
3301
  process.stderr.write(`${bold2("Tailing:")} ${dim2(logFile)} ${dim2("(Ctrl+C to stop)")}
2953
3302
 
2954
3303
  `);
2955
- let lastSize = existsSync10(logFile) ? statSync2(logFile).size : 0;
2956
- if (existsSync10(logFile)) {
3304
+ let lastSize = existsSync11(logFile) ? statSync2(logFile).size : 0;
3305
+ if (existsSync11(logFile)) {
2957
3306
  const content = readFileSync7(logFile, "utf-8");
2958
3307
  const lines = content.trim().split(`
2959
3308
  `).filter(Boolean);
@@ -2972,7 +3321,7 @@ async function tailLog(logFile, levelFilter) {
2972
3321
  }
2973
3322
  return new Promise((resolve) => {
2974
3323
  const interval = setInterval(() => {
2975
- if (!existsSync10(logFile))
3324
+ if (!existsSync11(logFile))
2976
3325
  return;
2977
3326
  const currentSize = statSync2(logFile).size;
2978
3327
  if (currentSize <= lastSize)
@@ -3032,7 +3381,7 @@ function cleanLogs(logsDir) {
3032
3381
  `);
3033
3382
  }
3034
3383
  function getLogFiles(logsDir) {
3035
- 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);
3384
+ return readdirSync2(logsDir).filter((f) => f.startsWith("locus-") && f.endsWith(".log")).map((f) => join10(logsDir, f)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs);
3036
3385
  }
3037
3386
  function formatEntry(entry) {
3038
3387
  const time = dim2(new Date(entry.ts).toLocaleTimeString());
@@ -3341,10 +3690,10 @@ var init_stream_renderer = __esm(() => {
3341
3690
  });
3342
3691
 
3343
3692
  // src/repl/clipboard.ts
3344
- import { execSync as execSync4 } from "node:child_process";
3345
- import { existsSync as existsSync11, mkdirSync as mkdirSync7 } from "node:fs";
3693
+ import { execSync as execSync5 } from "node:child_process";
3694
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
3346
3695
  import { tmpdir } from "node:os";
3347
- import { join as join10 } from "node:path";
3696
+ import { join as join11 } from "node:path";
3348
3697
  function readClipboardImage() {
3349
3698
  if (process.platform === "darwin") {
3350
3699
  return readMacOSClipboardImage();
@@ -3355,14 +3704,14 @@ function readClipboardImage() {
3355
3704
  return null;
3356
3705
  }
3357
3706
  function ensureStableDir() {
3358
- if (!existsSync11(STABLE_DIR)) {
3359
- mkdirSync7(STABLE_DIR, { recursive: true });
3707
+ if (!existsSync12(STABLE_DIR)) {
3708
+ mkdirSync8(STABLE_DIR, { recursive: true });
3360
3709
  }
3361
3710
  }
3362
3711
  function readMacOSClipboardImage() {
3363
3712
  try {
3364
3713
  ensureStableDir();
3365
- const destPath = join10(STABLE_DIR, `clipboard-${Date.now()}.png`);
3714
+ const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
3366
3715
  const script = [
3367
3716
  `set destPath to POSIX file "${destPath}"`,
3368
3717
  "try",
@@ -3380,13 +3729,13 @@ function readMacOSClipboardImage() {
3380
3729
  `return "ok"`
3381
3730
  ].join(`
3382
3731
  `);
3383
- const result = execSync4("osascript", {
3732
+ const result = execSync5("osascript", {
3384
3733
  input: script,
3385
3734
  encoding: "utf-8",
3386
3735
  timeout: 5000,
3387
3736
  stdio: ["pipe", "pipe", "pipe"]
3388
3737
  }).trim();
3389
- if (result === "ok" && existsSync11(destPath)) {
3738
+ if (result === "ok" && existsSync12(destPath)) {
3390
3739
  return destPath;
3391
3740
  }
3392
3741
  } catch {}
@@ -3394,14 +3743,14 @@ function readMacOSClipboardImage() {
3394
3743
  }
3395
3744
  function readLinuxClipboardImage() {
3396
3745
  try {
3397
- const targets = execSync4("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
3746
+ const targets = execSync5("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
3398
3747
  if (!targets.includes("image/png")) {
3399
3748
  return null;
3400
3749
  }
3401
3750
  ensureStableDir();
3402
- const destPath = join10(STABLE_DIR, `clipboard-${Date.now()}.png`);
3403
- execSync4(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
3404
- if (existsSync11(destPath)) {
3751
+ const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
3752
+ execSync5(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
3753
+ if (existsSync12(destPath)) {
3405
3754
  return destPath;
3406
3755
  }
3407
3756
  } catch {}
@@ -3409,13 +3758,13 @@ function readLinuxClipboardImage() {
3409
3758
  }
3410
3759
  var STABLE_DIR;
3411
3760
  var init_clipboard = __esm(() => {
3412
- STABLE_DIR = join10(tmpdir(), "locus-images");
3761
+ STABLE_DIR = join11(tmpdir(), "locus-images");
3413
3762
  });
3414
3763
 
3415
3764
  // src/repl/image-detect.ts
3416
- import { copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
3765
+ import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync9 } from "node:fs";
3417
3766
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
3418
- import { basename, extname, join as join11, resolve } from "node:path";
3767
+ import { basename, extname, join as join12, resolve } from "node:path";
3419
3768
  function detectImages(input) {
3420
3769
  const detected = [];
3421
3770
  const byResolved = new Map;
@@ -3509,15 +3858,15 @@ function collectReferencedAttachments(input, attachments) {
3509
3858
  return dedupeByResolvedPath(selected);
3510
3859
  }
3511
3860
  function relocateImages(images, projectRoot) {
3512
- const targetDir = join11(projectRoot, ".locus", "tmp", "images");
3861
+ const targetDir = join12(projectRoot, ".locus", "tmp", "images");
3513
3862
  for (const img of images) {
3514
3863
  if (!img.exists)
3515
3864
  continue;
3516
3865
  try {
3517
- if (!existsSync12(targetDir)) {
3518
- mkdirSync8(targetDir, { recursive: true });
3866
+ if (!existsSync13(targetDir)) {
3867
+ mkdirSync9(targetDir, { recursive: true });
3519
3868
  }
3520
- const dest = join11(targetDir, basename(img.stablePath));
3869
+ const dest = join12(targetDir, basename(img.stablePath));
3521
3870
  copyFileSync(img.stablePath, dest);
3522
3871
  img.stablePath = dest;
3523
3872
  } catch {}
@@ -3529,7 +3878,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
3529
3878
  return;
3530
3879
  let resolved = stripQuotes(rawPath).replace(/\\ /g, " ");
3531
3880
  if (resolved.startsWith("~/")) {
3532
- resolved = join11(homedir3(), resolved.slice(2));
3881
+ resolved = join12(homedir3(), resolved.slice(2));
3533
3882
  }
3534
3883
  resolved = resolve(resolved);
3535
3884
  const existing = byResolved.get(resolved);
@@ -3542,7 +3891,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
3542
3891
  ]);
3543
3892
  return;
3544
3893
  }
3545
- const exists = existsSync12(resolved);
3894
+ const exists = existsSync13(resolved);
3546
3895
  let stablePath = resolved;
3547
3896
  if (exists) {
3548
3897
  stablePath = copyToStable(resolved);
@@ -3596,10 +3945,10 @@ function dedupeByResolvedPath(images) {
3596
3945
  }
3597
3946
  function copyToStable(sourcePath) {
3598
3947
  try {
3599
- if (!existsSync12(STABLE_DIR2)) {
3600
- mkdirSync8(STABLE_DIR2, { recursive: true });
3948
+ if (!existsSync13(STABLE_DIR2)) {
3949
+ mkdirSync9(STABLE_DIR2, { recursive: true });
3601
3950
  }
3602
- const dest = join11(STABLE_DIR2, `${Date.now()}-${basename(sourcePath)}`);
3951
+ const dest = join12(STABLE_DIR2, `${Date.now()}-${basename(sourcePath)}`);
3603
3952
  copyFileSync(sourcePath, dest);
3604
3953
  return dest;
3605
3954
  } catch {
@@ -3619,7 +3968,7 @@ var init_image_detect = __esm(() => {
3619
3968
  ".tif",
3620
3969
  ".tiff"
3621
3970
  ]);
3622
- STABLE_DIR2 = join11(tmpdir2(), "locus-images");
3971
+ STABLE_DIR2 = join12(tmpdir2(), "locus-images");
3623
3972
  PLACEHOLDER_ID_PATTERN = /\(locus:\/\/screenshot-(\d+)\)/g;
3624
3973
  });
3625
3974
 
@@ -4421,7 +4770,7 @@ __export(exports_claude, {
4421
4770
  buildClaudeArgs: () => buildClaudeArgs,
4422
4771
  ClaudeRunner: () => ClaudeRunner
4423
4772
  });
4424
- import { execSync as execSync5, spawn as spawn2 } from "node:child_process";
4773
+ import { execSync as execSync6, spawn as spawn2 } from "node:child_process";
4425
4774
  function buildClaudeArgs(options) {
4426
4775
  const args = ["--dangerously-skip-permissions", "--no-session-persistence"];
4427
4776
  if (options.model) {
@@ -4439,7 +4788,7 @@ class ClaudeRunner {
4439
4788
  aborted = false;
4440
4789
  async isAvailable() {
4441
4790
  try {
4442
- execSync5("claude --version", {
4791
+ execSync6("claude --version", {
4443
4792
  encoding: "utf-8",
4444
4793
  stdio: ["pipe", "pipe", "pipe"]
4445
4794
  });
@@ -4450,7 +4799,7 @@ class ClaudeRunner {
4450
4799
  }
4451
4800
  async getVersion() {
4452
4801
  try {
4453
- const output = execSync5("claude --version", {
4802
+ const output = execSync6("claude --version", {
4454
4803
  encoding: "utf-8",
4455
4804
  stdio: ["pipe", "pipe", "pipe"]
4456
4805
  }).trim();
@@ -4617,8 +4966,8 @@ var init_claude = __esm(() => {
4617
4966
  import { exec } from "node:child_process";
4618
4967
  import {
4619
4968
  cpSync,
4620
- existsSync as existsSync13,
4621
- mkdirSync as mkdirSync9,
4969
+ existsSync as existsSync14,
4970
+ mkdirSync as mkdirSync10,
4622
4971
  mkdtempSync,
4623
4972
  readdirSync as readdirSync3,
4624
4973
  readFileSync as readFileSync8,
@@ -4626,10 +4975,10 @@ import {
4626
4975
  statSync as statSync3
4627
4976
  } from "node:fs";
4628
4977
  import { tmpdir as tmpdir3 } from "node:os";
4629
- import { dirname as dirname3, join as join12, relative } from "node:path";
4978
+ import { dirname as dirname3, join as join13, relative } from "node:path";
4630
4979
  import { promisify } from "node:util";
4631
4980
  function parseIgnoreFile(filePath) {
4632
- if (!existsSync13(filePath))
4981
+ if (!existsSync14(filePath))
4633
4982
  return [];
4634
4983
  const content = readFileSync8(filePath, "utf-8");
4635
4984
  const rules = [];
@@ -4699,7 +5048,7 @@ function findIgnoredPaths(projectRoot, rules) {
4699
5048
  for (const name of entries) {
4700
5049
  if (SKIP_DIRS.has(name))
4701
5050
  continue;
4702
- const fullPath = join12(dir, name);
5051
+ const fullPath = join13(dir, name);
4703
5052
  let stat = null;
4704
5053
  try {
4705
5054
  stat = statSync3(fullPath);
@@ -4733,7 +5082,7 @@ function findIgnoredPaths(projectRoot, rules) {
4733
5082
  }
4734
5083
  function backupIgnoredFiles(projectRoot) {
4735
5084
  const log = getLogger();
4736
- const ignorePath = join12(projectRoot, ".sandboxignore");
5085
+ const ignorePath = join13(projectRoot, ".sandboxignore");
4737
5086
  const rules = parseIgnoreFile(ignorePath);
4738
5087
  if (rules.length === 0)
4739
5088
  return NOOP_BACKUP;
@@ -4742,7 +5091,7 @@ function backupIgnoredFiles(projectRoot) {
4742
5091
  return NOOP_BACKUP;
4743
5092
  let backupDir;
4744
5093
  try {
4745
- backupDir = mkdtempSync(join12(tmpdir3(), "locus-sandbox-backup-"));
5094
+ backupDir = mkdtempSync(join13(tmpdir3(), "locus-sandbox-backup-"));
4746
5095
  } catch (err) {
4747
5096
  log.debug("Failed to create sandbox backup dir", {
4748
5097
  error: err instanceof Error ? err.message : String(err)
@@ -4752,9 +5101,9 @@ function backupIgnoredFiles(projectRoot) {
4752
5101
  const backed = [];
4753
5102
  for (const src of paths) {
4754
5103
  const rel = relative(projectRoot, src);
4755
- const dest = join12(backupDir, rel);
5104
+ const dest = join13(backupDir, rel);
4756
5105
  try {
4757
- mkdirSync9(dirname3(dest), { recursive: true });
5106
+ mkdirSync10(dirname3(dest), { recursive: true });
4758
5107
  cpSync(src, dest, { recursive: true, preserveTimestamps: true });
4759
5108
  backed.push({ src, dest });
4760
5109
  } catch (err) {
@@ -4776,7 +5125,7 @@ function backupIgnoredFiles(projectRoot) {
4776
5125
  restore() {
4777
5126
  for (const { src, dest } of backed) {
4778
5127
  try {
4779
- mkdirSync9(dirname3(src), { recursive: true });
5128
+ mkdirSync10(dirname3(src), { recursive: true });
4780
5129
  cpSync(dest, src, { recursive: true, preserveTimestamps: true });
4781
5130
  } catch (err) {
4782
5131
  log.debug("Failed to restore ignored file (potential data loss)", {
@@ -4794,7 +5143,7 @@ function backupIgnoredFiles(projectRoot) {
4794
5143
  }
4795
5144
  async function enforceSandboxIgnore(sandboxName, projectRoot) {
4796
5145
  const log = getLogger();
4797
- const ignorePath = join12(projectRoot, ".sandboxignore");
5146
+ const ignorePath = join13(projectRoot, ".sandboxignore");
4798
5147
  const rules = parseIgnoreFile(ignorePath);
4799
5148
  if (rules.length === 0)
4800
5149
  return;
@@ -5039,7 +5388,7 @@ var init_claude_sandbox = __esm(() => {
5039
5388
  });
5040
5389
 
5041
5390
  // src/ai/codex.ts
5042
- import { execSync as execSync6, spawn as spawn4 } from "node:child_process";
5391
+ import { execSync as execSync7, spawn as spawn4 } from "node:child_process";
5043
5392
  function buildCodexArgs(model) {
5044
5393
  const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
5045
5394
  if (model) {
@@ -5055,7 +5404,7 @@ class CodexRunner {
5055
5404
  aborted = false;
5056
5405
  async isAvailable() {
5057
5406
  try {
5058
- execSync6("codex --version", {
5407
+ execSync7("codex --version", {
5059
5408
  encoding: "utf-8",
5060
5409
  stdio: ["pipe", "pipe", "pipe"]
5061
5410
  });
@@ -5066,7 +5415,7 @@ class CodexRunner {
5066
5415
  }
5067
5416
  async getVersion() {
5068
5417
  try {
5069
- const output = execSync6("codex --version", {
5418
+ const output = execSync7("codex --version", {
5070
5419
  encoding: "utf-8",
5071
5420
  stdio: ["pipe", "pipe", "pipe"]
5072
5421
  }).trim();
@@ -7094,9 +7443,9 @@ var init_sprint = __esm(() => {
7094
7443
  });
7095
7444
 
7096
7445
  // src/core/prompt-builder.ts
7097
- import { execSync as execSync7 } from "node:child_process";
7098
- import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
7099
- import { join as join13 } from "node:path";
7446
+ import { execSync as execSync8 } from "node:child_process";
7447
+ import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
7448
+ import { join as join14 } from "node:path";
7100
7449
  function buildExecutionPrompt(ctx) {
7101
7450
  const sections = [];
7102
7451
  sections.push(buildSystemContext(ctx.projectRoot));
@@ -7126,13 +7475,13 @@ function buildFeedbackPrompt(ctx) {
7126
7475
  }
7127
7476
  function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
7128
7477
  const sections = [];
7129
- const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
7478
+ const locusmd = readFileSafe(join14(projectRoot, ".locus", "LOCUS.md"));
7130
7479
  if (locusmd) {
7131
7480
  sections.push(`<project-instructions>
7132
7481
  ${locusmd}
7133
7482
  </project-instructions>`);
7134
7483
  }
7135
- const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
7484
+ const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
7136
7485
  if (learnings) {
7137
7486
  sections.push(`<past-learnings>
7138
7487
  ${learnings}
@@ -7159,24 +7508,24 @@ ${userMessage}
7159
7508
  }
7160
7509
  function buildSystemContext(projectRoot) {
7161
7510
  const parts = [];
7162
- const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
7511
+ const locusmd = readFileSafe(join14(projectRoot, ".locus", "LOCUS.md"));
7163
7512
  if (locusmd) {
7164
7513
  parts.push(`<project-instructions>
7165
7514
  ${locusmd}
7166
7515
  </project-instructions>`);
7167
7516
  }
7168
- const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
7517
+ const learnings = readFileSafe(join14(projectRoot, ".locus", "LEARNINGS.md"));
7169
7518
  if (learnings) {
7170
7519
  parts.push(`<past-learnings>
7171
7520
  ${learnings}
7172
7521
  </past-learnings>`);
7173
7522
  }
7174
- const discussionsDir = join13(projectRoot, ".locus", "discussions");
7175
- if (existsSync14(discussionsDir)) {
7523
+ const discussionsDir = join14(projectRoot, ".locus", "discussions");
7524
+ if (existsSync15(discussionsDir)) {
7176
7525
  try {
7177
7526
  const files = readdirSync4(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
7178
7527
  for (const file of files) {
7179
- const content = readFileSafe(join13(discussionsDir, file));
7528
+ const content = readFileSafe(join14(discussionsDir, file));
7180
7529
  if (content) {
7181
7530
  const name = file.replace(".md", "");
7182
7531
  parts.push(`<discussion name="${name}">
@@ -7243,7 +7592,7 @@ ${parts.join(`
7243
7592
  function buildRepoContext(projectRoot) {
7244
7593
  const parts = [];
7245
7594
  try {
7246
- const tree = execSync7("find . -maxdepth 2 -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.locus/*' -not -path '*/dist/*' -not -path '*/build/*' | head -80", { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
7595
+ const tree = execSync8("find . -maxdepth 2 -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.locus/*' -not -path '*/dist/*' -not -path '*/build/*' | head -80", { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
7247
7596
  if (tree) {
7248
7597
  parts.push(`<file-tree>
7249
7598
  \`\`\`
@@ -7253,7 +7602,7 @@ ${tree}
7253
7602
  }
7254
7603
  } catch {}
7255
7604
  try {
7256
- const gitLog = execSync7("git log --oneline -10", {
7605
+ const gitLog = execSync8("git log --oneline -10", {
7257
7606
  cwd: projectRoot,
7258
7607
  encoding: "utf-8",
7259
7608
  stdio: ["pipe", "pipe", "pipe"]
@@ -7267,7 +7616,7 @@ ${gitLog}
7267
7616
  }
7268
7617
  } catch {}
7269
7618
  try {
7270
- const branch = execSync7("git rev-parse --abbrev-ref HEAD", {
7619
+ const branch = execSync8("git rev-parse --abbrev-ref HEAD", {
7271
7620
  cwd: projectRoot,
7272
7621
  encoding: "utf-8",
7273
7622
  stdio: ["pipe", "pipe", "pipe"]
@@ -7329,7 +7678,7 @@ function buildFeedbackInstructions() {
7329
7678
  }
7330
7679
  function readFileSafe(path) {
7331
7680
  try {
7332
- if (!existsSync14(path))
7681
+ if (!existsSync15(path))
7333
7682
  return null;
7334
7683
  return readFileSync9(path, "utf-8");
7335
7684
  } catch {
@@ -7523,7 +7872,7 @@ var init_diff_renderer = __esm(() => {
7523
7872
  });
7524
7873
 
7525
7874
  // src/repl/commands.ts
7526
- import { execSync as execSync8 } from "node:child_process";
7875
+ import { execSync as execSync9 } from "node:child_process";
7527
7876
  function getSlashCommands() {
7528
7877
  return [
7529
7878
  {
@@ -7721,7 +8070,7 @@ function cmdModel(args, ctx) {
7721
8070
  }
7722
8071
  function cmdDiff(_args, ctx) {
7723
8072
  try {
7724
- const diff = execSync8("git diff", {
8073
+ const diff = execSync9("git diff", {
7725
8074
  cwd: ctx.projectRoot,
7726
8075
  encoding: "utf-8",
7727
8076
  stdio: ["pipe", "pipe", "pipe"]
@@ -7757,7 +8106,7 @@ function cmdDiff(_args, ctx) {
7757
8106
  }
7758
8107
  function cmdUndo(_args, ctx) {
7759
8108
  try {
7760
- const status = execSync8("git status --porcelain", {
8109
+ const status = execSync9("git status --porcelain", {
7761
8110
  cwd: ctx.projectRoot,
7762
8111
  encoding: "utf-8",
7763
8112
  stdio: ["pipe", "pipe", "pipe"]
@@ -7767,7 +8116,7 @@ function cmdUndo(_args, ctx) {
7767
8116
  `);
7768
8117
  return;
7769
8118
  }
7770
- execSync8("git checkout .", {
8119
+ execSync9("git checkout .", {
7771
8120
  cwd: ctx.projectRoot,
7772
8121
  encoding: "utf-8",
7773
8122
  stdio: ["pipe", "pipe", "pipe"]
@@ -7809,7 +8158,7 @@ var init_commands = __esm(() => {
7809
8158
 
7810
8159
  // src/repl/completions.ts
7811
8160
  import { readdirSync as readdirSync5 } from "node:fs";
7812
- import { basename as basename2, dirname as dirname4, join as join14 } from "node:path";
8161
+ import { basename as basename2, dirname as dirname4, join as join15 } from "node:path";
7813
8162
 
7814
8163
  class SlashCommandCompletion {
7815
8164
  commands;
@@ -7864,7 +8213,7 @@ class FilePathCompletion {
7864
8213
  }
7865
8214
  findMatches(partial) {
7866
8215
  try {
7867
- const dir = partial.includes("/") ? join14(this.projectRoot, dirname4(partial)) : this.projectRoot;
8216
+ const dir = partial.includes("/") ? join15(this.projectRoot, dirname4(partial)) : this.projectRoot;
7868
8217
  const prefix = basename2(partial);
7869
8218
  const entries = readdirSync5(dir, { withFileTypes: true });
7870
8219
  return entries.filter((e) => {
@@ -7900,14 +8249,14 @@ class CombinedCompletion {
7900
8249
  var init_completions = () => {};
7901
8250
 
7902
8251
  // src/repl/input-history.ts
7903
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
7904
- import { dirname as dirname5, join as join15 } from "node:path";
8252
+ import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
8253
+ import { dirname as dirname5, join as join16 } from "node:path";
7905
8254
 
7906
8255
  class InputHistory {
7907
8256
  entries = [];
7908
8257
  filePath;
7909
8258
  constructor(projectRoot) {
7910
- this.filePath = join15(projectRoot, ".locus", "sessions", ".input-history");
8259
+ this.filePath = join16(projectRoot, ".locus", "sessions", ".input-history");
7911
8260
  this.load();
7912
8261
  }
7913
8262
  add(text) {
@@ -7946,7 +8295,7 @@ class InputHistory {
7946
8295
  }
7947
8296
  load() {
7948
8297
  try {
7949
- if (!existsSync15(this.filePath))
8298
+ if (!existsSync16(this.filePath))
7950
8299
  return;
7951
8300
  const content = readFileSync10(this.filePath, "utf-8");
7952
8301
  this.entries = content.split(`
@@ -7956,12 +8305,12 @@ class InputHistory {
7956
8305
  save() {
7957
8306
  try {
7958
8307
  const dir = dirname5(this.filePath);
7959
- if (!existsSync15(dir)) {
7960
- mkdirSync10(dir, { recursive: true });
8308
+ if (!existsSync16(dir)) {
8309
+ mkdirSync11(dir, { recursive: true });
7961
8310
  }
7962
8311
  const content = this.entries.map((e) => this.escape(e)).join(`
7963
8312
  `);
7964
- writeFileSync6(this.filePath, content, "utf-8");
8313
+ writeFileSync7(this.filePath, content, "utf-8");
7965
8314
  } catch {}
7966
8315
  }
7967
8316
  escape(text) {
@@ -7987,21 +8336,21 @@ var init_model_config = __esm(() => {
7987
8336
 
7988
8337
  // src/repl/session-manager.ts
7989
8338
  import {
7990
- existsSync as existsSync16,
7991
- mkdirSync as mkdirSync11,
8339
+ existsSync as existsSync17,
8340
+ mkdirSync as mkdirSync12,
7992
8341
  readdirSync as readdirSync6,
7993
8342
  readFileSync as readFileSync11,
7994
8343
  unlinkSync as unlinkSync3,
7995
- writeFileSync as writeFileSync7
8344
+ writeFileSync as writeFileSync8
7996
8345
  } from "node:fs";
7997
- import { basename as basename3, join as join16 } from "node:path";
8346
+ import { basename as basename3, join as join17 } from "node:path";
7998
8347
 
7999
8348
  class SessionManager {
8000
8349
  sessionsDir;
8001
8350
  constructor(projectRoot) {
8002
- this.sessionsDir = join16(projectRoot, ".locus", "sessions");
8003
- if (!existsSync16(this.sessionsDir)) {
8004
- mkdirSync11(this.sessionsDir, { recursive: true });
8351
+ this.sessionsDir = join17(projectRoot, ".locus", "sessions");
8352
+ if (!existsSync17(this.sessionsDir)) {
8353
+ mkdirSync12(this.sessionsDir, { recursive: true });
8005
8354
  }
8006
8355
  }
8007
8356
  create(options) {
@@ -8026,12 +8375,12 @@ class SessionManager {
8026
8375
  }
8027
8376
  isPersisted(sessionOrId) {
8028
8377
  const sessionId = typeof sessionOrId === "string" ? sessionOrId : sessionOrId.id;
8029
- return existsSync16(this.getSessionPath(sessionId));
8378
+ return existsSync17(this.getSessionPath(sessionId));
8030
8379
  }
8031
8380
  load(idOrPrefix) {
8032
8381
  const files = this.listSessionFiles();
8033
8382
  const exactPath = this.getSessionPath(idOrPrefix);
8034
- if (existsSync16(exactPath)) {
8383
+ if (existsSync17(exactPath)) {
8035
8384
  try {
8036
8385
  return JSON.parse(readFileSync11(exactPath, "utf-8"));
8037
8386
  } catch {
@@ -8054,7 +8403,7 @@ class SessionManager {
8054
8403
  save(session) {
8055
8404
  session.updated = new Date().toISOString();
8056
8405
  const path = this.getSessionPath(session.id);
8057
- writeFileSync7(path, `${JSON.stringify(session, null, 2)}
8406
+ writeFileSync8(path, `${JSON.stringify(session, null, 2)}
8058
8407
  `, "utf-8");
8059
8408
  }
8060
8409
  addMessage(session, message) {
@@ -8081,7 +8430,7 @@ class SessionManager {
8081
8430
  }
8082
8431
  delete(sessionId) {
8083
8432
  const path = this.getSessionPath(sessionId);
8084
- if (existsSync16(path)) {
8433
+ if (existsSync17(path)) {
8085
8434
  unlinkSync3(path);
8086
8435
  return true;
8087
8436
  }
@@ -8111,7 +8460,7 @@ class SessionManager {
8111
8460
  const remaining = withStats.length - pruned;
8112
8461
  if (remaining > MAX_SESSIONS) {
8113
8462
  const toRemove = remaining - MAX_SESSIONS;
8114
- const alive = withStats.filter((e) => existsSync16(e.path));
8463
+ const alive = withStats.filter((e) => existsSync17(e.path));
8115
8464
  for (let i = 0;i < toRemove && i < alive.length; i++) {
8116
8465
  try {
8117
8466
  unlinkSync3(alive[i].path);
@@ -8126,7 +8475,7 @@ class SessionManager {
8126
8475
  }
8127
8476
  listSessionFiles() {
8128
8477
  try {
8129
- return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
8478
+ return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join17(this.sessionsDir, f));
8130
8479
  } catch {
8131
8480
  return [];
8132
8481
  }
@@ -8135,7 +8484,7 @@ class SessionManager {
8135
8484
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
8136
8485
  }
8137
8486
  getSessionPath(sessionId) {
8138
- return join16(this.sessionsDir, `${sessionId}.json`);
8487
+ return join17(this.sessionsDir, `${sessionId}.json`);
8139
8488
  }
8140
8489
  }
8141
8490
  var MAX_SESSIONS = 50, SESSION_MAX_AGE_MS;
@@ -8145,17 +8494,17 @@ var init_session_manager = __esm(() => {
8145
8494
  });
8146
8495
 
8147
8496
  // src/repl/voice.ts
8148
- import { execSync as execSync9, spawn as spawn6 } from "node:child_process";
8149
- import { existsSync as existsSync17, mkdirSync as mkdirSync12, unlinkSync as unlinkSync4 } from "node:fs";
8497
+ import { execSync as execSync10, spawn as spawn6 } from "node:child_process";
8498
+ import { existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync4 } from "node:fs";
8150
8499
  import { cpus, homedir as homedir4, platform, tmpdir as tmpdir4 } from "node:os";
8151
- import { join as join17 } from "node:path";
8500
+ import { join as join18 } from "node:path";
8152
8501
  function getWhisperModelPath() {
8153
- return join17(WHISPER_MODELS_DIR, `ggml-${WHISPER_MODEL}.bin`);
8502
+ return join18(WHISPER_MODELS_DIR, `ggml-${WHISPER_MODEL}.bin`);
8154
8503
  }
8155
8504
  function commandExists(cmd) {
8156
8505
  try {
8157
8506
  const which = platform() === "win32" ? "where" : "which";
8158
- execSync9(`${which} ${cmd}`, { stdio: "pipe" });
8507
+ execSync10(`${which} ${cmd}`, { stdio: "pipe" });
8159
8508
  return true;
8160
8509
  } catch {
8161
8510
  return false;
@@ -8168,16 +8517,16 @@ function findWhisperBinary() {
8168
8517
  return name;
8169
8518
  }
8170
8519
  for (const name of candidates) {
8171
- const fullPath = join17(LOCUS_BIN_DIR, name);
8172
- if (existsSync17(fullPath))
8520
+ const fullPath = join18(LOCUS_BIN_DIR, name);
8521
+ if (existsSync18(fullPath))
8173
8522
  return fullPath;
8174
8523
  }
8175
8524
  if (platform() === "darwin") {
8176
8525
  const brewDirs = ["/opt/homebrew/bin", "/usr/local/bin"];
8177
8526
  for (const dir of brewDirs) {
8178
8527
  for (const name of candidates) {
8179
- const fullPath = join17(dir, name);
8180
- if (existsSync17(fullPath))
8528
+ const fullPath = join18(dir, name);
8529
+ if (existsSync18(fullPath))
8181
8530
  return fullPath;
8182
8531
  }
8183
8532
  }
@@ -8192,11 +8541,11 @@ function findSoxRecBinary() {
8192
8541
  if (platform() === "darwin") {
8193
8542
  const brewDirs = ["/opt/homebrew/bin", "/usr/local/bin"];
8194
8543
  for (const dir of brewDirs) {
8195
- const recPath = join17(dir, "rec");
8196
- if (existsSync17(recPath))
8544
+ const recPath = join18(dir, "rec");
8545
+ if (existsSync18(recPath))
8197
8546
  return recPath;
8198
- const soxPath = join17(dir, "sox");
8199
- if (existsSync17(soxPath))
8547
+ const soxPath = join18(dir, "sox");
8548
+ if (existsSync18(soxPath))
8200
8549
  return soxPath;
8201
8550
  }
8202
8551
  }
@@ -8205,7 +8554,7 @@ function findSoxRecBinary() {
8205
8554
  function checkDependencies() {
8206
8555
  const soxBinary = findSoxRecBinary();
8207
8556
  const whisperBinary = findWhisperBinary();
8208
- const modelDownloaded = existsSync17(getWhisperModelPath());
8557
+ const modelDownloaded = existsSync18(getWhisperModelPath());
8209
8558
  return {
8210
8559
  sox: soxBinary !== null,
8211
8560
  whisper: whisperBinary !== null,
@@ -8281,22 +8630,22 @@ function installSox(pm) {
8281
8630
  try {
8282
8631
  switch (pm) {
8283
8632
  case "brew":
8284
- execSync9("brew install sox", { stdio: "inherit", timeout: 300000 });
8633
+ execSync10("brew install sox", { stdio: "inherit", timeout: 300000 });
8285
8634
  break;
8286
8635
  case "apt":
8287
- execSync9("sudo apt-get install -y sox", {
8636
+ execSync10("sudo apt-get install -y sox", {
8288
8637
  stdio: "inherit",
8289
8638
  timeout: 300000
8290
8639
  });
8291
8640
  break;
8292
8641
  case "dnf":
8293
- execSync9("sudo dnf install -y sox", {
8642
+ execSync10("sudo dnf install -y sox", {
8294
8643
  stdio: "inherit",
8295
8644
  timeout: 300000
8296
8645
  });
8297
8646
  break;
8298
8647
  case "pacman":
8299
- execSync9("sudo pacman -S --noconfirm sox", {
8648
+ execSync10("sudo pacman -S --noconfirm sox", {
8300
8649
  stdio: "inherit",
8301
8650
  timeout: 300000
8302
8651
  });
@@ -8310,7 +8659,7 @@ function installSox(pm) {
8310
8659
  function installWhisperCpp(pm) {
8311
8660
  if (pm === "brew") {
8312
8661
  try {
8313
- execSync9("brew install whisper-cpp", {
8662
+ execSync10("brew install whisper-cpp", {
8314
8663
  stdio: "inherit",
8315
8664
  timeout: 300000
8316
8665
  });
@@ -8332,19 +8681,19 @@ function ensureBuildDeps(pm) {
8332
8681
  try {
8333
8682
  switch (pm) {
8334
8683
  case "apt":
8335
- execSync9("sudo apt-get install -y cmake g++ make git", {
8684
+ execSync10("sudo apt-get install -y cmake g++ make git", {
8336
8685
  stdio: "inherit",
8337
8686
  timeout: 300000
8338
8687
  });
8339
8688
  break;
8340
8689
  case "dnf":
8341
- execSync9("sudo dnf install -y cmake gcc-c++ make git", {
8690
+ execSync10("sudo dnf install -y cmake gcc-c++ make git", {
8342
8691
  stdio: "inherit",
8343
8692
  timeout: 300000
8344
8693
  });
8345
8694
  break;
8346
8695
  case "pacman":
8347
- execSync9("sudo pacman -S --noconfirm cmake gcc make git", {
8696
+ execSync10("sudo pacman -S --noconfirm cmake gcc make git", {
8348
8697
  stdio: "inherit",
8349
8698
  timeout: 300000
8350
8699
  });
@@ -8359,40 +8708,40 @@ function ensureBuildDeps(pm) {
8359
8708
  }
8360
8709
  function buildWhisperFromSource(pm) {
8361
8710
  const out = process.stderr;
8362
- const buildDir = join17(tmpdir4(), `locus-whisper-build-${process.pid}`);
8711
+ const buildDir = join18(tmpdir4(), `locus-whisper-build-${process.pid}`);
8363
8712
  if (!ensureBuildDeps(pm)) {
8364
8713
  out.write(` ${red2("✗")} Could not install build tools (cmake, g++, git).
8365
8714
  `);
8366
8715
  return false;
8367
8716
  }
8368
8717
  try {
8369
- mkdirSync12(buildDir, { recursive: true });
8370
- mkdirSync12(LOCUS_BIN_DIR, { recursive: true });
8718
+ mkdirSync13(buildDir, { recursive: true });
8719
+ mkdirSync13(LOCUS_BIN_DIR, { recursive: true });
8371
8720
  out.write(` ${dim2("Cloning whisper.cpp...")}
8372
8721
  `);
8373
- execSync9(`git clone --depth 1 https://github.com/ggerganov/whisper.cpp.git "${join17(buildDir, "whisper.cpp")}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
8374
- const srcDir = join17(buildDir, "whisper.cpp");
8722
+ execSync10(`git clone --depth 1 https://github.com/ggerganov/whisper.cpp.git "${join18(buildDir, "whisper.cpp")}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
8723
+ const srcDir = join18(buildDir, "whisper.cpp");
8375
8724
  const numCpus = cpus().length || 2;
8376
8725
  out.write(` ${dim2("Building whisper.cpp (this may take a few minutes)...")}
8377
8726
  `);
8378
- execSync9("cmake -B build -DCMAKE_BUILD_TYPE=Release", {
8727
+ execSync10("cmake -B build -DCMAKE_BUILD_TYPE=Release", {
8379
8728
  cwd: srcDir,
8380
8729
  stdio: ["pipe", "pipe", "pipe"],
8381
8730
  timeout: 120000
8382
8731
  });
8383
- execSync9(`cmake --build build --config Release -j${numCpus}`, {
8732
+ execSync10(`cmake --build build --config Release -j${numCpus}`, {
8384
8733
  cwd: srcDir,
8385
8734
  stdio: ["pipe", "pipe", "pipe"],
8386
8735
  timeout: 600000
8387
8736
  });
8388
- const destPath = join17(LOCUS_BIN_DIR, "whisper-cli");
8737
+ const destPath = join18(LOCUS_BIN_DIR, "whisper-cli");
8389
8738
  const binaryCandidates = [
8390
- join17(srcDir, "build", "bin", "whisper-cli"),
8391
- join17(srcDir, "build", "bin", "main")
8739
+ join18(srcDir, "build", "bin", "whisper-cli"),
8740
+ join18(srcDir, "build", "bin", "main")
8392
8741
  ];
8393
8742
  for (const candidate of binaryCandidates) {
8394
- if (existsSync17(candidate)) {
8395
- execSync9(`cp "${candidate}" "${destPath}" && chmod +x "${destPath}"`, {
8743
+ if (existsSync18(candidate)) {
8744
+ execSync10(`cp "${candidate}" "${destPath}" && chmod +x "${destPath}"`, {
8396
8745
  stdio: "pipe"
8397
8746
  });
8398
8747
  return true;
@@ -8407,7 +8756,7 @@ function buildWhisperFromSource(pm) {
8407
8756
  return false;
8408
8757
  } finally {
8409
8758
  try {
8410
- execSync9(`rm -rf "${buildDir}"`, { stdio: "pipe" });
8759
+ execSync10(`rm -rf "${buildDir}"`, { stdio: "pipe" });
8411
8760
  } catch {}
8412
8761
  }
8413
8762
  }
@@ -8470,20 +8819,20 @@ ${bold2("Installing voice dependencies...")}
8470
8819
  }
8471
8820
  function downloadModel() {
8472
8821
  const modelPath = getWhisperModelPath();
8473
- if (existsSync17(modelPath))
8822
+ if (existsSync18(modelPath))
8474
8823
  return true;
8475
- mkdirSync12(WHISPER_MODELS_DIR, { recursive: true });
8824
+ mkdirSync13(WHISPER_MODELS_DIR, { recursive: true });
8476
8825
  const url = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${WHISPER_MODEL === "base.en" ? "ggml-base.en.bin" : `ggml-${WHISPER_MODEL}.bin`}`;
8477
8826
  process.stderr.write(`${dim2("Downloading whisper model")} ${bold2(WHISPER_MODEL)} ${dim2("(~150MB)...")}
8478
8827
  `);
8479
8828
  try {
8480
8829
  if (commandExists("curl")) {
8481
- execSync9(`curl -L -o "${modelPath}" "${url}"`, {
8830
+ execSync10(`curl -L -o "${modelPath}" "${url}"`, {
8482
8831
  stdio: ["pipe", "pipe", "pipe"],
8483
8832
  timeout: 300000
8484
8833
  });
8485
8834
  } else if (commandExists("wget")) {
8486
- execSync9(`wget -O "${modelPath}" "${url}"`, {
8835
+ execSync10(`wget -O "${modelPath}" "${url}"`, {
8487
8836
  stdio: ["pipe", "pipe", "pipe"],
8488
8837
  timeout: 300000
8489
8838
  });
@@ -8517,7 +8866,7 @@ class VoiceController {
8517
8866
  onStateChange;
8518
8867
  constructor(options) {
8519
8868
  this.onStateChange = options.onStateChange;
8520
- this.tempFile = join17(tmpdir4(), `locus-voice-${process.pid}.wav`);
8869
+ this.tempFile = join18(tmpdir4(), `locus-voice-${process.pid}.wav`);
8521
8870
  this.deps = checkDependencies();
8522
8871
  }
8523
8872
  getState() {
@@ -8585,7 +8934,7 @@ ${red2("✗")} Recording failed: ${err.message}\r
8585
8934
  this.recordProcess = null;
8586
8935
  this.setState("idle");
8587
8936
  await sleep2(200);
8588
- if (!existsSync17(this.tempFile)) {
8937
+ if (!existsSync18(this.tempFile)) {
8589
8938
  return null;
8590
8939
  }
8591
8940
  try {
@@ -8676,12 +9025,12 @@ function sleep2(ms) {
8676
9025
  var WHISPER_MODEL = "base.en", WHISPER_MODELS_DIR, LOCUS_BIN_DIR;
8677
9026
  var init_voice = __esm(() => {
8678
9027
  init_terminal();
8679
- WHISPER_MODELS_DIR = join17(homedir4(), ".locus", "whisper-models");
8680
- LOCUS_BIN_DIR = join17(homedir4(), ".locus", "bin");
9028
+ WHISPER_MODELS_DIR = join18(homedir4(), ".locus", "whisper-models");
9029
+ LOCUS_BIN_DIR = join18(homedir4(), ".locus", "bin");
8681
9030
  });
8682
9031
 
8683
9032
  // src/repl/repl.ts
8684
- import { execSync as execSync10 } from "node:child_process";
9033
+ import { execSync as execSync11 } from "node:child_process";
8685
9034
  async function startRepl(options) {
8686
9035
  const { projectRoot, config } = options;
8687
9036
  const sessionManager = new SessionManager(projectRoot);
@@ -8699,7 +9048,7 @@ async function startRepl(options) {
8699
9048
  } else {
8700
9049
  let branch = "main";
8701
9050
  try {
8702
- branch = execSync10("git rev-parse --abbrev-ref HEAD", {
9051
+ branch = execSync11("git rev-parse --abbrev-ref HEAD", {
8703
9052
  cwd: projectRoot,
8704
9053
  encoding: "utf-8",
8705
9054
  stdio: ["pipe", "pipe", "pipe"]
@@ -9187,11 +9536,11 @@ var init_exec = __esm(() => {
9187
9536
  });
9188
9537
 
9189
9538
  // src/core/submodule.ts
9190
- import { execSync as execSync11 } from "node:child_process";
9191
- import { existsSync as existsSync18 } from "node:fs";
9192
- import { join as join18 } from "node:path";
9539
+ import { execSync as execSync12 } from "node:child_process";
9540
+ import { existsSync as existsSync19 } from "node:fs";
9541
+ import { join as join19 } from "node:path";
9193
9542
  function git2(args, cwd) {
9194
- return execSync11(`git ${args}`, {
9543
+ return execSync12(`git ${args}`, {
9195
9544
  cwd,
9196
9545
  encoding: "utf-8",
9197
9546
  stdio: ["pipe", "pipe", "pipe"]
@@ -9205,7 +9554,7 @@ function gitSafe(args, cwd) {
9205
9554
  }
9206
9555
  }
9207
9556
  function hasSubmodules(cwd) {
9208
- return existsSync18(join18(cwd, ".gitmodules"));
9557
+ return existsSync19(join19(cwd, ".gitmodules"));
9209
9558
  }
9210
9559
  function listSubmodules(cwd) {
9211
9560
  if (!hasSubmodules(cwd))
@@ -9225,7 +9574,7 @@ function listSubmodules(cwd) {
9225
9574
  continue;
9226
9575
  submodules.push({
9227
9576
  path,
9228
- absolutePath: join18(cwd, path),
9577
+ absolutePath: join19(cwd, path),
9229
9578
  dirty
9230
9579
  });
9231
9580
  }
@@ -9238,7 +9587,7 @@ function getDirtySubmodules(cwd) {
9238
9587
  const submodules = listSubmodules(cwd);
9239
9588
  const dirty = [];
9240
9589
  for (const sub of submodules) {
9241
- if (!existsSync18(sub.absolutePath))
9590
+ if (!existsSync19(sub.absolutePath))
9242
9591
  continue;
9243
9592
  const status = gitSafe("status --porcelain", sub.absolutePath);
9244
9593
  if (status && status.trim().length > 0) {
@@ -9259,7 +9608,7 @@ function commitDirtySubmodules(cwd, issueNumber, issueTitle) {
9259
9608
  const message = `chore: complete #${issueNumber} - ${issueTitle}
9260
9609
 
9261
9610
  Co-Authored-By: LocusAgent <agent@locusai.team>`;
9262
- execSync11("git commit -F -", {
9611
+ execSync12("git commit -F -", {
9263
9612
  input: message,
9264
9613
  cwd: sub.absolutePath,
9265
9614
  encoding: "utf-8",
@@ -9325,7 +9674,7 @@ function pushSubmoduleBranches(cwd) {
9325
9674
  const log = getLogger();
9326
9675
  const submodules = listSubmodules(cwd);
9327
9676
  for (const sub of submodules) {
9328
- if (!existsSync18(sub.absolutePath))
9677
+ if (!existsSync19(sub.absolutePath))
9329
9678
  continue;
9330
9679
  const branch = gitSafe("rev-parse --abbrev-ref HEAD", sub.absolutePath)?.trim();
9331
9680
  if (!branch || branch === "HEAD")
@@ -9346,7 +9695,7 @@ var init_submodule = __esm(() => {
9346
9695
  });
9347
9696
 
9348
9697
  // src/core/agent.ts
9349
- import { execSync as execSync12 } from "node:child_process";
9698
+ import { execSync as execSync13 } from "node:child_process";
9350
9699
  async function executeIssue(projectRoot, options) {
9351
9700
  const log = getLogger();
9352
9701
  const timer = createTimer();
@@ -9375,7 +9724,7 @@ ${cyan2("●")} ${bold2(`#${issueNumber}`)} ${issue.title}
9375
9724
  }
9376
9725
  let issueComments = [];
9377
9726
  try {
9378
- const commentsRaw = execSync12(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
9727
+ const commentsRaw = execSync13(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
9379
9728
  if (commentsRaw) {
9380
9729
  issueComments = commentsRaw.split(`
9381
9730
  `).filter(Boolean);
@@ -9539,12 +9888,12 @@ ${aiResult.success ? green("✓") : red2("✗")} Iteration ${aiResult.success ?
9539
9888
  }
9540
9889
  async function createIssuePR(projectRoot, config, issue) {
9541
9890
  try {
9542
- const currentBranch = execSync12("git rev-parse --abbrev-ref HEAD", {
9891
+ const currentBranch = execSync13("git rev-parse --abbrev-ref HEAD", {
9543
9892
  cwd: projectRoot,
9544
9893
  encoding: "utf-8",
9545
9894
  stdio: ["pipe", "pipe", "pipe"]
9546
9895
  }).trim();
9547
- const diff = execSync12(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
9896
+ const diff = execSync13(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
9548
9897
  cwd: projectRoot,
9549
9898
  encoding: "utf-8",
9550
9899
  stdio: ["pipe", "pipe", "pipe"]
@@ -9554,7 +9903,7 @@ async function createIssuePR(projectRoot, config, issue) {
9554
9903
  return;
9555
9904
  }
9556
9905
  pushSubmoduleBranches(projectRoot);
9557
- execSync12(`git push -u origin ${currentBranch}`, {
9906
+ execSync13(`git push -u origin ${currentBranch}`, {
9558
9907
  cwd: projectRoot,
9559
9908
  encoding: "utf-8",
9560
9909
  stdio: ["pipe", "pipe", "pipe"]
@@ -9609,9 +9958,9 @@ var init_agent = __esm(() => {
9609
9958
  });
9610
9959
 
9611
9960
  // src/core/conflict.ts
9612
- import { execSync as execSync13 } from "node:child_process";
9961
+ import { execSync as execSync14 } from "node:child_process";
9613
9962
  function git3(args, cwd) {
9614
- return execSync13(`git ${args}`, {
9963
+ return execSync14(`git ${args}`, {
9615
9964
  cwd,
9616
9965
  encoding: "utf-8",
9617
9966
  stdio: ["pipe", "pipe", "pipe"]
@@ -9740,19 +10089,19 @@ var init_conflict = __esm(() => {
9740
10089
 
9741
10090
  // src/core/run-state.ts
9742
10091
  import {
9743
- existsSync as existsSync19,
9744
- mkdirSync as mkdirSync13,
10092
+ existsSync as existsSync20,
10093
+ mkdirSync as mkdirSync14,
9745
10094
  readFileSync as readFileSync12,
9746
10095
  unlinkSync as unlinkSync5,
9747
- writeFileSync as writeFileSync8
10096
+ writeFileSync as writeFileSync9
9748
10097
  } from "node:fs";
9749
- import { dirname as dirname6, join as join19 } from "node:path";
10098
+ import { dirname as dirname6, join as join20 } from "node:path";
9750
10099
  function getRunStatePath(projectRoot) {
9751
- return join19(projectRoot, ".locus", "run-state.json");
10100
+ return join20(projectRoot, ".locus", "run-state.json");
9752
10101
  }
9753
10102
  function loadRunState(projectRoot) {
9754
10103
  const path = getRunStatePath(projectRoot);
9755
- if (!existsSync19(path))
10104
+ if (!existsSync20(path))
9756
10105
  return null;
9757
10106
  try {
9758
10107
  return JSON.parse(readFileSync12(path, "utf-8"));
@@ -9764,15 +10113,15 @@ function loadRunState(projectRoot) {
9764
10113
  function saveRunState(projectRoot, state) {
9765
10114
  const path = getRunStatePath(projectRoot);
9766
10115
  const dir = dirname6(path);
9767
- if (!existsSync19(dir)) {
9768
- mkdirSync13(dir, { recursive: true });
10116
+ if (!existsSync20(dir)) {
10117
+ mkdirSync14(dir, { recursive: true });
9769
10118
  }
9770
- writeFileSync8(path, `${JSON.stringify(state, null, 2)}
10119
+ writeFileSync9(path, `${JSON.stringify(state, null, 2)}
9771
10120
  `, "utf-8");
9772
10121
  }
9773
10122
  function clearRunState(projectRoot) {
9774
10123
  const path = getRunStatePath(projectRoot);
9775
- if (existsSync19(path)) {
10124
+ if (existsSync20(path)) {
9776
10125
  unlinkSync5(path);
9777
10126
  }
9778
10127
  }
@@ -9912,11 +10261,11 @@ var init_shutdown = __esm(() => {
9912
10261
  });
9913
10262
 
9914
10263
  // src/core/worktree.ts
9915
- import { execSync as execSync14 } from "node:child_process";
9916
- import { existsSync as existsSync20, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
9917
- import { join as join20 } from "node:path";
10264
+ import { execSync as execSync15 } from "node:child_process";
10265
+ import { existsSync as existsSync21, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
10266
+ import { join as join21 } from "node:path";
9918
10267
  function git4(args, cwd) {
9919
- return execSync14(`git ${args}`, {
10268
+ return execSync15(`git ${args}`, {
9920
10269
  cwd,
9921
10270
  encoding: "utf-8",
9922
10271
  stdio: ["pipe", "pipe", "pipe"]
@@ -9930,10 +10279,10 @@ function gitSafe3(args, cwd) {
9930
10279
  }
9931
10280
  }
9932
10281
  function getWorktreeDir(projectRoot) {
9933
- return join20(projectRoot, ".locus", "worktrees");
10282
+ return join21(projectRoot, ".locus", "worktrees");
9934
10283
  }
9935
10284
  function getWorktreePath(projectRoot, issueNumber) {
9936
- return join20(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
10285
+ return join21(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
9937
10286
  }
9938
10287
  function generateBranchName(issueNumber) {
9939
10288
  const randomSuffix = Math.random().toString(36).slice(2, 8);
@@ -9941,7 +10290,7 @@ function generateBranchName(issueNumber) {
9941
10290
  }
9942
10291
  function getWorktreeBranch(worktreePath) {
9943
10292
  try {
9944
- return execSync14("git branch --show-current", {
10293
+ return execSync15("git branch --show-current", {
9945
10294
  cwd: worktreePath,
9946
10295
  encoding: "utf-8",
9947
10296
  stdio: ["pipe", "pipe", "pipe"]
@@ -9953,7 +10302,7 @@ function getWorktreeBranch(worktreePath) {
9953
10302
  function createWorktree(projectRoot, issueNumber, baseBranch) {
9954
10303
  const log = getLogger();
9955
10304
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
9956
- if (existsSync20(worktreePath)) {
10305
+ if (existsSync21(worktreePath)) {
9957
10306
  log.verbose(`Worktree already exists for issue #${issueNumber}`);
9958
10307
  const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/issue-${issueNumber}`;
9959
10308
  return {
@@ -9981,7 +10330,7 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
9981
10330
  function removeWorktree(projectRoot, issueNumber) {
9982
10331
  const log = getLogger();
9983
10332
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
9984
- if (!existsSync20(worktreePath)) {
10333
+ if (!existsSync21(worktreePath)) {
9985
10334
  log.verbose(`Worktree for issue #${issueNumber} does not exist`);
9986
10335
  return;
9987
10336
  }
@@ -10000,7 +10349,7 @@ function removeWorktree(projectRoot, issueNumber) {
10000
10349
  function listWorktrees(projectRoot) {
10001
10350
  const log = getLogger();
10002
10351
  const worktreeDir = getWorktreeDir(projectRoot);
10003
- if (!existsSync20(worktreeDir)) {
10352
+ if (!existsSync21(worktreeDir)) {
10004
10353
  return [];
10005
10354
  }
10006
10355
  const entries = readdirSync7(worktreeDir).filter((entry) => entry.startsWith("issue-"));
@@ -10020,7 +10369,7 @@ function listWorktrees(projectRoot) {
10020
10369
  if (!match)
10021
10370
  continue;
10022
10371
  const issueNumber = Number.parseInt(match[1], 10);
10023
- const path = join20(worktreeDir, entry);
10372
+ const path = join21(worktreeDir, entry);
10024
10373
  const branch = getWorktreeBranch(path) ?? `locus/issue-${issueNumber}`;
10025
10374
  let resolvedPath;
10026
10375
  try {
@@ -10068,7 +10417,7 @@ var exports_run = {};
10068
10417
  __export(exports_run, {
10069
10418
  runCommand: () => runCommand
10070
10419
  });
10071
- import { execSync as execSync15 } from "node:child_process";
10420
+ import { execSync as execSync16 } from "node:child_process";
10072
10421
  function resolveExecutionContext(config, modelOverride) {
10073
10422
  const model = modelOverride ?? config.ai.model;
10074
10423
  const provider = inferProviderFromModel(model) ?? config.ai.provider;
@@ -10228,7 +10577,7 @@ ${yellow2("⚠")} A sprint run is already in progress.
10228
10577
  }
10229
10578
  if (!flags.dryRun) {
10230
10579
  try {
10231
- execSync15(`git checkout -B ${branchName}`, {
10580
+ execSync16(`git checkout -B ${branchName}`, {
10232
10581
  cwd: projectRoot,
10233
10582
  encoding: "utf-8",
10234
10583
  stdio: ["pipe", "pipe", "pipe"]
@@ -10278,7 +10627,7 @@ ${red2("✗")} Auto-rebase failed. Resolve manually.
10278
10627
  let sprintContext;
10279
10628
  if (i > 0 && !flags.dryRun) {
10280
10629
  try {
10281
- sprintContext = execSync15(`git diff origin/${config.agent.baseBranch}..HEAD`, {
10630
+ sprintContext = execSync16(`git diff origin/${config.agent.baseBranch}..HEAD`, {
10282
10631
  cwd: projectRoot,
10283
10632
  encoding: "utf-8",
10284
10633
  stdio: ["pipe", "pipe", "pipe"]
@@ -10343,7 +10692,7 @@ ${bold2("Summary:")}
10343
10692
  const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
10344
10693
  if (prNumber !== undefined) {
10345
10694
  try {
10346
- execSync15(`git checkout ${config.agent.baseBranch}`, {
10695
+ execSync16(`git checkout ${config.agent.baseBranch}`, {
10347
10696
  cwd: projectRoot,
10348
10697
  encoding: "utf-8",
10349
10698
  stdio: ["pipe", "pipe", "pipe"]
@@ -10388,7 +10737,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
10388
10737
  `);
10389
10738
  if (!flags.dryRun) {
10390
10739
  try {
10391
- execSync15(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
10740
+ execSync16(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
10392
10741
  cwd: projectRoot,
10393
10742
  encoding: "utf-8",
10394
10743
  stdio: ["pipe", "pipe", "pipe"]
@@ -10413,7 +10762,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
10413
10762
  if (!flags.dryRun) {
10414
10763
  if (result.success) {
10415
10764
  try {
10416
- execSync15(`git checkout ${config.agent.baseBranch}`, {
10765
+ execSync16(`git checkout ${config.agent.baseBranch}`, {
10417
10766
  cwd: projectRoot,
10418
10767
  encoding: "utf-8",
10419
10768
  stdio: ["pipe", "pipe", "pipe"]
@@ -10550,13 +10899,13 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
10550
10899
  `);
10551
10900
  if (state.type === "sprint" && state.branch) {
10552
10901
  try {
10553
- const currentBranch = execSync15("git rev-parse --abbrev-ref HEAD", {
10902
+ const currentBranch = execSync16("git rev-parse --abbrev-ref HEAD", {
10554
10903
  cwd: projectRoot,
10555
10904
  encoding: "utf-8",
10556
10905
  stdio: ["pipe", "pipe", "pipe"]
10557
10906
  }).trim();
10558
10907
  if (currentBranch !== state.branch) {
10559
- execSync15(`git checkout ${state.branch}`, {
10908
+ execSync16(`git checkout ${state.branch}`, {
10560
10909
  cwd: projectRoot,
10561
10910
  encoding: "utf-8",
10562
10911
  stdio: ["pipe", "pipe", "pipe"]
@@ -10623,7 +10972,7 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
10623
10972
  const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
10624
10973
  if (prNumber !== undefined) {
10625
10974
  try {
10626
- execSync15(`git checkout ${config.agent.baseBranch}`, {
10975
+ execSync16(`git checkout ${config.agent.baseBranch}`, {
10627
10976
  cwd: projectRoot,
10628
10977
  encoding: "utf-8",
10629
10978
  stdio: ["pipe", "pipe", "pipe"]
@@ -10659,14 +11008,14 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
10659
11008
  process.stderr.write(` ${dim2(`Committed submodule changes: ${committedSubmodules.join(", ")}`)}
10660
11009
  `);
10661
11010
  }
10662
- const status = execSync15("git status --porcelain", {
11011
+ const status = execSync16("git status --porcelain", {
10663
11012
  cwd: projectRoot,
10664
11013
  encoding: "utf-8",
10665
11014
  stdio: ["pipe", "pipe", "pipe"]
10666
11015
  }).trim();
10667
11016
  if (!status)
10668
11017
  return;
10669
- execSync15("git add -A", {
11018
+ execSync16("git add -A", {
10670
11019
  cwd: projectRoot,
10671
11020
  encoding: "utf-8",
10672
11021
  stdio: ["pipe", "pipe", "pipe"]
@@ -10674,7 +11023,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
10674
11023
  const message = `chore: complete #${issueNumber} - ${issueTitle}
10675
11024
 
10676
11025
  Co-Authored-By: LocusAgent <agent@locusai.team>`;
10677
- execSync15(`git commit -F -`, {
11026
+ execSync16(`git commit -F -`, {
10678
11027
  input: message,
10679
11028
  cwd: projectRoot,
10680
11029
  encoding: "utf-8",
@@ -10688,7 +11037,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
10688
11037
  if (!config.agent.autoPR)
10689
11038
  return;
10690
11039
  try {
10691
- const diff = execSync15(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
11040
+ const diff = execSync16(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
10692
11041
  cwd: projectRoot,
10693
11042
  encoding: "utf-8",
10694
11043
  stdio: ["pipe", "pipe", "pipe"]
@@ -10699,7 +11048,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
10699
11048
  return;
10700
11049
  }
10701
11050
  pushSubmoduleBranches(projectRoot);
10702
- execSync15(`git push -u origin ${branchName}`, {
11051
+ execSync16(`git push -u origin ${branchName}`, {
10703
11052
  cwd: projectRoot,
10704
11053
  encoding: "utf-8",
10705
11054
  stdio: ["pipe", "pipe", "pipe"]
@@ -10857,14 +11206,14 @@ __export(exports_plan, {
10857
11206
  parsePlanArgs: () => parsePlanArgs
10858
11207
  });
10859
11208
  import {
10860
- existsSync as existsSync21,
10861
- mkdirSync as mkdirSync14,
11209
+ existsSync as existsSync22,
11210
+ mkdirSync as mkdirSync15,
10862
11211
  readdirSync as readdirSync8,
10863
11212
  readFileSync as readFileSync13,
10864
- writeFileSync as writeFileSync9
11213
+ writeFileSync as writeFileSync10
10865
11214
  } from "node:fs";
10866
- import { join as join21 } from "node:path";
10867
- function printHelp() {
11215
+ import { join as join22 } from "node:path";
11216
+ function printHelp2() {
10868
11217
  process.stderr.write(`
10869
11218
  ${bold2("locus plan")} — AI-powered sprint planning
10870
11219
 
@@ -10894,12 +11243,12 @@ function normalizeSprintName(name) {
10894
11243
  return name.trim().toLowerCase();
10895
11244
  }
10896
11245
  function getPlansDir(projectRoot) {
10897
- return join21(projectRoot, ".locus", "plans");
11246
+ return join22(projectRoot, ".locus", "plans");
10898
11247
  }
10899
11248
  function ensurePlansDir(projectRoot) {
10900
11249
  const dir = getPlansDir(projectRoot);
10901
- if (!existsSync21(dir)) {
10902
- mkdirSync14(dir, { recursive: true });
11250
+ if (!existsSync22(dir)) {
11251
+ mkdirSync15(dir, { recursive: true });
10903
11252
  }
10904
11253
  return dir;
10905
11254
  }
@@ -10908,14 +11257,14 @@ function generateId() {
10908
11257
  }
10909
11258
  function loadPlanFile(projectRoot, id) {
10910
11259
  const dir = getPlansDir(projectRoot);
10911
- if (!existsSync21(dir))
11260
+ if (!existsSync22(dir))
10912
11261
  return null;
10913
11262
  const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
10914
11263
  const match = files.find((f) => f.startsWith(id));
10915
11264
  if (!match)
10916
11265
  return null;
10917
11266
  try {
10918
- const content = readFileSync13(join21(dir, match), "utf-8");
11267
+ const content = readFileSync13(join22(dir, match), "utf-8");
10919
11268
  return JSON.parse(content);
10920
11269
  } catch {
10921
11270
  return null;
@@ -10923,7 +11272,7 @@ function loadPlanFile(projectRoot, id) {
10923
11272
  }
10924
11273
  async function planCommand(projectRoot, args, flags = {}) {
10925
11274
  if (args[0] === "help" || args.length === 0) {
10926
- printHelp();
11275
+ printHelp2();
10927
11276
  return;
10928
11277
  }
10929
11278
  if (args[0] === "list") {
@@ -10961,7 +11310,7 @@ async function planCommand(projectRoot, args, flags = {}) {
10961
11310
  }
10962
11311
  function handleListPlans(projectRoot) {
10963
11312
  const dir = getPlansDir(projectRoot);
10964
- if (!existsSync21(dir)) {
11313
+ if (!existsSync22(dir)) {
10965
11314
  process.stderr.write(`${dim2("No saved plans yet.")}
10966
11315
  `);
10967
11316
  return;
@@ -10979,7 +11328,7 @@ ${bold2("Saved Plans:")}
10979
11328
  for (const file of files) {
10980
11329
  const id = file.replace(".json", "");
10981
11330
  try {
10982
- const content = readFileSync13(join21(dir, file), "utf-8");
11331
+ const content = readFileSync13(join22(dir, file), "utf-8");
10983
11332
  const plan = JSON.parse(content);
10984
11333
  const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
10985
11334
  const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
@@ -11090,7 +11439,7 @@ ${bold2("Approving plan:")}
11090
11439
  async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
11091
11440
  const id = generateId();
11092
11441
  const plansDir = ensurePlansDir(projectRoot);
11093
- const planPath = join21(plansDir, `${id}.json`);
11442
+ const planPath = join22(plansDir, `${id}.json`);
11094
11443
  const planPathRelative = `.locus/plans/${id}.json`;
11095
11444
  const displayDirective = directive;
11096
11445
  process.stderr.write(`
@@ -11132,7 +11481,7 @@ ${red2("✗")} Planning failed: ${aiResult.error}
11132
11481
  `);
11133
11482
  return;
11134
11483
  }
11135
- if (!existsSync21(planPath)) {
11484
+ if (!existsSync22(planPath)) {
11136
11485
  process.stderr.write(`
11137
11486
  ${yellow2("⚠")} Plan file was not created at ${bold2(planPathRelative)}.
11138
11487
  `);
@@ -11164,7 +11513,7 @@ ${yellow2("⚠")} Plan file has no issues.
11164
11513
  plan.sprint = sprintName;
11165
11514
  if (!plan.createdAt)
11166
11515
  plan.createdAt = new Date().toISOString();
11167
- writeFileSync9(planPath, JSON.stringify(plan, null, 2), "utf-8");
11516
+ writeFileSync10(planPath, JSON.stringify(plan, null, 2), "utf-8");
11168
11517
  process.stderr.write(`
11169
11518
  ${bold2("Plan saved:")} ${cyan2(id)}
11170
11519
 
@@ -11313,15 +11662,15 @@ ${directive}${sprintName ? `
11313
11662
 
11314
11663
  **Sprint:** ${sprintName}` : ""}
11315
11664
  </directive>`);
11316
- const locusPath = join21(projectRoot, ".locus", "LOCUS.md");
11317
- if (existsSync21(locusPath)) {
11665
+ const locusPath = join22(projectRoot, ".locus", "LOCUS.md");
11666
+ if (existsSync22(locusPath)) {
11318
11667
  const content = readFileSync13(locusPath, "utf-8");
11319
11668
  parts.push(`<project-context>
11320
11669
  ${content.slice(0, 3000)}
11321
11670
  </project-context>`);
11322
11671
  }
11323
- const learningsPath = join21(projectRoot, ".locus", "LEARNINGS.md");
11324
- if (existsSync21(learningsPath)) {
11672
+ const learningsPath = join22(projectRoot, ".locus", "LEARNINGS.md");
11673
+ if (existsSync22(learningsPath)) {
11325
11674
  const content = readFileSync13(learningsPath, "utf-8");
11326
11675
  parts.push(`<past-learnings>
11327
11676
  ${content.slice(0, 2000)}
@@ -11501,10 +11850,10 @@ var exports_review = {};
11501
11850
  __export(exports_review, {
11502
11851
  reviewCommand: () => reviewCommand
11503
11852
  });
11504
- import { execFileSync as execFileSync2, execSync as execSync16 } from "node:child_process";
11505
- import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
11506
- import { join as join22 } from "node:path";
11507
- function printHelp2() {
11853
+ import { execFileSync as execFileSync2, execSync as execSync17 } from "node:child_process";
11854
+ import { existsSync as existsSync23, readFileSync as readFileSync14 } from "node:fs";
11855
+ import { join as join23 } from "node:path";
11856
+ function printHelp3() {
11508
11857
  process.stderr.write(`
11509
11858
  ${bold2("locus review")} — AI-powered code review
11510
11859
 
@@ -11527,7 +11876,7 @@ ${bold2("Examples:")}
11527
11876
  }
11528
11877
  async function reviewCommand(projectRoot, args, flags = {}) {
11529
11878
  if (args[0] === "help") {
11530
- printHelp2();
11879
+ printHelp3();
11531
11880
  return;
11532
11881
  }
11533
11882
  const config = loadConfig(projectRoot);
@@ -11587,7 +11936,7 @@ ${bold2("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red2(
11587
11936
  async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
11588
11937
  let prInfo;
11589
11938
  try {
11590
- const result = execSync16(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11939
+ const result = execSync17(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11591
11940
  const raw = JSON.parse(result);
11592
11941
  prInfo = {
11593
11942
  number: raw.number,
@@ -11671,8 +12020,8 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
11671
12020
  parts.push(`<role>
11672
12021
  You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
11673
12022
  </role>`);
11674
- const locusPath = join22(projectRoot, ".locus", "LOCUS.md");
11675
- if (existsSync22(locusPath)) {
12023
+ const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12024
+ if (existsSync23(locusPath)) {
11676
12025
  const content = readFileSync14(locusPath, "utf-8");
11677
12026
  parts.push(`<project-context>
11678
12027
  ${content.slice(0, 2000)}
@@ -11734,8 +12083,8 @@ var exports_iterate = {};
11734
12083
  __export(exports_iterate, {
11735
12084
  iterateCommand: () => iterateCommand
11736
12085
  });
11737
- import { execSync as execSync17 } from "node:child_process";
11738
- function printHelp3() {
12086
+ import { execSync as execSync18 } from "node:child_process";
12087
+ function printHelp4() {
11739
12088
  process.stderr.write(`
11740
12089
  ${bold2("locus iterate")} — Re-execute tasks with PR feedback
11741
12090
 
@@ -11760,7 +12109,7 @@ ${bold2("Examples:")}
11760
12109
  }
11761
12110
  async function iterateCommand(projectRoot, args, flags = {}) {
11762
12111
  if (args[0] === "help") {
11763
- printHelp3();
12112
+ printHelp4();
11764
12113
  return;
11765
12114
  }
11766
12115
  const config = loadConfig(projectRoot);
@@ -11952,12 +12301,12 @@ ${bold2("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red2(`✗ ${
11952
12301
  }
11953
12302
  function findPRForIssue(projectRoot, issueNumber) {
11954
12303
  try {
11955
- const result = execSync17(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12304
+ const result = execSync18(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11956
12305
  const parsed = JSON.parse(result);
11957
12306
  if (parsed.length > 0) {
11958
12307
  return parsed[0].number;
11959
12308
  }
11960
- const branchResult = execSync17(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
12309
+ const branchResult = execSync18(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
11961
12310
  const branchParsed = JSON.parse(branchResult);
11962
12311
  if (branchParsed.length > 0) {
11963
12312
  return branchParsed[0].number;
@@ -11993,15 +12342,15 @@ __export(exports_discuss, {
11993
12342
  discussCommand: () => discussCommand
11994
12343
  });
11995
12344
  import {
11996
- existsSync as existsSync23,
11997
- mkdirSync as mkdirSync15,
12345
+ existsSync as existsSync24,
12346
+ mkdirSync as mkdirSync16,
11998
12347
  readdirSync as readdirSync9,
11999
12348
  readFileSync as readFileSync15,
12000
12349
  unlinkSync as unlinkSync6,
12001
- writeFileSync as writeFileSync10
12350
+ writeFileSync as writeFileSync11
12002
12351
  } from "node:fs";
12003
- import { join as join23 } from "node:path";
12004
- function printHelp4() {
12352
+ import { join as join24 } from "node:path";
12353
+ function printHelp5() {
12005
12354
  process.stderr.write(`
12006
12355
  ${bold2("locus discuss")} — AI-powered architectural discussions
12007
12356
 
@@ -12022,12 +12371,12 @@ ${bold2("Examples:")}
12022
12371
  `);
12023
12372
  }
12024
12373
  function getDiscussionsDir(projectRoot) {
12025
- return join23(projectRoot, ".locus", "discussions");
12374
+ return join24(projectRoot, ".locus", "discussions");
12026
12375
  }
12027
12376
  function ensureDiscussionsDir(projectRoot) {
12028
12377
  const dir = getDiscussionsDir(projectRoot);
12029
- if (!existsSync23(dir)) {
12030
- mkdirSync15(dir, { recursive: true });
12378
+ if (!existsSync24(dir)) {
12379
+ mkdirSync16(dir, { recursive: true });
12031
12380
  }
12032
12381
  return dir;
12033
12382
  }
@@ -12036,7 +12385,7 @@ function generateId2() {
12036
12385
  }
12037
12386
  async function discussCommand(projectRoot, args, flags = {}) {
12038
12387
  if (args[0] === "help") {
12039
- printHelp4();
12388
+ printHelp5();
12040
12389
  return;
12041
12390
  }
12042
12391
  const subcommand = args[0];
@@ -12053,7 +12402,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
12053
12402
  return deleteDiscussion(projectRoot, args[1]);
12054
12403
  }
12055
12404
  if (args.length === 0) {
12056
- printHelp4();
12405
+ printHelp5();
12057
12406
  return;
12058
12407
  }
12059
12408
  const topic = args.join(" ").trim();
@@ -12061,7 +12410,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
12061
12410
  }
12062
12411
  function listDiscussions(projectRoot) {
12063
12412
  const dir = getDiscussionsDir(projectRoot);
12064
- if (!existsSync23(dir)) {
12413
+ if (!existsSync24(dir)) {
12065
12414
  process.stderr.write(`${dim2("No discussions yet.")}
12066
12415
  `);
12067
12416
  return;
@@ -12078,7 +12427,7 @@ ${bold2("Discussions:")}
12078
12427
  `);
12079
12428
  for (const file of files) {
12080
12429
  const id = file.replace(".md", "");
12081
- const content = readFileSync15(join23(dir, file), "utf-8");
12430
+ const content = readFileSync15(join24(dir, file), "utf-8");
12082
12431
  const titleMatch = content.match(/^#\s+(.+)/m);
12083
12432
  const title = titleMatch ? titleMatch[1] : id;
12084
12433
  const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
@@ -12096,7 +12445,7 @@ function showDiscussion(projectRoot, id) {
12096
12445
  return;
12097
12446
  }
12098
12447
  const dir = getDiscussionsDir(projectRoot);
12099
- if (!existsSync23(dir)) {
12448
+ if (!existsSync24(dir)) {
12100
12449
  process.stderr.write(`${red2("✗")} No discussions found.
12101
12450
  `);
12102
12451
  return;
@@ -12108,7 +12457,7 @@ function showDiscussion(projectRoot, id) {
12108
12457
  `);
12109
12458
  return;
12110
12459
  }
12111
- const content = readFileSync15(join23(dir, match), "utf-8");
12460
+ const content = readFileSync15(join24(dir, match), "utf-8");
12112
12461
  process.stdout.write(`${content}
12113
12462
  `);
12114
12463
  }
@@ -12119,7 +12468,7 @@ function deleteDiscussion(projectRoot, id) {
12119
12468
  return;
12120
12469
  }
12121
12470
  const dir = getDiscussionsDir(projectRoot);
12122
- if (!existsSync23(dir)) {
12471
+ if (!existsSync24(dir)) {
12123
12472
  process.stderr.write(`${red2("✗")} No discussions found.
12124
12473
  `);
12125
12474
  return;
@@ -12131,7 +12480,7 @@ function deleteDiscussion(projectRoot, id) {
12131
12480
  `);
12132
12481
  return;
12133
12482
  }
12134
- unlinkSync6(join23(dir, match));
12483
+ unlinkSync6(join24(dir, match));
12135
12484
  process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
12136
12485
  `);
12137
12486
  }
@@ -12144,7 +12493,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
12144
12493
  return;
12145
12494
  }
12146
12495
  const dir = getDiscussionsDir(projectRoot);
12147
- if (!existsSync23(dir)) {
12496
+ if (!existsSync24(dir)) {
12148
12497
  process.stderr.write(`${red2("✗")} No discussions found.
12149
12498
  `);
12150
12499
  return;
@@ -12156,7 +12505,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
12156
12505
  `);
12157
12506
  return;
12158
12507
  }
12159
- const content = readFileSync15(join23(dir, match), "utf-8");
12508
+ const content = readFileSync15(join24(dir, match), "utf-8");
12160
12509
  const titleMatch = content.match(/^#\s+(.+)/m);
12161
12510
  const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
12162
12511
  await planCommand(projectRoot, [
@@ -12281,7 +12630,7 @@ ${turn.content}`;
12281
12630
  ...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
12282
12631
  ].join(`
12283
12632
  `);
12284
- writeFileSync10(join23(dir, `${id}.md`), markdown, "utf-8");
12633
+ writeFileSync11(join24(dir, `${id}.md`), markdown, "utf-8");
12285
12634
  process.stderr.write(`
12286
12635
  ${green("✓")} Discussion saved: ${cyan2(id)} ${dim2(`(${timer.formatted()})`)}
12287
12636
  `);
@@ -12296,15 +12645,15 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
12296
12645
  parts.push(`<role>
12297
12646
  You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
12298
12647
  </role>`);
12299
- const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12300
- if (existsSync23(locusPath)) {
12648
+ const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
12649
+ if (existsSync24(locusPath)) {
12301
12650
  const content = readFileSync15(locusPath, "utf-8");
12302
12651
  parts.push(`<project-context>
12303
12652
  ${content.slice(0, 3000)}
12304
12653
  </project-context>`);
12305
12654
  }
12306
- const learningsPath = join23(projectRoot, ".locus", "LEARNINGS.md");
12307
- if (existsSync23(learningsPath)) {
12655
+ const learningsPath = join24(projectRoot, ".locus", "LEARNINGS.md");
12656
+ if (existsSync24(learningsPath)) {
12308
12657
  const content = readFileSync15(learningsPath, "utf-8");
12309
12658
  parts.push(`<past-learnings>
12310
12659
  ${content.slice(0, 2000)}
@@ -12376,9 +12725,9 @@ __export(exports_artifacts, {
12376
12725
  formatDate: () => formatDate2,
12377
12726
  artifactsCommand: () => artifactsCommand
12378
12727
  });
12379
- import { existsSync as existsSync24, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
12380
- import { join as join24 } from "node:path";
12381
- function printHelp5() {
12728
+ import { existsSync as existsSync25, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
12729
+ import { join as join25 } from "node:path";
12730
+ function printHelp6() {
12382
12731
  process.stderr.write(`
12383
12732
  ${bold2("locus artifacts")} — View and manage AI-generated artifacts
12384
12733
 
@@ -12397,14 +12746,14 @@ ${dim2("Artifact names support partial matching.")}
12397
12746
  `);
12398
12747
  }
12399
12748
  function getArtifactsDir(projectRoot) {
12400
- return join24(projectRoot, ".locus", "artifacts");
12749
+ return join25(projectRoot, ".locus", "artifacts");
12401
12750
  }
12402
12751
  function listArtifacts(projectRoot) {
12403
12752
  const dir = getArtifactsDir(projectRoot);
12404
- if (!existsSync24(dir))
12753
+ if (!existsSync25(dir))
12405
12754
  return [];
12406
12755
  return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
12407
- const filePath = join24(dir, fileName);
12756
+ const filePath = join25(dir, fileName);
12408
12757
  const stat = statSync5(filePath);
12409
12758
  return {
12410
12759
  name: fileName.replace(/\.md$/, ""),
@@ -12417,8 +12766,8 @@ function listArtifacts(projectRoot) {
12417
12766
  function readArtifact(projectRoot, name) {
12418
12767
  const dir = getArtifactsDir(projectRoot);
12419
12768
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
12420
- const filePath = join24(dir, fileName);
12421
- if (!existsSync24(filePath))
12769
+ const filePath = join25(dir, fileName);
12770
+ if (!existsSync25(filePath))
12422
12771
  return null;
12423
12772
  const stat = statSync5(filePath);
12424
12773
  return {
@@ -12450,7 +12799,7 @@ function formatDate2(date) {
12450
12799
  }
12451
12800
  async function artifactsCommand(projectRoot, args) {
12452
12801
  if (args[0] === "help") {
12453
- printHelp5();
12802
+ printHelp6();
12454
12803
  return;
12455
12804
  }
12456
12805
  const subcommand = args[0];
@@ -12587,10 +12936,10 @@ __export(exports_sandbox2, {
12587
12936
  parseSandboxLogsArgs: () => parseSandboxLogsArgs,
12588
12937
  parseSandboxInstallArgs: () => parseSandboxInstallArgs
12589
12938
  });
12590
- import { execSync as execSync18, spawn as spawn7 } from "node:child_process";
12939
+ import { execSync as execSync19, spawn as spawn7 } from "node:child_process";
12591
12940
  import { createHash } from "node:crypto";
12592
- import { existsSync as existsSync25, readFileSync as readFileSync17 } from "node:fs";
12593
- import { basename as basename4, join as join25 } from "node:path";
12941
+ import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
12942
+ import { basename as basename4, join as join26 } from "node:path";
12594
12943
  import { createInterface as createInterface3 } from "node:readline";
12595
12944
  function printSandboxHelp() {
12596
12945
  process.stderr.write(`
@@ -12799,7 +13148,7 @@ function handleRemove(projectRoot) {
12799
13148
  process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
12800
13149
  `);
12801
13150
  try {
12802
- execSync18(`docker sandbox rm ${sandboxName}`, {
13151
+ execSync19(`docker sandbox rm ${sandboxName}`, {
12803
13152
  encoding: "utf-8",
12804
13153
  stdio: ["pipe", "pipe", "pipe"],
12805
13154
  timeout: 15000
@@ -13048,7 +13397,7 @@ async function handleLogs(projectRoot, args) {
13048
13397
  }
13049
13398
  function detectPackageManager2(projectRoot) {
13050
13399
  try {
13051
- const raw = readFileSync17(join25(projectRoot, "package.json"), "utf-8");
13400
+ const raw = readFileSync17(join26(projectRoot, "package.json"), "utf-8");
13052
13401
  const pkgJson = JSON.parse(raw);
13053
13402
  if (typeof pkgJson.packageManager === "string") {
13054
13403
  const name = pkgJson.packageManager.split("@")[0];
@@ -13057,13 +13406,13 @@ function detectPackageManager2(projectRoot) {
13057
13406
  }
13058
13407
  }
13059
13408
  } catch {}
13060
- if (existsSync25(join25(projectRoot, "bun.lock")) || existsSync25(join25(projectRoot, "bun.lockb"))) {
13409
+ if (existsSync26(join26(projectRoot, "bun.lock")) || existsSync26(join26(projectRoot, "bun.lockb"))) {
13061
13410
  return "bun";
13062
13411
  }
13063
- if (existsSync25(join25(projectRoot, "yarn.lock"))) {
13412
+ if (existsSync26(join26(projectRoot, "yarn.lock"))) {
13064
13413
  return "yarn";
13065
13414
  }
13066
- if (existsSync25(join25(projectRoot, "pnpm-lock.yaml"))) {
13415
+ if (existsSync26(join26(projectRoot, "pnpm-lock.yaml"))) {
13067
13416
  return "pnpm";
13068
13417
  }
13069
13418
  return "npm";
@@ -13112,8 +13461,8 @@ Installing dependencies (${bold2(installCmd.join(" "))}) in sandbox ${dim2(sandb
13112
13461
  ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
13113
13462
  `);
13114
13463
  }
13115
- const setupScript = join25(projectRoot, ".locus", "sandbox-setup.sh");
13116
- if (existsSync25(setupScript)) {
13464
+ const setupScript = join26(projectRoot, ".locus", "sandbox-setup.sh");
13465
+ if (existsSync26(setupScript)) {
13117
13466
  process.stderr.write(`Running ${bold2(".locus/sandbox-setup.sh")} in sandbox ${dim2(sandboxName)}...
13118
13467
  `);
13119
13468
  const hookOk = await runInteractiveCommand("docker", [
@@ -13201,7 +13550,7 @@ function runInteractiveCommand(command, args) {
13201
13550
  }
13202
13551
  async function createProviderSandbox(provider, sandboxName, projectRoot) {
13203
13552
  try {
13204
- execSync18(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
13553
+ execSync19(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
13205
13554
  stdio: ["pipe", "pipe", "pipe"],
13206
13555
  timeout: 120000
13207
13556
  });
@@ -13222,7 +13571,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
13222
13571
  }
13223
13572
  async function ensurePackageManagerInSandbox(sandboxName, pm) {
13224
13573
  try {
13225
- execSync18(`docker sandbox exec ${sandboxName} which ${pm}`, {
13574
+ execSync19(`docker sandbox exec ${sandboxName} which ${pm}`, {
13226
13575
  stdio: ["pipe", "pipe", "pipe"],
13227
13576
  timeout: 5000
13228
13577
  });
@@ -13231,7 +13580,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13231
13580
  process.stderr.write(`Installing ${bold2(pm)} in sandbox...
13232
13581
  `);
13233
13582
  try {
13234
- execSync18(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
13583
+ execSync19(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
13235
13584
  stdio: "inherit",
13236
13585
  timeout: 120000
13237
13586
  });
@@ -13243,7 +13592,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
13243
13592
  }
13244
13593
  async function ensureCodexInSandbox(sandboxName) {
13245
13594
  try {
13246
- execSync18(`docker sandbox exec ${sandboxName} which codex`, {
13595
+ execSync19(`docker sandbox exec ${sandboxName} which codex`, {
13247
13596
  stdio: ["pipe", "pipe", "pipe"],
13248
13597
  timeout: 5000
13249
13598
  });
@@ -13251,7 +13600,7 @@ async function ensureCodexInSandbox(sandboxName) {
13251
13600
  process.stderr.write(`Installing codex in sandbox...
13252
13601
  `);
13253
13602
  try {
13254
- execSync18(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
13603
+ execSync19(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
13255
13604
  } catch {
13256
13605
  process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
13257
13606
  `);
@@ -13260,7 +13609,7 @@ async function ensureCodexInSandbox(sandboxName) {
13260
13609
  }
13261
13610
  function isSandboxAlive(name) {
13262
13611
  try {
13263
- const output = execSync18("docker sandbox ls", {
13612
+ const output = execSync19("docker sandbox ls", {
13264
13613
  encoding: "utf-8",
13265
13614
  stdio: ["pipe", "pipe", "pipe"],
13266
13615
  timeout: 5000
@@ -13286,13 +13635,13 @@ init_context();
13286
13635
  init_logger();
13287
13636
  init_rate_limiter();
13288
13637
  init_terminal();
13289
- import { existsSync as existsSync26, readFileSync as readFileSync18 } from "node:fs";
13290
- import { join as join26 } from "node:path";
13638
+ import { existsSync as existsSync27, readFileSync as readFileSync18 } from "node:fs";
13639
+ import { join as join27 } from "node:path";
13291
13640
  import { fileURLToPath } from "node:url";
13292
13641
  function getCliVersion() {
13293
13642
  const fallbackVersion = "0.0.0";
13294
- const packageJsonPath = join26(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
13295
- if (!existsSync26(packageJsonPath)) {
13643
+ const packageJsonPath = join27(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
13644
+ if (!existsSync27(packageJsonPath)) {
13296
13645
  return fallbackVersion;
13297
13646
  }
13298
13647
  try {
@@ -13442,7 +13791,7 @@ function printLogo() {
13442
13791
  `);
13443
13792
  }
13444
13793
  }
13445
- function printHelp6() {
13794
+ function printHelp7() {
13446
13795
  printLogo();
13447
13796
  process.stderr.write(`
13448
13797
 
@@ -13463,6 +13812,7 @@ ${bold2("Commands:")}
13463
13812
  ${cyan2("status")} Dashboard view of current state
13464
13813
  ${cyan2("config")} View and manage settings
13465
13814
  ${cyan2("logs")} View, tail, and manage execution logs
13815
+ ${cyan2("create")} ${dim2("<name>")} Scaffold a new community package
13466
13816
  ${cyan2("install")} Install a community package
13467
13817
  ${cyan2("uninstall")} Remove an installed package
13468
13818
  ${cyan2("packages")} Manage installed packages (list, outdated)
@@ -13546,7 +13896,7 @@ async function main() {
13546
13896
  process.exit(0);
13547
13897
  }
13548
13898
  if (parsed.flags.help && !parsed.command) {
13549
- printHelp6();
13899
+ printHelp7();
13550
13900
  process.exit(0);
13551
13901
  }
13552
13902
  const command = resolveAlias(parsed.command);
@@ -13557,7 +13907,7 @@ async function main() {
13557
13907
  try {
13558
13908
  const root = getGitRoot(cwd);
13559
13909
  if (isInitialized(root)) {
13560
- logDir = join26(root, ".locus", "logs");
13910
+ logDir = join27(root, ".locus", "logs");
13561
13911
  getRateLimiter(root);
13562
13912
  }
13563
13913
  } catch {}
@@ -13578,7 +13928,7 @@ async function main() {
13578
13928
  printVersionNotice = startVersionCheck2(VERSION);
13579
13929
  }
13580
13930
  if (!command) {
13581
- printHelp6();
13931
+ printHelp7();
13582
13932
  process.exit(0);
13583
13933
  }
13584
13934
  if (command === "init") {
@@ -13587,6 +13937,13 @@ async function main() {
13587
13937
  logger.destroy();
13588
13938
  return;
13589
13939
  }
13940
+ if (command === "create") {
13941
+ const { createCommand: createCommand2 } = await Promise.resolve().then(() => (init_create(), exports_create));
13942
+ const createArgs = parsed.flags.help ? ["help"] : parsed.args;
13943
+ await createCommand2(createArgs);
13944
+ logger.destroy();
13945
+ return;
13946
+ }
13590
13947
  if (command === "install") {
13591
13948
  if (parsed.flags.list) {
13592
13949
  const { packagesCommand: packagesCommand2 } = await Promise.resolve().then(() => (init_packages(), exports_packages));