@locusai/cli 0.24.6 → 0.24.9

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 +381 -271
  2. package/package.json +2 -2
package/bin/locus.js CHANGED
@@ -39,8 +39,7 @@ var init_dist = __esm(() => {
39
39
  rebaseBeforeTask: true
40
40
  },
41
41
  sprint: {
42
- active: null,
43
- stopOnFailure: true
42
+ stopOnFailure: false
44
43
  },
45
44
  logging: {
46
45
  level: "normal",
@@ -3278,7 +3277,6 @@ ${bold2("Locus Configuration")}
3278
3277
  {
3279
3278
  title: "Sprint",
3280
3279
  entries: [
3281
- ["Active", config.sprint.active ?? dim2("(none)")],
3282
3280
  ["Stop on Failure", String(config.sprint.stopOnFailure)]
3283
3281
  ]
3284
3282
  },
@@ -7013,9 +7011,6 @@ async function sprintCommand(projectRoot, args) {
7013
7011
  case "show":
7014
7012
  await sprintShow(projectRoot, parsed);
7015
7013
  break;
7016
- case "active":
7017
- await sprintActive(projectRoot, parsed);
7018
- break;
7019
7014
  case "order":
7020
7015
  await sprintOrder(projectRoot, parsed);
7021
7016
  break;
@@ -7056,8 +7051,6 @@ async function sprintCreate(projectRoot, parsed) {
7056
7051
  process.stderr.write(` Due: ${parsed.flags.due}
7057
7052
  `);
7058
7053
  }
7059
- process.stderr.write(` Set as active sprint: ${bold2(`locus sprint active "${title}"`)}
7060
- `);
7061
7054
  } catch (e) {
7062
7055
  process.stderr.write(`\r${red2("✗")} Failed to create sprint: ${e.message}
7063
7056
  `);
@@ -7067,8 +7060,6 @@ async function sprintCreate(projectRoot, parsed) {
7067
7060
  async function sprintList(projectRoot, parsed) {
7068
7061
  const config = loadConfig(projectRoot);
7069
7062
  const { owner, repo } = config.github;
7070
- const activeSprint = config.sprint.active;
7071
- const normalizedActiveSprint = activeSprint ? normalizeMilestoneTitle(activeSprint) : null;
7072
7063
  const state = parsed.flags.all ? "all" : "open";
7073
7064
  process.stderr.write(`${cyan2("●")} Fetching sprints...`);
7074
7065
  let milestones;
@@ -7094,16 +7085,10 @@ async function sprintList(projectRoot, parsed) {
7094
7085
 
7095
7086
  `);
7096
7087
  const columns = [
7097
- {
7098
- key: "active",
7099
- header: " ",
7100
- minWidth: 2,
7101
- format: (_, row) => normalizedActiveSprint !== null && normalizeMilestoneTitle(String(row.title)) === normalizedActiveSprint ? green("●") : " "
7102
- },
7103
7088
  {
7104
7089
  key: "title",
7105
7090
  header: "Sprint",
7106
- format: (v, row) => normalizedActiveSprint !== null && normalizeMilestoneTitle(String(row.title)) === normalizedActiveSprint ? bold2(green(String(v))) : bold2(String(v))
7091
+ format: (v) => bold2(String(v))
7107
7092
  },
7108
7093
  {
7109
7094
  key: "progress",
@@ -7148,7 +7133,6 @@ async function sprintList(projectRoot, parsed) {
7148
7133
  }
7149
7134
  ];
7150
7135
  const rows = milestones.map((m) => ({
7151
- active: null,
7152
7136
  title: m.title,
7153
7137
  openIssues: m.openIssues,
7154
7138
  closedIssues: m.closedIssues,
@@ -7159,21 +7143,15 @@ async function sprintList(projectRoot, parsed) {
7159
7143
  process.stderr.write(`${renderTable(columns, rows)}
7160
7144
 
7161
7145
  `);
7162
- if (activeSprint) {
7163
- process.stderr.write(` ${green("●")} = active sprint
7164
- `);
7165
- }
7166
7146
  }
7167
7147
  async function sprintShow(projectRoot, parsed) {
7168
7148
  const config = loadConfig(projectRoot);
7169
7149
  const { owner, repo } = config.github;
7170
- const sprintName = parsed.positional[0] ?? config.sprint.active;
7150
+ const sprintName = parsed.positional[0];
7171
7151
  if (!sprintName) {
7172
- process.stderr.write(`${red2("✗")} No sprint specified and no active sprint set.
7152
+ process.stderr.write(`${red2("✗")} Missing sprint name.
7173
7153
  `);
7174
7154
  process.stderr.write(` Usage: ${bold2('locus sprint show "Sprint 1"')}
7175
- `);
7176
- process.stderr.write(` Or set active: ${bold2('locus sprint active "Sprint 1"')}
7177
7155
  `);
7178
7156
  process.exit(1);
7179
7157
  }
@@ -7226,10 +7204,6 @@ ${bold2(`Sprint: ${milestone.title}`)} ${dim2(`(${milestone.state})`)}
7226
7204
  {
7227
7205
  label: "State",
7228
7206
  value: milestone.state === "open" ? green("open") : dim2("closed")
7229
- },
7230
- {
7231
- label: "Active",
7232
- value: config.sprint.active && normalizeMilestoneTitle(config.sprint.active) === normalizeMilestoneTitle(milestone.title) ? green("yes") : dim2("no")
7233
7207
  }
7234
7208
  ]);
7235
7209
  process.stderr.write(`${details}
@@ -7298,49 +7272,12 @@ ${bold2("Tasks")} ${dim2(`(by execution order)`)}
7298
7272
  process.stderr.write(`
7299
7273
  `);
7300
7274
  }
7301
- async function sprintActive(projectRoot, parsed) {
7275
+ async function sprintOrder(projectRoot, parsed) {
7302
7276
  const sprintName = parsed.positional[0];
7303
7277
  if (!sprintName) {
7304
- const config2 = loadConfig(projectRoot);
7305
- if (config2.sprint.active) {
7306
- process.stderr.write(`Active sprint: ${bold2(green(config2.sprint.active))}
7307
- `);
7308
- } else {
7309
- process.stderr.write(`${dim2("No active sprint set.")}
7310
- `);
7311
- process.stderr.write(` Set one: ${bold2('locus sprint active "Sprint 1"')}
7312
- `);
7313
- }
7314
- return;
7315
- }
7316
- const config = loadConfig(projectRoot);
7317
- const { owner, repo } = config.github;
7318
- process.stderr.write(`${cyan2("●")} Verifying sprint...`);
7319
- let milestones;
7320
- try {
7321
- milestones = listMilestones(owner, repo, "open", { cwd: projectRoot });
7322
- } catch (e) {
7323
- process.stderr.write(`\r${red2("✗")} ${e.message}
7324
- `);
7325
- process.exit(1);
7326
- return;
7327
- }
7328
- const found = findMilestoneByTitle(milestones, sprintName);
7329
- if (!found) {
7330
- process.stderr.write(`\r${red2("✗")} Sprint "${sprintName}" not found (or is closed).
7331
- `);
7332
- process.exit(1);
7333
- return;
7334
- }
7335
- updateConfigValue(projectRoot, "sprint.active", found.title);
7336
- process.stderr.write(`\r${green("✓")} Active sprint set to "${bold2(found.title)}"
7278
+ process.stderr.write(`${red2("✗")} Missing sprint name.
7337
7279
  `);
7338
- }
7339
- async function sprintOrder(projectRoot, parsed) {
7340
- const config = loadConfig(projectRoot);
7341
- const sprintName = parsed.positional[0] ?? config.sprint.active;
7342
- if (!sprintName) {
7343
- process.stderr.write(`${red2("✗")} No sprint specified and no active sprint set.
7280
+ process.stderr.write(` Usage: ${bold2('locus sprint order "Sprint 1" 17 15 16')}
7344
7281
  `);
7345
7282
  process.exit(1);
7346
7283
  }
@@ -7475,15 +7412,15 @@ ${green("✓")} Sprint order updated.
7475
7412
  `);
7476
7413
  }
7477
7414
  async function sprintClose(projectRoot, parsed) {
7478
- const config = loadConfig(projectRoot);
7479
- const sprintName = parsed.positional[0] ?? config.sprint.active;
7415
+ const sprintName = parsed.positional[0];
7480
7416
  if (!sprintName) {
7481
- process.stderr.write(`${red2("✗")} No sprint specified.
7417
+ process.stderr.write(`${red2("✗")} Missing sprint name.
7482
7418
  `);
7483
7419
  process.stderr.write(` Usage: ${bold2('locus sprint close "Sprint 1"')}
7484
7420
  `);
7485
7421
  process.exit(1);
7486
7422
  }
7423
+ const config = loadConfig(projectRoot);
7487
7424
  const { owner, repo } = config.github;
7488
7425
  process.stderr.write(`${cyan2("●")} Closing sprint "${sprintName}"...`);
7489
7426
  let milestones;
@@ -7512,11 +7449,6 @@ async function sprintClose(projectRoot, parsed) {
7512
7449
  }
7513
7450
  process.stderr.write(`\r${green("✓")} Closed sprint "${bold2(milestone.title)}"
7514
7451
  `);
7515
- if (config.sprint.active && normalizeMilestoneTitle(config.sprint.active) === normalizeMilestoneTitle(milestone.title)) {
7516
- updateConfigValue(projectRoot, "sprint.active", null);
7517
- process.stderr.write(` ${dim2(`Cleared active sprint (was ${milestone.title})`)}
7518
- `);
7519
- }
7520
7452
  }
7521
7453
  function getOrder(labels) {
7522
7454
  for (const label of labels) {
@@ -7567,7 +7499,6 @@ ${bold2("Subcommands:")}
7567
7499
  ${cyan2("create")} ${dim2("(c)")} Create a new sprint
7568
7500
  ${cyan2("list")} ${dim2("(ls)")} List sprints (default)
7569
7501
  ${cyan2("show")} Show sprint details and task order
7570
- ${cyan2("active")} Set or show the active sprint
7571
7502
  ${cyan2("order")} Reorder sprint tasks
7572
7503
  ${cyan2("close")} Close a sprint
7573
7504
 
@@ -7585,7 +7516,6 @@ ${bold2("Examples:")}
7585
7516
  locus sprint create "Sprint 1" --due 2026-03-07
7586
7517
  locus sprint list
7587
7518
  locus sprint show "Sprint 1"
7588
- locus sprint active "Sprint 1"
7589
7519
  locus sprint order "Sprint 1" 17 15 16
7590
7520
  locus sprint order "Sprint 1" --show
7591
7521
  locus sprint close "Sprint 1"
@@ -10425,6 +10355,21 @@ var init_shutdown = __esm(() => {
10425
10355
  });
10426
10356
 
10427
10357
  // src/core/worktree.ts
10358
+ var exports_worktree = {};
10359
+ __export(exports_worktree, {
10360
+ sprintSlug: () => sprintSlug2,
10361
+ removeWorktree: () => removeWorktree,
10362
+ removeSprintWorktree: () => removeSprintWorktree,
10363
+ pushWorktreeBranch: () => pushWorktreeBranch,
10364
+ listWorktrees: () => listWorktrees,
10365
+ hasWorktreeChanges: () => hasWorktreeChanges,
10366
+ getWorktreePath: () => getWorktreePath,
10367
+ getWorktreeAge: () => getWorktreeAge,
10368
+ getSprintWorktreePath: () => getSprintWorktreePath,
10369
+ createWorktree: () => createWorktree,
10370
+ createSprintWorktree: () => createSprintWorktree,
10371
+ cleanupStaleWorktrees: () => cleanupStaleWorktrees
10372
+ });
10428
10373
  import { execSync as execSync16 } from "node:child_process";
10429
10374
  import { existsSync as existsSync21, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
10430
10375
  import { join as join21 } from "node:path";
@@ -10448,10 +10393,56 @@ function getWorktreeDir(projectRoot) {
10448
10393
  function getWorktreePath(projectRoot, issueNumber) {
10449
10394
  return join21(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
10450
10395
  }
10396
+ function getSprintWorktreePath(projectRoot, sprintSlug2) {
10397
+ return join21(getWorktreeDir(projectRoot), `sprint-${sprintSlug2}`);
10398
+ }
10451
10399
  function generateBranchName(issueNumber) {
10452
10400
  const randomSuffix = Math.random().toString(36).slice(2, 8);
10453
10401
  return `locus/issue-${issueNumber}-${randomSuffix}`;
10454
10402
  }
10403
+ function sprintSlug2(name) {
10404
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
10405
+ }
10406
+ function createSprintWorktree(projectRoot, sprintName, baseBranch) {
10407
+ const log = getLogger();
10408
+ const slug = sprintSlug2(sprintName);
10409
+ const worktreePath = getSprintWorktreePath(projectRoot, slug);
10410
+ if (existsSync21(worktreePath)) {
10411
+ log.verbose(`Sprint worktree already exists for "${sprintName}"`);
10412
+ const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/sprint-${slug}`;
10413
+ return { path: worktreePath, branch: existingBranch };
10414
+ }
10415
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
10416
+ const branch = `locus/sprint-${slug}-${randomSuffix}`;
10417
+ git4(`worktree add ${JSON.stringify(worktreePath)} -b ${branch} ${baseBranch}`, projectRoot);
10418
+ initSubmodules(worktreePath);
10419
+ log.info(`Created sprint worktree for "${sprintName}"`, {
10420
+ path: worktreePath,
10421
+ branch,
10422
+ baseBranch
10423
+ });
10424
+ return { path: worktreePath, branch };
10425
+ }
10426
+ function removeSprintWorktree(projectRoot, sprintName) {
10427
+ const log = getLogger();
10428
+ const slug = sprintSlug2(sprintName);
10429
+ const worktreePath = getSprintWorktreePath(projectRoot, slug);
10430
+ if (!existsSync21(worktreePath)) {
10431
+ log.verbose(`Sprint worktree for "${sprintName}" does not exist`);
10432
+ return;
10433
+ }
10434
+ const branch = getWorktreeBranch(worktreePath);
10435
+ try {
10436
+ git4(`worktree remove ${JSON.stringify(worktreePath)} --force`, projectRoot);
10437
+ log.info(`Removed sprint worktree for "${sprintName}"`);
10438
+ } catch (e) {
10439
+ log.warn(`Failed to remove sprint worktree: ${e}`);
10440
+ gitSafe3(`worktree remove ${JSON.stringify(worktreePath)} --force`, projectRoot);
10441
+ }
10442
+ if (branch) {
10443
+ gitSafe3(`branch -D ${branch}`, projectRoot);
10444
+ }
10445
+ }
10455
10446
  function getWorktreeBranch(worktreePath) {
10456
10447
  try {
10457
10448
  return execSync16("git branch --show-current", {
@@ -10571,6 +10562,36 @@ function cleanupStaleWorktrees(projectRoot) {
10571
10562
  }
10572
10563
  return cleaned;
10573
10564
  }
10565
+ function pushWorktreeBranch(projectRoot, issueNumber) {
10566
+ const worktreePath = getWorktreePath(projectRoot, issueNumber);
10567
+ if (!existsSync21(worktreePath)) {
10568
+ throw new Error(`Worktree for issue #${issueNumber} does not exist`);
10569
+ }
10570
+ const branch = getWorktreeBranch(worktreePath);
10571
+ if (!branch) {
10572
+ throw new Error(`Could not determine branch for worktree #${issueNumber}`);
10573
+ }
10574
+ git4(`push -u origin ${branch}`, worktreePath);
10575
+ return branch;
10576
+ }
10577
+ function hasWorktreeChanges(projectRoot, issueNumber) {
10578
+ const worktreePath = getWorktreePath(projectRoot, issueNumber);
10579
+ if (!existsSync21(worktreePath))
10580
+ return false;
10581
+ const status = gitSafe3("status --porcelain", worktreePath);
10582
+ return status !== null && status.trim().length > 0;
10583
+ }
10584
+ function getWorktreeAge(projectRoot, issueNumber) {
10585
+ const worktreePath = getWorktreePath(projectRoot, issueNumber);
10586
+ if (!existsSync21(worktreePath))
10587
+ return 0;
10588
+ try {
10589
+ const stat = statSync4(worktreePath);
10590
+ return Date.now() - stat.ctimeMs;
10591
+ } catch {
10592
+ return 0;
10593
+ }
10594
+ }
10574
10595
  var init_worktree = __esm(() => {
10575
10596
  init_logger();
10576
10597
  init_submodule();
@@ -10582,6 +10603,8 @@ __export(exports_run, {
10582
10603
  runCommand: () => runCommand
10583
10604
  });
10584
10605
  import { execSync as execSync17 } from "node:child_process";
10606
+ import { existsSync as existsSync22 } from "node:fs";
10607
+ import { join as join22 } from "node:path";
10585
10608
  function resolveExecutionContext(config, modelOverride) {
10586
10609
  const model = modelOverride ?? config.ai.model;
10587
10610
  const provider = inferProviderFromModel(model) ?? config.ai.provider;
@@ -10598,12 +10621,14 @@ function printRunHelp() {
10598
10621
  ${bold2("locus run")} — Execute issues using AI agents
10599
10622
 
10600
10623
  ${bold2("Usage:")}
10601
- locus run ${dim2("# Run active sprint (sequential)")}
10624
+ locus run ${dim2("# Run all open sprints (parallel)")}
10625
+ locus run --sprint <name> ${dim2("# Run a specific sprint")}
10602
10626
  locus run <issue> ${dim2("# Run single issue (worktree)")}
10603
10627
  locus run <issue> <issue> ... ${dim2("# Run multiple issues (parallel)")}
10604
10628
  locus run --resume ${dim2("# Resume interrupted run")}
10605
10629
 
10606
10630
  ${bold2("Options:")}
10631
+ --sprint <name> Run a specific sprint (instead of all active)
10607
10632
  --resume Resume a previously interrupted run
10608
10633
  --dry-run Show what would happen without executing
10609
10634
  --model <name> Override the AI model for this run
@@ -10616,7 +10641,8 @@ ${bold2("Sandbox:")}
10616
10641
  unsandboxed with a warning.
10617
10642
 
10618
10643
  ${bold2("Examples:")}
10619
- locus run ${dim2("# Execute active sprint")}
10644
+ locus run ${dim2("# Execute all open sprints")}
10645
+ locus run --sprint "Sprint 1" ${dim2("# Run a specific sprint")}
10620
10646
  locus run 42 ${dim2("# Run single issue")}
10621
10647
  locus run 42 43 44 ${dim2("# Run issues in parallel")}
10622
10648
  locus run --resume ${dim2("# Resume after failure")}
@@ -10669,11 +10695,11 @@ async function runCommand(projectRoot, args, flags = {}) {
10669
10695
  }
10670
10696
  }
10671
10697
  if (flags.resume) {
10672
- return handleResume(projectRoot, config, sandboxed, runRef);
10698
+ return handleResume(projectRoot, config, sandboxed, flags);
10673
10699
  }
10674
10700
  const issueNumbers = args.filter((a) => /^\d+$/.test(a)).map(Number);
10675
10701
  if (issueNumbers.length === 0) {
10676
- return handleSprintRun(projectRoot, config, flags, sandboxed, runRef);
10702
+ return handleSprintRun(projectRoot, config, flags, sandboxed);
10677
10703
  }
10678
10704
  if (issueNumbers.length === 1) {
10679
10705
  return handleSingleIssue(projectRoot, config, issueNumbers[0], flags, sandboxed);
@@ -10683,18 +10709,56 @@ async function runCommand(projectRoot, args, flags = {}) {
10683
10709
  cleanupShutdown();
10684
10710
  }
10685
10711
  }
10686
- async function handleSprintRun(projectRoot, config, flags, sandboxed, runRef) {
10687
- const log = getLogger();
10688
- const execution = resolveExecutionContext(config, flags.model);
10689
- if (!config.sprint.active) {
10690
- process.stderr.write(`${red2("✗")} No active sprint. Set one with: ${bold2("locus sprint active <name>")}
10712
+ async function handleSprintRun(projectRoot, config, flags, sandboxed) {
10713
+ let sprintNames;
10714
+ if (flags.sprint) {
10715
+ sprintNames = [flags.sprint];
10716
+ } else {
10717
+ process.stderr.write(`${cyan2("●")} Detecting open sprints...`);
10718
+ try {
10719
+ const milestones = listMilestones(config.github.owner, config.github.repo, "open", { cwd: projectRoot });
10720
+ sprintNames = milestones.map((m) => m.title);
10721
+ } catch (e) {
10722
+ process.stderr.write(`\r${red2("✗")} Failed to fetch sprints: ${e.message}
10723
+ `);
10724
+ return;
10725
+ }
10726
+ process.stderr.write("\r\x1B[2K");
10727
+ }
10728
+ if (sprintNames.length === 0) {
10729
+ process.stderr.write(`${red2("✗")} No open sprints found.
10730
+ `);
10731
+ process.stderr.write(` Create one: ${bold2('locus sprint create "Sprint 1"')}
10691
10732
  `);
10692
10733
  process.stderr.write(` Or specify issue numbers: ${bold2("locus run 42 43 44")}
10693
10734
  `);
10694
10735
  return;
10695
10736
  }
10696
- const sprintName = config.sprint.active;
10697
- runRef.sprintName = sprintName;
10737
+ const cleaned = cleanupStaleWorktrees(projectRoot);
10738
+ if (cleaned > 0) {
10739
+ process.stderr.write(` ${dim2(`Cleaned up ${cleaned} stale worktree${cleaned === 1 ? "" : "s"}.`)}
10740
+ `);
10741
+ }
10742
+ if (sprintNames.length === 1) {
10743
+ await executeSingleSprint(projectRoot, config, sprintNames[0], flags, sandboxed);
10744
+ } else {
10745
+ process.stderr.write(`
10746
+ ${bold2("Running")} ${cyan2(`${sprintNames.length} sprints`)} ${dim2("(parallel, worktrees)")}
10747
+
10748
+ `);
10749
+ const timer = createTimer();
10750
+ const promises = sprintNames.map((name) => executeSingleSprint(projectRoot, config, name, flags, sandboxed).catch((e) => {
10751
+ getLogger().warn(`Sprint "${name}" threw: ${e}`);
10752
+ }));
10753
+ await Promise.allSettled(promises);
10754
+ process.stderr.write(`
10755
+ ${bold2("All sprints complete")} ${dim2(`(${timer.formatted()})`)}
10756
+
10757
+ `);
10758
+ }
10759
+ }
10760
+ async function executeSingleSprint(projectRoot, config, sprintName, flags, sandboxed) {
10761
+ const execution = resolveExecutionContext(config, flags.model);
10698
10762
  process.stderr.write(`
10699
10763
  ${bold2("Sprint:")} ${cyan2(sprintName)}
10700
10764
  `);
@@ -10703,7 +10767,7 @@ ${bold2("Sprint:")} ${cyan2(sprintName)}
10703
10767
  const stats2 = getRunStats(existingState);
10704
10768
  if (stats2.inProgress > 0 || stats2.pending > 0) {
10705
10769
  process.stderr.write(`
10706
- ${yellow2("⚠")} A run for this sprint is already in progress.
10770
+ ${yellow2("⚠")} A run for sprint "${sprintName}" is already in progress.
10707
10771
  `);
10708
10772
  process.stderr.write(` Use ${bold2("locus run --resume")} to continue.
10709
10773
  `);
@@ -10724,14 +10788,33 @@ ${yellow2("⚠")} A run for this sprint is already in progress.
10724
10788
  return;
10725
10789
  }
10726
10790
  issues = sortByOrder2(issues);
10727
- const randomSuffix = Math.random().toString(36).slice(2, 8);
10728
- const branchName = `locus/sprint-${sprintName.toLowerCase().replace(/\s+/g, "-")}-${randomSuffix}`;
10791
+ let worktreePath;
10792
+ let branchName;
10793
+ if (!flags.dryRun) {
10794
+ try {
10795
+ const wt = createSprintWorktree(projectRoot, sprintName, config.agent.baseBranch);
10796
+ worktreePath = wt.path;
10797
+ branchName = wt.branch;
10798
+ process.stderr.write(` ${dim2(`Worktree: ${wt.path}`)}
10799
+ `);
10800
+ process.stderr.write(` ${dim2(`Branch: ${wt.branch}`)}
10801
+ `);
10802
+ } catch (e) {
10803
+ process.stderr.write(`${red2("✗")} Failed to create worktree: ${e}
10804
+ `);
10805
+ return;
10806
+ }
10807
+ } else {
10808
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
10809
+ branchName = `locus/sprint-${sprintName.toLowerCase().replace(/\s+/g, "-")}-${randomSuffix}`;
10810
+ }
10811
+ const workDir = worktreePath ?? projectRoot;
10729
10812
  const state = createSprintRunState(sprintName, branchName, issues.map((issue, i) => ({
10730
10813
  number: issue.number,
10731
10814
  order: getOrder2(issue) ?? i + 1
10732
10815
  })));
10733
10816
  saveRunState(projectRoot, state);
10734
- process.stderr.write(` ${dim2(`${issues.length} tasks, branch: ${branchName}`)}
10817
+ process.stderr.write(` ${dim2(`${issues.length} tasks`)}
10735
10818
 
10736
10819
  `);
10737
10820
  for (const task of state.tasks) {
@@ -10746,20 +10829,6 @@ ${yellow2("⚠")} A run for this sprint is already in progress.
10746
10829
 
10747
10830
  `);
10748
10831
  }
10749
- if (!flags.dryRun) {
10750
- try {
10751
- execSync17(`git checkout -B ${branchName}`, {
10752
- cwd: projectRoot,
10753
- encoding: "utf-8",
10754
- stdio: ["pipe", "pipe", "pipe"]
10755
- });
10756
- log.info(`Checked out branch ${branchName}`);
10757
- } catch (e) {
10758
- process.stderr.write(`${red2("✗")} Failed to create branch: ${e}
10759
- `);
10760
- return;
10761
- }
10762
- }
10763
10832
  const timer = createTimer();
10764
10833
  for (let i = 0;i < state.tasks.length; i++) {
10765
10834
  const task = state.tasks[i];
@@ -10771,27 +10840,37 @@ ${yellow2("⚠")} A run for this sprint is already in progress.
10771
10840
  }
10772
10841
  await getRateLimiter().checkBeforeRequest();
10773
10842
  if (config.agent.rebaseBeforeTask && !flags.dryRun && i > 0) {
10774
- const conflictResult = checkForConflicts(projectRoot, config.agent.baseBranch);
10843
+ const conflictResult = checkForConflicts(workDir, config.agent.baseBranch);
10775
10844
  if (conflictResult.baseAdvanced) {
10776
10845
  printConflictReport(conflictResult, config.agent.baseBranch);
10777
10846
  if (conflictResult.hasConflict) {
10778
10847
  markTaskFailed(state, task.issue, "Merge conflict with base branch");
10779
10848
  saveRunState(projectRoot, state);
10780
- process.stderr.write(`
10849
+ process.stderr.write(` ${red2("✗")} Task #${task.issue} skipped due to conflicts.
10850
+ `);
10851
+ if (config.sprint.stopOnFailure) {
10852
+ process.stderr.write(`
10781
10853
  ${red2("✗")} Sprint stopped due to conflicts.
10782
10854
  `);
10783
- process.stderr.write(` Resolve conflicts and run: ${bold2("locus run --resume")}
10855
+ process.stderr.write(` Resolve conflicts and run: ${bold2("locus run --resume")}
10784
10856
  `);
10785
- return;
10857
+ break;
10858
+ }
10859
+ continue;
10786
10860
  }
10787
- const rebaseResult = attemptRebase(projectRoot, config.agent.baseBranch);
10861
+ const rebaseResult = attemptRebase(workDir, config.agent.baseBranch);
10788
10862
  if (!rebaseResult.success) {
10789
10863
  markTaskFailed(state, task.issue, "Rebase failed");
10790
10864
  saveRunState(projectRoot, state);
10791
- process.stderr.write(`
10792
- ${red2("✗")} Auto-rebase failed. Resolve manually.
10865
+ process.stderr.write(` ${red2("✗")} Auto-rebase failed for task #${task.issue}.
10793
10866
  `);
10794
- return;
10867
+ if (config.sprint.stopOnFailure) {
10868
+ process.stderr.write(`
10869
+ ${red2("✗")} Sprint stopped. Resolve manually.
10870
+ `);
10871
+ break;
10872
+ }
10873
+ continue;
10795
10874
  }
10796
10875
  }
10797
10876
  }
@@ -10799,19 +10878,19 @@ ${red2("✗")} Auto-rebase failed. Resolve manually.
10799
10878
  if (i > 0 && !flags.dryRun) {
10800
10879
  try {
10801
10880
  sprintContext = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD`, {
10802
- cwd: projectRoot,
10881
+ cwd: workDir,
10803
10882
  encoding: "utf-8",
10804
10883
  stdio: ["pipe", "pipe", "pipe"]
10805
10884
  }).trim();
10806
10885
  } catch {}
10807
10886
  }
10808
10887
  process.stderr.write(`
10809
- ${progressBar(i, state.tasks.length, { label: "Sprint Progress" })}
10888
+ ${progressBar(i, state.tasks.length, { label: `Sprint: ${sprintName}` })}
10810
10889
 
10811
10890
  `);
10812
10891
  markTaskInProgress(state, task.issue);
10813
10892
  saveRunState(projectRoot, state);
10814
- const result = await executeIssue(projectRoot, {
10893
+ const result = await executeIssue(workDir, {
10815
10894
  issueNumber: task.issue,
10816
10895
  provider: execution.provider,
10817
10896
  model: execution.model,
@@ -10825,7 +10904,7 @@ ${progressBar(i, state.tasks.length, { label: "Sprint Progress" })}
10825
10904
  if (result.success) {
10826
10905
  if (!flags.dryRun) {
10827
10906
  const issueTitle = issue?.title ?? "";
10828
- ensureTaskCommit(projectRoot, task.issue, issueTitle);
10907
+ ensureTaskCommit(workDir, task.issue, issueTitle);
10829
10908
  if (sandboxed && i < state.tasks.length - 1) {
10830
10909
  process.stderr.write(` ${dim2("↻ Sandbox will resync on next task")}
10831
10910
  `);
@@ -10837,18 +10916,20 @@ ${progressBar(i, state.tasks.length, { label: "Sprint Progress" })}
10837
10916
  saveRunState(projectRoot, state);
10838
10917
  if (config.sprint.stopOnFailure) {
10839
10918
  process.stderr.write(`
10840
- ${red2("✗")} Sprint stopped: task #${task.issue} failed.
10919
+ ${red2("✗")} Sprint "${sprintName}" stopped: task #${task.issue} failed.
10841
10920
  `);
10842
10921
  process.stderr.write(` Resume with: ${bold2("locus run --resume")}
10843
10922
  `);
10844
- return;
10923
+ break;
10845
10924
  }
10925
+ process.stderr.write(` ${yellow2("⚠")} Task #${task.issue} failed, continuing to next task.
10926
+ `);
10846
10927
  }
10847
10928
  saveRunState(projectRoot, state);
10848
10929
  }
10849
10930
  const stats = getRunStats(state);
10850
10931
  process.stderr.write(`
10851
- ${progressBar(stats.done, stats.total, { label: "Sprint Complete" })}
10932
+ ${progressBar(stats.done, stats.total, { label: `Sprint Complete: ${sprintName}` })}
10852
10933
  `);
10853
10934
  process.stderr.write(`
10854
10935
  ${bold2("Summary:")}
@@ -10861,21 +10942,18 @@ ${bold2("Summary:")}
10861
10942
  issue: t.issue,
10862
10943
  title: issues.find((i) => i.number === t.issue)?.title
10863
10944
  }));
10864
- const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
10865
- if (prNumber !== undefined) {
10866
- try {
10867
- execSync17(`git checkout ${config.agent.baseBranch}`, {
10868
- cwd: projectRoot,
10869
- encoding: "utf-8",
10870
- stdio: ["pipe", "pipe", "pipe"]
10871
- });
10872
- process.stderr.write(` ${dim2(`Checked out ${config.agent.baseBranch}`)}
10873
- `);
10874
- } catch {}
10875
- }
10945
+ await createSprintPR(workDir, config, sprintName, branchName, completedTasks);
10876
10946
  }
10877
10947
  if (stats.failed === 0) {
10878
10948
  clearRunState(projectRoot, sprintName);
10949
+ if (worktreePath) {
10950
+ removeSprintWorktree(projectRoot, sprintName);
10951
+ }
10952
+ } else {
10953
+ if (worktreePath) {
10954
+ process.stderr.write(` ${yellow2("⚠")} Sprint worktree preserved: ${dim2(worktreePath)}
10955
+ `);
10956
+ }
10879
10957
  }
10880
10958
  }
10881
10959
  async function handleSingleIssue(projectRoot, config, issueNumber, flags, sandboxed) {
@@ -11056,43 +11134,87 @@ ${yellow2("⚠")} Failed worktrees preserved for debugging:
11056
11134
  clearRunState(projectRoot);
11057
11135
  }
11058
11136
  }
11059
- async function handleResume(projectRoot, config, sandboxed, runRef) {
11060
- const execution = resolveExecutionContext(config);
11061
- const sprintName = config.sprint.active ?? undefined;
11062
- let state = sprintName ? loadRunState(projectRoot, sprintName) : null;
11063
- if (!state) {
11064
- state = loadRunState(projectRoot);
11137
+ async function handleResume(projectRoot, config, sandboxed, flags) {
11138
+ if (flags.sprint) {
11139
+ const state = loadRunState(projectRoot, flags.sprint);
11140
+ if (!state) {
11141
+ process.stderr.write(`${red2("✗")} No run state found for sprint "${flags.sprint}".
11142
+ `);
11143
+ return;
11144
+ }
11145
+ await resumeSingleRun(projectRoot, config, state, sandboxed);
11146
+ return;
11065
11147
  }
11066
- if (!state) {
11148
+ const sprintsToResume = [];
11149
+ try {
11150
+ const { readdirSync: readdirSync8 } = await import("node:fs");
11151
+ const runStateDir = join22(projectRoot, ".locus", "run-state");
11152
+ if (existsSync22(runStateDir)) {
11153
+ const files = readdirSync8(runStateDir).filter((f) => f.endsWith(".json"));
11154
+ for (const file of files) {
11155
+ const sprintName = file === "_parallel.json" ? undefined : file.replace(/\.json$/, "");
11156
+ const state = loadRunState(projectRoot, sprintName);
11157
+ if (state) {
11158
+ const stats = getRunStats(state);
11159
+ if (stats.failed > 0 || stats.pending > 0 || stats.inProgress > 0) {
11160
+ sprintsToResume.push(state);
11161
+ }
11162
+ }
11163
+ }
11164
+ }
11165
+ } catch {}
11166
+ if (sprintsToResume.length === 0) {
11067
11167
  process.stderr.write(`${red2("✗")} No run state found. Nothing to resume.
11068
11168
  `);
11069
11169
  return;
11070
11170
  }
11071
- runRef.sprintName = state.sprint;
11171
+ if (sprintsToResume.length === 1) {
11172
+ await resumeSingleRun(projectRoot, config, sprintsToResume[0], sandboxed);
11173
+ } else {
11174
+ process.stderr.write(`
11175
+ ${bold2("Resuming")} ${cyan2(`${sprintsToResume.length} runs`)} ${dim2("(parallel)")}
11176
+
11177
+ `);
11178
+ const promises = sprintsToResume.map((state) => resumeSingleRun(projectRoot, config, state, sandboxed).catch((e) => {
11179
+ getLogger().warn(`Resume for "${state.sprint}" threw: ${e}`);
11180
+ }));
11181
+ await Promise.allSettled(promises);
11182
+ }
11183
+ }
11184
+ async function resumeSingleRun(projectRoot, config, state, sandboxed) {
11185
+ const execution = resolveExecutionContext(config);
11072
11186
  const stats = getRunStats(state);
11073
11187
  process.stderr.write(`
11074
- ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
11188
+ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}${state.sprint ? ` (${cyan2(state.sprint)})` : ""}
11075
11189
  `);
11076
11190
  process.stderr.write(` Done: ${stats.done}, Failed: ${stats.failed}, Pending: ${stats.pending}
11077
11191
 
11078
11192
  `);
11079
- if (state.type === "sprint" && state.branch) {
11080
- try {
11081
- const currentBranch = execSync17("git rev-parse --abbrev-ref HEAD", {
11082
- cwd: projectRoot,
11083
- encoding: "utf-8",
11084
- stdio: ["pipe", "pipe", "pipe"]
11085
- }).trim();
11086
- if (currentBranch !== state.branch) {
11087
- execSync17(`git checkout ${state.branch}`, {
11193
+ let workDir = projectRoot;
11194
+ if (state.type === "sprint" && state.sprint) {
11195
+ const { getSprintWorktreePath: getSprintWorktreePath2, sprintSlug: sprintSlug3 } = await Promise.resolve().then(() => (init_worktree(), exports_worktree));
11196
+ const { existsSync: existsSync23 } = await import("node:fs");
11197
+ const wtPath = getSprintWorktreePath2(projectRoot, sprintSlug3(state.sprint));
11198
+ if (existsSync23(wtPath)) {
11199
+ workDir = wtPath;
11200
+ } else if (state.branch) {
11201
+ try {
11202
+ const currentBranch = execSync17("git rev-parse --abbrev-ref HEAD", {
11088
11203
  cwd: projectRoot,
11089
11204
  encoding: "utf-8",
11090
11205
  stdio: ["pipe", "pipe", "pipe"]
11091
- });
11092
- }
11093
- } catch {
11094
- process.stderr.write(`${yellow2("⚠")} Could not checkout branch ${state.branch}
11206
+ }).trim();
11207
+ if (currentBranch !== state.branch) {
11208
+ execSync17(`git checkout ${state.branch}`, {
11209
+ cwd: projectRoot,
11210
+ encoding: "utf-8",
11211
+ stdio: ["pipe", "pipe", "pipe"]
11212
+ });
11213
+ }
11214
+ } catch {
11215
+ process.stderr.write(`${yellow2("⚠")} Could not checkout branch ${state.branch}
11095
11216
  `);
11217
+ }
11096
11218
  }
11097
11219
  }
11098
11220
  const isSprintRun = state.type === "sprint";
@@ -11106,7 +11228,7 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
11106
11228
  }
11107
11229
  markTaskInProgress(state, task.issue);
11108
11230
  saveRunState(projectRoot, state);
11109
- const result = await executeIssue(projectRoot, {
11231
+ const result = await executeIssue(workDir, {
11110
11232
  issueNumber: task.issue,
11111
11233
  provider: execution.provider,
11112
11234
  model: execution.model,
@@ -11122,7 +11244,7 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
11122
11244
  const iss = getIssue(task.issue, { cwd: projectRoot });
11123
11245
  issueTitle = iss.title;
11124
11246
  } catch {}
11125
- ensureTaskCommit(projectRoot, task.issue, issueTitle);
11247
+ ensureTaskCommit(workDir, task.issue, issueTitle);
11126
11248
  if (sandboxed) {
11127
11249
  process.stderr.write(` ${dim2("↻ Sandbox will resync on next task")}
11128
11250
  `);
@@ -11138,6 +11260,8 @@ ${red2("✗")} Sprint stopped: task #${task.issue} failed.
11138
11260
  `);
11139
11261
  return;
11140
11262
  }
11263
+ process.stderr.write(` ${yellow2("⚠")} Task #${task.issue} failed, continuing to next task.
11264
+ `);
11141
11265
  }
11142
11266
  saveRunState(projectRoot, state);
11143
11267
  task = getNextTask(state);
@@ -11149,21 +11273,13 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
11149
11273
  `);
11150
11274
  if (isSprintRun && state.branch && state.sprint && finalStats.done > 0) {
11151
11275
  const completedTasks = state.tasks.filter((t) => t.status === "done").map((t) => ({ issue: t.issue }));
11152
- const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
11153
- if (prNumber !== undefined) {
11154
- try {
11155
- execSync17(`git checkout ${config.agent.baseBranch}`, {
11156
- cwd: projectRoot,
11157
- encoding: "utf-8",
11158
- stdio: ["pipe", "pipe", "pipe"]
11159
- });
11160
- process.stderr.write(` ${dim2(`Checked out ${config.agent.baseBranch}`)}
11161
- `);
11162
- } catch {}
11163
- }
11276
+ await createSprintPR(workDir, config, state.sprint, state.branch, completedTasks);
11164
11277
  }
11165
11278
  if (finalStats.failed === 0) {
11166
11279
  clearRunState(projectRoot, state.sprint);
11280
+ if (isSprintRun && state.sprint) {
11281
+ removeSprintWorktree(projectRoot, state.sprint);
11282
+ }
11167
11283
  }
11168
11284
  }
11169
11285
  function sortByOrder2(issues) {
@@ -11181,22 +11297,22 @@ function getOrder2(issue) {
11181
11297
  }
11182
11298
  return null;
11183
11299
  }
11184
- function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
11300
+ function ensureTaskCommit(workDir, issueNumber, issueTitle) {
11185
11301
  try {
11186
- const committedSubmodules = commitDirtySubmodules(projectRoot, issueNumber, issueTitle);
11302
+ const committedSubmodules = commitDirtySubmodules(workDir, issueNumber, issueTitle);
11187
11303
  if (committedSubmodules.length > 0) {
11188
11304
  process.stderr.write(` ${dim2(`Committed submodule changes: ${committedSubmodules.join(", ")}`)}
11189
11305
  `);
11190
11306
  }
11191
11307
  const status = execSync17("git status --porcelain", {
11192
- cwd: projectRoot,
11308
+ cwd: workDir,
11193
11309
  encoding: "utf-8",
11194
11310
  stdio: ["pipe", "pipe", "pipe"]
11195
11311
  }).trim();
11196
11312
  if (!status)
11197
11313
  return;
11198
11314
  execSync17("git add -A", {
11199
- cwd: projectRoot,
11315
+ cwd: workDir,
11200
11316
  encoding: "utf-8",
11201
11317
  stdio: ["pipe", "pipe", "pipe"]
11202
11318
  });
@@ -11205,7 +11321,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
11205
11321
  Co-Authored-By: LocusAgent <agent@locusai.team>`;
11206
11322
  execSync17(`git commit -F -`, {
11207
11323
  input: message,
11208
- cwd: projectRoot,
11324
+ cwd: workDir,
11209
11325
  encoding: "utf-8",
11210
11326
  stdio: ["pipe", "pipe", "pipe"]
11211
11327
  });
@@ -11213,12 +11329,12 @@ Co-Authored-By: LocusAgent <agent@locusai.team>`;
11213
11329
  `);
11214
11330
  } catch {}
11215
11331
  }
11216
- async function createSprintPR(projectRoot, config, sprintName, branchName, tasks) {
11332
+ async function createSprintPR(workDir, config, sprintName, branchName, tasks) {
11217
11333
  if (!config.agent.autoPR)
11218
11334
  return;
11219
11335
  try {
11220
11336
  const diff = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
11221
- cwd: projectRoot,
11337
+ cwd: workDir,
11222
11338
  encoding: "utf-8",
11223
11339
  stdio: ["pipe", "pipe", "pipe"]
11224
11340
  }).trim();
@@ -11227,15 +11343,15 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
11227
11343
  `);
11228
11344
  return;
11229
11345
  }
11230
- pushSubmoduleBranches(projectRoot);
11346
+ pushSubmoduleBranches(workDir);
11231
11347
  execSync17(`git push -u origin ${branchName}`, {
11232
- cwd: projectRoot,
11348
+ cwd: workDir,
11233
11349
  encoding: "utf-8",
11234
11350
  stdio: ["pipe", "pipe", "pipe"]
11235
11351
  });
11236
11352
  const taskLines = tasks.map((t) => `- Closes #${t.issue}${t.title ? `: ${t.title}` : ""}`).join(`
11237
11353
  `);
11238
- const submoduleSummary = getSubmoduleChangeSummary(projectRoot, config.agent.baseBranch);
11354
+ const submoduleSummary = getSubmoduleChangeSummary(workDir, config.agent.baseBranch);
11239
11355
  let prBody = `## Sprint: ${sprintName}
11240
11356
 
11241
11357
  ${taskLines}`;
@@ -11249,7 +11365,7 @@ ${submoduleSummary}`;
11249
11365
  ---
11250
11366
 
11251
11367
  \uD83E\uDD16 Automated by [Locus](https://github.com/asgarovf/locusai)`;
11252
- const prNumber = createPR(`Sprint: ${sprintName}`, prBody, branchName, config.agent.baseBranch, { cwd: projectRoot });
11368
+ const prNumber = createPR(`Sprint: ${sprintName}`, prBody, branchName, config.agent.baseBranch, { cwd: workDir });
11253
11369
  process.stderr.write(` ${green("✓")} Created sprint PR #${prNumber}
11254
11370
  `);
11255
11371
  return prNumber;
@@ -11283,8 +11399,8 @@ __export(exports_status, {
11283
11399
  statusCommand: () => statusCommand
11284
11400
  });
11285
11401
  import { execSync as execSync18 } from "node:child_process";
11286
- import { existsSync as existsSync22 } from "node:fs";
11287
- import { dirname as dirname7, join as join22 } from "node:path";
11402
+ import { existsSync as existsSync23 } from "node:fs";
11403
+ import { dirname as dirname7, join as join23 } from "node:path";
11288
11404
  async function statusCommand(projectRoot) {
11289
11405
  const config = loadConfig(projectRoot);
11290
11406
  const spinner = new Spinner;
@@ -11293,21 +11409,19 @@ async function statusCommand(projectRoot) {
11293
11409
  lines.push(` ${dim2("Repo:")} ${cyan2(`${config.github.owner}/${config.github.repo}`)}`);
11294
11410
  lines.push(` ${dim2("Provider:")} ${config.ai.provider} / ${config.ai.model}`);
11295
11411
  lines.push(` ${dim2("Branch:")} ${config.agent.baseBranch}`);
11296
- if (config.sprint.active) {
11297
- const sprintName = config.sprint.active;
11298
- try {
11299
- const milestones = listMilestones(config.github.owner, config.github.repo, "open", { cwd: projectRoot });
11300
- const milestone = milestones.find((m) => m.title.toLowerCase() === sprintName.toLowerCase());
11301
- if (milestone) {
11412
+ try {
11413
+ const milestones = listMilestones(config.github.owner, config.github.repo, "open", { cwd: projectRoot });
11414
+ if (milestones.length > 0) {
11415
+ for (const milestone of milestones) {
11302
11416
  const total = milestone.openIssues + milestone.closedIssues;
11303
11417
  const done = milestone.closedIssues;
11304
11418
  const dueStr = milestone.dueOn ? ` due ${new Date(milestone.dueOn).toLocaleDateString("en-US", { month: "short", day: "numeric" })}` : "";
11305
11419
  lines.push("");
11306
- lines.push(` ${bold2("Sprint:")} ${cyan2(sprintName)} (${done} of ${total} done${dueStr})`);
11420
+ lines.push(` ${bold2("Sprint:")} ${cyan2(milestone.title)} (${done} of ${total} done${dueStr})`);
11307
11421
  if (total > 0) {
11308
11422
  lines.push(` ${progressBar(done, total, { width: 30 })}`);
11309
11423
  }
11310
- const issues = listIssues({ milestone: sprintName, state: "all" }, { cwd: projectRoot });
11424
+ const issues = listIssues({ milestone: milestone.title, state: "all" }, { cwd: projectRoot });
11311
11425
  const queued = issues.filter((i) => i.labels.some((l) => l === "locus:queued")).length;
11312
11426
  const inProgress = issues.filter((i) => i.labels.some((l) => l === "locus:in-progress")).length;
11313
11427
  const failed = issues.filter((i) => i.labels.some((l) => l === "locus:failed")).length;
@@ -11323,17 +11437,14 @@ async function statusCommand(projectRoot) {
11323
11437
  if (parts.length > 0) {
11324
11438
  lines.push(` ${parts.join(" ")}`);
11325
11439
  }
11326
- } else {
11327
- lines.push("");
11328
- lines.push(` ${bold2("Sprint:")} ${cyan2(sprintName)} ${dim2("(not found)")}`);
11329
11440
  }
11330
- } catch {
11441
+ } else {
11331
11442
  lines.push("");
11332
- lines.push(` ${bold2("Sprint:")} ${cyan2(sprintName)} ${dim2("(could not fetch)")}`);
11443
+ lines.push(` ${dim2("Sprint:")} ${dim2("no open sprints")}`);
11333
11444
  }
11334
- } else {
11445
+ } catch {
11335
11446
  lines.push("");
11336
- lines.push(` ${dim2("Sprint:")} ${dim2("none active")}`);
11447
+ lines.push(` ${dim2("Sprint:")} ${dim2("(could not fetch)")}`);
11337
11448
  }
11338
11449
  const runState = loadRunState(projectRoot);
11339
11450
  if (runState) {
@@ -11421,13 +11532,13 @@ ${drawBox(lines, { title: "Locus Status" })}
11421
11532
  `);
11422
11533
  }
11423
11534
  function getPm2Bin() {
11424
- const pkgsBin = join22(getPackagesDir(), "node_modules", ".bin", "pm2");
11425
- if (existsSync22(pkgsBin))
11535
+ const pkgsBin = join23(getPackagesDir(), "node_modules", ".bin", "pm2");
11536
+ if (existsSync23(pkgsBin))
11426
11537
  return pkgsBin;
11427
11538
  let dir = process.cwd();
11428
11539
  while (dir !== dirname7(dir)) {
11429
- const candidate = join22(dir, "node_modules", ".bin", "pm2");
11430
- if (existsSync22(candidate))
11540
+ const candidate = join23(dir, "node_modules", ".bin", "pm2");
11541
+ if (existsSync23(candidate))
11431
11542
  return candidate;
11432
11543
  dir = dirname7(dir);
11433
11544
  }
@@ -11623,13 +11734,13 @@ __export(exports_plan, {
11623
11734
  parsePlanArgs: () => parsePlanArgs
11624
11735
  });
11625
11736
  import {
11626
- existsSync as existsSync23,
11737
+ existsSync as existsSync24,
11627
11738
  mkdirSync as mkdirSync15,
11628
11739
  readdirSync as readdirSync8,
11629
11740
  readFileSync as readFileSync13,
11630
11741
  writeFileSync as writeFileSync10
11631
11742
  } from "node:fs";
11632
- import { join as join23 } from "node:path";
11743
+ import { join as join24 } from "node:path";
11633
11744
  function printHelp2() {
11634
11745
  process.stderr.write(`
11635
11746
  ${bold2("locus plan")} — AI-powered sprint planning
@@ -11661,11 +11772,11 @@ function normalizeSprintName(name) {
11661
11772
  return name.trim().toLowerCase();
11662
11773
  }
11663
11774
  function getPlansDir(projectRoot) {
11664
- return join23(projectRoot, ".locus", "plans");
11775
+ return join24(projectRoot, ".locus", "plans");
11665
11776
  }
11666
11777
  function ensurePlansDir(projectRoot) {
11667
11778
  const dir = getPlansDir(projectRoot);
11668
- if (!existsSync23(dir)) {
11779
+ if (!existsSync24(dir)) {
11669
11780
  mkdirSync15(dir, { recursive: true });
11670
11781
  }
11671
11782
  return dir;
@@ -11675,14 +11786,14 @@ function generateId() {
11675
11786
  }
11676
11787
  function loadPlanFile(projectRoot, id) {
11677
11788
  const dir = getPlansDir(projectRoot);
11678
- if (!existsSync23(dir))
11789
+ if (!existsSync24(dir))
11679
11790
  return null;
11680
11791
  const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
11681
11792
  const match = files.find((f) => f.startsWith(id));
11682
11793
  if (!match)
11683
11794
  return null;
11684
11795
  try {
11685
- const content = readFileSync13(join23(dir, match), "utf-8");
11796
+ const content = readFileSync13(join24(dir, match), "utf-8");
11686
11797
  return JSON.parse(content);
11687
11798
  } catch {
11688
11799
  return null;
@@ -11749,7 +11860,7 @@ async function planCommand(projectRoot, args, flags = {}) {
11749
11860
  }
11750
11861
  function handleListPlans(projectRoot) {
11751
11862
  const dir = getPlansDir(projectRoot);
11752
- if (!existsSync23(dir)) {
11863
+ if (!existsSync24(dir)) {
11753
11864
  process.stderr.write(`${dim2("No saved plans yet.")}
11754
11865
  `);
11755
11866
  return;
@@ -11767,7 +11878,7 @@ ${bold2("Saved Plans:")}
11767
11878
  for (const file of files) {
11768
11879
  const id = file.replace(".json", "");
11769
11880
  try {
11770
- const content = readFileSync13(join23(dir, file), "utf-8");
11881
+ const content = readFileSync13(join24(dir, file), "utf-8");
11771
11882
  const plan = JSON.parse(content);
11772
11883
  const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
11773
11884
  const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
@@ -11835,7 +11946,7 @@ async function handleRefinePlan(projectRoot, id, feedback, flags) {
11835
11946
  return;
11836
11947
  }
11837
11948
  const config = loadConfig(projectRoot);
11838
- const planPath = join23(getPlansDir(projectRoot), `${plan.id}.json`);
11949
+ const planPath = join24(getPlansDir(projectRoot), `${plan.id}.json`);
11839
11950
  const planPathRelative = `.locus/plans/${plan.id}.json`;
11840
11951
  process.stderr.write(`
11841
11952
  ${bold2("Refining plan:")} ${cyan2(plan.directive)}
@@ -11874,7 +11985,7 @@ ${red2("✗")} Refinement failed: ${aiResult.error}
11874
11985
  `);
11875
11986
  return;
11876
11987
  }
11877
- if (!existsSync23(planPath)) {
11988
+ if (!existsSync24(planPath)) {
11878
11989
  process.stderr.write(`
11879
11990
  ${yellow2("⚠")} Plan file was not found at ${bold2(planPathRelative)}.
11880
11991
  `);
@@ -11972,7 +12083,7 @@ ${bold2("Approving plan:")}
11972
12083
  async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
11973
12084
  const id = generateId();
11974
12085
  const plansDir = ensurePlansDir(projectRoot);
11975
- const planPath = join23(plansDir, `${id}.json`);
12086
+ const planPath = join24(plansDir, `${id}.json`);
11976
12087
  const planPathRelative = `.locus/plans/${id}.json`;
11977
12088
  const displayDirective = directive;
11978
12089
  process.stderr.write(`
@@ -12015,7 +12126,7 @@ ${red2("✗")} Planning failed: ${aiResult.error}
12015
12126
  `);
12016
12127
  return;
12017
12128
  }
12018
- if (!existsSync23(planPath)) {
12129
+ if (!existsSync24(planPath)) {
12019
12130
  process.stderr.write(`
12020
12131
  ${yellow2("⚠")} Plan file was not created at ${bold2(planPathRelative)}.
12021
12132
  `);
@@ -12197,15 +12308,15 @@ ${directive}${sprintName ? `
12197
12308
 
12198
12309
  **Sprint:** ${sprintName}` : ""}
12199
12310
  </directive>`);
12200
- const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12201
- if (existsSync23(locusPath)) {
12311
+ const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
12312
+ if (existsSync24(locusPath)) {
12202
12313
  const content = readFileSync13(locusPath, "utf-8");
12203
12314
  parts.push(`<project-context>
12204
12315
  ${content.slice(0, 3000)}
12205
12316
  </project-context>`);
12206
12317
  }
12207
- const learningsPath = join23(projectRoot, ".locus", "LEARNINGS.md");
12208
- if (existsSync23(learningsPath)) {
12318
+ const learningsPath = join24(projectRoot, ".locus", "LEARNINGS.md");
12319
+ if (existsSync24(learningsPath)) {
12209
12320
  const content = readFileSync13(learningsPath, "utf-8");
12210
12321
  parts.push(`<past-learnings>
12211
12322
  ${content.slice(0, 2000)}
@@ -12258,8 +12369,8 @@ ${JSON.stringify(plan, null, 2)}
12258
12369
  parts.push(`<feedback>
12259
12370
  ${feedback}
12260
12371
  </feedback>`);
12261
- const locusPath = join23(projectRoot, ".locus", "LOCUS.md");
12262
- if (existsSync23(locusPath)) {
12372
+ const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
12373
+ if (existsSync24(locusPath)) {
12263
12374
  const content = readFileSync13(locusPath, "utf-8");
12264
12375
  parts.push(`<project-context>
12265
12376
  ${content.slice(0, 3000)}
@@ -12442,8 +12553,8 @@ __export(exports_review, {
12442
12553
  reviewCommand: () => reviewCommand
12443
12554
  });
12444
12555
  import { execFileSync as execFileSync2, execSync as execSync19 } from "node:child_process";
12445
- import { existsSync as existsSync24, readFileSync as readFileSync14 } from "node:fs";
12446
- import { join as join24 } from "node:path";
12556
+ import { existsSync as existsSync25, readFileSync as readFileSync14 } from "node:fs";
12557
+ import { join as join25 } from "node:path";
12447
12558
  function printHelp3() {
12448
12559
  process.stderr.write(`
12449
12560
  ${bold2("locus review")} — AI-powered code review
@@ -12612,8 +12723,8 @@ function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
12612
12723
  parts.push(`<role>
12613
12724
  You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
12614
12725
  </role>`);
12615
- const locusPath = join24(projectRoot, ".locus", "LOCUS.md");
12616
- if (existsSync24(locusPath)) {
12726
+ const locusPath = join25(projectRoot, ".locus", "LOCUS.md");
12727
+ if (existsSync25(locusPath)) {
12617
12728
  const content = readFileSync14(locusPath, "utf-8");
12618
12729
  parts.push(`<project-context>
12619
12730
  ${content.slice(0, 2000)}
@@ -12789,13 +12900,8 @@ ${bold2("Finding PR for issue")} ${cyan2(`#${issueNumber}`)}...
12789
12900
  return handleSinglePR(projectRoot, config, prNumber, flags);
12790
12901
  }
12791
12902
  async function handleSprint(projectRoot, config, flags) {
12792
- if (!config.sprint.active) {
12793
- process.stderr.write(`${red2("✗")} No active sprint set.
12794
- `);
12795
- return;
12796
- }
12797
12903
  process.stderr.write(`
12798
- ${bold2("Iterating on sprint:")} ${cyan2(config.sprint.active)}
12904
+ ${bold2("Iterating on sprint PRs...")}
12799
12905
 
12800
12906
  `);
12801
12907
  const prs = listPRs({ label: "agent:managed", state: "open" }, { cwd: projectRoot });
@@ -12934,14 +13040,14 @@ __export(exports_discuss, {
12934
13040
  discussCommand: () => discussCommand
12935
13041
  });
12936
13042
  import {
12937
- existsSync as existsSync25,
13043
+ existsSync as existsSync26,
12938
13044
  mkdirSync as mkdirSync16,
12939
13045
  readdirSync as readdirSync9,
12940
13046
  readFileSync as readFileSync15,
12941
13047
  unlinkSync as unlinkSync6,
12942
13048
  writeFileSync as writeFileSync11
12943
13049
  } from "node:fs";
12944
- import { join as join25 } from "node:path";
13050
+ import { join as join26 } from "node:path";
12945
13051
  function printHelp5() {
12946
13052
  process.stderr.write(`
12947
13053
  ${bold2("locus discuss")} — AI-powered architectural discussions
@@ -12963,11 +13069,11 @@ ${bold2("Examples:")}
12963
13069
  `);
12964
13070
  }
12965
13071
  function getDiscussionsDir(projectRoot) {
12966
- return join25(projectRoot, ".locus", "discussions");
13072
+ return join26(projectRoot, ".locus", "discussions");
12967
13073
  }
12968
13074
  function ensureDiscussionsDir(projectRoot) {
12969
13075
  const dir = getDiscussionsDir(projectRoot);
12970
- if (!existsSync25(dir)) {
13076
+ if (!existsSync26(dir)) {
12971
13077
  mkdirSync16(dir, { recursive: true });
12972
13078
  }
12973
13079
  return dir;
@@ -13002,7 +13108,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
13002
13108
  }
13003
13109
  function listDiscussions(projectRoot) {
13004
13110
  const dir = getDiscussionsDir(projectRoot);
13005
- if (!existsSync25(dir)) {
13111
+ if (!existsSync26(dir)) {
13006
13112
  process.stderr.write(`${dim2("No discussions yet.")}
13007
13113
  `);
13008
13114
  return;
@@ -13019,7 +13125,7 @@ ${bold2("Discussions:")}
13019
13125
  `);
13020
13126
  for (const file of files) {
13021
13127
  const id = file.replace(".md", "");
13022
- const content = readFileSync15(join25(dir, file), "utf-8");
13128
+ const content = readFileSync15(join26(dir, file), "utf-8");
13023
13129
  const titleMatch = content.match(/^#\s+(.+)/m);
13024
13130
  const title = titleMatch ? titleMatch[1] : id;
13025
13131
  const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
@@ -13037,7 +13143,7 @@ function showDiscussion(projectRoot, id) {
13037
13143
  return;
13038
13144
  }
13039
13145
  const dir = getDiscussionsDir(projectRoot);
13040
- if (!existsSync25(dir)) {
13146
+ if (!existsSync26(dir)) {
13041
13147
  process.stderr.write(`${red2("✗")} No discussions found.
13042
13148
  `);
13043
13149
  return;
@@ -13049,7 +13155,7 @@ function showDiscussion(projectRoot, id) {
13049
13155
  `);
13050
13156
  return;
13051
13157
  }
13052
- const content = readFileSync15(join25(dir, match), "utf-8");
13158
+ const content = readFileSync15(join26(dir, match), "utf-8");
13053
13159
  process.stdout.write(`${content}
13054
13160
  `);
13055
13161
  }
@@ -13060,7 +13166,7 @@ function deleteDiscussion(projectRoot, id) {
13060
13166
  return;
13061
13167
  }
13062
13168
  const dir = getDiscussionsDir(projectRoot);
13063
- if (!existsSync25(dir)) {
13169
+ if (!existsSync26(dir)) {
13064
13170
  process.stderr.write(`${red2("✗")} No discussions found.
13065
13171
  `);
13066
13172
  return;
@@ -13072,7 +13178,7 @@ function deleteDiscussion(projectRoot, id) {
13072
13178
  `);
13073
13179
  return;
13074
13180
  }
13075
- unlinkSync6(join25(dir, match));
13181
+ unlinkSync6(join26(dir, match));
13076
13182
  process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
13077
13183
  `);
13078
13184
  }
@@ -13085,7 +13191,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
13085
13191
  return;
13086
13192
  }
13087
13193
  const dir = getDiscussionsDir(projectRoot);
13088
- if (!existsSync25(dir)) {
13194
+ if (!existsSync26(dir)) {
13089
13195
  process.stderr.write(`${red2("✗")} No discussions found.
13090
13196
  `);
13091
13197
  return;
@@ -13097,7 +13203,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
13097
13203
  `);
13098
13204
  return;
13099
13205
  }
13100
- const content = readFileSync15(join25(dir, match), "utf-8");
13206
+ const content = readFileSync15(join26(dir, match), "utf-8");
13101
13207
  const titleMatch = content.match(/^#\s+(.+)/m);
13102
13208
  const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
13103
13209
  await planCommand(projectRoot, [
@@ -13223,7 +13329,7 @@ ${turn.content}`;
13223
13329
  ...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
13224
13330
  ].join(`
13225
13331
  `);
13226
- writeFileSync11(join25(dir, `${id}.md`), markdown, "utf-8");
13332
+ writeFileSync11(join26(dir, `${id}.md`), markdown, "utf-8");
13227
13333
  process.stderr.write(`
13228
13334
  ${green("✓")} Discussion saved: ${cyan2(id)} ${dim2(`(${timer.formatted()})`)}
13229
13335
  `);
@@ -13238,15 +13344,15 @@ function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFi
13238
13344
  parts.push(`<role>
13239
13345
  You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
13240
13346
  </role>`);
13241
- const locusPath = join25(projectRoot, ".locus", "LOCUS.md");
13242
- if (existsSync25(locusPath)) {
13347
+ const locusPath = join26(projectRoot, ".locus", "LOCUS.md");
13348
+ if (existsSync26(locusPath)) {
13243
13349
  const content = readFileSync15(locusPath, "utf-8");
13244
13350
  parts.push(`<project-context>
13245
13351
  ${content.slice(0, 3000)}
13246
13352
  </project-context>`);
13247
13353
  }
13248
- const learningsPath = join25(projectRoot, ".locus", "LEARNINGS.md");
13249
- if (existsSync25(learningsPath)) {
13354
+ const learningsPath = join26(projectRoot, ".locus", "LEARNINGS.md");
13355
+ if (existsSync26(learningsPath)) {
13250
13356
  const content = readFileSync15(learningsPath, "utf-8");
13251
13357
  parts.push(`<past-learnings>
13252
13358
  ${content.slice(0, 2000)}
@@ -13318,8 +13424,8 @@ __export(exports_artifacts, {
13318
13424
  formatDate: () => formatDate2,
13319
13425
  artifactsCommand: () => artifactsCommand
13320
13426
  });
13321
- import { existsSync as existsSync26, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
13322
- import { join as join26 } from "node:path";
13427
+ import { existsSync as existsSync27, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
13428
+ import { join as join27 } from "node:path";
13323
13429
  function printHelp6() {
13324
13430
  process.stderr.write(`
13325
13431
  ${bold2("locus artifacts")} — View and manage AI-generated artifacts
@@ -13339,14 +13445,14 @@ ${dim2("Artifact names support partial matching.")}
13339
13445
  `);
13340
13446
  }
13341
13447
  function getArtifactsDir(projectRoot) {
13342
- return join26(projectRoot, ".locus", "artifacts");
13448
+ return join27(projectRoot, ".locus", "artifacts");
13343
13449
  }
13344
13450
  function listArtifacts(projectRoot) {
13345
13451
  const dir = getArtifactsDir(projectRoot);
13346
- if (!existsSync26(dir))
13452
+ if (!existsSync27(dir))
13347
13453
  return [];
13348
13454
  return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
13349
- const filePath = join26(dir, fileName);
13455
+ const filePath = join27(dir, fileName);
13350
13456
  const stat = statSync5(filePath);
13351
13457
  return {
13352
13458
  name: fileName.replace(/\.md$/, ""),
@@ -13359,8 +13465,8 @@ function listArtifacts(projectRoot) {
13359
13465
  function readArtifact(projectRoot, name) {
13360
13466
  const dir = getArtifactsDir(projectRoot);
13361
13467
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
13362
- const filePath = join26(dir, fileName);
13363
- if (!existsSync26(filePath))
13468
+ const filePath = join27(dir, fileName);
13469
+ if (!existsSync27(filePath))
13364
13470
  return null;
13365
13471
  const stat = statSync5(filePath);
13366
13472
  return {
@@ -13721,8 +13827,8 @@ __export(exports_sandbox2, {
13721
13827
  });
13722
13828
  import { execSync as execSync22, spawn as spawn7 } from "node:child_process";
13723
13829
  import { createHash } from "node:crypto";
13724
- import { existsSync as existsSync27, readFileSync as readFileSync17 } from "node:fs";
13725
- import { basename as basename4, join as join27 } from "node:path";
13830
+ import { existsSync as existsSync28, readFileSync as readFileSync17 } from "node:fs";
13831
+ import { basename as basename4, join as join28 } from "node:path";
13726
13832
  import { createInterface as createInterface3 } from "node:readline";
13727
13833
  function printSandboxHelp() {
13728
13834
  process.stderr.write(`
@@ -14252,7 +14358,7 @@ async function handleLogs(projectRoot, args) {
14252
14358
  }
14253
14359
  function detectPackageManager2(projectRoot) {
14254
14360
  try {
14255
- const raw = readFileSync17(join27(projectRoot, "package.json"), "utf-8");
14361
+ const raw = readFileSync17(join28(projectRoot, "package.json"), "utf-8");
14256
14362
  const pkgJson = JSON.parse(raw);
14257
14363
  if (typeof pkgJson.packageManager === "string") {
14258
14364
  const name = pkgJson.packageManager.split("@")[0];
@@ -14261,13 +14367,13 @@ function detectPackageManager2(projectRoot) {
14261
14367
  }
14262
14368
  }
14263
14369
  } catch {}
14264
- if (existsSync27(join27(projectRoot, "bun.lock")) || existsSync27(join27(projectRoot, "bun.lockb"))) {
14370
+ if (existsSync28(join28(projectRoot, "bun.lock")) || existsSync28(join28(projectRoot, "bun.lockb"))) {
14265
14371
  return "bun";
14266
14372
  }
14267
- if (existsSync27(join27(projectRoot, "yarn.lock"))) {
14373
+ if (existsSync28(join28(projectRoot, "yarn.lock"))) {
14268
14374
  return "yarn";
14269
14375
  }
14270
- if (existsSync27(join27(projectRoot, "pnpm-lock.yaml"))) {
14376
+ if (existsSync28(join28(projectRoot, "pnpm-lock.yaml"))) {
14271
14377
  return "pnpm";
14272
14378
  }
14273
14379
  return "npm";
@@ -14370,9 +14476,9 @@ Installing sandbox dependencies (${bold2(installCmd.join(" "))}) to container fi
14370
14476
  ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
14371
14477
  `);
14372
14478
  }
14373
- const setupScript = join27(projectRoot, ".locus", "sandbox-setup.sh");
14374
- const containerSetupScript = containerWorkdir ? join27(containerWorkdir, ".locus", "sandbox-setup.sh") : setupScript;
14375
- if (existsSync27(setupScript)) {
14479
+ const setupScript = join28(projectRoot, ".locus", "sandbox-setup.sh");
14480
+ const containerSetupScript = containerWorkdir ? join28(containerWorkdir, ".locus", "sandbox-setup.sh") : setupScript;
14481
+ if (existsSync28(setupScript)) {
14376
14482
  process.stderr.write(`Running ${bold2(".locus/sandbox-setup.sh")} in sandbox ${dim2(sandboxName)}...
14377
14483
  `);
14378
14484
  const hookOk = await runInteractiveCommand("docker", [
@@ -14540,13 +14646,13 @@ init_context();
14540
14646
  init_logger();
14541
14647
  init_rate_limiter();
14542
14648
  init_terminal();
14543
- import { existsSync as existsSync28, readFileSync as readFileSync18 } from "node:fs";
14544
- import { join as join28 } from "node:path";
14649
+ import { existsSync as existsSync29, readFileSync as readFileSync18 } from "node:fs";
14650
+ import { join as join29 } from "node:path";
14545
14651
  import { fileURLToPath } from "node:url";
14546
14652
  function getCliVersion() {
14547
14653
  const fallbackVersion = "0.0.0";
14548
- const packageJsonPath = join28(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
14549
- if (!existsSync28(packageJsonPath)) {
14654
+ const packageJsonPath = join29(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
14655
+ if (!existsSync29(packageJsonPath)) {
14550
14656
  return fallbackVersion;
14551
14657
  }
14552
14658
  try {
@@ -14660,6 +14766,9 @@ function parseArgs(argv) {
14660
14766
  case "--no-sandbox":
14661
14767
  flags.noSandbox = true;
14662
14768
  break;
14769
+ case "--sprint":
14770
+ flags.sprint = rawArgs[++i];
14771
+ break;
14663
14772
  default:
14664
14773
  if (arg.startsWith("--sandbox=")) {
14665
14774
  flags.sandbox = arg.slice("--sandbox=".length);
@@ -14823,7 +14932,7 @@ async function main() {
14823
14932
  try {
14824
14933
  const root = getGitRoot(cwd);
14825
14934
  if (isInitialized(root)) {
14826
- logDir = join28(root, ".locus", "logs");
14935
+ logDir = join29(root, ".locus", "logs");
14827
14936
  getRateLimiter(root);
14828
14937
  }
14829
14938
  } catch {}
@@ -14963,7 +15072,8 @@ async function main() {
14963
15072
  dryRun: parsed.flags.dryRun,
14964
15073
  model: parsed.flags.model,
14965
15074
  sandbox: parsed.flags.sandbox,
14966
- noSandbox: parsed.flags.noSandbox
15075
+ noSandbox: parsed.flags.noSandbox,
15076
+ sprint: parsed.flags.sprint
14967
15077
  });
14968
15078
  break;
14969
15079
  }