@locusai/cli 0.21.5 → 0.21.6

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 +365 -207
  2. package/package.json +1 -1
package/bin/locus.js CHANGED
@@ -4557,8 +4557,18 @@ var init_claude = __esm(() => {
4557
4557
 
4558
4558
  // src/core/sandbox-ignore.ts
4559
4559
  import { exec } from "node:child_process";
4560
- import { existsSync as existsSync13, readFileSync as readFileSync8 } from "node:fs";
4561
- import { join as join12 } from "node:path";
4560
+ import {
4561
+ cpSync,
4562
+ existsSync as existsSync13,
4563
+ mkdirSync as mkdirSync9,
4564
+ mkdtempSync,
4565
+ readdirSync as readdirSync3,
4566
+ readFileSync as readFileSync8,
4567
+ rmSync,
4568
+ statSync as statSync3
4569
+ } from "node:fs";
4570
+ import { tmpdir as tmpdir3 } from "node:os";
4571
+ import { dirname as dirname3, join as join12, relative } from "node:path";
4562
4572
  import { promisify } from "node:util";
4563
4573
  function parseIgnoreFile(filePath) {
4564
4574
  if (!existsSync13(filePath))
@@ -4606,6 +4616,124 @@ function buildCleanupScript(rules, workspacePath) {
4606
4616
  }
4607
4617
  return `${commands.join(" 2>/dev/null ; ")} 2>/dev/null`;
4608
4618
  }
4619
+ function patternToRegex(pattern) {
4620
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
4621
+ return new RegExp(`^${escaped}$`);
4622
+ }
4623
+ function findIgnoredPaths(projectRoot, rules) {
4624
+ const positive = rules.filter((r) => !r.negated);
4625
+ const negated = rules.filter((r) => r.negated);
4626
+ if (positive.length === 0)
4627
+ return [];
4628
+ const positiveMatchers = positive.map((r) => ({
4629
+ regex: patternToRegex(r.pattern),
4630
+ isDirectory: r.isDirectory
4631
+ }));
4632
+ const negatedRegexes = negated.map((r) => patternToRegex(r.pattern));
4633
+ const matches = [];
4634
+ function walk(dir) {
4635
+ let entries;
4636
+ try {
4637
+ entries = readdirSync3(dir);
4638
+ } catch {
4639
+ return;
4640
+ }
4641
+ for (const name of entries) {
4642
+ if (SKIP_DIRS.has(name))
4643
+ continue;
4644
+ const fullPath = join12(dir, name);
4645
+ let stat;
4646
+ try {
4647
+ stat = statSync3(fullPath);
4648
+ } catch {
4649
+ continue;
4650
+ }
4651
+ const isDir = stat.isDirectory();
4652
+ let matched = false;
4653
+ for (const m of positiveMatchers) {
4654
+ if (m.isDirectory && !isDir)
4655
+ continue;
4656
+ if (m.regex.test(name)) {
4657
+ matched = true;
4658
+ break;
4659
+ }
4660
+ }
4661
+ if (matched) {
4662
+ const isNegated = negatedRegexes.some((nr) => nr.test(name));
4663
+ if (!isNegated) {
4664
+ matches.push(fullPath);
4665
+ continue;
4666
+ }
4667
+ }
4668
+ if (isDir) {
4669
+ walk(fullPath);
4670
+ }
4671
+ }
4672
+ }
4673
+ walk(projectRoot);
4674
+ return matches;
4675
+ }
4676
+ function backupIgnoredFiles(projectRoot) {
4677
+ const log = getLogger();
4678
+ const ignorePath = join12(projectRoot, ".sandboxignore");
4679
+ const rules = parseIgnoreFile(ignorePath);
4680
+ if (rules.length === 0)
4681
+ return NOOP_BACKUP;
4682
+ const paths = findIgnoredPaths(projectRoot, rules);
4683
+ if (paths.length === 0)
4684
+ return NOOP_BACKUP;
4685
+ let backupDir;
4686
+ try {
4687
+ backupDir = mkdtempSync(join12(tmpdir3(), "locus-sandbox-backup-"));
4688
+ } catch (err) {
4689
+ log.debug("Failed to create sandbox backup dir", {
4690
+ error: err instanceof Error ? err.message : String(err)
4691
+ });
4692
+ return NOOP_BACKUP;
4693
+ }
4694
+ const backed = [];
4695
+ for (const src of paths) {
4696
+ const rel = relative(projectRoot, src);
4697
+ const dest = join12(backupDir, rel);
4698
+ try {
4699
+ mkdirSync9(dirname3(dest), { recursive: true });
4700
+ cpSync(src, dest, { recursive: true, preserveTimestamps: true });
4701
+ backed.push({ src, dest });
4702
+ } catch (err) {
4703
+ log.debug("Failed to back up ignored file", {
4704
+ src,
4705
+ error: err instanceof Error ? err.message : String(err)
4706
+ });
4707
+ }
4708
+ }
4709
+ if (backed.length === 0) {
4710
+ rmSync(backupDir, { recursive: true, force: true });
4711
+ return NOOP_BACKUP;
4712
+ }
4713
+ log.debug("Backed up sandbox-ignored files", {
4714
+ count: backed.length,
4715
+ backupDir
4716
+ });
4717
+ return {
4718
+ restore() {
4719
+ for (const { src, dest } of backed) {
4720
+ try {
4721
+ mkdirSync9(dirname3(src), { recursive: true });
4722
+ cpSync(dest, src, { recursive: true, preserveTimestamps: true });
4723
+ } catch (err) {
4724
+ log.debug("Failed to restore ignored file (potential data loss)", {
4725
+ src,
4726
+ error: err instanceof Error ? err.message : String(err)
4727
+ });
4728
+ }
4729
+ }
4730
+ try {
4731
+ rmSync(backupDir, { recursive: true, force: true });
4732
+ } catch {}
4733
+ log.debug("Restored sandbox-ignored files", { count: backed.length });
4734
+ }
4735
+ };
4736
+ }
4609
4737
  async function enforceSandboxIgnore(sandboxName, projectRoot) {
4610
4738
  const log = getLogger();
4611
4739
  const ignorePath = join12(projectRoot, ".sandboxignore");
@@ -4629,10 +4757,20 @@ async function enforceSandboxIgnore(sandboxName, projectRoot) {
4629
4757
  });
4630
4758
  }
4631
4759
  }
4632
- var execAsync;
4760
+ var execAsync, SKIP_DIRS, NOOP_BACKUP;
4633
4761
  var init_sandbox_ignore = __esm(() => {
4634
4762
  init_logger();
4635
4763
  execAsync = promisify(exec);
4764
+ SKIP_DIRS = new Set([
4765
+ "node_modules",
4766
+ ".git",
4767
+ ".locus",
4768
+ "dist",
4769
+ "build",
4770
+ ".next",
4771
+ ".cache"
4772
+ ]);
4773
+ NOOP_BACKUP = { restore() {} };
4636
4774
  });
4637
4775
 
4638
4776
  // src/ai/claude-sandbox.ts
@@ -4669,6 +4807,7 @@ class SandboxedClaudeRunner {
4669
4807
  }
4670
4808
  const claudeArgs = ["-p", options.prompt, ...buildClaudeArgs(options)];
4671
4809
  options.onStatusChange?.("Syncing sandbox...");
4810
+ const backup = backupIgnoredFiles(options.cwd);
4672
4811
  await enforceSandboxIgnore(this.sandboxName, options.cwd);
4673
4812
  options.onStatusChange?.("Thinking...");
4674
4813
  const dockerArgs = [
@@ -4685,95 +4824,99 @@ class SandboxedClaudeRunner {
4685
4824
  args: dockerArgs.join(" "),
4686
4825
  cwd: options.cwd
4687
4826
  });
4688
- return await new Promise((resolve2) => {
4689
- let output = "";
4690
- let errorOutput = "";
4691
- this.process = spawn3("docker", dockerArgs, {
4692
- stdio: ["ignore", "pipe", "pipe"],
4693
- env: process.env
4694
- });
4695
- if (options.verbose) {
4696
- let lineBuffer = "";
4697
- const seenToolIds = new Set;
4698
- this.process.stdout?.on("data", (chunk) => {
4699
- lineBuffer += chunk.toString();
4700
- const lines = lineBuffer.split(`
4701
- `);
4702
- lineBuffer = lines.pop() ?? "";
4703
- for (const line of lines) {
4704
- if (!line.trim())
4705
- continue;
4706
- try {
4707
- const event = JSON.parse(line);
4708
- if (event.type === "assistant" && event.message?.content) {
4709
- for (const item of event.message.content) {
4710
- if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
4711
- seenToolIds.add(item.id);
4712
- options.onToolActivity?.(formatToolCall2(item.name ?? "", item.input ?? {}));
4827
+ try {
4828
+ return await new Promise((resolve2) => {
4829
+ let output = "";
4830
+ let errorOutput = "";
4831
+ this.process = spawn3("docker", dockerArgs, {
4832
+ stdio: ["ignore", "pipe", "pipe"],
4833
+ env: process.env
4834
+ });
4835
+ if (options.verbose) {
4836
+ let lineBuffer = "";
4837
+ const seenToolIds = new Set;
4838
+ this.process.stdout?.on("data", (chunk) => {
4839
+ lineBuffer += chunk.toString();
4840
+ const lines = lineBuffer.split(`
4841
+ `);
4842
+ lineBuffer = lines.pop() ?? "";
4843
+ for (const line of lines) {
4844
+ if (!line.trim())
4845
+ continue;
4846
+ try {
4847
+ const event = JSON.parse(line);
4848
+ if (event.type === "assistant" && event.message?.content) {
4849
+ for (const item of event.message.content) {
4850
+ if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
4851
+ seenToolIds.add(item.id);
4852
+ options.onToolActivity?.(formatToolCall2(item.name ?? "", item.input ?? {}));
4853
+ }
4713
4854
  }
4855
+ } else if (event.type === "result") {
4856
+ const text = event.result ?? "";
4857
+ output = text;
4858
+ options.onOutput?.(text);
4714
4859
  }
4715
- } else if (event.type === "result") {
4716
- const text = event.result ?? "";
4717
- output = text;
4718
- options.onOutput?.(text);
4719
- }
4720
- } catch {
4721
- const newLine = `${line}
4860
+ } catch {
4861
+ const newLine = `${line}
4722
4862
  `;
4723
- output += newLine;
4724
- options.onOutput?.(newLine);
4863
+ output += newLine;
4864
+ options.onOutput?.(newLine);
4865
+ }
4725
4866
  }
4726
- }
4727
- });
4728
- } else {
4729
- this.process.stdout?.on("data", (chunk) => {
4867
+ });
4868
+ } else {
4869
+ this.process.stdout?.on("data", (chunk) => {
4870
+ const text = chunk.toString();
4871
+ output += text;
4872
+ options.onOutput?.(text);
4873
+ });
4874
+ }
4875
+ this.process.stderr?.on("data", (chunk) => {
4730
4876
  const text = chunk.toString();
4731
- output += text;
4732
- options.onOutput?.(text);
4877
+ errorOutput += text;
4878
+ log.debug("sandboxed claude stderr", { text: text.slice(0, 500) });
4733
4879
  });
4734
- }
4735
- this.process.stderr?.on("data", (chunk) => {
4736
- const text = chunk.toString();
4737
- errorOutput += text;
4738
- log.debug("sandboxed claude stderr", { text: text.slice(0, 500) });
4739
- });
4740
- this.process.on("close", (code) => {
4741
- this.process = null;
4742
- if (this.aborted) {
4880
+ this.process.on("close", (code) => {
4881
+ this.process = null;
4882
+ if (this.aborted) {
4883
+ resolve2({
4884
+ success: false,
4885
+ output,
4886
+ error: "Aborted by user",
4887
+ exitCode: code ?? 143
4888
+ });
4889
+ return;
4890
+ }
4891
+ if (code === 0) {
4892
+ resolve2({ success: true, output, exitCode: 0 });
4893
+ } else {
4894
+ resolve2({
4895
+ success: false,
4896
+ output,
4897
+ error: errorOutput || `sandboxed claude exited with code ${code}`,
4898
+ exitCode: code ?? 1
4899
+ });
4900
+ }
4901
+ });
4902
+ this.process.on("error", (err) => {
4903
+ this.process = null;
4743
4904
  resolve2({
4744
4905
  success: false,
4745
4906
  output,
4746
- error: "Aborted by user",
4747
- exitCode: code ?? 143
4907
+ error: `Failed to spawn docker sandbox: ${err.message}`,
4908
+ exitCode: 1
4748
4909
  });
4749
- return;
4750
- }
4751
- if (code === 0) {
4752
- resolve2({ success: true, output, exitCode: 0 });
4753
- } else {
4754
- resolve2({
4755
- success: false,
4756
- output,
4757
- error: errorOutput || `sandboxed claude exited with code ${code}`,
4758
- exitCode: code ?? 1
4910
+ });
4911
+ if (options.signal) {
4912
+ options.signal.addEventListener("abort", () => {
4913
+ this.abort();
4759
4914
  });
4760
4915
  }
4761
4916
  });
4762
- this.process.on("error", (err) => {
4763
- this.process = null;
4764
- resolve2({
4765
- success: false,
4766
- output,
4767
- error: `Failed to spawn docker sandbox: ${err.message}`,
4768
- exitCode: 1
4769
- });
4770
- });
4771
- if (options.signal) {
4772
- options.signal.addEventListener("abort", () => {
4773
- this.abort();
4774
- });
4775
- }
4776
- });
4917
+ } finally {
4918
+ backup.restore();
4919
+ }
4777
4920
  }
4778
4921
  abort() {
4779
4922
  this.aborted = true;
@@ -5043,6 +5186,7 @@ class SandboxedCodexRunner {
5043
5186
  }
5044
5187
  const codexArgs = buildCodexArgs(options.model);
5045
5188
  options.onStatusChange?.("Syncing sandbox...");
5189
+ const backup = backupIgnoredFiles(options.cwd);
5046
5190
  await enforceSandboxIgnore(this.sandboxName, options.cwd);
5047
5191
  if (!this.codexInstalled) {
5048
5192
  options.onStatusChange?.("Checking codex...");
@@ -5065,111 +5209,115 @@ class SandboxedCodexRunner {
5065
5209
  args: dockerArgs.join(" "),
5066
5210
  cwd: options.cwd
5067
5211
  });
5068
- return await new Promise((resolve2) => {
5069
- let rawOutput = "";
5070
- let errorOutput = "";
5071
- this.process = spawn5("docker", dockerArgs, {
5072
- stdio: ["pipe", "pipe", "pipe"],
5073
- env: process.env
5074
- });
5075
- let agentMessages = [];
5076
- const flushAgentMessages = () => {
5077
- if (agentMessages.length > 0) {
5078
- options.onOutput?.(agentMessages.join(`
5212
+ try {
5213
+ return await new Promise((resolve2) => {
5214
+ let rawOutput = "";
5215
+ let errorOutput = "";
5216
+ this.process = spawn5("docker", dockerArgs, {
5217
+ stdio: ["pipe", "pipe", "pipe"],
5218
+ env: process.env
5219
+ });
5220
+ let agentMessages = [];
5221
+ const flushAgentMessages = () => {
5222
+ if (agentMessages.length > 0) {
5223
+ options.onOutput?.(agentMessages.join(`
5079
5224
 
5080
5225
  `));
5081
- agentMessages = [];
5082
- }
5083
- };
5084
- let lineBuffer = "";
5085
- this.process.stdout?.on("data", (chunk) => {
5086
- lineBuffer += chunk.toString();
5087
- const lines = lineBuffer.split(`
5226
+ agentMessages = [];
5227
+ }
5228
+ };
5229
+ let lineBuffer = "";
5230
+ this.process.stdout?.on("data", (chunk) => {
5231
+ lineBuffer += chunk.toString();
5232
+ const lines = lineBuffer.split(`
5088
5233
  `);
5089
- lineBuffer = lines.pop() ?? "";
5090
- for (const line of lines) {
5091
- if (!line.trim())
5092
- continue;
5093
- rawOutput += `${line}
5234
+ lineBuffer = lines.pop() ?? "";
5235
+ for (const line of lines) {
5236
+ if (!line.trim())
5237
+ continue;
5238
+ rawOutput += `${line}
5094
5239
  `;
5095
- log.debug("sandboxed codex stdout line", { line });
5096
- try {
5097
- const event = JSON.parse(line);
5098
- const { type, item } = event;
5099
- if (type === "item.started" && item?.type === "command_execution") {
5100
- const cmd = (item.command ?? "").split(`
5240
+ log.debug("sandboxed codex stdout line", { line });
5241
+ try {
5242
+ const event = JSON.parse(line);
5243
+ const { type, item } = event;
5244
+ if (type === "item.started" && item?.type === "command_execution") {
5245
+ const cmd = (item.command ?? "").split(`
5101
5246
  `)[0].slice(0, 80);
5102
- options.onToolActivity?.(`running: ${cmd}`);
5103
- } else if (type === "item.completed" && item?.type === "command_execution") {
5104
- const code = item.exit_code;
5105
- options.onToolActivity?.(code === 0 ? "done" : `exit ${code}`);
5106
- } else if (type === "item.completed" && item?.type === "reasoning") {
5107
- const text = (item.text ?? "").trim().replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
5108
- if (text)
5109
- options.onToolActivity?.(text);
5110
- } else if (type === "item.completed" && item?.type === "agent_message") {
5111
- const text = item.text ?? "";
5112
- if (text) {
5113
- agentMessages.push(text);
5114
- options.onToolActivity?.(text.split(`
5247
+ options.onToolActivity?.(`running: ${cmd}`);
5248
+ } else if (type === "item.completed" && item?.type === "command_execution") {
5249
+ const code = item.exit_code;
5250
+ options.onToolActivity?.(code === 0 ? "done" : `exit ${code}`);
5251
+ } else if (type === "item.completed" && item?.type === "reasoning") {
5252
+ const text = (item.text ?? "").trim().replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
5253
+ if (text)
5254
+ options.onToolActivity?.(text);
5255
+ } else if (type === "item.completed" && item?.type === "agent_message") {
5256
+ const text = item.text ?? "";
5257
+ if (text) {
5258
+ agentMessages.push(text);
5259
+ options.onToolActivity?.(text.split(`
5115
5260
  `)[0].slice(0, 80));
5261
+ }
5262
+ } else if (type === "turn.completed") {
5263
+ flushAgentMessages();
5116
5264
  }
5117
- } else if (type === "turn.completed") {
5118
- flushAgentMessages();
5119
- }
5120
- } catch {
5121
- const newLine = `${line}
5265
+ } catch {
5266
+ const newLine = `${line}
5122
5267
  `;
5123
- rawOutput += newLine;
5124
- options.onOutput?.(newLine);
5268
+ rawOutput += newLine;
5269
+ options.onOutput?.(newLine);
5270
+ }
5125
5271
  }
5126
- }
5127
- });
5128
- this.process.stderr?.on("data", (chunk) => {
5129
- const text = chunk.toString();
5130
- errorOutput += text;
5131
- log.debug("sandboxed codex stderr", { text: text.slice(0, 500) });
5132
- });
5133
- this.process.on("close", (code) => {
5134
- this.process = null;
5135
- flushAgentMessages();
5136
- if (this.aborted) {
5272
+ });
5273
+ this.process.stderr?.on("data", (chunk) => {
5274
+ const text = chunk.toString();
5275
+ errorOutput += text;
5276
+ log.debug("sandboxed codex stderr", { text: text.slice(0, 500) });
5277
+ });
5278
+ this.process.on("close", (code) => {
5279
+ this.process = null;
5280
+ flushAgentMessages();
5281
+ if (this.aborted) {
5282
+ resolve2({
5283
+ success: false,
5284
+ output: rawOutput,
5285
+ error: "Aborted by user",
5286
+ exitCode: code ?? 143
5287
+ });
5288
+ return;
5289
+ }
5290
+ if (code === 0) {
5291
+ resolve2({ success: true, output: rawOutput, exitCode: 0 });
5292
+ } else {
5293
+ resolve2({
5294
+ success: false,
5295
+ output: rawOutput,
5296
+ error: errorOutput || `sandboxed codex exited with code ${code}`,
5297
+ exitCode: code ?? 1
5298
+ });
5299
+ }
5300
+ });
5301
+ this.process.on("error", (err) => {
5302
+ this.process = null;
5137
5303
  resolve2({
5138
5304
  success: false,
5139
5305
  output: rawOutput,
5140
- error: "Aborted by user",
5141
- exitCode: code ?? 143
5306
+ error: `Failed to spawn docker sandbox: ${err.message}`,
5307
+ exitCode: 1
5142
5308
  });
5143
- return;
5144
- }
5145
- if (code === 0) {
5146
- resolve2({ success: true, output: rawOutput, exitCode: 0 });
5147
- } else {
5148
- resolve2({
5149
- success: false,
5150
- output: rawOutput,
5151
- error: errorOutput || `sandboxed codex exited with code ${code}`,
5152
- exitCode: code ?? 1
5309
+ });
5310
+ if (options.signal) {
5311
+ options.signal.addEventListener("abort", () => {
5312
+ this.abort();
5153
5313
  });
5154
5314
  }
5315
+ this.process.stdin?.write(options.prompt);
5316
+ this.process.stdin?.end();
5155
5317
  });
5156
- this.process.on("error", (err) => {
5157
- this.process = null;
5158
- resolve2({
5159
- success: false,
5160
- output: rawOutput,
5161
- error: `Failed to spawn docker sandbox: ${err.message}`,
5162
- exitCode: 1
5163
- });
5164
- });
5165
- if (options.signal) {
5166
- options.signal.addEventListener("abort", () => {
5167
- this.abort();
5168
- });
5169
- }
5170
- this.process.stdin?.write(options.prompt);
5171
- this.process.stdin?.end();
5172
- });
5318
+ } finally {
5319
+ backup.restore();
5320
+ }
5173
5321
  }
5174
5322
  abort() {
5175
5323
  this.aborted = true;
@@ -6887,7 +7035,7 @@ var init_sprint = __esm(() => {
6887
7035
 
6888
7036
  // src/core/prompt-builder.ts
6889
7037
  import { execSync as execSync7 } from "node:child_process";
6890
- import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
7038
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
6891
7039
  import { join as join13 } from "node:path";
6892
7040
  function buildExecutionPrompt(ctx) {
6893
7041
  const sections = [];
@@ -6965,7 +7113,7 @@ ${learnings}
6965
7113
  const discussionsDir = join13(projectRoot, ".locus", "discussions");
6966
7114
  if (existsSync14(discussionsDir)) {
6967
7115
  try {
6968
- const files = readdirSync3(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
7116
+ const files = readdirSync4(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
6969
7117
  for (const file of files) {
6970
7118
  const content = readFileSafe(join13(discussionsDir, file));
6971
7119
  if (content) {
@@ -7583,8 +7731,8 @@ var init_commands = __esm(() => {
7583
7731
  });
7584
7732
 
7585
7733
  // src/repl/completions.ts
7586
- import { readdirSync as readdirSync4 } from "node:fs";
7587
- import { basename as basename2, dirname as dirname3, join as join14 } from "node:path";
7734
+ import { readdirSync as readdirSync5 } from "node:fs";
7735
+ import { basename as basename2, dirname as dirname4, join as join14 } from "node:path";
7588
7736
 
7589
7737
  class SlashCommandCompletion {
7590
7738
  commands;
@@ -7639,9 +7787,9 @@ class FilePathCompletion {
7639
7787
  }
7640
7788
  findMatches(partial) {
7641
7789
  try {
7642
- const dir = partial.includes("/") ? join14(this.projectRoot, dirname3(partial)) : this.projectRoot;
7790
+ const dir = partial.includes("/") ? join14(this.projectRoot, dirname4(partial)) : this.projectRoot;
7643
7791
  const prefix = basename2(partial);
7644
- const entries = readdirSync4(dir, { withFileTypes: true });
7792
+ const entries = readdirSync5(dir, { withFileTypes: true });
7645
7793
  return entries.filter((e) => {
7646
7794
  if (e.name.startsWith("."))
7647
7795
  return false;
@@ -7650,7 +7798,7 @@ class FilePathCompletion {
7650
7798
  return e.name.startsWith(prefix);
7651
7799
  }).map((e) => {
7652
7800
  const name = e.isDirectory() ? `${e.name}/` : e.name;
7653
- return partial.includes("/") ? `${dirname3(partial)}/${name}` : name;
7801
+ return partial.includes("/") ? `${dirname4(partial)}/${name}` : name;
7654
7802
  }).slice(0, 20);
7655
7803
  } catch {
7656
7804
  return [];
@@ -7675,8 +7823,8 @@ class CombinedCompletion {
7675
7823
  var init_completions = () => {};
7676
7824
 
7677
7825
  // src/repl/input-history.ts
7678
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
7679
- import { dirname as dirname4, join as join15 } from "node:path";
7826
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
7827
+ import { dirname as dirname5, join as join15 } from "node:path";
7680
7828
 
7681
7829
  class InputHistory {
7682
7830
  entries = [];
@@ -7730,9 +7878,9 @@ class InputHistory {
7730
7878
  }
7731
7879
  save() {
7732
7880
  try {
7733
- const dir = dirname4(this.filePath);
7881
+ const dir = dirname5(this.filePath);
7734
7882
  if (!existsSync15(dir)) {
7735
- mkdirSync9(dir, { recursive: true });
7883
+ mkdirSync10(dir, { recursive: true });
7736
7884
  }
7737
7885
  const content = this.entries.map((e) => this.escape(e)).join(`
7738
7886
  `);
@@ -7763,8 +7911,8 @@ var init_model_config = __esm(() => {
7763
7911
  // src/repl/session-manager.ts
7764
7912
  import {
7765
7913
  existsSync as existsSync16,
7766
- mkdirSync as mkdirSync10,
7767
- readdirSync as readdirSync5,
7914
+ mkdirSync as mkdirSync11,
7915
+ readdirSync as readdirSync6,
7768
7916
  readFileSync as readFileSync11,
7769
7917
  unlinkSync as unlinkSync3,
7770
7918
  writeFileSync as writeFileSync7
@@ -7776,7 +7924,7 @@ class SessionManager {
7776
7924
  constructor(projectRoot) {
7777
7925
  this.sessionsDir = join16(projectRoot, ".locus", "sessions");
7778
7926
  if (!existsSync16(this.sessionsDir)) {
7779
- mkdirSync10(this.sessionsDir, { recursive: true });
7927
+ mkdirSync11(this.sessionsDir, { recursive: true });
7780
7928
  }
7781
7929
  }
7782
7930
  create(options) {
@@ -7901,7 +8049,7 @@ class SessionManager {
7901
8049
  }
7902
8050
  listSessionFiles() {
7903
8051
  try {
7904
- return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
8052
+ return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
7905
8053
  } catch {
7906
8054
  return [];
7907
8055
  }
@@ -8945,12 +9093,12 @@ var init_conflict = __esm(() => {
8945
9093
  // src/core/run-state.ts
8946
9094
  import {
8947
9095
  existsSync as existsSync18,
8948
- mkdirSync as mkdirSync11,
9096
+ mkdirSync as mkdirSync12,
8949
9097
  readFileSync as readFileSync12,
8950
9098
  unlinkSync as unlinkSync4,
8951
9099
  writeFileSync as writeFileSync8
8952
9100
  } from "node:fs";
8953
- import { dirname as dirname5, join as join18 } from "node:path";
9101
+ import { dirname as dirname6, join as join18 } from "node:path";
8954
9102
  function getRunStatePath(projectRoot) {
8955
9103
  return join18(projectRoot, ".locus", "run-state.json");
8956
9104
  }
@@ -8967,9 +9115,9 @@ function loadRunState(projectRoot) {
8967
9115
  }
8968
9116
  function saveRunState(projectRoot, state) {
8969
9117
  const path = getRunStatePath(projectRoot);
8970
- const dir = dirname5(path);
9118
+ const dir = dirname6(path);
8971
9119
  if (!existsSync18(dir)) {
8972
- mkdirSync11(dir, { recursive: true });
9120
+ mkdirSync12(dir, { recursive: true });
8973
9121
  }
8974
9122
  writeFileSync8(path, `${JSON.stringify(state, null, 2)}
8975
9123
  `, "utf-8");
@@ -9117,7 +9265,7 @@ var init_shutdown = __esm(() => {
9117
9265
 
9118
9266
  // src/core/worktree.ts
9119
9267
  import { execSync as execSync13 } from "node:child_process";
9120
- import { existsSync as existsSync19, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
9268
+ import { existsSync as existsSync19, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
9121
9269
  import { join as join19 } from "node:path";
9122
9270
  function git4(args, cwd) {
9123
9271
  return execSync13(`git ${args}`, {
@@ -9207,7 +9355,7 @@ function listWorktrees(projectRoot) {
9207
9355
  if (!existsSync19(worktreeDir)) {
9208
9356
  return [];
9209
9357
  }
9210
- const entries = readdirSync6(worktreeDir).filter((entry) => entry.startsWith("issue-"));
9358
+ const entries = readdirSync7(worktreeDir).filter((entry) => entry.startsWith("issue-"));
9211
9359
  const gitWorktreeList = gitSafe3("worktree list --porcelain", projectRoot);
9212
9360
  const activeWorktrees = new Set;
9213
9361
  if (gitWorktreeList) {
@@ -10062,8 +10210,8 @@ __export(exports_plan, {
10062
10210
  });
10063
10211
  import {
10064
10212
  existsSync as existsSync20,
10065
- mkdirSync as mkdirSync12,
10066
- readdirSync as readdirSync7,
10213
+ mkdirSync as mkdirSync13,
10214
+ readdirSync as readdirSync8,
10067
10215
  readFileSync as readFileSync13,
10068
10216
  writeFileSync as writeFileSync9
10069
10217
  } from "node:fs";
@@ -10103,7 +10251,7 @@ function getPlansDir(projectRoot) {
10103
10251
  function ensurePlansDir(projectRoot) {
10104
10252
  const dir = getPlansDir(projectRoot);
10105
10253
  if (!existsSync20(dir)) {
10106
- mkdirSync12(dir, { recursive: true });
10254
+ mkdirSync13(dir, { recursive: true });
10107
10255
  }
10108
10256
  return dir;
10109
10257
  }
@@ -10114,7 +10262,7 @@ function loadPlanFile(projectRoot, id) {
10114
10262
  const dir = getPlansDir(projectRoot);
10115
10263
  if (!existsSync20(dir))
10116
10264
  return null;
10117
- const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
10265
+ const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
10118
10266
  const match = files.find((f) => f.startsWith(id));
10119
10267
  if (!match)
10120
10268
  return null;
@@ -10170,7 +10318,7 @@ function handleListPlans(projectRoot) {
10170
10318
  `);
10171
10319
  return;
10172
10320
  }
10173
- const files = readdirSync7(dir).filter((f) => f.endsWith(".json")).sort().reverse();
10321
+ const files = readdirSync8(dir).filter((f) => f.endsWith(".json")).sort().reverse();
10174
10322
  if (files.length === 0) {
10175
10323
  process.stderr.write(`${dim("No saved plans yet.")}
10176
10324
  `);
@@ -11198,8 +11346,8 @@ __export(exports_discuss, {
11198
11346
  });
11199
11347
  import {
11200
11348
  existsSync as existsSync22,
11201
- mkdirSync as mkdirSync13,
11202
- readdirSync as readdirSync8,
11349
+ mkdirSync as mkdirSync14,
11350
+ readdirSync as readdirSync9,
11203
11351
  readFileSync as readFileSync15,
11204
11352
  unlinkSync as unlinkSync5,
11205
11353
  writeFileSync as writeFileSync10
@@ -11231,7 +11379,7 @@ function getDiscussionsDir(projectRoot) {
11231
11379
  function ensureDiscussionsDir(projectRoot) {
11232
11380
  const dir = getDiscussionsDir(projectRoot);
11233
11381
  if (!existsSync22(dir)) {
11234
- mkdirSync13(dir, { recursive: true });
11382
+ mkdirSync14(dir, { recursive: true });
11235
11383
  }
11236
11384
  return dir;
11237
11385
  }
@@ -11270,7 +11418,7 @@ function listDiscussions(projectRoot) {
11270
11418
  `);
11271
11419
  return;
11272
11420
  }
11273
- const files = readdirSync8(dir).filter((f) => f.endsWith(".md")).sort().reverse();
11421
+ const files = readdirSync9(dir).filter((f) => f.endsWith(".md")).sort().reverse();
11274
11422
  if (files.length === 0) {
11275
11423
  process.stderr.write(`${dim("No discussions yet.")}
11276
11424
  `);
@@ -11305,7 +11453,7 @@ function showDiscussion(projectRoot, id) {
11305
11453
  `);
11306
11454
  return;
11307
11455
  }
11308
- const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
11456
+ const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
11309
11457
  const match = files.find((f) => f.startsWith(id));
11310
11458
  if (!match) {
11311
11459
  process.stderr.write(`${red("✗")} Discussion "${id}" not found.
@@ -11328,7 +11476,7 @@ function deleteDiscussion(projectRoot, id) {
11328
11476
  `);
11329
11477
  return;
11330
11478
  }
11331
- const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
11479
+ const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
11332
11480
  const match = files.find((f) => f.startsWith(id));
11333
11481
  if (!match) {
11334
11482
  process.stderr.write(`${red("✗")} Discussion "${id}" not found.
@@ -11353,7 +11501,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
11353
11501
  `);
11354
11502
  return;
11355
11503
  }
11356
- const files = readdirSync8(dir).filter((f) => f.endsWith(".md"));
11504
+ const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
11357
11505
  const match = files.find((f) => f.startsWith(id));
11358
11506
  if (!match) {
11359
11507
  process.stderr.write(`${red("✗")} Discussion "${id}" not found.
@@ -11577,7 +11725,7 @@ __export(exports_artifacts, {
11577
11725
  formatDate: () => formatDate2,
11578
11726
  artifactsCommand: () => artifactsCommand
11579
11727
  });
11580
- import { existsSync as existsSync23, readdirSync as readdirSync9, readFileSync as readFileSync16, statSync as statSync4 } from "node:fs";
11728
+ import { existsSync as existsSync23, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
11581
11729
  import { join as join23 } from "node:path";
11582
11730
  function printHelp5() {
11583
11731
  process.stderr.write(`
@@ -11604,9 +11752,9 @@ function listArtifacts(projectRoot) {
11604
11752
  const dir = getArtifactsDir(projectRoot);
11605
11753
  if (!existsSync23(dir))
11606
11754
  return [];
11607
- return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
11755
+ return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
11608
11756
  const filePath = join23(dir, fileName);
11609
- const stat = statSync4(filePath);
11757
+ const stat = statSync5(filePath);
11610
11758
  return {
11611
11759
  name: fileName.replace(/\.md$/, ""),
11612
11760
  fileName,
@@ -11621,7 +11769,7 @@ function readArtifact(projectRoot, name) {
11621
11769
  const filePath = join23(dir, fileName);
11622
11770
  if (!existsSync23(filePath))
11623
11771
  return null;
11624
- const stat = statSync4(filePath);
11772
+ const stat = statSync5(filePath);
11625
11773
  return {
11626
11774
  content: readFileSync16(filePath, "utf-8"),
11627
11775
  info: {
@@ -11961,7 +12109,12 @@ async function handleAgentLogin(projectRoot, agent) {
11961
12109
  });
11962
12110
  await new Promise((resolve2) => {
11963
12111
  child.on("close", async (code) => {
11964
- await enforceSandboxIgnore(sandboxName, projectRoot);
12112
+ const backup = backupIgnoredFiles(projectRoot);
12113
+ try {
12114
+ await enforceSandboxIgnore(sandboxName, projectRoot);
12115
+ } finally {
12116
+ backup.restore();
12117
+ }
11965
12118
  if (code === 0) {
11966
12119
  process.stderr.write(`
11967
12120
  ${green("✓")} ${agent} session ended. Auth should now be persisted in the sandbox.
@@ -12408,7 +12561,12 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
12408
12561
  if (provider === "codex") {
12409
12562
  await ensureCodexInSandbox(sandboxName);
12410
12563
  }
12411
- await enforceSandboxIgnore(sandboxName, projectRoot);
12564
+ const backup = backupIgnoredFiles(projectRoot);
12565
+ try {
12566
+ await enforceSandboxIgnore(sandboxName, projectRoot);
12567
+ } finally {
12568
+ backup.restore();
12569
+ }
12412
12570
  return true;
12413
12571
  }
12414
12572
  async function ensurePackageManagerInSandbox(sandboxName, pm) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locusai/cli",
3
- "version": "0.21.5",
3
+ "version": "0.21.6",
4
4
  "description": "GitHub-native AI engineering assistant",
5
5
  "type": "module",
6
6
  "bin": {