@locusai/cli 0.18.0 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/locus.js +779 -526
  2. package/package.json +1 -1
package/bin/locus.js CHANGED
@@ -807,6 +807,14 @@ var init_rate_limiter = __esm(() => {
807
807
  });
808
808
 
809
809
  // src/display/progress.ts
810
+ var exports_progress = {};
811
+ __export(exports_progress, {
812
+ renderTaskStatus: () => renderTaskStatus,
813
+ progressBar: () => progressBar,
814
+ formatDuration: () => formatDuration,
815
+ createTimer: () => createTimer,
816
+ Spinner: () => Spinner
817
+ });
810
818
  function progressBar(current, total, options = {}) {
811
819
  const { width = 30, showPercent = true, showCount = true, label } = options;
812
820
  const percent = total > 0 ? current / total : 0;
@@ -903,6 +911,149 @@ var init_progress = __esm(() => {
903
911
  SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
904
912
  });
905
913
 
914
+ // src/core/sandbox.ts
915
+ var exports_sandbox = {};
916
+ __export(exports_sandbox, {
917
+ resolveSandboxMode: () => resolveSandboxMode,
918
+ displaySandboxWarning: () => displaySandboxWarning,
919
+ detectSandboxSupport: () => detectSandboxSupport
920
+ });
921
+ import { execFile } from "node:child_process";
922
+ import { createInterface } from "node:readline";
923
+ async function detectSandboxSupport() {
924
+ if (cachedStatus)
925
+ return cachedStatus;
926
+ const log = getLogger();
927
+ log.debug("Detecting Docker sandbox support...");
928
+ const status = await runDetection();
929
+ cachedStatus = status;
930
+ if (status.available) {
931
+ log.verbose("Docker sandbox support detected");
932
+ } else {
933
+ log.verbose(`Docker sandbox not available: ${status.reason}`);
934
+ }
935
+ return status;
936
+ }
937
+ function runDetection() {
938
+ return new Promise((resolve) => {
939
+ let settled = false;
940
+ const child = execFile("docker", ["sandbox", "ls"], { timeout: TIMEOUT_MS }, (error, _stdout, stderr) => {
941
+ if (settled)
942
+ return;
943
+ settled = true;
944
+ if (!error) {
945
+ resolve({ available: true });
946
+ return;
947
+ }
948
+ const code = error.code;
949
+ if (code === "ENOENT") {
950
+ resolve({ available: false, reason: "Docker is not installed" });
951
+ return;
952
+ }
953
+ if (error.killed) {
954
+ resolve({ available: false, reason: "Docker is not responding" });
955
+ return;
956
+ }
957
+ const stderrStr = (stderr ?? "").toLowerCase();
958
+ if (stderrStr.includes("unknown") || stderrStr.includes("not a docker command") || stderrStr.includes("is not a docker command")) {
959
+ resolve({
960
+ available: false,
961
+ reason: "Docker Desktop 4.58+ with sandbox support required"
962
+ });
963
+ return;
964
+ }
965
+ resolve({
966
+ available: false,
967
+ reason: "Docker Desktop 4.58+ with sandbox support required"
968
+ });
969
+ });
970
+ child.on?.("error", (err) => {
971
+ if (settled)
972
+ return;
973
+ settled = true;
974
+ if (err.code === "ENOENT") {
975
+ resolve({ available: false, reason: "Docker is not installed" });
976
+ } else {
977
+ resolve({
978
+ available: false,
979
+ reason: "Docker Desktop 4.58+ with sandbox support required"
980
+ });
981
+ }
982
+ });
983
+ });
984
+ }
985
+ function resolveSandboxMode(config, flags) {
986
+ if (flags.noSandbox) {
987
+ return "disabled";
988
+ }
989
+ if (flags.sandbox !== undefined) {
990
+ if (flags.sandbox === "require") {
991
+ return "required";
992
+ }
993
+ throw new Error(`Invalid --sandbox value: "${flags.sandbox}". Valid values: require`);
994
+ }
995
+ if (!config.enabled) {
996
+ return "disabled";
997
+ }
998
+ return "auto";
999
+ }
1000
+ async function displaySandboxWarning(mode, status) {
1001
+ if (mode === "required" && !status.available) {
1002
+ process.stderr.write(`
1003
+ ${red("✖")} Docker sandbox required but not available: ${bold(status.reason ?? "Docker Desktop 4.58+ with sandbox support required")}
1004
+ `);
1005
+ process.stderr.write(` Install Docker Desktop 4.58+ or remove --sandbox=require to continue.
1006
+
1007
+ `);
1008
+ process.exit(1);
1009
+ }
1010
+ if (mode === "disabled") {
1011
+ process.stderr.write(`
1012
+ ${yellow("⚠")} ${bold("WARNING:")} Running without sandbox. The AI agent will have unrestricted
1013
+ `);
1014
+ process.stderr.write(` access to your filesystem, network, and environment variables.
1015
+ `);
1016
+ if (process.stdin.isTTY) {
1017
+ process.stderr.write(` Press ${bold("Enter")} to continue or ${bold("Ctrl+C")} to abort.
1018
+ `);
1019
+ await waitForEnter();
1020
+ }
1021
+ process.stderr.write(`
1022
+ `);
1023
+ return false;
1024
+ }
1025
+ if (mode === "auto" && !status.available) {
1026
+ process.stderr.write(`
1027
+ ${yellow("⚠")} Docker sandbox not available. Install Docker Desktop 4.58+ for secure execution.
1028
+ `);
1029
+ process.stderr.write(` Running without sandbox. Use ${dim("--no-sandbox")} to suppress this warning.
1030
+
1031
+ `);
1032
+ return false;
1033
+ }
1034
+ return true;
1035
+ }
1036
+ function waitForEnter() {
1037
+ return new Promise((resolve) => {
1038
+ const rl = createInterface({
1039
+ input: process.stdin,
1040
+ output: process.stderr
1041
+ });
1042
+ rl.once("line", () => {
1043
+ rl.close();
1044
+ resolve();
1045
+ });
1046
+ rl.once("close", () => {
1047
+ resolve();
1048
+ });
1049
+ });
1050
+ }
1051
+ var TIMEOUT_MS = 5000, cachedStatus = null;
1052
+ var init_sandbox = __esm(() => {
1053
+ init_terminal();
1054
+ init_logger();
1055
+ });
1056
+
906
1057
  // src/commands/upgrade.ts
907
1058
  var exports_upgrade = {};
908
1059
  __export(exports_upgrade, {
@@ -1289,6 +1440,9 @@ function updateIssueLabels(number, addLabels, removeLabels, options = {}) {
1289
1440
  }
1290
1441
  gh(args, options);
1291
1442
  }
1443
+ function deleteIssue(number, options = {}) {
1444
+ gh(`issue delete ${number} --yes`, options);
1445
+ }
1292
1446
  function addIssueComment(number, body, options = {}) {
1293
1447
  const cwd = options.cwd ?? process.cwd();
1294
1448
  execFileSync("gh", ["issue", "comment", String(number), "--body", body], {
@@ -1840,7 +1994,8 @@ var init_init = __esm(() => {
1840
1994
  ".locus/logs/",
1841
1995
  ".locus/worktrees/",
1842
1996
  ".locus/artifacts/",
1843
- ".locus/discussions/"
1997
+ ".locus/discussions/",
1998
+ ".locus/tmp/"
1844
1999
  ];
1845
2000
  });
1846
2001
 
@@ -2911,14 +3066,87 @@ var init_stream_renderer = __esm(() => {
2911
3066
  init_terminal();
2912
3067
  });
2913
3068
 
3069
+ // src/repl/clipboard.ts
3070
+ import { execSync as execSync4 } from "node:child_process";
3071
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
3072
+ import { tmpdir } from "node:os";
3073
+ import { join as join9 } from "node:path";
3074
+ function readClipboardImage() {
3075
+ if (process.platform === "darwin") {
3076
+ return readMacOSClipboardImage();
3077
+ }
3078
+ if (process.platform === "linux") {
3079
+ return readLinuxClipboardImage();
3080
+ }
3081
+ return null;
3082
+ }
3083
+ function ensureStableDir() {
3084
+ if (!existsSync10(STABLE_DIR)) {
3085
+ mkdirSync7(STABLE_DIR, { recursive: true });
3086
+ }
3087
+ }
3088
+ function readMacOSClipboardImage() {
3089
+ try {
3090
+ ensureStableDir();
3091
+ const destPath = join9(STABLE_DIR, `clipboard-${Date.now()}.png`);
3092
+ const script = [
3093
+ `set destPath to POSIX file "${destPath}"`,
3094
+ "try",
3095
+ ` set imgData to the clipboard as «class PNGf»`,
3096
+ "on error",
3097
+ " try",
3098
+ ` set imgData to the clipboard as «class TIFF»`,
3099
+ " on error",
3100
+ ` return "no-image"`,
3101
+ " end try",
3102
+ "end try",
3103
+ "set fRef to open for access destPath with write permission",
3104
+ "write imgData to fRef",
3105
+ "close access fRef",
3106
+ `return "ok"`
3107
+ ].join(`
3108
+ `);
3109
+ const result = execSync4("osascript", {
3110
+ input: script,
3111
+ encoding: "utf-8",
3112
+ timeout: 5000,
3113
+ stdio: ["pipe", "pipe", "pipe"]
3114
+ }).trim();
3115
+ if (result === "ok" && existsSync10(destPath)) {
3116
+ return destPath;
3117
+ }
3118
+ } catch {}
3119
+ return null;
3120
+ }
3121
+ function readLinuxClipboardImage() {
3122
+ try {
3123
+ const targets = execSync4("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
3124
+ if (!targets.includes("image/png")) {
3125
+ return null;
3126
+ }
3127
+ ensureStableDir();
3128
+ const destPath = join9(STABLE_DIR, `clipboard-${Date.now()}.png`);
3129
+ execSync4(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
3130
+ if (existsSync10(destPath)) {
3131
+ return destPath;
3132
+ }
3133
+ } catch {}
3134
+ return null;
3135
+ }
3136
+ var STABLE_DIR;
3137
+ var init_clipboard = __esm(() => {
3138
+ STABLE_DIR = join9(tmpdir(), "locus-images");
3139
+ });
3140
+
2914
3141
  // src/repl/image-detect.ts
2915
- import { copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
2916
- import { homedir as homedir3, tmpdir } from "node:os";
2917
- import { basename, extname, join as join9, resolve } from "node:path";
3142
+ import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync8 } from "node:fs";
3143
+ import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
3144
+ import { basename, extname, join as join10, resolve } from "node:path";
2918
3145
  function detectImages(input) {
2919
3146
  const detected = [];
2920
3147
  const byResolved = new Map;
2921
- for (const line of input.split(`
3148
+ const sanitized = input.replace(/!\[Screenshot:[^\]]*\]\(locus:\/\/screenshot-\d+\)/g, "");
3149
+ for (const line of sanitized.split(`
2922
3150
  `)) {
2923
3151
  const trimmed = line.trim();
2924
3152
  if (!trimmed)
@@ -2929,20 +3157,20 @@ function detectImages(input) {
2929
3157
  }
2930
3158
  }
2931
3159
  const quotedPattern = /["']([^"']+\.(?:png|jpg|jpeg|gif|webp|bmp|svg))["']/gi;
2932
- for (const match of input.matchAll(quotedPattern)) {
3160
+ for (const match of sanitized.matchAll(quotedPattern)) {
2933
3161
  if (!match[0] || !match[1])
2934
3162
  continue;
2935
3163
  addIfImage(match[1], match[0], detected, byResolved);
2936
3164
  }
2937
3165
  const escapedPattern = /(?:\/|~\/|\.\/)?(?:[^\s"'\\]|\\ )+\.(?:png|jpg|jpeg|gif|webp|bmp|svg|tiff?)/gi;
2938
- for (const match of input.matchAll(escapedPattern)) {
3166
+ for (const match of sanitized.matchAll(escapedPattern)) {
2939
3167
  if (!match[0])
2940
3168
  continue;
2941
3169
  const path = match[0].replace(/\\ /g, " ");
2942
3170
  addIfImage(path, match[0], detected, byResolved);
2943
3171
  }
2944
3172
  const plainPattern = /(?:\/|~\/|\.\/)[^\s"']+\.(?:png|jpg|jpeg|gif|webp|bmp|svg|tiff?)/gi;
2945
- for (const match of input.matchAll(plainPattern)) {
3173
+ for (const match of sanitized.matchAll(plainPattern)) {
2946
3174
  if (!match[0])
2947
3175
  continue;
2948
3176
  addIfImage(match[0], match[0], detected, byResolved);
@@ -3006,13 +3234,28 @@ function collectReferencedAttachments(input, attachments) {
3006
3234
  const selected = attachments.filter((attachment) => ids.has(attachment.id));
3007
3235
  return dedupeByResolvedPath(selected);
3008
3236
  }
3237
+ function relocateImages(images, projectRoot) {
3238
+ const targetDir = join10(projectRoot, ".locus", "tmp", "images");
3239
+ for (const img of images) {
3240
+ if (!img.exists)
3241
+ continue;
3242
+ try {
3243
+ if (!existsSync11(targetDir)) {
3244
+ mkdirSync8(targetDir, { recursive: true });
3245
+ }
3246
+ const dest = join10(targetDir, basename(img.stablePath));
3247
+ copyFileSync(img.stablePath, dest);
3248
+ img.stablePath = dest;
3249
+ } catch {}
3250
+ }
3251
+ }
3009
3252
  function addIfImage(rawPath, rawMatch, detected, byResolved) {
3010
3253
  const ext = extname(rawPath).toLowerCase();
3011
3254
  if (!IMAGE_EXTENSIONS.has(ext))
3012
3255
  return;
3013
3256
  let resolved = stripQuotes(rawPath).replace(/\\ /g, " ");
3014
3257
  if (resolved.startsWith("~/")) {
3015
- resolved = join9(homedir3(), resolved.slice(2));
3258
+ resolved = join10(homedir3(), resolved.slice(2));
3016
3259
  }
3017
3260
  resolved = resolve(resolved);
3018
3261
  const existing = byResolved.get(resolved);
@@ -3025,7 +3268,7 @@ function addIfImage(rawPath, rawMatch, detected, byResolved) {
3025
3268
  ]);
3026
3269
  return;
3027
3270
  }
3028
- const exists = existsSync10(resolved);
3271
+ const exists = existsSync11(resolved);
3029
3272
  let stablePath = resolved;
3030
3273
  if (exists) {
3031
3274
  stablePath = copyToStable(resolved);
@@ -3079,17 +3322,17 @@ function dedupeByResolvedPath(images) {
3079
3322
  }
3080
3323
  function copyToStable(sourcePath) {
3081
3324
  try {
3082
- if (!existsSync10(STABLE_DIR)) {
3083
- mkdirSync7(STABLE_DIR, { recursive: true });
3325
+ if (!existsSync11(STABLE_DIR2)) {
3326
+ mkdirSync8(STABLE_DIR2, { recursive: true });
3084
3327
  }
3085
- const dest = join9(STABLE_DIR, `${Date.now()}-${basename(sourcePath)}`);
3328
+ const dest = join10(STABLE_DIR2, `${Date.now()}-${basename(sourcePath)}`);
3086
3329
  copyFileSync(sourcePath, dest);
3087
3330
  return dest;
3088
3331
  } catch {
3089
3332
  return sourcePath;
3090
3333
  }
3091
3334
  }
3092
- var IMAGE_EXTENSIONS, STABLE_DIR, PLACEHOLDER_SCHEME = "locus://screenshot-", PLACEHOLDER_ID_PATTERN;
3335
+ var IMAGE_EXTENSIONS, STABLE_DIR2, PLACEHOLDER_SCHEME = "locus://screenshot-", PLACEHOLDER_ID_PATTERN;
3093
3336
  var init_image_detect = __esm(() => {
3094
3337
  IMAGE_EXTENSIONS = new Set([
3095
3338
  ".png",
@@ -3102,7 +3345,7 @@ var init_image_detect = __esm(() => {
3102
3345
  ".tif",
3103
3346
  ".tiff"
3104
3347
  ]);
3105
- STABLE_DIR = join9(tmpdir(), "locus-images");
3348
+ STABLE_DIR2 = join10(tmpdir2(), "locus-images");
3106
3349
  PLACEHOLDER_ID_PATTERN = /\(locus:\/\/screenshot-(\d+)\)/g;
3107
3350
  });
3108
3351
 
@@ -3521,6 +3764,15 @@ ${dim("Press Ctrl+C again to exit")}\r
3521
3764
  pasteBuffer += pending.slice(0, endIdx);
3522
3765
  pending = pending.slice(endIdx + PASTE_END.length);
3523
3766
  isPasting = false;
3767
+ if (pasteBuffer.trim() === "") {
3768
+ const clipboardImagePath = readClipboardImage();
3769
+ if (clipboardImagePath) {
3770
+ insertText(clipboardImagePath);
3771
+ pasteBuffer = "";
3772
+ render();
3773
+ continue;
3774
+ }
3775
+ }
3524
3776
  insertText(normalizeLineEndings(pasteBuffer));
3525
3777
  pasteBuffer = "";
3526
3778
  render();
@@ -3812,6 +4064,7 @@ var CSI = "\x1B[", SAVE_CURSOR = "\x1B7", RESTORE_CURSOR = "\x1B8", ENABLE_BRACK
3812
4064
  `, ESC = "\x1B", BACKSPACE = "", SEQ_LEFT, SEQ_RIGHT, SEQ_UP, SEQ_DOWN, SEQ_HOME, SEQ_END, SEQ_HOME_1, SEQ_END_4, SEQ_HOME_O = "\x1BOH", SEQ_END_O = "\x1BOF", SEQ_DELETE, SEQ_WORD_LEFT, SEQ_WORD_RIGHT, SEQ_SHIFT_LEFT, SEQ_SHIFT_RIGHT, SEQ_META_LEFT, SEQ_META_RIGHT, SEQ_META_SHIFT_LEFT, SEQ_META_SHIFT_RIGHT, SEQ_SHIFT_ENTER_CSI_U, SEQ_SHIFT_ENTER_MODIFY, SEQ_SHIFT_ENTER_TILDE, SEQ_ALT_ENTER, CONTROL_SEQUENCES;
3813
4065
  var init_input_handler = __esm(() => {
3814
4066
  init_terminal();
4067
+ init_clipboard();
3815
4068
  init_image_detect();
3816
4069
  ENABLE_BRACKETED_PASTE = `${CSI}?2004h`;
3817
4070
  DISABLE_BRACKETED_PASTE = `${CSI}?2004l`;
@@ -3874,7 +4127,7 @@ __export(exports_claude, {
3874
4127
  buildClaudeArgs: () => buildClaudeArgs,
3875
4128
  ClaudeRunner: () => ClaudeRunner
3876
4129
  });
3877
- import { execSync as execSync4, spawn as spawn2 } from "node:child_process";
4130
+ import { execSync as execSync5, spawn as spawn2 } from "node:child_process";
3878
4131
  function buildClaudeArgs(options) {
3879
4132
  const args = [
3880
4133
  "--dangerously-skip-permissions",
@@ -3895,7 +4148,7 @@ class ClaudeRunner {
3895
4148
  aborted = false;
3896
4149
  async isAvailable() {
3897
4150
  try {
3898
- execSync4("claude --version", {
4151
+ execSync5("claude --version", {
3899
4152
  encoding: "utf-8",
3900
4153
  stdio: ["pipe", "pipe", "pipe"]
3901
4154
  });
@@ -3906,7 +4159,7 @@ class ClaudeRunner {
3906
4159
  }
3907
4160
  async getVersion() {
3908
4161
  try {
3909
- const output = execSync4("claude --version", {
4162
+ const output = execSync5("claude --version", {
3910
4163
  encoding: "utf-8",
3911
4164
  stdio: ["pipe", "pipe", "pipe"]
3912
4165
  }).trim();
@@ -4071,11 +4324,11 @@ var init_claude = __esm(() => {
4071
4324
 
4072
4325
  // src/core/sandbox-ignore.ts
4073
4326
  import { exec } from "node:child_process";
4074
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
4075
- import { join as join10 } from "node:path";
4327
+ import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
4328
+ import { join as join11 } from "node:path";
4076
4329
  import { promisify } from "node:util";
4077
4330
  function parseIgnoreFile(filePath) {
4078
- if (!existsSync11(filePath))
4331
+ if (!existsSync12(filePath))
4079
4332
  return [];
4080
4333
  const content = readFileSync8(filePath, "utf-8");
4081
4334
  const rules = [];
@@ -4122,7 +4375,7 @@ function buildCleanupScript(rules, workspacePath) {
4122
4375
  }
4123
4376
  async function enforceSandboxIgnore(sandboxName, projectRoot) {
4124
4377
  const log = getLogger();
4125
- const ignorePath = join10(projectRoot, ".sandboxignore");
4378
+ const ignorePath = join11(projectRoot, ".sandboxignore");
4126
4379
  const rules = parseIgnoreFile(ignorePath);
4127
4380
  if (rules.length === 0)
4128
4381
  return;
@@ -4151,19 +4404,19 @@ var init_sandbox_ignore = __esm(() => {
4151
4404
 
4152
4405
  // src/core/run-state.ts
4153
4406
  import {
4154
- existsSync as existsSync12,
4155
- mkdirSync as mkdirSync8,
4407
+ existsSync as existsSync13,
4408
+ mkdirSync as mkdirSync9,
4156
4409
  readFileSync as readFileSync9,
4157
4410
  unlinkSync as unlinkSync3,
4158
4411
  writeFileSync as writeFileSync6
4159
4412
  } from "node:fs";
4160
- import { dirname as dirname3, join as join11 } from "node:path";
4413
+ import { dirname as dirname3, join as join12 } from "node:path";
4161
4414
  function getRunStatePath(projectRoot) {
4162
- return join11(projectRoot, ".locus", "run-state.json");
4415
+ return join12(projectRoot, ".locus", "run-state.json");
4163
4416
  }
4164
4417
  function loadRunState(projectRoot) {
4165
4418
  const path = getRunStatePath(projectRoot);
4166
- if (!existsSync12(path))
4419
+ if (!existsSync13(path))
4167
4420
  return null;
4168
4421
  try {
4169
4422
  return JSON.parse(readFileSync9(path, "utf-8"));
@@ -4175,15 +4428,15 @@ function loadRunState(projectRoot) {
4175
4428
  function saveRunState(projectRoot, state) {
4176
4429
  const path = getRunStatePath(projectRoot);
4177
4430
  const dir = dirname3(path);
4178
- if (!existsSync12(dir)) {
4179
- mkdirSync8(dir, { recursive: true });
4431
+ if (!existsSync13(dir)) {
4432
+ mkdirSync9(dir, { recursive: true });
4180
4433
  }
4181
4434
  writeFileSync6(path, `${JSON.stringify(state, null, 2)}
4182
4435
  `, "utf-8");
4183
4436
  }
4184
4437
  function clearRunState(projectRoot) {
4185
4438
  const path = getRunStatePath(projectRoot);
4186
- if (existsSync12(path)) {
4439
+ if (existsSync13(path)) {
4187
4440
  unlinkSync3(path);
4188
4441
  }
4189
4442
  }
@@ -4257,7 +4510,7 @@ var init_run_state = __esm(() => {
4257
4510
  });
4258
4511
 
4259
4512
  // src/core/shutdown.ts
4260
- import { execSync as execSync5 } from "node:child_process";
4513
+ import { execSync as execSync6 } from "node:child_process";
4261
4514
  function registerActiveSandbox(name) {
4262
4515
  activeSandboxes.add(name);
4263
4516
  }
@@ -4267,7 +4520,7 @@ function unregisterActiveSandbox(name) {
4267
4520
  function cleanupActiveSandboxes() {
4268
4521
  for (const name of activeSandboxes) {
4269
4522
  try {
4270
- execSync5(`docker sandbox rm ${name}`, { timeout: 1e4 });
4523
+ execSync6(`docker sandbox rm ${name}`, { timeout: 1e4 });
4271
4524
  } catch {}
4272
4525
  }
4273
4526
  activeSandboxes.clear();
@@ -4340,7 +4593,7 @@ var init_shutdown = __esm(() => {
4340
4593
  });
4341
4594
 
4342
4595
  // src/ai/claude-sandbox.ts
4343
- import { execSync as execSync6, spawn as spawn3 } from "node:child_process";
4596
+ import { execSync as execSync7, spawn as spawn3 } from "node:child_process";
4344
4597
 
4345
4598
  class SandboxedClaudeRunner {
4346
4599
  name = "claude-sandboxed";
@@ -4556,7 +4809,7 @@ class SandboxedClaudeRunner {
4556
4809
  sandboxName: this.sandboxName
4557
4810
  });
4558
4811
  try {
4559
- execSync6(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
4812
+ execSync7(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
4560
4813
  } catch {}
4561
4814
  }
4562
4815
  }
@@ -4570,7 +4823,7 @@ class SandboxedClaudeRunner {
4570
4823
  const log = getLogger();
4571
4824
  log.debug("Destroying sandbox", { sandboxName: this.sandboxName });
4572
4825
  try {
4573
- execSync6(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
4826
+ execSync7(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
4574
4827
  } catch {}
4575
4828
  unregisterActiveSandbox(this.sandboxName);
4576
4829
  this.sandboxName = null;
@@ -4582,7 +4835,7 @@ class SandboxedClaudeRunner {
4582
4835
  const log = getLogger();
4583
4836
  log.debug("Cleaning up sandbox", { sandboxName: this.sandboxName });
4584
4837
  try {
4585
- execSync6(`docker sandbox rm ${this.sandboxName}`, {
4838
+ execSync7(`docker sandbox rm ${this.sandboxName}`, {
4586
4839
  timeout: 60000
4587
4840
  });
4588
4841
  } catch {}
@@ -4658,7 +4911,7 @@ var init_claude_sandbox = __esm(() => {
4658
4911
  });
4659
4912
 
4660
4913
  // src/ai/codex.ts
4661
- import { execSync as execSync7, spawn as spawn4 } from "node:child_process";
4914
+ import { execSync as execSync8, spawn as spawn4 } from "node:child_process";
4662
4915
  function buildCodexArgs(model) {
4663
4916
  const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
4664
4917
  if (model) {
@@ -4674,7 +4927,7 @@ class CodexRunner {
4674
4927
  aborted = false;
4675
4928
  async isAvailable() {
4676
4929
  try {
4677
- execSync7("codex --version", {
4930
+ execSync8("codex --version", {
4678
4931
  encoding: "utf-8",
4679
4932
  stdio: ["pipe", "pipe", "pipe"]
4680
4933
  });
@@ -4685,7 +4938,7 @@ class CodexRunner {
4685
4938
  }
4686
4939
  async getVersion() {
4687
4940
  try {
4688
- const output = execSync7("codex --version", {
4941
+ const output = execSync8("codex --version", {
4689
4942
  encoding: "utf-8",
4690
4943
  stdio: ["pipe", "pipe", "pipe"]
4691
4944
  }).trim();
@@ -4833,7 +5086,7 @@ var init_codex = __esm(() => {
4833
5086
  });
4834
5087
 
4835
5088
  // src/ai/codex-sandbox.ts
4836
- import { execSync as execSync8, spawn as spawn5 } from "node:child_process";
5089
+ import { execSync as execSync9, spawn as spawn5 } from "node:child_process";
4837
5090
 
4838
5091
  class SandboxedCodexRunner {
4839
5092
  name = "codex-sandboxed";
@@ -5076,7 +5329,7 @@ class SandboxedCodexRunner {
5076
5329
  sandboxName: this.sandboxName
5077
5330
  });
5078
5331
  try {
5079
- execSync8(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
5332
+ execSync9(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
5080
5333
  } catch {}
5081
5334
  }
5082
5335
  }
@@ -5090,7 +5343,7 @@ class SandboxedCodexRunner {
5090
5343
  const log = getLogger();
5091
5344
  log.debug("Destroying sandbox", { sandboxName: this.sandboxName });
5092
5345
  try {
5093
- execSync8(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
5346
+ execSync9(`docker sandbox rm ${this.sandboxName}`, { timeout: 60000 });
5094
5347
  } catch {}
5095
5348
  unregisterActiveSandbox(this.sandboxName);
5096
5349
  this.sandboxName = null;
@@ -5102,7 +5355,7 @@ class SandboxedCodexRunner {
5102
5355
  const log = getLogger();
5103
5356
  log.debug("Cleaning up sandbox", { sandboxName: this.sandboxName });
5104
5357
  try {
5105
- execSync8(`docker sandbox rm ${this.sandboxName}`, {
5358
+ execSync9(`docker sandbox rm ${this.sandboxName}`, {
5106
5359
  timeout: 60000
5107
5360
  });
5108
5361
  } catch {}
@@ -5493,7 +5746,7 @@ var exports_issue = {};
5493
5746
  __export(exports_issue, {
5494
5747
  issueCommand: () => issueCommand
5495
5748
  });
5496
- import { createInterface } from "node:readline";
5749
+ import { createInterface as createInterface2 } from "node:readline";
5497
5750
  function parseIssueArgs(args) {
5498
5751
  const flags = {};
5499
5752
  const positional = [];
@@ -5572,6 +5825,10 @@ async function issueCommand(projectRoot, args) {
5572
5825
  case "close":
5573
5826
  await issueClose(projectRoot, parsed);
5574
5827
  break;
5828
+ case "delete":
5829
+ case "rm":
5830
+ await issueDelete(projectRoot, parsed);
5831
+ break;
5575
5832
  default:
5576
5833
  if (/^\d+$/.test(parsed.subcommand)) {
5577
5834
  parsed.positional.unshift(parsed.subcommand);
@@ -5699,21 +5956,26 @@ ${dim("────────────────────────
5699
5956
  }
5700
5957
  }
5701
5958
  function buildIssueCreationPrompt(userRequest) {
5702
- return [
5703
- "You are a task planner for a software development team.",
5704
- "Given a user request, create a well-structured GitHub issue.",
5705
- "",
5706
- "Output ONLY a valid JSON object with exactly these fields:",
5707
- '- "title": A concise, actionable issue title (max 80 characters)',
5708
- '- "body": Detailed markdown description with context, acceptance criteria, and technical notes',
5709
- '- "priority": One of: critical, high, medium, low',
5710
- '- "type": One of: feature, bug, chore, refactor, docs',
5711
- "",
5712
- `User request: ${userRequest}`,
5713
- "",
5714
- "Output ONLY the JSON object. No explanations, no code execution, no other text."
5715
- ].join(`
5716
- `);
5959
+ return `<role>
5960
+ You are a task planner for a software development team.
5961
+ Given a user request, create a well-structured GitHub issue.
5962
+ </role>
5963
+
5964
+ <output-format>
5965
+ Output ONLY a valid JSON object with exactly these fields:
5966
+ - "title": A concise, actionable issue title (max 80 characters)
5967
+ - "body": Detailed markdown description with context, acceptance criteria, and technical notes
5968
+ - "priority": One of: critical, high, medium, low
5969
+ - "type": One of: feature, bug, chore, refactor, docs
5970
+ </output-format>
5971
+
5972
+ <user-request>
5973
+ ${userRequest}
5974
+ </user-request>
5975
+
5976
+ <constraints>
5977
+ Output ONLY the JSON object. No explanations, no code execution, no other text.
5978
+ </constraints>`;
5717
5979
  }
5718
5980
  function extractJSON(text) {
5719
5981
  const codeBlock = text.match(/```(?:json)?\s*([\s\S]*?)```/);
@@ -5726,7 +5988,7 @@ function extractJSON(text) {
5726
5988
  }
5727
5989
  function askQuestion(question) {
5728
5990
  return new Promise((resolve2) => {
5729
- const rl = createInterface({
5991
+ const rl = createInterface2({
5730
5992
  input: process.stdin,
5731
5993
  output: process.stderr
5732
5994
  });
@@ -5977,6 +6239,43 @@ async function issueClose(projectRoot, parsed) {
5977
6239
  process.exit(1);
5978
6240
  }
5979
6241
  }
6242
+ async function issueDelete(projectRoot, parsed) {
6243
+ const issueNumbers = parsed.positional.filter((a) => /^\d+$/.test(a)).map(Number);
6244
+ if (issueNumbers.length === 0) {
6245
+ process.stderr.write(`${red("✗")} No issue numbers provided.
6246
+ `);
6247
+ process.stderr.write(` Usage: ${bold("locus issue delete <number...>")}
6248
+ `);
6249
+ process.exit(1);
6250
+ }
6251
+ const label = issueNumbers.length === 1 ? `issue #${issueNumbers[0]}` : `${issueNumbers.length} issues (#${issueNumbers.join(", #")})`;
6252
+ process.stderr.write(`${yellow("⚠")} This will ${bold("permanently delete")} ${label}.
6253
+ `);
6254
+ const answer = await askQuestion(`${cyan("?")} Are you sure? ${dim("[y/N]")} `);
6255
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
6256
+ process.stderr.write(`${yellow("○")} Cancelled.
6257
+ `);
6258
+ return;
6259
+ }
6260
+ let failed = 0;
6261
+ for (const num of issueNumbers) {
6262
+ process.stderr.write(`${cyan("●")} Deleting issue #${num}...`);
6263
+ try {
6264
+ deleteIssue(num, { cwd: projectRoot });
6265
+ process.stderr.write(`\r${green("✓")} Deleted issue #${num}
6266
+ `);
6267
+ } catch (e) {
6268
+ process.stderr.write(`\r${red("✗")} #${num}: ${e.message}
6269
+ `);
6270
+ failed++;
6271
+ }
6272
+ }
6273
+ if (issueNumbers.length > 1 && failed === 0) {
6274
+ process.stderr.write(`
6275
+ ${green("✓")} All ${issueNumbers.length} issues deleted.
6276
+ `);
6277
+ }
6278
+ }
5980
6279
  function formatPriority(labels) {
5981
6280
  for (const label of labels) {
5982
6281
  if (label === "p:critical")
@@ -6079,6 +6378,7 @@ ${bold("Subcommands:")}
6079
6378
  ${cyan("show")} Show issue details
6080
6379
  ${cyan("label")} Bulk-update labels / sprint assignment
6081
6380
  ${cyan("close")} Close an issue
6381
+ ${cyan("delete")} ${dim("(rm)")} Permanently delete issues (bulk)
6082
6382
 
6083
6383
  ${bold("Create options:")}
6084
6384
  ${dim("--sprint, -s")} Assign to sprint (milestone)
@@ -6100,6 +6400,7 @@ ${bold("Examples:")}
6100
6400
  locus issue show 42
6101
6401
  locus issue label 42 43 --sprint "Sprint 2"
6102
6402
  locus issue close 42
6403
+ locus issue delete 42 43 44
6103
6404
 
6104
6405
  `);
6105
6406
  }
@@ -6765,9 +7066,9 @@ var init_sprint = __esm(() => {
6765
7066
  });
6766
7067
 
6767
7068
  // src/core/prompt-builder.ts
6768
- import { execSync as execSync9 } from "node:child_process";
6769
- import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "node:fs";
6770
- import { join as join12 } from "node:path";
7069
+ import { execSync as execSync10 } from "node:child_process";
7070
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "node:fs";
7071
+ import { join as join13 } from "node:path";
6771
7072
  function buildExecutionPrompt(ctx) {
6772
7073
  const sections = [];
6773
7074
  sections.push(buildSystemContext(ctx.projectRoot));
@@ -6797,30 +7098,30 @@ function buildFeedbackPrompt(ctx) {
6797
7098
  }
6798
7099
  function buildReplPrompt(userMessage, projectRoot, _config, previousMessages) {
6799
7100
  const sections = [];
6800
- const locusmd = readFileSafe(join12(projectRoot, "LOCUS.md"));
7101
+ const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
6801
7102
  if (locusmd) {
6802
- sections.push(`# Project Instructions
6803
-
6804
- ${locusmd}`);
7103
+ sections.push(`<project-instructions>
7104
+ ${locusmd}
7105
+ </project-instructions>`);
6805
7106
  }
6806
- const learnings = readFileSafe(join12(projectRoot, ".locus", "LEARNINGS.md"));
7107
+ const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
6807
7108
  if (learnings) {
6808
- sections.push(`# Past Learnings
6809
-
6810
- ${learnings}`);
7109
+ sections.push(`<past-learnings>
7110
+ ${learnings}
7111
+ </past-learnings>`);
6811
7112
  }
6812
7113
  if (previousMessages && previousMessages.length > 0) {
6813
7114
  const recent = previousMessages.slice(-10);
6814
7115
  const historyLines = recent.map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`);
6815
- sections.push(`# Previous Conversation
6816
-
7116
+ sections.push(`<previous-conversation>
6817
7117
  ${historyLines.join(`
6818
7118
 
6819
- `)}`);
7119
+ `)}
7120
+ </previous-conversation>`);
6820
7121
  }
6821
- sections.push(`# Current Request
6822
-
6823
- ${userMessage}`);
7122
+ sections.push(`<current-request>
7123
+ ${userMessage}
7124
+ </current-request>`);
6824
7125
  return sections.join(`
6825
7126
 
6826
7127
  ---
@@ -6828,63 +7129,66 @@ ${userMessage}`);
6828
7129
  `);
6829
7130
  }
6830
7131
  function buildSystemContext(projectRoot) {
6831
- const parts = ["# System Context"];
6832
- const locusmd = readFileSafe(join12(projectRoot, "LOCUS.md"));
7132
+ const parts = [];
7133
+ const locusmd = readFileSafe(join13(projectRoot, ".locus", "LOCUS.md"));
6833
7134
  if (locusmd) {
6834
- parts.push(`## Project Instructions (LOCUS.md)
6835
-
6836
- ${locusmd}`);
7135
+ parts.push(`<project-instructions>
7136
+ ${locusmd}
7137
+ </project-instructions>`);
6837
7138
  }
6838
- const learnings = readFileSafe(join12(projectRoot, ".locus", "LEARNINGS.md"));
7139
+ const learnings = readFileSafe(join13(projectRoot, ".locus", "LEARNINGS.md"));
6839
7140
  if (learnings) {
6840
- parts.push(`## Past Learnings
6841
-
6842
- ${learnings}`);
7141
+ parts.push(`<past-learnings>
7142
+ ${learnings}
7143
+ </past-learnings>`);
6843
7144
  }
6844
- const discussionsDir = join12(projectRoot, ".locus", "discussions");
6845
- if (existsSync13(discussionsDir)) {
7145
+ const discussionsDir = join13(projectRoot, ".locus", "discussions");
7146
+ if (existsSync14(discussionsDir)) {
6846
7147
  try {
6847
7148
  const files = readdirSync3(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
6848
7149
  for (const file of files) {
6849
- const content = readFileSafe(join12(discussionsDir, file));
7150
+ const content = readFileSafe(join13(discussionsDir, file));
6850
7151
  if (content) {
6851
- parts.push(`## Discussion: ${file.replace(".md", "")}
6852
-
6853
- ${content.slice(0, 2000)}`);
7152
+ const name = file.replace(".md", "");
7153
+ parts.push(`<discussion name="${name}">
7154
+ ${content.slice(0, 2000)}
7155
+ </discussion>`);
6854
7156
  }
6855
7157
  }
6856
7158
  } catch {}
6857
7159
  }
6858
- return parts.join(`
7160
+ return `<system-context>
7161
+ ${parts.join(`
6859
7162
 
6860
- `);
7163
+ `)}
7164
+ </system-context>`;
6861
7165
  }
6862
7166
  function buildTaskContext(issue, comments) {
6863
- const parts = [
6864
- `# Task Context`,
6865
- ``,
6866
- `## Issue #${issue.number}: ${issue.title}`,
6867
- ``,
6868
- issue.body || "_No description provided._"
6869
- ];
7167
+ const parts = [];
7168
+ const issueParts = [issue.body || "_No description provided._"];
6870
7169
  const labels = issue.labels.filter((l) => l.startsWith("p:") || l.startsWith("type:"));
6871
7170
  if (labels.length > 0) {
6872
- parts.push(`
6873
- **Labels:** ${labels.join(", ")}`);
7171
+ issueParts.push(`**Labels:** ${labels.join(", ")}`);
6874
7172
  }
7173
+ parts.push(`<issue number="${issue.number}" title="${issue.title}">
7174
+ ${issueParts.join(`
7175
+
7176
+ `)}
7177
+ </issue>`);
6875
7178
  if (comments && comments.length > 0) {
6876
- parts.push(`
6877
- ## Issue Comments
6878
- `);
6879
- for (const comment of comments) {
6880
- parts.push(comment);
6881
- }
7179
+ parts.push(`<issue-comments>
7180
+ ${comments.join(`
7181
+ `)}
7182
+ </issue-comments>`);
6882
7183
  }
6883
- return parts.join(`
6884
- `);
7184
+ return `<task-context>
7185
+ ${parts.join(`
7186
+
7187
+ `)}
7188
+ </task-context>`;
6885
7189
  }
6886
7190
  function buildSprintContext(sprintName, position, diffSummary) {
6887
- const parts = ["# Sprint Context"];
7191
+ const parts = [];
6888
7192
  if (sprintName) {
6889
7193
  parts.push(`**Sprint:** ${sprintName}`);
6890
7194
  }
@@ -6892,61 +7196,63 @@ function buildSprintContext(sprintName, position, diffSummary) {
6892
7196
  parts.push(`**Position:** Task ${position}`);
6893
7197
  }
6894
7198
  if (diffSummary) {
6895
- parts.push(`
6896
- ## Changes from Previous Tasks
6897
-
7199
+ parts.push(`<previous-changes>
6898
7200
  The following changes have already been made by earlier tasks in this sprint:
6899
7201
 
6900
7202
  \`\`\`diff
6901
7203
  ${diffSummary}
6902
- \`\`\``);
7204
+ \`\`\`
7205
+ </previous-changes>`);
6903
7206
  }
6904
- parts.push(`
6905
- **Important:** Build upon the changes from previous tasks. Do not revert or undo their work.`);
6906
- return parts.join(`
6907
- `);
7207
+ parts.push(`**Important:** Build upon the changes from previous tasks. Do not revert or undo their work.`);
7208
+ return `<sprint-context>
7209
+ ${parts.join(`
7210
+
7211
+ `)}
7212
+ </sprint-context>`;
6908
7213
  }
6909
7214
  function buildRepoContext(projectRoot) {
6910
- const parts = ["# Repository Context"];
7215
+ const parts = [];
6911
7216
  try {
6912
- const tree = execSync9("find . -maxdepth 2 -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.locus/*' -not -path '*/dist/*' -not -path '*/build/*' | head -80", { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
7217
+ const tree = execSync10("find . -maxdepth 2 -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.locus/*' -not -path '*/dist/*' -not -path '*/build/*' | head -80", { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6913
7218
  if (tree) {
6914
- parts.push(`## File Tree
6915
-
7219
+ parts.push(`<file-tree>
6916
7220
  \`\`\`
6917
7221
  ${tree}
6918
- \`\`\``);
7222
+ \`\`\`
7223
+ </file-tree>`);
6919
7224
  }
6920
7225
  } catch {}
6921
7226
  try {
6922
- const gitLog = execSync9("git log --oneline -10", {
7227
+ const gitLog = execSync10("git log --oneline -10", {
6923
7228
  cwd: projectRoot,
6924
7229
  encoding: "utf-8",
6925
7230
  stdio: ["pipe", "pipe", "pipe"]
6926
7231
  }).trim();
6927
7232
  if (gitLog) {
6928
- parts.push(`## Recent Commits
6929
-
7233
+ parts.push(`<recent-commits>
6930
7234
  \`\`\`
6931
7235
  ${gitLog}
6932
- \`\`\``);
7236
+ \`\`\`
7237
+ </recent-commits>`);
6933
7238
  }
6934
7239
  } catch {}
6935
7240
  try {
6936
- const branch = execSync9("git rev-parse --abbrev-ref HEAD", {
7241
+ const branch = execSync10("git rev-parse --abbrev-ref HEAD", {
6937
7242
  cwd: projectRoot,
6938
7243
  encoding: "utf-8",
6939
7244
  stdio: ["pipe", "pipe", "pipe"]
6940
7245
  }).trim();
6941
7246
  parts.push(`**Current branch:** ${branch}`);
6942
7247
  } catch {}
6943
- return parts.join(`
7248
+ return `<repository-context>
7249
+ ${parts.join(`
6944
7250
 
6945
- `);
7251
+ `)}
7252
+ </repository-context>`;
6946
7253
  }
6947
7254
  function buildExecutionRules(config) {
6948
- return `# Execution Rules
6949
-
7255
+ return `<execution-rules>
6950
7256
  1. **Commit format:** Use conventional commits: \`feat: <title> (#<issue>)\`, \`fix: ...\`, \`chore: ...\`. Every commit message MUST be multi-line: the first line is the title, then a blank line, then \`Co-Authored-By: LocusAgent <agent@locusai.team>\` as a Git trailer. Use \`git commit -m "<title>" -m "Co-Authored-By: LocusAgent <agent@locusai.team>"\` (two separate -m flags) to ensure the trailer is on its own line.
6951
7257
  2. **Code quality:** Follow existing code style. Run linters/formatters if available.
6952
7258
  3. **Testing:** If test files exist for modified code, update them accordingly.
@@ -6958,41 +7264,41 @@ function buildExecutionRules(config) {
6958
7264
  5. **Base branch:** ${config.agent.baseBranch}
6959
7265
  6. **Provider:** ${config.ai.provider} / ${config.ai.model}
6960
7266
 
6961
- When you are done, provide a brief summary of what you changed and why.`;
7267
+ When you are done, provide a brief summary of what you changed and why.
7268
+ </execution-rules>`;
6962
7269
  }
6963
7270
  function buildPRContext(prNumber, diff, comments) {
6964
7271
  const parts = [
6965
- `# Current State — PR #${prNumber}`,
6966
- ``,
6967
- `## PR Diff`,
6968
- ``,
6969
- "```diff",
6970
- diff.slice(0, 1e4),
6971
- "```"
7272
+ `<pr-diff>
7273
+ \`\`\`diff
7274
+ ${diff.slice(0, 1e4)}
7275
+ \`\`\`
7276
+ </pr-diff>`
6972
7277
  ];
6973
7278
  if (comments.length > 0) {
6974
- parts.push(`
6975
- ## Review Comments
6976
- `);
6977
- for (const comment of comments) {
6978
- parts.push(comment);
6979
- }
7279
+ parts.push(`<review-comments>
7280
+ ${comments.join(`
7281
+ `)}
7282
+ </review-comments>`);
6980
7283
  }
6981
- return parts.join(`
6982
- `);
7284
+ return `<pr-context number="${prNumber}">
7285
+ ${parts.join(`
7286
+
7287
+ `)}
7288
+ </pr-context>`;
6983
7289
  }
6984
7290
  function buildFeedbackInstructions() {
6985
- return `# Instructions
6986
-
7291
+ return `<instructions>
6987
7292
  1. Address ALL review feedback from the comments above.
6988
7293
  2. Make targeted changes — do NOT rewrite code from scratch.
6989
7294
  3. If a reviewer comment is unclear, make your best judgment and note your interpretation.
6990
7295
  4. Push changes to the same branch — do NOT create a new PR.
6991
- 5. When done, summarize what you changed in response to each comment.`;
7296
+ 5. When done, summarize what you changed in response to each comment.
7297
+ </instructions>`;
6992
7298
  }
6993
7299
  function readFileSafe(path) {
6994
7300
  try {
6995
- if (!existsSync13(path))
7301
+ if (!existsSync14(path))
6996
7302
  return null;
6997
7303
  return readFileSync10(path, "utf-8");
6998
7304
  } catch {
@@ -7186,7 +7492,7 @@ var init_diff_renderer = __esm(() => {
7186
7492
  });
7187
7493
 
7188
7494
  // src/repl/commands.ts
7189
- import { execSync as execSync10 } from "node:child_process";
7495
+ import { execSync as execSync11 } from "node:child_process";
7190
7496
  function getSlashCommands() {
7191
7497
  return [
7192
7498
  {
@@ -7378,7 +7684,7 @@ function cmdModel(args, ctx) {
7378
7684
  }
7379
7685
  function cmdDiff(_args, ctx) {
7380
7686
  try {
7381
- const diff = execSync10("git diff", {
7687
+ const diff = execSync11("git diff", {
7382
7688
  cwd: ctx.projectRoot,
7383
7689
  encoding: "utf-8",
7384
7690
  stdio: ["pipe", "pipe", "pipe"]
@@ -7414,7 +7720,7 @@ function cmdDiff(_args, ctx) {
7414
7720
  }
7415
7721
  function cmdUndo(_args, ctx) {
7416
7722
  try {
7417
- const status = execSync10("git status --porcelain", {
7723
+ const status = execSync11("git status --porcelain", {
7418
7724
  cwd: ctx.projectRoot,
7419
7725
  encoding: "utf-8",
7420
7726
  stdio: ["pipe", "pipe", "pipe"]
@@ -7424,7 +7730,7 @@ function cmdUndo(_args, ctx) {
7424
7730
  `);
7425
7731
  return;
7426
7732
  }
7427
- execSync10("git checkout .", {
7733
+ execSync11("git checkout .", {
7428
7734
  cwd: ctx.projectRoot,
7429
7735
  encoding: "utf-8",
7430
7736
  stdio: ["pipe", "pipe", "pipe"]
@@ -7458,7 +7764,7 @@ var init_commands = __esm(() => {
7458
7764
 
7459
7765
  // src/repl/completions.ts
7460
7766
  import { readdirSync as readdirSync4 } from "node:fs";
7461
- import { basename as basename2, dirname as dirname4, join as join13 } from "node:path";
7767
+ import { basename as basename2, dirname as dirname4, join as join14 } from "node:path";
7462
7768
 
7463
7769
  class SlashCommandCompletion {
7464
7770
  commands;
@@ -7513,7 +7819,7 @@ class FilePathCompletion {
7513
7819
  }
7514
7820
  findMatches(partial) {
7515
7821
  try {
7516
- const dir = partial.includes("/") ? join13(this.projectRoot, dirname4(partial)) : this.projectRoot;
7822
+ const dir = partial.includes("/") ? join14(this.projectRoot, dirname4(partial)) : this.projectRoot;
7517
7823
  const prefix = basename2(partial);
7518
7824
  const entries = readdirSync4(dir, { withFileTypes: true });
7519
7825
  return entries.filter((e) => {
@@ -7549,14 +7855,14 @@ class CombinedCompletion {
7549
7855
  var init_completions = () => {};
7550
7856
 
7551
7857
  // src/repl/input-history.ts
7552
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
7553
- import { dirname as dirname5, join as join14 } from "node:path";
7858
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
7859
+ import { dirname as dirname5, join as join15 } from "node:path";
7554
7860
 
7555
7861
  class InputHistory {
7556
7862
  entries = [];
7557
7863
  filePath;
7558
7864
  constructor(projectRoot) {
7559
- this.filePath = join14(projectRoot, ".locus", "sessions", ".input-history");
7865
+ this.filePath = join15(projectRoot, ".locus", "sessions", ".input-history");
7560
7866
  this.load();
7561
7867
  }
7562
7868
  add(text) {
@@ -7595,7 +7901,7 @@ class InputHistory {
7595
7901
  }
7596
7902
  load() {
7597
7903
  try {
7598
- if (!existsSync14(this.filePath))
7904
+ if (!existsSync15(this.filePath))
7599
7905
  return;
7600
7906
  const content = readFileSync11(this.filePath, "utf-8");
7601
7907
  this.entries = content.split(`
@@ -7605,8 +7911,8 @@ class InputHistory {
7605
7911
  save() {
7606
7912
  try {
7607
7913
  const dir = dirname5(this.filePath);
7608
- if (!existsSync14(dir)) {
7609
- mkdirSync9(dir, { recursive: true });
7914
+ if (!existsSync15(dir)) {
7915
+ mkdirSync10(dir, { recursive: true });
7610
7916
  }
7611
7917
  const content = this.entries.map((e) => this.escape(e)).join(`
7612
7918
  `);
@@ -7636,21 +7942,21 @@ var init_model_config = __esm(() => {
7636
7942
 
7637
7943
  // src/repl/session-manager.ts
7638
7944
  import {
7639
- existsSync as existsSync15,
7640
- mkdirSync as mkdirSync10,
7945
+ existsSync as existsSync16,
7946
+ mkdirSync as mkdirSync11,
7641
7947
  readdirSync as readdirSync5,
7642
7948
  readFileSync as readFileSync12,
7643
7949
  unlinkSync as unlinkSync4,
7644
7950
  writeFileSync as writeFileSync8
7645
7951
  } from "node:fs";
7646
- import { basename as basename3, join as join15 } from "node:path";
7952
+ import { basename as basename3, join as join16 } from "node:path";
7647
7953
 
7648
7954
  class SessionManager {
7649
7955
  sessionsDir;
7650
7956
  constructor(projectRoot) {
7651
- this.sessionsDir = join15(projectRoot, ".locus", "sessions");
7652
- if (!existsSync15(this.sessionsDir)) {
7653
- mkdirSync10(this.sessionsDir, { recursive: true });
7957
+ this.sessionsDir = join16(projectRoot, ".locus", "sessions");
7958
+ if (!existsSync16(this.sessionsDir)) {
7959
+ mkdirSync11(this.sessionsDir, { recursive: true });
7654
7960
  }
7655
7961
  }
7656
7962
  create(options) {
@@ -7675,12 +7981,12 @@ class SessionManager {
7675
7981
  }
7676
7982
  isPersisted(sessionOrId) {
7677
7983
  const sessionId = typeof sessionOrId === "string" ? sessionOrId : sessionOrId.id;
7678
- return existsSync15(this.getSessionPath(sessionId));
7984
+ return existsSync16(this.getSessionPath(sessionId));
7679
7985
  }
7680
7986
  load(idOrPrefix) {
7681
7987
  const files = this.listSessionFiles();
7682
7988
  const exactPath = this.getSessionPath(idOrPrefix);
7683
- if (existsSync15(exactPath)) {
7989
+ if (existsSync16(exactPath)) {
7684
7990
  try {
7685
7991
  return JSON.parse(readFileSync12(exactPath, "utf-8"));
7686
7992
  } catch {
@@ -7730,7 +8036,7 @@ class SessionManager {
7730
8036
  }
7731
8037
  delete(sessionId) {
7732
8038
  const path = this.getSessionPath(sessionId);
7733
- if (existsSync15(path)) {
8039
+ if (existsSync16(path)) {
7734
8040
  unlinkSync4(path);
7735
8041
  return true;
7736
8042
  }
@@ -7760,7 +8066,7 @@ class SessionManager {
7760
8066
  const remaining = withStats.length - pruned;
7761
8067
  if (remaining > MAX_SESSIONS) {
7762
8068
  const toRemove = remaining - MAX_SESSIONS;
7763
- const alive = withStats.filter((e) => existsSync15(e.path));
8069
+ const alive = withStats.filter((e) => existsSync16(e.path));
7764
8070
  for (let i = 0;i < toRemove && i < alive.length; i++) {
7765
8071
  try {
7766
8072
  unlinkSync4(alive[i].path);
@@ -7775,7 +8081,7 @@ class SessionManager {
7775
8081
  }
7776
8082
  listSessionFiles() {
7777
8083
  try {
7778
- return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join15(this.sessionsDir, f));
8084
+ return readdirSync5(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
7779
8085
  } catch {
7780
8086
  return [];
7781
8087
  }
@@ -7784,7 +8090,7 @@ class SessionManager {
7784
8090
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
7785
8091
  }
7786
8092
  getSessionPath(sessionId) {
7787
- return join15(this.sessionsDir, `${sessionId}.json`);
8093
+ return join16(this.sessionsDir, `${sessionId}.json`);
7788
8094
  }
7789
8095
  }
7790
8096
  var MAX_SESSIONS = 50, SESSION_MAX_AGE_MS;
@@ -7794,7 +8100,7 @@ var init_session_manager = __esm(() => {
7794
8100
  });
7795
8101
 
7796
8102
  // src/repl/repl.ts
7797
- import { execSync as execSync11 } from "node:child_process";
8103
+ import { execSync as execSync12 } from "node:child_process";
7798
8104
  async function startRepl(options) {
7799
8105
  const { projectRoot, config } = options;
7800
8106
  const sessionManager = new SessionManager(projectRoot);
@@ -7812,7 +8118,7 @@ async function startRepl(options) {
7812
8118
  } else {
7813
8119
  let branch = "main";
7814
8120
  try {
7815
- branch = execSync11("git rev-parse --abbrev-ref HEAD", {
8121
+ branch = execSync12("git rev-parse --abbrev-ref HEAD", {
7816
8122
  cwd: projectRoot,
7817
8123
  encoding: "utf-8",
7818
8124
  stdio: ["pipe", "pipe", "pipe"]
@@ -7837,6 +8143,7 @@ async function executeOneShotPrompt(prompt, session, sessionManager, options) {
7837
8143
  const normalized = normalizeImagePlaceholders(prompt);
7838
8144
  const text = normalized.text;
7839
8145
  const images = collectReferencedAttachments(text, normalized.attachments);
8146
+ relocateImages(images, projectRoot);
7840
8147
  const imageContext = buildImageContext(images);
7841
8148
  const fullPrompt = buildReplPrompt(text + imageContext, projectRoot, config, session.messages);
7842
8149
  sessionManager.addMessage(session, {
@@ -7931,6 +8238,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
7931
8238
  continue;
7932
8239
  }
7933
8240
  history.add(text);
8241
+ relocateImages(result.images, projectRoot);
7934
8242
  const imageContext = buildImageContext(result.images);
7935
8243
  const fullPrompt = buildReplPrompt(text + imageContext, projectRoot, { ...config, ai: { provider: currentProvider, model: currentModel } }, session.messages);
7936
8244
  sessionManager.addMessage(session, {
@@ -8223,7 +8531,7 @@ var init_exec = __esm(() => {
8223
8531
  });
8224
8532
 
8225
8533
  // src/core/agent.ts
8226
- import { execSync as execSync12 } from "node:child_process";
8534
+ import { execSync as execSync13 } from "node:child_process";
8227
8535
  async function executeIssue(projectRoot, options) {
8228
8536
  const log = getLogger();
8229
8537
  const timer = createTimer();
@@ -8252,7 +8560,7 @@ ${cyan("●")} ${bold(`#${issueNumber}`)} ${issue.title}
8252
8560
  }
8253
8561
  let issueComments = [];
8254
8562
  try {
8255
- const commentsRaw = execSync12(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
8563
+ const commentsRaw = execSync13(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
8256
8564
  if (commentsRaw) {
8257
8565
  issueComments = commentsRaw.split(`
8258
8566
  `).filter(Boolean);
@@ -8416,12 +8724,12 @@ ${aiResult.success ? green("✓") : red("✗")} Iteration ${aiResult.success ? "
8416
8724
  }
8417
8725
  async function createIssuePR(projectRoot, config, issue) {
8418
8726
  try {
8419
- const currentBranch = execSync12("git rev-parse --abbrev-ref HEAD", {
8727
+ const currentBranch = execSync13("git rev-parse --abbrev-ref HEAD", {
8420
8728
  cwd: projectRoot,
8421
8729
  encoding: "utf-8",
8422
8730
  stdio: ["pipe", "pipe", "pipe"]
8423
8731
  }).trim();
8424
- const diff = execSync12(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
8732
+ const diff = execSync13(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
8425
8733
  cwd: projectRoot,
8426
8734
  encoding: "utf-8",
8427
8735
  stdio: ["pipe", "pipe", "pipe"]
@@ -8430,7 +8738,7 @@ async function createIssuePR(projectRoot, config, issue) {
8430
8738
  getLogger().verbose("No changes to create PR for");
8431
8739
  return;
8432
8740
  }
8433
- execSync12(`git push -u origin ${currentBranch}`, {
8741
+ execSync13(`git push -u origin ${currentBranch}`, {
8434
8742
  cwd: projectRoot,
8435
8743
  encoding: "utf-8",
8436
8744
  stdio: ["pipe", "pipe", "pipe"]
@@ -8476,9 +8784,9 @@ var init_agent = __esm(() => {
8476
8784
  });
8477
8785
 
8478
8786
  // src/core/conflict.ts
8479
- import { execSync as execSync13 } from "node:child_process";
8787
+ import { execSync as execSync14 } from "node:child_process";
8480
8788
  function git2(args, cwd) {
8481
- return execSync13(`git ${args}`, {
8789
+ return execSync14(`git ${args}`, {
8482
8790
  cwd,
8483
8791
  encoding: "utf-8",
8484
8792
  stdio: ["pipe", "pipe", "pipe"]
@@ -8603,144 +8911,12 @@ var init_conflict = __esm(() => {
8603
8911
  init_logger();
8604
8912
  });
8605
8913
 
8606
- // src/core/sandbox.ts
8607
- import { execFile } from "node:child_process";
8608
- async function detectSandboxSupport() {
8609
- if (cachedStatus)
8610
- return cachedStatus;
8611
- const log = getLogger();
8612
- log.debug("Detecting Docker sandbox support...");
8613
- const status = await runDetection();
8614
- cachedStatus = status;
8615
- if (status.available) {
8616
- log.verbose("Docker sandbox support detected");
8617
- } else {
8618
- log.verbose(`Docker sandbox not available: ${status.reason}`);
8619
- }
8620
- return status;
8621
- }
8622
- function runDetection() {
8623
- return new Promise((resolve2) => {
8624
- let settled = false;
8625
- const child = execFile("docker", ["sandbox", "ls"], { timeout: TIMEOUT_MS }, (error, _stdout, stderr) => {
8626
- if (settled)
8627
- return;
8628
- settled = true;
8629
- if (!error) {
8630
- resolve2({ available: true });
8631
- return;
8632
- }
8633
- const code = error.code;
8634
- if (code === "ENOENT") {
8635
- resolve2({ available: false, reason: "Docker is not installed" });
8636
- return;
8637
- }
8638
- if (error.killed) {
8639
- resolve2({ available: false, reason: "Docker is not responding" });
8640
- return;
8641
- }
8642
- const stderrStr = (stderr ?? "").toLowerCase();
8643
- if (stderrStr.includes("unknown") || stderrStr.includes("not a docker command") || stderrStr.includes("is not a docker command")) {
8644
- resolve2({
8645
- available: false,
8646
- reason: "Docker Desktop 4.58+ with sandbox support required"
8647
- });
8648
- return;
8649
- }
8650
- resolve2({
8651
- available: false,
8652
- reason: "Docker Desktop 4.58+ with sandbox support required"
8653
- });
8654
- });
8655
- child.on?.("error", (err) => {
8656
- if (settled)
8657
- return;
8658
- settled = true;
8659
- if (err.code === "ENOENT") {
8660
- resolve2({ available: false, reason: "Docker is not installed" });
8661
- } else {
8662
- resolve2({
8663
- available: false,
8664
- reason: "Docker Desktop 4.58+ with sandbox support required"
8665
- });
8666
- }
8667
- });
8668
- });
8669
- }
8670
- async function cleanupStaleSandboxes() {
8671
- const log = getLogger();
8672
- try {
8673
- const { stdout } = await execFileAsync("docker", ["sandbox", "ls"], {
8674
- timeout: TIMEOUT_MS
8675
- });
8676
- const lines = stdout.trim().split(`
8677
- `);
8678
- if (lines.length <= 1)
8679
- return 0;
8680
- const staleNames = [];
8681
- for (const line of lines.slice(1)) {
8682
- const name = line.trim().split(/\s+/)[0];
8683
- if (name?.startsWith("locus-")) {
8684
- staleNames.push(name);
8685
- }
8686
- }
8687
- if (staleNames.length === 0)
8688
- return 0;
8689
- log.verbose(`Found ${staleNames.length} stale sandbox(es) to clean up`);
8690
- let cleaned = 0;
8691
- for (const name of staleNames) {
8692
- try {
8693
- await execFileAsync("docker", ["sandbox", "rm", name], {
8694
- timeout: 1e4
8695
- });
8696
- log.debug(`Removed stale sandbox: ${name}`);
8697
- cleaned++;
8698
- } catch {
8699
- log.debug(`Failed to remove stale sandbox: ${name}`);
8700
- }
8701
- }
8702
- return cleaned;
8703
- } catch {
8704
- return 0;
8705
- }
8706
- }
8707
- function execFileAsync(file, args, options) {
8708
- return new Promise((resolve2, reject) => {
8709
- execFile(file, args, options, (error, stdout, stderr) => {
8710
- if (error)
8711
- reject(error);
8712
- else
8713
- resolve2({ stdout: stdout ?? "", stderr: stderr ?? "" });
8714
- });
8715
- });
8716
- }
8717
- function resolveSandboxMode(config, flags) {
8718
- if (flags.noSandbox) {
8719
- return "disabled";
8720
- }
8721
- if (flags.sandbox !== undefined) {
8722
- if (flags.sandbox === "require") {
8723
- return "required";
8724
- }
8725
- throw new Error(`Invalid --sandbox value: "${flags.sandbox}". Valid values: require`);
8726
- }
8727
- if (!config.enabled) {
8728
- return "disabled";
8729
- }
8730
- return "auto";
8731
- }
8732
- var TIMEOUT_MS = 5000, cachedStatus = null;
8733
- var init_sandbox = __esm(() => {
8734
- init_terminal();
8735
- init_logger();
8736
- });
8737
-
8738
8914
  // src/core/worktree.ts
8739
- import { execSync as execSync14 } from "node:child_process";
8740
- import { existsSync as existsSync16, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
8741
- import { join as join16 } from "node:path";
8915
+ import { execSync as execSync15 } from "node:child_process";
8916
+ import { existsSync as existsSync17, readdirSync as readdirSync6, realpathSync, statSync as statSync3 } from "node:fs";
8917
+ import { join as join17 } from "node:path";
8742
8918
  function git3(args, cwd) {
8743
- return execSync14(`git ${args}`, {
8919
+ return execSync15(`git ${args}`, {
8744
8920
  cwd,
8745
8921
  encoding: "utf-8",
8746
8922
  stdio: ["pipe", "pipe", "pipe"]
@@ -8754,10 +8930,10 @@ function gitSafe2(args, cwd) {
8754
8930
  }
8755
8931
  }
8756
8932
  function getWorktreeDir(projectRoot) {
8757
- return join16(projectRoot, ".locus", "worktrees");
8933
+ return join17(projectRoot, ".locus", "worktrees");
8758
8934
  }
8759
8935
  function getWorktreePath(projectRoot, issueNumber) {
8760
- return join16(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
8936
+ return join17(getWorktreeDir(projectRoot), `issue-${issueNumber}`);
8761
8937
  }
8762
8938
  function generateBranchName(issueNumber) {
8763
8939
  const randomSuffix = Math.random().toString(36).slice(2, 8);
@@ -8765,7 +8941,7 @@ function generateBranchName(issueNumber) {
8765
8941
  }
8766
8942
  function getWorktreeBranch(worktreePath) {
8767
8943
  try {
8768
- return execSync14("git branch --show-current", {
8944
+ return execSync15("git branch --show-current", {
8769
8945
  cwd: worktreePath,
8770
8946
  encoding: "utf-8",
8771
8947
  stdio: ["pipe", "pipe", "pipe"]
@@ -8777,7 +8953,7 @@ function getWorktreeBranch(worktreePath) {
8777
8953
  function createWorktree(projectRoot, issueNumber, baseBranch) {
8778
8954
  const log = getLogger();
8779
8955
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
8780
- if (existsSync16(worktreePath)) {
8956
+ if (existsSync17(worktreePath)) {
8781
8957
  log.verbose(`Worktree already exists for issue #${issueNumber}`);
8782
8958
  const existingBranch = getWorktreeBranch(worktreePath) ?? `locus/issue-${issueNumber}`;
8783
8959
  return {
@@ -8804,7 +8980,7 @@ function createWorktree(projectRoot, issueNumber, baseBranch) {
8804
8980
  function removeWorktree(projectRoot, issueNumber) {
8805
8981
  const log = getLogger();
8806
8982
  const worktreePath = getWorktreePath(projectRoot, issueNumber);
8807
- if (!existsSync16(worktreePath)) {
8983
+ if (!existsSync17(worktreePath)) {
8808
8984
  log.verbose(`Worktree for issue #${issueNumber} does not exist`);
8809
8985
  return;
8810
8986
  }
@@ -8823,7 +8999,7 @@ function removeWorktree(projectRoot, issueNumber) {
8823
8999
  function listWorktrees(projectRoot) {
8824
9000
  const log = getLogger();
8825
9001
  const worktreeDir = getWorktreeDir(projectRoot);
8826
- if (!existsSync16(worktreeDir)) {
9002
+ if (!existsSync17(worktreeDir)) {
8827
9003
  return [];
8828
9004
  }
8829
9005
  const entries = readdirSync6(worktreeDir).filter((entry) => entry.startsWith("issue-"));
@@ -8843,7 +9019,7 @@ function listWorktrees(projectRoot) {
8843
9019
  if (!match)
8844
9020
  continue;
8845
9021
  const issueNumber = Number.parseInt(match[1], 10);
8846
- const path = join16(worktreeDir, entry);
9022
+ const path = join17(worktreeDir, entry);
8847
9023
  const branch = getWorktreeBranch(path) ?? `locus/issue-${issueNumber}`;
8848
9024
  let resolvedPath;
8849
9025
  try {
@@ -8890,7 +9066,7 @@ var exports_run = {};
8890
9066
  __export(exports_run, {
8891
9067
  runCommand: () => runCommand
8892
9068
  });
8893
- import { execSync as execSync15 } from "node:child_process";
9069
+ import { execSync as execSync16 } from "node:child_process";
8894
9070
  function printRunHelp() {
8895
9071
  process.stderr.write(`
8896
9072
  ${bold("locus run")} — Execute issues using AI agents
@@ -8956,13 +9132,6 @@ async function runCommand(projectRoot, args, flags = {}) {
8956
9132
  process.stderr.write(`${yellow("⚠")} Running without sandbox. The AI agent will have unrestricted access to your filesystem, network, and environment variables.
8957
9133
  `);
8958
9134
  }
8959
- if (sandboxed) {
8960
- const staleCleaned = await cleanupStaleSandboxes();
8961
- if (staleCleaned > 0) {
8962
- process.stderr.write(` ${dim(`Cleaned up ${staleCleaned} stale sandbox${staleCleaned === 1 ? "" : "es"}.`)}
8963
- `);
8964
- }
8965
- }
8966
9135
  if (flags.resume) {
8967
9136
  return handleResume(projectRoot, config, sandboxed);
8968
9137
  }
@@ -9041,7 +9210,7 @@ ${yellow("⚠")} A sprint run is already in progress.
9041
9210
  }
9042
9211
  if (!flags.dryRun) {
9043
9212
  try {
9044
- execSync15(`git checkout -B ${branchName}`, {
9213
+ execSync16(`git checkout -B ${branchName}`, {
9045
9214
  cwd: projectRoot,
9046
9215
  encoding: "utf-8",
9047
9216
  stdio: ["pipe", "pipe", "pipe"]
@@ -9091,7 +9260,7 @@ ${red("✗")} Auto-rebase failed. Resolve manually.
9091
9260
  let sprintContext;
9092
9261
  if (i > 0 && !flags.dryRun) {
9093
9262
  try {
9094
- sprintContext = execSync15(`git diff origin/${config.agent.baseBranch}..HEAD`, {
9263
+ sprintContext = execSync16(`git diff origin/${config.agent.baseBranch}..HEAD`, {
9095
9264
  cwd: projectRoot,
9096
9265
  encoding: "utf-8",
9097
9266
  stdio: ["pipe", "pipe", "pipe"]
@@ -9111,13 +9280,16 @@ ${progressBar(i, state.tasks.length, { label: "Sprint Progress" })}
9111
9280
  dryRun: flags.dryRun,
9112
9281
  sprintContext,
9113
9282
  skipPR: true,
9114
- sandboxed,
9115
- sandboxName: config.sandbox.name
9283
+ sandboxed
9116
9284
  });
9117
9285
  if (result.success) {
9118
9286
  if (!flags.dryRun) {
9119
9287
  const issueTitle = issue?.title ?? "";
9120
9288
  ensureTaskCommit(projectRoot, task.issue, issueTitle);
9289
+ if (sandboxed && i < state.tasks.length - 1) {
9290
+ process.stderr.write(` ${dim("↻ Sandbox will resync on next task")}
9291
+ `);
9292
+ }
9121
9293
  }
9122
9294
  markTaskDone(state, task.issue, result.prNumber);
9123
9295
  } else {
@@ -9152,7 +9324,7 @@ ${bold("Summary:")}
9152
9324
  const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
9153
9325
  if (prNumber !== undefined) {
9154
9326
  try {
9155
- execSync15(`git checkout ${config.agent.baseBranch}`, {
9327
+ execSync16(`git checkout ${config.agent.baseBranch}`, {
9156
9328
  cwd: projectRoot,
9157
9329
  encoding: "utf-8",
9158
9330
  stdio: ["pipe", "pipe", "pipe"]
@@ -9349,13 +9521,13 @@ ${bold("Resuming")} ${state.type} run ${dim(state.runId)}
9349
9521
  `);
9350
9522
  if (state.type === "sprint" && state.branch) {
9351
9523
  try {
9352
- const currentBranch = execSync15("git rev-parse --abbrev-ref HEAD", {
9524
+ const currentBranch = execSync16("git rev-parse --abbrev-ref HEAD", {
9353
9525
  cwd: projectRoot,
9354
9526
  encoding: "utf-8",
9355
9527
  stdio: ["pipe", "pipe", "pipe"]
9356
9528
  }).trim();
9357
9529
  if (currentBranch !== state.branch) {
9358
- execSync15(`git checkout ${state.branch}`, {
9530
+ execSync16(`git checkout ${state.branch}`, {
9359
9531
  cwd: projectRoot,
9360
9532
  encoding: "utf-8",
9361
9533
  stdio: ["pipe", "pipe", "pipe"]
@@ -9383,7 +9555,7 @@ ${bold("Resuming")} ${state.type} run ${dim(state.runId)}
9383
9555
  model: config.ai.model,
9384
9556
  skipPR: isSprintRun,
9385
9557
  sandboxed,
9386
- sandboxName: config.sandbox.name
9558
+ sandboxName: isSprintRun ? undefined : config.sandbox.name
9387
9559
  });
9388
9560
  if (result.success) {
9389
9561
  if (isSprintRun) {
@@ -9393,6 +9565,10 @@ ${bold("Resuming")} ${state.type} run ${dim(state.runId)}
9393
9565
  issueTitle = iss.title;
9394
9566
  } catch {}
9395
9567
  ensureTaskCommit(projectRoot, task.issue, issueTitle);
9568
+ if (sandboxed) {
9569
+ process.stderr.write(` ${dim("↻ Sandbox will resync on next task")}
9570
+ `);
9571
+ }
9396
9572
  }
9397
9573
  markTaskDone(state, task.issue, result.prNumber);
9398
9574
  } else {
@@ -9418,7 +9594,7 @@ ${bold("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fail
9418
9594
  const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
9419
9595
  if (prNumber !== undefined) {
9420
9596
  try {
9421
- execSync15(`git checkout ${config.agent.baseBranch}`, {
9597
+ execSync16(`git checkout ${config.agent.baseBranch}`, {
9422
9598
  cwd: projectRoot,
9423
9599
  encoding: "utf-8",
9424
9600
  stdio: ["pipe", "pipe", "pipe"]
@@ -9449,14 +9625,14 @@ function getOrder2(issue) {
9449
9625
  }
9450
9626
  function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
9451
9627
  try {
9452
- const status = execSync15("git status --porcelain", {
9628
+ const status = execSync16("git status --porcelain", {
9453
9629
  cwd: projectRoot,
9454
9630
  encoding: "utf-8",
9455
9631
  stdio: ["pipe", "pipe", "pipe"]
9456
9632
  }).trim();
9457
9633
  if (!status)
9458
9634
  return;
9459
- execSync15("git add -A", {
9635
+ execSync16("git add -A", {
9460
9636
  cwd: projectRoot,
9461
9637
  encoding: "utf-8",
9462
9638
  stdio: ["pipe", "pipe", "pipe"]
@@ -9464,7 +9640,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
9464
9640
  const message = `chore: complete #${issueNumber} - ${issueTitle}
9465
9641
 
9466
9642
  Co-Authored-By: LocusAgent <agent@locusai.team>`;
9467
- execSync15(`git commit -F -`, {
9643
+ execSync16(`git commit -F -`, {
9468
9644
  input: message,
9469
9645
  cwd: projectRoot,
9470
9646
  encoding: "utf-8",
@@ -9478,7 +9654,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
9478
9654
  if (!config.agent.autoPR)
9479
9655
  return;
9480
9656
  try {
9481
- const diff = execSync15(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
9657
+ const diff = execSync16(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
9482
9658
  cwd: projectRoot,
9483
9659
  encoding: "utf-8",
9484
9660
  stdio: ["pipe", "pipe", "pipe"]
@@ -9488,7 +9664,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
9488
9664
  `);
9489
9665
  return;
9490
9666
  }
9491
- execSync15(`git push -u origin ${branchName}`, {
9667
+ execSync16(`git push -u origin ${branchName}`, {
9492
9668
  cwd: projectRoot,
9493
9669
  encoding: "utf-8",
9494
9670
  stdio: ["pipe", "pipe", "pipe"]
@@ -9535,6 +9711,8 @@ __export(exports_status, {
9535
9711
  });
9536
9712
  async function statusCommand(projectRoot) {
9537
9713
  const config = loadConfig(projectRoot);
9714
+ const spinner = new Spinner;
9715
+ spinner.start("Fetching project status...");
9538
9716
  const lines = [];
9539
9717
  lines.push(` ${dim("Repo:")} ${cyan(`${config.github.owner}/${config.github.repo}`)}`);
9540
9718
  lines.push(` ${dim("Provider:")} ${config.ai.provider} / ${config.ai.model}`);
@@ -9611,6 +9789,7 @@ async function statusCommand(projectRoot) {
9611
9789
  }
9612
9790
  }
9613
9791
  } catch {}
9792
+ spinner.stop();
9614
9793
  lines.push("");
9615
9794
  process.stderr.write(`
9616
9795
  ${drawBox(lines, { title: "Locus Status" })}
@@ -9634,13 +9813,13 @@ __export(exports_plan, {
9634
9813
  parsePlanArgs: () => parsePlanArgs
9635
9814
  });
9636
9815
  import {
9637
- existsSync as existsSync17,
9638
- mkdirSync as mkdirSync11,
9816
+ existsSync as existsSync18,
9817
+ mkdirSync as mkdirSync12,
9639
9818
  readdirSync as readdirSync7,
9640
9819
  readFileSync as readFileSync13,
9641
9820
  writeFileSync as writeFileSync9
9642
9821
  } from "node:fs";
9643
- import { join as join17 } from "node:path";
9822
+ import { join as join18 } from "node:path";
9644
9823
  function printHelp() {
9645
9824
  process.stderr.write(`
9646
9825
  ${bold("locus plan")} — AI-powered sprint planning
@@ -9671,12 +9850,12 @@ function normalizeSprintName(name) {
9671
9850
  return name.trim().toLowerCase();
9672
9851
  }
9673
9852
  function getPlansDir(projectRoot) {
9674
- return join17(projectRoot, ".locus", "plans");
9853
+ return join18(projectRoot, ".locus", "plans");
9675
9854
  }
9676
9855
  function ensurePlansDir(projectRoot) {
9677
9856
  const dir = getPlansDir(projectRoot);
9678
- if (!existsSync17(dir)) {
9679
- mkdirSync11(dir, { recursive: true });
9857
+ if (!existsSync18(dir)) {
9858
+ mkdirSync12(dir, { recursive: true });
9680
9859
  }
9681
9860
  return dir;
9682
9861
  }
@@ -9685,14 +9864,14 @@ function generateId() {
9685
9864
  }
9686
9865
  function loadPlanFile(projectRoot, id) {
9687
9866
  const dir = getPlansDir(projectRoot);
9688
- if (!existsSync17(dir))
9867
+ if (!existsSync18(dir))
9689
9868
  return null;
9690
9869
  const files = readdirSync7(dir).filter((f) => f.endsWith(".json"));
9691
9870
  const match = files.find((f) => f.startsWith(id));
9692
9871
  if (!match)
9693
9872
  return null;
9694
9873
  try {
9695
- const content = readFileSync13(join17(dir, match), "utf-8");
9874
+ const content = readFileSync13(join18(dir, match), "utf-8");
9696
9875
  return JSON.parse(content);
9697
9876
  } catch {
9698
9877
  return null;
@@ -9738,7 +9917,7 @@ async function planCommand(projectRoot, args, flags = {}) {
9738
9917
  }
9739
9918
  function handleListPlans(projectRoot) {
9740
9919
  const dir = getPlansDir(projectRoot);
9741
- if (!existsSync17(dir)) {
9920
+ if (!existsSync18(dir)) {
9742
9921
  process.stderr.write(`${dim("No saved plans yet.")}
9743
9922
  `);
9744
9923
  return;
@@ -9756,7 +9935,7 @@ ${bold("Saved Plans:")}
9756
9935
  for (const file of files) {
9757
9936
  const id = file.replace(".json", "");
9758
9937
  try {
9759
- const content = readFileSync13(join17(dir, file), "utf-8");
9938
+ const content = readFileSync13(join18(dir, file), "utf-8");
9760
9939
  const plan = JSON.parse(content);
9761
9940
  const date = plan.createdAt ? plan.createdAt.slice(0, 10) : "";
9762
9941
  const issueCount = Array.isArray(plan.issues) ? plan.issues.length : 0;
@@ -9867,7 +10046,7 @@ ${bold("Approving plan:")}
9867
10046
  async function handleAIPlan(projectRoot, config, directive, sprintName, flags) {
9868
10047
  const id = generateId();
9869
10048
  const plansDir = ensurePlansDir(projectRoot);
9870
- const planPath = join17(plansDir, `${id}.json`);
10049
+ const planPath = join18(plansDir, `${id}.json`);
9871
10050
  const planPathRelative = `.locus/plans/${id}.json`;
9872
10051
  const displayDirective = directive;
9873
10052
  process.stderr.write(`
@@ -9901,7 +10080,7 @@ ${red("✗")} Planning failed: ${aiResult.error}
9901
10080
  `);
9902
10081
  return;
9903
10082
  }
9904
- if (!existsSync17(planPath)) {
10083
+ if (!existsSync18(planPath)) {
9905
10084
  process.stderr.write(`
9906
10085
  ${yellow("⚠")} Plan file was not created at ${bold(planPathRelative)}.
9907
10086
  `);
@@ -9979,16 +10158,21 @@ ${i.body?.slice(0, 300) ?? ""}`).join(`
9979
10158
 
9980
10159
  `);
9981
10160
  const { runAI: runAI2 } = await Promise.resolve().then(() => (init_run_ai(), exports_run_ai));
9982
- const prompt = `You are organizing GitHub issues for a sprint. Analyze these issues and suggest the optimal execution order.
10161
+ const prompt = `<role>
10162
+ You are organizing GitHub issues for a sprint. Analyze these issues and suggest the optimal execution order.
10163
+ </role>
9983
10164
 
9984
- Issues:
10165
+ <issues>
9985
10166
  ${issueDescriptions}
10167
+ </issues>
9986
10168
 
10169
+ <instructions>
9987
10170
  For each issue, output a line in this format:
9988
10171
  ORDER: #<number> <reason for this position>
9989
10172
 
9990
10173
  Order them so that dependencies are respected (issues that produce code needed by later issues should come first).
9991
- Start with foundational/setup tasks, then core features, then integration/testing.`;
10174
+ Start with foundational/setup tasks, then core features, then integration/testing.
10175
+ </instructions>`;
9992
10176
  const aiResult = await runAI2({
9993
10177
  prompt,
9994
10178
  provider: config.ai.provider,
@@ -10061,59 +10245,62 @@ ${bold("Suggested Order:")}
10061
10245
  }
10062
10246
  function buildPlanningPrompt(projectRoot, config, directive, sprintName, id, planPathRelative) {
10063
10247
  const parts = [];
10064
- parts.push(`You are a sprint planning assistant for the GitHub repository ${config.github.owner}/${config.github.repo}.`);
10065
- parts.push("");
10066
- parts.push(`DIRECTIVE: ${directive}`);
10067
- if (sprintName) {
10068
- parts.push(`SPRINT: ${sprintName}`);
10069
- }
10070
- parts.push("");
10071
- const locusPath = join17(projectRoot, "LOCUS.md");
10072
- if (existsSync17(locusPath)) {
10248
+ parts.push(`<role>
10249
+ You are a sprint planning assistant for the GitHub repository ${config.github.owner}/${config.github.repo}.
10250
+ </role>`);
10251
+ parts.push(`<directive>
10252
+ ${directive}${sprintName ? `
10253
+
10254
+ **Sprint:** ${sprintName}` : ""}
10255
+ </directive>`);
10256
+ const locusPath = join18(projectRoot, ".locus", "LOCUS.md");
10257
+ if (existsSync18(locusPath)) {
10073
10258
  const content = readFileSync13(locusPath, "utf-8");
10074
- parts.push("PROJECT CONTEXT (LOCUS.md):");
10075
- parts.push(content.slice(0, 3000));
10076
- parts.push("");
10259
+ parts.push(`<project-context>
10260
+ ${content.slice(0, 3000)}
10261
+ </project-context>`);
10077
10262
  }
10078
- const learningsPath = join17(projectRoot, ".locus", "LEARNINGS.md");
10079
- if (existsSync17(learningsPath)) {
10263
+ const learningsPath = join18(projectRoot, ".locus", "LEARNINGS.md");
10264
+ if (existsSync18(learningsPath)) {
10080
10265
  const content = readFileSync13(learningsPath, "utf-8");
10081
- parts.push("PAST LEARNINGS:");
10082
- parts.push(content.slice(0, 2000));
10083
- parts.push("");
10084
- }
10085
- parts.push("TASK:");
10086
- parts.push(`Break down the directive into specific, actionable GitHub issues and write them to the file: ${planPathRelative}`);
10087
- parts.push("");
10088
- parts.push(`Write ONLY a valid JSON file to ${planPathRelative} with this exact structure:`);
10089
- parts.push("");
10090
- parts.push("```json");
10091
- parts.push("{");
10092
- parts.push(` "id": "${id}",`);
10093
- parts.push(` "directive": ${JSON.stringify(directive)},`);
10094
- parts.push(` "sprint": ${sprintName ? JSON.stringify(sprintName) : "null"},`);
10095
- parts.push(` "createdAt": "${new Date().toISOString()}",`);
10096
- parts.push(' "issues": [');
10097
- parts.push(" {");
10098
- parts.push(' "order": 1,');
10099
- parts.push(' "title": "concise issue title",');
10100
- parts.push(' "body": "detailed markdown body with acceptance criteria",');
10101
- parts.push(' "priority": "critical|high|medium|low",');
10102
- parts.push(' "type": "feature|bug|chore|refactor|docs",');
10103
- parts.push(' "dependsOn": "none or comma-separated order numbers"');
10104
- parts.push(" }");
10105
- parts.push(" ]");
10106
- parts.push("}");
10107
- parts.push("```");
10108
- parts.push("");
10109
- parts.push("Requirements for the issues:");
10110
- parts.push("- Break the directive into 3-10 specific, actionable issues");
10111
- parts.push("- Each issue must be independently executable by an AI agent");
10112
- parts.push("- Order them so dependencies are respected (foundational tasks first)");
10113
- parts.push("- Write detailed issue bodies with clear acceptance criteria");
10114
- parts.push("- Use valid GitHub Markdown only in issue bodies");
10115
- parts.push("- Create the file using the Write tool — do not print the JSON to the terminal");
10266
+ parts.push(`<past-learnings>
10267
+ ${content.slice(0, 2000)}
10268
+ </past-learnings>`);
10269
+ }
10270
+ parts.push(`<task>
10271
+ Break down the directive into specific, actionable GitHub issues and write them to the file: ${planPathRelative}
10272
+
10273
+ Write ONLY a valid JSON file to ${planPathRelative} with this exact structure:
10274
+
10275
+ \`\`\`json
10276
+ {
10277
+ "id": "${id}",
10278
+ "directive": ${JSON.stringify(directive)},
10279
+ "sprint": ${sprintName ? JSON.stringify(sprintName) : "null"},
10280
+ "createdAt": "${new Date().toISOString()}",
10281
+ "issues": [
10282
+ {
10283
+ "order": 1,
10284
+ "title": "concise issue title",
10285
+ "body": "detailed markdown body with acceptance criteria",
10286
+ "priority": "critical|high|medium|low",
10287
+ "type": "feature|bug|chore|refactor|docs",
10288
+ "dependsOn": "none or comma-separated order numbers"
10289
+ }
10290
+ ]
10291
+ }
10292
+ \`\`\`
10293
+ </task>`);
10294
+ parts.push(`<requirements>
10295
+ - Break the directive into 3-10 specific, actionable issues
10296
+ - Each issue must be independently executable by an AI agent
10297
+ - Order them so dependencies are respected (foundational tasks first)
10298
+ - Write detailed issue bodies with clear acceptance criteria
10299
+ - Use valid GitHub Markdown only in issue bodies
10300
+ - Create the file using the Write tool — do not print the JSON to the terminal
10301
+ </requirements>`);
10116
10302
  return parts.join(`
10303
+
10117
10304
  `);
10118
10305
  }
10119
10306
  function sanitizePlanOutput(output) {
@@ -10253,9 +10440,9 @@ var exports_review = {};
10253
10440
  __export(exports_review, {
10254
10441
  reviewCommand: () => reviewCommand
10255
10442
  });
10256
- import { execSync as execSync16 } from "node:child_process";
10257
- import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
10258
- import { join as join18 } from "node:path";
10443
+ import { execSync as execSync17 } from "node:child_process";
10444
+ import { existsSync as existsSync19, readFileSync as readFileSync14 } from "node:fs";
10445
+ import { join as join19 } from "node:path";
10259
10446
  function printHelp2() {
10260
10447
  process.stderr.write(`
10261
10448
  ${bold("locus review")} — AI-powered code review
@@ -10331,7 +10518,7 @@ ${bold("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red(`
10331
10518
  async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
10332
10519
  let prInfo;
10333
10520
  try {
10334
- const result = execSync16(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10521
+ const result = execSync17(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10335
10522
  const raw = JSON.parse(result);
10336
10523
  prInfo = {
10337
10524
  number: raw.number,
@@ -10397,7 +10584,7 @@ ${output.slice(0, 60000)}
10397
10584
 
10398
10585
  ---
10399
10586
  _Reviewed by Locus AI (${config.ai.provider}/${flags.model ?? config.ai.model})_`;
10400
- execSync16(`gh pr comment ${pr.number} --body ${JSON.stringify(reviewBody)}`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10587
+ execSync17(`gh pr comment ${pr.number} --body ${JSON.stringify(reviewBody)}`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10401
10588
  process.stderr.write(` ${green("✓")} Review posted ${dim(`(${timer.formatted()})`)}
10402
10589
  `);
10403
10590
  } catch (e) {
@@ -10412,47 +10599,55 @@ _Reviewed by Locus AI (${config.ai.provider}/${flags.model ?? config.ai.model})_
10412
10599
  }
10413
10600
  function buildReviewPrompt(projectRoot, config, pr, diff, focus) {
10414
10601
  const parts = [];
10415
- parts.push(`You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.`);
10416
- parts.push("");
10417
- const locusPath = join18(projectRoot, "LOCUS.md");
10418
- if (existsSync18(locusPath)) {
10602
+ parts.push(`<role>
10603
+ You are an expert code reviewer for the ${config.github.owner}/${config.github.repo} repository.
10604
+ </role>`);
10605
+ const locusPath = join19(projectRoot, ".locus", "LOCUS.md");
10606
+ if (existsSync19(locusPath)) {
10419
10607
  const content = readFileSync14(locusPath, "utf-8");
10420
- parts.push("PROJECT CONTEXT:");
10421
- parts.push(content.slice(0, 2000));
10422
- parts.push("");
10608
+ parts.push(`<project-context>
10609
+ ${content.slice(0, 2000)}
10610
+ </project-context>`);
10423
10611
  }
10424
- parts.push(`PULL REQUEST #${pr.number}: ${pr.title}`);
10425
- parts.push(`Branch: ${pr.head} → ${pr.base}`);
10612
+ const prMeta = [`Branch: ${pr.head} ${pr.base}`];
10426
10613
  if (pr.body) {
10427
- parts.push(`Description:
10614
+ prMeta.push(`Description:
10428
10615
  ${pr.body.slice(0, 1000)}`);
10429
10616
  }
10430
- parts.push("");
10431
- parts.push("DIFF:");
10432
- parts.push(diff.slice(0, 50000));
10433
- parts.push("");
10434
- parts.push("REVIEW INSTRUCTIONS:");
10435
- parts.push("Provide a thorough code review. For each issue found, describe:");
10436
- parts.push("1. The file and approximate location");
10437
- parts.push("2. What the issue is");
10438
- parts.push("3. Why it matters");
10439
- parts.push("4. How to fix it");
10440
- parts.push("");
10441
- parts.push("Categories to check:");
10442
- parts.push("- Correctness: bugs, logic errors, edge cases");
10443
- parts.push("- Security: injection, XSS, auth issues, secret exposure");
10444
- parts.push("- Performance: N+1 queries, unnecessary allocations, missing caching");
10445
- parts.push("- Maintainability: naming, complexity, code organization");
10446
- parts.push("- Testing: missing tests, inadequate coverage");
10617
+ parts.push(`<pull-request number="${pr.number}" title="${pr.title}">
10618
+ ${prMeta.join(`
10619
+ `)}
10620
+ </pull-request>`);
10621
+ parts.push(`<diff>
10622
+ ${diff.slice(0, 50000)}
10623
+ </diff>`);
10624
+ let instructions = `Provide a thorough code review. For each issue found, describe:
10625
+ 1. The file and approximate location
10626
+ 2. What the issue is
10627
+ 3. Why it matters
10628
+ 4. How to fix it
10629
+
10630
+ Categories to check:
10631
+ - Correctness: bugs, logic errors, edge cases
10632
+ - Security: injection, XSS, auth issues, secret exposure
10633
+ - Performance: N+1 queries, unnecessary allocations, missing caching
10634
+ - Maintainability: naming, complexity, code organization
10635
+ - Testing: missing tests, inadequate coverage`;
10447
10636
  if (focus) {
10448
- parts.push("");
10449
- parts.push(`FOCUS AREAS: ${focus}`);
10450
- parts.push("Pay special attention to the above areas.");
10637
+ instructions += `
10638
+
10639
+ **Focus areas:** ${focus}
10640
+ Pay special attention to the above areas.`;
10451
10641
  }
10452
- parts.push("");
10453
- parts.push("End with an overall assessment: APPROVE, REQUEST_CHANGES, or COMMENT.");
10454
- parts.push("Be constructive and specific. Praise good patterns too.");
10642
+ instructions += `
10643
+
10644
+ End with an overall assessment: APPROVE, REQUEST_CHANGES, or COMMENT.
10645
+ Be constructive and specific. Praise good patterns too.`;
10646
+ parts.push(`<review-instructions>
10647
+ ${instructions}
10648
+ </review-instructions>`);
10455
10649
  return parts.join(`
10650
+
10456
10651
  `);
10457
10652
  }
10458
10653
  var init_review = __esm(() => {
@@ -10468,7 +10663,7 @@ var exports_iterate = {};
10468
10663
  __export(exports_iterate, {
10469
10664
  iterateCommand: () => iterateCommand
10470
10665
  });
10471
- import { execSync as execSync17 } from "node:child_process";
10666
+ import { execSync as execSync18 } from "node:child_process";
10472
10667
  function printHelp3() {
10473
10668
  process.stderr.write(`
10474
10669
  ${bold("locus iterate")} — Re-execute tasks with PR feedback
@@ -10678,12 +10873,12 @@ ${bold("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red(`✗ ${fa
10678
10873
  }
10679
10874
  function findPRForIssue(projectRoot, issueNumber) {
10680
10875
  try {
10681
- const result = execSync17(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10876
+ const result = execSync18(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10682
10877
  const parsed = JSON.parse(result);
10683
10878
  if (parsed.length > 0) {
10684
10879
  return parsed[0].number;
10685
10880
  }
10686
- const branchResult = execSync17(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10881
+ const branchResult = execSync18(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
10687
10882
  const branchParsed = JSON.parse(branchResult);
10688
10883
  if (branchParsed.length > 0) {
10689
10884
  return branchParsed[0].number;
@@ -10718,14 +10913,14 @@ __export(exports_discuss, {
10718
10913
  discussCommand: () => discussCommand
10719
10914
  });
10720
10915
  import {
10721
- existsSync as existsSync19,
10722
- mkdirSync as mkdirSync12,
10916
+ existsSync as existsSync20,
10917
+ mkdirSync as mkdirSync13,
10723
10918
  readdirSync as readdirSync8,
10724
10919
  readFileSync as readFileSync15,
10725
10920
  unlinkSync as unlinkSync5,
10726
10921
  writeFileSync as writeFileSync10
10727
10922
  } from "node:fs";
10728
- import { join as join19 } from "node:path";
10923
+ import { join as join20 } from "node:path";
10729
10924
  function printHelp4() {
10730
10925
  process.stderr.write(`
10731
10926
  ${bold("locus discuss")} — AI-powered architectural discussions
@@ -10747,12 +10942,12 @@ ${bold("Examples:")}
10747
10942
  `);
10748
10943
  }
10749
10944
  function getDiscussionsDir(projectRoot) {
10750
- return join19(projectRoot, ".locus", "discussions");
10945
+ return join20(projectRoot, ".locus", "discussions");
10751
10946
  }
10752
10947
  function ensureDiscussionsDir(projectRoot) {
10753
10948
  const dir = getDiscussionsDir(projectRoot);
10754
- if (!existsSync19(dir)) {
10755
- mkdirSync12(dir, { recursive: true });
10949
+ if (!existsSync20(dir)) {
10950
+ mkdirSync13(dir, { recursive: true });
10756
10951
  }
10757
10952
  return dir;
10758
10953
  }
@@ -10786,7 +10981,7 @@ async function discussCommand(projectRoot, args, flags = {}) {
10786
10981
  }
10787
10982
  function listDiscussions(projectRoot) {
10788
10983
  const dir = getDiscussionsDir(projectRoot);
10789
- if (!existsSync19(dir)) {
10984
+ if (!existsSync20(dir)) {
10790
10985
  process.stderr.write(`${dim("No discussions yet.")}
10791
10986
  `);
10792
10987
  return;
@@ -10803,7 +10998,7 @@ ${bold("Discussions:")}
10803
10998
  `);
10804
10999
  for (const file of files) {
10805
11000
  const id = file.replace(".md", "");
10806
- const content = readFileSync15(join19(dir, file), "utf-8");
11001
+ const content = readFileSync15(join20(dir, file), "utf-8");
10807
11002
  const titleMatch = content.match(/^#\s+(.+)/m);
10808
11003
  const title = titleMatch ? titleMatch[1] : id;
10809
11004
  const dateMatch = content.match(/\*\*Date:\*\*\s*(.+)/);
@@ -10821,7 +11016,7 @@ function showDiscussion(projectRoot, id) {
10821
11016
  return;
10822
11017
  }
10823
11018
  const dir = getDiscussionsDir(projectRoot);
10824
- if (!existsSync19(dir)) {
11019
+ if (!existsSync20(dir)) {
10825
11020
  process.stderr.write(`${red("✗")} No discussions found.
10826
11021
  `);
10827
11022
  return;
@@ -10833,7 +11028,7 @@ function showDiscussion(projectRoot, id) {
10833
11028
  `);
10834
11029
  return;
10835
11030
  }
10836
- const content = readFileSync15(join19(dir, match), "utf-8");
11031
+ const content = readFileSync15(join20(dir, match), "utf-8");
10837
11032
  process.stdout.write(`${content}
10838
11033
  `);
10839
11034
  }
@@ -10844,7 +11039,7 @@ function deleteDiscussion(projectRoot, id) {
10844
11039
  return;
10845
11040
  }
10846
11041
  const dir = getDiscussionsDir(projectRoot);
10847
- if (!existsSync19(dir)) {
11042
+ if (!existsSync20(dir)) {
10848
11043
  process.stderr.write(`${red("✗")} No discussions found.
10849
11044
  `);
10850
11045
  return;
@@ -10856,7 +11051,7 @@ function deleteDiscussion(projectRoot, id) {
10856
11051
  `);
10857
11052
  return;
10858
11053
  }
10859
- unlinkSync5(join19(dir, match));
11054
+ unlinkSync5(join20(dir, match));
10860
11055
  process.stderr.write(`${green("✓")} Deleted discussion: ${match.replace(".md", "")}
10861
11056
  `);
10862
11057
  }
@@ -10869,7 +11064,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
10869
11064
  return;
10870
11065
  }
10871
11066
  const dir = getDiscussionsDir(projectRoot);
10872
- if (!existsSync19(dir)) {
11067
+ if (!existsSync20(dir)) {
10873
11068
  process.stderr.write(`${red("✗")} No discussions found.
10874
11069
  `);
10875
11070
  return;
@@ -10881,7 +11076,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
10881
11076
  `);
10882
11077
  return;
10883
11078
  }
10884
- const content = readFileSync15(join19(dir, match), "utf-8");
11079
+ const content = readFileSync15(join20(dir, match), "utf-8");
10885
11080
  const titleMatch = content.match(/^#\s+(.+)/m);
10886
11081
  const discussionTitle = titleMatch ? titleMatch[1].trim() : id;
10887
11082
  await planCommand(projectRoot, [
@@ -10995,7 +11190,7 @@ ${turn.content}`;
10995
11190
  ...conversation.length > 1 ? [`---`, ``, `## Discussion Transcript`, ``, transcript, ``] : []
10996
11191
  ].join(`
10997
11192
  `);
10998
- writeFileSync10(join19(dir, `${id}.md`), markdown, "utf-8");
11193
+ writeFileSync10(join20(dir, `${id}.md`), markdown, "utf-8");
10999
11194
  process.stderr.write(`
11000
11195
  ${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
11001
11196
  `);
@@ -11007,55 +11202,67 @@ ${green("✓")} Discussion saved: ${cyan(id)} ${dim(`(${timer.formatted()})`)}
11007
11202
  }
11008
11203
  function buildDiscussionPrompt(projectRoot, config, topic, conversation, forceFinal) {
11009
11204
  const parts = [];
11010
- parts.push(`You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.`);
11011
- parts.push("");
11012
- const locusPath = join19(projectRoot, "LOCUS.md");
11013
- if (existsSync19(locusPath)) {
11205
+ parts.push(`<role>
11206
+ You are a senior software architect and consultant for the ${config.github.owner}/${config.github.repo} project.
11207
+ </role>`);
11208
+ const locusPath = join20(projectRoot, ".locus", "LOCUS.md");
11209
+ if (existsSync20(locusPath)) {
11014
11210
  const content = readFileSync15(locusPath, "utf-8");
11015
- parts.push("PROJECT CONTEXT:");
11016
- parts.push(content.slice(0, 3000));
11017
- parts.push("");
11211
+ parts.push(`<project-context>
11212
+ ${content.slice(0, 3000)}
11213
+ </project-context>`);
11018
11214
  }
11019
- const learningsPath = join19(projectRoot, ".locus", "LEARNINGS.md");
11020
- if (existsSync19(learningsPath)) {
11215
+ const learningsPath = join20(projectRoot, ".locus", "LEARNINGS.md");
11216
+ if (existsSync20(learningsPath)) {
11021
11217
  const content = readFileSync15(learningsPath, "utf-8");
11022
- parts.push("PAST LEARNINGS:");
11023
- parts.push(content.slice(0, 2000));
11024
- parts.push("");
11218
+ parts.push(`<past-learnings>
11219
+ ${content.slice(0, 2000)}
11220
+ </past-learnings>`);
11025
11221
  }
11026
- parts.push(`DISCUSSION TOPIC: ${topic}`);
11027
- parts.push("");
11222
+ parts.push(`<discussion-topic>
11223
+ ${topic}
11224
+ </discussion-topic>`);
11028
11225
  if (conversation.length === 0) {
11029
- parts.push("Before providing recommendations, you need to ask targeted clarifying questions.");
11030
- parts.push("");
11031
- parts.push("Ask 3-5 focused questions that will significantly improve the quality of your analysis.");
11032
- parts.push("Format as a numbered list. Be specific and focused on the most important unknowns.");
11033
- parts.push("Do NOT provide any analysis yet questions only.");
11226
+ parts.push(`<instructions>
11227
+ Before providing recommendations, you need to ask targeted clarifying questions.
11228
+
11229
+ Ask 3-5 focused questions that will significantly improve the quality of your analysis.
11230
+ Format as a numbered list. Be specific and focused on the most important unknowns.
11231
+ Do NOT provide any analysis yet — questions only.
11232
+ </instructions>`);
11034
11233
  } else {
11035
- parts.push("CONVERSATION SO FAR:");
11036
- parts.push("");
11234
+ const historyLines = [];
11037
11235
  for (const turn of conversation) {
11038
11236
  if (turn.role === "user") {
11039
- parts.push(`USER: ${turn.content}`);
11237
+ historyLines.push(`USER: ${turn.content}`);
11040
11238
  } else {
11041
- parts.push(`ASSISTANT: ${turn.content}`);
11239
+ historyLines.push(`ASSISTANT: ${turn.content}`);
11042
11240
  }
11043
- parts.push("");
11044
11241
  }
11242
+ parts.push(`<conversation-history>
11243
+ ${historyLines.join(`
11244
+
11245
+ `)}
11246
+ </conversation-history>`);
11045
11247
  if (forceFinal) {
11046
- parts.push("Based on everything discussed, provide your complete analysis and recommendations now.");
11047
- parts.push("Format as a thorough markdown document with a clear title (# Heading), sections, trade-offs, and actionable recommendations.");
11248
+ parts.push(`<instructions>
11249
+ Based on everything discussed, provide your complete analysis and recommendations now.
11250
+ Format as a thorough markdown document with a clear title (# Heading), sections, trade-offs, and actionable recommendations.
11251
+ </instructions>`);
11048
11252
  } else {
11049
- parts.push("Review the information gathered so far.");
11050
- parts.push("");
11051
- parts.push("If you have enough information to make a thorough recommendation:");
11052
- parts.push(" → Provide a complete analysis as a markdown document with a title (# Heading), sections, trade-offs, and concrete recommendations.");
11053
- parts.push("");
11054
- parts.push("If you still need key information to give a good answer:");
11055
- parts.push(" → Ask 2-3 more focused follow-up questions (numbered list only, no analysis yet).");
11253
+ parts.push(`<instructions>
11254
+ Review the information gathered so far.
11255
+
11256
+ If you have enough information to make a thorough recommendation:
11257
+ → Provide a complete analysis as a markdown document with a title (# Heading), sections, trade-offs, and concrete recommendations.
11258
+
11259
+ If you still need key information to give a good answer:
11260
+ → Ask 2-3 more focused follow-up questions (numbered list only, no analysis yet).
11261
+ </instructions>`);
11056
11262
  }
11057
11263
  }
11058
11264
  return parts.join(`
11265
+
11059
11266
  `);
11060
11267
  }
11061
11268
  var MAX_DISCUSSION_ROUNDS = 5;
@@ -11077,8 +11284,8 @@ __export(exports_artifacts, {
11077
11284
  formatDate: () => formatDate2,
11078
11285
  artifactsCommand: () => artifactsCommand
11079
11286
  });
11080
- import { existsSync as existsSync20, readdirSync as readdirSync9, readFileSync as readFileSync16, statSync as statSync4 } from "node:fs";
11081
- import { join as join20 } from "node:path";
11287
+ import { existsSync as existsSync21, readdirSync as readdirSync9, readFileSync as readFileSync16, statSync as statSync4 } from "node:fs";
11288
+ import { join as join21 } from "node:path";
11082
11289
  function printHelp5() {
11083
11290
  process.stderr.write(`
11084
11291
  ${bold("locus artifacts")} — View and manage AI-generated artifacts
@@ -11098,14 +11305,14 @@ ${dim("Artifact names support partial matching.")}
11098
11305
  `);
11099
11306
  }
11100
11307
  function getArtifactsDir(projectRoot) {
11101
- return join20(projectRoot, ".locus", "artifacts");
11308
+ return join21(projectRoot, ".locus", "artifacts");
11102
11309
  }
11103
11310
  function listArtifacts(projectRoot) {
11104
11311
  const dir = getArtifactsDir(projectRoot);
11105
- if (!existsSync20(dir))
11312
+ if (!existsSync21(dir))
11106
11313
  return [];
11107
11314
  return readdirSync9(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
11108
- const filePath = join20(dir, fileName);
11315
+ const filePath = join21(dir, fileName);
11109
11316
  const stat = statSync4(filePath);
11110
11317
  return {
11111
11318
  name: fileName.replace(/\.md$/, ""),
@@ -11118,8 +11325,8 @@ function listArtifacts(projectRoot) {
11118
11325
  function readArtifact(projectRoot, name) {
11119
11326
  const dir = getArtifactsDir(projectRoot);
11120
11327
  const fileName = name.endsWith(".md") ? name : `${name}.md`;
11121
- const filePath = join20(dir, fileName);
11122
- if (!existsSync20(filePath))
11328
+ const filePath = join21(dir, fileName);
11329
+ if (!existsSync21(filePath))
11123
11330
  return null;
11124
11331
  const stat = statSync4(filePath);
11125
11332
  return {
@@ -11282,11 +11489,11 @@ var init_artifacts = __esm(() => {
11282
11489
  });
11283
11490
 
11284
11491
  // src/commands/sandbox.ts
11285
- var exports_sandbox = {};
11286
- __export(exports_sandbox, {
11492
+ var exports_sandbox2 = {};
11493
+ __export(exports_sandbox2, {
11287
11494
  sandboxCommand: () => sandboxCommand
11288
11495
  });
11289
- import { execSync as execSync18, spawn as spawn6 } from "node:child_process";
11496
+ import { execSync as execSync19, spawn as spawn6 } from "node:child_process";
11290
11497
  function printSandboxHelp() {
11291
11498
  process.stderr.write(`
11292
11499
  ${bold("locus sandbox")} — Manage Docker sandbox lifecycle
@@ -11400,7 +11607,7 @@ async function handleAgentLogin(projectRoot, agent) {
11400
11607
  process.stderr.write(`Creating sandbox ${bold(sandboxName)} with workspace ${dim(projectRoot)}...
11401
11608
  `);
11402
11609
  try {
11403
- execSync18(`docker sandbox run --name ${sandboxName} claude ${projectRoot} -- --version`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
11610
+ execSync19(`docker sandbox run --name ${sandboxName} claude ${projectRoot} -- --version`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
11404
11611
  } catch {}
11405
11612
  if (!isSandboxAlive(sandboxName)) {
11406
11613
  process.stderr.write(`${red("✗")} Failed to create sandbox.
@@ -11463,7 +11670,7 @@ function handleRemove(projectRoot) {
11463
11670
  process.stderr.write(`Removing sandbox ${bold(sandboxName)}...
11464
11671
  `);
11465
11672
  try {
11466
- execSync18(`docker sandbox rm ${sandboxName}`, {
11673
+ execSync19(`docker sandbox rm ${sandboxName}`, {
11467
11674
  encoding: "utf-8",
11468
11675
  stdio: ["pipe", "pipe", "pipe"],
11469
11676
  timeout: 15000
@@ -11500,7 +11707,7 @@ ${bold("Sandbox Status")}
11500
11707
  }
11501
11708
  async function ensureCodexInSandbox(sandboxName) {
11502
11709
  try {
11503
- execSync18(`docker sandbox exec ${sandboxName} which codex`, {
11710
+ execSync19(`docker sandbox exec ${sandboxName} which codex`, {
11504
11711
  stdio: ["pipe", "pipe", "pipe"],
11505
11712
  timeout: 5000
11506
11713
  });
@@ -11508,7 +11715,7 @@ async function ensureCodexInSandbox(sandboxName) {
11508
11715
  process.stderr.write(`Installing codex in sandbox...
11509
11716
  `);
11510
11717
  try {
11511
- execSync18(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
11718
+ execSync19(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
11512
11719
  } catch {
11513
11720
  process.stderr.write(`${red("✗")} Failed to install codex in sandbox.
11514
11721
  `);
@@ -11517,7 +11724,7 @@ async function ensureCodexInSandbox(sandboxName) {
11517
11724
  }
11518
11725
  function isSandboxAlive(name) {
11519
11726
  try {
11520
- const output = execSync18("docker sandbox ls", {
11727
+ const output = execSync19("docker sandbox ls", {
11521
11728
  encoding: "utf-8",
11522
11729
  stdio: ["pipe", "pipe", "pipe"],
11523
11730
  timeout: 5000
@@ -11540,13 +11747,13 @@ init_context();
11540
11747
  init_logger();
11541
11748
  init_rate_limiter();
11542
11749
  init_terminal();
11543
- import { existsSync as existsSync21, readFileSync as readFileSync17 } from "node:fs";
11544
- import { join as join21 } from "node:path";
11750
+ import { existsSync as existsSync22, readFileSync as readFileSync17 } from "node:fs";
11751
+ import { join as join22 } from "node:path";
11545
11752
  import { fileURLToPath } from "node:url";
11546
11753
  function getCliVersion() {
11547
11754
  const fallbackVersion = "0.0.0";
11548
- const packageJsonPath = join21(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
11549
- if (!existsSync21(packageJsonPath)) {
11755
+ const packageJsonPath = join22(fileURLToPath(new URL(".", import.meta.url)), "..", "package.json");
11756
+ if (!existsSync22(packageJsonPath)) {
11550
11757
  return fallbackVersion;
11551
11758
  }
11552
11759
  try {
@@ -11720,6 +11927,46 @@ function resolveAlias(command) {
11720
11927
  };
11721
11928
  return aliases[command] ?? command;
11722
11929
  }
11930
+ function requiresSandboxSync(command, args, flags) {
11931
+ if (flags.noSandbox)
11932
+ return false;
11933
+ if (flags.help)
11934
+ return false;
11935
+ switch (command) {
11936
+ case "run":
11937
+ case "review":
11938
+ case "iterate":
11939
+ return true;
11940
+ case "exec":
11941
+ return args[0] !== "sessions" && args[0] !== "help";
11942
+ case "issue":
11943
+ return args[0] === "create";
11944
+ case "plan":
11945
+ if (args.length === 0)
11946
+ return false;
11947
+ return !["list", "show", "approve", "help"].includes(args[0]);
11948
+ case "discuss":
11949
+ if (args.length === 0)
11950
+ return false;
11951
+ return !["list", "show", "delete", "help"].includes(args[0]);
11952
+ case "config":
11953
+ return args[0] === "set";
11954
+ default:
11955
+ return false;
11956
+ }
11957
+ }
11958
+ async function prepareSandbox() {
11959
+ const { Spinner: Spinner2 } = await Promise.resolve().then(() => (init_progress(), exports_progress));
11960
+ const { detectSandboxSupport: detectSandboxSupport2 } = await Promise.resolve().then(() => (init_sandbox(), exports_sandbox));
11961
+ const spinner = new Spinner2;
11962
+ spinner.start("Preparing sandbox...");
11963
+ const status = await detectSandboxSupport2();
11964
+ if (status.available) {
11965
+ spinner.succeed("Sandbox ready");
11966
+ } else {
11967
+ spinner.warn(`Sandbox not available: ${status.reason}`);
11968
+ }
11969
+ }
11723
11970
  async function main() {
11724
11971
  const parsed = parseArgs(process.argv);
11725
11972
  if (parsed.flags.version) {
@@ -11739,7 +11986,7 @@ async function main() {
11739
11986
  try {
11740
11987
  const root = getGitRoot(cwd);
11741
11988
  if (isInitialized(root)) {
11742
- logDir = join21(root, ".locus", "logs");
11989
+ logDir = join22(root, ".locus", "logs");
11743
11990
  getRateLimiter(root);
11744
11991
  }
11745
11992
  } catch {}
@@ -11821,6 +12068,12 @@ async function main() {
11821
12068
  `);
11822
12069
  process.exit(1);
11823
12070
  }
12071
+ if (requiresSandboxSync(command, parsed.args, parsed.flags)) {
12072
+ const config = loadConfig(projectRoot);
12073
+ if (config.sandbox.enabled) {
12074
+ await prepareSandbox();
12075
+ }
12076
+ }
11824
12077
  switch (command) {
11825
12078
  case "config": {
11826
12079
  const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
@@ -11917,7 +12170,7 @@ async function main() {
11917
12170
  break;
11918
12171
  }
11919
12172
  case "sandbox": {
11920
- const { sandboxCommand: sandboxCommand2 } = await Promise.resolve().then(() => (init_sandbox2(), exports_sandbox));
12173
+ const { sandboxCommand: sandboxCommand2 } = await Promise.resolve().then(() => (init_sandbox2(), exports_sandbox2));
11921
12174
  const sandboxArgs = parsed.flags.help ? ["help"] : parsed.args;
11922
12175
  await sandboxCommand2(projectRoot, sandboxArgs);
11923
12176
  break;