werkmeister 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -44,7 +44,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
44
44
  var define_SANDBOX_BUILD_default;
45
45
  var init_define_SANDBOX_BUILD = __esm({
46
46
  "<define:__SANDBOX_BUILD__>"() {
47
- define_SANDBOX_BUILD_default = { sha: "8bf0338bcc3351a5e1154beba68c1b6f9beea71a", dirty: false };
47
+ define_SANDBOX_BUILD_default = { sha: "79cdadc2a1a4f3138deddeb36dbdcbfe124eccef", dirty: false };
48
48
  }
49
49
  });
50
50
 
@@ -1171,8 +1171,8 @@ var require_command = __commonJS({
1171
1171
  init_define_SANDBOX_BUILD();
1172
1172
  var EventEmitter2 = __require("node:events").EventEmitter;
1173
1173
  var childProcess = __require("node:child_process");
1174
- var path73 = __require("node:path");
1175
- var fs65 = __require("node:fs");
1174
+ var path74 = __require("node:path");
1175
+ var fs66 = __require("node:fs");
1176
1176
  var process4 = __require("node:process");
1177
1177
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1178
1178
  var { CommanderError: CommanderError2 } = require_error();
@@ -2153,7 +2153,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2153
2153
  * @param {string} subcommandName
2154
2154
  */
2155
2155
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2156
- if (fs65.existsSync(executableFile)) return;
2156
+ if (fs66.existsSync(executableFile)) return;
2157
2157
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2158
2158
  const executableMissing = `'${executableFile}' does not exist
2159
2159
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2171,11 +2171,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2171
2171
  let launchWithNode = false;
2172
2172
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2173
2173
  function findFile(baseDir, baseName) {
2174
- const localBin = path73.resolve(baseDir, baseName);
2175
- if (fs65.existsSync(localBin)) return localBin;
2176
- if (sourceExt.includes(path73.extname(baseName))) return void 0;
2174
+ const localBin = path74.resolve(baseDir, baseName);
2175
+ if (fs66.existsSync(localBin)) return localBin;
2176
+ if (sourceExt.includes(path74.extname(baseName))) return void 0;
2177
2177
  const foundExt = sourceExt.find(
2178
- (ext) => fs65.existsSync(`${localBin}${ext}`)
2178
+ (ext) => fs66.existsSync(`${localBin}${ext}`)
2179
2179
  );
2180
2180
  if (foundExt) return `${localBin}${foundExt}`;
2181
2181
  return void 0;
@@ -2187,21 +2187,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2187
2187
  if (this._scriptPath) {
2188
2188
  let resolvedScriptPath;
2189
2189
  try {
2190
- resolvedScriptPath = fs65.realpathSync(this._scriptPath);
2190
+ resolvedScriptPath = fs66.realpathSync(this._scriptPath);
2191
2191
  } catch {
2192
2192
  resolvedScriptPath = this._scriptPath;
2193
2193
  }
2194
- executableDir = path73.resolve(
2195
- path73.dirname(resolvedScriptPath),
2194
+ executableDir = path74.resolve(
2195
+ path74.dirname(resolvedScriptPath),
2196
2196
  executableDir
2197
2197
  );
2198
2198
  }
2199
2199
  if (executableDir) {
2200
2200
  let localFile = findFile(executableDir, executableFile);
2201
2201
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2202
- const legacyName = path73.basename(
2202
+ const legacyName = path74.basename(
2203
2203
  this._scriptPath,
2204
- path73.extname(this._scriptPath)
2204
+ path74.extname(this._scriptPath)
2205
2205
  );
2206
2206
  if (legacyName !== this._name) {
2207
2207
  localFile = findFile(
@@ -2212,7 +2212,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2212
2212
  }
2213
2213
  executableFile = localFile || executableFile;
2214
2214
  }
2215
- launchWithNode = sourceExt.includes(path73.extname(executableFile));
2215
+ launchWithNode = sourceExt.includes(path74.extname(executableFile));
2216
2216
  let proc;
2217
2217
  if (process4.platform !== "win32") {
2218
2218
  if (launchWithNode) {
@@ -3059,7 +3059,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3059
3059
  * @return {Command}
3060
3060
  */
3061
3061
  nameFromFilename(filename) {
3062
- this._name = path73.basename(filename, path73.extname(filename));
3062
+ this._name = path74.basename(filename, path74.extname(filename));
3063
3063
  return this;
3064
3064
  }
3065
3065
  /**
@@ -3073,9 +3073,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3073
3073
  * @param {string} [path]
3074
3074
  * @return {(string|null|Command)}
3075
3075
  */
3076
- executableDir(path74) {
3077
- if (path74 === void 0) return this._executableDir;
3078
- this._executableDir = path74;
3076
+ executableDir(path75) {
3077
+ if (path75 === void 0) return this._executableDir;
3078
+ this._executableDir = path75;
3079
3079
  return this;
3080
3080
  }
3081
3081
  /**
@@ -3462,17 +3462,17 @@ var require_visit = __commonJS({
3462
3462
  visit2.BREAK = BREAK;
3463
3463
  visit2.SKIP = SKIP;
3464
3464
  visit2.REMOVE = REMOVE;
3465
- function visit_(key, node, visitor, path73) {
3466
- const ctrl = callVisitor(key, node, visitor, path73);
3465
+ function visit_(key, node, visitor, path74) {
3466
+ const ctrl = callVisitor(key, node, visitor, path74);
3467
3467
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
3468
- replaceNode(key, path73, ctrl);
3469
- return visit_(key, ctrl, visitor, path73);
3468
+ replaceNode(key, path74, ctrl);
3469
+ return visit_(key, ctrl, visitor, path74);
3470
3470
  }
3471
3471
  if (typeof ctrl !== "symbol") {
3472
3472
  if (identity.isCollection(node)) {
3473
- path73 = Object.freeze(path73.concat(node));
3473
+ path74 = Object.freeze(path74.concat(node));
3474
3474
  for (let i = 0; i < node.items.length; ++i) {
3475
- const ci = visit_(i, node.items[i], visitor, path73);
3475
+ const ci = visit_(i, node.items[i], visitor, path74);
3476
3476
  if (typeof ci === "number")
3477
3477
  i = ci - 1;
3478
3478
  else if (ci === BREAK)
@@ -3483,13 +3483,13 @@ var require_visit = __commonJS({
3483
3483
  }
3484
3484
  }
3485
3485
  } else if (identity.isPair(node)) {
3486
- path73 = Object.freeze(path73.concat(node));
3487
- const ck = visit_("key", node.key, visitor, path73);
3486
+ path74 = Object.freeze(path74.concat(node));
3487
+ const ck = visit_("key", node.key, visitor, path74);
3488
3488
  if (ck === BREAK)
3489
3489
  return BREAK;
3490
3490
  else if (ck === REMOVE)
3491
3491
  node.key = null;
3492
- const cv = visit_("value", node.value, visitor, path73);
3492
+ const cv = visit_("value", node.value, visitor, path74);
3493
3493
  if (cv === BREAK)
3494
3494
  return BREAK;
3495
3495
  else if (cv === REMOVE)
@@ -3510,17 +3510,17 @@ var require_visit = __commonJS({
3510
3510
  visitAsync.BREAK = BREAK;
3511
3511
  visitAsync.SKIP = SKIP;
3512
3512
  visitAsync.REMOVE = REMOVE;
3513
- async function visitAsync_(key, node, visitor, path73) {
3514
- const ctrl = await callVisitor(key, node, visitor, path73);
3513
+ async function visitAsync_(key, node, visitor, path74) {
3514
+ const ctrl = await callVisitor(key, node, visitor, path74);
3515
3515
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
3516
- replaceNode(key, path73, ctrl);
3517
- return visitAsync_(key, ctrl, visitor, path73);
3516
+ replaceNode(key, path74, ctrl);
3517
+ return visitAsync_(key, ctrl, visitor, path74);
3518
3518
  }
3519
3519
  if (typeof ctrl !== "symbol") {
3520
3520
  if (identity.isCollection(node)) {
3521
- path73 = Object.freeze(path73.concat(node));
3521
+ path74 = Object.freeze(path74.concat(node));
3522
3522
  for (let i = 0; i < node.items.length; ++i) {
3523
- const ci = await visitAsync_(i, node.items[i], visitor, path73);
3523
+ const ci = await visitAsync_(i, node.items[i], visitor, path74);
3524
3524
  if (typeof ci === "number")
3525
3525
  i = ci - 1;
3526
3526
  else if (ci === BREAK)
@@ -3531,13 +3531,13 @@ var require_visit = __commonJS({
3531
3531
  }
3532
3532
  }
3533
3533
  } else if (identity.isPair(node)) {
3534
- path73 = Object.freeze(path73.concat(node));
3535
- const ck = await visitAsync_("key", node.key, visitor, path73);
3534
+ path74 = Object.freeze(path74.concat(node));
3535
+ const ck = await visitAsync_("key", node.key, visitor, path74);
3536
3536
  if (ck === BREAK)
3537
3537
  return BREAK;
3538
3538
  else if (ck === REMOVE)
3539
3539
  node.key = null;
3540
- const cv = await visitAsync_("value", node.value, visitor, path73);
3540
+ const cv = await visitAsync_("value", node.value, visitor, path74);
3541
3541
  if (cv === BREAK)
3542
3542
  return BREAK;
3543
3543
  else if (cv === REMOVE)
@@ -3564,23 +3564,23 @@ var require_visit = __commonJS({
3564
3564
  }
3565
3565
  return visitor;
3566
3566
  }
3567
- function callVisitor(key, node, visitor, path73) {
3567
+ function callVisitor(key, node, visitor, path74) {
3568
3568
  if (typeof visitor === "function")
3569
- return visitor(key, node, path73);
3569
+ return visitor(key, node, path74);
3570
3570
  if (identity.isMap(node))
3571
- return visitor.Map?.(key, node, path73);
3571
+ return visitor.Map?.(key, node, path74);
3572
3572
  if (identity.isSeq(node))
3573
- return visitor.Seq?.(key, node, path73);
3573
+ return visitor.Seq?.(key, node, path74);
3574
3574
  if (identity.isPair(node))
3575
- return visitor.Pair?.(key, node, path73);
3575
+ return visitor.Pair?.(key, node, path74);
3576
3576
  if (identity.isScalar(node))
3577
- return visitor.Scalar?.(key, node, path73);
3577
+ return visitor.Scalar?.(key, node, path74);
3578
3578
  if (identity.isAlias(node))
3579
- return visitor.Alias?.(key, node, path73);
3579
+ return visitor.Alias?.(key, node, path74);
3580
3580
  return void 0;
3581
3581
  }
3582
- function replaceNode(key, path73, node) {
3583
- const parent = path73[path73.length - 1];
3582
+ function replaceNode(key, path74, node) {
3583
+ const parent = path74[path74.length - 1];
3584
3584
  if (identity.isCollection(parent)) {
3585
3585
  parent.items[key] = node;
3586
3586
  } else if (identity.isPair(parent)) {
@@ -4199,10 +4199,10 @@ var require_Collection = __commonJS({
4199
4199
  var createNode = require_createNode();
4200
4200
  var identity = require_identity();
4201
4201
  var Node = require_Node();
4202
- function collectionFromPath(schema, path73, value) {
4202
+ function collectionFromPath(schema, path74, value) {
4203
4203
  let v = value;
4204
- for (let i = path73.length - 1; i >= 0; --i) {
4205
- const k = path73[i];
4204
+ for (let i = path74.length - 1; i >= 0; --i) {
4205
+ const k = path74[i];
4206
4206
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
4207
4207
  const a = [];
4208
4208
  a[k] = v;
@@ -4221,7 +4221,7 @@ var require_Collection = __commonJS({
4221
4221
  sourceObjects: /* @__PURE__ */ new Map()
4222
4222
  });
4223
4223
  }
4224
- var isEmptyPath = (path73) => path73 == null || typeof path73 === "object" && !!path73[Symbol.iterator]().next().done;
4224
+ var isEmptyPath = (path74) => path74 == null || typeof path74 === "object" && !!path74[Symbol.iterator]().next().done;
4225
4225
  var Collection = class extends Node.NodeBase {
4226
4226
  constructor(type, schema) {
4227
4227
  super(type);
@@ -4251,11 +4251,11 @@ var require_Collection = __commonJS({
4251
4251
  * be a Pair instance or a `{ key, value }` object, which may not have a key
4252
4252
  * that already exists in the map.
4253
4253
  */
4254
- addIn(path73, value) {
4255
- if (isEmptyPath(path73))
4254
+ addIn(path74, value) {
4255
+ if (isEmptyPath(path74))
4256
4256
  this.add(value);
4257
4257
  else {
4258
- const [key, ...rest] = path73;
4258
+ const [key, ...rest] = path74;
4259
4259
  const node = this.get(key, true);
4260
4260
  if (identity.isCollection(node))
4261
4261
  node.addIn(rest, value);
@@ -4269,8 +4269,8 @@ var require_Collection = __commonJS({
4269
4269
  * Removes a value from the collection.
4270
4270
  * @returns `true` if the item was found and removed.
4271
4271
  */
4272
- deleteIn(path73) {
4273
- const [key, ...rest] = path73;
4272
+ deleteIn(path74) {
4273
+ const [key, ...rest] = path74;
4274
4274
  if (rest.length === 0)
4275
4275
  return this.delete(key);
4276
4276
  const node = this.get(key, true);
@@ -4284,8 +4284,8 @@ var require_Collection = __commonJS({
4284
4284
  * scalar values from their surrounding node; to disable set `keepScalar` to
4285
4285
  * `true` (collections are always returned intact).
4286
4286
  */
4287
- getIn(path73, keepScalar) {
4288
- const [key, ...rest] = path73;
4287
+ getIn(path74, keepScalar) {
4288
+ const [key, ...rest] = path74;
4289
4289
  const node = this.get(key, true);
4290
4290
  if (rest.length === 0)
4291
4291
  return !keepScalar && identity.isScalar(node) ? node.value : node;
@@ -4303,8 +4303,8 @@ var require_Collection = __commonJS({
4303
4303
  /**
4304
4304
  * Checks if the collection includes a value with the key `key`.
4305
4305
  */
4306
- hasIn(path73) {
4307
- const [key, ...rest] = path73;
4306
+ hasIn(path74) {
4307
+ const [key, ...rest] = path74;
4308
4308
  if (rest.length === 0)
4309
4309
  return this.has(key);
4310
4310
  const node = this.get(key, true);
@@ -4314,8 +4314,8 @@ var require_Collection = __commonJS({
4314
4314
  * Sets a value in this collection. For `!!set`, `value` needs to be a
4315
4315
  * boolean to add/remove the item from the set.
4316
4316
  */
4317
- setIn(path73, value) {
4318
- const [key, ...rest] = path73;
4317
+ setIn(path74, value) {
4318
+ const [key, ...rest] = path74;
4319
4319
  if (rest.length === 0) {
4320
4320
  this.set(key, value);
4321
4321
  } else {
@@ -6865,9 +6865,9 @@ var require_Document = __commonJS({
6865
6865
  this.contents.add(value);
6866
6866
  }
6867
6867
  /** Adds a value to the document. */
6868
- addIn(path73, value) {
6868
+ addIn(path74, value) {
6869
6869
  if (assertCollection(this.contents))
6870
- this.contents.addIn(path73, value);
6870
+ this.contents.addIn(path74, value);
6871
6871
  }
6872
6872
  /**
6873
6873
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -6942,14 +6942,14 @@ var require_Document = __commonJS({
6942
6942
  * Removes a value from the document.
6943
6943
  * @returns `true` if the item was found and removed.
6944
6944
  */
6945
- deleteIn(path73) {
6946
- if (Collection.isEmptyPath(path73)) {
6945
+ deleteIn(path74) {
6946
+ if (Collection.isEmptyPath(path74)) {
6947
6947
  if (this.contents == null)
6948
6948
  return false;
6949
6949
  this.contents = null;
6950
6950
  return true;
6951
6951
  }
6952
- return assertCollection(this.contents) ? this.contents.deleteIn(path73) : false;
6952
+ return assertCollection(this.contents) ? this.contents.deleteIn(path74) : false;
6953
6953
  }
6954
6954
  /**
6955
6955
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -6964,10 +6964,10 @@ var require_Document = __commonJS({
6964
6964
  * scalar values from their surrounding node; to disable set `keepScalar` to
6965
6965
  * `true` (collections are always returned intact).
6966
6966
  */
6967
- getIn(path73, keepScalar) {
6968
- if (Collection.isEmptyPath(path73))
6967
+ getIn(path74, keepScalar) {
6968
+ if (Collection.isEmptyPath(path74))
6969
6969
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
6970
- return identity.isCollection(this.contents) ? this.contents.getIn(path73, keepScalar) : void 0;
6970
+ return identity.isCollection(this.contents) ? this.contents.getIn(path74, keepScalar) : void 0;
6971
6971
  }
6972
6972
  /**
6973
6973
  * Checks if the document includes a value with the key `key`.
@@ -6978,10 +6978,10 @@ var require_Document = __commonJS({
6978
6978
  /**
6979
6979
  * Checks if the document includes a value at `path`.
6980
6980
  */
6981
- hasIn(path73) {
6982
- if (Collection.isEmptyPath(path73))
6981
+ hasIn(path74) {
6982
+ if (Collection.isEmptyPath(path74))
6983
6983
  return this.contents !== void 0;
6984
- return identity.isCollection(this.contents) ? this.contents.hasIn(path73) : false;
6984
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path74) : false;
6985
6985
  }
6986
6986
  /**
6987
6987
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -6998,13 +6998,13 @@ var require_Document = __commonJS({
6998
6998
  * Sets a value in this document. For `!!set`, `value` needs to be a
6999
6999
  * boolean to add/remove the item from the set.
7000
7000
  */
7001
- setIn(path73, value) {
7002
- if (Collection.isEmptyPath(path73)) {
7001
+ setIn(path74, value) {
7002
+ if (Collection.isEmptyPath(path74)) {
7003
7003
  this.contents = value;
7004
7004
  } else if (this.contents == null) {
7005
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path73), value);
7005
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path74), value);
7006
7006
  } else if (assertCollection(this.contents)) {
7007
- this.contents.setIn(path73, value);
7007
+ this.contents.setIn(path74, value);
7008
7008
  }
7009
7009
  }
7010
7010
  /**
@@ -8984,9 +8984,9 @@ var require_cst_visit = __commonJS({
8984
8984
  visit2.BREAK = BREAK;
8985
8985
  visit2.SKIP = SKIP;
8986
8986
  visit2.REMOVE = REMOVE;
8987
- visit2.itemAtPath = (cst, path73) => {
8987
+ visit2.itemAtPath = (cst, path74) => {
8988
8988
  let item = cst;
8989
- for (const [field, index] of path73) {
8989
+ for (const [field, index] of path74) {
8990
8990
  const tok = item?.[field];
8991
8991
  if (tok && "items" in tok) {
8992
8992
  item = tok.items[index];
@@ -8995,23 +8995,23 @@ var require_cst_visit = __commonJS({
8995
8995
  }
8996
8996
  return item;
8997
8997
  };
8998
- visit2.parentCollection = (cst, path73) => {
8999
- const parent = visit2.itemAtPath(cst, path73.slice(0, -1));
9000
- const field = path73[path73.length - 1][0];
8998
+ visit2.parentCollection = (cst, path74) => {
8999
+ const parent = visit2.itemAtPath(cst, path74.slice(0, -1));
9000
+ const field = path74[path74.length - 1][0];
9001
9001
  const coll = parent?.[field];
9002
9002
  if (coll && "items" in coll)
9003
9003
  return coll;
9004
9004
  throw new Error("Parent collection not found");
9005
9005
  };
9006
- function _visit(path73, item, visitor) {
9007
- let ctrl = visitor(item, path73);
9006
+ function _visit(path74, item, visitor) {
9007
+ let ctrl = visitor(item, path74);
9008
9008
  if (typeof ctrl === "symbol")
9009
9009
  return ctrl;
9010
9010
  for (const field of ["key", "value"]) {
9011
9011
  const token = item[field];
9012
9012
  if (token && "items" in token) {
9013
9013
  for (let i = 0; i < token.items.length; ++i) {
9014
- const ci = _visit(Object.freeze(path73.concat([[field, i]])), token.items[i], visitor);
9014
+ const ci = _visit(Object.freeze(path74.concat([[field, i]])), token.items[i], visitor);
9015
9015
  if (typeof ci === "number")
9016
9016
  i = ci - 1;
9017
9017
  else if (ci === BREAK)
@@ -9022,10 +9022,10 @@ var require_cst_visit = __commonJS({
9022
9022
  }
9023
9023
  }
9024
9024
  if (typeof ctrl === "function" && field === "key")
9025
- ctrl = ctrl(item, path73);
9025
+ ctrl = ctrl(item, path74);
9026
9026
  }
9027
9027
  }
9028
- return typeof ctrl === "function" ? ctrl(item, path73) : ctrl;
9028
+ return typeof ctrl === "function" ? ctrl(item, path74) : ctrl;
9029
9029
  }
9030
9030
  exports.visit = visit2;
9031
9031
  }
@@ -10331,14 +10331,14 @@ var require_parser = __commonJS({
10331
10331
  case "scalar":
10332
10332
  case "single-quoted-scalar":
10333
10333
  case "double-quoted-scalar": {
10334
- const fs65 = this.flowScalar(this.type);
10334
+ const fs66 = this.flowScalar(this.type);
10335
10335
  if (atNextItem || it.value) {
10336
- map.items.push({ start: start2, key: fs65, sep: [] });
10336
+ map.items.push({ start: start2, key: fs66, sep: [] });
10337
10337
  this.onKeyLine = true;
10338
10338
  } else if (it.sep) {
10339
- this.stack.push(fs65);
10339
+ this.stack.push(fs66);
10340
10340
  } else {
10341
- Object.assign(it, { key: fs65, sep: [] });
10341
+ Object.assign(it, { key: fs66, sep: [] });
10342
10342
  this.onKeyLine = true;
10343
10343
  }
10344
10344
  return;
@@ -10466,13 +10466,13 @@ var require_parser = __commonJS({
10466
10466
  case "scalar":
10467
10467
  case "single-quoted-scalar":
10468
10468
  case "double-quoted-scalar": {
10469
- const fs65 = this.flowScalar(this.type);
10469
+ const fs66 = this.flowScalar(this.type);
10470
10470
  if (!it || it.value)
10471
- fc.items.push({ start: [], key: fs65, sep: [] });
10471
+ fc.items.push({ start: [], key: fs66, sep: [] });
10472
10472
  else if (it.sep)
10473
- this.stack.push(fs65);
10473
+ this.stack.push(fs66);
10474
10474
  else
10475
- Object.assign(it, { key: fs65, sep: [] });
10475
+ Object.assign(it, { key: fs66, sep: [] });
10476
10476
  return;
10477
10477
  }
10478
10478
  case "flow-map-end":
@@ -10920,14 +10920,14 @@ function loadConfig() {
10920
10920
  const data = (0, import_yaml.parse)(fs3.readFileSync(p, "utf8"));
10921
10921
  return isDict(data) ? data : {};
10922
10922
  }
10923
- function registryPath(overrideKey, envName, basename26) {
10923
+ function registryPath(overrideKey, envName, basename27) {
10924
10924
  const override = loadConfig()["overrides"]?.[overrideKey];
10925
10925
  if (override)
10926
10926
  return path3.resolve(expandUser(String(override)));
10927
10927
  const fromEnv = envVar(envName);
10928
10928
  if (fromEnv)
10929
10929
  return path3.resolve(expandUser(fromEnv));
10930
- return path3.join(sandboxHome(), basename26);
10930
+ return path3.join(sandboxHome(), basename27);
10931
10931
  }
10932
10932
  function servicesYamlPath() {
10933
10933
  return registryPath("services_yaml", "SERVICES_YAML", "services.yaml");
@@ -11163,7 +11163,7 @@ function monorepoRoot() {
11163
11163
  }
11164
11164
  return sandboxRoot();
11165
11165
  }
11166
- var import_yaml;
11166
+ var import_yaml, IDLE_SERVICE_TYPES;
11167
11167
  var init_config = __esm({
11168
11168
  "../../packages/sandbox-core/dist/config.js"() {
11169
11169
  "use strict";
@@ -11171,6 +11171,7 @@ var init_config = __esm({
11171
11171
  import_yaml = __toESM(require_dist(), 1);
11172
11172
  init_dot_dir();
11173
11173
  init_reporter();
11174
+ IDLE_SERVICE_TYPES = ["cli", "library", "extension"];
11174
11175
  }
11175
11176
  });
11176
11177
 
@@ -11845,6 +11846,8 @@ function fromGh(ref, i) {
11845
11846
  ref,
11846
11847
  title: i.title?.trim() || void 0,
11847
11848
  status: titleCase(i.state),
11849
+ ...i.state?.toUpperCase() === "CLOSED" ? { statusCategory: "done" } : {},
11850
+ labels: i.labels?.map((l) => l.name),
11848
11851
  type: i.labels?.map((l) => l.name).join(", ") || void 0,
11849
11852
  assignee: i.assignees?.map((a) => a.login).join(", ") || void 0,
11850
11853
  description: i.body?.trim() || void 0,
@@ -11863,7 +11866,7 @@ async function view(ref, fields) {
11863
11866
  return null;
11864
11867
  }
11865
11868
  }
11866
- var GITHUB_ISSUE_PATTERN, FULL_RE, CLOSED, OPEN, STATUS_LABEL_COLOR, ghBin, titleCase, github;
11869
+ var GITHUB_ISSUE_PATTERN, FULL_RE, CLOSED, NOT_PLANNED, STATUS_PREFIX, OPEN, STATUS_LABEL_COLOR, ghBin, titleCase, github;
11867
11870
  var init_github = __esm({
11868
11871
  "../../packages/sandbox-core/dist/issue_trackers/github.js"() {
11869
11872
  "use strict";
@@ -11874,7 +11877,9 @@ var init_github = __esm({
11874
11877
  init_proc();
11875
11878
  GITHUB_ISSUE_PATTERN = "^([A-Za-z0-9_.-]+/)?[A-Za-z0-9_.-]+#[0-9]+$";
11876
11879
  FULL_RE = /^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#(\d+)$/;
11877
- CLOSED = /* @__PURE__ */ new Set(["closed", "close", "done"]);
11880
+ CLOSED = /* @__PURE__ */ new Set(["closed", "close", "done", "completed"]);
11881
+ NOT_PLANNED = /* @__PURE__ */ new Set(["not planned", "abandoned"]);
11882
+ STATUS_PREFIX = "status:";
11878
11883
  OPEN = /* @__PURE__ */ new Set(["open", "reopen", "reopened"]);
11879
11884
  STATUS_LABEL_COLOR = "1D76DB";
11880
11885
  ghBin = () => envVar("GH_BIN") || "gh";
@@ -11918,12 +11923,24 @@ var init_github = __esm({
11918
11923
  if (status2) {
11919
11924
  const s = status2.toLowerCase();
11920
11925
  if (CLOSED.has(s))
11921
- await gh(["issue", "close", n, "-R", repo]);
11926
+ await gh(["issue", "close", n, "-R", repo, "--reason", "completed"]);
11927
+ else if (NOT_PLANNED.has(s))
11928
+ await gh(["issue", "close", n, "-R", repo, "--reason", "not planned"]);
11922
11929
  else if (OPEN.has(s))
11923
11930
  await gh(["issue", "reopen", n, "-R", repo]);
11924
11931
  else {
11932
+ const stale = s.startsWith(STATUS_PREFIX) ? ((await view(ref, "labels"))?.labels ?? []).filter((l) => l.toLowerCase().startsWith(STATUS_PREFIX) && l.toLowerCase() !== s) : [];
11925
11933
  await gh(["label", "create", status2, "-R", repo, "--color", STATUS_LABEL_COLOR, "--force"]);
11926
- await gh(["issue", "edit", n, "-R", repo, "--add-label", status2]);
11934
+ await gh([
11935
+ "issue",
11936
+ "edit",
11937
+ n,
11938
+ "-R",
11939
+ repo,
11940
+ "--add-label",
11941
+ status2,
11942
+ ...stale.length ? ["--remove-label", stale.join(",")] : []
11943
+ ]);
11927
11944
  }
11928
11945
  }
11929
11946
  if (update.comment)
@@ -12053,19 +12070,19 @@ async function updateIssueRest(key, update) {
12053
12070
  const creds = await jiraCreds();
12054
12071
  if (!creds)
12055
12072
  return null;
12056
- const path73 = `/rest/api/2/issue/${encodeURIComponent(key)}`;
12073
+ const path74 = `/rest/api/2/issue/${encodeURIComponent(key)}`;
12057
12074
  if (update.status) {
12058
12075
  const want = update.status.trim().toLowerCase();
12059
- const { transitions = [] } = await jiraGet(creds, `${path73}/transitions`);
12076
+ const { transitions = [] } = await jiraGet(creds, `${path74}/transitions`);
12060
12077
  const t = transitions.find((x) => x.to?.name?.toLowerCase() === want) ?? transitions.find((x) => x.name?.toLowerCase() === want);
12061
12078
  if (!t) {
12062
12079
  const names = transitions.map((x) => x.to?.name ?? x.name).filter(Boolean).join(", ");
12063
12080
  throw new Error(`${key} has no transition to "${update.status}" (available: ${names || "none"})`);
12064
12081
  }
12065
- await jiraFetch(creds, `${path73}/transitions`, { transition: { id: t.id } });
12082
+ await jiraFetch(creds, `${path74}/transitions`, { transition: { id: t.id } });
12066
12083
  }
12067
12084
  if (update.comment)
12068
- await jiraFetch(creds, `${path73}/comment`, { body: update.comment });
12085
+ await jiraFetch(creds, `${path74}/comment`, { body: update.comment });
12069
12086
  return true;
12070
12087
  }
12071
12088
  var JIRA_PLACEHOLDERS, isJiraPlaceholder, ISSUE_FIELDS;
@@ -12119,6 +12136,7 @@ function fromRest(r) {
12119
12136
  ref: r.key,
12120
12137
  title: str(f.summary),
12121
12138
  status: str(f.status?.name),
12139
+ statusCategory: category(f.status?.statusCategory?.key),
12122
12140
  type: str(f.issuetype?.name),
12123
12141
  assignee: str(f.assignee?.displayName),
12124
12142
  description: str(f.description),
@@ -12129,7 +12147,7 @@ function fromRest(r) {
12129
12147
  async function fetchViaMcp(key) {
12130
12148
  const parsed = await askClaudeJson(`You are a data extractor, not an agent. Do exactly two things and nothing else:
12131
12149
  1. Fetch Jira issue ${key} INCLUDING ITS COMMENTS using getJiraIssue ${jiraSitePhrase()} \u2014 request fields ["summary","status","issuetype","assignee","description","comment"].
12132
- 2. Output ONLY a JSON object between <result> and </result> tags with keys "title","status","type","assignee","description" (strings, "" when unavailable) and "comments" (an array of {"author","created","body"}, oldest first). ` + READ_ONLY + "\nIf you cannot fetch the issue, output <result>{}</result> and nothing else.", { tools: true });
12150
+ 2. Output ONLY a JSON object between <result> and </result> tags with keys "title","status","statusCategory" (the status category key: new, indeterminate or done), "type","assignee","description" (strings, "" when unavailable) and "comments" (an array of {"author","created","body"}, oldest first). ` + READ_ONLY + "\nIf you cannot fetch the issue, output <result>{}</result> and nothing else.", { tools: true });
12133
12151
  if (!parsed || !str(parsed.title))
12134
12152
  return null;
12135
12153
  const comments = Array.isArray(parsed.comments) ? parsed.comments.flatMap((c) => {
@@ -12143,6 +12161,7 @@ async function fetchViaMcp(key) {
12143
12161
  ref: key,
12144
12162
  title: str(parsed.title),
12145
12163
  status: str(parsed.status),
12164
+ statusCategory: category(parsed.statusCategory),
12146
12165
  type: str(parsed.type),
12147
12166
  assignee: str(parsed.assignee),
12148
12167
  description: str(parsed.description),
@@ -12173,7 +12192,7 @@ ${update.comment}`
12173
12192
  if (parsed?.ok !== true)
12174
12193
  throw new Error(str(parsed?.error) ?? `Could not update ${key} through the Atlassian MCP`);
12175
12194
  }
12176
- var KEY_RE, str, READ_ONLY, SANDBOX_ISSUES_REPO, ISSUE_TYPES, jira;
12195
+ var KEY_RE, str, CATEGORIES, category, READ_ONLY, SANDBOX_ISSUES_REPO, ISSUE_TYPES, jira;
12177
12196
  var init_jira = __esm({
12178
12197
  "../../packages/sandbox-core/dist/issue_trackers/jira.js"() {
12179
12198
  "use strict";
@@ -12183,6 +12202,8 @@ var init_jira = __esm({
12183
12202
  init_jira_rest();
12184
12203
  KEY_RE = /[A-Za-z][A-Za-z0-9]+-\d+/;
12185
12204
  str = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
12205
+ CATEGORIES = ["new", "indeterminate", "done"];
12206
+ category = (v) => CATEGORIES.find((c) => c === str(v)?.toLowerCase());
12186
12207
  READ_ONLY = "Do not create, edit, transition, or comment on anything; write no prose outside the tags.";
12187
12208
  SANDBOX_ISSUES_REPO = "jbbz-sandbox/werkmeister";
12188
12209
  ISSUE_TYPES = { bug: "Bug", feature: "Story", task: "Task" };
@@ -12420,7 +12441,8 @@ function readAutonomous(workspaceRoot) {
12420
12441
  return isTrue(readWorkspaceField(workspaceRoot, "autonomous"));
12421
12442
  }
12422
12443
  function readAutoStop(workspaceRoot) {
12423
- return isTrue(readWorkspaceField(workspaceRoot, "auto_stop"));
12444
+ const value = readWorkspaceField(workspaceRoot, "auto_stop");
12445
+ return value === void 0 || value === null || value === "" ? readAutonomous(workspaceRoot) : isTrue(value);
12424
12446
  }
12425
12447
  function readModel(workspaceRoot) {
12426
12448
  const model = readWorkspaceField(workspaceRoot, "model");
@@ -12795,7 +12817,7 @@ var init_workspace_prompts = __esm({
12795
12817
  {
12796
12818
  key: "auto_stop",
12797
12819
  label: "Auto-stop on session end",
12798
- help: "Off by default. On: when a Claude session in the workspace ends and no other one is open there, a SessionEnd hook runs `sandbox stop --if-idle`, which stops this feature's containers (slot and volumes kept; `sandbox up` resumes). Shared infra is never stopped.",
12820
+ help: "On by default for autonomous workspaces, off otherwise. On: when a Claude session in the workspace ends and no other one is open there, a SessionEnd hook runs `sandbox stop --if-idle`, which stops this feature's containers (slot and volumes kept; `sandbox up` resumes). Shared infra is never stopped.",
12799
12821
  boolean: true
12800
12822
  },
12801
12823
  {
@@ -13311,7 +13333,16 @@ var init_hooks_gen = __esm({
13311
13333
  // No-arg forms act on the cwd's FEATURE; `autonomousRules` adds the named ones.
13312
13334
  "Bash(sandbox up)",
13313
13335
  "Bash(sandbox restart)",
13314
- ...["status", "pr", "issue view", "init-workspace", "skills", "standards", "notes context"].map((cmd) => `Bash(sandbox ${cmd}:*)`),
13336
+ ...[
13337
+ "status",
13338
+ "pr",
13339
+ "issue view",
13340
+ "issue start",
13341
+ "init-workspace",
13342
+ "skills",
13343
+ "standards",
13344
+ "notes context"
13345
+ ].map((cmd) => `Bash(sandbox ${cmd}:*)`),
13315
13346
  "Bash(docker ps:*)",
13316
13347
  // Local build and test; no `npm install`, so a new dependency waits for a person.
13317
13348
  ...["npm ci", "npm run", "npm test", "npx", "node", "python3"].map((cmd) => `Bash(${cmd}:*)`),
@@ -13560,8 +13591,8 @@ Lifecycle: \`working \u2192 ready \u2192 pr-open \u2192 merged \u2192 shipped \u
13560
13591
  | \`sandbox pr status\` | record merged / shipped / closed from GitHub and release tags |
13561
13592
  | \`sandbox pr adopt <pr-number\\|url> [repo]\` | the fix landed outside this workspace: write the PR file from that PR, then record its status |
13562
13593
  | \`sandbox pr adopt --no-pr "<reason>" [repo]\` | the fix is already on base with no PR: write a \`closed\` PR file with the reason |
13563
- | \`sandbox close-out\` | after ship: close the issue, write \`closeout.md\`, remove the workspace, delete merged branches |
13564
- | \`sandbox close-out --abandon "<reason>"\` | give up: record the reason, push and keep every branch, remove the workspace |
13594
+ | \`sandbox close-out\` | after ship: move the issue to done if shipping didn't, write \`closeout.md\`, remove the workspace, delete merged branches |
13595
+ | \`sandbox close-out --abandon "<reason>"\` | give up: record the reason, comment it on the issue, push and keep every branch, remove the workspace |
13565
13596
  `;
13566
13597
  }
13567
13598
  var init_pr_rules = __esm({
@@ -14184,7 +14215,7 @@ var init_icm_templates = __esm({
14184
14215
  "One commit per code repo touched, on that worktree's branch",
14185
14216
  "`output/pr-<repo>.md` per repo"
14186
14217
  ],
14187
- humanCheck: "Read each `pr-*.md`. Set `status: approved` on each one to open \u2014 that flip is the approval. Then `sandbox pr open` runs the gate and opens it. Merging is yours. After the release, `sandbox pr status`, then `sandbox close-out`.",
14218
+ humanCheck: "Read each `pr-*.md`. Set `status: approved` on each one to open \u2014 that flip is the approval. Then `sandbox pr open` runs the gate and opens it. Merging is yours. After the release, `sandbox pr status`, then ask the person before `sandbox close-out`.",
14188
14219
  selfCheck: "Review each `pr-*.md` once against `../../BRIEF.md` and the live issue. Add `## Recommendations for reviewer` to its body \u2014 every decision and question collected on the way \u2014 then set `status: approved` yourself and run `sandbox pr open`. Stop at the PR URL. Merging is the person's."
14189
14220
  })
14190
14221
  };
@@ -14462,6 +14493,7 @@ function renderIssueRef(issue) {
14462
14493
  "",
14463
14494
  `${name}${title}. ${copy}`,
14464
14495
  "Before each stage, re-read the live issue for newer direction: `sandbox issue view`",
14496
+ "Its status moves by itself (In Progress \u2192 In Review \u2192 Done): `docs/workspace-lifecycle.md` \xA7 Issue status in the sandbox repo.",
14465
14497
  ""
14466
14498
  ].join("\n");
14467
14499
  }
@@ -14495,6 +14527,17 @@ function proposeDir(stages) {
14495
14527
  const i = stages.findIndex((s) => s.name === "propose");
14496
14528
  return i < 0 ? null : stageDirs(stages)[i];
14497
14529
  }
14530
+ function restartLine(feature2, services) {
14531
+ const [servicesConfig] = loadSandboxServiceConfigs();
14532
+ const isIdle = (s) => IDLE_SERVICE_TYPES.includes(servicesConfig[s]?.type ?? "");
14533
+ const names = (list2) => list2.map((s) => `\`${feature2}-${s}\``).join(", ");
14534
+ const restarted = services.filter((s) => !isIdle(s));
14535
+ const idle = services.filter(isIdle);
14536
+ return [
14537
+ restarted.length ? restarted.map((s) => `\`docker restart ${feature2}-${s}\``).join(", ") : "",
14538
+ idle.length ? `${names(idle)} run no watcher, so a restart rebuilds nothing: build and test inside them with \`docker exec\` as \`.sandbox/sandbox-context.md\` \xA7 Test commands shows` : ""
14539
+ ].filter(Boolean).join("; ");
14540
+ }
14498
14541
  function vars(ctx, stages) {
14499
14542
  const dirs = stageDirs(stages);
14500
14543
  const feature2 = (ctx.meta["FEATURE"] ?? "").trim();
@@ -14503,8 +14546,7 @@ function vars(ctx, stages) {
14503
14546
  return {
14504
14547
  at: (name) => `../${dirs[stages.findIndex((s) => s.name === name)]}`,
14505
14548
  repos: repos.length ? repos.map((r) => `\`../../${r}/\``).join(", ") : "the code worktrees in this workspace",
14506
- // Container names follow docker-compose.feature.yml.njk: <feature>-<service>.
14507
- restart: feature2 && services.length ? services.map((s) => `\`docker restart ${feature2}-${s}\``).join(", ") : "restart whatever serves the change as `.sandbox/sandbox-context.md` describes",
14549
+ restart: feature2 && services.length ? restartLine(feature2, services) : "restart whatever serves the change as `.sandbox/sandbox-context.md` describes",
14508
14550
  standards: "",
14509
14551
  autonomous: isTrue(ctx.inputs["autonomous"])
14510
14552
  };
@@ -14555,7 +14597,7 @@ function standardsRef(tier, r) {
14555
14597
  const rows = tier === "all" ? r.active : r.active.filter((d) => universal.includes(d));
14556
14598
  return `- Reference (every run): \`../../_shared/standards.md\` \u2014 rows ${[...rows, "ponytail"].map((d) => `\`${d}\``).join(", ")}`;
14557
14599
  }
14558
- function renderContract(dir, stage, v) {
14600
+ function renderContract(dir, stage, v, startsIssue = false) {
14559
14601
  const c = stage.contract(v);
14560
14602
  return [
14561
14603
  `# ${dir} \u2014 ${stage.job}`,
@@ -14569,6 +14611,8 @@ function renderContract(dir, stage, v) {
14569
14611
  `Do NOT load: ${c.doNotLoad}`,
14570
14612
  "",
14571
14613
  "## Process",
14614
+ // Step 0, so the contracts' own "steps 1–3" references keep their numbers.
14615
+ ...startsIssue ? [`0. ${START_ISSUE}`] : [],
14572
14616
  ...c.process.map((p, i) => `${i + 1}. ${p}`),
14573
14617
  "",
14574
14618
  "## Outputs",
@@ -14615,7 +14659,7 @@ function renderClaudeMd(ctx, stages) {
14615
14659
  ...proposeDir(stages) ? [
14616
14660
  "| asked to open or create a PR \u2014 including `/create-pr` or `finishing-a-development-branch` | `_shared/pr-rules.md`, then `sandbox pr open`, then Auto-fix on (rule 5) | PR URL is in the PR file and Auto-fix is on |"
14617
14661
  ] : [],
14618
- "| a PR merged or released, or asked to close out | `sandbox pr status`, then `sandbox close-out` | closed out |",
14662
+ "| a PR merged or released, or asked to close out | `sandbox pr status`, then ask the person before `sandbox close-out` | closed out after the person says yes |",
14619
14663
  "| asked for status | `ls stages/*/output/` | report which stages hold artifacts |",
14620
14664
  "| containers / ports / routing broken | `.sandbox/sandbox-context.md` | \u2014 |",
14621
14665
  "| you hit a bug or need to log a feature/task \u2014 in the `sandbox` tool or in a repo's code | `_shared/issues-report.md`: if this workspace's changes caused a bug, fix it here; otherwise file an issue where it belongs | fixed, or issue URL reported to the person |",
@@ -14695,10 +14739,7 @@ function render(ctx) {
14695
14739
  if (propose2)
14696
14740
  files["_shared/pr-rules.md"] = buildPrRulesMd(`stages/${propose2}/output/`, base.autonomous);
14697
14741
  stages.forEach((stage, i) => {
14698
- files[`stages/${dirs[i]}/CONTEXT.md`] = renderContract(dirs[i], stage, {
14699
- ...base,
14700
- standards: standardsRef(stage.standards, r)
14701
- });
14742
+ files[`stages/${dirs[i]}/CONTEXT.md`] = renderContract(dirs[i], stage, { ...base, standards: standardsRef(stage.standards, r) }, i === 0 && Boolean(issueIdOfMeta(ctx.meta)));
14702
14743
  });
14703
14744
  return files;
14704
14745
  }
@@ -14768,7 +14809,7 @@ function init(ctx) {
14768
14809
  }
14769
14810
  icm.refresh(root);
14770
14811
  }
14771
- var PONYTAIL_ROW, ONE_RULE, NO_PROMPT, READ_ISSUE, PLACEHOLDER_BRIEF_RE, icm;
14812
+ var PONYTAIL_ROW, START_ISSUE, ONE_RULE, NO_PROMPT, READ_ISSUE, PLACEHOLDER_BRIEF_RE, icm;
14772
14813
  var init_icm = __esm({
14773
14814
  "../../packages/sandbox-core/dist/strategies/icm.js"() {
14774
14815
  "use strict";
@@ -14786,6 +14827,7 @@ var init_icm = __esm({
14786
14827
  init_icm_templates();
14787
14828
  init_issue_section();
14788
14829
  PONYTAIL_ROW = "| ponytail | already loaded: the ponytail plugin's SessionStart hook prints `PONYTAIL MODE ACTIVE` in every session. Invoke the `ponytail` skill only if that line is missing from your session context | any code edit |";
14830
+ START_ISSUE = "Run `sandbox issue start`: it moves the issue to In Progress, and does nothing when the issue is already past that or the tracker refuses.";
14789
14831
  ONE_RULE = [
14790
14832
  "## The one rule",
14791
14833
  "",
@@ -15095,69 +15137,69 @@ var require_chownr = __commonJS({
15095
15137
  "../../node_modules/chownr/chownr.js"(exports, module) {
15096
15138
  "use strict";
15097
15139
  init_define_SANDBOX_BUILD();
15098
- var fs65 = __require("fs");
15099
- var path73 = __require("path");
15100
- var LCHOWN = fs65.lchown ? "lchown" : "chown";
15101
- var LCHOWNSYNC = fs65.lchownSync ? "lchownSync" : "chownSync";
15102
- var needEISDIRHandled = fs65.lchown && !process.version.match(/v1[1-9]+\./) && !process.version.match(/v10\.[6-9]/);
15103
- var lchownSync = (path74, uid, gid) => {
15140
+ var fs66 = __require("fs");
15141
+ var path74 = __require("path");
15142
+ var LCHOWN = fs66.lchown ? "lchown" : "chown";
15143
+ var LCHOWNSYNC = fs66.lchownSync ? "lchownSync" : "chownSync";
15144
+ var needEISDIRHandled = fs66.lchown && !process.version.match(/v1[1-9]+\./) && !process.version.match(/v10\.[6-9]/);
15145
+ var lchownSync = (path75, uid, gid) => {
15104
15146
  try {
15105
- return fs65[LCHOWNSYNC](path74, uid, gid);
15147
+ return fs66[LCHOWNSYNC](path75, uid, gid);
15106
15148
  } catch (er) {
15107
15149
  if (er.code !== "ENOENT")
15108
15150
  throw er;
15109
15151
  }
15110
15152
  };
15111
- var chownSync = (path74, uid, gid) => {
15153
+ var chownSync = (path75, uid, gid) => {
15112
15154
  try {
15113
- return fs65.chownSync(path74, uid, gid);
15155
+ return fs66.chownSync(path75, uid, gid);
15114
15156
  } catch (er) {
15115
15157
  if (er.code !== "ENOENT")
15116
15158
  throw er;
15117
15159
  }
15118
15160
  };
15119
- var handleEISDIR = needEISDIRHandled ? (path74, uid, gid, cb) => (er) => {
15161
+ var handleEISDIR = needEISDIRHandled ? (path75, uid, gid, cb) => (er) => {
15120
15162
  if (!er || er.code !== "EISDIR")
15121
15163
  cb(er);
15122
15164
  else
15123
- fs65.chown(path74, uid, gid, cb);
15165
+ fs66.chown(path75, uid, gid, cb);
15124
15166
  } : (_, __, ___, cb) => cb;
15125
- var handleEISDirSync = needEISDIRHandled ? (path74, uid, gid) => {
15167
+ var handleEISDirSync = needEISDIRHandled ? (path75, uid, gid) => {
15126
15168
  try {
15127
- return lchownSync(path74, uid, gid);
15169
+ return lchownSync(path75, uid, gid);
15128
15170
  } catch (er) {
15129
15171
  if (er.code !== "EISDIR")
15130
15172
  throw er;
15131
- chownSync(path74, uid, gid);
15173
+ chownSync(path75, uid, gid);
15132
15174
  }
15133
- } : (path74, uid, gid) => lchownSync(path74, uid, gid);
15175
+ } : (path75, uid, gid) => lchownSync(path75, uid, gid);
15134
15176
  var nodeVersion = process.version;
15135
- var readdir = (path74, options, cb) => fs65.readdir(path74, options, cb);
15136
- var readdirSync19 = (path74, options) => fs65.readdirSync(path74, options);
15177
+ var readdir = (path75, options, cb) => fs66.readdir(path75, options, cb);
15178
+ var readdirSync20 = (path75, options) => fs66.readdirSync(path75, options);
15137
15179
  if (/^v4\./.test(nodeVersion))
15138
- readdir = (path74, options, cb) => fs65.readdir(path74, cb);
15180
+ readdir = (path75, options, cb) => fs66.readdir(path75, cb);
15139
15181
  var chown = (cpath, uid, gid, cb) => {
15140
- fs65[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, (er) => {
15182
+ fs66[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, (er) => {
15141
15183
  cb(er && er.code !== "ENOENT" ? er : null);
15142
15184
  }));
15143
15185
  };
15144
15186
  var chownrKid = (p, child, uid, gid, cb) => {
15145
15187
  if (typeof child === "string")
15146
- return fs65.lstat(path73.resolve(p, child), (er, stats) => {
15188
+ return fs66.lstat(path74.resolve(p, child), (er, stats) => {
15147
15189
  if (er)
15148
15190
  return cb(er.code !== "ENOENT" ? er : null);
15149
15191
  stats.name = child;
15150
15192
  chownrKid(p, stats, uid, gid, cb);
15151
15193
  });
15152
15194
  if (child.isDirectory()) {
15153
- chownr(path73.resolve(p, child.name), uid, gid, (er) => {
15195
+ chownr(path74.resolve(p, child.name), uid, gid, (er) => {
15154
15196
  if (er)
15155
15197
  return cb(er);
15156
- const cpath = path73.resolve(p, child.name);
15198
+ const cpath = path74.resolve(p, child.name);
15157
15199
  chown(cpath, uid, gid, cb);
15158
15200
  });
15159
15201
  } else {
15160
- const cpath = path73.resolve(p, child.name);
15202
+ const cpath = path74.resolve(p, child.name);
15161
15203
  chown(cpath, uid, gid, cb);
15162
15204
  }
15163
15205
  };
@@ -15187,7 +15229,7 @@ var require_chownr = __commonJS({
15187
15229
  var chownrKidSync = (p, child, uid, gid) => {
15188
15230
  if (typeof child === "string") {
15189
15231
  try {
15190
- const stats = fs65.lstatSync(path73.resolve(p, child));
15232
+ const stats = fs66.lstatSync(path74.resolve(p, child));
15191
15233
  stats.name = child;
15192
15234
  child = stats;
15193
15235
  } catch (er) {
@@ -15198,13 +15240,13 @@ var require_chownr = __commonJS({
15198
15240
  }
15199
15241
  }
15200
15242
  if (child.isDirectory())
15201
- chownrSync(path73.resolve(p, child.name), uid, gid);
15202
- handleEISDirSync(path73.resolve(p, child.name), uid, gid);
15243
+ chownrSync(path74.resolve(p, child.name), uid, gid);
15244
+ handleEISDirSync(path74.resolve(p, child.name), uid, gid);
15203
15245
  };
15204
15246
  var chownrSync = (p, uid, gid) => {
15205
15247
  let children;
15206
15248
  try {
15207
- children = readdirSync19(p, { withFileTypes: true });
15249
+ children = readdirSync20(p, { withFileTypes: true });
15208
15250
  } catch (er) {
15209
15251
  if (er.code === "ENOENT")
15210
15252
  return;
@@ -15355,7 +15397,7 @@ var require_buffer_list = __commonJS({
15355
15397
  }
15356
15398
  }, {
15357
15399
  key: "join",
15358
- value: function join52(s) {
15400
+ value: function join53(s) {
15359
15401
  if (this.length === 0) return "";
15360
15402
  var p = this.head;
15361
15403
  var ret = "" + p.data;
@@ -19200,9 +19242,9 @@ var require_pump = __commonJS({
19200
19242
  init_define_SANDBOX_BUILD();
19201
19243
  var once = require_once();
19202
19244
  var eos = require_end_of_stream2();
19203
- var fs65;
19245
+ var fs66;
19204
19246
  try {
19205
- fs65 = __require("fs");
19247
+ fs66 = __require("fs");
19206
19248
  } catch (e) {
19207
19249
  }
19208
19250
  var noop = function() {
@@ -19213,8 +19255,8 @@ var require_pump = __commonJS({
19213
19255
  };
19214
19256
  var isFS = function(stream) {
19215
19257
  if (!ancient) return false;
19216
- if (!fs65) return false;
19217
- return (stream instanceof (fs65.ReadStream || noop) || stream instanceof (fs65.WriteStream || noop)) && isFn(stream.close);
19258
+ if (!fs66) return false;
19259
+ return (stream instanceof (fs66.ReadStream || noop) || stream instanceof (fs66.WriteStream || noop)) && isFn(stream.close);
19218
19260
  };
19219
19261
  var isRequest = function(stream) {
19220
19262
  return stream.setHeader && isFn(stream.abort);
@@ -19274,8 +19316,8 @@ var require_pump = __commonJS({
19274
19316
  var require_mkdirp_classic = __commonJS({
19275
19317
  "../../node_modules/mkdirp-classic/index.js"(exports, module) {
19276
19318
  init_define_SANDBOX_BUILD();
19277
- var path73 = __require("path");
19278
- var fs65 = __require("fs");
19319
+ var path74 = __require("path");
19320
+ var fs66 = __require("fs");
19279
19321
  var _0777 = parseInt("0777", 8);
19280
19322
  module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
19281
19323
  function mkdirP(p, opts, f, made) {
@@ -19286,14 +19328,14 @@ var require_mkdirp_classic = __commonJS({
19286
19328
  opts = { mode: opts };
19287
19329
  }
19288
19330
  var mode = opts.mode;
19289
- var xfs = opts.fs || fs65;
19331
+ var xfs = opts.fs || fs66;
19290
19332
  if (mode === void 0) {
19291
19333
  mode = _0777 & ~process.umask();
19292
19334
  }
19293
19335
  if (!made) made = null;
19294
19336
  var cb = f || function() {
19295
19337
  };
19296
- p = path73.resolve(p);
19338
+ p = path74.resolve(p);
19297
19339
  xfs.mkdir(p, mode, function(er) {
19298
19340
  if (!er) {
19299
19341
  made = made || p;
@@ -19301,7 +19343,7 @@ var require_mkdirp_classic = __commonJS({
19301
19343
  }
19302
19344
  switch (er.code) {
19303
19345
  case "ENOENT":
19304
- mkdirP(path73.dirname(p), opts, function(er2, made2) {
19346
+ mkdirP(path74.dirname(p), opts, function(er2, made2) {
19305
19347
  if (er2) cb(er2, made2);
19306
19348
  else mkdirP(p, opts, cb, made2);
19307
19349
  });
@@ -19323,19 +19365,19 @@ var require_mkdirp_classic = __commonJS({
19323
19365
  opts = { mode: opts };
19324
19366
  }
19325
19367
  var mode = opts.mode;
19326
- var xfs = opts.fs || fs65;
19368
+ var xfs = opts.fs || fs66;
19327
19369
  if (mode === void 0) {
19328
19370
  mode = _0777 & ~process.umask();
19329
19371
  }
19330
19372
  if (!made) made = null;
19331
- p = path73.resolve(p);
19373
+ p = path74.resolve(p);
19332
19374
  try {
19333
19375
  xfs.mkdirSync(p, mode);
19334
19376
  made = made || p;
19335
19377
  } catch (err0) {
19336
19378
  switch (err0.code) {
19337
19379
  case "ENOENT":
19338
- made = sync(path73.dirname(p), opts, made);
19380
+ made = sync(path74.dirname(p), opts, made);
19339
19381
  sync(p, opts, made);
19340
19382
  break;
19341
19383
  // In the case of any other error, just see if there's a dir
@@ -19365,8 +19407,8 @@ var require_tar_fs = __commonJS({
19365
19407
  var tar2 = require_tar_stream();
19366
19408
  var pump = require_pump();
19367
19409
  var mkdirp = require_mkdirp_classic();
19368
- var fs65 = __require("fs");
19369
- var path73 = __require("path");
19410
+ var fs66 = __require("fs");
19411
+ var path74 = __require("path");
19370
19412
  var os21 = __require("os");
19371
19413
  var win32 = os21.platform() === "win32";
19372
19414
  var noop = function() {
@@ -19377,23 +19419,23 @@ var require_tar_fs = __commonJS({
19377
19419
  var normalize = !win32 ? echo : function(name) {
19378
19420
  return name.replace(/\\/g, "/").replace(/[:?<>|]/g, "_");
19379
19421
  };
19380
- var statAll = function(fs66, stat, cwd, ignore, entries, sort) {
19422
+ var statAll = function(fs67, stat, cwd, ignore, entries, sort) {
19381
19423
  var queue = entries || ["."];
19382
19424
  for (var i = 0; i < queue.length; i++) {
19383
- if (!inCwd(path73.resolve(cwd, queue[i]), cwd)) throw new Error(queue[i] + " is not a valid path");
19425
+ if (!inCwd(path74.resolve(cwd, queue[i]), cwd)) throw new Error(queue[i] + " is not a valid path");
19384
19426
  }
19385
19427
  return function loop(callback) {
19386
19428
  if (!queue.length) return callback();
19387
19429
  var next = queue.shift();
19388
- var nextAbs = path73.join(cwd, next);
19389
- stat.call(fs66, nextAbs, function(err, stat2) {
19430
+ var nextAbs = path74.join(cwd, next);
19431
+ stat.call(fs67, nextAbs, function(err, stat2) {
19390
19432
  if (err) return callback(err);
19391
19433
  if (!stat2.isDirectory()) return callback(null, next, stat2);
19392
- fs66.readdir(nextAbs, function(err2, files) {
19434
+ fs67.readdir(nextAbs, function(err2, files) {
19393
19435
  if (err2) return callback(err2);
19394
19436
  if (sort) files.sort();
19395
19437
  for (var i2 = 0; i2 < files.length; i2++) {
19396
- if (!ignore(path73.join(cwd, next, files[i2]))) queue.push(path73.join(next, files[i2]));
19438
+ if (!ignore(path74.join(cwd, next, files[i2]))) queue.push(path74.join(next, files[i2]));
19397
19439
  }
19398
19440
  callback(null, next, stat2);
19399
19441
  });
@@ -19404,7 +19446,7 @@ var require_tar_fs = __commonJS({
19404
19446
  return function(header) {
19405
19447
  header.name = header.name.split("/").slice(level).join("/");
19406
19448
  var linkname = header.linkname;
19407
- if (linkname && (header.type === "link" || path73.isAbsolute(linkname))) {
19449
+ if (linkname && (header.type === "link" || path74.isAbsolute(linkname))) {
19408
19450
  header.linkname = linkname.split("/").slice(level).join("/");
19409
19451
  }
19410
19452
  return map(header);
@@ -19413,7 +19455,7 @@ var require_tar_fs = __commonJS({
19413
19455
  exports.pack = function(cwd, opts) {
19414
19456
  if (!cwd) cwd = ".";
19415
19457
  if (!opts) opts = {};
19416
- var xfs = opts.fs || fs65;
19458
+ var xfs = opts.fs || fs66;
19417
19459
  var ignore = opts.ignore || opts.filter || noop;
19418
19460
  var map = opts.map || noop;
19419
19461
  var mapStream = opts.mapStream || echo;
@@ -19434,7 +19476,7 @@ var require_tar_fs = __commonJS({
19434
19476
  fmode |= parseInt(222, 8);
19435
19477
  }
19436
19478
  var onsymlink = function(filename, header) {
19437
- xfs.readlink(path73.join(cwd, filename), function(err, linkname) {
19479
+ xfs.readlink(path74.join(cwd, filename), function(err, linkname) {
19438
19480
  if (err) return pack2.destroy(err);
19439
19481
  header.linkname = normalize(linkname);
19440
19482
  pack2.entry(header, onnextentry);
@@ -19475,7 +19517,7 @@ var require_tar_fs = __commonJS({
19475
19517
  }
19476
19518
  var entry = pack2.entry(header, onnextentry);
19477
19519
  if (!entry) return;
19478
- var rs = mapStream(xfs.createReadStream(path73.join(cwd, filename), { start: 0, end: header.size > 0 ? header.size - 1 : header.size }), header);
19520
+ var rs = mapStream(xfs.createReadStream(path74.join(cwd, filename), { start: 0, end: header.size > 0 ? header.size - 1 : header.size }), header);
19479
19521
  rs.on("error", function(err2) {
19480
19522
  entry.destroy(err2);
19481
19523
  });
@@ -19500,7 +19542,7 @@ var require_tar_fs = __commonJS({
19500
19542
  exports.extract = function(cwd, opts) {
19501
19543
  if (!cwd) cwd = ".";
19502
19544
  if (!opts) opts = {};
19503
- var xfs = opts.fs || fs65;
19545
+ var xfs = opts.fs || fs66;
19504
19546
  var ignore = opts.ignore || opts.filter || noop;
19505
19547
  var map = opts.map || noop;
19506
19548
  var mapStream = opts.mapStream || echo;
@@ -19553,14 +19595,14 @@ var require_tar_fs = __commonJS({
19553
19595
  extract.on("entry", function(header, stream, next) {
19554
19596
  header = map(header) || header;
19555
19597
  header.name = normalize(header.name);
19556
- var name = path73.join(cwd, path73.join("/", header.name));
19598
+ var name = path74.join(cwd, path74.join("/", header.name));
19557
19599
  if (ignore(name, header)) {
19558
19600
  stream.resume();
19559
19601
  return next();
19560
19602
  }
19561
19603
  var stat = function(err) {
19562
19604
  if (err) return next(err);
19563
- if (path73.join(name, ".") === path73.join(cwd, ".")) return next();
19605
+ if (path74.join(name, ".") === path74.join(cwd, ".")) return next();
19564
19606
  utimes(name, header, function(err2) {
19565
19607
  if (err2) return next(err2);
19566
19608
  if (win32) return next();
@@ -19570,9 +19612,9 @@ var require_tar_fs = __commonJS({
19570
19612
  var onsymlink = function() {
19571
19613
  if (win32) return next();
19572
19614
  xfs.unlink(name, function() {
19573
- var dst = path73.resolve(path73.dirname(name), header.linkname);
19615
+ var dst = path74.resolve(path74.dirname(name), header.linkname);
19574
19616
  if (!inCwd(dst, cwd)) return next(new Error(name + " is not a valid symlink"));
19575
- validateNotSymlink(xfs, dst, path73.resolve(cwd), function(err, valid) {
19617
+ validateNotSymlink(xfs, dst, path74.resolve(cwd), function(err, valid) {
19576
19618
  if (err) return next(err);
19577
19619
  if (!valid) return next(new Error(name + " is not a valid symlink"));
19578
19620
  xfs.symlink(header.linkname, name, stat);
@@ -19582,7 +19624,7 @@ var require_tar_fs = __commonJS({
19582
19624
  var onlink = function() {
19583
19625
  if (win32) return next();
19584
19626
  xfs.unlink(name, function() {
19585
- var srcpath = path73.join(cwd, path73.join("/", header.linkname));
19627
+ var srcpath = path74.join(cwd, path74.join("/", header.linkname));
19586
19628
  xfs.realpath(srcpath, function(err, dst) {
19587
19629
  if (err || !inCwd(dst, cwd)) return next(new Error(name + " is not a valid hardlink"));
19588
19630
  xfs.link(dst, name, function(err2) {
@@ -19622,8 +19664,8 @@ var require_tar_fs = __commonJS({
19622
19664
  gid: header.gid
19623
19665
  }, stat);
19624
19666
  }
19625
- var dir = path73.dirname(name);
19626
- validate(xfs, dir, path73.join(cwd, "."), function(err, valid) {
19667
+ var dir = path74.dirname(name);
19668
+ validate(xfs, dir, path74.join(cwd, "."), function(err, valid) {
19627
19669
  if (err) return next(err);
19628
19670
  if (!valid) return next(new Error(dir + " is not a valid path"));
19629
19671
  mkdirfix(dir, {
@@ -19650,20 +19692,20 @@ var require_tar_fs = __commonJS({
19650
19692
  if (opts.finish) extract.on("finish", opts.finish);
19651
19693
  return extract;
19652
19694
  };
19653
- function validate(fs66, name, root, cb) {
19695
+ function validate(fs67, name, root, cb) {
19654
19696
  if (name === root) return cb(null, true);
19655
- fs66.lstat(name, function(err, st) {
19697
+ fs67.lstat(name, function(err, st) {
19656
19698
  if (err && err.code !== "ENOENT") return cb(err);
19657
- if (err || st.isDirectory()) return validate(fs66, path73.join(name, ".."), root, cb);
19699
+ if (err || st.isDirectory()) return validate(fs67, path74.join(name, ".."), root, cb);
19658
19700
  cb(null, false);
19659
19701
  });
19660
19702
  }
19661
- function validateNotSymlink(fs66, name, root, cb) {
19703
+ function validateNotSymlink(fs67, name, root, cb) {
19662
19704
  if (name === root) return cb(null, true);
19663
- if (!name.startsWith(root + path73.sep)) return cb(null, false);
19664
- fs66.lstat(name, function(err, st) {
19705
+ if (!name.startsWith(root + path74.sep)) return cb(null, false);
19706
+ fs67.lstat(name, function(err, st) {
19665
19707
  if (err && err.code !== "ENOENT" && err.code !== "EPERM") return cb(err);
19666
- if (err || !st.isSymbolicLink()) return validateNotSymlink(fs66, path73.join(name, ".."), root, cb);
19708
+ if (err || !st.isSymbolicLink()) return validateNotSymlink(fs67, path74.join(name, ".."), root, cb);
19667
19709
  cb(null, false);
19668
19710
  });
19669
19711
  }
@@ -19677,8 +19719,8 @@ var require_tar_fs = __commonJS({
19677
19719
  });
19678
19720
  }
19679
19721
  function inCwd(dst, cwd) {
19680
- cwd = path73.resolve(cwd);
19681
- return cwd === dst || dst.startsWith(cwd + path73.sep);
19722
+ cwd = path74.resolve(cwd);
19723
+ return cwd === dst || dst.startsWith(cwd + path74.sep);
19682
19724
  }
19683
19725
  }
19684
19726
  });
@@ -22548,11 +22590,11 @@ var require_lib = __commonJS({
22548
22590
  function lookupEscape(ch) {
22549
22591
  return escapeMap[ch];
22550
22592
  }
22551
- function _prettifyError(path73, withInternals, err) {
22593
+ function _prettifyError(path74, withInternals, err) {
22552
22594
  if (!err.Update) {
22553
22595
  err = new _exports.TemplateError(err);
22554
22596
  }
22555
- err.Update(path73);
22597
+ err.Update(path74);
22556
22598
  if (!withInternals) {
22557
22599
  var old = err;
22558
22600
  err = new Error(old.message);
@@ -22613,8 +22655,8 @@ var require_lib = __commonJS({
22613
22655
  err.lineno = lineno;
22614
22656
  err.colno = colno;
22615
22657
  err.firstUpdate = true;
22616
- err.Update = function Update(path73) {
22617
- var msg = "(" + (path73 || "unknown path") + ")";
22658
+ err.Update = function Update(path74) {
22659
+ var msg = "(" + (path74 || "unknown path") + ")";
22618
22660
  if (this.firstUpdate) {
22619
22661
  if (this.lineno && this.colno) {
22620
22662
  msg += " [Line " + this.lineno + ", Column " + this.colno + "]";
@@ -26464,7 +26506,7 @@ var require_filters = __commonJS({
26464
26506
  return r.copySafeness(str3, res);
26465
26507
  }
26466
26508
  _exports.indent = indent;
26467
- function join52(arr, del, attr) {
26509
+ function join53(arr, del, attr) {
26468
26510
  del = del || "";
26469
26511
  if (attr) {
26470
26512
  arr = lib.map(arr, function(v) {
@@ -26473,7 +26515,7 @@ var require_filters = __commonJS({
26473
26515
  }
26474
26516
  return arr.join(del);
26475
26517
  }
26476
- _exports.join = join52;
26518
+ _exports.join = join53;
26477
26519
  function last(arr) {
26478
26520
  return arr[arr.length - 1];
26479
26521
  }
@@ -26826,7 +26868,7 @@ var require_loader = __commonJS({
26826
26868
  };
26827
26869
  return _setPrototypeOf(o, p);
26828
26870
  }
26829
- var path73 = __require("path");
26871
+ var path74 = __require("path");
26830
26872
  var _require = require_object();
26831
26873
  var EmitterObj = _require.EmitterObj;
26832
26874
  module.exports = /* @__PURE__ */ function(_EmitterObj) {
@@ -26836,7 +26878,7 @@ var require_loader = __commonJS({
26836
26878
  }
26837
26879
  var _proto = Loader.prototype;
26838
26880
  _proto.resolve = function resolve13(from, to) {
26839
- return path73.resolve(path73.dirname(from), to);
26881
+ return path74.resolve(path74.dirname(from), to);
26840
26882
  };
26841
26883
  _proto.isRelative = function isRelative(filename) {
26842
26884
  return filename.indexOf("./") === 0 || filename.indexOf("../") === 0;
@@ -26910,8 +26952,8 @@ var require_node_loaders = __commonJS({
26910
26952
  };
26911
26953
  return _setPrototypeOf(o, p);
26912
26954
  }
26913
- var fs65 = __require("fs");
26914
- var path73 = __require("path");
26955
+ var fs66 = __require("fs");
26956
+ var path74 = __require("path");
26915
26957
  var Loader = require_loader();
26916
26958
  var _require = require_precompiled_loader();
26917
26959
  var PrecompiledLoader = _require.PrecompiledLoader;
@@ -26929,7 +26971,7 @@ var require_node_loaders = __commonJS({
26929
26971
  _this.noCache = !!opts.noCache;
26930
26972
  if (searchPaths) {
26931
26973
  searchPaths = Array.isArray(searchPaths) ? searchPaths : [searchPaths];
26932
- _this.searchPaths = searchPaths.map(path73.normalize);
26974
+ _this.searchPaths = searchPaths.map(path74.normalize);
26933
26975
  } else {
26934
26976
  _this.searchPaths = ["."];
26935
26977
  }
@@ -26939,10 +26981,10 @@ var require_node_loaders = __commonJS({
26939
26981
  } catch (e) {
26940
26982
  throw new Error("watch requires chokidar to be installed");
26941
26983
  }
26942
- var paths = _this.searchPaths.filter(fs65.existsSync);
26984
+ var paths = _this.searchPaths.filter(fs66.existsSync);
26943
26985
  var watcher = chokidar.watch(paths);
26944
26986
  watcher.on("all", function(event, fullname) {
26945
- fullname = path73.resolve(fullname);
26987
+ fullname = path74.resolve(fullname);
26946
26988
  if (event === "change" && fullname in _this.pathsToNames) {
26947
26989
  _this.emit("update", _this.pathsToNames[fullname], fullname);
26948
26990
  }
@@ -26958,9 +27000,9 @@ var require_node_loaders = __commonJS({
26958
27000
  var fullpath = null;
26959
27001
  var paths = this.searchPaths;
26960
27002
  for (var i = 0; i < paths.length; i++) {
26961
- var basePath = path73.resolve(paths[i]);
26962
- var p = path73.resolve(paths[i], name);
26963
- if (p.indexOf(basePath) === 0 && fs65.existsSync(p)) {
27003
+ var basePath = path74.resolve(paths[i]);
27004
+ var p = path74.resolve(paths[i], name);
27005
+ if (p.indexOf(basePath) === 0 && fs66.existsSync(p)) {
26964
27006
  fullpath = p;
26965
27007
  break;
26966
27008
  }
@@ -26970,7 +27012,7 @@ var require_node_loaders = __commonJS({
26970
27012
  }
26971
27013
  this.pathsToNames[fullpath] = name;
26972
27014
  var source = {
26973
- src: fs65.readFileSync(fullpath, "utf-8"),
27015
+ src: fs66.readFileSync(fullpath, "utf-8"),
26974
27016
  path: fullpath,
26975
27017
  noCache: this.noCache
26976
27018
  };
@@ -27022,7 +27064,7 @@ var require_node_loaders = __commonJS({
27022
27064
  }
27023
27065
  this.pathsToNames[fullpath] = name;
27024
27066
  var source = {
27025
- src: fs65.readFileSync(fullpath, "utf-8"),
27067
+ src: fs66.readFileSync(fullpath, "utf-8"),
27026
27068
  path: fullpath,
27027
27069
  noCache: this.noCache
27028
27070
  };
@@ -27229,13 +27271,13 @@ var require_express_app = __commonJS({
27229
27271
  "../../node_modules/nunjucks/src/express-app.js"(exports, module) {
27230
27272
  "use strict";
27231
27273
  init_define_SANDBOX_BUILD();
27232
- var path73 = __require("path");
27274
+ var path74 = __require("path");
27233
27275
  module.exports = function express(env, app) {
27234
27276
  function NunjucksView(name, opts) {
27235
27277
  this.name = name;
27236
27278
  this.path = name;
27237
27279
  this.defaultEngine = opts.defaultEngine;
27238
- this.ext = path73.extname(name);
27280
+ this.ext = path74.extname(name);
27239
27281
  if (!this.ext && !this.defaultEngine) {
27240
27282
  throw new Error("No default engine was specified and no extension was provided.");
27241
27283
  }
@@ -27616,7 +27658,7 @@ var require_environment = __commonJS({
27616
27658
  return _Obj2.apply(this, arguments) || this;
27617
27659
  }
27618
27660
  var _proto3 = Template2.prototype;
27619
- _proto3.init = function init3(src, env, path73, eagerCompile) {
27661
+ _proto3.init = function init3(src, env, path74, eagerCompile) {
27620
27662
  this.env = env || new Environment();
27621
27663
  if (lib.isObject(src)) {
27622
27664
  switch (src.type) {
@@ -27634,7 +27676,7 @@ var require_environment = __commonJS({
27634
27676
  } else {
27635
27677
  throw new Error("src must be a string or an object describing the source");
27636
27678
  }
27637
- this.path = path73;
27679
+ this.path = path74;
27638
27680
  if (eagerCompile) {
27639
27681
  try {
27640
27682
  this._compile();
@@ -27786,8 +27828,8 @@ var require_precompile = __commonJS({
27786
27828
  "../../node_modules/nunjucks/src/precompile.js"(exports, module) {
27787
27829
  "use strict";
27788
27830
  init_define_SANDBOX_BUILD();
27789
- var fs65 = __require("fs");
27790
- var path73 = __require("path");
27831
+ var fs66 = __require("fs");
27832
+ var path74 = __require("path");
27791
27833
  var _require = require_lib();
27792
27834
  var _prettifyError = _require._prettifyError;
27793
27835
  var compiler = require_compiler();
@@ -27819,14 +27861,14 @@ var require_precompile = __commonJS({
27819
27861
  if (opts.isString) {
27820
27862
  return precompileString(input2, opts);
27821
27863
  }
27822
- var pathStats = fs65.existsSync(input2) && fs65.statSync(input2);
27864
+ var pathStats = fs66.existsSync(input2) && fs66.statSync(input2);
27823
27865
  var precompiled = [];
27824
27866
  var templates = [];
27825
27867
  function addTemplates(dir) {
27826
- fs65.readdirSync(dir).forEach(function(file) {
27827
- var filepath = path73.join(dir, file);
27828
- var subpath = filepath.substr(path73.join(input2, "/").length);
27829
- var stat = fs65.statSync(filepath);
27868
+ fs66.readdirSync(dir).forEach(function(file) {
27869
+ var filepath = path74.join(dir, file);
27870
+ var subpath = filepath.substr(path74.join(input2, "/").length);
27871
+ var stat = fs66.statSync(filepath);
27830
27872
  if (stat && stat.isDirectory()) {
27831
27873
  subpath += "/";
27832
27874
  if (!match(subpath, opts.exclude)) {
@@ -27838,13 +27880,13 @@ var require_precompile = __commonJS({
27838
27880
  });
27839
27881
  }
27840
27882
  if (pathStats.isFile()) {
27841
- precompiled.push(_precompile(fs65.readFileSync(input2, "utf-8"), opts.name || input2, env));
27883
+ precompiled.push(_precompile(fs66.readFileSync(input2, "utf-8"), opts.name || input2, env));
27842
27884
  } else if (pathStats.isDirectory()) {
27843
27885
  addTemplates(input2);
27844
27886
  for (var i = 0; i < templates.length; i++) {
27845
- var name = templates[i].replace(path73.join(input2, "/"), "");
27887
+ var name = templates[i].replace(path74.join(input2, "/"), "");
27846
27888
  try {
27847
- precompiled.push(_precompile(fs65.readFileSync(templates[i], "utf-8"), name, env));
27889
+ precompiled.push(_precompile(fs66.readFileSync(templates[i], "utf-8"), name, env));
27848
27890
  } catch (e) {
27849
27891
  if (opts.force) {
27850
27892
  console.error(e);
@@ -28223,11 +28265,11 @@ var require_nunjucks = __commonJS({
28223
28265
  reset: function reset() {
28224
28266
  e = void 0;
28225
28267
  },
28226
- compile: function compile(src, env, path73, eagerCompile) {
28268
+ compile: function compile(src, env, path74, eagerCompile) {
28227
28269
  if (!e) {
28228
28270
  configure();
28229
28271
  }
28230
- return new Template(src, env, path73, eagerCompile);
28272
+ return new Template(src, env, path74, eagerCompile);
28231
28273
  },
28232
28274
  render: function render2(name, ctx, cb) {
28233
28275
  if (!e) {
@@ -28279,12 +28321,12 @@ function resolveSecrets(all, servicesConfig, serviceNames, overrides = {}) {
28279
28321
  function getSecretsPath() {
28280
28322
  return secretsPath();
28281
28323
  }
28282
- function loadSecrets(path73) {
28283
- if (!(fs26.existsSync(path73) && fs26.statSync(path73).isFile())) {
28324
+ function loadSecrets(path74) {
28325
+ if (!(fs26.existsSync(path74) && fs26.statSync(path74).isFile())) {
28284
28326
  return {};
28285
28327
  }
28286
28328
  const result = {};
28287
- for (const rawLine of fs26.readFileSync(path73, "utf8").split("\n")) {
28329
+ for (const rawLine of fs26.readFileSync(path74, "utf8").split("\n")) {
28288
28330
  const line = rawLine.trim();
28289
28331
  if (!line || line.startsWith("#") || !line.includes("=")) {
28290
28332
  continue;
@@ -28575,6 +28617,7 @@ function getTemplateOrNull(env, name) {
28575
28617
  function composeHeader(feature2, source) {
28576
28618
  return `# Sandbox docker-compose for feature: ${feature2}
28577
28619
  # Generated by: ${source}
28620
+ # Regenerated by \`sandbox up\` whenever stale; do not hand-edit.
28578
28621
  #
28579
28622
  # Run from workspace root:
28580
28623
  # docker compose up -d --build (start)
@@ -28742,6 +28785,7 @@ function renderFeatureCompose(feature2, slot, localServices, config, workspaceOv
28742
28785
  base_port: portStart,
28743
28786
  local_services: svcObjects,
28744
28787
  services_config: servicesConfig,
28788
+ idle_types: IDLE_SERVICE_TYPES,
28745
28789
  setup_configs: setupConfigs,
28746
28790
  ports_config: portsConfig,
28747
28791
  workspace_path: workspaceRoot,
@@ -29298,9 +29342,9 @@ async function generateWorkspaceCompose(feature2, servicesStr, workspaceRoot, te
29298
29342
  continue;
29299
29343
  }
29300
29344
  const stripped = (svcDef["path"] ?? "").replace(/\/+$/, "");
29301
- const basename26 = stripped.includes("/") ? stripped.slice(stripped.lastIndexOf("/") + 1) : stripped;
29302
- if (basename26 !== svcName) {
29303
- pathAliases[basename26] = svcName;
29345
+ const basename27 = stripped.includes("/") ? stripped.slice(stripped.lastIndexOf("/") + 1) : stripped;
29346
+ if (basename27 !== svcName) {
29347
+ pathAliases[basename27] = svcName;
29304
29348
  }
29305
29349
  }
29306
29350
  const resolve13 = (name) => {
@@ -29482,6 +29526,18 @@ function refreshKubeconfig() {
29482
29526
  }
29483
29527
  return fs29.existsSync(kubeconfig) && fs29.statSync(kubeconfig).isFile();
29484
29528
  }
29529
+ function clusterReachable() {
29530
+ try {
29531
+ return run(["kubectl", "--request-timeout=3s", "get", "--raw", "/readyz"], {
29532
+ capture: true,
29533
+ timeout: 20
29534
+ }).status === 0;
29535
+ } catch (e) {
29536
+ if (e instanceof ProcError)
29537
+ return false;
29538
+ throw e;
29539
+ }
29540
+ }
29485
29541
  function ensurePortForwardsHealthy() {
29486
29542
  const health = checkPortForwardHealth();
29487
29543
  if (health["k8s-forwards"] ?? false) {
@@ -29493,6 +29549,10 @@ function ensurePortForwardsHealthy() {
29493
29549
  error("Not restarting k8s-forwards without a kubeconfig file to mount");
29494
29550
  return "failed";
29495
29551
  }
29552
+ if (!clusterReachable()) {
29553
+ warn("No reachable k8s cluster \u2014 skipping port-forwards; services not run locally are unavailable");
29554
+ return "failed";
29555
+ }
29496
29556
  const { argv, env } = composeInvocation();
29497
29557
  try {
29498
29558
  const result = run([...argv, "up", "-d", "--force-recreate", "k8s-forwards"], {
@@ -30070,7 +30130,8 @@ async function up(feature2, workspace, { signal } = {}) {
30070
30130
  generateFeatureEnv(feature2, slot, localServices, config, workspace);
30071
30131
  } else if (isFeatureComposeStale(feature2, slot, localServices, config, workspace)) {
30072
30132
  staleCompose = true;
30073
- warn(`${COMPOSE_FILE} predates the current service registry \u2014 run 'sandbox init-workspace' in the workspace to regenerate it.`);
30133
+ warn(`${COMPOSE_FILE} predates the current template or registry \u2014 regenerating...`);
30134
+ generateFeatureCompose(feature2, slot, localServices, config, workspace, "sandbox up");
30074
30135
  }
30075
30136
  generateSupergraphConfig(feature2, slot, localServices, config, workspace);
30076
30137
  raw(`
@@ -30169,27 +30230,119 @@ function refuseInsideOwnContainer(feature2, verb) {
30169
30230
  Run it from the host: cd to the workspace and run 'sandbox ${verb}'.`, "INSIDE_OWN_CONTAINER");
30170
30231
  }
30171
30232
  }
30172
- function otherLiveSessions(workspaceRoot, sessionId, sessionsDir = path30.join(os8.homedir(), ".claude", "sessions")) {
30233
+ function liveSessions(workspaceRoot, sessionsDir = path30.join(os8.homedir(), ".claude", "sessions")) {
30173
30234
  let names;
30174
30235
  try {
30175
30236
  names = fs31.readdirSync(sessionsDir).filter((n) => n.endsWith(".json"));
30176
30237
  } catch {
30177
30238
  return [];
30178
30239
  }
30179
- const pids = [];
30240
+ const sessions = [];
30180
30241
  for (const name of names) {
30181
30242
  try {
30182
30243
  const s = JSON.parse(fs31.readFileSync(path30.join(sessionsDir, name), "utf8"));
30183
30244
  const cwd = String(s.cwd ?? "");
30184
30245
  const inWorkspace = cwd === workspaceRoot || cwd.startsWith(workspaceRoot + path30.sep);
30185
- if (!inWorkspace || s.sessionId === sessionId || !Number.isInteger(s.pid))
30246
+ if (!inWorkspace || !Number.isInteger(s.pid))
30186
30247
  continue;
30187
30248
  process.kill(s.pid, 0);
30188
- pids.push(s.pid);
30249
+ sessions.push({
30250
+ pid: s.pid,
30251
+ sessionId: typeof s.sessionId === "string" ? s.sessionId : null,
30252
+ status: typeof s.status === "string" ? s.status : null,
30253
+ statusUpdatedAt: Number.isFinite(s.statusUpdatedAt) ? s.statusUpdatedAt : null
30254
+ });
30189
30255
  } catch {
30190
30256
  }
30191
30257
  }
30192
- return pids;
30258
+ return sessions;
30259
+ }
30260
+ function otherLiveSessions(workspaceRoot, sessionId, sessionsDir) {
30261
+ return liveSessions(workspaceRoot, sessionsDir).filter((s) => s.sessionId !== sessionId).map((s) => s.pid);
30262
+ }
30263
+ function busyReason(f, input2, cutoff) {
30264
+ const active = input2.sessions(f.workspace).some((s) => s.status !== "idle" || (s.statusUpdatedAt ?? Infinity) > cutoff);
30265
+ if (active)
30266
+ return "session active";
30267
+ if (f.startedAt.some((t) => t > cutoff))
30268
+ return `started < ${input2.minutes} min ago`;
30269
+ if ((input2.lastExec(f.workspace) ?? 0) > cutoff)
30270
+ return `gate ran < ${input2.minutes} min ago`;
30271
+ return null;
30272
+ }
30273
+ function idleStopMinutes() {
30274
+ const v = load()["overrides"]?.["idle_stop_minutes"];
30275
+ return Number.isInteger(v) && v >= 0 ? v : 30;
30276
+ }
30277
+ function lastExecMarker(workspaceRoot) {
30278
+ return path30.join(dotDir(workspaceRoot), "last-exec");
30279
+ }
30280
+ function mtimeMs(p) {
30281
+ try {
30282
+ return fs31.statSync(p).mtimeMs;
30283
+ } catch {
30284
+ return null;
30285
+ }
30286
+ }
30287
+ function runningFeatures(runCmd) {
30288
+ try {
30289
+ const ids = runCmd(["docker", "ps", "-q", "--filter", `label=${LABEL_FEATURE2}`]);
30290
+ if (ids.status !== 0)
30291
+ return null;
30292
+ const list2 = ids.stdout.split("\n").filter(Boolean);
30293
+ if (!list2.length)
30294
+ return [];
30295
+ const fmt = `{{index .Config.Labels "${LABEL_FEATURE2}"}} {{index .Config.Labels "${LABEL_WORKDIR}"}} {{.State.StartedAt}}`;
30296
+ const inspect = runCmd(["docker", "inspect", "--format", fmt, ...list2]);
30297
+ if (inspect.status !== 0)
30298
+ return null;
30299
+ const byFeature = /* @__PURE__ */ new Map();
30300
+ for (const line of inspect.stdout.split("\n").filter(Boolean)) {
30301
+ const [feature2, workspace, started] = line.split(" ");
30302
+ if (!feature2 || !workspace)
30303
+ continue;
30304
+ const f = byFeature.get(feature2) ?? { feature: feature2, workspace, startedAt: [] };
30305
+ f.startedAt.push(Date.parse(started));
30306
+ byFeature.set(feature2, f);
30307
+ }
30308
+ return [...byFeature.values()];
30309
+ } catch {
30310
+ return null;
30311
+ }
30312
+ }
30313
+ async function stopIdle(minutes, opts = {}) {
30314
+ const runCmd = opts.run ?? ((cmd) => run(cmd, { capture: true, timeout: 20 }));
30315
+ const found = runningFeatures(runCmd);
30316
+ if (!found)
30317
+ return { ok: true, lines: ["docker unavailable \u2014 nothing reaped"] };
30318
+ const running = found.filter((f) => !opts.only || f.feature === opts.only);
30319
+ if (!running.length)
30320
+ return { ok: true, lines: ["no running feature containers"] };
30321
+ const input2 = {
30322
+ minutes,
30323
+ now: opts.now ?? Date.now(),
30324
+ running,
30325
+ sessions: (ws) => liveSessions(ws, opts.sessionsDir),
30326
+ lastExec: (ws) => mtimeMs(lastExecMarker(ws))
30327
+ };
30328
+ const cutoff = input2.now - minutes * 6e4;
30329
+ const lines = [];
30330
+ let ok = true;
30331
+ for (const f of running) {
30332
+ const why = f.feature === "infra" ? "shared infra" : insideOwnContainer(f.feature) ? "running inside its own container" : busyReason(f, input2, cutoff);
30333
+ if (why) {
30334
+ lines.push(`${f.feature}: kept (${why})`);
30335
+ continue;
30336
+ }
30337
+ try {
30338
+ await (opts.stop ?? stop)(f.feature);
30339
+ lines.push(`${f.feature}: stopped (idle \u2265 ${minutes} min)`);
30340
+ } catch (e) {
30341
+ ok = false;
30342
+ lines.push(`${f.feature}: stop failed \u2014 ${e.message}`);
30343
+ }
30344
+ }
30345
+ return { ok, lines };
30193
30346
  }
30194
30347
  function idleStopFeature(input2) {
30195
30348
  const { feature: feature2, workspaceRoot, sessionId, reason: reason2, entrypoint, sessionsDir } = input2;
@@ -30514,7 +30667,7 @@ async function teardownNotes(feature2) {
30514
30667
  graduated
30515
30668
  };
30516
30669
  }
30517
- var _CONTINUING_REASONS, REAL_PROXY_OPS;
30670
+ var _CONTINUING_REASONS, LABEL_FEATURE2, LABEL_WORKDIR, REAL_PROXY_OPS;
30518
30671
  var init_lifecycle = __esm({
30519
30672
  "../../packages/sandbox-core/dist/lifecycle.js"() {
30520
30673
  "use strict";
@@ -30525,17 +30678,22 @@ var init_lifecycle = __esm({
30525
30678
  init_compose_file();
30526
30679
  init_config();
30527
30680
  init_docker();
30681
+ init_dot_dir();
30528
30682
  init_errors();
30529
30683
  init_feature_routing();
30530
30684
  init_generate();
30685
+ init_global_config();
30531
30686
  init_graduate();
30532
30687
  init_k8s();
30533
30688
  init_notes();
30689
+ init_proc();
30534
30690
  init_reporter();
30535
30691
  init_secrets();
30536
30692
  init_slots();
30537
30693
  init_strategies();
30538
30694
  _CONTINUING_REASONS = /* @__PURE__ */ new Set(["clear", "resume"]);
30695
+ LABEL_FEATURE2 = "com.sandbox.local.feature";
30696
+ LABEL_WORKDIR = "com.docker.compose.project.working_dir";
30539
30697
  REAL_PROXY_OPS = {
30540
30698
  generate: () => generateCaddyfile(),
30541
30699
  slots: () => getAllSlots(),
@@ -31434,6 +31592,7 @@ async function run3(args) {
31434
31592
  console.log(`
31435
31593
  ${Colors.BOLD}=== sandbox up: ${feature2} ===${Colors.RESET}
31436
31594
  `);
31595
+ if (args.dev) process.env.SANDBOX_DEV = "1";
31437
31596
  printSummary(await up(feature2, workspace, { signal: interruptSignal() }));
31438
31597
  }
31439
31598
  var init_up = __esm({
@@ -31954,6 +32113,17 @@ async function run8(args) {
31954
32113
  stopIfIdle();
31955
32114
  return;
31956
32115
  }
32116
+ if (args.idle != null) {
32117
+ const minutes = Number(args.idle);
32118
+ if (!Number.isInteger(minutes) || minutes < 1) {
32119
+ error("--idle takes whole minutes \u2265 1");
32120
+ process.exit(1);
32121
+ }
32122
+ const result = await stopIdle(minutes, { only: args.feature ?? void 0 });
32123
+ console.log(result.lines.join("\n"));
32124
+ if (!result.ok) process.exit(1);
32125
+ return;
32126
+ }
31957
32127
  const dotenv = loadDotenv(process.cwd());
31958
32128
  const feature2 = args.feature || dotenv["FEATURE"];
31959
32129
  if (!feature2) {
@@ -32565,16 +32735,19 @@ function discoverFragments(reposRoot) {
32565
32735
  continue;
32566
32736
  if (fs40.statSync(path39.join(reposRoot, repo, ".git"), { throwIfNoEntry: false })?.isFile())
32567
32737
  continue;
32568
- let doc;
32569
- try {
32570
- doc = (0, import_yaml2.parse)(fs40.readFileSync(file, "utf8"));
32571
- } catch (e) {
32572
- throw new RegistryError(`${file}: ${e instanceof Error ? e.message : String(e)}`);
32573
- }
32574
- out.push({ repo, path: file, doc: isDict7(doc) ? doc : {} });
32738
+ out.push(readFragment(repo, file));
32575
32739
  }
32576
32740
  return out;
32577
32741
  }
32742
+ function readFragment(repo, file) {
32743
+ let doc;
32744
+ try {
32745
+ doc = (0, import_yaml2.parse)(fs40.readFileSync(file, "utf8"));
32746
+ } catch (e) {
32747
+ throw new RegistryError(`${file}: ${e instanceof Error ? e.message : String(e)}`);
32748
+ }
32749
+ return { repo, path: file, doc: isDict7(doc) ? doc : {} };
32750
+ }
32578
32751
  function expandPlaceholders(value, paths) {
32579
32752
  if (typeof value === "string") {
32580
32753
  return value.replace(PLACEHOLDER, (_m, key) => paths[key]);
@@ -32736,6 +32909,18 @@ function readLocalOverlay(localPath) {
32736
32909
  throw new RegistryError(`${localPath}: must be a map of registry keys`);
32737
32910
  return doc;
32738
32911
  }
32912
+ function branchRegistry(repo, worktree, reposRoot = getReposRoot()) {
32913
+ const file = path39.join(worktree, FRAGMENT_REL);
32914
+ if (!fs40.existsSync(file))
32915
+ return null;
32916
+ const fragments = discoverFragments(reposRoot).filter((f) => f.repo !== repo);
32917
+ fragments.push(readFragment(repo, file));
32918
+ return mergeRegistry(fragments, {
32919
+ defaults: REGISTRY_DEFAULTS,
32920
+ local: readLocalOverlay(servicesLocalYamlPath()),
32921
+ paths: placeholderPaths()
32922
+ });
32923
+ }
32739
32924
  function syncRegistry(opts = {}) {
32740
32925
  const label = "config services.yaml";
32741
32926
  const reposRoot = opts.reposRoot ?? getReposRoot();
@@ -37633,8 +37818,8 @@ var require_utils = __commonJS({
37633
37818
  }
37634
37819
  return ind;
37635
37820
  }
37636
- function removeDotSegments(path73) {
37637
- let input2 = path73;
37821
+ function removeDotSegments(path74) {
37822
+ let input2 = path74;
37638
37823
  const output = [];
37639
37824
  let nextSlash = -1;
37640
37825
  let len = 0;
@@ -38044,8 +38229,8 @@ var require_schemes = __commonJS({
38044
38229
  }
38045
38230
  if (wsComponent.resourceName) {
38046
38231
  const queryIndex = wsComponent.resourceName.indexOf("?");
38047
- const path73 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
38048
- wsComponent.path = path73 && path73 !== "/" ? path73 : void 0;
38232
+ const path74 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
38233
+ wsComponent.path = path74 && path74 !== "/" ? path74 : void 0;
38049
38234
  wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
38050
38235
  wsComponent.resourceName = void 0;
38051
38236
  }
@@ -49517,10 +49702,10 @@ var require_lib3 = __commonJS({
49517
49702
  exports.analyse = analyse;
49518
49703
  var detectFile = (filepath, opts = {}) => new Promise((resolve13, reject) => {
49519
49704
  let fd;
49520
- const fs65 = (0, node_1.default)();
49705
+ const fs66 = (0, node_1.default)();
49521
49706
  const handler = (err, buffer) => {
49522
49707
  if (fd) {
49523
- fs65.closeSync(fd);
49708
+ fs66.closeSync(fd);
49524
49709
  }
49525
49710
  if (err) {
49526
49711
  reject(err);
@@ -49532,9 +49717,9 @@ var require_lib3 = __commonJS({
49532
49717
  };
49533
49718
  const sampleSize = (opts === null || opts === void 0 ? void 0 : opts.sampleSize) || 0;
49534
49719
  if (sampleSize > 0) {
49535
- fd = fs65.openSync(filepath, "r");
49720
+ fd = fs66.openSync(filepath, "r");
49536
49721
  let sample = Buffer.allocUnsafe(sampleSize);
49537
- fs65.read(fd, sample, 0, sampleSize, opts.offset, (err, bytesRead) => {
49722
+ fs66.read(fd, sample, 0, sampleSize, opts.offset, (err, bytesRead) => {
49538
49723
  if (err) {
49539
49724
  handler(err, null);
49540
49725
  } else {
@@ -49546,22 +49731,22 @@ var require_lib3 = __commonJS({
49546
49731
  });
49547
49732
  return;
49548
49733
  }
49549
- fs65.readFile(filepath, handler);
49734
+ fs66.readFile(filepath, handler);
49550
49735
  });
49551
49736
  exports.detectFile = detectFile;
49552
49737
  var detectFileSync = (filepath, opts = {}) => {
49553
- const fs65 = (0, node_1.default)();
49738
+ const fs66 = (0, node_1.default)();
49554
49739
  if (opts && opts.sampleSize) {
49555
- const fd = fs65.openSync(filepath, "r");
49740
+ const fd = fs66.openSync(filepath, "r");
49556
49741
  let sample = Buffer.allocUnsafe(opts.sampleSize);
49557
- const bytesRead = fs65.readSync(fd, sample, 0, opts.sampleSize, opts.offset);
49742
+ const bytesRead = fs66.readSync(fd, sample, 0, opts.sampleSize, opts.offset);
49558
49743
  if (bytesRead < opts.sampleSize) {
49559
49744
  sample = sample.subarray(0, bytesRead);
49560
49745
  }
49561
- fs65.closeSync(fd);
49746
+ fs66.closeSync(fd);
49562
49747
  return (0, exports.detect)(sample);
49563
49748
  }
49564
- return (0, exports.detect)(fs65.readFileSync(filepath));
49749
+ return (0, exports.detect)(fs66.readFileSync(filepath));
49565
49750
  };
49566
49751
  exports.detectFileSync = detectFileSync;
49567
49752
  exports.default = {
@@ -54328,8 +54513,8 @@ function parseRect(size, position) {
54328
54513
  const [x, y] = (position ?? "60,60").split(",").map(Number);
54329
54514
  return { width, height: height2, x, y };
54330
54515
  }
54331
- async function wdFetch(port, path73, method, body) {
54332
- const r = await fetch(`http://127.0.0.1:${port}${path73}`, {
54516
+ async function wdFetch(port, path74, method, body) {
54517
+ const r = await fetch(`http://127.0.0.1:${port}${path74}`, {
54333
54518
  method,
54334
54519
  headers: body !== void 0 ? { "Content-Type": "application/json" } : void 0,
54335
54520
  body: body !== void 0 ? JSON.stringify(body) : void 0
@@ -54337,7 +54522,7 @@ async function wdFetch(port, path73, method, body) {
54337
54522
  const data = await r.json().catch(() => ({}));
54338
54523
  if (!r.ok || data?.value?.error) {
54339
54524
  const msg = data?.value?.message || data?.value?.error || `HTTP ${r.status}`;
54340
- throw new Error(`safaridriver ${method} ${path73} failed: ${msg}`);
54525
+ throw new Error(`safaridriver ${method} ${path74} failed: ${msg}`);
54341
54526
  }
54342
54527
  return data.value;
54343
54528
  }
@@ -55175,11 +55360,131 @@ var init_issue_snapshot = __esm({
55175
55360
  }
55176
55361
  });
55177
55362
 
55363
+ // ../../packages/sandbox-core/dist/issue_status.js
55364
+ import * as fs49 from "node:fs";
55365
+ import * as path57 from "node:path";
55366
+ function resolveIssueStatuses(overrides, tracker) {
55367
+ const map = overrides["issue_statuses"] ?? {};
55368
+ const out = { ...DEFAULTS[tracker] };
55369
+ for (const point of ISSUE_POINTS) {
55370
+ const alias = ALIASES[point];
55371
+ if (Object.hasOwn(map, point))
55372
+ out[point] = blankToNull(map[point]);
55373
+ else if (alias && overrides[alias] !== void 0)
55374
+ out[point] = blankToNull(overrides[alias]);
55375
+ }
55376
+ return out;
55377
+ }
55378
+ function issuePosition(issue, statuses) {
55379
+ const names = new Set([issue.status, ...issue.labels ?? []].flatMap((n) => n ? [n.trim().toLowerCase()] : []));
55380
+ const is = (point) => {
55381
+ const s = statuses[point];
55382
+ return s !== null && names.has(s.toLowerCase());
55383
+ };
55384
+ if (issue.statusCategory === "done" || is("done"))
55385
+ return 4;
55386
+ if (is("merged"))
55387
+ return 3;
55388
+ if (is("in_review"))
55389
+ return 2;
55390
+ if (is("in_progress"))
55391
+ return 1;
55392
+ return issue.statusCategory === "indeterminate" ? 1 : 0;
55393
+ }
55394
+ async function moveIssue(tracker, ref, point, statuses, opts) {
55395
+ const status2 = statuses[point];
55396
+ try {
55397
+ if (status2 === null) {
55398
+ if (!opts.alwaysComment)
55399
+ return "";
55400
+ await tracker.updateIssue(ref, { comment: opts.comment });
55401
+ return `${ref} commented (no ${point} status configured)`;
55402
+ }
55403
+ const issue = await tracker.fetchIssue(ref);
55404
+ if (!issue)
55405
+ return `${ref} not moved to "${status2}": could not read its current status`;
55406
+ const pos = issuePosition(issue, statuses);
55407
+ if (!(RANK[point] > pos || opts.back && pos >= 2 && pos < 4)) {
55408
+ if (opts.alwaysComment)
55409
+ await tracker.updateIssue(ref, { comment: opts.comment });
55410
+ const label = issue.labels?.find((l) => ISSUE_POINTS.some((p) => statuses[p]?.toLowerCase() === l.toLowerCase()));
55411
+ return `${ref} already ${issue.status ?? "past that"}${label ? ` (${label})` : ""}`;
55412
+ }
55413
+ await tracker.updateIssue(ref, { status: status2, comment: opts.comment });
55414
+ return `${ref} \u2192 ${status2}`;
55415
+ } catch (e) {
55416
+ return `${ref} not moved to "${status2 ?? point}": ${e.message}`;
55417
+ }
55418
+ }
55419
+ function currentStage(ws) {
55420
+ let dirs;
55421
+ try {
55422
+ dirs = fs49.readdirSync(path57.join(ws, "stages")).filter((d) => /^\d+_/.test(d)).sort();
55423
+ } catch {
55424
+ return "research phase";
55425
+ }
55426
+ const empty = dirs.find((d) => {
55427
+ try {
55428
+ return !fs49.readdirSync(path57.join(ws, "stages", d, "output")).some((f) => !f.startsWith("."));
55429
+ } catch {
55430
+ return true;
55431
+ }
55432
+ });
55433
+ return `stage ${empty ?? dirs[0] ?? "01"}`;
55434
+ }
55435
+ async function startIssue(ws, tracker = issueTracker(), statuses = resolveIssueStatuses(load()["overrides"] ?? {}, loadIssueTracker())) {
55436
+ const ref = issueIdOf(loadConfigFile(ws, null));
55437
+ if (!ref)
55438
+ return "no issue";
55439
+ return moveIssue(tracker, ref, "in_progress", statuses, {
55440
+ comment: `Work started: ${currentStage(ws)} in workspace ${path57.basename(ws)}.`
55441
+ });
55442
+ }
55443
+ var ISSUE_POINTS, DEFAULTS, ALIASES, RANK, blankToNull;
55444
+ var init_issue_status = __esm({
55445
+ "../../packages/sandbox-core/dist/issue_status.js"() {
55446
+ "use strict";
55447
+ init_define_SANDBOX_BUILD();
55448
+ init_global_config();
55449
+ init_issue_tracker();
55450
+ init_workspace_prompts();
55451
+ ISSUE_POINTS = ["in_progress", "in_review", "merged", "done", "abandoned"];
55452
+ DEFAULTS = {
55453
+ jira: {
55454
+ in_progress: "In Progress",
55455
+ in_review: "In Review",
55456
+ merged: null,
55457
+ done: "Done",
55458
+ abandoned: null
55459
+ },
55460
+ github: {
55461
+ in_progress: "status: in progress",
55462
+ in_review: "status: in review",
55463
+ merged: null,
55464
+ done: "closed",
55465
+ abandoned: "not planned"
55466
+ }
55467
+ };
55468
+ ALIASES = {
55469
+ in_review: "issue_review_status",
55470
+ done: "issue_done_status"
55471
+ };
55472
+ RANK = {
55473
+ in_progress: 1,
55474
+ in_review: 2,
55475
+ merged: 3,
55476
+ done: 4,
55477
+ abandoned: 4
55478
+ };
55479
+ blankToNull = (v) => v === null || v === void 0 || String(v).trim() === "" ? null : String(v).trim();
55480
+ }
55481
+ });
55482
+
55178
55483
  // ../../packages/sandbox-core/dist/pr_lifecycle.js
55179
55484
  import { execFile } from "node:child_process";
55180
- import * as fs49 from "node:fs";
55485
+ import * as fs50 from "node:fs";
55181
55486
  import * as os16 from "node:os";
55182
- import * as path57 from "node:path";
55487
+ import * as path58 from "node:path";
55183
55488
  async function httpStatus(url) {
55184
55489
  try {
55185
55490
  return (await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(5e3) })).status;
@@ -55196,27 +55501,51 @@ function defaultDeps() {
55196
55501
  archetypes: loadArchetypes(),
55197
55502
  repos: loadReposConfig(),
55198
55503
  tracker: issueTracker(),
55199
- reviewStatus: String(overrides["issue_review_status"] ?? "In Review"),
55200
- doneStatus: String(overrides["issue_done_status"] ?? (loadIssueTracker() === "github" ? "closed" : "Done")),
55504
+ statuses: resolveIssueStatuses(overrides, loadIssueTracker()),
55201
55505
  commitRecord: commitAndPush,
55202
55506
  remove: (name) => removeWorkspace(name, { closedOut: true }),
55203
55507
  terminals: (ws) => openTerminalSessions(ws),
55204
55508
  sourceOf: async (dir) => (await worktreeSources([dir]))[0] ?? null,
55205
- session: (ws) => callbackFields(ws, process.env)
55509
+ session: (ws) => callbackFields(ws, process.env),
55510
+ branchRegistry: (repo, worktree) => branchRegistry(repo, worktree),
55511
+ stop: stopWorkspace,
55512
+ reapIdle: async () => {
55513
+ const minutes = idleStopMinutes();
55514
+ return minutes ? stopIdle(minutes) : { lines: [] };
55515
+ }
55206
55516
  };
55207
55517
  }
55518
+ async function stopWorkspace(ws) {
55519
+ const feature2 = readEnv(ws, "FEATURE");
55520
+ if (!feature2)
55521
+ return "containers already stopped";
55522
+ try {
55523
+ await stop(feature2);
55524
+ return "containers stopped (slot and volumes kept; `werkmeister up` resumes)";
55525
+ } catch (e) {
55526
+ if (e.code === "NOT_RUNNING")
55527
+ return "containers already stopped";
55528
+ return `could not stop containers \u2014 ${e.message}`;
55529
+ }
55530
+ }
55531
+ function touchExecMarker(ws) {
55532
+ try {
55533
+ fs50.writeFileSync(lastExecMarker(ws), "");
55534
+ } catch {
55535
+ }
55536
+ }
55208
55537
  function prFileDir(ws) {
55209
55538
  let names;
55210
55539
  try {
55211
- names = fs49.readdirSync(path57.join(ws, "stages")).sort();
55540
+ names = fs50.readdirSync(path58.join(ws, "stages")).sort();
55212
55541
  } catch {
55213
- return path57.join(dotDir(ws), "notes");
55542
+ return path58.join(dotDir(ws), "notes");
55214
55543
  }
55215
55544
  const dir = names.find((n) => /^\d+_propose$/.test(n)) ?? names.find((n) => /^\d+_ship$/.test(n));
55216
- return dir ? path57.join(ws, "stages", dir, "output") : null;
55545
+ return dir ? path58.join(ws, "stages", dir, "output") : null;
55217
55546
  }
55218
55547
  function parsePrFile(p) {
55219
- const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(fs49.readFileSync(p, "utf8"));
55548
+ const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(fs50.readFileSync(p, "utf8"));
55220
55549
  let fm = null;
55221
55550
  let yamlError = "";
55222
55551
  try {
@@ -55230,7 +55559,7 @@ function parsePrFile(p) {
55230
55559
  const heading2 = /^#\s+(.+)$/m.exec(m[2]);
55231
55560
  return {
55232
55561
  path: p,
55233
- dir: path57.basename(p).replace(/^pr-/, "").replace(/\.md$/, ""),
55562
+ dir: path58.basename(p).replace(/^pr-/, "").replace(/\.md$/, ""),
55234
55563
  fm,
55235
55564
  title: heading2 ? heading2[1].trim() : "",
55236
55565
  body: heading2 ? m[2].slice(heading2.index + heading2[0].length).replace(/^\s*\n/, "") : m[2]
@@ -55242,10 +55571,10 @@ function readPrFiles(ws) {
55242
55571
  return [];
55243
55572
  let names = [];
55244
55573
  try {
55245
- names = fs49.readdirSync(dir).filter((n) => /^pr-.+\.md$/.test(n)).sort();
55574
+ names = fs50.readdirSync(dir).filter((n) => /^pr-.+\.md$/.test(n)).sort();
55246
55575
  } catch {
55247
55576
  }
55248
- return names.map((n) => parsePrFile(path57.join(dir, n)));
55577
+ return names.map((n) => parsePrFile(path58.join(dir, n)));
55249
55578
  }
55250
55579
  function writePrFields(file, fields) {
55251
55580
  updateNoteStatus(file.path, fields);
@@ -55260,18 +55589,18 @@ function passTable(text) {
55260
55589
  }
55261
55590
  function stagesCheck(ws) {
55262
55591
  const prDir = prFileDir(ws);
55263
- if (!fs49.existsSync(path57.join(ws, "stages"))) {
55264
- const open = openDecisions(ws).map((n) => path57.basename(n.path));
55592
+ if (!fs50.existsSync(path58.join(ws, "stages"))) {
55593
+ const open = openDecisions(ws).map((n) => path58.basename(n.path));
55265
55594
  return {
55266
55595
  name: "stages",
55267
55596
  ok: open.length === 0,
55268
55597
  detail: open.length ? `open decision notes: ${open.join(", ")}` : "no open decisions"
55269
55598
  };
55270
55599
  }
55271
- const own = prDir ? path57.basename(path57.dirname(prDir)) : "";
55272
- const empty = fs49.readdirSync(path57.join(ws, "stages")).sort().filter((d) => d < own).filter((d) => {
55600
+ const own = prDir ? path58.basename(path58.dirname(prDir)) : "";
55601
+ const empty = fs50.readdirSync(path58.join(ws, "stages")).sort().filter((d) => d < own).filter((d) => {
55273
55602
  try {
55274
- return !fs49.readdirSync(path57.join(ws, "stages", d, "output")).some((f) => !f.startsWith("."));
55603
+ return !fs50.readdirSync(path58.join(ws, "stages", d, "output")).some((f) => !f.startsWith("."));
55275
55604
  } catch {
55276
55605
  return true;
55277
55606
  }
@@ -55283,14 +55612,14 @@ function stagesCheck(ws) {
55283
55612
  };
55284
55613
  }
55285
55614
  function exerciseFile(ws) {
55286
- if (!fs49.existsSync(path57.join(ws, "stages"))) {
55287
- const p = path57.join(dotDir(ws), "notes", "exercise.md");
55288
- return fs49.existsSync(p) ? p : null;
55615
+ if (!fs50.existsSync(path58.join(ws, "stages"))) {
55616
+ const p = path58.join(dotDir(ws), "notes", "exercise.md");
55617
+ return fs50.existsSync(p) ? p : null;
55289
55618
  }
55290
55619
  for (const name of ["exercise.md", "verify.md"]) {
55291
- for (const d of fs49.readdirSync(path57.join(ws, "stages")).sort()) {
55292
- const p = path57.join(ws, "stages", d, "output", name);
55293
- if (fs49.existsSync(p))
55620
+ for (const d of fs50.readdirSync(path58.join(ws, "stages")).sort()) {
55621
+ const p = path58.join(ws, "stages", d, "output", name);
55622
+ if (fs50.existsSync(p))
55294
55623
  return p;
55295
55624
  }
55296
55625
  }
@@ -55318,7 +55647,7 @@ function mapTouched(files, repo) {
55318
55647
  }
55319
55648
  function readEnv(ws, key) {
55320
55649
  try {
55321
- for (const line of fs49.readFileSync(path57.join(ws, ".env"), "utf8").split(/\r?\n/)) {
55650
+ for (const line of fs50.readFileSync(path58.join(ws, ".env"), "utf8").split(/\r?\n/)) {
55322
55651
  const eq = line.indexOf("=");
55323
55652
  if (eq > 0 && line.slice(0, eq).trim() === key)
55324
55653
  return line.slice(eq + 1).trim();
@@ -55332,14 +55661,24 @@ async function runGate(ws, file, deps = defaultDeps()) {
55332
55661
  const add = (name, ok, detail) => checks.push({ name, ok, detail });
55333
55662
  const table = passTable(file.body);
55334
55663
  add("acceptance", table === null, table ? `PR file ${table}` : "all PASS");
55335
- const dir = path57.join(ws, file.dir);
55664
+ const dir = path58.join(ws, file.dir);
55336
55665
  const status2 = await deps.run("git", ["-C", dir, "status", "--porcelain"]);
55337
55666
  add("clean", status2.code === 0 && status2.stdout.trim() === "", status2.code ? status2.stderr.trim() : status2.stdout.trim() ? `uncommitted:
55338
55667
  ${status2.stdout.trimEnd()}` : "clean");
55668
+ let services = deps.services;
55669
+ let repo = deps.repos[file.dir] ?? {};
55670
+ try {
55671
+ const own = deps.branchRegistry(file.dir, dir);
55672
+ if (own) {
55673
+ services = own.services;
55674
+ repo = own.repos[file.dir] ?? {};
55675
+ }
55676
+ } catch (e) {
55677
+ add("registry", false, `branch fragment: ${e instanceof Error ? e.message : String(e)}`);
55678
+ }
55339
55679
  const cfg = loadConfigFile(ws, null);
55340
55680
  const feature2 = String(cfg["feature"] ?? readEnv(ws, "FEATURE")).trim();
55341
55681
  const local = new Set(String(cfg["services"] ?? "").split(",").map((s) => s.trim()).filter(Boolean));
55342
- const repo = deps.repos[file.dir] ?? {};
55343
55682
  const ps = await deps.run("docker", ["ps", "--format", "{{.Names}}"]);
55344
55683
  const running = new Set(ps.stdout.split("\n").map((s) => s.trim()).filter(Boolean));
55345
55684
  const containerFor = (svc) => {
@@ -55357,7 +55696,7 @@ ${status2.stdout.trimEnd()}` : "clean");
55357
55696
  const touched = diff.code === 0 ? mapTouched(diff.stdout.split("\n").filter(Boolean), repo) : [];
55358
55697
  const skips = file.fm["skip_tests"] ?? {};
55359
55698
  for (const svc of touched) {
55360
- const cmd = serviceTestCommand(svc, deps.services[svc], deps.archetypes);
55699
+ const cmd = serviceTestCommand(svc, services[svc], deps.archetypes);
55361
55700
  const why = reason(skips[svc]);
55362
55701
  if (!cmd) {
55363
55702
  add(`test ${svc}`, Boolean(why), why ? `skipped: ${why}` : "no test command registered \u2014 add `test:` to its registry entry, or `skip_tests: { " + svc + ": <reason> }`");
@@ -55368,16 +55707,17 @@ ${status2.stdout.trimEnd()}` : "clean");
55368
55707
  add(`test ${svc}`, Boolean(why), why ? `skipped: ${why}` : "no running container for this repo \u2014 run `sandbox up`");
55369
55708
  continue;
55370
55709
  }
55710
+ touchExecMarker(ws);
55371
55711
  const res = await deps.run("docker", ["exec", container, "sh", "-c", cmd]);
55372
55712
  add(`test ${svc}`, res.code === 0, `${cmd} \u2192 exit ${res.code}${res.code ? `
55373
55713
  ${(res.stdout + res.stderr).trimEnd().split("\n").slice(-15).join("\n")}` : ""}`);
55374
55714
  }
55375
55715
  const evidence = exerciseFile(ws);
55376
- const evidenceProblem = evidence ? passTable(fs49.readFileSync(evidence, "utf8")) : "is missing";
55377
- add("exercise", evidenceProblem === null, evidence ? `${path57.relative(ws, evidence)} ${evidenceProblem ?? "all PASS"}` : "no exercise.md \u2014 record the exercise checks first");
55716
+ const evidenceProblem = evidence ? passTable(fs50.readFileSync(evidence, "utf8")) : "is missing";
55717
+ add("exercise", evidenceProblem === null, evidence ? `${path58.relative(ws, evidence)} ${evidenceProblem ?? "all PASS"}` : "no exercise.md \u2014 record the exercise checks first");
55378
55718
  const slot = Number(readEnv(ws, "SANDBOX_SLOT"));
55379
55719
  for (const svc of touched.filter((s) => local.has(s))) {
55380
- const def = deps.services[svc] ?? {};
55720
+ const def = services[svc] ?? {};
55381
55721
  if (!running.has(`${feature2}-${svc}`)) {
55382
55722
  add(`baseline ${svc}`, false, "container not running \u2014 run `sandbox up`");
55383
55723
  continue;
@@ -55398,6 +55738,7 @@ ${(res.stdout + res.stderr).trimEnd().split("\n").slice(-15).join("\n")}` : ""}`
55398
55738
  else if (!container)
55399
55739
  add("release", false, "no running container for this repo \u2014 run `sandbox up`");
55400
55740
  else {
55741
+ touchExecMarker(ws);
55401
55742
  const res = await deps.run("docker", ["exec", container, "sh", "-c", repo["release_check"]]);
55402
55743
  add("release", res.code === 0, `${repo["release_check"]} \u2192 exit ${res.code}${res.code ? `
55403
55744
  ${(res.stdout + res.stderr).trimEnd().split("\n").slice(-5).join("\n")}` : ""}`);
@@ -55444,7 +55785,7 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55444
55785
  lines.push(...formatGate(f.dir, g));
55445
55786
  continue;
55446
55787
  }
55447
- const dir = path57.join(ws, f.dir);
55788
+ const dir = path58.join(ws, f.dir);
55448
55789
  const branch = String(f.fm["branch"] ?? "");
55449
55790
  const push = await deps.run("git", ["-C", dir, "push", "-u", "origin", branch]);
55450
55791
  if (push.code !== 0) {
@@ -55468,8 +55809,8 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55468
55809
  ], dir);
55469
55810
  let url = existing2.code === 0 ? existing2.stdout.trim() : "";
55470
55811
  if (!url) {
55471
- const bodyFile = path57.join(fs49.mkdtempSync(path57.join(os16.tmpdir(), "sandbox-pr-")), "body.md");
55472
- fs49.writeFileSync(bodyFile, f.body);
55812
+ const bodyFile = path58.join(fs50.mkdtempSync(path58.join(os16.tmpdir(), "sandbox-pr-")), "body.md");
55813
+ fs50.writeFileSync(bodyFile, f.body);
55473
55814
  const created = await deps.run("gh", [
55474
55815
  "pr",
55475
55816
  "create",
@@ -55484,7 +55825,7 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55484
55825
  "--body-file",
55485
55826
  bodyFile
55486
55827
  ], dir);
55487
- fs49.rmSync(path57.dirname(bodyFile), { recursive: true, force: true });
55828
+ fs50.rmSync(path58.dirname(bodyFile), { recursive: true, force: true });
55488
55829
  url = created.stdout.trim().split("\n").pop() ?? "";
55489
55830
  if (created.code !== 0 || !url.startsWith("http")) {
55490
55831
  ok = false;
@@ -55493,53 +55834,76 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55493
55834
  }
55494
55835
  }
55495
55836
  writePrFields(f, { status: "open", url, ...await deps.session(ws) });
55496
- opened.push(path57.relative(ws, f.path));
55837
+ opened.push(path58.relative(ws, f.path));
55497
55838
  lines.push(`${f.dir}: open ${url}`);
55498
55839
  const ref = issueIdOf(loadConfigFile(ws, null));
55499
55840
  if (ref) {
55500
- try {
55501
- await deps.tracker.updateIssue(ref, { status: deps.reviewStatus });
55502
- } catch (e) {
55503
- lines.push(` (issue ${ref} not moved to ${deps.reviewStatus}: ${e.message})`);
55504
- }
55841
+ const moved = await moveIssue(deps.tracker, ref, "in_review", deps.statuses, {
55842
+ comment: `PR opened: ${url}`
55843
+ });
55844
+ if (moved)
55845
+ lines.push(` issue ${moved}`);
55505
55846
  }
55506
55847
  }
55507
55848
  if (opened.length)
55508
55849
  lines.push((await deps.commitRecord(ws, "chore: record opened PR(s)", opened)).message);
55850
+ if (ok && opened.length)
55851
+ lines.push(await deps.stop(ws));
55509
55852
  return { ok, lines };
55510
55853
  }
55511
55854
  async function refreshPrStatus(ws, deps = defaultDeps()) {
55512
55855
  const lines = [];
55513
55856
  const changed = [];
55857
+ const seen = {
55858
+ shipped: [],
55859
+ merged: [],
55860
+ closed: [],
55861
+ changesRequested: [],
55862
+ reReview: []
55863
+ };
55514
55864
  for (const f of readPrFiles(ws)) {
55515
55865
  const before = statusOf(f);
55866
+ const reviewBefore = f.fm["review"];
55516
55867
  if (before === "open" && f.fm["url"]) {
55517
55868
  const v = await deps.run("gh", [
55518
55869
  "pr",
55519
55870
  "view",
55520
55871
  String(f.fm["url"]),
55521
55872
  "--json",
55522
- "state,mergeCommit"
55873
+ "state,mergeCommit,reviewDecision,latestReviews"
55523
55874
  ]);
55524
55875
  if (v.code !== 0) {
55525
55876
  lines.push(`${f.dir}: gh pr view failed \u2014 ${v.stderr.trim()}`);
55526
55877
  continue;
55527
55878
  }
55528
55879
  const view2 = JSON.parse(v.stdout);
55529
- if (view2.state === "MERGED")
55880
+ const changesRequested = view2.reviewDecision === "CHANGES_REQUESTED" || (view2.latestReviews ?? []).some((r) => r.state === "CHANGES_REQUESTED");
55881
+ const url = String(f.fm["url"]);
55882
+ if (view2.state === "MERGED") {
55530
55883
  writePrFields(f, { status: "merged", merge_sha: view2.mergeCommit?.oid ?? "" });
55531
- else if (view2.state === "CLOSED")
55884
+ seen.merged.push(url);
55885
+ } else if (view2.state === "CLOSED") {
55532
55886
  writePrFields(f, {
55533
55887
  status: "closed",
55534
55888
  reason: reason(f.fm["reason"]) || "closed on GitHub"
55535
55889
  });
55890
+ seen.closed.push(url);
55891
+ } else if (changesRequested) {
55892
+ if (reviewBefore !== "changes_requested") {
55893
+ writePrFields(f, { review: "changes_requested" });
55894
+ seen.changesRequested.push(url);
55895
+ }
55896
+ } else if (reviewBefore === "changes_requested") {
55897
+ writePrFields(f, { review: void 0 });
55898
+ seen.reReview.push(url);
55899
+ }
55536
55900
  }
55537
55901
  if (statusOf(f) === "merged") {
55538
55902
  const repo = deps.repos[f.dir] ?? {};
55539
55903
  if (repo["ship"] !== "tag") {
55540
55904
  writePrFields(f, { status: "shipped" });
55541
55905
  } else {
55542
- const dir = path57.join(ws, f.dir);
55906
+ const dir = path58.join(ws, f.dir);
55543
55907
  await deps.run("git", ["-C", dir, "fetch", "-q", "--tags", "origin"]);
55544
55908
  const tags = await deps.run("git", [
55545
55909
  "-C",
@@ -55557,16 +55921,43 @@ async function refreshPrStatus(ws, deps = defaultDeps()) {
55557
55921
  }
55558
55922
  }
55559
55923
  const after = statusOf(f);
55560
- if (after !== before)
55561
- changed.push(path57.relative(ws, f.path));
55924
+ if (after === "shipped" && before !== "shipped")
55925
+ seen.shipped.push(f);
55926
+ if (after !== before || f.fm["review"] !== reviewBefore)
55927
+ changed.push(path58.relative(ws, f.path));
55562
55928
  lines.push(`${f.dir}: ${after}${f.fm["release"] ? ` (${f.fm["release"]})` : ""}${f.fm["url"] ? ` ${f.fm["url"]}` : ""}`);
55563
55929
  }
55930
+ const moved = await moveIssueForPrs(ws, seen, deps);
55931
+ if (moved)
55932
+ lines.push(`issue ${moved}`);
55933
+ if (seen.merged.length || seen.shipped.length)
55934
+ lines.push(await deps.stop(ws));
55564
55935
  if (changed.length)
55565
55936
  lines.push((await deps.commitRecord(ws, "chore: record PR status", changed)).message);
55566
55937
  return { lines, changed: changed.length > 0 };
55567
55938
  }
55939
+ async function moveIssueForPrs(ws, seen, deps) {
55940
+ const anything = Object.values(seen).some((v) => v.length);
55941
+ const ref = anything ? issueIdOf(loadConfigFile(ws, null)) : "";
55942
+ if (!ref)
55943
+ return "";
55944
+ const files = readPrFiles(ws);
55945
+ const state = lifecycleOf(ws).state;
55946
+ const move = (point, comment, back = false) => moveIssue(deps.tracker, ref, point, deps.statuses, { comment, back });
55947
+ if (seen.shipped.length && state === "shipped")
55948
+ return move("done", shippedComment(files.filter((f) => statusOf(f) === "shipped")));
55949
+ if (seen.merged.length && files.every((f) => PAST_MERGE.has(statusOf(f))))
55950
+ return move("merged", `Merged: ${seen.merged.join(", ")}`);
55951
+ if (seen.changesRequested.length)
55952
+ return move("in_progress", `Changes requested: ${seen.changesRequested.join(", ")}`, true);
55953
+ if (seen.closed.length && !files.some((f) => statusOf(f) === "open"))
55954
+ return move("in_progress", `PR closed without merging: ${seen.closed.join(", ")}`, true);
55955
+ if (seen.reReview.length)
55956
+ return move("in_review", `Back in review: ${seen.reReview.join(", ")}`);
55957
+ return "";
55958
+ }
55568
55959
  async function callbackFields(ws, env) {
55569
- const session = env["CLAUDE_CODE_SESSION_ID"] || (await resolveSessionSource(path57.basename(ws)).catch(() => null))?.sessionId;
55960
+ const session = env["CLAUDE_CODE_SESSION_ID"] || (await resolveSessionSource(path58.basename(ws)).catch(() => null))?.sessionId;
55570
55961
  if (!session)
55571
55962
  return {};
55572
55963
  const host = env["CLAUDE_CODE_HOST_SESSION_ID"];
@@ -55584,23 +55975,23 @@ function callbackMessage(status2, url, release) {
55584
55975
  function takeLock(file) {
55585
55976
  for (let i = 0; i < 2; i++) {
55586
55977
  try {
55587
- fs49.closeSync(fs49.openSync(file, "wx"));
55978
+ fs50.closeSync(fs50.openSync(file, "wx"));
55588
55979
  return true;
55589
55980
  } catch (e) {
55590
55981
  if (e.code !== "EEXIST")
55591
55982
  throw e;
55592
55983
  try {
55593
- if (Date.now() - fs49.statSync(file).mtimeMs < LOCK_STALE_MS)
55984
+ if (Date.now() - fs50.statSync(file).mtimeMs < LOCK_STALE_MS)
55594
55985
  return false;
55595
- fs49.rmSync(file, { force: true });
55986
+ fs50.rmSync(file, { force: true });
55596
55987
  } catch {
55597
55988
  }
55598
55989
  }
55599
55990
  }
55600
55991
  return false;
55601
55992
  }
55602
- async function claimCallbacks(workspaces, opts, deps = defaultDeps(), lockFile = path57.join(sandboxHome(), "pr-callbacks.lock")) {
55603
- fs49.mkdirSync(path57.dirname(lockFile), { recursive: true });
55993
+ async function claimCallbacks(workspaces, opts, deps = defaultDeps(), lockFile = path58.join(sandboxHome(), "pr-callbacks.lock")) {
55994
+ fs50.mkdirSync(path58.dirname(lockFile), { recursive: true });
55604
55995
  if (!takeLock(lockFile))
55605
55996
  return { claimed: [], lines: ["another PR watch pass is running"] };
55606
55997
  const claimed2 = [];
@@ -55619,7 +56010,7 @@ async function claimCallbacks(workspaces, opts, deps = defaultDeps(), lockFile =
55619
56010
  continue;
55620
56011
  if (before.some((f) => statusOf(f) === "open" || statusOf(f) === "merged"))
55621
56012
  for (const l of (await refreshPrStatus(ws, deps)).lines)
55622
- lines.push(`${path57.basename(ws)}/${l}`);
56013
+ lines.push(`${path58.basename(ws)}/${l}`);
55623
56014
  const changed = [];
55624
56015
  for (const f of callbacks()) {
55625
56016
  const status2 = statusOf(f);
@@ -55637,23 +56028,23 @@ async function claimCallbacks(workspaces, opts, deps = defaultDeps(), lockFile =
55637
56028
  message
55638
56029
  });
55639
56030
  writePrFields(f, SETTLED.has(status2) ? { session: void 0, host_session: void 0, notified: void 0 } : { notified: status2 });
55640
- changed.push(path57.relative(ws, f.path));
56031
+ changed.push(path58.relative(ws, f.path));
55641
56032
  }
55642
56033
  if (changed.length)
55643
56034
  lines.push((await deps.commitRecord(ws, "chore: record PR callback", changed)).message);
55644
56035
  }
55645
56036
  } finally {
55646
- fs49.rmSync(lockFile, { force: true });
56037
+ fs50.rmSync(lockFile, { force: true });
55647
56038
  }
55648
56039
  return { claimed: claimed2, lines };
55649
56040
  }
55650
56041
  async function deliverHeadless(cb) {
55651
- const name = path57.basename(cb.ws);
56042
+ const name = path58.basename(cb.ws);
55652
56043
  if ((await resolveSessionSource(name, cb.session)).sessionId !== cb.session)
55653
56044
  throw new WorkspaceError(`session ${cb.session} is gone`);
55654
56045
  await sendToClaudeSession(name, cb.message, cb.session);
55655
56046
  }
55656
- async function watchPrs(opts, deps = defaultDeps(), send = deliverHeadless) {
56047
+ async function watchPrs(opts, deps = defaultDeps(), send = deliverHeadless, idleLock = path58.join(sandboxHome(), "idle-stop.lock")) {
55657
56048
  const { workspaces } = await listWorkspaces();
55658
56049
  const { claimed: claimed2, lines } = await claimCallbacks(workspaces.map((w) => w.path), opts, deps);
55659
56050
  const desktop = [];
@@ -55669,6 +56060,15 @@ async function watchPrs(opts, deps = defaultDeps(), send = deliverHeadless) {
55669
56060
  lines.push(`${cb.dir}: could not resume session ${cb.session}: ${e.message}`);
55670
56061
  }
55671
56062
  }
56063
+ fs50.mkdirSync(path58.dirname(idleLock), { recursive: true });
56064
+ if (takeLock(idleLock)) {
56065
+ try {
56066
+ lines.push(...(await deps.reapIdle()).lines);
56067
+ } finally {
56068
+ fs50.rmSync(idleLock, { force: true });
56069
+ }
56070
+ } else
56071
+ lines.push("another idle-stop pass is running");
55672
56072
  return { ok: true, lines, desktop };
55673
56073
  }
55674
56074
  async function adoptPr(ws, opts, deps = defaultDeps()) {
@@ -55682,8 +56082,8 @@ async function adoptPr(ws, opts, deps = defaultDeps()) {
55682
56082
  throw new WorkspaceError(`Name the repo \u2014 this workspace has ${wts.map((w) => w.repo).join(", ") || "no code worktree"}.`);
55683
56083
  repo = wts[0].repo;
55684
56084
  }
55685
- const file = path57.join(dir, `pr-${repo}.md`);
55686
- if (fs49.existsSync(file))
56085
+ const file = path58.join(dir, `pr-${repo}.md`);
56086
+ if (fs50.existsSync(file))
55687
56087
  throw new WorkspaceError(`${file} already exists \u2014 remove it first to adopt.`);
55688
56088
  let fm;
55689
56089
  let title;
@@ -55695,7 +56095,7 @@ async function adoptPr(ws, opts, deps = defaultDeps()) {
55695
56095
  } else {
55696
56096
  if (!opts.pr)
55697
56097
  throw new WorkspaceError("Give a PR number or URL, or --no-pr <reason>.");
55698
- const v = await deps.run("gh", ["pr", "view", opts.pr, "--json", "url,headRefName,baseRefName,title,state,mergeCommit"], path57.join(ws, repo));
56098
+ const v = await deps.run("gh", ["pr", "view", opts.pr, "--json", "url,headRefName,baseRefName,title,state,mergeCommit"], path58.join(ws, repo));
55699
56099
  if (v.code !== 0)
55700
56100
  throw new WorkspaceError(`gh pr view ${opts.pr} failed \u2014 ${v.stderr.trim()}`);
55701
56101
  const pr = JSON.parse(v.stdout);
@@ -55713,7 +56113,7 @@ async function adoptPr(ws, opts, deps = defaultDeps()) {
55713
56113
  };
55714
56114
  title = pr.title;
55715
56115
  }
55716
- fs49.writeFileSync(file, `---
56116
+ fs50.writeFileSync(file, `---
55717
56117
  ${import_yaml14.default.stringify(fm)}---
55718
56118
  # ${title}
55719
56119
 
@@ -55722,7 +56122,7 @@ Adopted with \`sandbox pr adopt\`: the work landed outside this workspace.
55722
56122
  const lines = [`${repo}: adopted as ${fm["status"]}`];
55723
56123
  if (fm["status"] !== "closed")
55724
56124
  lines.push(...(await refreshPrStatus(ws, deps)).lines);
55725
- lines.push((await deps.commitRecord(ws, "chore: adopt PR", [path57.relative(ws, file)])).message);
56125
+ lines.push((await deps.commitRecord(ws, "chore: adopt PR", [path58.relative(ws, file)])).message);
55726
56126
  return { ok: true, lines };
55727
56127
  }
55728
56128
  function lifecycleOf(ws) {
@@ -55738,7 +56138,7 @@ function lifecycleOf(ws) {
55738
56138
  }));
55739
56139
  const all = (set) => prs.every((p) => set.has(p.status));
55740
56140
  let state;
55741
- if (fs49.existsSync(path57.join(ws, "closeout.md")))
56141
+ if (fs50.existsSync(path58.join(ws, "closeout.md")))
55742
56142
  state = "closed-out";
55743
56143
  else if (!prs.length)
55744
56144
  state = "working";
@@ -55757,7 +56157,7 @@ function lifecycleOf(ws) {
55757
56157
  return { state, prs };
55758
56158
  }
55759
56159
  async function removalBlockers(ws, worktrees, run34 = execRun) {
55760
- if (fs49.existsSync(path57.join(ws, "closeout.md")))
56160
+ if (fs50.existsSync(path58.join(ws, "closeout.md")))
55761
56161
  return [];
55762
56162
  const files = readPrFiles(ws);
55763
56163
  const out = [];
@@ -55802,7 +56202,7 @@ function abandonWorkspace(ws, why) {
55802
56202
  for (const f of files)
55803
56203
  if (!SETTLED.has(statusOf(f)))
55804
56204
  writePrFields(f, { status: "abandoned", reason: why });
55805
- fs49.writeFileSync(path57.join(ws, "closeout.md"), renderCloseout(files, "not closed (abandoned)", `Abandoned: ${why}`));
56205
+ fs50.writeFileSync(path58.join(ws, "closeout.md"), renderCloseout(files, "not closed (abandoned)", `Abandoned: ${why}`));
55806
56206
  }
55807
56207
  async function openTerminalSessions(ws, run34 = execRun) {
55808
56208
  const ps = await run34("ps", ["-axo", "pid=,tty=,comm="]);
@@ -55811,7 +56211,7 @@ async function openTerminalSessions(ws, run34 = execRun) {
55811
56211
  const ttys = /* @__PURE__ */ new Map();
55812
56212
  for (const line of ps.stdout.split("\n")) {
55813
56213
  const m = /^\s*(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
55814
- if (m && path57.basename(m[3]) === "claude" && m[2] !== "??" && m[2] !== "?")
56214
+ if (m && path58.basename(m[3]) === "claude" && m[2] !== "??" && m[2] !== "?")
55815
56215
  ttys.set(m[1], m[2]);
55816
56216
  }
55817
56217
  if (!ttys.size)
@@ -55824,7 +56224,7 @@ async function openTerminalSessions(ws, run34 = execRun) {
55824
56224
  pid = line.slice(1);
55825
56225
  else if (line.startsWith("n") && ttys.has(pid)) {
55826
56226
  const cwd = line.slice(1);
55827
- if (cwd === ws || cwd.startsWith(ws + path57.sep))
56227
+ if (cwd === ws || cwd.startsWith(ws + path58.sep))
55828
56228
  out.push(`terminal session: pid ${pid} on ${ttys.get(pid)} in ${cwd} \u2014 /exit it`);
55829
56229
  }
55830
56230
  }
@@ -55850,28 +56250,26 @@ async function closeOut(name, opts = {}, deps = defaultDeps()) {
55850
56250
  const ref = issueIdOf(cfg);
55851
56251
  let issueLine = "none";
55852
56252
  if (ref) {
55853
- const done = deps.doneStatus.toLowerCase();
55854
- const current = (await deps.tracker.fetchIssue(ref))?.status ?? "";
55855
- if (abandoned) {
56253
+ if (why) {
56254
+ issueLine = await moveIssue(deps.tracker, ref, "abandoned", deps.statuses, {
56255
+ comment: `Abandoned: ${why}`,
56256
+ alwaysComment: true
56257
+ });
56258
+ } else if (abandoned) {
56259
+ const current = (await deps.tracker.fetchIssue(ref))?.status ?? "";
55856
56260
  issueLine = `${ref} left ${current || "as is"} (abandoned)`;
55857
- } else if ([done, "closed", "done"].includes(current.toLowerCase())) {
55858
- issueLine = `${ref} already ${current}`;
55859
56261
  } else {
55860
- const files2 = readPrFiles(ws);
55861
- const shipped = files2.filter((f) => statusOf(f) === "shipped").map((f) => `${f.fm["release"] ?? "merge"} \u2014 ${f.fm["url"]}`);
55862
- await deps.tracker.updateIssue(ref, {
55863
- status: deps.doneStatus,
55864
- comment: `Shipped: ${shipped.join("; ") || "no PRs"}`
55865
- });
55866
- issueLine = `${ref} \u2192 ${deps.doneStatus}`;
56262
+ issueLine = await moveIssue(deps.tracker, ref, "done", deps.statuses, {
56263
+ comment: shippedComment(readPrFiles(ws).filter((f) => statusOf(f) === "shipped"))
56264
+ }) || `${ref} left as is (no done status configured)`;
55867
56265
  }
55868
56266
  lines.push(`Issue: ${issueLine}`);
55869
56267
  }
55870
56268
  const files = readPrFiles(ws);
55871
- fs49.writeFileSync(path57.join(ws, "closeout.md"), renderCloseout(files, issueLine, why ? `Abandoned: ${why}` : void 0));
56269
+ fs50.writeFileSync(path58.join(ws, "closeout.md"), renderCloseout(files, issueLine, why ? `Abandoned: ${why}` : void 0));
55872
56270
  const branches = [];
55873
56271
  for (const f of abandoned ? [] : files.filter((x) => (statusOf(x) === "merged" || statusOf(x) === "shipped") && !x.fm["adopted"])) {
55874
- const src = await deps.sourceOf(path57.join(ws, f.dir));
56272
+ const src = await deps.sourceOf(path58.join(ws, f.dir));
55875
56273
  if (src && f.fm["branch"])
55876
56274
  branches.push({ src, branch: String(f.fm["branch"]) });
55877
56275
  }
@@ -55900,7 +56298,7 @@ async function closeOut(name, opts = {}, deps = defaultDeps()) {
55900
56298
  const command = `sandbox close-out ${name}${why ? " --abandon" : ""}`;
55901
56299
  return { command, output: lines.filter(Boolean).join("\n"), path: ws, terminals };
55902
56300
  }
55903
- var import_yaml14, SETTLED, PAST_MERGE, execRun, statusOf, reason, PASS_CELL, FAIL_CELL, LOCK_STALE_MS;
56301
+ var import_yaml14, SETTLED, PAST_MERGE, execRun, statusOf, reason, PASS_CELL, FAIL_CELL, shippedComment, LOCK_STALE_MS;
55904
56302
  var init_pr_lifecycle = __esm({
55905
56303
  "../../packages/sandbox-core/dist/pr_lifecycle.js"() {
55906
56304
  "use strict";
@@ -55911,8 +56309,11 @@ var init_pr_lifecycle = __esm({
55911
56309
  init_config();
55912
56310
  init_global_config();
55913
56311
  init_graduate();
56312
+ init_issue_status();
55914
56313
  init_issue_tracker();
56314
+ init_lifecycle();
55915
56315
  init_notes();
56316
+ init_registry_sync();
55916
56317
  init_sandbox();
55917
56318
  init_slots();
55918
56319
  init_workspace_prompts();
@@ -55932,6 +56333,7 @@ var init_pr_lifecycle = __esm({
55932
56333
  reason = (v) => typeof v === "string" ? v.trim() : "";
55933
56334
  PASS_CELL = /\|\s*PASS\s*\|/;
55934
56335
  FAIL_CELL = /\|\s*FAIL\s*\|/;
56336
+ shippedComment = (files) => `Shipped: ${files.map((f) => `${f.fm["release"] ?? "merge"} \u2014 ${f.fm["url"]}`).join("; ") || "no PRs"}`;
55935
56337
  LOCK_STALE_MS = 15 * 6e4;
55936
56338
  }
55937
56339
  });
@@ -56746,12 +57148,12 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
56746
57148
  }
56747
57149
  return result;
56748
57150
  }
56749
- function findNodeAtLocation(root, path73) {
57151
+ function findNodeAtLocation(root, path74) {
56750
57152
  if (!root) {
56751
57153
  return void 0;
56752
57154
  }
56753
57155
  let node = root;
56754
- for (let segment of path73) {
57156
+ for (let segment of path74) {
56755
57157
  if (typeof segment === "string") {
56756
57158
  if (node.type !== "object" || !Array.isArray(node.children)) {
56757
57159
  return void 0;
@@ -57118,14 +57520,14 @@ var init_parser = __esm({
57118
57520
 
57119
57521
  // ../../node_modules/jsonc-parser/lib/esm/impl/edit.js
57120
57522
  function setProperty(text, originalPath, value, options) {
57121
- const path73 = originalPath.slice();
57523
+ const path74 = originalPath.slice();
57122
57524
  const errors = [];
57123
57525
  const root = parseTree(text, errors);
57124
57526
  let parent = void 0;
57125
57527
  let lastSegment = void 0;
57126
- while (path73.length > 0) {
57127
- lastSegment = path73.pop();
57128
- parent = findNodeAtLocation(root, path73);
57528
+ while (path74.length > 0) {
57529
+ lastSegment = path74.pop();
57530
+ parent = findNodeAtLocation(root, path74);
57129
57531
  if (parent === void 0 && value !== void 0) {
57130
57532
  if (typeof lastSegment === "string") {
57131
57533
  value = { [lastSegment]: value };
@@ -57269,8 +57671,8 @@ var init_edit = __esm({
57269
57671
  });
57270
57672
 
57271
57673
  // ../../node_modules/jsonc-parser/lib/esm/main.js
57272
- function modify(text, path73, value, options) {
57273
- return setProperty(text, path73, value, options);
57674
+ function modify(text, path74, value, options) {
57675
+ return setProperty(text, path74, value, options);
57274
57676
  }
57275
57677
  function applyEdits(text, edits) {
57276
57678
  let sortedEdits = edits.slice(0).sort((a, b) => {
@@ -57352,20 +57754,20 @@ var init_main2 = __esm({
57352
57754
  });
57353
57755
 
57354
57756
  // ../../packages/sandbox-core/dist/plugin_check.js
57355
- import * as fs50 from "node:fs";
57757
+ import * as fs51 from "node:fs";
57356
57758
  import * as os17 from "node:os";
57357
- import * as path58 from "node:path";
57759
+ import * as path59 from "node:path";
57358
57760
  function defaultRepoPluginDir() {
57359
- return path58.join(sandboxRoot(), "plugins", PLUGIN_NAME);
57761
+ return path59.join(sandboxRoot(), "plugins", PLUGIN_NAME);
57360
57762
  }
57361
57763
  function defaultClaudePluginsDir() {
57362
- return path58.join(os17.homedir(), ".claude", "plugins");
57764
+ return path59.join(os17.homedir(), ".claude", "plugins");
57363
57765
  }
57364
57766
  function repoPluginVersion(repoPluginDir) {
57365
- const manifest = path58.join(repoPluginDir, ".claude-plugin", "plugin.json");
57767
+ const manifest = path59.join(repoPluginDir, ".claude-plugin", "plugin.json");
57366
57768
  let data;
57367
57769
  try {
57368
- data = JSON.parse(fs50.readFileSync(manifest, "utf8"));
57770
+ data = JSON.parse(fs51.readFileSync(manifest, "utf8"));
57369
57771
  } catch {
57370
57772
  return null;
57371
57773
  }
@@ -57373,10 +57775,10 @@ function repoPluginVersion(repoPluginDir) {
57373
57775
  return version ? String(version) : null;
57374
57776
  }
57375
57777
  function loadRegistry(claudePluginsDir) {
57376
- const registry = path58.join(claudePluginsDir, "installed_plugins.json");
57778
+ const registry = path59.join(claudePluginsDir, "installed_plugins.json");
57377
57779
  let data;
57378
57780
  try {
57379
- data = JSON.parse(fs50.readFileSync(registry, "utf8"));
57781
+ data = JSON.parse(fs51.readFileSync(registry, "utf8"));
57380
57782
  } catch {
57381
57783
  return null;
57382
57784
  }
@@ -57409,7 +57811,7 @@ function skillBodies(skillsDir) {
57409
57811
  const bodies = /* @__PURE__ */ new Map();
57410
57812
  let isDir6 = false;
57411
57813
  try {
57412
- isDir6 = fs50.statSync(skillsDir).isDirectory();
57814
+ isDir6 = fs51.statSync(skillsDir).isDirectory();
57413
57815
  } catch {
57414
57816
  isDir6 = false;
57415
57817
  }
@@ -57418,14 +57820,14 @@ function skillBodies(skillsDir) {
57418
57820
  }
57419
57821
  let entries;
57420
57822
  try {
57421
- entries = fs50.readdirSync(skillsDir, { withFileTypes: true });
57823
+ entries = fs51.readdirSync(skillsDir, { withFileTypes: true });
57422
57824
  } catch {
57423
57825
  return bodies;
57424
57826
  }
57425
57827
  const names = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
57426
57828
  for (const name of names) {
57427
57829
  try {
57428
- bodies.set(name, fs50.readFileSync(path58.join(skillsDir, name, "SKILL.md"), "utf8"));
57830
+ bodies.set(name, fs51.readFileSync(path59.join(skillsDir, name, "SKILL.md"), "utf8"));
57429
57831
  } catch {
57430
57832
  continue;
57431
57833
  }
@@ -57459,7 +57861,7 @@ function pluginDriftWarnings(repoPluginDir, claudePluginsDir) {
57459
57861
  }
57460
57862
  let repoSkillsIsDir = false;
57461
57863
  try {
57462
- repoSkillsIsDir = fs50.statSync(path58.join(repoPluginDir, "skills")).isDirectory();
57864
+ repoSkillsIsDir = fs51.statSync(path59.join(repoPluginDir, "skills")).isDirectory();
57463
57865
  } catch {
57464
57866
  repoSkillsIsDir = false;
57465
57867
  }
@@ -57480,7 +57882,7 @@ function pluginDriftWarnings(repoPluginDir, claudePluginsDir) {
57480
57882
  `${PLUGIN_NAME} plugin is not installed \u2014 the ops skills (${PLUGIN_NAME}:diagnose, ${PLUGIN_NAME}:status, ${PLUGIN_NAME}:workspace, \u2026) will not surface in Claude Code sessions. Run \`w9r setup\` to install it (or use /plugin in Claude Code). The sb-* skills are unaffected \u2014 they are projected into each workspace by init-workspace.`
57481
57883
  ];
57482
57884
  }
57483
- const drift = skillDrift(path58.join(repoPluginDir, "skills"), path58.join(installPath, "skills"));
57885
+ const drift = skillDrift(path59.join(repoPluginDir, "skills"), path59.join(installPath, "skills"));
57484
57886
  if (!drift.length) {
57485
57887
  return [];
57486
57888
  }
@@ -57506,15 +57908,15 @@ var init_plugin_check = __esm({
57506
57908
  });
57507
57909
 
57508
57910
  // ../../packages/sandbox-core/dist/test_run.js
57509
- import * as fs51 from "node:fs";
57911
+ import * as fs52 from "node:fs";
57510
57912
  import { createRequire } from "node:module";
57511
- import * as path59 from "node:path";
57913
+ import * as path60 from "node:path";
57512
57914
  function playwrightSearchDirs(workspace) {
57513
57915
  const dirs = [workspace];
57514
57916
  try {
57515
- for (const entry of fs51.readdirSync(workspace, { withFileTypes: true })) {
57516
- if (entry.isDirectory() && fs51.existsSync(path59.join(workspace, entry.name, ".git"))) {
57517
- dirs.push(path59.join(workspace, entry.name));
57917
+ for (const entry of fs52.readdirSync(workspace, { withFileTypes: true })) {
57918
+ if (entry.isDirectory() && fs52.existsSync(path60.join(workspace, entry.name, ".git"))) {
57919
+ dirs.push(path60.join(workspace, entry.name));
57518
57920
  }
57519
57921
  }
57520
57922
  } catch {
@@ -57523,20 +57925,20 @@ function playwrightSearchDirs(workspace) {
57523
57925
  }
57524
57926
  function playwrightCwd(dirs) {
57525
57927
  for (const dir of dirs) {
57526
- if (!(fs51.existsSync(dir) && fs51.statSync(dir).isDirectory())) {
57928
+ if (!(fs52.existsSync(dir) && fs52.statSync(dir).isDirectory())) {
57527
57929
  continue;
57528
57930
  }
57529
- if (CONFIG_NAMES.some((name) => fs51.existsSync(path59.join(dir, name)))) {
57931
+ if (CONFIG_NAMES.some((name) => fs52.existsSync(path60.join(dir, name)))) {
57530
57932
  return dir;
57531
57933
  }
57532
57934
  }
57533
57935
  return null;
57534
57936
  }
57535
57937
  function noSuiteMessage(workspace) {
57536
- const repoNames = playwrightSearchDirs(workspace).slice(1).map((d) => path59.basename(d));
57938
+ const repoNames = playwrightSearchDirs(workspace).slice(1).map((d) => path60.basename(d));
57537
57939
  let services = "";
57538
57940
  try {
57539
- services = /^SERVICES=(.*)$/m.exec(fs51.readFileSync(path59.join(workspace, ".env"), "utf8"))?.[1] ?? "";
57941
+ services = /^SERVICES=(.*)$/m.exec(fs52.readFileSync(path60.join(workspace, ".env"), "utf8"))?.[1] ?? "";
57540
57942
  } catch {
57541
57943
  }
57542
57944
  const selected = services.split(",").map((s) => s.trim()).filter(Boolean);
@@ -57550,8 +57952,8 @@ ${lines.join("\n")}` : "");
57550
57952
  }
57551
57953
  function installedPlaywrightVersion(cwd) {
57552
57954
  try {
57553
- const pkg = createRequire(path59.join(cwd, "package.json")).resolve("@playwright/test/package.json");
57554
- return JSON.parse(fs51.readFileSync(pkg, "utf8")).version ?? null;
57955
+ const pkg = createRequire(path60.join(cwd, "package.json")).resolve("@playwright/test/package.json");
57956
+ return JSON.parse(fs52.readFileSync(pkg, "utf8")).version ?? null;
57555
57957
  } catch {
57556
57958
  return null;
57557
57959
  }
@@ -57631,9 +58033,9 @@ var init_test_run = __esm({
57631
58033
  });
57632
58034
 
57633
58035
  // ../../packages/sandbox-core/dist/machine_setup.js
57634
- import * as fs52 from "node:fs";
58036
+ import * as fs53 from "node:fs";
57635
58037
  import * as os18 from "node:os";
57636
- import * as path60 from "node:path";
58038
+ import * as path61 from "node:path";
57637
58039
  function briefOutput(out, maxLines = 4) {
57638
58040
  const lines = out.split("\n").map((l) => l.trimEnd()).filter((l) => l.trim() && !/^\s*[✔✓⣾⣽⣻⢿⡿⣟⣯⣷]/.test(l));
57639
58041
  if (!lines.length)
@@ -57737,17 +58139,17 @@ function defaultInteractiveRunner(cmd) {
57737
58139
  function readJson2(p) {
57738
58140
  let data;
57739
58141
  try {
57740
- data = JSON.parse(fs52.readFileSync(p, "utf8"));
58142
+ data = JSON.parse(fs53.readFileSync(p, "utf8"));
57741
58143
  } catch {
57742
58144
  return null;
57743
58145
  }
57744
58146
  return typeof data === "object" && data !== null && !Array.isArray(data) ? data : null;
57745
58147
  }
57746
58148
  function knownMarketplaces(claudePluginsDir) {
57747
- return readJson2(path60.join(claudePluginsDir, "known_marketplaces.json")) || {};
58149
+ return readJson2(path61.join(claudePluginsDir, "known_marketplaces.json")) || {};
57748
58150
  }
57749
58151
  function installedVersion(claudePluginsDir, spec) {
57750
- const registry = readJson2(path60.join(claudePluginsDir, "installed_plugins.json")) || {};
58152
+ const registry = readJson2(path61.join(claudePluginsDir, "installed_plugins.json")) || {};
57751
58153
  const plugins = registry["plugins"];
57752
58154
  if (typeof plugins !== "object" || plugins === null || Array.isArray(plugins)) {
57753
58155
  return null;
@@ -57840,9 +58242,9 @@ function syncOptionalPlugin(spec, claudePluginsDir, runner, checkOnly, install)
57840
58242
  }));
57841
58243
  }
57842
58244
  function syncRepo(reposRoot, spec, runner, checkOnly) {
57843
- const dest = path60.join(reposRoot, spec.name);
58245
+ const dest = path61.join(reposRoot, spec.name);
57844
58246
  const label = `repo ${spec.name}`;
57845
- if (!fs52.existsSync(dest)) {
58247
+ if (!fs53.existsSync(dest)) {
57846
58248
  if (checkOnly) {
57847
58249
  return [
57848
58250
  {
@@ -57861,7 +58263,7 @@ function syncRepo(reposRoot, spec, runner, checkOnly) {
57861
58263
  const detail2 = notFound ? `${spec.url} is not reachable \u2014 it may not exist yet, or you may not be authenticated to GitHub (\`gh auth login\`, or check your git credential helper). ${spec.missingConsequence}` : `clone failed: ${briefOutput(out3)}`;
57862
58264
  return [{ label, ok: false, optional: true, detail: detail2 }];
57863
58265
  }
57864
- if (!fs52.existsSync(path60.join(dest, ".git"))) {
58266
+ if (!fs53.existsSync(path61.join(dest, ".git"))) {
57865
58267
  return [
57866
58268
  {
57867
58269
  label,
@@ -57894,8 +58296,8 @@ function syncPathDirs(checkOnly, dirs) {
57894
58296
  continue;
57895
58297
  const target = expandUser(value);
57896
58298
  const label = `paths.${key}`;
57897
- if (fs52.existsSync(target)) {
57898
- if (!fs52.statSync(target).isDirectory()) {
58299
+ if (fs53.existsSync(target)) {
58300
+ if (!fs53.statSync(target).isDirectory()) {
57899
58301
  steps.push({ label, ok: false, detail: `${target} exists but is not a directory` });
57900
58302
  } else {
57901
58303
  steps.push({ label, ok: true, detail: target });
@@ -57907,7 +58309,7 @@ function syncPathDirs(checkOnly, dirs) {
57907
58309
  continue;
57908
58310
  }
57909
58311
  try {
57910
- fs52.mkdirSync(target, { recursive: true });
58312
+ fs53.mkdirSync(target, { recursive: true });
57911
58313
  steps.push({ label, ok: true, detail: `created ${target}` });
57912
58314
  } catch (e) {
57913
58315
  steps.push({ label, ok: false, detail: `could not create ${target}: ${e}` });
@@ -57916,7 +58318,7 @@ function syncPathDirs(checkOnly, dirs) {
57916
58318
  return steps;
57917
58319
  }
57918
58320
  function syncRegistryConfigs(checkOnly, options = {}) {
57919
- const legacyDir = options.legacyDir === void 0 ? path60.join(sandboxRoot(), LEGACY_CONFIG_DIR) : options.legacyDir;
58321
+ const legacyDir = options.legacyDir === void 0 ? path61.join(sandboxRoot(), LEGACY_CONFIG_DIR) : options.legacyDir;
57920
58322
  const files = [
57921
58323
  [
57922
58324
  "services.local.yaml",
@@ -57935,12 +58337,12 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57935
58337
  const steps = [];
57936
58338
  for (const [name, dest, legacyName, content] of files) {
57937
58339
  const label2 = `config ${name}`;
57938
- if (fs52.existsSync(dest)) {
58340
+ if (fs53.existsSync(dest)) {
57939
58341
  steps.push({ label: label2, ok: true, detail: `present at ${dest}` });
57940
58342
  continue;
57941
58343
  }
57942
- const legacy = legacyDir && legacyName ? path60.join(legacyDir, legacyName) : null;
57943
- const migratable = legacy !== null && fs52.existsSync(legacy);
58344
+ const legacy = legacyDir && legacyName ? path61.join(legacyDir, legacyName) : null;
58345
+ const migratable = legacy !== null && fs53.existsSync(legacy);
57944
58346
  if (checkOnly) {
57945
58347
  steps.push({
57946
58348
  label: label2,
@@ -57951,8 +58353,8 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57951
58353
  }
57952
58354
  try {
57953
58355
  if (migratable) {
57954
- fs52.mkdirSync(path60.dirname(dest), { recursive: true });
57955
- fs52.copyFileSync(legacy, dest);
58356
+ fs53.mkdirSync(path61.dirname(dest), { recursive: true });
58357
+ fs53.copyFileSync(legacy, dest);
57956
58358
  steps.push({ label: label2, ok: true, detail: `migrated from ${legacy}` });
57957
58359
  } else {
57958
58360
  const result = seedFile(dest, content());
@@ -57964,7 +58366,7 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57964
58366
  }
57965
58367
  const label = "config secrets.env";
57966
58368
  const secrets = secretsPath();
57967
- if (fs52.existsSync(secrets)) {
58369
+ if (fs53.existsSync(secrets)) {
57968
58370
  steps.push({ label, ok: true, detail: `present at ${secrets}` });
57969
58371
  } else if (checkOnly) {
57970
58372
  steps.push({ label, ok: true, detail: `absent \u2014 would write a template to ${secrets}` });
@@ -57985,11 +58387,11 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57985
58387
  function resolveCertScript(startDir) {
57986
58388
  let dir = startDir ?? sandboxRoot();
57987
58389
  for (let i = 0; i < 5; i++) {
57988
- const candidate = path60.join(dir, "bin", "generate-certs.sh");
57989
- if (fs52.existsSync(candidate)) {
58390
+ const candidate = path61.join(dir, "bin", "generate-certs.sh");
58391
+ if (fs53.existsSync(candidate)) {
57990
58392
  return candidate;
57991
58393
  }
57992
- const parent = path60.dirname(dir);
58394
+ const parent = path61.dirname(dir);
57993
58395
  if (parent === dir)
57994
58396
  break;
57995
58397
  dir = parent;
@@ -58102,15 +58504,15 @@ ${JIRA_CONFIG_SNIPPET}`
58102
58504
  ];
58103
58505
  }
58104
58506
  function vscodeSettingsPath() {
58105
- return process.platform === "darwin" ? path60.join(os18.homedir(), "Library", "Application Support", "Code", "User", "settings.json") : path60.join(os18.homedir(), ".config", "Code", "User", "settings.json");
58507
+ return process.platform === "darwin" ? path61.join(os18.homedir(), "Library", "Application Support", "Code", "User", "settings.json") : path61.join(os18.homedir(), ".config", "Code", "User", "settings.json");
58106
58508
  }
58107
- function editorSchemaState(settingsPath = vscodeSettingsPath(), schemasDir = path60.join(sandboxRoot(), "infra", "schemas")) {
58509
+ function editorSchemaState(settingsPath = vscodeSettingsPath(), schemasDir = path61.join(sandboxRoot(), "infra", "schemas")) {
58108
58510
  const state = { present: false, set: {}, drop: [], text: "{}" };
58109
- if (!fs52.existsSync(path60.dirname(settingsPath)))
58511
+ if (!fs53.existsSync(path61.dirname(settingsPath)))
58110
58512
  return state;
58111
58513
  state.present = true;
58112
- if (fs52.existsSync(settingsPath))
58113
- state.text = fs52.readFileSync(settingsPath, "utf8");
58514
+ if (fs53.existsSync(settingsPath))
58515
+ state.text = fs53.readFileSync(settingsPath, "utf8");
58114
58516
  const errors = [];
58115
58517
  const data = parse7(state.text, errors, { allowTrailingComma: true });
58116
58518
  if (errors.length) {
@@ -58120,7 +58522,7 @@ function editorSchemaState(settingsPath = vscodeSettingsPath(), schemasDir = pat
58120
58522
  const mapped = data?.["yaml.schemas"] ?? {};
58121
58523
  const covers = (v, glob) => v === glob || Array.isArray(v) && v.includes(glob);
58122
58524
  for (const [file, glob] of Object.entries(EDITOR_SCHEMAS)) {
58123
- const want = path60.join(schemasDir, file);
58525
+ const want = path61.join(schemasDir, file);
58124
58526
  if (!covers(mapped[want], glob))
58125
58527
  state.set[want] = glob;
58126
58528
  for (const [key, v] of Object.entries(mapped)) {
@@ -58165,11 +58567,11 @@ function syncEditorSchemas(checkOnly, install, settingsPath = vscodeSettingsPath
58165
58567
  for (const [key, glob] of Object.entries(state.set)) {
58166
58568
  text = applyEdits(text, modify(text, ["yaml.schemas", key], glob, fmt));
58167
58569
  }
58168
- fs52.writeFileSync(settingsPath, text);
58570
+ fs53.writeFileSync(settingsPath, text);
58169
58571
  return [{ label, ok: true, optional: true, detail: `mapped in ${settingsPath}` }];
58170
58572
  }
58171
58573
  function claudeUserSettingsPath() {
58172
- return path60.join(os18.homedir(), ".claude", "settings.json");
58574
+ return path61.join(os18.homedir(), ".claude", "settings.json");
58173
58575
  }
58174
58576
  function claudeStandardsDirState(settingsPath = claudeUserSettingsPath(), dir = standardsDirPath()) {
58175
58577
  const state = {
@@ -58178,12 +58580,12 @@ function claudeStandardsDirState(settingsPath = claudeUserSettingsPath(), dir =
58178
58580
  granted: false,
58179
58581
  text: "{}"
58180
58582
  };
58181
- state.present = fs52.existsSync(path60.dirname(settingsPath));
58182
- state.dirMissing = !(fs52.existsSync(dir) && fs52.statSync(dir).isDirectory());
58583
+ state.present = fs53.existsSync(path61.dirname(settingsPath));
58584
+ state.dirMissing = !(fs53.existsSync(dir) && fs53.statSync(dir).isDirectory());
58183
58585
  if (!state.present || state.dirMissing)
58184
58586
  return state;
58185
- if (fs52.existsSync(settingsPath))
58186
- state.text = fs52.readFileSync(settingsPath, "utf8");
58587
+ if (fs53.existsSync(settingsPath))
58588
+ state.text = fs53.readFileSync(settingsPath, "utf8");
58187
58589
  const errors = [];
58188
58590
  const data = parse7(state.text, errors, { allowTrailingComma: true });
58189
58591
  if (errors.length) {
@@ -58195,8 +58597,8 @@ function claudeStandardsDirState(settingsPath = claudeUserSettingsPath(), dir =
58195
58597
  state.error = "permissions.additionalDirectories is not an array";
58196
58598
  return state;
58197
58599
  }
58198
- const want = path60.resolve(dir);
58199
- state.granted = (dirs ?? []).some((d) => path60.resolve(expandUser(String(d))) === want);
58600
+ const want = path61.resolve(dir);
58601
+ state.granted = (dirs ?? []).some((d) => path61.resolve(expandUser(String(d))) === want);
58200
58602
  return state;
58201
58603
  }
58202
58604
  function syncClaudeStandardsDir(checkOnly, install, settingsPath = claudeUserSettingsPath(), dir = standardsDirPath()) {
@@ -58226,18 +58628,18 @@ function syncClaudeStandardsDir(checkOnly, install, settingsPath = claudeUserSet
58226
58628
  ...fmt,
58227
58629
  isArrayInsertion: true
58228
58630
  }) : modify(state.text, ["permissions", "additionalDirectories"], [dir], fmt);
58229
- fs52.writeFileSync(settingsPath, applyEdits(state.text, edits));
58631
+ fs53.writeFileSync(settingsPath, applyEdits(state.text, edits));
58230
58632
  return [{ label, ok: true, optional: true, detail: `granted in ${settingsPath}` }];
58231
58633
  }
58232
58634
  function vscodeExtensionState(runner = defaultRunner, root = sandboxRoot()) {
58233
58635
  const [rc, out] = runner(["code", "--list-extensions", "--show-versions"]);
58234
58636
  const prefix = `${VSCODE_EXTENSION_ID}@`;
58235
58637
  const line = rc === 0 ? out.split("\n").find((l) => l.trim().toLowerCase().startsWith(prefix)) : void 0;
58236
- const vsix = path60.join(root, BUNDLED_VSIX_REL);
58638
+ const vsix = path61.join(root, BUNDLED_VSIX_REL);
58237
58639
  return {
58238
58640
  cli: rc === 0,
58239
58641
  installed: line ? line.trim().slice(prefix.length) : null,
58240
- bundled: fs52.existsSync(vsix) ? { path: vsix, version: readJson2(path60.join(root, "package.json"))?.version } : null
58642
+ bundled: fs53.existsSync(vsix) ? { path: vsix, version: readJson2(path61.join(root, "package.json"))?.version } : null
58241
58643
  };
58242
58644
  }
58243
58645
  function syncVscodeExtension(checkOnly, install, state = vscodeExtensionState(), runner = defaultRunner) {
@@ -58391,7 +58793,7 @@ function syncPlaywright(runner, checkOnly, interactive) {
58391
58793
  const dirs = [...out.matchAll(/Install location:\s*(\S.*)$/gm)].map((m) => m[1].trim());
58392
58794
  if (rc2 !== 0 || dirs.length === 0)
58393
58795
  return null;
58394
- return dirs.filter((d) => !fs52.existsSync(path60.join(d, "INSTALLATION_COMPLETE")));
58796
+ return dirs.filter((d) => !fs53.existsSync(path61.join(d, "INSTALLATION_COMPLETE")));
58395
58797
  };
58396
58798
  const before = missingDirs();
58397
58799
  if (before === null) {
@@ -58435,8 +58837,8 @@ function syncPlaywright(runner, checkOnly, interactive) {
58435
58837
  async function runSetup(options = {}) {
58436
58838
  const checkOnly = options.checkOnly ?? false;
58437
58839
  const reposRoot = options.reposRoot ?? getReposRoot();
58438
- const claudePluginsDir = options.claudePluginsDir ?? path60.join(os18.homedir(), ".claude", "plugins");
58439
- const repoPluginDir = options.repoPluginDir ?? path60.join(sandboxRoot(), "plugins", PLUGIN_NAME);
58840
+ const claudePluginsDir = options.claudePluginsDir ?? path61.join(os18.homedir(), ".claude", "plugins");
58841
+ const repoPluginDir = options.repoPluginDir ?? path61.join(sandboxRoot(), "plugins", PLUGIN_NAME);
58440
58842
  const runner = options.runner ?? defaultRunner;
58441
58843
  const jira2 = options.jira ?? loadJira();
58442
58844
  const steps = [];
@@ -58480,7 +58882,7 @@ async function runSetup(options = {}) {
58480
58882
  detail: "no workspace repo configured and `gh` could not name one \u2014 set overrides.workspace_repo (or run `gh auth login`, then `sandbox setup`). Workspaces will fall back to plain directories."
58481
58883
  });
58482
58884
  }
58483
- if (options.demo || fs52.existsSync(path60.join(reposRoot, DEMO_SERVICES_REPO))) {
58885
+ if (options.demo || fs53.existsSync(path61.join(reposRoot, DEMO_SERVICES_REPO))) {
58484
58886
  steps.push(...syncRepo(reposRoot, DEMO_SERVICES_SPEC, runner, checkOnly));
58485
58887
  }
58486
58888
  steps.push(...syncRegistry({ reposRoot, checkOnly }));
@@ -58554,7 +58956,7 @@ var init_machine_setup = __esm({
58554
58956
  url: DEMO_SERVICES_REPO_URL,
58555
58957
  missingConsequence: "The demo backend/frontend services will not start until it is cloned there."
58556
58958
  };
58557
- LEGACY_CONFIG_DIR = path60.join("infra", "config");
58959
+ LEGACY_CONFIG_DIR = path61.join("infra", "config");
58558
58960
  JIRA_TOKEN_URL = "https://id.atlassian.com/manage-profile/security/api-tokens";
58559
58961
  JIRA_REQUIRED_KEYS = ["base_url", "email", "api_token"];
58560
58962
  JIRA_CONFIG_SNIPPET = "jira:\n base_url: https://your-org.atlassian.net\n email: <your-atlassian-login-email>\n api_token: PASTE_TOKEN_HERE";
@@ -58563,13 +58965,13 @@ var init_machine_setup = __esm({
58563
58965
  "workspace.schema.json": "**/.sandbox/workspace.yml"
58564
58966
  };
58565
58967
  VSCODE_EXTENSION_ID = "jbbz.werkmeister-vscode";
58566
- BUNDLED_VSIX_REL = path60.join("dist", "sandbox-workspaces.vsix");
58968
+ BUNDLED_VSIX_REL = path61.join("dist", "sandbox-workspaces.vsix");
58567
58969
  }
58568
58970
  });
58569
58971
 
58570
58972
  // ../../packages/sandbox-core/dist/mcp_gen.js
58571
- import * as fs53 from "node:fs";
58572
- import * as path61 from "node:path";
58973
+ import * as fs54 from "node:fs";
58974
+ import * as path62 from "node:path";
58573
58975
  function resolveWorkspaceMcpServers(presentArchetypes2, archetypes) {
58574
58976
  const names = /* @__PURE__ */ new Set();
58575
58977
  for (const archName of Object.keys(presentArchetypes2)) {
@@ -58610,12 +59012,12 @@ function substituteVars(value, vars2) {
58610
59012
  return value;
58611
59013
  }
58612
59014
  function writeWorkspaceMcp(workspaceRoot, serverNames, definitions, vars2 = {}) {
58613
- const mcpPath = path61.join(workspaceRoot, ".mcp.json");
58614
- const isFile4 = fs53.existsSync(mcpPath) && fs53.statSync(mcpPath).isFile();
59015
+ const mcpPath = path62.join(workspaceRoot, ".mcp.json");
59016
+ const isFile4 = fs54.existsSync(mcpPath) && fs54.statSync(mcpPath).isFile();
58615
59017
  let config = {};
58616
59018
  if (isFile4) {
58617
59019
  try {
58618
- const existing2 = JSON.parse(fs53.readFileSync(mcpPath, "utf8"));
59020
+ const existing2 = JSON.parse(fs54.readFileSync(mcpPath, "utf8"));
58619
59021
  if (typeof existing2 === "object" && existing2 !== null && !Array.isArray(existing2)) {
58620
59022
  config = existing2;
58621
59023
  } else {
@@ -58646,7 +59048,7 @@ function writeWorkspaceMcp(workspaceRoot, serverNames, definitions, vars2 = {})
58646
59048
  if (legacy?.headers?.["X-Sandbox-Feature"] !== void 0)
58647
59049
  delete servers[LEGACY_SERVER];
58648
59050
  Object.assign(servers, wanted);
58649
- fs53.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
59051
+ fs54.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
58650
59052
  return mcpPath;
58651
59053
  }
58652
59054
  var LEGACY_SERVER;
@@ -58659,9 +59061,9 @@ var init_mcp_gen = __esm({
58659
59061
  });
58660
59062
 
58661
59063
  // ../../packages/sandbox-core/dist/workspace_init.js
58662
- import * as fs54 from "node:fs";
59064
+ import * as fs55 from "node:fs";
58663
59065
  import * as os19 from "node:os";
58664
- import * as path62 from "node:path";
59066
+ import * as path63 from "node:path";
58665
59067
  function fail2(message) {
58666
59068
  error(message);
58667
59069
  throw new WorkspaceInitError(message);
@@ -58684,7 +59086,7 @@ async function resolveIssueCopy(root, issueId, opts) {
58684
59086
  }
58685
59087
  function isDir4(p) {
58686
59088
  try {
58687
- return fs54.statSync(p).isDirectory();
59089
+ return fs55.statSync(p).isDirectory();
58688
59090
  } catch {
58689
59091
  return false;
58690
59092
  }
@@ -58701,26 +59103,26 @@ function splitMultiValue(value) {
58701
59103
  return value.split(",").map((s) => s.trim()).filter((s) => s);
58702
59104
  }
58703
59105
  function hasWorkspaceConfig(workspaceRoot, override) {
58704
- if (override && fs54.existsSync(expandUserPath(override))) {
59106
+ if (override && fs55.existsSync(expandUserPath(override))) {
58705
59107
  return true;
58706
59108
  }
58707
59109
  const envOverride = envVar("WORKSPACE_CONFIG");
58708
- if (envOverride && fs54.existsSync(expandUserPath(envOverride))) {
59110
+ if (envOverride && fs55.existsSync(expandUserPath(envOverride))) {
58709
59111
  return true;
58710
59112
  }
58711
59113
  const sandbox = dotDir(workspaceRoot);
58712
- return _WORKSPACE_CONFIG_NAMES.some((name) => fs54.existsSync(path62.join(sandbox, name)));
59114
+ return _WORKSPACE_CONFIG_NAMES.some((name) => fs55.existsSync(path63.join(sandbox, name)));
58713
59115
  }
58714
59116
  function writeWorkspaceConfig(workspaceRoot, resolved) {
58715
59117
  if (Object.keys(resolved).length === 0) {
58716
59118
  return;
58717
59119
  }
58718
59120
  const sandboxDir = dotDir(workspaceRoot);
58719
- const target = path62.join(sandboxDir, _WORKSPACE_CONFIG_NAMES[0]);
58720
- const legacies = _WORKSPACE_CONFIG_NAMES.slice(1).map((name) => path62.join(sandboxDir, name));
59121
+ const target = path63.join(sandboxDir, _WORKSPACE_CONFIG_NAMES[0]);
59122
+ const legacies = _WORKSPACE_CONFIG_NAMES.slice(1).map((name) => path63.join(sandboxDir, name));
58721
59123
  let existing2 = {};
58722
59124
  for (const source of [target, ...legacies]) {
58723
- if (fs54.existsSync(source) && fs54.statSync(source).isFile()) {
59125
+ if (fs55.existsSync(source) && fs55.statSync(source).isFile()) {
58724
59126
  try {
58725
59127
  const data = loadWorkspaceConfig(source);
58726
59128
  if (typeof data === "object" && data !== null && !Array.isArray(data)) {
@@ -58742,21 +59144,23 @@ function writeWorkspaceConfig(workspaceRoot, resolved) {
58742
59144
  }
58743
59145
  if (field === "autonomous" || field === "auto_stop")
58744
59146
  value = value === "true";
59147
+ if (field === "auto_stop" && value === false && !(field in existing2))
59148
+ continue;
58745
59149
  merged[field] = value;
58746
59150
  }
58747
59151
  }
58748
59152
  const body = YAML8.stringify(merged, { sortMapEntries: true, blockQuote: "literal" });
58749
59153
  const serialized = _WORKSPACE_CONFIG_HEADER + body;
58750
- if (fs54.existsSync(target) && fs54.readFileSync(target, "utf8") === serialized) {
59154
+ if (fs55.existsSync(target) && fs55.readFileSync(target, "utf8") === serialized) {
58751
59155
  return;
58752
59156
  }
58753
- fs54.mkdirSync(sandboxDir, { recursive: true });
58754
- fs54.writeFileSync(target, serialized);
59157
+ fs55.mkdirSync(sandboxDir, { recursive: true });
59158
+ fs55.writeFileSync(target, serialized);
58755
59159
  success(`Persisted inputs to ${target}`);
58756
59160
  for (const legacy of legacies) {
58757
- if (fs54.existsSync(legacy)) {
58758
- fs54.unlinkSync(legacy);
58759
- info(`Removed legacy ${path62.basename(legacy)} (superseded by workspace.yml)`);
59161
+ if (fs55.existsSync(legacy)) {
59162
+ fs55.unlinkSync(legacy);
59163
+ info(`Removed legacy ${path63.basename(legacy)} (superseded by workspace.yml)`);
58760
59164
  }
58761
59165
  }
58762
59166
  }
@@ -58774,16 +59178,16 @@ function resolveReposRoot() {
58774
59178
  function resolveWorkspacesRoot() {
58775
59179
  const configured = loadPaths()["workspaces_root"];
58776
59180
  if (configured) {
58777
- return path62.resolve(expandUser(configured));
59181
+ return path63.resolve(expandUser(configured));
58778
59182
  }
58779
- return path62.resolve(path62.join(os19.homedir(), "workspace"));
59183
+ return path63.resolve(path63.join(os19.homedir(), "workspace"));
58780
59184
  }
58781
59185
  function createWorkspaceRoot(workspaceDir, branch, force) {
58782
59186
  const reposRoot = resolveReposRoot();
58783
- const rootRepoSrc = path62.join(reposRoot, WORKSPACE_ROOT_REPO);
59187
+ const rootRepoSrc = path63.join(reposRoot, WORKSPACE_ROOT_REPO);
58784
59188
  info(`Workspace: ${workspaceDir}`);
58785
59189
  info(`Branch: ${branch}`);
58786
- if (fs54.existsSync(path62.join(rootRepoSrc, ".git"))) {
59190
+ if (fs55.existsSync(path63.join(rootRepoSrc, ".git"))) {
58787
59191
  try {
58788
59192
  createWorktree(rootRepoSrc, workspaceDir, branch, { force });
58789
59193
  } catch (exc) {
@@ -58792,11 +59196,11 @@ function createWorkspaceRoot(workspaceDir, branch, force) {
58792
59196
  success(`Created workspace root (worktree of ${WORKSPACE_ROOT_REPO}) at ${workspaceDir}`);
58793
59197
  } else {
58794
59198
  warn(`No '${WORKSPACE_ROOT_REPO}' repo at ${rootRepoSrc} \u2014 workspace root will be a plain directory (no git history for notes/config).`);
58795
- fs54.mkdirSync(workspaceDir, { recursive: true });
59199
+ fs55.mkdirSync(workspaceDir, { recursive: true });
58796
59200
  }
58797
59201
  }
58798
59202
  function commitWorkspaceState(workspaceDir, workspaceName) {
58799
- if (!fs54.existsSync(path62.join(workspaceDir, ".git"))) {
59203
+ if (!fs55.existsSync(path63.join(workspaceDir, ".git"))) {
58800
59204
  return;
58801
59205
  }
58802
59206
  try {
@@ -58836,9 +59240,9 @@ function candidateWorkspaceRoots() {
58836
59240
  const roots = [];
58837
59241
  const configured = loadPaths()["workspaces_root"];
58838
59242
  if (configured) {
58839
- roots.push(path62.resolve(expandUserPath(configured)));
59243
+ roots.push(path63.resolve(expandUserPath(configured)));
58840
59244
  }
58841
- roots.push(path62.join(os19.homedir(), "workspace"));
59245
+ roots.push(path63.join(os19.homedir(), "workspace"));
58842
59246
  const seen = /* @__PURE__ */ new Set();
58843
59247
  const unique = [];
58844
59248
  for (const r of roots) {
@@ -58850,20 +59254,20 @@ function candidateWorkspaceRoots() {
58850
59254
  return unique;
58851
59255
  }
58852
59256
  function findWorkspaceRoot2() {
58853
- const cwd = path62.resolve(process.cwd());
59257
+ const cwd = path63.resolve(process.cwd());
58854
59258
  for (const root of candidateWorkspaceRoots()) {
58855
- const rel = path62.relative(root, cwd);
58856
- if (rel === ".." || rel.startsWith(".." + path62.sep) || path62.isAbsolute(rel)) {
59259
+ const rel = path63.relative(root, cwd);
59260
+ if (rel === ".." || rel.startsWith(".." + path63.sep) || path63.isAbsolute(rel)) {
58857
59261
  continue;
58858
59262
  }
58859
59263
  if (rel === "") {
58860
59264
  continue;
58861
59265
  }
58862
- return path62.join(root, rel.split(path62.sep)[0]);
59266
+ return path63.join(root, rel.split(path63.sep)[0]);
58863
59267
  }
58864
59268
  for (const directory of [cwd, ...ancestors(cwd)]) {
58865
- if (fs54.existsSync(path62.join(directory, ".git"))) {
58866
- return path62.dirname(directory);
59269
+ if (fs55.existsSync(path63.join(directory, ".git"))) {
59270
+ return path63.dirname(directory);
58867
59271
  }
58868
59272
  }
58869
59273
  const rootsDisplay = candidateWorkspaceRoots().join(", ");
@@ -58874,7 +59278,7 @@ function ancestors(p) {
58874
59278
  const result = [];
58875
59279
  let cur = p;
58876
59280
  while (true) {
58877
- const parent = path62.dirname(cur);
59281
+ const parent = path63.dirname(cur);
58878
59282
  if (parent === cur) {
58879
59283
  break;
58880
59284
  }
@@ -58893,16 +59297,16 @@ function getBranch(p) {
58893
59297
  return result2.stdout.trim() || "unknown";
58894
59298
  }
58895
59299
  function discoverRepos(workspaceRoot) {
58896
- return repoDirs(workspaceRoot).map((d) => [path62.basename(d), getBranch(d)]);
59300
+ return repoDirs(workspaceRoot).map((d) => [path63.basename(d), getBranch(d)]);
58897
59301
  }
58898
59302
  function workspaceBranch(workspaceRoot) {
58899
- if (fs54.existsSync(path62.join(workspaceRoot, ".git"))) {
59303
+ if (fs55.existsSync(path63.join(workspaceRoot, ".git"))) {
58900
59304
  const branch = getBranch(workspaceRoot);
58901
59305
  if (branch && branch !== "unknown") {
58902
59306
  return branch;
58903
59307
  }
58904
59308
  }
58905
- return `${getuser()}/${path62.basename(workspaceRoot)}`;
59309
+ return `${getuser()}/${path63.basename(workspaceRoot)}`;
58906
59310
  }
58907
59311
  function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58908
59312
  const reposRoot = getReposRoot();
@@ -58911,12 +59315,12 @@ function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58911
59315
  const present = new Set(discoverRepos(workspaceRoot).map(([name]) => name));
58912
59316
  const toCreate = desired.filter((r) => !present.has(r));
58913
59317
  const toRemove = [...present].filter((r) => !desiredSet.has(r)).sort();
58914
- const missingSrc = toCreate.filter((r) => !isDir4(path62.join(reposRoot, r)));
59318
+ const missingSrc = toCreate.filter((r) => !isDir4(path63.join(reposRoot, r)));
58915
59319
  if (missingSrc.length) {
58916
59320
  fail2(`Source repo(s) not found under ${reposRoot}: ${missingSrc.join(", ")}. Clone them there (or fix \`repos\` in .sandbox/workspace.yml) and rerun.`);
58917
59321
  }
58918
59322
  if (toRemove.length && !force) {
58919
- const dirty = toRemove.filter((r) => worktreeIsDirty(path62.join(workspaceRoot, r)));
59323
+ const dirty = toRemove.filter((r) => worktreeIsDirty(path63.join(workspaceRoot, r)));
58920
59324
  if (dirty.length) {
58921
59325
  fail2(`Refusing to remove worktree(s) with uncommitted changes: ${dirty.join(", ")}. Commit or stash them, or rerun with --force to discard them.`);
58922
59326
  }
@@ -58926,7 +59330,7 @@ function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58926
59330
  }
58927
59331
  const branch = workspaceBranch(workspaceRoot);
58928
59332
  for (const repo of toRemove) {
58929
- const [ok, message] = removeWorktree(path62.join(reposRoot, repo), path62.join(workspaceRoot, repo), branch);
59333
+ const [ok, message] = removeWorktree(path63.join(reposRoot, repo), path63.join(workspaceRoot, repo), branch);
58930
59334
  if (ok) {
58931
59335
  success(`Removed worktree '${repo}' (no longer in workspace.yml).`);
58932
59336
  if (message) {
@@ -58939,8 +59343,8 @@ function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58939
59343
  const created = [];
58940
59344
  try {
58941
59345
  for (const repo of toCreate) {
58942
- const src = path62.join(reposRoot, repo);
58943
- const dest = path62.join(workspaceRoot, repo);
59346
+ const src = path63.join(reposRoot, repo);
59347
+ const dest = path63.join(workspaceRoot, repo);
58944
59348
  createWorktree(src, dest, branch, { force });
58945
59349
  created.push([dest, src]);
58946
59350
  }
@@ -58968,10 +59372,10 @@ function resolveTask(argsTask, workspaceName, readStdinTask2) {
58968
59372
  return [slugToTask(workspaceName), {}];
58969
59373
  }
58970
59374
  function warnIfExisting(claudeMdPath) {
58971
- if (!fs54.existsSync(claudeMdPath)) {
59375
+ if (!fs55.existsSync(claudeMdPath)) {
58972
59376
  return;
58973
59377
  }
58974
- const existing2 = fs54.readFileSync(claudeMdPath, "utf8");
59378
+ const existing2 = fs55.readFileSync(claudeMdPath, "utf8");
58975
59379
  if (existing2.includes("## Layout")) {
58976
59380
  warn("CLAUDE.md already initialised \u2014 overwriting with fresh content.");
58977
59381
  } else {
@@ -58994,7 +59398,7 @@ function logMeta(task, meta) {
58994
59398
  }
58995
59399
  async function initWorkspace(args, workspaceRootOverride, opts = {}) {
58996
59400
  const workspaceRoot = workspaceRootOverride ?? findWorkspaceRoot2();
58997
- const workspaceName = path62.basename(workspaceRoot);
59401
+ const workspaceName = path63.basename(workspaceRoot);
58998
59402
  info(`Workspace root: ${workspaceRoot}`);
58999
59403
  const inputs = resolveInputs(args, workspaceRoot);
59000
59404
  const strategy = strategyById(parseStrategyId(inputs["context_strategy"]));
@@ -59023,7 +59427,7 @@ async function initWorkspace(args, workspaceRootOverride, opts = {}) {
59023
59427
  }
59024
59428
  reconcileWorktrees(workspaceRoot, desiredRepos, Boolean(args["force"]));
59025
59429
  }
59026
- const claudeMdPath = path62.join(workspaceRoot, "CLAUDE.md");
59430
+ const claudeMdPath = path63.join(workspaceRoot, "CLAUDE.md");
59027
59431
  warnIfExisting(claudeMdPath);
59028
59432
  info("Scanning for git repos...");
59029
59433
  const repos = discoverRepos(workspaceRoot);
@@ -59080,7 +59484,7 @@ async function initWorkspace(args, workspaceRootOverride, opts = {}) {
59080
59484
  for (const warning of pluginDriftWarnings()) {
59081
59485
  warn(warning);
59082
59486
  }
59083
- const envPath = path62.join(workspaceRoot, ".env");
59487
+ const envPath = path63.join(workspaceRoot, ".env");
59084
59488
  seedWorkspaceMetadataEnv(envPath, meta);
59085
59489
  success(`Wrote ${envPath}`);
59086
59490
  const feature2 = (meta["FEATURE"] ?? "").trim();
@@ -59139,8 +59543,8 @@ async function provisionWorkspace(resolved, opts = {}) {
59139
59543
  fail2(`Workspace name '${workspaceName}' is not a valid slug (expected lowercase letters, digits, and single hyphens).`);
59140
59544
  }
59141
59545
  const branch = buildBranchName(os19.userInfo().username, workspaceName);
59142
- const workspaceDir = path62.join(resolveWorkspacesRoot(), workspaceName);
59143
- if (fs54.existsSync(workspaceDir)) {
59546
+ const workspaceDir = path63.join(resolveWorkspacesRoot(), workspaceName);
59547
+ if (fs55.existsSync(workspaceDir)) {
59144
59548
  info(`Workspace already exists: ${workspaceDir} \u2014 re-running init-workspace.`);
59145
59549
  } else {
59146
59550
  const repos = await selectRepos(resolved.repos, resolved.services);
@@ -59297,21 +59701,21 @@ __export(sandbox_exports, {
59297
59701
  import { execFile as execFile2, spawn as spawn8 } from "node:child_process";
59298
59702
  import { EventEmitter } from "node:events";
59299
59703
  import { createReadStream } from "node:fs";
59300
- import fs55 from "node:fs/promises";
59704
+ import fs56 from "node:fs/promises";
59301
59705
  import os20 from "node:os";
59302
- import path63 from "node:path";
59706
+ import path64 from "node:path";
59303
59707
  import readline5 from "node:readline";
59304
59708
  import { promisify } from "node:util";
59305
59709
  async function isDir5(p) {
59306
59710
  try {
59307
- return (await fs55.stat(p)).isDirectory();
59711
+ return (await fs56.stat(p)).isDirectory();
59308
59712
  } catch {
59309
59713
  return false;
59310
59714
  }
59311
59715
  }
59312
59716
  async function isFile3(p) {
59313
59717
  try {
59314
- return (await fs55.stat(p)).isFile();
59718
+ return (await fs56.stat(p)).isFile();
59315
59719
  } catch {
59316
59720
  return false;
59317
59721
  }
@@ -59322,23 +59726,23 @@ async function resolveSandboxBin(opts = {}) {
59322
59726
  return explicit;
59323
59727
  const root = envVar("ROOT");
59324
59728
  if (root) {
59325
- const cand = path63.join(root, "sandbox");
59729
+ const cand = path64.join(root, "sandbox");
59326
59730
  if (await isExecutable(cand))
59327
59731
  return cand;
59328
59732
  }
59329
59733
  let dir = opts.from ?? import.meta.dirname;
59330
59734
  for (let i = 0; i < 8; i++) {
59331
- const cand = path63.join(dir, "sandbox");
59332
- const hasCli = await isDir5(path63.join(dir, "apps", "cli"));
59735
+ const cand = path64.join(dir, "sandbox");
59736
+ const hasCli = await isDir5(path64.join(dir, "apps", "cli"));
59333
59737
  if (await isExecutable(cand) && hasCli) {
59334
59738
  return cand;
59335
59739
  }
59336
- const parent = path63.dirname(dir);
59740
+ const parent = path64.dirname(dir);
59337
59741
  if (parent === dir)
59338
59742
  break;
59339
59743
  dir = parent;
59340
59744
  }
59341
- const local = path63.join(os20.homedir(), ".local", "bin", "sandbox");
59745
+ const local = path64.join(os20.homedir(), ".local", "bin", "sandbox");
59342
59746
  if (await isExecutable(local))
59343
59747
  return local;
59344
59748
  throw new WorkspaceError("Could not locate the `sandbox` CLI. Set SANDBOX_ROOT to your sandbox checkout (or SANDBOX_BIN to the wrapper itself), or ensure the repo with its ./sandbox wrapper is reachable.");
@@ -59435,7 +59839,7 @@ async function readServicesRegistry() {
59435
59839
  if (!p)
59436
59840
  return null;
59437
59841
  try {
59438
- return await fs55.readFile(p, "utf-8");
59842
+ return await fs56.readFile(p, "utf-8");
59439
59843
  } catch {
59440
59844
  return null;
59441
59845
  }
@@ -59592,7 +59996,7 @@ function pickValidServices(raw2, known) {
59592
59996
  }
59593
59997
  async function readWorkspaceConfig(workspaceDir) {
59594
59998
  try {
59595
- const text = await fs55.readFile(path63.join(dotDir(workspaceDir), "workspace.yml"), "utf-8");
59999
+ const text = await fs56.readFile(path64.join(dotDir(workspaceDir), "workspace.yml"), "utf-8");
59596
60000
  const data = (0, import_yaml15.parse)(text);
59597
60001
  const issueId = issueIdOf(data) || null;
59598
60002
  const labels = Array.isArray(data?.labels) ? data.labels.filter((l) => typeof l === "string") : [];
@@ -59606,19 +60010,19 @@ async function listWorkspaces() {
59606
60010
  const root = workspacesRoot();
59607
60011
  let entries;
59608
60012
  try {
59609
- entries = await fs55.readdir(root, { withFileTypes: true });
60013
+ entries = await fs56.readdir(root, { withFileTypes: true });
59610
60014
  } catch (e) {
59611
60015
  throw new WorkspaceError(`Could not read workspaces root ${root}: ${e.message}`);
59612
60016
  }
59613
60017
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
59614
60018
  const workspaces = await Promise.all(names.map(async (name) => {
59615
- const p = path63.join(root, name);
60019
+ const p = path64.join(root, name);
59616
60020
  const { issueId, labels, model } = await readWorkspaceConfig(p);
59617
60021
  return {
59618
60022
  name,
59619
60023
  path: p,
59620
60024
  // vscode://file + an absolute path (already leading-slash) → single slash.
59621
- vscodeUri: `vscode://file${p.split(path63.sep).map(encodeURIComponent).join("/")}`,
60025
+ vscodeUri: `vscode://file${p.split(path64.sep).map(encodeURIComponent).join("/")}`,
59622
60026
  issueId,
59623
60027
  issueUrl: issueId ? issueTracker().url(issueId) : null,
59624
60028
  labels,
@@ -59795,7 +60199,7 @@ async function resolveWorkspaceDir(name) {
59795
60199
  if (!clean || clean.includes("/") || clean.includes("\\") || clean.includes("..")) {
59796
60200
  throw new WorkspaceError(`Invalid workspace name: ${JSON.stringify(name)}`);
59797
60201
  }
59798
- const target = path63.join(workspacesRoot(), clean);
60202
+ const target = path64.join(workspacesRoot(), clean);
59799
60203
  if (!await isDir5(target)) {
59800
60204
  throw new WorkspaceError(`No such workspace: ${clean}`);
59801
60205
  }
@@ -59806,7 +60210,7 @@ function reconcileDirForKey(name) {
59806
60210
  if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..")) {
59807
60211
  throw new WorkspaceError(`Invalid reconcile session: ${JSON.stringify(name)}`);
59808
60212
  }
59809
- return path63.join(reconcileRoot(), slug);
60213
+ return path64.join(reconcileRoot(), slug);
59810
60214
  }
59811
60215
  async function resolveSessionDir(name) {
59812
60216
  if ((name ?? "").startsWith(RECONCILE_PREFIX)) {
@@ -59826,7 +60230,7 @@ async function openWorkspace(name) {
59826
60230
  }
59827
60231
  async function readEnvValue(dir, key) {
59828
60232
  try {
59829
- const text = await fs55.readFile(path63.join(dir, ".env"), "utf-8");
60233
+ const text = await fs56.readFile(path64.join(dir, ".env"), "utf-8");
59830
60234
  for (const line of text.split(/\r?\n/)) {
59831
60235
  const eq = line.indexOf("=");
59832
60236
  if (eq > 0 && line.slice(0, eq).trim() === key)
@@ -59838,7 +60242,7 @@ async function readEnvValue(dir, key) {
59838
60242
  }
59839
60243
  async function readWorkspaceFeature(dir) {
59840
60244
  try {
59841
- const text = await fs55.readFile(path63.join(dotDir(dir), "workspace.yml"), "utf-8");
60245
+ const text = await fs56.readFile(path64.join(dotDir(dir), "workspace.yml"), "utf-8");
59842
60246
  const m = /^feature: *["']?([^"'\s#]+)/m.exec(text);
59843
60247
  return m ? m[1] : null;
59844
60248
  } catch {
@@ -59846,7 +60250,7 @@ async function readWorkspaceFeature(dir) {
59846
60250
  }
59847
60251
  }
59848
60252
  async function readFileTail(file, maxBytes = 131072) {
59849
- const fh = await fs55.open(file, "r");
60253
+ const fh = await fs56.open(file, "r");
59850
60254
  try {
59851
60255
  const { size } = await fh.stat();
59852
60256
  const start2 = Math.max(0, size - maxBytes);
@@ -59942,7 +60346,7 @@ async function loadPortsDoc() {
59942
60346
  if (!file)
59943
60347
  return null;
59944
60348
  try {
59945
- return (0, import_yaml15.parse)(await fs55.readFile(file, "utf8")) ?? null;
60349
+ return (0, import_yaml15.parse)(await fs56.readFile(file, "utf8")) ?? null;
59946
60350
  } catch {
59947
60351
  return null;
59948
60352
  }
@@ -59953,12 +60357,12 @@ function slotPortLabel(slot, ports) {
59953
60357
  }
59954
60358
  async function getWorkspaceStatuses() {
59955
60359
  const root = workspacesRoot();
59956
- const entries = await fs55.readdir(root, { withFileTypes: true }).catch(() => []);
60360
+ const entries = await fs56.readdir(root, { withFileTypes: true }).catch(() => []);
59957
60361
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
59958
60362
  const projects = await dockerComposeProjects();
59959
60363
  const portsDoc = await loadPortsDoc();
59960
60364
  const statuses = await Promise.all(names.map(async (name) => {
59961
- const dir = path63.join(root, name);
60365
+ const dir = path64.join(root, name);
59962
60366
  let project = await readEnvValue(dir, "COMPOSE_PROJECT_NAME");
59963
60367
  if (!project) {
59964
60368
  const feature2 = await readWorkspaceFeature(dir);
@@ -60037,19 +60441,19 @@ async function getIntercepts() {
60037
60441
  }
60038
60442
  async function hasGit(dir) {
60039
60443
  try {
60040
- await fs55.access(path63.join(dir, ".git"));
60444
+ await fs56.access(path64.join(dir, ".git"));
60041
60445
  return true;
60042
60446
  } catch {
60043
60447
  return false;
60044
60448
  }
60045
60449
  }
60046
60450
  async function listRepoWorktrees(workspaceDir) {
60047
- const entries = await fs55.readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
60451
+ const entries = await fs56.readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
60048
60452
  const out = [];
60049
60453
  for (const e of entries) {
60050
60454
  if (!e.isDirectory() || e.name.startsWith("."))
60051
60455
  continue;
60052
- const dir = path63.join(workspaceDir, e.name);
60456
+ const dir = path64.join(workspaceDir, e.name);
60053
60457
  if (await hasGit(dir))
60054
60458
  out.push({ repo: e.name, dir });
60055
60459
  }
@@ -60091,10 +60495,10 @@ async function ghPrForWorktree(repoDir) {
60091
60495
  }
60092
60496
  async function getWorkspacePRs() {
60093
60497
  const root = workspacesRoot();
60094
- const entries = await fs55.readdir(root, { withFileTypes: true }).catch(() => []);
60498
+ const entries = await fs56.readdir(root, { withFileTypes: true }).catch(() => []);
60095
60499
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
60096
60500
  return Promise.all(names.map(async (name) => {
60097
- const repos = await listRepoWorktrees(path63.join(root, name));
60501
+ const repos = await listRepoWorktrees(path64.join(root, name));
60098
60502
  const repoPrs = await Promise.all(repos.map(async ({ repo, dir }) => ({ repo, ...await ghPrForWorktree(dir) })));
60099
60503
  return { name, repos: repoPrs };
60100
60504
  }));
@@ -60302,7 +60706,7 @@ async function worktreeSources(worktrees) {
60302
60706
  if (res && res.code === 0) {
60303
60707
  const common2 = stripAnsi(res.stdout).trim();
60304
60708
  if (common2)
60305
- srcs.add(path63.dirname(common2));
60709
+ srcs.add(path64.dirname(common2));
60306
60710
  }
60307
60711
  }
60308
60712
  return [...srcs];
@@ -60311,8 +60715,8 @@ function gitErrorLine(text) {
60311
60715
  const lines = stripAnsi(text).trim().split("\n");
60312
60716
  return (lines.find((l) => /^\s*!\s|^(error|fatal):/.test(l)) ?? lines[0]).trim().replace(/\s+/g, " ");
60313
60717
  }
60314
- async function commitAndPush(dir, message = `chore: handoff ${path63.basename(dir)} before workspace teardown`, paths = []) {
60315
- const label = path63.basename(dir);
60718
+ async function commitAndPush(dir, message = `chore: handoff ${path64.basename(dir)} before workspace teardown`, paths = []) {
60719
+ const label = path64.basename(dir);
60316
60720
  const br = await runCapture("git", ["-C", dir, "rev-parse", "--abbrev-ref", "HEAD"]).catch(() => null);
60317
60721
  const branch = br && br.code === 0 ? stripAnsi(br.stdout).trim() : null;
60318
60722
  if (!branch || branch === "HEAD")
@@ -60394,7 +60798,7 @@ ${parts.join("\n")}`);
60394
60798
  parts.push(detail);
60395
60799
  }
60396
60800
  }
60397
- await fs55.rm(target, { recursive: true, force: true });
60801
+ await fs56.rm(target, { recursive: true, force: true });
60398
60802
  for (const src of sources) {
60399
60803
  await runCapture("git", ["-C", src, "worktree", "prune"]).catch(() => {
60400
60804
  });
@@ -60559,17 +60963,17 @@ function encodeClaudeProjectDir(p) {
60559
60963
  return p.replace(/[^a-zA-Z0-9]/g, "-");
60560
60964
  }
60561
60965
  async function findClaudeProjectDir(workspacePath) {
60562
- const root = path63.join(os20.homedir(), ".claude", "projects");
60966
+ const root = path64.join(os20.homedir(), ".claude", "projects");
60563
60967
  const want = encodeClaudeProjectDir(workspacePath);
60564
60968
  let entries;
60565
60969
  try {
60566
- entries = await fs55.readdir(root, { withFileTypes: true });
60970
+ entries = await fs56.readdir(root, { withFileTypes: true });
60567
60971
  } catch {
60568
60972
  return null;
60569
60973
  }
60570
60974
  for (const e of entries) {
60571
60975
  if (e.isDirectory() && encodeClaudeProjectDir(e.name) === want) {
60572
- return path63.join(root, e.name);
60976
+ return path64.join(root, e.name);
60573
60977
  }
60574
60978
  }
60575
60979
  return null;
@@ -60577,7 +60981,7 @@ async function findClaudeProjectDir(workspacePath) {
60577
60981
  async function listSessionFiles(projectDir) {
60578
60982
  let names;
60579
60983
  try {
60580
- names = await fs55.readdir(projectDir);
60984
+ names = await fs56.readdir(projectDir);
60581
60985
  } catch {
60582
60986
  return [];
60583
60987
  }
@@ -60586,8 +60990,8 @@ async function listSessionFiles(projectDir) {
60586
60990
  if (!n.endsWith(".jsonl"))
60587
60991
  continue;
60588
60992
  try {
60589
- const st = await fs55.stat(path63.join(projectDir, n));
60590
- out.push({ file: path63.join(projectDir, n), mtime: st.mtime });
60993
+ const st = await fs56.stat(path64.join(projectDir, n));
60994
+ out.push({ file: path64.join(projectDir, n), mtime: st.mtime });
60591
60995
  } catch {
60592
60996
  }
60593
60997
  }
@@ -60597,7 +61001,7 @@ function pickSession(sessions, sessionId) {
60597
61001
  if (!sessions.length)
60598
61002
  return null;
60599
61003
  if (sessionId) {
60600
- const match = sessions.find((s) => path63.basename(s.file, ".jsonl") === sessionId);
61004
+ const match = sessions.find((s) => path64.basename(s.file, ".jsonl") === sessionId);
60601
61005
  if (match)
60602
61006
  return match;
60603
61007
  }
@@ -60640,7 +61044,7 @@ async function listSessions(name) {
60640
61044
  return [];
60641
61045
  const files = await listSessionFiles(projectDir);
60642
61046
  return Promise.all(files.map(async (f, i) => {
60643
- const sessionId = path63.basename(f.file, ".jsonl");
61047
+ const sessionId = path64.basename(f.file, ".jsonl");
60644
61048
  return {
60645
61049
  sessionId,
60646
61050
  title: await readSessionTitle(f.file, sessionId),
@@ -60802,7 +61206,7 @@ async function loadPriceTable() {
60802
61206
  if (!p)
60803
61207
  return DEFAULT_PRICES;
60804
61208
  try {
60805
- const doc = (0, import_yaml15.parse)(await fs55.readFile(p, "utf-8"));
61209
+ const doc = (0, import_yaml15.parse)(await fs56.readFile(p, "utf-8"));
60806
61210
  return doc?.models && Object.keys(doc.models).length ? doc.models : DEFAULT_PRICES;
60807
61211
  } catch {
60808
61212
  return DEFAULT_PRICES;
@@ -60827,7 +61231,7 @@ async function getWorkspaceUsage(name) {
60827
61231
  for (const f of files) {
60828
61232
  let text;
60829
61233
  try {
60830
- text = await fs55.readFile(f.file, "utf-8");
61234
+ text = await fs56.readFile(f.file, "utf-8");
60831
61235
  } catch {
60832
61236
  continue;
60833
61237
  }
@@ -60848,7 +61252,7 @@ async function getWorkspaceUsage(name) {
60848
61252
  if (usd != null)
60849
61253
  sessionUsd = (sessionUsd ?? 0) + usd;
60850
61254
  }
60851
- const sessionId = path63.basename(f.file, ".jsonl");
61255
+ const sessionId = path64.basename(f.file, ".jsonl");
60852
61256
  sessions.push({
60853
61257
  sessionId,
60854
61258
  title: await readSessionTitle(f.file, sessionId),
@@ -60893,7 +61297,7 @@ async function resolveSessionSource(name, sessionId) {
60893
61297
  workspacePath: target,
60894
61298
  projectDir,
60895
61299
  sessionFile: chosen.file,
60896
- sessionId: path63.basename(chosen.file, ".jsonl"),
61300
+ sessionId: path64.basename(chosen.file, ".jsonl"),
60897
61301
  sessionCount: sessions.length,
60898
61302
  updatedAt: chosen.mtime.toISOString()
60899
61303
  };
@@ -60914,10 +61318,10 @@ async function getClaudeTranscript(name, limit = 20, sessionId) {
60914
61318
  const chosen = pickSession(sessions, sessionId);
60915
61319
  if (!chosen)
60916
61320
  return empty;
60917
- const resolvedId = path63.basename(chosen.file, ".jsonl");
61321
+ const resolvedId = path64.basename(chosen.file, ".jsonl");
60918
61322
  let text;
60919
61323
  try {
60920
- text = await fs55.readFile(chosen.file, "utf-8");
61324
+ text = await fs56.readFile(chosen.file, "utf-8");
60921
61325
  } catch (e) {
60922
61326
  throw new WorkspaceError(`Could not read session file: ${e.message}`);
60923
61327
  }
@@ -61019,7 +61423,7 @@ async function getK8sForwardsHealth() {
61019
61423
  if (!file) {
61020
61424
  throw new WorkspaceError("Could not locate the service registry (~/.sandbox/services.yaml) \u2014 run `sandbox setup`.");
61021
61425
  }
61022
- const doc = (0, import_yaml15.parse)(await fs55.readFile(file, "utf-8")) ?? {};
61426
+ const doc = (0, import_yaml15.parse)(await fs56.readFile(file, "utf-8")) ?? {};
61023
61427
  const forwards = await Promise.all(forwardSpecs(doc.services, doc.infrastructure).map(async (d) => {
61024
61428
  const r = await checkForwardHealth(d.listen, d.health);
61025
61429
  return { name: d.name, port: d.listen, healthy: r.ok, detail: r.detail };
@@ -61111,16 +61515,16 @@ async function launchPrReconcile(url, issueRef) {
61111
61515
  }
61112
61516
  const { repo, number } = parseRepoAndNumber(url);
61113
61517
  const slug = `${repo}-${number}`;
61114
- const dir = path63.join(reconcileRoot(), slug);
61115
- await fs55.mkdir(dir, { recursive: true });
61518
+ const dir = path64.join(reconcileRoot(), slug);
61519
+ await fs56.mkdir(dir, { recursive: true });
61116
61520
  const [issue, pr, diffRes] = await Promise.all([
61117
61521
  fetchIssueCard(issueRef || url),
61118
61522
  ghPrView(url),
61119
61523
  runCapture("gh", ["pr", "diff", url]).catch(() => null)
61120
61524
  ]);
61121
61525
  const diff = diffRes && diffRes.code === 0 ? stripAnsi(diffRes.stdout) : "";
61122
- const ctxFile = path63.join(dir, "reconcile-context.md");
61123
- await fs55.writeFile(ctxFile, buildReconcileContext(issue, pr, diff), "utf-8");
61526
+ const ctxFile = path64.join(dir, "reconcile-context.md");
61527
+ await fs56.writeFile(ctxFile, buildReconcileContext(issue, pr, diff), "utf-8");
61124
61528
  await openTerminalRunning(`cd ${shellQuote(dir)} && claude -p --disallowedTools Bash Edit Write --append-system-prompt-file ${shellQuote(ctxFile)} ${shellQuote(RECONCILE_PROMPT)}`, false);
61125
61529
  return { sessionKey: `${RECONCILE_PREFIX}${slug}`, slug, issue };
61126
61530
  }
@@ -61174,7 +61578,7 @@ var init_sandbox = __esm({
61174
61578
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
61175
61579
  ].filter((p) => Boolean(p));
61176
61580
  DISTILLED = ["feature", "task", "description", "starting_prompt", "services"];
61177
- reconcileRoot = () => path63.join(dotDir(os20.homedir()), "reconcile");
61581
+ reconcileRoot = () => path64.join(dotDir(os20.homedir()), "reconcile");
61178
61582
  RECONCILE_PREFIX = "reconcile:";
61179
61583
  APPROVAL_LABEL = "ready-for-human-approval";
61180
61584
  FAILING_CONCLUSIONS = /* @__PURE__ */ new Set([
@@ -61464,8 +61868,8 @@ __export(detect_exports, {
61464
61868
  run: () => run10,
61465
61869
  runDetect: () => runDetect
61466
61870
  });
61467
- import * as fs56 from "node:fs";
61468
- import * as path64 from "node:path";
61871
+ import * as fs57 from "node:fs";
61872
+ import * as path65 from "node:path";
61469
61873
  function mark(fv) {
61470
61874
  return fv.confidence === "stated" ? "" : ` ${Colors.YELLOW}[${fv.confidence}]${Colors.RESET}`;
61471
61875
  }
@@ -61518,8 +61922,8 @@ function printPlan(plan) {
61518
61922
  async function runDetect(args, deps) {
61519
61923
  let root;
61520
61924
  if (args.repo) {
61521
- root = path64.resolve(deps.cwd, args.repo);
61522
- if (!fs56.statSync(root, { throwIfNoEntry: false })?.isDirectory()) {
61925
+ root = path65.resolve(deps.cwd, args.repo);
61926
+ if (!fs57.statSync(root, { throwIfNoEntry: false })?.isDirectory()) {
61523
61927
  warn(`${root} is not a directory \u2014 pass the repo path: sandbox detect <repo>`);
61524
61928
  return 2;
61525
61929
  }
@@ -61828,18 +62232,18 @@ var open_exports = {};
61828
62232
  __export(open_exports, {
61829
62233
  run: () => run15
61830
62234
  });
61831
- import * as fs57 from "node:fs";
61832
- import * as path65 from "node:path";
62235
+ import * as fs58 from "node:fs";
62236
+ import * as path66 from "node:path";
61833
62237
  function loadWorkspaceYaml(start2) {
61834
- let dir = path65.resolve(start2);
62238
+ let dir = path66.resolve(start2);
61835
62239
  for (; ; ) {
61836
62240
  for (const name of ["workspace.yml", "workspace.yaml"]) {
61837
- const p = path65.join(dotDir(dir), name);
61838
- if (fs57.existsSync(p) && fs57.statSync(p).isFile()) {
62241
+ const p = path66.join(dotDir(dir), name);
62242
+ if (fs58.existsSync(p) && fs58.statSync(p).isFile()) {
61839
62243
  return readWorkspaceYaml(dir);
61840
62244
  }
61841
62245
  }
61842
- const parent = path65.dirname(dir);
62246
+ const parent = path66.dirname(dir);
61843
62247
  if (parent === dir) return {};
61844
62248
  dir = parent;
61845
62249
  }
@@ -61969,8 +62373,8 @@ var init_clean = __esm({
61969
62373
  });
61970
62374
 
61971
62375
  // ../../packages/sandbox-core/dist/stats.js
61972
- import * as fs58 from "node:fs";
61973
- import * as path66 from "node:path";
62376
+ import * as fs59 from "node:fs";
62377
+ import * as path67 from "node:path";
61974
62378
  function sleep4(seconds, signal) {
61975
62379
  return new Promise((resolve13) => {
61976
62380
  if (signal?.aborted)
@@ -62068,12 +62472,12 @@ function pad2(n) {
62068
62472
  return String(n).padStart(2, "0");
62069
62473
  }
62070
62474
  function saveReport(stats, recommendations) {
62071
- const statsDir = path66.join(sandboxRoot(), "stats");
62072
- if (!fs58.existsSync(statsDir))
62073
- fs58.mkdirSync(statsDir);
62475
+ const statsDir = path67.join(sandboxRoot(), "stats");
62476
+ if (!fs59.existsSync(statsDir))
62477
+ fs59.mkdirSync(statsDir);
62074
62478
  const now = /* @__PURE__ */ new Date();
62075
62479
  const timestamp = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}-${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
62076
- const csvPath = path66.join(statsDir, `docker-stats-${timestamp}.csv`);
62480
+ const csvPath = path67.join(statsDir, `docker-stats-${timestamp}.csv`);
62077
62481
  const csvRows = [
62078
62482
  ["container", "avg_cpu_pct", "max_cpu_pct", "avg_mem_mib", "max_mem_mib", "samples"].join(",")
62079
62483
  ];
@@ -62088,8 +62492,8 @@ function saveReport(stats, recommendations) {
62088
62492
  String(s.samples)
62089
62493
  ].join(","));
62090
62494
  }
62091
- fs58.writeFileSync(csvPath, csvRows.join("\r\n") + "\r\n");
62092
- const summaryPath = path66.join(statsDir, `resource-summary-${timestamp}.txt`);
62495
+ fs59.writeFileSync(csvPath, csvRows.join("\r\n") + "\r\n");
62496
+ const summaryPath = path67.join(statsDir, `resource-summary-${timestamp}.txt`);
62093
62497
  const generated = `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())} ${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`;
62094
62498
  let out = "";
62095
62499
  out += "Docker Resource Usage Summary\n";
@@ -62107,7 +62511,7 @@ function saveReport(stats, recommendations) {
62107
62511
  out += ` ${r.name.padEnd(35)} cpus: ${r.recommended_cpus.padStart(5)} memory: ${r.recommended_memory.padStart(5)}
62108
62512
  `;
62109
62513
  }
62110
- fs58.writeFileSync(summaryPath, out);
62514
+ fs59.writeFileSync(summaryPath, out);
62111
62515
  return summaryPath;
62112
62516
  }
62113
62517
  async function runCollection(durationMinutes, intervalSeconds, save2, signal) {
@@ -62559,14 +62963,14 @@ var init_standards2 = __esm({
62559
62963
  });
62560
62964
 
62561
62965
  // ../../packages/sandbox-core/dist/pr_guard.js
62562
- import * as fs59 from "node:fs";
62563
- import * as path67 from "node:path";
62966
+ import * as fs60 from "node:fs";
62967
+ import * as path68 from "node:path";
62564
62968
  function findRecordRoot(start2) {
62565
- let dir = path67.resolve(start2);
62969
+ let dir = path68.resolve(start2);
62566
62970
  for (; ; ) {
62567
- if (fs59.existsSync(path67.join(dotDir(dir), "workspace.yml")))
62971
+ if (fs60.existsSync(path68.join(dotDir(dir), "workspace.yml")))
62568
62972
  return dir;
62569
- const parent = path67.dirname(dir);
62973
+ const parent = path68.dirname(dir);
62570
62974
  if (parent === dir)
62571
62975
  return null;
62572
62976
  dir = parent;
@@ -62574,11 +62978,11 @@ function findRecordRoot(start2) {
62574
62978
  }
62575
62979
  function prFileDirName(root) {
62576
62980
  try {
62577
- const names = fs59.readdirSync(path67.join(root, "stages")).sort();
62981
+ const names = fs60.readdirSync(path68.join(root, "stages")).sort();
62578
62982
  const dir = names.find((n) => /^\d+_propose$/.test(n)) ?? names.find((n) => /^\d+_ship$/.test(n));
62579
62983
  return dir ? `stages/${dir}/output/` : "the propose stage's output/";
62580
62984
  } catch {
62581
- return `${path67.basename(dotDir(root))}/notes/`;
62985
+ return `${path68.basename(dotDir(root))}/notes/`;
62582
62986
  }
62583
62987
  }
62584
62988
  function decide2(input2) {
@@ -62590,13 +62994,13 @@ function decide2(input2) {
62590
62994
  const opensPr = tool === "Bash" && GH_PR_CREATE.test(command) || MCP_CREATE_PR.test(tool);
62591
62995
  if (!opensPr)
62592
62996
  return null;
62593
- const rules = ["_shared/pr-rules.md", ...DOT_DIRS.map((d) => `${d}/pr-rules.md`)].find((p) => fs59.existsSync(path67.join(root, p))) ?? "docs/workspace-lifecycle.md in the sandbox repo";
62594
- return `Inside sandbox workspace \`${path67.basename(root)}\`, PRs open only through \`sandbox pr open\`: it runs the pre-PR gate and records the URL. Write the PR file in \`${prFileDirName(root)}\`, have the person set \`status: approved\`, then run \`sandbox pr open\`. Rules: \`${rules}\`.`;
62997
+ const rules = ["_shared/pr-rules.md", ...DOT_DIRS.map((d) => `${d}/pr-rules.md`)].find((p) => fs60.existsSync(path68.join(root, p))) ?? "docs/workspace-lifecycle.md in the sandbox repo";
62998
+ return `Inside sandbox workspace \`${path68.basename(root)}\`, PRs open only through \`sandbox pr open\`: it runs the pre-PR gate and records the URL. Write the PR file in \`${prFileDirName(root)}\`, have the person set \`status: approved\`, then run \`sandbox pr open\`. Rules: \`${rules}\`.`;
62595
62999
  }
62596
63000
  function main2() {
62597
63001
  let reason2 = null;
62598
63002
  try {
62599
- const raw2 = process.stdin.isTTY ? "" : fs59.readFileSync(0, "utf8");
63003
+ const raw2 = process.stdin.isTTY ? "" : fs60.readFileSync(0, "utf8");
62600
63004
  const parsed = raw2.trim() ? JSON.parse(raw2) : {};
62601
63005
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed))
62602
63006
  reason2 = decide2(parsed);
@@ -62661,13 +63065,13 @@ __export(init_workspace_exports, {
62661
63065
  readStdinTask: () => readStdinTask,
62662
63066
  run: () => run23
62663
63067
  });
62664
- import * as fs60 from "node:fs";
63068
+ import * as fs61 from "node:fs";
62665
63069
  function readStdinTask() {
62666
63070
  if (process.stdin.isTTY) {
62667
63071
  return "";
62668
63072
  }
62669
63073
  try {
62670
- return fs60.readFileSync(0, "utf8");
63074
+ return fs61.readFileSync(0, "utf8");
62671
63075
  } catch {
62672
63076
  return "";
62673
63077
  }
@@ -62700,8 +63104,8 @@ __export(workspace_exports, {
62700
63104
  browserFormCommand: () => browserFormCommand,
62701
63105
  run: () => run24
62702
63106
  });
62703
- import * as fs61 from "node:fs";
62704
- import * as path68 from "node:path";
63107
+ import * as fs62 from "node:fs";
63108
+ import * as path69 from "node:path";
62705
63109
  async function resolveWorkspaceInputs(args) {
62706
63110
  const base = process.cwd();
62707
63111
  let resolved = resolveInputs(args, base);
@@ -62737,12 +63141,12 @@ async function resolveWorkspaceInputs(args) {
62737
63141
  return { resolved, issue };
62738
63142
  }
62739
63143
  function which2(cmd) {
62740
- const dirs = (process.env.PATH || "").split(path68.delimiter);
63144
+ const dirs = (process.env.PATH || "").split(path69.delimiter);
62741
63145
  for (const dir of dirs) {
62742
63146
  if (!dir) continue;
62743
- const full = path68.join(dir, cmd);
63147
+ const full = path69.join(dir, cmd);
62744
63148
  try {
62745
- fs61.accessSync(full, fs61.constants.X_OK);
63149
+ fs62.accessSync(full, fs62.constants.X_OK);
62746
63150
  return full;
62747
63151
  } catch {
62748
63152
  }
@@ -62763,8 +63167,8 @@ function browserFormCommand(node, cliJs, args) {
62763
63167
  return cmd;
62764
63168
  }
62765
63169
  function runBrowserForm(args) {
62766
- const cliJs = path68.join(getMonorepoRoot(), "apps", "sandbox-dashboard", "dist", "form-cli.js");
62767
- if (!(fs61.existsSync(cliJs) && fs61.statSync(cliJs).isFile())) {
63170
+ const cliJs = path69.join(getMonorepoRoot(), "apps", "sandbox-dashboard", "dist", "form-cli.js");
63171
+ if (!(fs62.existsSync(cliJs) && fs62.statSync(cliJs).isFile())) {
62768
63172
  error(
62769
63173
  `Browser form not built: ${cliJs} is missing. Run \`npm --prefix apps/sandbox-dashboard run build\` (or \`sandbox dashboard\` once) first.`
62770
63174
  );
@@ -62887,7 +63291,7 @@ __export(issue_exports, {
62887
63291
  run: () => run26,
62888
63292
  updateFromArgs: () => updateFromArgs
62889
63293
  });
62890
- import * as fs62 from "node:fs";
63294
+ import * as fs63 from "node:fs";
62891
63295
  function updateFromArgs(args) {
62892
63296
  const status2 = args.status || void 0;
62893
63297
  const comment = args.comment || void 0;
@@ -62895,7 +63299,7 @@ function updateFromArgs(args) {
62895
63299
  return "Nothing to update: pass --status <name> and/or --comment <text>.";
62896
63300
  return { status: status2, comment };
62897
63301
  }
62898
- function newIssueFromArgs(args, read = (p) => fs62.readFileSync(p, "utf8")) {
63302
+ function newIssueFromArgs(args, read = (p) => fs63.readFileSync(p, "utf8")) {
62899
63303
  const project = (args.project ?? "").trim().toUpperCase();
62900
63304
  const type = args.type ?? "";
62901
63305
  const title = (args.title ?? "").trim();
@@ -62935,6 +63339,14 @@ async function run26(args) {
62935
63339
  }
62936
63340
  return;
62937
63341
  }
63342
+ if (args.action === "start") {
63343
+ const workspace = findWorkspaceRoot(process.cwd());
63344
+ if (workspace === null)
63345
+ fail3("Not inside a sandbox workspace (no .sandbox/ directory found above CWD).");
63346
+ const line = await startIssue(workspace) || "left as is (no in_progress status configured)";
63347
+ (line.includes("not moved") ? warn : success)(`Issue: ${line}`);
63348
+ return;
63349
+ }
62938
63350
  const update = args.action === "update" ? updateFromArgs(args) : null;
62939
63351
  if (typeof update === "string") fail3(update);
62940
63352
  let text = args.ref ?? "";
@@ -62974,6 +63386,7 @@ var init_issue = __esm({
62974
63386
  "use strict";
62975
63387
  init_define_SANDBOX_BUILD();
62976
63388
  init_gate();
63389
+ init_issue_status();
62977
63390
  init_issue_tracker();
62978
63391
  init_workspace_prompts();
62979
63392
  init_config2();
@@ -63063,10 +63476,10 @@ var close_out_exports = {};
63063
63476
  __export(close_out_exports, {
63064
63477
  run: () => run29
63065
63478
  });
63066
- import path69 from "node:path";
63479
+ import path70 from "node:path";
63067
63480
  async function run29(args) {
63068
63481
  const root = findRecordRoot(process.cwd());
63069
- const name = args.name ?? (root ? path69.basename(root) : null);
63482
+ const name = args.name ?? (root ? path70.basename(root) : null);
63070
63483
  if (!name) {
63071
63484
  error("No workspace given and not inside one.");
63072
63485
  process.exit(1);
@@ -63118,9 +63531,9 @@ async function run30(args) {
63118
63531
  error("Feature name required: pass it as an argument or set FEATURE in a .env file.");
63119
63532
  process.exit(1);
63120
63533
  }
63121
- const { path: path73 } = await openWorkspace(name);
63534
+ const { path: path74 } = await openWorkspace(name);
63122
63535
  console.log(
63123
- `${Colors.GREEN}Opened '${name}' in VS Code${Colors.RESET} ${Colors.DIM}${path73}${Colors.RESET}`
63536
+ `${Colors.GREEN}Opened '${name}' in VS Code${Colors.RESET} ${Colors.DIM}${path74}${Colors.RESET}`
63124
63537
  );
63125
63538
  }
63126
63539
  var init_code = __esm({
@@ -63138,8 +63551,8 @@ var setup_exports = {};
63138
63551
  __export(setup_exports, {
63139
63552
  run: () => run31
63140
63553
  });
63141
- import * as fs63 from "node:fs";
63142
- import * as path70 from "node:path";
63554
+ import * as fs64 from "node:fs";
63555
+ import * as path71 from "node:path";
63143
63556
  function heading(text) {
63144
63557
  console.log(`
63145
63558
  ${Colors.BOLD}${text}${Colors.RESET}`);
@@ -63245,7 +63658,7 @@ async function configureMachine(interactive) {
63245
63658
  interactive
63246
63659
  );
63247
63660
  }
63248
- if (!fs63.existsSync(servicesLocalYamlPath()) && interactive) {
63661
+ if (!fs64.existsSync(servicesLocalYamlPath()) && interactive) {
63249
63662
  k8sContext = await prompt(
63250
63663
  "Kube context for `sandbox intercept` (services.local.yaml k8s_context)",
63251
63664
  k8sContext
@@ -63407,9 +63820,9 @@ async function configureMachine(interactive) {
63407
63820
  info(
63408
63821
  `${zshrcPath()}: ${completionChanged ? "installed tab completion" : "tab completion already installed"}`
63409
63822
  );
63410
- if (fs63.existsSync(desktopConfigPath())) {
63823
+ if (fs64.existsSync(desktopConfigPath())) {
63411
63824
  info(
63412
- `${path70.basename(desktopConfigPath())}: ${desktopChanged ? "dropped stale env block" : "no env block to remove"}`
63825
+ `${path71.basename(desktopConfigPath())}: ${desktopChanged ? "dropped stale env block" : "no env block to remove"}`
63413
63826
  );
63414
63827
  }
63415
63828
  return {
@@ -63520,8 +63933,8 @@ var config_exports = {};
63520
63933
  __export(config_exports, {
63521
63934
  run: () => run32
63522
63935
  });
63523
- import * as fs64 from "node:fs";
63524
- import * as path71 from "node:path";
63936
+ import * as fs65 from "node:fs";
63937
+ import * as path72 from "node:path";
63525
63938
  function show(cfg) {
63526
63939
  const paths = cfg.paths || {};
63527
63940
  console.log(`${Colors.BOLD}config file:${Colors.RESET} ${configPath()}`);
@@ -63616,9 +64029,9 @@ async function run32(args) {
63616
64029
  info(`Regenerated ${envSh}`);
63617
64030
  info(`${zshenvPath()}: ${zshChanged ? "updated" : "already sources env.sh"}`);
63618
64031
  const desktopPath = desktopConfigPath();
63619
- if (fs64.existsSync(desktopPath)) {
64032
+ if (fs65.existsSync(desktopPath)) {
63620
64033
  info(
63621
- `${path71.basename(desktopPath)}: ${desktopChanged ? "dropped stale env block" : "no env block to remove"}`
64034
+ `${path72.basename(desktopPath)}: ${desktopChanged ? "dropped stale env block" : "no env block to remove"}`
63622
64035
  );
63623
64036
  }
63624
64037
  info("Open a new shell (or `source ~/.zshenv`) for the exports to take effect.");
@@ -63669,7 +64082,7 @@ var init_registry = __esm({
63669
64082
 
63670
64083
  // src/main.ts
63671
64084
  init_define_SANDBOX_BUILD();
63672
- import path72 from "node:path";
64085
+ import path73 from "node:path";
63673
64086
  import { fileURLToPath } from "node:url";
63674
64087
 
63675
64088
  // ../../node_modules/commander/esm.mjs
@@ -63723,7 +64136,7 @@ function staleBuildWarning(build, repoRoot) {
63723
64136
 
63724
64137
  // src/main.ts
63725
64138
  init_interrupt();
63726
- var _REPO_ROOT = path72.resolve(path72.dirname(fileURLToPath(import.meta.url)), "..");
64139
+ var _REPO_ROOT = path73.resolve(path73.dirname(fileURLToPath(import.meta.url)), "..");
63727
64140
  if (!envVar("ROOT")) {
63728
64141
  process.env.SANDBOX_ROOT = _REPO_ROOT;
63729
64142
  }
@@ -63744,11 +64157,15 @@ var SPECS = [
63744
64157
  ).option(
63745
64158
  "-w, --workspace <path>",
63746
64159
  "Path to directory containing repo checkouts. Default: ~/workspace (override with WORKSPACE env var)."
64160
+ ).option(
64161
+ "--dev",
64162
+ "Run the file watchers in cli/library/extension containers, which otherwise idle."
63747
64163
  );
63748
64164
  },
63749
64165
  namespace: ([feature2], o) => ({
63750
64166
  feature: feature2 ?? null,
63751
- workspace: o.workspace ?? null
64167
+ workspace: o.workspace ?? null,
64168
+ dev: o.dev ?? false
63752
64169
  })
63753
64170
  },
63754
64171
  {
@@ -63819,9 +64236,16 @@ var SPECS = [
63819
64236
  ).option(
63820
64237
  "--if-idle",
63821
64238
  "SessionEnd hook mode: stop the workspace's own feature unless another Claude session is open there; silent, always exits 0, never stops infra"
64239
+ ).option(
64240
+ "--idle <minutes>",
64241
+ "Stop every workspace's feature containers nothing has used for <minutes> (a busy or recently active session, a gate exec or a fresh start all count as use); with [feature], only that one"
63822
64242
  );
63823
64243
  },
63824
- namespace: ([feature2], o) => ({ feature: feature2 ?? null, if_idle: Boolean(o.ifIdle) })
64244
+ namespace: ([feature2], o) => ({
64245
+ feature: feature2 ?? null,
64246
+ if_idle: Boolean(o.ifIdle),
64247
+ idle: o.idle ?? null
64248
+ })
63825
64249
  },
63826
64250
  {
63827
64251
  name: "status",
@@ -64157,8 +64581,8 @@ var SPECS = [
64157
64581
  ).addArgument(
64158
64582
  new Argument(
64159
64583
  "<action>",
64160
- "view: print the issue; update: change its status and/or add a comment; create: file a new issue and print its URL (Jira)"
64161
- ).choices(["view", "update", "create"])
64584
+ "view: print the issue; update: change its status and/or add a comment; create: file a new issue and print its URL (Jira); start: move the workspace's issue to in progress (forward only)"
64585
+ ).choices(["view", "update", "create", "start"])
64162
64586
  ).argument(
64163
64587
  "[ref]",
64164
64588
  "Issue ID; omit inside a workspace to use its .sandbox/workspace.yml issue"
@@ -64456,7 +64880,7 @@ ${_RED}Interrupted${_RESET}`));
64456
64880
  return completeLine(program2);
64457
64881
  }
64458
64882
  if (process.argv[2] !== "hooks" && !process.argv.includes("--if-idle")) {
64459
- const warning = staleBuildWarning(BUILD_STAMP, path72.resolve(_REPO_ROOT, "../.."));
64883
+ const warning = staleBuildWarning(BUILD_STAMP, path73.resolve(_REPO_ROOT, "../.."));
64460
64884
  if (warning) console.error(warning);
64461
64885
  }
64462
64886
  if (process.argv.length <= 2) {