werkmeister 0.14.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: "5503d8809006bd32e3b203187be0e307fe0292ff", 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
  {
@@ -13276,7 +13298,7 @@ function writeWorkspaceSettings(workspaceRoot) {
13276
13298
  writeWorkspaceModel(workspaceRoot, readModel(workspaceRoot));
13277
13299
  writeAutoStopHook(workspaceRoot, readAutoStop(workspaceRoot));
13278
13300
  }
13279
- var _HOOK_MARKER, _GATE_MARKER, _PR_GUARD_MARKER, _AUTO_STOP_MARKER, _SKILLS_SELF_HEAL, SESSION_START_COMMAND, PRE_TOOL_USE_GATE_COMMAND, PR_GUARD_COMMAND, SESSION_END_COMMAND, _SESSION_END_TIMEOUT_S, READ_ONLY_GIT_RULES, AUTONOMOUS_RULES, AUTONOMOUS_DENY, permissionsRecord, _strings;
13301
+ var _HOOK_MARKER, _GATE_MARKER, _PR_GUARD_MARKER, _AUTO_STOP_MARKER, _SKILLS_SELF_HEAL, SESSION_START_COMMAND, PRE_TOOL_USE_GATE_COMMAND, PR_GUARD_COMMAND, SESSION_END_COMMAND, _SESSION_END_TIMEOUT_S, READ_ONLY_GIT_RULES, PLUGIN_MCP_PREFIXES, pluginTool, AUTONOMOUS_RULES, AUTONOMOUS_DENY, permissionsRecord, _strings;
13280
13302
  var init_hooks_gen = __esm({
13281
13303
  "../../packages/sandbox-core/dist/hooks_gen.js"() {
13282
13304
  "use strict";
@@ -13299,6 +13321,11 @@ var init_hooks_gen = __esm({
13299
13321
  "Bash(git status:*)",
13300
13322
  "Bash(git ls-files:*)"
13301
13323
  ];
13324
+ PLUGIN_MCP_PREFIXES = [
13325
+ "mcp__plugin_werkmeister_workspaces__",
13326
+ "mcp__plugin_sandbox-tools_sandbox-workspace__"
13327
+ ];
13328
+ pluginTool = (tool) => PLUGIN_MCP_PREFIXES.map((prefix) => `${prefix}${tool}`);
13302
13329
  AUTONOMOUS_RULES = [
13303
13330
  "Bash(git:*)",
13304
13331
  // `sandbox pr open` does the push and the PR; gh is read-only here.
@@ -13306,7 +13333,16 @@ var init_hooks_gen = __esm({
13306
13333
  // No-arg forms act on the cwd's FEATURE; `autonomousRules` adds the named ones.
13307
13334
  "Bash(sandbox up)",
13308
13335
  "Bash(sandbox restart)",
13309
- ...["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}:*)`),
13310
13346
  "Bash(docker ps:*)",
13311
13347
  // Local build and test; no `npm install`, so a new dependency waits for a person.
13312
13348
  ...["npm ci", "npm run", "npm test", "npx", "node", "python3"].map((cmd) => `Bash(${cmd}:*)`),
@@ -13322,7 +13358,7 @@ var init_hooks_gen = __esm({
13322
13358
  "workspace-prs",
13323
13359
  "kube-context",
13324
13360
  "k8s-forwards-status"
13325
- ].map((tool) => `mcp__plugin_sandbox-tools_sandbox-workspace__${tool}`)
13361
+ ].flatMap(pluginTool)
13326
13362
  ];
13327
13363
  AUTONOMOUS_DENY = [
13328
13364
  ...[
@@ -13365,7 +13401,7 @@ var init_hooks_gen = __esm({
13365
13401
  "claude-send",
13366
13402
  "claude-resume",
13367
13403
  "sandbox-connect"
13368
- ].map((tool) => `mcp__plugin_sandbox-tools_sandbox-workspace__${tool}`)
13404
+ ].flatMap(pluginTool)
13369
13405
  ];
13370
13406
  permissionsRecord = (workspaceRoot) => path7.join(dotDir(workspaceRoot), "claude-permissions.json");
13371
13407
  _strings = (v) => Array.isArray(v) ? v.filter((r) => typeof r === "string") : [];
@@ -13555,8 +13591,8 @@ Lifecycle: \`working \u2192 ready \u2192 pr-open \u2192 merged \u2192 shipped \u
13555
13591
  | \`sandbox pr status\` | record merged / shipped / closed from GitHub and release tags |
13556
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 |
13557
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 |
13558
- | \`sandbox close-out\` | after ship: close the issue, write \`closeout.md\`, remove the workspace, delete merged branches |
13559
- | \`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 |
13560
13596
  `;
13561
13597
  }
13562
13598
  var init_pr_rules = __esm({
@@ -14179,7 +14215,7 @@ var init_icm_templates = __esm({
14179
14215
  "One commit per code repo touched, on that worktree's branch",
14180
14216
  "`output/pr-<repo>.md` per repo"
14181
14217
  ],
14182
- 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`.",
14183
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."
14184
14220
  })
14185
14221
  };
@@ -14457,6 +14493,7 @@ function renderIssueRef(issue) {
14457
14493
  "",
14458
14494
  `${name}${title}. ${copy}`,
14459
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.",
14460
14497
  ""
14461
14498
  ].join("\n");
14462
14499
  }
@@ -14490,6 +14527,17 @@ function proposeDir(stages) {
14490
14527
  const i = stages.findIndex((s) => s.name === "propose");
14491
14528
  return i < 0 ? null : stageDirs(stages)[i];
14492
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
+ }
14493
14541
  function vars(ctx, stages) {
14494
14542
  const dirs = stageDirs(stages);
14495
14543
  const feature2 = (ctx.meta["FEATURE"] ?? "").trim();
@@ -14498,8 +14546,7 @@ function vars(ctx, stages) {
14498
14546
  return {
14499
14547
  at: (name) => `../${dirs[stages.findIndex((s) => s.name === name)]}`,
14500
14548
  repos: repos.length ? repos.map((r) => `\`../../${r}/\``).join(", ") : "the code worktrees in this workspace",
14501
- // Container names follow docker-compose.feature.yml.njk: <feature>-<service>.
14502
- 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",
14503
14550
  standards: "",
14504
14551
  autonomous: isTrue(ctx.inputs["autonomous"])
14505
14552
  };
@@ -14550,7 +14597,7 @@ function standardsRef(tier, r) {
14550
14597
  const rows = tier === "all" ? r.active : r.active.filter((d) => universal.includes(d));
14551
14598
  return `- Reference (every run): \`../../_shared/standards.md\` \u2014 rows ${[...rows, "ponytail"].map((d) => `\`${d}\``).join(", ")}`;
14552
14599
  }
14553
- function renderContract(dir, stage, v) {
14600
+ function renderContract(dir, stage, v, startsIssue = false) {
14554
14601
  const c = stage.contract(v);
14555
14602
  return [
14556
14603
  `# ${dir} \u2014 ${stage.job}`,
@@ -14564,6 +14611,8 @@ function renderContract(dir, stage, v) {
14564
14611
  `Do NOT load: ${c.doNotLoad}`,
14565
14612
  "",
14566
14613
  "## Process",
14614
+ // Step 0, so the contracts' own "steps 1–3" references keep their numbers.
14615
+ ...startsIssue ? [`0. ${START_ISSUE}`] : [],
14567
14616
  ...c.process.map((p, i) => `${i + 1}. ${p}`),
14568
14617
  "",
14569
14618
  "## Outputs",
@@ -14610,7 +14659,7 @@ function renderClaudeMd(ctx, stages) {
14610
14659
  ...proposeDir(stages) ? [
14611
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 |"
14612
14661
  ] : [],
14613
- "| 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 |",
14614
14663
  "| asked for status | `ls stages/*/output/` | report which stages hold artifacts |",
14615
14664
  "| containers / ports / routing broken | `.sandbox/sandbox-context.md` | \u2014 |",
14616
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 |",
@@ -14690,10 +14739,7 @@ function render(ctx) {
14690
14739
  if (propose2)
14691
14740
  files["_shared/pr-rules.md"] = buildPrRulesMd(`stages/${propose2}/output/`, base.autonomous);
14692
14741
  stages.forEach((stage, i) => {
14693
- files[`stages/${dirs[i]}/CONTEXT.md`] = renderContract(dirs[i], stage, {
14694
- ...base,
14695
- standards: standardsRef(stage.standards, r)
14696
- });
14742
+ files[`stages/${dirs[i]}/CONTEXT.md`] = renderContract(dirs[i], stage, { ...base, standards: standardsRef(stage.standards, r) }, i === 0 && Boolean(issueIdOfMeta(ctx.meta)));
14697
14743
  });
14698
14744
  return files;
14699
14745
  }
@@ -14763,7 +14809,7 @@ function init(ctx) {
14763
14809
  }
14764
14810
  icm.refresh(root);
14765
14811
  }
14766
- 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;
14767
14813
  var init_icm = __esm({
14768
14814
  "../../packages/sandbox-core/dist/strategies/icm.js"() {
14769
14815
  "use strict";
@@ -14781,6 +14827,7 @@ var init_icm = __esm({
14781
14827
  init_icm_templates();
14782
14828
  init_issue_section();
14783
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.";
14784
14831
  ONE_RULE = [
14785
14832
  "## The one rule",
14786
14833
  "",
@@ -15090,69 +15137,69 @@ var require_chownr = __commonJS({
15090
15137
  "../../node_modules/chownr/chownr.js"(exports, module) {
15091
15138
  "use strict";
15092
15139
  init_define_SANDBOX_BUILD();
15093
- var fs65 = __require("fs");
15094
- var path73 = __require("path");
15095
- var LCHOWN = fs65.lchown ? "lchown" : "chown";
15096
- var LCHOWNSYNC = fs65.lchownSync ? "lchownSync" : "chownSync";
15097
- var needEISDIRHandled = fs65.lchown && !process.version.match(/v1[1-9]+\./) && !process.version.match(/v10\.[6-9]/);
15098
- 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) => {
15099
15146
  try {
15100
- return fs65[LCHOWNSYNC](path74, uid, gid);
15147
+ return fs66[LCHOWNSYNC](path75, uid, gid);
15101
15148
  } catch (er) {
15102
15149
  if (er.code !== "ENOENT")
15103
15150
  throw er;
15104
15151
  }
15105
15152
  };
15106
- var chownSync = (path74, uid, gid) => {
15153
+ var chownSync = (path75, uid, gid) => {
15107
15154
  try {
15108
- return fs65.chownSync(path74, uid, gid);
15155
+ return fs66.chownSync(path75, uid, gid);
15109
15156
  } catch (er) {
15110
15157
  if (er.code !== "ENOENT")
15111
15158
  throw er;
15112
15159
  }
15113
15160
  };
15114
- var handleEISDIR = needEISDIRHandled ? (path74, uid, gid, cb) => (er) => {
15161
+ var handleEISDIR = needEISDIRHandled ? (path75, uid, gid, cb) => (er) => {
15115
15162
  if (!er || er.code !== "EISDIR")
15116
15163
  cb(er);
15117
15164
  else
15118
- fs65.chown(path74, uid, gid, cb);
15165
+ fs66.chown(path75, uid, gid, cb);
15119
15166
  } : (_, __, ___, cb) => cb;
15120
- var handleEISDirSync = needEISDIRHandled ? (path74, uid, gid) => {
15167
+ var handleEISDirSync = needEISDIRHandled ? (path75, uid, gid) => {
15121
15168
  try {
15122
- return lchownSync(path74, uid, gid);
15169
+ return lchownSync(path75, uid, gid);
15123
15170
  } catch (er) {
15124
15171
  if (er.code !== "EISDIR")
15125
15172
  throw er;
15126
- chownSync(path74, uid, gid);
15173
+ chownSync(path75, uid, gid);
15127
15174
  }
15128
- } : (path74, uid, gid) => lchownSync(path74, uid, gid);
15175
+ } : (path75, uid, gid) => lchownSync(path75, uid, gid);
15129
15176
  var nodeVersion = process.version;
15130
- var readdir = (path74, options, cb) => fs65.readdir(path74, options, cb);
15131
- 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);
15132
15179
  if (/^v4\./.test(nodeVersion))
15133
- readdir = (path74, options, cb) => fs65.readdir(path74, cb);
15180
+ readdir = (path75, options, cb) => fs66.readdir(path75, cb);
15134
15181
  var chown = (cpath, uid, gid, cb) => {
15135
- fs65[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, (er) => {
15182
+ fs66[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, (er) => {
15136
15183
  cb(er && er.code !== "ENOENT" ? er : null);
15137
15184
  }));
15138
15185
  };
15139
15186
  var chownrKid = (p, child, uid, gid, cb) => {
15140
15187
  if (typeof child === "string")
15141
- return fs65.lstat(path73.resolve(p, child), (er, stats) => {
15188
+ return fs66.lstat(path74.resolve(p, child), (er, stats) => {
15142
15189
  if (er)
15143
15190
  return cb(er.code !== "ENOENT" ? er : null);
15144
15191
  stats.name = child;
15145
15192
  chownrKid(p, stats, uid, gid, cb);
15146
15193
  });
15147
15194
  if (child.isDirectory()) {
15148
- chownr(path73.resolve(p, child.name), uid, gid, (er) => {
15195
+ chownr(path74.resolve(p, child.name), uid, gid, (er) => {
15149
15196
  if (er)
15150
15197
  return cb(er);
15151
- const cpath = path73.resolve(p, child.name);
15198
+ const cpath = path74.resolve(p, child.name);
15152
15199
  chown(cpath, uid, gid, cb);
15153
15200
  });
15154
15201
  } else {
15155
- const cpath = path73.resolve(p, child.name);
15202
+ const cpath = path74.resolve(p, child.name);
15156
15203
  chown(cpath, uid, gid, cb);
15157
15204
  }
15158
15205
  };
@@ -15182,7 +15229,7 @@ var require_chownr = __commonJS({
15182
15229
  var chownrKidSync = (p, child, uid, gid) => {
15183
15230
  if (typeof child === "string") {
15184
15231
  try {
15185
- const stats = fs65.lstatSync(path73.resolve(p, child));
15232
+ const stats = fs66.lstatSync(path74.resolve(p, child));
15186
15233
  stats.name = child;
15187
15234
  child = stats;
15188
15235
  } catch (er) {
@@ -15193,13 +15240,13 @@ var require_chownr = __commonJS({
15193
15240
  }
15194
15241
  }
15195
15242
  if (child.isDirectory())
15196
- chownrSync(path73.resolve(p, child.name), uid, gid);
15197
- 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);
15198
15245
  };
15199
15246
  var chownrSync = (p, uid, gid) => {
15200
15247
  let children;
15201
15248
  try {
15202
- children = readdirSync19(p, { withFileTypes: true });
15249
+ children = readdirSync20(p, { withFileTypes: true });
15203
15250
  } catch (er) {
15204
15251
  if (er.code === "ENOENT")
15205
15252
  return;
@@ -15350,7 +15397,7 @@ var require_buffer_list = __commonJS({
15350
15397
  }
15351
15398
  }, {
15352
15399
  key: "join",
15353
- value: function join52(s) {
15400
+ value: function join53(s) {
15354
15401
  if (this.length === 0) return "";
15355
15402
  var p = this.head;
15356
15403
  var ret = "" + p.data;
@@ -19195,9 +19242,9 @@ var require_pump = __commonJS({
19195
19242
  init_define_SANDBOX_BUILD();
19196
19243
  var once = require_once();
19197
19244
  var eos = require_end_of_stream2();
19198
- var fs65;
19245
+ var fs66;
19199
19246
  try {
19200
- fs65 = __require("fs");
19247
+ fs66 = __require("fs");
19201
19248
  } catch (e) {
19202
19249
  }
19203
19250
  var noop = function() {
@@ -19208,8 +19255,8 @@ var require_pump = __commonJS({
19208
19255
  };
19209
19256
  var isFS = function(stream) {
19210
19257
  if (!ancient) return false;
19211
- if (!fs65) return false;
19212
- 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);
19213
19260
  };
19214
19261
  var isRequest = function(stream) {
19215
19262
  return stream.setHeader && isFn(stream.abort);
@@ -19269,8 +19316,8 @@ var require_pump = __commonJS({
19269
19316
  var require_mkdirp_classic = __commonJS({
19270
19317
  "../../node_modules/mkdirp-classic/index.js"(exports, module) {
19271
19318
  init_define_SANDBOX_BUILD();
19272
- var path73 = __require("path");
19273
- var fs65 = __require("fs");
19319
+ var path74 = __require("path");
19320
+ var fs66 = __require("fs");
19274
19321
  var _0777 = parseInt("0777", 8);
19275
19322
  module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
19276
19323
  function mkdirP(p, opts, f, made) {
@@ -19281,14 +19328,14 @@ var require_mkdirp_classic = __commonJS({
19281
19328
  opts = { mode: opts };
19282
19329
  }
19283
19330
  var mode = opts.mode;
19284
- var xfs = opts.fs || fs65;
19331
+ var xfs = opts.fs || fs66;
19285
19332
  if (mode === void 0) {
19286
19333
  mode = _0777 & ~process.umask();
19287
19334
  }
19288
19335
  if (!made) made = null;
19289
19336
  var cb = f || function() {
19290
19337
  };
19291
- p = path73.resolve(p);
19338
+ p = path74.resolve(p);
19292
19339
  xfs.mkdir(p, mode, function(er) {
19293
19340
  if (!er) {
19294
19341
  made = made || p;
@@ -19296,7 +19343,7 @@ var require_mkdirp_classic = __commonJS({
19296
19343
  }
19297
19344
  switch (er.code) {
19298
19345
  case "ENOENT":
19299
- mkdirP(path73.dirname(p), opts, function(er2, made2) {
19346
+ mkdirP(path74.dirname(p), opts, function(er2, made2) {
19300
19347
  if (er2) cb(er2, made2);
19301
19348
  else mkdirP(p, opts, cb, made2);
19302
19349
  });
@@ -19318,19 +19365,19 @@ var require_mkdirp_classic = __commonJS({
19318
19365
  opts = { mode: opts };
19319
19366
  }
19320
19367
  var mode = opts.mode;
19321
- var xfs = opts.fs || fs65;
19368
+ var xfs = opts.fs || fs66;
19322
19369
  if (mode === void 0) {
19323
19370
  mode = _0777 & ~process.umask();
19324
19371
  }
19325
19372
  if (!made) made = null;
19326
- p = path73.resolve(p);
19373
+ p = path74.resolve(p);
19327
19374
  try {
19328
19375
  xfs.mkdirSync(p, mode);
19329
19376
  made = made || p;
19330
19377
  } catch (err0) {
19331
19378
  switch (err0.code) {
19332
19379
  case "ENOENT":
19333
- made = sync(path73.dirname(p), opts, made);
19380
+ made = sync(path74.dirname(p), opts, made);
19334
19381
  sync(p, opts, made);
19335
19382
  break;
19336
19383
  // In the case of any other error, just see if there's a dir
@@ -19360,8 +19407,8 @@ var require_tar_fs = __commonJS({
19360
19407
  var tar2 = require_tar_stream();
19361
19408
  var pump = require_pump();
19362
19409
  var mkdirp = require_mkdirp_classic();
19363
- var fs65 = __require("fs");
19364
- var path73 = __require("path");
19410
+ var fs66 = __require("fs");
19411
+ var path74 = __require("path");
19365
19412
  var os21 = __require("os");
19366
19413
  var win32 = os21.platform() === "win32";
19367
19414
  var noop = function() {
@@ -19372,23 +19419,23 @@ var require_tar_fs = __commonJS({
19372
19419
  var normalize = !win32 ? echo : function(name) {
19373
19420
  return name.replace(/\\/g, "/").replace(/[:?<>|]/g, "_");
19374
19421
  };
19375
- var statAll = function(fs66, stat, cwd, ignore, entries, sort) {
19422
+ var statAll = function(fs67, stat, cwd, ignore, entries, sort) {
19376
19423
  var queue = entries || ["."];
19377
19424
  for (var i = 0; i < queue.length; i++) {
19378
- 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");
19379
19426
  }
19380
19427
  return function loop(callback) {
19381
19428
  if (!queue.length) return callback();
19382
19429
  var next = queue.shift();
19383
- var nextAbs = path73.join(cwd, next);
19384
- stat.call(fs66, nextAbs, function(err, stat2) {
19430
+ var nextAbs = path74.join(cwd, next);
19431
+ stat.call(fs67, nextAbs, function(err, stat2) {
19385
19432
  if (err) return callback(err);
19386
19433
  if (!stat2.isDirectory()) return callback(null, next, stat2);
19387
- fs66.readdir(nextAbs, function(err2, files) {
19434
+ fs67.readdir(nextAbs, function(err2, files) {
19388
19435
  if (err2) return callback(err2);
19389
19436
  if (sort) files.sort();
19390
19437
  for (var i2 = 0; i2 < files.length; i2++) {
19391
- 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]));
19392
19439
  }
19393
19440
  callback(null, next, stat2);
19394
19441
  });
@@ -19399,7 +19446,7 @@ var require_tar_fs = __commonJS({
19399
19446
  return function(header) {
19400
19447
  header.name = header.name.split("/").slice(level).join("/");
19401
19448
  var linkname = header.linkname;
19402
- if (linkname && (header.type === "link" || path73.isAbsolute(linkname))) {
19449
+ if (linkname && (header.type === "link" || path74.isAbsolute(linkname))) {
19403
19450
  header.linkname = linkname.split("/").slice(level).join("/");
19404
19451
  }
19405
19452
  return map(header);
@@ -19408,7 +19455,7 @@ var require_tar_fs = __commonJS({
19408
19455
  exports.pack = function(cwd, opts) {
19409
19456
  if (!cwd) cwd = ".";
19410
19457
  if (!opts) opts = {};
19411
- var xfs = opts.fs || fs65;
19458
+ var xfs = opts.fs || fs66;
19412
19459
  var ignore = opts.ignore || opts.filter || noop;
19413
19460
  var map = opts.map || noop;
19414
19461
  var mapStream = opts.mapStream || echo;
@@ -19429,7 +19476,7 @@ var require_tar_fs = __commonJS({
19429
19476
  fmode |= parseInt(222, 8);
19430
19477
  }
19431
19478
  var onsymlink = function(filename, header) {
19432
- xfs.readlink(path73.join(cwd, filename), function(err, linkname) {
19479
+ xfs.readlink(path74.join(cwd, filename), function(err, linkname) {
19433
19480
  if (err) return pack2.destroy(err);
19434
19481
  header.linkname = normalize(linkname);
19435
19482
  pack2.entry(header, onnextentry);
@@ -19470,7 +19517,7 @@ var require_tar_fs = __commonJS({
19470
19517
  }
19471
19518
  var entry = pack2.entry(header, onnextentry);
19472
19519
  if (!entry) return;
19473
- 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);
19474
19521
  rs.on("error", function(err2) {
19475
19522
  entry.destroy(err2);
19476
19523
  });
@@ -19495,7 +19542,7 @@ var require_tar_fs = __commonJS({
19495
19542
  exports.extract = function(cwd, opts) {
19496
19543
  if (!cwd) cwd = ".";
19497
19544
  if (!opts) opts = {};
19498
- var xfs = opts.fs || fs65;
19545
+ var xfs = opts.fs || fs66;
19499
19546
  var ignore = opts.ignore || opts.filter || noop;
19500
19547
  var map = opts.map || noop;
19501
19548
  var mapStream = opts.mapStream || echo;
@@ -19548,14 +19595,14 @@ var require_tar_fs = __commonJS({
19548
19595
  extract.on("entry", function(header, stream, next) {
19549
19596
  header = map(header) || header;
19550
19597
  header.name = normalize(header.name);
19551
- var name = path73.join(cwd, path73.join("/", header.name));
19598
+ var name = path74.join(cwd, path74.join("/", header.name));
19552
19599
  if (ignore(name, header)) {
19553
19600
  stream.resume();
19554
19601
  return next();
19555
19602
  }
19556
19603
  var stat = function(err) {
19557
19604
  if (err) return next(err);
19558
- if (path73.join(name, ".") === path73.join(cwd, ".")) return next();
19605
+ if (path74.join(name, ".") === path74.join(cwd, ".")) return next();
19559
19606
  utimes(name, header, function(err2) {
19560
19607
  if (err2) return next(err2);
19561
19608
  if (win32) return next();
@@ -19565,9 +19612,9 @@ var require_tar_fs = __commonJS({
19565
19612
  var onsymlink = function() {
19566
19613
  if (win32) return next();
19567
19614
  xfs.unlink(name, function() {
19568
- var dst = path73.resolve(path73.dirname(name), header.linkname);
19615
+ var dst = path74.resolve(path74.dirname(name), header.linkname);
19569
19616
  if (!inCwd(dst, cwd)) return next(new Error(name + " is not a valid symlink"));
19570
- validateNotSymlink(xfs, dst, path73.resolve(cwd), function(err, valid) {
19617
+ validateNotSymlink(xfs, dst, path74.resolve(cwd), function(err, valid) {
19571
19618
  if (err) return next(err);
19572
19619
  if (!valid) return next(new Error(name + " is not a valid symlink"));
19573
19620
  xfs.symlink(header.linkname, name, stat);
@@ -19577,7 +19624,7 @@ var require_tar_fs = __commonJS({
19577
19624
  var onlink = function() {
19578
19625
  if (win32) return next();
19579
19626
  xfs.unlink(name, function() {
19580
- var srcpath = path73.join(cwd, path73.join("/", header.linkname));
19627
+ var srcpath = path74.join(cwd, path74.join("/", header.linkname));
19581
19628
  xfs.realpath(srcpath, function(err, dst) {
19582
19629
  if (err || !inCwd(dst, cwd)) return next(new Error(name + " is not a valid hardlink"));
19583
19630
  xfs.link(dst, name, function(err2) {
@@ -19617,8 +19664,8 @@ var require_tar_fs = __commonJS({
19617
19664
  gid: header.gid
19618
19665
  }, stat);
19619
19666
  }
19620
- var dir = path73.dirname(name);
19621
- 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) {
19622
19669
  if (err) return next(err);
19623
19670
  if (!valid) return next(new Error(dir + " is not a valid path"));
19624
19671
  mkdirfix(dir, {
@@ -19645,20 +19692,20 @@ var require_tar_fs = __commonJS({
19645
19692
  if (opts.finish) extract.on("finish", opts.finish);
19646
19693
  return extract;
19647
19694
  };
19648
- function validate(fs66, name, root, cb) {
19695
+ function validate(fs67, name, root, cb) {
19649
19696
  if (name === root) return cb(null, true);
19650
- fs66.lstat(name, function(err, st) {
19697
+ fs67.lstat(name, function(err, st) {
19651
19698
  if (err && err.code !== "ENOENT") return cb(err);
19652
- 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);
19653
19700
  cb(null, false);
19654
19701
  });
19655
19702
  }
19656
- function validateNotSymlink(fs66, name, root, cb) {
19703
+ function validateNotSymlink(fs67, name, root, cb) {
19657
19704
  if (name === root) return cb(null, true);
19658
- if (!name.startsWith(root + path73.sep)) return cb(null, false);
19659
- fs66.lstat(name, function(err, st) {
19705
+ if (!name.startsWith(root + path74.sep)) return cb(null, false);
19706
+ fs67.lstat(name, function(err, st) {
19660
19707
  if (err && err.code !== "ENOENT" && err.code !== "EPERM") return cb(err);
19661
- 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);
19662
19709
  cb(null, false);
19663
19710
  });
19664
19711
  }
@@ -19672,8 +19719,8 @@ var require_tar_fs = __commonJS({
19672
19719
  });
19673
19720
  }
19674
19721
  function inCwd(dst, cwd) {
19675
- cwd = path73.resolve(cwd);
19676
- return cwd === dst || dst.startsWith(cwd + path73.sep);
19722
+ cwd = path74.resolve(cwd);
19723
+ return cwd === dst || dst.startsWith(cwd + path74.sep);
19677
19724
  }
19678
19725
  }
19679
19726
  });
@@ -22543,11 +22590,11 @@ var require_lib = __commonJS({
22543
22590
  function lookupEscape(ch) {
22544
22591
  return escapeMap[ch];
22545
22592
  }
22546
- function _prettifyError(path73, withInternals, err) {
22593
+ function _prettifyError(path74, withInternals, err) {
22547
22594
  if (!err.Update) {
22548
22595
  err = new _exports.TemplateError(err);
22549
22596
  }
22550
- err.Update(path73);
22597
+ err.Update(path74);
22551
22598
  if (!withInternals) {
22552
22599
  var old = err;
22553
22600
  err = new Error(old.message);
@@ -22608,8 +22655,8 @@ var require_lib = __commonJS({
22608
22655
  err.lineno = lineno;
22609
22656
  err.colno = colno;
22610
22657
  err.firstUpdate = true;
22611
- err.Update = function Update(path73) {
22612
- var msg = "(" + (path73 || "unknown path") + ")";
22658
+ err.Update = function Update(path74) {
22659
+ var msg = "(" + (path74 || "unknown path") + ")";
22613
22660
  if (this.firstUpdate) {
22614
22661
  if (this.lineno && this.colno) {
22615
22662
  msg += " [Line " + this.lineno + ", Column " + this.colno + "]";
@@ -26459,7 +26506,7 @@ var require_filters = __commonJS({
26459
26506
  return r.copySafeness(str3, res);
26460
26507
  }
26461
26508
  _exports.indent = indent;
26462
- function join52(arr, del, attr) {
26509
+ function join53(arr, del, attr) {
26463
26510
  del = del || "";
26464
26511
  if (attr) {
26465
26512
  arr = lib.map(arr, function(v) {
@@ -26468,7 +26515,7 @@ var require_filters = __commonJS({
26468
26515
  }
26469
26516
  return arr.join(del);
26470
26517
  }
26471
- _exports.join = join52;
26518
+ _exports.join = join53;
26472
26519
  function last(arr) {
26473
26520
  return arr[arr.length - 1];
26474
26521
  }
@@ -26821,7 +26868,7 @@ var require_loader = __commonJS({
26821
26868
  };
26822
26869
  return _setPrototypeOf(o, p);
26823
26870
  }
26824
- var path73 = __require("path");
26871
+ var path74 = __require("path");
26825
26872
  var _require = require_object();
26826
26873
  var EmitterObj = _require.EmitterObj;
26827
26874
  module.exports = /* @__PURE__ */ function(_EmitterObj) {
@@ -26831,7 +26878,7 @@ var require_loader = __commonJS({
26831
26878
  }
26832
26879
  var _proto = Loader.prototype;
26833
26880
  _proto.resolve = function resolve13(from, to) {
26834
- return path73.resolve(path73.dirname(from), to);
26881
+ return path74.resolve(path74.dirname(from), to);
26835
26882
  };
26836
26883
  _proto.isRelative = function isRelative(filename) {
26837
26884
  return filename.indexOf("./") === 0 || filename.indexOf("../") === 0;
@@ -26905,8 +26952,8 @@ var require_node_loaders = __commonJS({
26905
26952
  };
26906
26953
  return _setPrototypeOf(o, p);
26907
26954
  }
26908
- var fs65 = __require("fs");
26909
- var path73 = __require("path");
26955
+ var fs66 = __require("fs");
26956
+ var path74 = __require("path");
26910
26957
  var Loader = require_loader();
26911
26958
  var _require = require_precompiled_loader();
26912
26959
  var PrecompiledLoader = _require.PrecompiledLoader;
@@ -26924,7 +26971,7 @@ var require_node_loaders = __commonJS({
26924
26971
  _this.noCache = !!opts.noCache;
26925
26972
  if (searchPaths) {
26926
26973
  searchPaths = Array.isArray(searchPaths) ? searchPaths : [searchPaths];
26927
- _this.searchPaths = searchPaths.map(path73.normalize);
26974
+ _this.searchPaths = searchPaths.map(path74.normalize);
26928
26975
  } else {
26929
26976
  _this.searchPaths = ["."];
26930
26977
  }
@@ -26934,10 +26981,10 @@ var require_node_loaders = __commonJS({
26934
26981
  } catch (e) {
26935
26982
  throw new Error("watch requires chokidar to be installed");
26936
26983
  }
26937
- var paths = _this.searchPaths.filter(fs65.existsSync);
26984
+ var paths = _this.searchPaths.filter(fs66.existsSync);
26938
26985
  var watcher = chokidar.watch(paths);
26939
26986
  watcher.on("all", function(event, fullname) {
26940
- fullname = path73.resolve(fullname);
26987
+ fullname = path74.resolve(fullname);
26941
26988
  if (event === "change" && fullname in _this.pathsToNames) {
26942
26989
  _this.emit("update", _this.pathsToNames[fullname], fullname);
26943
26990
  }
@@ -26953,9 +27000,9 @@ var require_node_loaders = __commonJS({
26953
27000
  var fullpath = null;
26954
27001
  var paths = this.searchPaths;
26955
27002
  for (var i = 0; i < paths.length; i++) {
26956
- var basePath = path73.resolve(paths[i]);
26957
- var p = path73.resolve(paths[i], name);
26958
- 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)) {
26959
27006
  fullpath = p;
26960
27007
  break;
26961
27008
  }
@@ -26965,7 +27012,7 @@ var require_node_loaders = __commonJS({
26965
27012
  }
26966
27013
  this.pathsToNames[fullpath] = name;
26967
27014
  var source = {
26968
- src: fs65.readFileSync(fullpath, "utf-8"),
27015
+ src: fs66.readFileSync(fullpath, "utf-8"),
26969
27016
  path: fullpath,
26970
27017
  noCache: this.noCache
26971
27018
  };
@@ -27017,7 +27064,7 @@ var require_node_loaders = __commonJS({
27017
27064
  }
27018
27065
  this.pathsToNames[fullpath] = name;
27019
27066
  var source = {
27020
- src: fs65.readFileSync(fullpath, "utf-8"),
27067
+ src: fs66.readFileSync(fullpath, "utf-8"),
27021
27068
  path: fullpath,
27022
27069
  noCache: this.noCache
27023
27070
  };
@@ -27224,13 +27271,13 @@ var require_express_app = __commonJS({
27224
27271
  "../../node_modules/nunjucks/src/express-app.js"(exports, module) {
27225
27272
  "use strict";
27226
27273
  init_define_SANDBOX_BUILD();
27227
- var path73 = __require("path");
27274
+ var path74 = __require("path");
27228
27275
  module.exports = function express(env, app) {
27229
27276
  function NunjucksView(name, opts) {
27230
27277
  this.name = name;
27231
27278
  this.path = name;
27232
27279
  this.defaultEngine = opts.defaultEngine;
27233
- this.ext = path73.extname(name);
27280
+ this.ext = path74.extname(name);
27234
27281
  if (!this.ext && !this.defaultEngine) {
27235
27282
  throw new Error("No default engine was specified and no extension was provided.");
27236
27283
  }
@@ -27611,7 +27658,7 @@ var require_environment = __commonJS({
27611
27658
  return _Obj2.apply(this, arguments) || this;
27612
27659
  }
27613
27660
  var _proto3 = Template2.prototype;
27614
- _proto3.init = function init3(src, env, path73, eagerCompile) {
27661
+ _proto3.init = function init3(src, env, path74, eagerCompile) {
27615
27662
  this.env = env || new Environment();
27616
27663
  if (lib.isObject(src)) {
27617
27664
  switch (src.type) {
@@ -27629,7 +27676,7 @@ var require_environment = __commonJS({
27629
27676
  } else {
27630
27677
  throw new Error("src must be a string or an object describing the source");
27631
27678
  }
27632
- this.path = path73;
27679
+ this.path = path74;
27633
27680
  if (eagerCompile) {
27634
27681
  try {
27635
27682
  this._compile();
@@ -27781,8 +27828,8 @@ var require_precompile = __commonJS({
27781
27828
  "../../node_modules/nunjucks/src/precompile.js"(exports, module) {
27782
27829
  "use strict";
27783
27830
  init_define_SANDBOX_BUILD();
27784
- var fs65 = __require("fs");
27785
- var path73 = __require("path");
27831
+ var fs66 = __require("fs");
27832
+ var path74 = __require("path");
27786
27833
  var _require = require_lib();
27787
27834
  var _prettifyError = _require._prettifyError;
27788
27835
  var compiler = require_compiler();
@@ -27814,14 +27861,14 @@ var require_precompile = __commonJS({
27814
27861
  if (opts.isString) {
27815
27862
  return precompileString(input2, opts);
27816
27863
  }
27817
- var pathStats = fs65.existsSync(input2) && fs65.statSync(input2);
27864
+ var pathStats = fs66.existsSync(input2) && fs66.statSync(input2);
27818
27865
  var precompiled = [];
27819
27866
  var templates = [];
27820
27867
  function addTemplates(dir) {
27821
- fs65.readdirSync(dir).forEach(function(file) {
27822
- var filepath = path73.join(dir, file);
27823
- var subpath = filepath.substr(path73.join(input2, "/").length);
27824
- 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);
27825
27872
  if (stat && stat.isDirectory()) {
27826
27873
  subpath += "/";
27827
27874
  if (!match(subpath, opts.exclude)) {
@@ -27833,13 +27880,13 @@ var require_precompile = __commonJS({
27833
27880
  });
27834
27881
  }
27835
27882
  if (pathStats.isFile()) {
27836
- 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));
27837
27884
  } else if (pathStats.isDirectory()) {
27838
27885
  addTemplates(input2);
27839
27886
  for (var i = 0; i < templates.length; i++) {
27840
- var name = templates[i].replace(path73.join(input2, "/"), "");
27887
+ var name = templates[i].replace(path74.join(input2, "/"), "");
27841
27888
  try {
27842
- precompiled.push(_precompile(fs65.readFileSync(templates[i], "utf-8"), name, env));
27889
+ precompiled.push(_precompile(fs66.readFileSync(templates[i], "utf-8"), name, env));
27843
27890
  } catch (e) {
27844
27891
  if (opts.force) {
27845
27892
  console.error(e);
@@ -28218,11 +28265,11 @@ var require_nunjucks = __commonJS({
28218
28265
  reset: function reset() {
28219
28266
  e = void 0;
28220
28267
  },
28221
- compile: function compile(src, env, path73, eagerCompile) {
28268
+ compile: function compile(src, env, path74, eagerCompile) {
28222
28269
  if (!e) {
28223
28270
  configure();
28224
28271
  }
28225
- return new Template(src, env, path73, eagerCompile);
28272
+ return new Template(src, env, path74, eagerCompile);
28226
28273
  },
28227
28274
  render: function render2(name, ctx, cb) {
28228
28275
  if (!e) {
@@ -28274,12 +28321,12 @@ function resolveSecrets(all, servicesConfig, serviceNames, overrides = {}) {
28274
28321
  function getSecretsPath() {
28275
28322
  return secretsPath();
28276
28323
  }
28277
- function loadSecrets(path73) {
28278
- if (!(fs26.existsSync(path73) && fs26.statSync(path73).isFile())) {
28324
+ function loadSecrets(path74) {
28325
+ if (!(fs26.existsSync(path74) && fs26.statSync(path74).isFile())) {
28279
28326
  return {};
28280
28327
  }
28281
28328
  const result = {};
28282
- for (const rawLine of fs26.readFileSync(path73, "utf8").split("\n")) {
28329
+ for (const rawLine of fs26.readFileSync(path74, "utf8").split("\n")) {
28283
28330
  const line = rawLine.trim();
28284
28331
  if (!line || line.startsWith("#") || !line.includes("=")) {
28285
28332
  continue;
@@ -28570,6 +28617,7 @@ function getTemplateOrNull(env, name) {
28570
28617
  function composeHeader(feature2, source) {
28571
28618
  return `# Sandbox docker-compose for feature: ${feature2}
28572
28619
  # Generated by: ${source}
28620
+ # Regenerated by \`sandbox up\` whenever stale; do not hand-edit.
28573
28621
  #
28574
28622
  # Run from workspace root:
28575
28623
  # docker compose up -d --build (start)
@@ -28737,6 +28785,7 @@ function renderFeatureCompose(feature2, slot, localServices, config, workspaceOv
28737
28785
  base_port: portStart,
28738
28786
  local_services: svcObjects,
28739
28787
  services_config: servicesConfig,
28788
+ idle_types: IDLE_SERVICE_TYPES,
28740
28789
  setup_configs: setupConfigs,
28741
28790
  ports_config: portsConfig,
28742
28791
  workspace_path: workspaceRoot,
@@ -29293,9 +29342,9 @@ async function generateWorkspaceCompose(feature2, servicesStr, workspaceRoot, te
29293
29342
  continue;
29294
29343
  }
29295
29344
  const stripped = (svcDef["path"] ?? "").replace(/\/+$/, "");
29296
- const basename26 = stripped.includes("/") ? stripped.slice(stripped.lastIndexOf("/") + 1) : stripped;
29297
- if (basename26 !== svcName) {
29298
- pathAliases[basename26] = svcName;
29345
+ const basename27 = stripped.includes("/") ? stripped.slice(stripped.lastIndexOf("/") + 1) : stripped;
29346
+ if (basename27 !== svcName) {
29347
+ pathAliases[basename27] = svcName;
29299
29348
  }
29300
29349
  }
29301
29350
  const resolve13 = (name) => {
@@ -29477,6 +29526,18 @@ function refreshKubeconfig() {
29477
29526
  }
29478
29527
  return fs29.existsSync(kubeconfig) && fs29.statSync(kubeconfig).isFile();
29479
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
+ }
29480
29541
  function ensurePortForwardsHealthy() {
29481
29542
  const health = checkPortForwardHealth();
29482
29543
  if (health["k8s-forwards"] ?? false) {
@@ -29488,6 +29549,10 @@ function ensurePortForwardsHealthy() {
29488
29549
  error("Not restarting k8s-forwards without a kubeconfig file to mount");
29489
29550
  return "failed";
29490
29551
  }
29552
+ if (!clusterReachable()) {
29553
+ warn("No reachable k8s cluster \u2014 skipping port-forwards; services not run locally are unavailable");
29554
+ return "failed";
29555
+ }
29491
29556
  const { argv, env } = composeInvocation();
29492
29557
  try {
29493
29558
  const result = run([...argv, "up", "-d", "--force-recreate", "k8s-forwards"], {
@@ -30065,7 +30130,8 @@ async function up(feature2, workspace, { signal } = {}) {
30065
30130
  generateFeatureEnv(feature2, slot, localServices, config, workspace);
30066
30131
  } else if (isFeatureComposeStale(feature2, slot, localServices, config, workspace)) {
30067
30132
  staleCompose = true;
30068
- 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");
30069
30135
  }
30070
30136
  generateSupergraphConfig(feature2, slot, localServices, config, workspace);
30071
30137
  raw(`
@@ -30164,27 +30230,119 @@ function refuseInsideOwnContainer(feature2, verb) {
30164
30230
  Run it from the host: cd to the workspace and run 'sandbox ${verb}'.`, "INSIDE_OWN_CONTAINER");
30165
30231
  }
30166
30232
  }
30167
- function otherLiveSessions(workspaceRoot, sessionId, sessionsDir = path30.join(os8.homedir(), ".claude", "sessions")) {
30233
+ function liveSessions(workspaceRoot, sessionsDir = path30.join(os8.homedir(), ".claude", "sessions")) {
30168
30234
  let names;
30169
30235
  try {
30170
30236
  names = fs31.readdirSync(sessionsDir).filter((n) => n.endsWith(".json"));
30171
30237
  } catch {
30172
30238
  return [];
30173
30239
  }
30174
- const pids = [];
30240
+ const sessions = [];
30175
30241
  for (const name of names) {
30176
30242
  try {
30177
30243
  const s = JSON.parse(fs31.readFileSync(path30.join(sessionsDir, name), "utf8"));
30178
30244
  const cwd = String(s.cwd ?? "");
30179
30245
  const inWorkspace = cwd === workspaceRoot || cwd.startsWith(workspaceRoot + path30.sep);
30180
- if (!inWorkspace || s.sessionId === sessionId || !Number.isInteger(s.pid))
30246
+ if (!inWorkspace || !Number.isInteger(s.pid))
30181
30247
  continue;
30182
30248
  process.kill(s.pid, 0);
30183
- 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
+ });
30184
30255
  } catch {
30185
30256
  }
30186
30257
  }
30187
- 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 };
30188
30346
  }
30189
30347
  function idleStopFeature(input2) {
30190
30348
  const { feature: feature2, workspaceRoot, sessionId, reason: reason2, entrypoint, sessionsDir } = input2;
@@ -30509,7 +30667,7 @@ async function teardownNotes(feature2) {
30509
30667
  graduated
30510
30668
  };
30511
30669
  }
30512
- var _CONTINUING_REASONS, REAL_PROXY_OPS;
30670
+ var _CONTINUING_REASONS, LABEL_FEATURE2, LABEL_WORKDIR, REAL_PROXY_OPS;
30513
30671
  var init_lifecycle = __esm({
30514
30672
  "../../packages/sandbox-core/dist/lifecycle.js"() {
30515
30673
  "use strict";
@@ -30520,17 +30678,22 @@ var init_lifecycle = __esm({
30520
30678
  init_compose_file();
30521
30679
  init_config();
30522
30680
  init_docker();
30681
+ init_dot_dir();
30523
30682
  init_errors();
30524
30683
  init_feature_routing();
30525
30684
  init_generate();
30685
+ init_global_config();
30526
30686
  init_graduate();
30527
30687
  init_k8s();
30528
30688
  init_notes();
30689
+ init_proc();
30529
30690
  init_reporter();
30530
30691
  init_secrets();
30531
30692
  init_slots();
30532
30693
  init_strategies();
30533
30694
  _CONTINUING_REASONS = /* @__PURE__ */ new Set(["clear", "resume"]);
30695
+ LABEL_FEATURE2 = "com.sandbox.local.feature";
30696
+ LABEL_WORKDIR = "com.docker.compose.project.working_dir";
30534
30697
  REAL_PROXY_OPS = {
30535
30698
  generate: () => generateCaddyfile(),
30536
30699
  slots: () => getAllSlots(),
@@ -31429,6 +31592,7 @@ async function run3(args) {
31429
31592
  console.log(`
31430
31593
  ${Colors.BOLD}=== sandbox up: ${feature2} ===${Colors.RESET}
31431
31594
  `);
31595
+ if (args.dev) process.env.SANDBOX_DEV = "1";
31432
31596
  printSummary(await up(feature2, workspace, { signal: interruptSignal() }));
31433
31597
  }
31434
31598
  var init_up = __esm({
@@ -31949,6 +32113,17 @@ async function run8(args) {
31949
32113
  stopIfIdle();
31950
32114
  return;
31951
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
+ }
31952
32127
  const dotenv = loadDotenv(process.cwd());
31953
32128
  const feature2 = args.feature || dotenv["FEATURE"];
31954
32129
  if (!feature2) {
@@ -32507,7 +32682,7 @@ models:
32507
32682
  claude-sonnet-5: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 }
32508
32683
  claude-haiku-4-5: { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.10 }
32509
32684
  `;
32510
- SANDBOX_MCP_SERVER = "sandbox";
32685
+ SANDBOX_MCP_SERVER = "werkmeister";
32511
32686
  REGISTRY_DEFAULTS = {
32512
32687
  archetypes: {
32513
32688
  "node-web": {
@@ -32560,16 +32735,19 @@ function discoverFragments(reposRoot) {
32560
32735
  continue;
32561
32736
  if (fs40.statSync(path39.join(reposRoot, repo, ".git"), { throwIfNoEntry: false })?.isFile())
32562
32737
  continue;
32563
- let doc;
32564
- try {
32565
- doc = (0, import_yaml2.parse)(fs40.readFileSync(file, "utf8"));
32566
- } catch (e) {
32567
- throw new RegistryError(`${file}: ${e instanceof Error ? e.message : String(e)}`);
32568
- }
32569
- out.push({ repo, path: file, doc: isDict7(doc) ? doc : {} });
32738
+ out.push(readFragment(repo, file));
32570
32739
  }
32571
32740
  return out;
32572
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
+ }
32573
32751
  function expandPlaceholders(value, paths) {
32574
32752
  if (typeof value === "string") {
32575
32753
  return value.replace(PLACEHOLDER, (_m, key) => paths[key]);
@@ -32731,6 +32909,18 @@ function readLocalOverlay(localPath) {
32731
32909
  throw new RegistryError(`${localPath}: must be a map of registry keys`);
32732
32910
  return doc;
32733
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
+ }
32734
32924
  function syncRegistry(opts = {}) {
32735
32925
  const label = "config services.yaml";
32736
32926
  const reposRoot = opts.reposRoot ?? getReposRoot();
@@ -37628,8 +37818,8 @@ var require_utils = __commonJS({
37628
37818
  }
37629
37819
  return ind;
37630
37820
  }
37631
- function removeDotSegments(path73) {
37632
- let input2 = path73;
37821
+ function removeDotSegments(path74) {
37822
+ let input2 = path74;
37633
37823
  const output = [];
37634
37824
  let nextSlash = -1;
37635
37825
  let len = 0;
@@ -38039,8 +38229,8 @@ var require_schemes = __commonJS({
38039
38229
  }
38040
38230
  if (wsComponent.resourceName) {
38041
38231
  const queryIndex = wsComponent.resourceName.indexOf("?");
38042
- const path73 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
38043
- 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;
38044
38234
  wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
38045
38235
  wsComponent.resourceName = void 0;
38046
38236
  }
@@ -49512,10 +49702,10 @@ var require_lib3 = __commonJS({
49512
49702
  exports.analyse = analyse;
49513
49703
  var detectFile = (filepath, opts = {}) => new Promise((resolve13, reject) => {
49514
49704
  let fd;
49515
- const fs65 = (0, node_1.default)();
49705
+ const fs66 = (0, node_1.default)();
49516
49706
  const handler = (err, buffer) => {
49517
49707
  if (fd) {
49518
- fs65.closeSync(fd);
49708
+ fs66.closeSync(fd);
49519
49709
  }
49520
49710
  if (err) {
49521
49711
  reject(err);
@@ -49527,9 +49717,9 @@ var require_lib3 = __commonJS({
49527
49717
  };
49528
49718
  const sampleSize = (opts === null || opts === void 0 ? void 0 : opts.sampleSize) || 0;
49529
49719
  if (sampleSize > 0) {
49530
- fd = fs65.openSync(filepath, "r");
49720
+ fd = fs66.openSync(filepath, "r");
49531
49721
  let sample = Buffer.allocUnsafe(sampleSize);
49532
- fs65.read(fd, sample, 0, sampleSize, opts.offset, (err, bytesRead) => {
49722
+ fs66.read(fd, sample, 0, sampleSize, opts.offset, (err, bytesRead) => {
49533
49723
  if (err) {
49534
49724
  handler(err, null);
49535
49725
  } else {
@@ -49541,22 +49731,22 @@ var require_lib3 = __commonJS({
49541
49731
  });
49542
49732
  return;
49543
49733
  }
49544
- fs65.readFile(filepath, handler);
49734
+ fs66.readFile(filepath, handler);
49545
49735
  });
49546
49736
  exports.detectFile = detectFile;
49547
49737
  var detectFileSync = (filepath, opts = {}) => {
49548
- const fs65 = (0, node_1.default)();
49738
+ const fs66 = (0, node_1.default)();
49549
49739
  if (opts && opts.sampleSize) {
49550
- const fd = fs65.openSync(filepath, "r");
49740
+ const fd = fs66.openSync(filepath, "r");
49551
49741
  let sample = Buffer.allocUnsafe(opts.sampleSize);
49552
- const bytesRead = fs65.readSync(fd, sample, 0, opts.sampleSize, opts.offset);
49742
+ const bytesRead = fs66.readSync(fd, sample, 0, opts.sampleSize, opts.offset);
49553
49743
  if (bytesRead < opts.sampleSize) {
49554
49744
  sample = sample.subarray(0, bytesRead);
49555
49745
  }
49556
- fs65.closeSync(fd);
49746
+ fs66.closeSync(fd);
49557
49747
  return (0, exports.detect)(sample);
49558
49748
  }
49559
- return (0, exports.detect)(fs65.readFileSync(filepath));
49749
+ return (0, exports.detect)(fs66.readFileSync(filepath));
49560
49750
  };
49561
49751
  exports.detectFileSync = detectFileSync;
49562
49752
  exports.default = {
@@ -54253,7 +54443,7 @@ function cacheSet(key, v) {
54253
54443
  await fs45.writeFile(tmp, JSON.stringify(store), "utf-8");
54254
54444
  await fs45.rename(tmp, file);
54255
54445
  } catch (e) {
54256
- console.error(`[sandbox-workspace] cache write failed for ${key}:`, e.message);
54446
+ console.error(`[werkmeister] cache write failed for ${key}:`, e.message);
54257
54447
  }
54258
54448
  });
54259
54449
  writeChain = run34.catch(() => {
@@ -54266,7 +54456,7 @@ function runBatchOnce(keys, job) {
54266
54456
  return;
54267
54457
  for (const k of fresh)
54268
54458
  inFlight.add(k);
54269
- void Promise.resolve().then(() => job(fresh)).catch((e) => console.error("[sandbox-workspace] background cache refresh failed:", e.message)).finally(() => {
54459
+ void Promise.resolve().then(() => job(fresh)).catch((e) => console.error("[werkmeister] background cache refresh failed:", e.message)).finally(() => {
54270
54460
  for (const k of fresh)
54271
54461
  inFlight.delete(k);
54272
54462
  });
@@ -54323,8 +54513,8 @@ function parseRect(size, position) {
54323
54513
  const [x, y] = (position ?? "60,60").split(",").map(Number);
54324
54514
  return { width, height: height2, x, y };
54325
54515
  }
54326
- async function wdFetch(port, path73, method, body) {
54327
- 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}`, {
54328
54518
  method,
54329
54519
  headers: body !== void 0 ? { "Content-Type": "application/json" } : void 0,
54330
54520
  body: body !== void 0 ? JSON.stringify(body) : void 0
@@ -54332,7 +54522,7 @@ async function wdFetch(port, path73, method, body) {
54332
54522
  const data = await r.json().catch(() => ({}));
54333
54523
  if (!r.ok || data?.value?.error) {
54334
54524
  const msg = data?.value?.message || data?.value?.error || `HTTP ${r.status}`;
54335
- throw new Error(`safaridriver ${method} ${path73} failed: ${msg}`);
54525
+ throw new Error(`safaridriver ${method} ${path74} failed: ${msg}`);
54336
54526
  }
54337
54527
  return data.value;
54338
54528
  }
@@ -55170,11 +55360,131 @@ var init_issue_snapshot = __esm({
55170
55360
  }
55171
55361
  });
55172
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
+
55173
55483
  // ../../packages/sandbox-core/dist/pr_lifecycle.js
55174
55484
  import { execFile } from "node:child_process";
55175
- import * as fs49 from "node:fs";
55485
+ import * as fs50 from "node:fs";
55176
55486
  import * as os16 from "node:os";
55177
- import * as path57 from "node:path";
55487
+ import * as path58 from "node:path";
55178
55488
  async function httpStatus(url) {
55179
55489
  try {
55180
55490
  return (await fetch(url, { redirect: "manual", signal: AbortSignal.timeout(5e3) })).status;
@@ -55191,27 +55501,51 @@ function defaultDeps() {
55191
55501
  archetypes: loadArchetypes(),
55192
55502
  repos: loadReposConfig(),
55193
55503
  tracker: issueTracker(),
55194
- reviewStatus: String(overrides["issue_review_status"] ?? "In Review"),
55195
- doneStatus: String(overrides["issue_done_status"] ?? (loadIssueTracker() === "github" ? "closed" : "Done")),
55504
+ statuses: resolveIssueStatuses(overrides, loadIssueTracker()),
55196
55505
  commitRecord: commitAndPush,
55197
55506
  remove: (name) => removeWorkspace(name, { closedOut: true }),
55198
55507
  terminals: (ws) => openTerminalSessions(ws),
55199
55508
  sourceOf: async (dir) => (await worktreeSources([dir]))[0] ?? null,
55200
- 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
+ }
55201
55516
  };
55202
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
+ }
55203
55537
  function prFileDir(ws) {
55204
55538
  let names;
55205
55539
  try {
55206
- names = fs49.readdirSync(path57.join(ws, "stages")).sort();
55540
+ names = fs50.readdirSync(path58.join(ws, "stages")).sort();
55207
55541
  } catch {
55208
- return path57.join(dotDir(ws), "notes");
55542
+ return path58.join(dotDir(ws), "notes");
55209
55543
  }
55210
55544
  const dir = names.find((n) => /^\d+_propose$/.test(n)) ?? names.find((n) => /^\d+_ship$/.test(n));
55211
- return dir ? path57.join(ws, "stages", dir, "output") : null;
55545
+ return dir ? path58.join(ws, "stages", dir, "output") : null;
55212
55546
  }
55213
55547
  function parsePrFile(p) {
55214
- 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"));
55215
55549
  let fm = null;
55216
55550
  let yamlError = "";
55217
55551
  try {
@@ -55225,7 +55559,7 @@ function parsePrFile(p) {
55225
55559
  const heading2 = /^#\s+(.+)$/m.exec(m[2]);
55226
55560
  return {
55227
55561
  path: p,
55228
- dir: path57.basename(p).replace(/^pr-/, "").replace(/\.md$/, ""),
55562
+ dir: path58.basename(p).replace(/^pr-/, "").replace(/\.md$/, ""),
55229
55563
  fm,
55230
55564
  title: heading2 ? heading2[1].trim() : "",
55231
55565
  body: heading2 ? m[2].slice(heading2.index + heading2[0].length).replace(/^\s*\n/, "") : m[2]
@@ -55237,10 +55571,10 @@ function readPrFiles(ws) {
55237
55571
  return [];
55238
55572
  let names = [];
55239
55573
  try {
55240
- names = fs49.readdirSync(dir).filter((n) => /^pr-.+\.md$/.test(n)).sort();
55574
+ names = fs50.readdirSync(dir).filter((n) => /^pr-.+\.md$/.test(n)).sort();
55241
55575
  } catch {
55242
55576
  }
55243
- return names.map((n) => parsePrFile(path57.join(dir, n)));
55577
+ return names.map((n) => parsePrFile(path58.join(dir, n)));
55244
55578
  }
55245
55579
  function writePrFields(file, fields) {
55246
55580
  updateNoteStatus(file.path, fields);
@@ -55255,18 +55589,18 @@ function passTable(text) {
55255
55589
  }
55256
55590
  function stagesCheck(ws) {
55257
55591
  const prDir = prFileDir(ws);
55258
- if (!fs49.existsSync(path57.join(ws, "stages"))) {
55259
- 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));
55260
55594
  return {
55261
55595
  name: "stages",
55262
55596
  ok: open.length === 0,
55263
55597
  detail: open.length ? `open decision notes: ${open.join(", ")}` : "no open decisions"
55264
55598
  };
55265
55599
  }
55266
- const own = prDir ? path57.basename(path57.dirname(prDir)) : "";
55267
- 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) => {
55268
55602
  try {
55269
- 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("."));
55270
55604
  } catch {
55271
55605
  return true;
55272
55606
  }
@@ -55278,14 +55612,14 @@ function stagesCheck(ws) {
55278
55612
  };
55279
55613
  }
55280
55614
  function exerciseFile(ws) {
55281
- if (!fs49.existsSync(path57.join(ws, "stages"))) {
55282
- const p = path57.join(dotDir(ws), "notes", "exercise.md");
55283
- 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;
55284
55618
  }
55285
55619
  for (const name of ["exercise.md", "verify.md"]) {
55286
- for (const d of fs49.readdirSync(path57.join(ws, "stages")).sort()) {
55287
- const p = path57.join(ws, "stages", d, "output", name);
55288
- 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))
55289
55623
  return p;
55290
55624
  }
55291
55625
  }
@@ -55313,7 +55647,7 @@ function mapTouched(files, repo) {
55313
55647
  }
55314
55648
  function readEnv(ws, key) {
55315
55649
  try {
55316
- 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/)) {
55317
55651
  const eq = line.indexOf("=");
55318
55652
  if (eq > 0 && line.slice(0, eq).trim() === key)
55319
55653
  return line.slice(eq + 1).trim();
@@ -55327,14 +55661,24 @@ async function runGate(ws, file, deps = defaultDeps()) {
55327
55661
  const add = (name, ok, detail) => checks.push({ name, ok, detail });
55328
55662
  const table = passTable(file.body);
55329
55663
  add("acceptance", table === null, table ? `PR file ${table}` : "all PASS");
55330
- const dir = path57.join(ws, file.dir);
55664
+ const dir = path58.join(ws, file.dir);
55331
55665
  const status2 = await deps.run("git", ["-C", dir, "status", "--porcelain"]);
55332
55666
  add("clean", status2.code === 0 && status2.stdout.trim() === "", status2.code ? status2.stderr.trim() : status2.stdout.trim() ? `uncommitted:
55333
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
+ }
55334
55679
  const cfg = loadConfigFile(ws, null);
55335
55680
  const feature2 = String(cfg["feature"] ?? readEnv(ws, "FEATURE")).trim();
55336
55681
  const local = new Set(String(cfg["services"] ?? "").split(",").map((s) => s.trim()).filter(Boolean));
55337
- const repo = deps.repos[file.dir] ?? {};
55338
55682
  const ps = await deps.run("docker", ["ps", "--format", "{{.Names}}"]);
55339
55683
  const running = new Set(ps.stdout.split("\n").map((s) => s.trim()).filter(Boolean));
55340
55684
  const containerFor = (svc) => {
@@ -55352,7 +55696,7 @@ ${status2.stdout.trimEnd()}` : "clean");
55352
55696
  const touched = diff.code === 0 ? mapTouched(diff.stdout.split("\n").filter(Boolean), repo) : [];
55353
55697
  const skips = file.fm["skip_tests"] ?? {};
55354
55698
  for (const svc of touched) {
55355
- const cmd = serviceTestCommand(svc, deps.services[svc], deps.archetypes);
55699
+ const cmd = serviceTestCommand(svc, services[svc], deps.archetypes);
55356
55700
  const why = reason(skips[svc]);
55357
55701
  if (!cmd) {
55358
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> }`");
@@ -55363,16 +55707,17 @@ ${status2.stdout.trimEnd()}` : "clean");
55363
55707
  add(`test ${svc}`, Boolean(why), why ? `skipped: ${why}` : "no running container for this repo \u2014 run `sandbox up`");
55364
55708
  continue;
55365
55709
  }
55710
+ touchExecMarker(ws);
55366
55711
  const res = await deps.run("docker", ["exec", container, "sh", "-c", cmd]);
55367
55712
  add(`test ${svc}`, res.code === 0, `${cmd} \u2192 exit ${res.code}${res.code ? `
55368
55713
  ${(res.stdout + res.stderr).trimEnd().split("\n").slice(-15).join("\n")}` : ""}`);
55369
55714
  }
55370
55715
  const evidence = exerciseFile(ws);
55371
- const evidenceProblem = evidence ? passTable(fs49.readFileSync(evidence, "utf8")) : "is missing";
55372
- 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");
55373
55718
  const slot = Number(readEnv(ws, "SANDBOX_SLOT"));
55374
55719
  for (const svc of touched.filter((s) => local.has(s))) {
55375
- const def = deps.services[svc] ?? {};
55720
+ const def = services[svc] ?? {};
55376
55721
  if (!running.has(`${feature2}-${svc}`)) {
55377
55722
  add(`baseline ${svc}`, false, "container not running \u2014 run `sandbox up`");
55378
55723
  continue;
@@ -55393,6 +55738,7 @@ ${(res.stdout + res.stderr).trimEnd().split("\n").slice(-15).join("\n")}` : ""}`
55393
55738
  else if (!container)
55394
55739
  add("release", false, "no running container for this repo \u2014 run `sandbox up`");
55395
55740
  else {
55741
+ touchExecMarker(ws);
55396
55742
  const res = await deps.run("docker", ["exec", container, "sh", "-c", repo["release_check"]]);
55397
55743
  add("release", res.code === 0, `${repo["release_check"]} \u2192 exit ${res.code}${res.code ? `
55398
55744
  ${(res.stdout + res.stderr).trimEnd().split("\n").slice(-5).join("\n")}` : ""}`);
@@ -55439,7 +55785,7 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55439
55785
  lines.push(...formatGate(f.dir, g));
55440
55786
  continue;
55441
55787
  }
55442
- const dir = path57.join(ws, f.dir);
55788
+ const dir = path58.join(ws, f.dir);
55443
55789
  const branch = String(f.fm["branch"] ?? "");
55444
55790
  const push = await deps.run("git", ["-C", dir, "push", "-u", "origin", branch]);
55445
55791
  if (push.code !== 0) {
@@ -55463,8 +55809,8 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55463
55809
  ], dir);
55464
55810
  let url = existing2.code === 0 ? existing2.stdout.trim() : "";
55465
55811
  if (!url) {
55466
- const bodyFile = path57.join(fs49.mkdtempSync(path57.join(os16.tmpdir(), "sandbox-pr-")), "body.md");
55467
- 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);
55468
55814
  const created = await deps.run("gh", [
55469
55815
  "pr",
55470
55816
  "create",
@@ -55479,7 +55825,7 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55479
55825
  "--body-file",
55480
55826
  bodyFile
55481
55827
  ], dir);
55482
- fs49.rmSync(path57.dirname(bodyFile), { recursive: true, force: true });
55828
+ fs50.rmSync(path58.dirname(bodyFile), { recursive: true, force: true });
55483
55829
  url = created.stdout.trim().split("\n").pop() ?? "";
55484
55830
  if (created.code !== 0 || !url.startsWith("http")) {
55485
55831
  ok = false;
@@ -55488,53 +55834,76 @@ async function openPrs(ws, only, deps = defaultDeps()) {
55488
55834
  }
55489
55835
  }
55490
55836
  writePrFields(f, { status: "open", url, ...await deps.session(ws) });
55491
- opened.push(path57.relative(ws, f.path));
55837
+ opened.push(path58.relative(ws, f.path));
55492
55838
  lines.push(`${f.dir}: open ${url}`);
55493
55839
  const ref = issueIdOf(loadConfigFile(ws, null));
55494
55840
  if (ref) {
55495
- try {
55496
- await deps.tracker.updateIssue(ref, { status: deps.reviewStatus });
55497
- } catch (e) {
55498
- lines.push(` (issue ${ref} not moved to ${deps.reviewStatus}: ${e.message})`);
55499
- }
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}`);
55500
55846
  }
55501
55847
  }
55502
55848
  if (opened.length)
55503
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));
55504
55852
  return { ok, lines };
55505
55853
  }
55506
55854
  async function refreshPrStatus(ws, deps = defaultDeps()) {
55507
55855
  const lines = [];
55508
55856
  const changed = [];
55857
+ const seen = {
55858
+ shipped: [],
55859
+ merged: [],
55860
+ closed: [],
55861
+ changesRequested: [],
55862
+ reReview: []
55863
+ };
55509
55864
  for (const f of readPrFiles(ws)) {
55510
55865
  const before = statusOf(f);
55866
+ const reviewBefore = f.fm["review"];
55511
55867
  if (before === "open" && f.fm["url"]) {
55512
55868
  const v = await deps.run("gh", [
55513
55869
  "pr",
55514
55870
  "view",
55515
55871
  String(f.fm["url"]),
55516
55872
  "--json",
55517
- "state,mergeCommit"
55873
+ "state,mergeCommit,reviewDecision,latestReviews"
55518
55874
  ]);
55519
55875
  if (v.code !== 0) {
55520
55876
  lines.push(`${f.dir}: gh pr view failed \u2014 ${v.stderr.trim()}`);
55521
55877
  continue;
55522
55878
  }
55523
55879
  const view2 = JSON.parse(v.stdout);
55524
- 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") {
55525
55883
  writePrFields(f, { status: "merged", merge_sha: view2.mergeCommit?.oid ?? "" });
55526
- else if (view2.state === "CLOSED")
55884
+ seen.merged.push(url);
55885
+ } else if (view2.state === "CLOSED") {
55527
55886
  writePrFields(f, {
55528
55887
  status: "closed",
55529
55888
  reason: reason(f.fm["reason"]) || "closed on GitHub"
55530
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
+ }
55531
55900
  }
55532
55901
  if (statusOf(f) === "merged") {
55533
55902
  const repo = deps.repos[f.dir] ?? {};
55534
55903
  if (repo["ship"] !== "tag") {
55535
55904
  writePrFields(f, { status: "shipped" });
55536
55905
  } else {
55537
- const dir = path57.join(ws, f.dir);
55906
+ const dir = path58.join(ws, f.dir);
55538
55907
  await deps.run("git", ["-C", dir, "fetch", "-q", "--tags", "origin"]);
55539
55908
  const tags = await deps.run("git", [
55540
55909
  "-C",
@@ -55552,16 +55921,43 @@ async function refreshPrStatus(ws, deps = defaultDeps()) {
55552
55921
  }
55553
55922
  }
55554
55923
  const after = statusOf(f);
55555
- if (after !== before)
55556
- 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));
55557
55928
  lines.push(`${f.dir}: ${after}${f.fm["release"] ? ` (${f.fm["release"]})` : ""}${f.fm["url"] ? ` ${f.fm["url"]}` : ""}`);
55558
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));
55559
55935
  if (changed.length)
55560
55936
  lines.push((await deps.commitRecord(ws, "chore: record PR status", changed)).message);
55561
55937
  return { lines, changed: changed.length > 0 };
55562
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
+ }
55563
55959
  async function callbackFields(ws, env) {
55564
- 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;
55565
55961
  if (!session)
55566
55962
  return {};
55567
55963
  const host = env["CLAUDE_CODE_HOST_SESSION_ID"];
@@ -55579,23 +55975,23 @@ function callbackMessage(status2, url, release) {
55579
55975
  function takeLock(file) {
55580
55976
  for (let i = 0; i < 2; i++) {
55581
55977
  try {
55582
- fs49.closeSync(fs49.openSync(file, "wx"));
55978
+ fs50.closeSync(fs50.openSync(file, "wx"));
55583
55979
  return true;
55584
55980
  } catch (e) {
55585
55981
  if (e.code !== "EEXIST")
55586
55982
  throw e;
55587
55983
  try {
55588
- if (Date.now() - fs49.statSync(file).mtimeMs < LOCK_STALE_MS)
55984
+ if (Date.now() - fs50.statSync(file).mtimeMs < LOCK_STALE_MS)
55589
55985
  return false;
55590
- fs49.rmSync(file, { force: true });
55986
+ fs50.rmSync(file, { force: true });
55591
55987
  } catch {
55592
55988
  }
55593
55989
  }
55594
55990
  }
55595
55991
  return false;
55596
55992
  }
55597
- async function claimCallbacks(workspaces, opts, deps = defaultDeps(), lockFile = path57.join(sandboxHome(), "pr-callbacks.lock")) {
55598
- 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 });
55599
55995
  if (!takeLock(lockFile))
55600
55996
  return { claimed: [], lines: ["another PR watch pass is running"] };
55601
55997
  const claimed2 = [];
@@ -55614,7 +56010,7 @@ async function claimCallbacks(workspaces, opts, deps = defaultDeps(), lockFile =
55614
56010
  continue;
55615
56011
  if (before.some((f) => statusOf(f) === "open" || statusOf(f) === "merged"))
55616
56012
  for (const l of (await refreshPrStatus(ws, deps)).lines)
55617
- lines.push(`${path57.basename(ws)}/${l}`);
56013
+ lines.push(`${path58.basename(ws)}/${l}`);
55618
56014
  const changed = [];
55619
56015
  for (const f of callbacks()) {
55620
56016
  const status2 = statusOf(f);
@@ -55632,23 +56028,23 @@ async function claimCallbacks(workspaces, opts, deps = defaultDeps(), lockFile =
55632
56028
  message
55633
56029
  });
55634
56030
  writePrFields(f, SETTLED.has(status2) ? { session: void 0, host_session: void 0, notified: void 0 } : { notified: status2 });
55635
- changed.push(path57.relative(ws, f.path));
56031
+ changed.push(path58.relative(ws, f.path));
55636
56032
  }
55637
56033
  if (changed.length)
55638
56034
  lines.push((await deps.commitRecord(ws, "chore: record PR callback", changed)).message);
55639
56035
  }
55640
56036
  } finally {
55641
- fs49.rmSync(lockFile, { force: true });
56037
+ fs50.rmSync(lockFile, { force: true });
55642
56038
  }
55643
56039
  return { claimed: claimed2, lines };
55644
56040
  }
55645
56041
  async function deliverHeadless(cb) {
55646
- const name = path57.basename(cb.ws);
56042
+ const name = path58.basename(cb.ws);
55647
56043
  if ((await resolveSessionSource(name, cb.session)).sessionId !== cb.session)
55648
56044
  throw new WorkspaceError(`session ${cb.session} is gone`);
55649
56045
  await sendToClaudeSession(name, cb.message, cb.session);
55650
56046
  }
55651
- async function watchPrs(opts, deps = defaultDeps(), send = deliverHeadless) {
56047
+ async function watchPrs(opts, deps = defaultDeps(), send = deliverHeadless, idleLock = path58.join(sandboxHome(), "idle-stop.lock")) {
55652
56048
  const { workspaces } = await listWorkspaces();
55653
56049
  const { claimed: claimed2, lines } = await claimCallbacks(workspaces.map((w) => w.path), opts, deps);
55654
56050
  const desktop = [];
@@ -55664,6 +56060,15 @@ async function watchPrs(opts, deps = defaultDeps(), send = deliverHeadless) {
55664
56060
  lines.push(`${cb.dir}: could not resume session ${cb.session}: ${e.message}`);
55665
56061
  }
55666
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");
55667
56072
  return { ok: true, lines, desktop };
55668
56073
  }
55669
56074
  async function adoptPr(ws, opts, deps = defaultDeps()) {
@@ -55677,8 +56082,8 @@ async function adoptPr(ws, opts, deps = defaultDeps()) {
55677
56082
  throw new WorkspaceError(`Name the repo \u2014 this workspace has ${wts.map((w) => w.repo).join(", ") || "no code worktree"}.`);
55678
56083
  repo = wts[0].repo;
55679
56084
  }
55680
- const file = path57.join(dir, `pr-${repo}.md`);
55681
- if (fs49.existsSync(file))
56085
+ const file = path58.join(dir, `pr-${repo}.md`);
56086
+ if (fs50.existsSync(file))
55682
56087
  throw new WorkspaceError(`${file} already exists \u2014 remove it first to adopt.`);
55683
56088
  let fm;
55684
56089
  let title;
@@ -55690,7 +56095,7 @@ async function adoptPr(ws, opts, deps = defaultDeps()) {
55690
56095
  } else {
55691
56096
  if (!opts.pr)
55692
56097
  throw new WorkspaceError("Give a PR number or URL, or --no-pr <reason>.");
55693
- 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));
55694
56099
  if (v.code !== 0)
55695
56100
  throw new WorkspaceError(`gh pr view ${opts.pr} failed \u2014 ${v.stderr.trim()}`);
55696
56101
  const pr = JSON.parse(v.stdout);
@@ -55708,7 +56113,7 @@ async function adoptPr(ws, opts, deps = defaultDeps()) {
55708
56113
  };
55709
56114
  title = pr.title;
55710
56115
  }
55711
- fs49.writeFileSync(file, `---
56116
+ fs50.writeFileSync(file, `---
55712
56117
  ${import_yaml14.default.stringify(fm)}---
55713
56118
  # ${title}
55714
56119
 
@@ -55717,7 +56122,7 @@ Adopted with \`sandbox pr adopt\`: the work landed outside this workspace.
55717
56122
  const lines = [`${repo}: adopted as ${fm["status"]}`];
55718
56123
  if (fm["status"] !== "closed")
55719
56124
  lines.push(...(await refreshPrStatus(ws, deps)).lines);
55720
- 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);
55721
56126
  return { ok: true, lines };
55722
56127
  }
55723
56128
  function lifecycleOf(ws) {
@@ -55733,7 +56138,7 @@ function lifecycleOf(ws) {
55733
56138
  }));
55734
56139
  const all = (set) => prs.every((p) => set.has(p.status));
55735
56140
  let state;
55736
- if (fs49.existsSync(path57.join(ws, "closeout.md")))
56141
+ if (fs50.existsSync(path58.join(ws, "closeout.md")))
55737
56142
  state = "closed-out";
55738
56143
  else if (!prs.length)
55739
56144
  state = "working";
@@ -55752,7 +56157,7 @@ function lifecycleOf(ws) {
55752
56157
  return { state, prs };
55753
56158
  }
55754
56159
  async function removalBlockers(ws, worktrees, run34 = execRun) {
55755
- if (fs49.existsSync(path57.join(ws, "closeout.md")))
56160
+ if (fs50.existsSync(path58.join(ws, "closeout.md")))
55756
56161
  return [];
55757
56162
  const files = readPrFiles(ws);
55758
56163
  const out = [];
@@ -55797,7 +56202,7 @@ function abandonWorkspace(ws, why) {
55797
56202
  for (const f of files)
55798
56203
  if (!SETTLED.has(statusOf(f)))
55799
56204
  writePrFields(f, { status: "abandoned", reason: why });
55800
- 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}`));
55801
56206
  }
55802
56207
  async function openTerminalSessions(ws, run34 = execRun) {
55803
56208
  const ps = await run34("ps", ["-axo", "pid=,tty=,comm="]);
@@ -55806,7 +56211,7 @@ async function openTerminalSessions(ws, run34 = execRun) {
55806
56211
  const ttys = /* @__PURE__ */ new Map();
55807
56212
  for (const line of ps.stdout.split("\n")) {
55808
56213
  const m = /^\s*(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
55809
- if (m && path57.basename(m[3]) === "claude" && m[2] !== "??" && m[2] !== "?")
56214
+ if (m && path58.basename(m[3]) === "claude" && m[2] !== "??" && m[2] !== "?")
55810
56215
  ttys.set(m[1], m[2]);
55811
56216
  }
55812
56217
  if (!ttys.size)
@@ -55819,7 +56224,7 @@ async function openTerminalSessions(ws, run34 = execRun) {
55819
56224
  pid = line.slice(1);
55820
56225
  else if (line.startsWith("n") && ttys.has(pid)) {
55821
56226
  const cwd = line.slice(1);
55822
- if (cwd === ws || cwd.startsWith(ws + path57.sep))
56227
+ if (cwd === ws || cwd.startsWith(ws + path58.sep))
55823
56228
  out.push(`terminal session: pid ${pid} on ${ttys.get(pid)} in ${cwd} \u2014 /exit it`);
55824
56229
  }
55825
56230
  }
@@ -55845,28 +56250,26 @@ async function closeOut(name, opts = {}, deps = defaultDeps()) {
55845
56250
  const ref = issueIdOf(cfg);
55846
56251
  let issueLine = "none";
55847
56252
  if (ref) {
55848
- const done = deps.doneStatus.toLowerCase();
55849
- const current = (await deps.tracker.fetchIssue(ref))?.status ?? "";
55850
- 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 ?? "";
55851
56260
  issueLine = `${ref} left ${current || "as is"} (abandoned)`;
55852
- } else if ([done, "closed", "done"].includes(current.toLowerCase())) {
55853
- issueLine = `${ref} already ${current}`;
55854
56261
  } else {
55855
- const files2 = readPrFiles(ws);
55856
- const shipped = files2.filter((f) => statusOf(f) === "shipped").map((f) => `${f.fm["release"] ?? "merge"} \u2014 ${f.fm["url"]}`);
55857
- await deps.tracker.updateIssue(ref, {
55858
- status: deps.doneStatus,
55859
- comment: `Shipped: ${shipped.join("; ") || "no PRs"}`
55860
- });
55861
- 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)`;
55862
56265
  }
55863
56266
  lines.push(`Issue: ${issueLine}`);
55864
56267
  }
55865
56268
  const files = readPrFiles(ws);
55866
- 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));
55867
56270
  const branches = [];
55868
56271
  for (const f of abandoned ? [] : files.filter((x) => (statusOf(x) === "merged" || statusOf(x) === "shipped") && !x.fm["adopted"])) {
55869
- const src = await deps.sourceOf(path57.join(ws, f.dir));
56272
+ const src = await deps.sourceOf(path58.join(ws, f.dir));
55870
56273
  if (src && f.fm["branch"])
55871
56274
  branches.push({ src, branch: String(f.fm["branch"]) });
55872
56275
  }
@@ -55895,7 +56298,7 @@ async function closeOut(name, opts = {}, deps = defaultDeps()) {
55895
56298
  const command = `sandbox close-out ${name}${why ? " --abandon" : ""}`;
55896
56299
  return { command, output: lines.filter(Boolean).join("\n"), path: ws, terminals };
55897
56300
  }
55898
- 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;
55899
56302
  var init_pr_lifecycle = __esm({
55900
56303
  "../../packages/sandbox-core/dist/pr_lifecycle.js"() {
55901
56304
  "use strict";
@@ -55906,8 +56309,11 @@ var init_pr_lifecycle = __esm({
55906
56309
  init_config();
55907
56310
  init_global_config();
55908
56311
  init_graduate();
56312
+ init_issue_status();
55909
56313
  init_issue_tracker();
56314
+ init_lifecycle();
55910
56315
  init_notes();
56316
+ init_registry_sync();
55911
56317
  init_sandbox();
55912
56318
  init_slots();
55913
56319
  init_workspace_prompts();
@@ -55927,6 +56333,7 @@ var init_pr_lifecycle = __esm({
55927
56333
  reason = (v) => typeof v === "string" ? v.trim() : "";
55928
56334
  PASS_CELL = /\|\s*PASS\s*\|/;
55929
56335
  FAIL_CELL = /\|\s*FAIL\s*\|/;
56336
+ shippedComment = (files) => `Shipped: ${files.map((f) => `${f.fm["release"] ?? "merge"} \u2014 ${f.fm["url"]}`).join("; ") || "no PRs"}`;
55930
56337
  LOCK_STALE_MS = 15 * 6e4;
55931
56338
  }
55932
56339
  });
@@ -56741,12 +57148,12 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
56741
57148
  }
56742
57149
  return result;
56743
57150
  }
56744
- function findNodeAtLocation(root, path73) {
57151
+ function findNodeAtLocation(root, path74) {
56745
57152
  if (!root) {
56746
57153
  return void 0;
56747
57154
  }
56748
57155
  let node = root;
56749
- for (let segment of path73) {
57156
+ for (let segment of path74) {
56750
57157
  if (typeof segment === "string") {
56751
57158
  if (node.type !== "object" || !Array.isArray(node.children)) {
56752
57159
  return void 0;
@@ -57113,14 +57520,14 @@ var init_parser = __esm({
57113
57520
 
57114
57521
  // ../../node_modules/jsonc-parser/lib/esm/impl/edit.js
57115
57522
  function setProperty(text, originalPath, value, options) {
57116
- const path73 = originalPath.slice();
57523
+ const path74 = originalPath.slice();
57117
57524
  const errors = [];
57118
57525
  const root = parseTree(text, errors);
57119
57526
  let parent = void 0;
57120
57527
  let lastSegment = void 0;
57121
- while (path73.length > 0) {
57122
- lastSegment = path73.pop();
57123
- parent = findNodeAtLocation(root, path73);
57528
+ while (path74.length > 0) {
57529
+ lastSegment = path74.pop();
57530
+ parent = findNodeAtLocation(root, path74);
57124
57531
  if (parent === void 0 && value !== void 0) {
57125
57532
  if (typeof lastSegment === "string") {
57126
57533
  value = { [lastSegment]: value };
@@ -57264,8 +57671,8 @@ var init_edit = __esm({
57264
57671
  });
57265
57672
 
57266
57673
  // ../../node_modules/jsonc-parser/lib/esm/main.js
57267
- function modify(text, path73, value, options) {
57268
- return setProperty(text, path73, value, options);
57674
+ function modify(text, path74, value, options) {
57675
+ return setProperty(text, path74, value, options);
57269
57676
  }
57270
57677
  function applyEdits(text, edits) {
57271
57678
  let sortedEdits = edits.slice(0).sort((a, b) => {
@@ -57347,20 +57754,20 @@ var init_main2 = __esm({
57347
57754
  });
57348
57755
 
57349
57756
  // ../../packages/sandbox-core/dist/plugin_check.js
57350
- import * as fs50 from "node:fs";
57757
+ import * as fs51 from "node:fs";
57351
57758
  import * as os17 from "node:os";
57352
- import * as path58 from "node:path";
57759
+ import * as path59 from "node:path";
57353
57760
  function defaultRepoPluginDir() {
57354
- return path58.join(sandboxRoot(), "plugins", PLUGIN_NAME);
57761
+ return path59.join(sandboxRoot(), "plugins", PLUGIN_NAME);
57355
57762
  }
57356
57763
  function defaultClaudePluginsDir() {
57357
- return path58.join(os17.homedir(), ".claude", "plugins");
57764
+ return path59.join(os17.homedir(), ".claude", "plugins");
57358
57765
  }
57359
57766
  function repoPluginVersion(repoPluginDir) {
57360
- const manifest = path58.join(repoPluginDir, ".claude-plugin", "plugin.json");
57767
+ const manifest = path59.join(repoPluginDir, ".claude-plugin", "plugin.json");
57361
57768
  let data;
57362
57769
  try {
57363
- data = JSON.parse(fs50.readFileSync(manifest, "utf8"));
57770
+ data = JSON.parse(fs51.readFileSync(manifest, "utf8"));
57364
57771
  } catch {
57365
57772
  return null;
57366
57773
  }
@@ -57368,23 +57775,23 @@ function repoPluginVersion(repoPluginDir) {
57368
57775
  return version ? String(version) : null;
57369
57776
  }
57370
57777
  function loadRegistry(claudePluginsDir) {
57371
- const registry = path58.join(claudePluginsDir, "installed_plugins.json");
57778
+ const registry = path59.join(claudePluginsDir, "installed_plugins.json");
57372
57779
  let data;
57373
57780
  try {
57374
- data = JSON.parse(fs50.readFileSync(registry, "utf8"));
57781
+ data = JSON.parse(fs51.readFileSync(registry, "utf8"));
57375
57782
  } catch {
57376
57783
  return null;
57377
57784
  }
57378
57785
  const plugins = typeof data === "object" && data !== null && !Array.isArray(data) ? data["plugins"] : void 0;
57379
57786
  return typeof plugins === "object" && plugins !== null && !Array.isArray(plugins) ? plugins : null;
57380
57787
  }
57381
- function installedPlugin(claudePluginsDir) {
57788
+ function installedPlugin(claudePluginsDir, name = PLUGIN_NAME) {
57382
57789
  const plugins = loadRegistry(claudePluginsDir);
57383
57790
  if (plugins === null) {
57384
57791
  return [null, null];
57385
57792
  }
57386
57793
  for (const [key, entries] of Object.entries(plugins)) {
57387
- if (!key.startsWith(`${PLUGIN_NAME}@`) || !Array.isArray(entries)) {
57794
+ if (!key.startsWith(`${name}@`) || !Array.isArray(entries)) {
57388
57795
  continue;
57389
57796
  }
57390
57797
  for (const entry of entries) {
@@ -57404,7 +57811,7 @@ function skillBodies(skillsDir) {
57404
57811
  const bodies = /* @__PURE__ */ new Map();
57405
57812
  let isDir6 = false;
57406
57813
  try {
57407
- isDir6 = fs50.statSync(skillsDir).isDirectory();
57814
+ isDir6 = fs51.statSync(skillsDir).isDirectory();
57408
57815
  } catch {
57409
57816
  isDir6 = false;
57410
57817
  }
@@ -57413,14 +57820,14 @@ function skillBodies(skillsDir) {
57413
57820
  }
57414
57821
  let entries;
57415
57822
  try {
57416
- entries = fs50.readdirSync(skillsDir, { withFileTypes: true });
57823
+ entries = fs51.readdirSync(skillsDir, { withFileTypes: true });
57417
57824
  } catch {
57418
57825
  return bodies;
57419
57826
  }
57420
57827
  const names = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
57421
57828
  for (const name of names) {
57422
57829
  try {
57423
- 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"));
57424
57831
  } catch {
57425
57832
  continue;
57426
57833
  }
@@ -57454,7 +57861,7 @@ function pluginDriftWarnings(repoPluginDir, claudePluginsDir) {
57454
57861
  }
57455
57862
  let repoSkillsIsDir = false;
57456
57863
  try {
57457
- repoSkillsIsDir = fs50.statSync(path58.join(repoPluginDir, "skills")).isDirectory();
57864
+ repoSkillsIsDir = fs51.statSync(path59.join(repoPluginDir, "skills")).isDirectory();
57458
57865
  } catch {
57459
57866
  repoSkillsIsDir = false;
57460
57867
  }
@@ -57466,11 +57873,16 @@ function pluginDriftWarnings(repoPluginDir, claudePluginsDir) {
57466
57873
  }
57467
57874
  const [installedVersion2, installPath] = installedPlugin(claudePluginsDir);
57468
57875
  if (installPath === null) {
57876
+ if (installedPlugin(claudePluginsDir, LEGACY_PLUGIN_NAME)[1] !== null) {
57877
+ return [
57878
+ `the ${LEGACY_PLUGIN_NAME} plugin was renamed ${PLUGIN_NAME}, and only the old one is installed \u2014 its skills are now ${PLUGIN_NAME}:diagnose, ${PLUGIN_NAME}:status, ${PLUGIN_NAME}:workspace, \u2026 Run \`w9r setup\` to switch over.`
57879
+ ];
57880
+ }
57469
57881
  return [
57470
- `${PLUGIN_NAME} plugin is not installed \u2014 the sandbox-* ops skills (sandbox-diagnose, sandbox-status, sandbox-workspace, \u2026) will not surface in Claude Code sessions. Run \`sandbox 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.`
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.`
57471
57883
  ];
57472
57884
  }
57473
- const drift = skillDrift(path58.join(repoPluginDir, "skills"), path58.join(installPath, "skills"));
57885
+ const drift = skillDrift(path59.join(repoPluginDir, "skills"), path59.join(installPath, "skills"));
57474
57886
  if (!drift.length) {
57475
57887
  return [];
57476
57888
  }
@@ -57483,27 +57895,28 @@ function pluginDriftWarnings(repoPluginDir, claudePluginsDir) {
57483
57895
  }
57484
57896
  return [`${lag}: ${drift.join("; ")} \u2014 ${REFRESH_HINT}.`];
57485
57897
  }
57486
- var PLUGIN_NAME, REFRESH_HINT;
57898
+ var PLUGIN_NAME, LEGACY_PLUGIN_NAME, REFRESH_HINT;
57487
57899
  var init_plugin_check = __esm({
57488
57900
  "../../packages/sandbox-core/dist/plugin_check.js"() {
57489
57901
  "use strict";
57490
57902
  init_define_SANDBOX_BUILD();
57491
57903
  init_config();
57492
- PLUGIN_NAME = "sandbox-tools";
57493
- REFRESH_HINT = "run `sandbox setup` to refresh it so sessions pick up the current sandbox-* ops skills";
57904
+ PLUGIN_NAME = "werkmeister";
57905
+ LEGACY_PLUGIN_NAME = "sandbox-tools";
57906
+ REFRESH_HINT = "run `w9r setup` to refresh it so sessions pick up the current werkmeister ops skills";
57494
57907
  }
57495
57908
  });
57496
57909
 
57497
57910
  // ../../packages/sandbox-core/dist/test_run.js
57498
- import * as fs51 from "node:fs";
57911
+ import * as fs52 from "node:fs";
57499
57912
  import { createRequire } from "node:module";
57500
- import * as path59 from "node:path";
57913
+ import * as path60 from "node:path";
57501
57914
  function playwrightSearchDirs(workspace) {
57502
57915
  const dirs = [workspace];
57503
57916
  try {
57504
- for (const entry of fs51.readdirSync(workspace, { withFileTypes: true })) {
57505
- if (entry.isDirectory() && fs51.existsSync(path59.join(workspace, entry.name, ".git"))) {
57506
- 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));
57507
57920
  }
57508
57921
  }
57509
57922
  } catch {
@@ -57512,20 +57925,20 @@ function playwrightSearchDirs(workspace) {
57512
57925
  }
57513
57926
  function playwrightCwd(dirs) {
57514
57927
  for (const dir of dirs) {
57515
- if (!(fs51.existsSync(dir) && fs51.statSync(dir).isDirectory())) {
57928
+ if (!(fs52.existsSync(dir) && fs52.statSync(dir).isDirectory())) {
57516
57929
  continue;
57517
57930
  }
57518
- if (CONFIG_NAMES.some((name) => fs51.existsSync(path59.join(dir, name)))) {
57931
+ if (CONFIG_NAMES.some((name) => fs52.existsSync(path60.join(dir, name)))) {
57519
57932
  return dir;
57520
57933
  }
57521
57934
  }
57522
57935
  return null;
57523
57936
  }
57524
57937
  function noSuiteMessage(workspace) {
57525
- const repoNames = playwrightSearchDirs(workspace).slice(1).map((d) => path59.basename(d));
57938
+ const repoNames = playwrightSearchDirs(workspace).slice(1).map((d) => path60.basename(d));
57526
57939
  let services = "";
57527
57940
  try {
57528
- 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] ?? "";
57529
57942
  } catch {
57530
57943
  }
57531
57944
  const selected = services.split(",").map((s) => s.trim()).filter(Boolean);
@@ -57539,8 +57952,8 @@ ${lines.join("\n")}` : "");
57539
57952
  }
57540
57953
  function installedPlaywrightVersion(cwd) {
57541
57954
  try {
57542
- const pkg = createRequire(path59.join(cwd, "package.json")).resolve("@playwright/test/package.json");
57543
- 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;
57544
57957
  } catch {
57545
57958
  return null;
57546
57959
  }
@@ -57620,15 +58033,62 @@ var init_test_run = __esm({
57620
58033
  });
57621
58034
 
57622
58035
  // ../../packages/sandbox-core/dist/machine_setup.js
57623
- import * as fs52 from "node:fs";
58036
+ import * as fs53 from "node:fs";
57624
58037
  import * as os18 from "node:os";
57625
- import * as path60 from "node:path";
58038
+ import * as path61 from "node:path";
57626
58039
  function briefOutput(out, maxLines = 4) {
57627
58040
  const lines = out.split("\n").map((l) => l.trimEnd()).filter((l) => l.trim() && !/^\s*[✔✓⣾⣽⣻⢿⡿⣟⣯⣷]/.test(l));
57628
58041
  if (!lines.length)
57629
58042
  return out.trim().slice(0, 200);
57630
58043
  return lines.slice(0, maxLines).join("; ");
57631
58044
  }
58045
+ function retireLegacyPlugin(claudePluginsDir, runner, checkOnly) {
58046
+ const label = `plugin ${LEGACY_PLUGIN.name} (renamed ${PLUGIN_NAME})`;
58047
+ const installed = installedVersion(claudePluginsDir, LEGACY_PLUGIN) !== null;
58048
+ const marketplaceKnown = LEGACY_PLUGIN.marketplace in knownMarketplaces(claudePluginsDir);
58049
+ if (!installed && !marketplaceKnown)
58050
+ return [];
58051
+ if (checkOnly) {
58052
+ return [
58053
+ {
58054
+ label,
58055
+ ok: false,
58056
+ detail: `old name still set up; run \`w9r setup\` to switch to ${PLUGIN_NAME}`
58057
+ }
58058
+ ];
58059
+ }
58060
+ const steps = [];
58061
+ if (installed) {
58062
+ const [rc, out] = runner([
58063
+ "claude",
58064
+ "plugin",
58065
+ "uninstall",
58066
+ `${LEGACY_PLUGIN.name}@${LEGACY_PLUGIN.marketplace}`
58067
+ ]);
58068
+ steps.push({
58069
+ label,
58070
+ ok: rc === 0,
58071
+ detail: rc === 0 ? "uninstalled" : `uninstall failed: ${out}`
58072
+ });
58073
+ if (rc !== 0)
58074
+ return steps;
58075
+ }
58076
+ if (marketplaceKnown) {
58077
+ const [rc, out] = runner([
58078
+ "claude",
58079
+ "plugin",
58080
+ "marketplace",
58081
+ "remove",
58082
+ LEGACY_PLUGIN.marketplace
58083
+ ]);
58084
+ steps.push({
58085
+ label,
58086
+ ok: rc === 0,
58087
+ detail: rc === 0 ? `removed marketplace ${LEGACY_PLUGIN.marketplace}` : `marketplace remove failed: ${out}`
58088
+ });
58089
+ }
58090
+ return steps;
58091
+ }
57632
58092
  function workspaceRepoUrl(slug) {
57633
58093
  return `https://github.com/${slug}.git`;
57634
58094
  }
@@ -57679,17 +58139,17 @@ function defaultInteractiveRunner(cmd) {
57679
58139
  function readJson2(p) {
57680
58140
  let data;
57681
58141
  try {
57682
- data = JSON.parse(fs52.readFileSync(p, "utf8"));
58142
+ data = JSON.parse(fs53.readFileSync(p, "utf8"));
57683
58143
  } catch {
57684
58144
  return null;
57685
58145
  }
57686
58146
  return typeof data === "object" && data !== null && !Array.isArray(data) ? data : null;
57687
58147
  }
57688
58148
  function knownMarketplaces(claudePluginsDir) {
57689
- return readJson2(path60.join(claudePluginsDir, "known_marketplaces.json")) || {};
58149
+ return readJson2(path61.join(claudePluginsDir, "known_marketplaces.json")) || {};
57690
58150
  }
57691
58151
  function installedVersion(claudePluginsDir, spec) {
57692
- const registry = readJson2(path60.join(claudePluginsDir, "installed_plugins.json")) || {};
58152
+ const registry = readJson2(path61.join(claudePluginsDir, "installed_plugins.json")) || {};
57693
58153
  const plugins = registry["plugins"];
57694
58154
  if (typeof plugins !== "object" || plugins === null || Array.isArray(plugins)) {
57695
58155
  return null;
@@ -57782,9 +58242,9 @@ function syncOptionalPlugin(spec, claudePluginsDir, runner, checkOnly, install)
57782
58242
  }));
57783
58243
  }
57784
58244
  function syncRepo(reposRoot, spec, runner, checkOnly) {
57785
- const dest = path60.join(reposRoot, spec.name);
58245
+ const dest = path61.join(reposRoot, spec.name);
57786
58246
  const label = `repo ${spec.name}`;
57787
- if (!fs52.existsSync(dest)) {
58247
+ if (!fs53.existsSync(dest)) {
57788
58248
  if (checkOnly) {
57789
58249
  return [
57790
58250
  {
@@ -57803,7 +58263,7 @@ function syncRepo(reposRoot, spec, runner, checkOnly) {
57803
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)}`;
57804
58264
  return [{ label, ok: false, optional: true, detail: detail2 }];
57805
58265
  }
57806
- if (!fs52.existsSync(path60.join(dest, ".git"))) {
58266
+ if (!fs53.existsSync(path61.join(dest, ".git"))) {
57807
58267
  return [
57808
58268
  {
57809
58269
  label,
@@ -57836,8 +58296,8 @@ function syncPathDirs(checkOnly, dirs) {
57836
58296
  continue;
57837
58297
  const target = expandUser(value);
57838
58298
  const label = `paths.${key}`;
57839
- if (fs52.existsSync(target)) {
57840
- if (!fs52.statSync(target).isDirectory()) {
58299
+ if (fs53.existsSync(target)) {
58300
+ if (!fs53.statSync(target).isDirectory()) {
57841
58301
  steps.push({ label, ok: false, detail: `${target} exists but is not a directory` });
57842
58302
  } else {
57843
58303
  steps.push({ label, ok: true, detail: target });
@@ -57849,7 +58309,7 @@ function syncPathDirs(checkOnly, dirs) {
57849
58309
  continue;
57850
58310
  }
57851
58311
  try {
57852
- fs52.mkdirSync(target, { recursive: true });
58312
+ fs53.mkdirSync(target, { recursive: true });
57853
58313
  steps.push({ label, ok: true, detail: `created ${target}` });
57854
58314
  } catch (e) {
57855
58315
  steps.push({ label, ok: false, detail: `could not create ${target}: ${e}` });
@@ -57858,7 +58318,7 @@ function syncPathDirs(checkOnly, dirs) {
57858
58318
  return steps;
57859
58319
  }
57860
58320
  function syncRegistryConfigs(checkOnly, options = {}) {
57861
- 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;
57862
58322
  const files = [
57863
58323
  [
57864
58324
  "services.local.yaml",
@@ -57877,12 +58337,12 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57877
58337
  const steps = [];
57878
58338
  for (const [name, dest, legacyName, content] of files) {
57879
58339
  const label2 = `config ${name}`;
57880
- if (fs52.existsSync(dest)) {
58340
+ if (fs53.existsSync(dest)) {
57881
58341
  steps.push({ label: label2, ok: true, detail: `present at ${dest}` });
57882
58342
  continue;
57883
58343
  }
57884
- const legacy = legacyDir && legacyName ? path60.join(legacyDir, legacyName) : null;
57885
- 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);
57886
58346
  if (checkOnly) {
57887
58347
  steps.push({
57888
58348
  label: label2,
@@ -57893,8 +58353,8 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57893
58353
  }
57894
58354
  try {
57895
58355
  if (migratable) {
57896
- fs52.mkdirSync(path60.dirname(dest), { recursive: true });
57897
- fs52.copyFileSync(legacy, dest);
58356
+ fs53.mkdirSync(path61.dirname(dest), { recursive: true });
58357
+ fs53.copyFileSync(legacy, dest);
57898
58358
  steps.push({ label: label2, ok: true, detail: `migrated from ${legacy}` });
57899
58359
  } else {
57900
58360
  const result = seedFile(dest, content());
@@ -57906,7 +58366,7 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57906
58366
  }
57907
58367
  const label = "config secrets.env";
57908
58368
  const secrets = secretsPath();
57909
- if (fs52.existsSync(secrets)) {
58369
+ if (fs53.existsSync(secrets)) {
57910
58370
  steps.push({ label, ok: true, detail: `present at ${secrets}` });
57911
58371
  } else if (checkOnly) {
57912
58372
  steps.push({ label, ok: true, detail: `absent \u2014 would write a template to ${secrets}` });
@@ -57927,11 +58387,11 @@ function syncRegistryConfigs(checkOnly, options = {}) {
57927
58387
  function resolveCertScript(startDir) {
57928
58388
  let dir = startDir ?? sandboxRoot();
57929
58389
  for (let i = 0; i < 5; i++) {
57930
- const candidate = path60.join(dir, "bin", "generate-certs.sh");
57931
- if (fs52.existsSync(candidate)) {
58390
+ const candidate = path61.join(dir, "bin", "generate-certs.sh");
58391
+ if (fs53.existsSync(candidate)) {
57932
58392
  return candidate;
57933
58393
  }
57934
- const parent = path60.dirname(dir);
58394
+ const parent = path61.dirname(dir);
57935
58395
  if (parent === dir)
57936
58396
  break;
57937
58397
  dir = parent;
@@ -58044,15 +58504,15 @@ ${JIRA_CONFIG_SNIPPET}`
58044
58504
  ];
58045
58505
  }
58046
58506
  function vscodeSettingsPath() {
58047
- 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");
58048
58508
  }
58049
- function editorSchemaState(settingsPath = vscodeSettingsPath(), schemasDir = path60.join(sandboxRoot(), "infra", "schemas")) {
58509
+ function editorSchemaState(settingsPath = vscodeSettingsPath(), schemasDir = path61.join(sandboxRoot(), "infra", "schemas")) {
58050
58510
  const state = { present: false, set: {}, drop: [], text: "{}" };
58051
- if (!fs52.existsSync(path60.dirname(settingsPath)))
58511
+ if (!fs53.existsSync(path61.dirname(settingsPath)))
58052
58512
  return state;
58053
58513
  state.present = true;
58054
- if (fs52.existsSync(settingsPath))
58055
- state.text = fs52.readFileSync(settingsPath, "utf8");
58514
+ if (fs53.existsSync(settingsPath))
58515
+ state.text = fs53.readFileSync(settingsPath, "utf8");
58056
58516
  const errors = [];
58057
58517
  const data = parse7(state.text, errors, { allowTrailingComma: true });
58058
58518
  if (errors.length) {
@@ -58062,7 +58522,7 @@ function editorSchemaState(settingsPath = vscodeSettingsPath(), schemasDir = pat
58062
58522
  const mapped = data?.["yaml.schemas"] ?? {};
58063
58523
  const covers = (v, glob) => v === glob || Array.isArray(v) && v.includes(glob);
58064
58524
  for (const [file, glob] of Object.entries(EDITOR_SCHEMAS)) {
58065
- const want = path60.join(schemasDir, file);
58525
+ const want = path61.join(schemasDir, file);
58066
58526
  if (!covers(mapped[want], glob))
58067
58527
  state.set[want] = glob;
58068
58528
  for (const [key, v] of Object.entries(mapped)) {
@@ -58107,11 +58567,11 @@ function syncEditorSchemas(checkOnly, install, settingsPath = vscodeSettingsPath
58107
58567
  for (const [key, glob] of Object.entries(state.set)) {
58108
58568
  text = applyEdits(text, modify(text, ["yaml.schemas", key], glob, fmt));
58109
58569
  }
58110
- fs52.writeFileSync(settingsPath, text);
58570
+ fs53.writeFileSync(settingsPath, text);
58111
58571
  return [{ label, ok: true, optional: true, detail: `mapped in ${settingsPath}` }];
58112
58572
  }
58113
58573
  function claudeUserSettingsPath() {
58114
- return path60.join(os18.homedir(), ".claude", "settings.json");
58574
+ return path61.join(os18.homedir(), ".claude", "settings.json");
58115
58575
  }
58116
58576
  function claudeStandardsDirState(settingsPath = claudeUserSettingsPath(), dir = standardsDirPath()) {
58117
58577
  const state = {
@@ -58120,12 +58580,12 @@ function claudeStandardsDirState(settingsPath = claudeUserSettingsPath(), dir =
58120
58580
  granted: false,
58121
58581
  text: "{}"
58122
58582
  };
58123
- state.present = fs52.existsSync(path60.dirname(settingsPath));
58124
- 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());
58125
58585
  if (!state.present || state.dirMissing)
58126
58586
  return state;
58127
- if (fs52.existsSync(settingsPath))
58128
- state.text = fs52.readFileSync(settingsPath, "utf8");
58587
+ if (fs53.existsSync(settingsPath))
58588
+ state.text = fs53.readFileSync(settingsPath, "utf8");
58129
58589
  const errors = [];
58130
58590
  const data = parse7(state.text, errors, { allowTrailingComma: true });
58131
58591
  if (errors.length) {
@@ -58137,8 +58597,8 @@ function claudeStandardsDirState(settingsPath = claudeUserSettingsPath(), dir =
58137
58597
  state.error = "permissions.additionalDirectories is not an array";
58138
58598
  return state;
58139
58599
  }
58140
- const want = path60.resolve(dir);
58141
- 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);
58142
58602
  return state;
58143
58603
  }
58144
58604
  function syncClaudeStandardsDir(checkOnly, install, settingsPath = claudeUserSettingsPath(), dir = standardsDirPath()) {
@@ -58168,18 +58628,18 @@ function syncClaudeStandardsDir(checkOnly, install, settingsPath = claudeUserSet
58168
58628
  ...fmt,
58169
58629
  isArrayInsertion: true
58170
58630
  }) : modify(state.text, ["permissions", "additionalDirectories"], [dir], fmt);
58171
- fs52.writeFileSync(settingsPath, applyEdits(state.text, edits));
58631
+ fs53.writeFileSync(settingsPath, applyEdits(state.text, edits));
58172
58632
  return [{ label, ok: true, optional: true, detail: `granted in ${settingsPath}` }];
58173
58633
  }
58174
58634
  function vscodeExtensionState(runner = defaultRunner, root = sandboxRoot()) {
58175
58635
  const [rc, out] = runner(["code", "--list-extensions", "--show-versions"]);
58176
58636
  const prefix = `${VSCODE_EXTENSION_ID}@`;
58177
58637
  const line = rc === 0 ? out.split("\n").find((l) => l.trim().toLowerCase().startsWith(prefix)) : void 0;
58178
- const vsix = path60.join(root, BUNDLED_VSIX_REL);
58638
+ const vsix = path61.join(root, BUNDLED_VSIX_REL);
58179
58639
  return {
58180
58640
  cli: rc === 0,
58181
58641
  installed: line ? line.trim().slice(prefix.length) : null,
58182
- 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
58183
58643
  };
58184
58644
  }
58185
58645
  function syncVscodeExtension(checkOnly, install, state = vscodeExtensionState(), runner = defaultRunner) {
@@ -58333,7 +58793,7 @@ function syncPlaywright(runner, checkOnly, interactive) {
58333
58793
  const dirs = [...out.matchAll(/Install location:\s*(\S.*)$/gm)].map((m) => m[1].trim());
58334
58794
  if (rc2 !== 0 || dirs.length === 0)
58335
58795
  return null;
58336
- return dirs.filter((d) => !fs52.existsSync(path60.join(d, "INSTALLATION_COMPLETE")));
58796
+ return dirs.filter((d) => !fs53.existsSync(path61.join(d, "INSTALLATION_COMPLETE")));
58337
58797
  };
58338
58798
  const before = missingDirs();
58339
58799
  if (before === null) {
@@ -58377,8 +58837,8 @@ function syncPlaywright(runner, checkOnly, interactive) {
58377
58837
  async function runSetup(options = {}) {
58378
58838
  const checkOnly = options.checkOnly ?? false;
58379
58839
  const reposRoot = options.reposRoot ?? getReposRoot();
58380
- const claudePluginsDir = options.claudePluginsDir ?? path60.join(os18.homedir(), ".claude", "plugins");
58381
- const repoPluginDir = options.repoPluginDir ?? path60.join(sandboxRoot(), "plugins", "sandbox-tools");
58840
+ const claudePluginsDir = options.claudePluginsDir ?? path61.join(os18.homedir(), ".claude", "plugins");
58841
+ const repoPluginDir = options.repoPluginDir ?? path61.join(sandboxRoot(), "plugins", PLUGIN_NAME);
58382
58842
  const runner = options.runner ?? defaultRunner;
58383
58843
  const jira2 = options.jira ?? loadJira();
58384
58844
  const steps = [];
@@ -58393,15 +58853,19 @@ async function runSetup(options = {}) {
58393
58853
  steps.push({
58394
58854
  label: "claude CLI",
58395
58855
  ok: false,
58396
- detail: "not found on PATH \u2014 the sandbox-tools and ponytail plugins cannot be installed without it. Install Claude Code from https://claude.com/claude-code (the VS Code extension alone does not put `claude` on PATH), then re-run `sandbox setup`."
58856
+ detail: `not found on PATH \u2014 the ${PLUGIN_NAME} and ponytail plugins cannot be installed without it. Install Claude Code from https://claude.com/claude-code (the VS Code extension alone does not put \`claude\` on PATH), then re-run \`sandbox setup\`.`
58397
58857
  });
58398
58858
  } else {
58399
58859
  for (const spec of REQUIRED_PLUGINS) {
58400
58860
  let driftProbe = null;
58401
- if (spec.name === "sandbox-tools") {
58861
+ if (spec.name === PLUGIN_NAME) {
58402
58862
  driftProbe = () => pluginDriftWarnings(repoPluginDir, claudePluginsDir);
58403
58863
  }
58404
- steps.push(...syncPlugin(spec, claudePluginsDir, runner, checkOnly, driftProbe));
58864
+ const synced = syncPlugin(spec, claudePluginsDir, runner, checkOnly, driftProbe);
58865
+ steps.push(...synced);
58866
+ if (spec.name === PLUGIN_NAME && (checkOnly || synced.every((step) => step.ok))) {
58867
+ steps.push(...retireLegacyPlugin(claudePluginsDir, runner, checkOnly));
58868
+ }
58405
58869
  }
58406
58870
  for (const spec of OPTIONAL_PLUGINS) {
58407
58871
  steps.push(...syncOptionalPlugin(spec, claudePluginsDir, runner, checkOnly, options.installOptionalPlugins ?? false));
@@ -58418,7 +58882,7 @@ async function runSetup(options = {}) {
58418
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."
58419
58883
  });
58420
58884
  }
58421
- if (options.demo || fs52.existsSync(path60.join(reposRoot, DEMO_SERVICES_REPO))) {
58885
+ if (options.demo || fs53.existsSync(path61.join(reposRoot, DEMO_SERVICES_REPO))) {
58422
58886
  steps.push(...syncRepo(reposRoot, DEMO_SERVICES_SPEC, runner, checkOnly));
58423
58887
  }
58424
58888
  steps.push(...syncRegistry({ reposRoot, checkOnly }));
@@ -58432,7 +58896,7 @@ async function runSetup(options = {}) {
58432
58896
  steps.push(...syncVscodeExtension(checkOnly, options.installVscodeExtension ?? false, vscodeExtensionState(runner), runner));
58433
58897
  return steps;
58434
58898
  }
58435
- var REQUIRED_PLUGINS, OPTIONAL_PLUGINS, WORKSPACE_ROOT_REPO, isLogin, DEMO_SERVICES_REPO, DEMO_SERVICES_REPO_URL, workspaceRootSpec, DEMO_SERVICES_SPEC, LEGACY_CONFIG_DIR, JIRA_TOKEN_URL, JIRA_REQUIRED_KEYS, JIRA_CONFIG_SNIPPET, EDITOR_SCHEMAS, VSCODE_EXTENSION_ID, BUNDLED_VSIX_REL;
58899
+ var REQUIRED_PLUGINS, LEGACY_PLUGIN, OPTIONAL_PLUGINS, WORKSPACE_ROOT_REPO, isLogin, DEMO_SERVICES_REPO, DEMO_SERVICES_REPO_URL, workspaceRootSpec, DEMO_SERVICES_SPEC, LEGACY_CONFIG_DIR, JIRA_TOKEN_URL, JIRA_REQUIRED_KEYS, JIRA_CONFIG_SNIPPET, EDITOR_SCHEMAS, VSCODE_EXTENSION_ID, BUNDLED_VSIX_REL;
58436
58900
  var init_machine_setup = __esm({
58437
58901
  "../../packages/sandbox-core/dist/machine_setup.js"() {
58438
58902
  "use strict";
@@ -58449,11 +58913,11 @@ var init_machine_setup = __esm({
58449
58913
  init_test_run();
58450
58914
  REQUIRED_PLUGINS = [
58451
58915
  // This repo doubles as the marketplace: `.claude-plugin/marketplace.json` at
58452
- // the root serves `plugins/sandbox-tools`. `marketplace` must match the
58916
+ // the root serves `plugins/werkmeister`. `marketplace` must match the
58453
58917
  // `name` field in that manifest, or install resolves nothing.
58454
58918
  {
58455
- name: "sandbox-tools",
58456
- marketplace: "sandbox",
58919
+ name: "werkmeister",
58920
+ marketplace: "werkmeister",
58457
58921
  source: "jbbz-sandbox/werkmeister@main"
58458
58922
  },
58459
58923
  {
@@ -58462,6 +58926,11 @@ var init_machine_setup = __esm({
58462
58926
  source: "DietrichGebert/ponytail"
58463
58927
  }
58464
58928
  ];
58929
+ LEGACY_PLUGIN = {
58930
+ name: LEGACY_PLUGIN_NAME,
58931
+ marketplace: "sandbox",
58932
+ source: "jbbz-sandbox/werkmeister@main"
58933
+ };
58465
58934
  OPTIONAL_PLUGINS = [
58466
58935
  {
58467
58936
  // Not obra/superpowers directly: that repo's manifest names itself
@@ -58487,7 +58956,7 @@ var init_machine_setup = __esm({
58487
58956
  url: DEMO_SERVICES_REPO_URL,
58488
58957
  missingConsequence: "The demo backend/frontend services will not start until it is cloned there."
58489
58958
  };
58490
- LEGACY_CONFIG_DIR = path60.join("infra", "config");
58959
+ LEGACY_CONFIG_DIR = path61.join("infra", "config");
58491
58960
  JIRA_TOKEN_URL = "https://id.atlassian.com/manage-profile/security/api-tokens";
58492
58961
  JIRA_REQUIRED_KEYS = ["base_url", "email", "api_token"];
58493
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";
@@ -58495,14 +58964,14 @@ var init_machine_setup = __esm({
58495
58964
  "services-fragment.schema.json": "**/.sandbox/services.yaml",
58496
58965
  "workspace.schema.json": "**/.sandbox/workspace.yml"
58497
58966
  };
58498
- VSCODE_EXTENSION_ID = "jbbz.vscode-plugin";
58499
- BUNDLED_VSIX_REL = path60.join("dist", "sandbox-workspaces.vsix");
58967
+ VSCODE_EXTENSION_ID = "jbbz.werkmeister-vscode";
58968
+ BUNDLED_VSIX_REL = path61.join("dist", "sandbox-workspaces.vsix");
58500
58969
  }
58501
58970
  });
58502
58971
 
58503
58972
  // ../../packages/sandbox-core/dist/mcp_gen.js
58504
- import * as fs53 from "node:fs";
58505
- import * as path61 from "node:path";
58973
+ import * as fs54 from "node:fs";
58974
+ import * as path62 from "node:path";
58506
58975
  function resolveWorkspaceMcpServers(presentArchetypes2, archetypes) {
58507
58976
  const names = /* @__PURE__ */ new Set();
58508
58977
  for (const archName of Object.keys(presentArchetypes2)) {
@@ -58543,12 +59012,12 @@ function substituteVars(value, vars2) {
58543
59012
  return value;
58544
59013
  }
58545
59014
  function writeWorkspaceMcp(workspaceRoot, serverNames, definitions, vars2 = {}) {
58546
- const mcpPath = path61.join(workspaceRoot, ".mcp.json");
58547
- 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();
58548
59017
  let config = {};
58549
59018
  if (isFile4) {
58550
59019
  try {
58551
- const existing2 = JSON.parse(fs53.readFileSync(mcpPath, "utf8"));
59020
+ const existing2 = JSON.parse(fs54.readFileSync(mcpPath, "utf8"));
58552
59021
  if (typeof existing2 === "object" && existing2 !== null && !Array.isArray(existing2)) {
58553
59022
  config = existing2;
58554
59023
  } else {
@@ -58575,21 +59044,26 @@ function writeWorkspaceMcp(workspaceRoot, serverNames, definitions, vars2 = {})
58575
59044
  delete servers[name];
58576
59045
  }
58577
59046
  }
59047
+ const legacy = servers[LEGACY_SERVER];
59048
+ if (legacy?.headers?.["X-Sandbox-Feature"] !== void 0)
59049
+ delete servers[LEGACY_SERVER];
58578
59050
  Object.assign(servers, wanted);
58579
- fs53.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
59051
+ fs54.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
58580
59052
  return mcpPath;
58581
59053
  }
59054
+ var LEGACY_SERVER;
58582
59055
  var init_mcp_gen = __esm({
58583
59056
  "../../packages/sandbox-core/dist/mcp_gen.js"() {
58584
59057
  "use strict";
58585
59058
  init_define_SANDBOX_BUILD();
59059
+ LEGACY_SERVER = "sandbox";
58586
59060
  }
58587
59061
  });
58588
59062
 
58589
59063
  // ../../packages/sandbox-core/dist/workspace_init.js
58590
- import * as fs54 from "node:fs";
59064
+ import * as fs55 from "node:fs";
58591
59065
  import * as os19 from "node:os";
58592
- import * as path62 from "node:path";
59066
+ import * as path63 from "node:path";
58593
59067
  function fail2(message) {
58594
59068
  error(message);
58595
59069
  throw new WorkspaceInitError(message);
@@ -58612,7 +59086,7 @@ async function resolveIssueCopy(root, issueId, opts) {
58612
59086
  }
58613
59087
  function isDir4(p) {
58614
59088
  try {
58615
- return fs54.statSync(p).isDirectory();
59089
+ return fs55.statSync(p).isDirectory();
58616
59090
  } catch {
58617
59091
  return false;
58618
59092
  }
@@ -58629,26 +59103,26 @@ function splitMultiValue(value) {
58629
59103
  return value.split(",").map((s) => s.trim()).filter((s) => s);
58630
59104
  }
58631
59105
  function hasWorkspaceConfig(workspaceRoot, override) {
58632
- if (override && fs54.existsSync(expandUserPath(override))) {
59106
+ if (override && fs55.existsSync(expandUserPath(override))) {
58633
59107
  return true;
58634
59108
  }
58635
59109
  const envOverride = envVar("WORKSPACE_CONFIG");
58636
- if (envOverride && fs54.existsSync(expandUserPath(envOverride))) {
59110
+ if (envOverride && fs55.existsSync(expandUserPath(envOverride))) {
58637
59111
  return true;
58638
59112
  }
58639
59113
  const sandbox = dotDir(workspaceRoot);
58640
- 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)));
58641
59115
  }
58642
59116
  function writeWorkspaceConfig(workspaceRoot, resolved) {
58643
59117
  if (Object.keys(resolved).length === 0) {
58644
59118
  return;
58645
59119
  }
58646
59120
  const sandboxDir = dotDir(workspaceRoot);
58647
- const target = path62.join(sandboxDir, _WORKSPACE_CONFIG_NAMES[0]);
58648
- 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));
58649
59123
  let existing2 = {};
58650
59124
  for (const source of [target, ...legacies]) {
58651
- if (fs54.existsSync(source) && fs54.statSync(source).isFile()) {
59125
+ if (fs55.existsSync(source) && fs55.statSync(source).isFile()) {
58652
59126
  try {
58653
59127
  const data = loadWorkspaceConfig(source);
58654
59128
  if (typeof data === "object" && data !== null && !Array.isArray(data)) {
@@ -58670,21 +59144,23 @@ function writeWorkspaceConfig(workspaceRoot, resolved) {
58670
59144
  }
58671
59145
  if (field === "autonomous" || field === "auto_stop")
58672
59146
  value = value === "true";
59147
+ if (field === "auto_stop" && value === false && !(field in existing2))
59148
+ continue;
58673
59149
  merged[field] = value;
58674
59150
  }
58675
59151
  }
58676
59152
  const body = YAML8.stringify(merged, { sortMapEntries: true, blockQuote: "literal" });
58677
59153
  const serialized = _WORKSPACE_CONFIG_HEADER + body;
58678
- if (fs54.existsSync(target) && fs54.readFileSync(target, "utf8") === serialized) {
59154
+ if (fs55.existsSync(target) && fs55.readFileSync(target, "utf8") === serialized) {
58679
59155
  return;
58680
59156
  }
58681
- fs54.mkdirSync(sandboxDir, { recursive: true });
58682
- fs54.writeFileSync(target, serialized);
59157
+ fs55.mkdirSync(sandboxDir, { recursive: true });
59158
+ fs55.writeFileSync(target, serialized);
58683
59159
  success(`Persisted inputs to ${target}`);
58684
59160
  for (const legacy of legacies) {
58685
- if (fs54.existsSync(legacy)) {
58686
- fs54.unlinkSync(legacy);
58687
- 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)`);
58688
59164
  }
58689
59165
  }
58690
59166
  }
@@ -58702,16 +59178,16 @@ function resolveReposRoot() {
58702
59178
  function resolveWorkspacesRoot() {
58703
59179
  const configured = loadPaths()["workspaces_root"];
58704
59180
  if (configured) {
58705
- return path62.resolve(expandUser(configured));
59181
+ return path63.resolve(expandUser(configured));
58706
59182
  }
58707
- return path62.resolve(path62.join(os19.homedir(), "workspace"));
59183
+ return path63.resolve(path63.join(os19.homedir(), "workspace"));
58708
59184
  }
58709
59185
  function createWorkspaceRoot(workspaceDir, branch, force) {
58710
59186
  const reposRoot = resolveReposRoot();
58711
- const rootRepoSrc = path62.join(reposRoot, WORKSPACE_ROOT_REPO);
59187
+ const rootRepoSrc = path63.join(reposRoot, WORKSPACE_ROOT_REPO);
58712
59188
  info(`Workspace: ${workspaceDir}`);
58713
59189
  info(`Branch: ${branch}`);
58714
- if (fs54.existsSync(path62.join(rootRepoSrc, ".git"))) {
59190
+ if (fs55.existsSync(path63.join(rootRepoSrc, ".git"))) {
58715
59191
  try {
58716
59192
  createWorktree(rootRepoSrc, workspaceDir, branch, { force });
58717
59193
  } catch (exc) {
@@ -58720,11 +59196,11 @@ function createWorkspaceRoot(workspaceDir, branch, force) {
58720
59196
  success(`Created workspace root (worktree of ${WORKSPACE_ROOT_REPO}) at ${workspaceDir}`);
58721
59197
  } else {
58722
59198
  warn(`No '${WORKSPACE_ROOT_REPO}' repo at ${rootRepoSrc} \u2014 workspace root will be a plain directory (no git history for notes/config).`);
58723
- fs54.mkdirSync(workspaceDir, { recursive: true });
59199
+ fs55.mkdirSync(workspaceDir, { recursive: true });
58724
59200
  }
58725
59201
  }
58726
59202
  function commitWorkspaceState(workspaceDir, workspaceName) {
58727
- if (!fs54.existsSync(path62.join(workspaceDir, ".git"))) {
59203
+ if (!fs55.existsSync(path63.join(workspaceDir, ".git"))) {
58728
59204
  return;
58729
59205
  }
58730
59206
  try {
@@ -58764,9 +59240,9 @@ function candidateWorkspaceRoots() {
58764
59240
  const roots = [];
58765
59241
  const configured = loadPaths()["workspaces_root"];
58766
59242
  if (configured) {
58767
- roots.push(path62.resolve(expandUserPath(configured)));
59243
+ roots.push(path63.resolve(expandUserPath(configured)));
58768
59244
  }
58769
- roots.push(path62.join(os19.homedir(), "workspace"));
59245
+ roots.push(path63.join(os19.homedir(), "workspace"));
58770
59246
  const seen = /* @__PURE__ */ new Set();
58771
59247
  const unique = [];
58772
59248
  for (const r of roots) {
@@ -58778,20 +59254,20 @@ function candidateWorkspaceRoots() {
58778
59254
  return unique;
58779
59255
  }
58780
59256
  function findWorkspaceRoot2() {
58781
- const cwd = path62.resolve(process.cwd());
59257
+ const cwd = path63.resolve(process.cwd());
58782
59258
  for (const root of candidateWorkspaceRoots()) {
58783
- const rel = path62.relative(root, cwd);
58784
- 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)) {
58785
59261
  continue;
58786
59262
  }
58787
59263
  if (rel === "") {
58788
59264
  continue;
58789
59265
  }
58790
- return path62.join(root, rel.split(path62.sep)[0]);
59266
+ return path63.join(root, rel.split(path63.sep)[0]);
58791
59267
  }
58792
59268
  for (const directory of [cwd, ...ancestors(cwd)]) {
58793
- if (fs54.existsSync(path62.join(directory, ".git"))) {
58794
- return path62.dirname(directory);
59269
+ if (fs55.existsSync(path63.join(directory, ".git"))) {
59270
+ return path63.dirname(directory);
58795
59271
  }
58796
59272
  }
58797
59273
  const rootsDisplay = candidateWorkspaceRoots().join(", ");
@@ -58802,7 +59278,7 @@ function ancestors(p) {
58802
59278
  const result = [];
58803
59279
  let cur = p;
58804
59280
  while (true) {
58805
- const parent = path62.dirname(cur);
59281
+ const parent = path63.dirname(cur);
58806
59282
  if (parent === cur) {
58807
59283
  break;
58808
59284
  }
@@ -58821,16 +59297,16 @@ function getBranch(p) {
58821
59297
  return result2.stdout.trim() || "unknown";
58822
59298
  }
58823
59299
  function discoverRepos(workspaceRoot) {
58824
- return repoDirs(workspaceRoot).map((d) => [path62.basename(d), getBranch(d)]);
59300
+ return repoDirs(workspaceRoot).map((d) => [path63.basename(d), getBranch(d)]);
58825
59301
  }
58826
59302
  function workspaceBranch(workspaceRoot) {
58827
- if (fs54.existsSync(path62.join(workspaceRoot, ".git"))) {
59303
+ if (fs55.existsSync(path63.join(workspaceRoot, ".git"))) {
58828
59304
  const branch = getBranch(workspaceRoot);
58829
59305
  if (branch && branch !== "unknown") {
58830
59306
  return branch;
58831
59307
  }
58832
59308
  }
58833
- return `${getuser()}/${path62.basename(workspaceRoot)}`;
59309
+ return `${getuser()}/${path63.basename(workspaceRoot)}`;
58834
59310
  }
58835
59311
  function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58836
59312
  const reposRoot = getReposRoot();
@@ -58839,12 +59315,12 @@ function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58839
59315
  const present = new Set(discoverRepos(workspaceRoot).map(([name]) => name));
58840
59316
  const toCreate = desired.filter((r) => !present.has(r));
58841
59317
  const toRemove = [...present].filter((r) => !desiredSet.has(r)).sort();
58842
- const missingSrc = toCreate.filter((r) => !isDir4(path62.join(reposRoot, r)));
59318
+ const missingSrc = toCreate.filter((r) => !isDir4(path63.join(reposRoot, r)));
58843
59319
  if (missingSrc.length) {
58844
59320
  fail2(`Source repo(s) not found under ${reposRoot}: ${missingSrc.join(", ")}. Clone them there (or fix \`repos\` in .sandbox/workspace.yml) and rerun.`);
58845
59321
  }
58846
59322
  if (toRemove.length && !force) {
58847
- const dirty = toRemove.filter((r) => worktreeIsDirty(path62.join(workspaceRoot, r)));
59323
+ const dirty = toRemove.filter((r) => worktreeIsDirty(path63.join(workspaceRoot, r)));
58848
59324
  if (dirty.length) {
58849
59325
  fail2(`Refusing to remove worktree(s) with uncommitted changes: ${dirty.join(", ")}. Commit or stash them, or rerun with --force to discard them.`);
58850
59326
  }
@@ -58854,7 +59330,7 @@ function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58854
59330
  }
58855
59331
  const branch = workspaceBranch(workspaceRoot);
58856
59332
  for (const repo of toRemove) {
58857
- 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);
58858
59334
  if (ok) {
58859
59335
  success(`Removed worktree '${repo}' (no longer in workspace.yml).`);
58860
59336
  if (message) {
@@ -58867,8 +59343,8 @@ function reconcileWorktrees(workspaceRoot, desiredRepos, force) {
58867
59343
  const created = [];
58868
59344
  try {
58869
59345
  for (const repo of toCreate) {
58870
- const src = path62.join(reposRoot, repo);
58871
- const dest = path62.join(workspaceRoot, repo);
59346
+ const src = path63.join(reposRoot, repo);
59347
+ const dest = path63.join(workspaceRoot, repo);
58872
59348
  createWorktree(src, dest, branch, { force });
58873
59349
  created.push([dest, src]);
58874
59350
  }
@@ -58896,10 +59372,10 @@ function resolveTask(argsTask, workspaceName, readStdinTask2) {
58896
59372
  return [slugToTask(workspaceName), {}];
58897
59373
  }
58898
59374
  function warnIfExisting(claudeMdPath) {
58899
- if (!fs54.existsSync(claudeMdPath)) {
59375
+ if (!fs55.existsSync(claudeMdPath)) {
58900
59376
  return;
58901
59377
  }
58902
- const existing2 = fs54.readFileSync(claudeMdPath, "utf8");
59378
+ const existing2 = fs55.readFileSync(claudeMdPath, "utf8");
58903
59379
  if (existing2.includes("## Layout")) {
58904
59380
  warn("CLAUDE.md already initialised \u2014 overwriting with fresh content.");
58905
59381
  } else {
@@ -58922,7 +59398,7 @@ function logMeta(task, meta) {
58922
59398
  }
58923
59399
  async function initWorkspace(args, workspaceRootOverride, opts = {}) {
58924
59400
  const workspaceRoot = workspaceRootOverride ?? findWorkspaceRoot2();
58925
- const workspaceName = path62.basename(workspaceRoot);
59401
+ const workspaceName = path63.basename(workspaceRoot);
58926
59402
  info(`Workspace root: ${workspaceRoot}`);
58927
59403
  const inputs = resolveInputs(args, workspaceRoot);
58928
59404
  const strategy = strategyById(parseStrategyId(inputs["context_strategy"]));
@@ -58951,7 +59427,7 @@ async function initWorkspace(args, workspaceRootOverride, opts = {}) {
58951
59427
  }
58952
59428
  reconcileWorktrees(workspaceRoot, desiredRepos, Boolean(args["force"]));
58953
59429
  }
58954
- const claudeMdPath = path62.join(workspaceRoot, "CLAUDE.md");
59430
+ const claudeMdPath = path63.join(workspaceRoot, "CLAUDE.md");
58955
59431
  warnIfExisting(claudeMdPath);
58956
59432
  info("Scanning for git repos...");
58957
59433
  const repos = discoverRepos(workspaceRoot);
@@ -59008,7 +59484,7 @@ async function initWorkspace(args, workspaceRootOverride, opts = {}) {
59008
59484
  for (const warning of pluginDriftWarnings()) {
59009
59485
  warn(warning);
59010
59486
  }
59011
- const envPath = path62.join(workspaceRoot, ".env");
59487
+ const envPath = path63.join(workspaceRoot, ".env");
59012
59488
  seedWorkspaceMetadataEnv(envPath, meta);
59013
59489
  success(`Wrote ${envPath}`);
59014
59490
  const feature2 = (meta["FEATURE"] ?? "").trim();
@@ -59067,8 +59543,8 @@ async function provisionWorkspace(resolved, opts = {}) {
59067
59543
  fail2(`Workspace name '${workspaceName}' is not a valid slug (expected lowercase letters, digits, and single hyphens).`);
59068
59544
  }
59069
59545
  const branch = buildBranchName(os19.userInfo().username, workspaceName);
59070
- const workspaceDir = path62.join(resolveWorkspacesRoot(), workspaceName);
59071
- if (fs54.existsSync(workspaceDir)) {
59546
+ const workspaceDir = path63.join(resolveWorkspacesRoot(), workspaceName);
59547
+ if (fs55.existsSync(workspaceDir)) {
59072
59548
  info(`Workspace already exists: ${workspaceDir} \u2014 re-running init-workspace.`);
59073
59549
  } else {
59074
59550
  const repos = await selectRepos(resolved.repos, resolved.services);
@@ -59225,21 +59701,21 @@ __export(sandbox_exports, {
59225
59701
  import { execFile as execFile2, spawn as spawn8 } from "node:child_process";
59226
59702
  import { EventEmitter } from "node:events";
59227
59703
  import { createReadStream } from "node:fs";
59228
- import fs55 from "node:fs/promises";
59704
+ import fs56 from "node:fs/promises";
59229
59705
  import os20 from "node:os";
59230
- import path63 from "node:path";
59706
+ import path64 from "node:path";
59231
59707
  import readline5 from "node:readline";
59232
59708
  import { promisify } from "node:util";
59233
59709
  async function isDir5(p) {
59234
59710
  try {
59235
- return (await fs55.stat(p)).isDirectory();
59711
+ return (await fs56.stat(p)).isDirectory();
59236
59712
  } catch {
59237
59713
  return false;
59238
59714
  }
59239
59715
  }
59240
59716
  async function isFile3(p) {
59241
59717
  try {
59242
- return (await fs55.stat(p)).isFile();
59718
+ return (await fs56.stat(p)).isFile();
59243
59719
  } catch {
59244
59720
  return false;
59245
59721
  }
@@ -59250,23 +59726,23 @@ async function resolveSandboxBin(opts = {}) {
59250
59726
  return explicit;
59251
59727
  const root = envVar("ROOT");
59252
59728
  if (root) {
59253
- const cand = path63.join(root, "sandbox");
59729
+ const cand = path64.join(root, "sandbox");
59254
59730
  if (await isExecutable(cand))
59255
59731
  return cand;
59256
59732
  }
59257
59733
  let dir = opts.from ?? import.meta.dirname;
59258
59734
  for (let i = 0; i < 8; i++) {
59259
- const cand = path63.join(dir, "sandbox");
59260
- 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"));
59261
59737
  if (await isExecutable(cand) && hasCli) {
59262
59738
  return cand;
59263
59739
  }
59264
- const parent = path63.dirname(dir);
59740
+ const parent = path64.dirname(dir);
59265
59741
  if (parent === dir)
59266
59742
  break;
59267
59743
  dir = parent;
59268
59744
  }
59269
- const local = path63.join(os20.homedir(), ".local", "bin", "sandbox");
59745
+ const local = path64.join(os20.homedir(), ".local", "bin", "sandbox");
59270
59746
  if (await isExecutable(local))
59271
59747
  return local;
59272
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.");
@@ -59363,7 +59839,7 @@ async function readServicesRegistry() {
59363
59839
  if (!p)
59364
59840
  return null;
59365
59841
  try {
59366
- return await fs55.readFile(p, "utf-8");
59842
+ return await fs56.readFile(p, "utf-8");
59367
59843
  } catch {
59368
59844
  return null;
59369
59845
  }
@@ -59520,7 +59996,7 @@ function pickValidServices(raw2, known) {
59520
59996
  }
59521
59997
  async function readWorkspaceConfig(workspaceDir) {
59522
59998
  try {
59523
- 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");
59524
60000
  const data = (0, import_yaml15.parse)(text);
59525
60001
  const issueId = issueIdOf(data) || null;
59526
60002
  const labels = Array.isArray(data?.labels) ? data.labels.filter((l) => typeof l === "string") : [];
@@ -59534,19 +60010,19 @@ async function listWorkspaces() {
59534
60010
  const root = workspacesRoot();
59535
60011
  let entries;
59536
60012
  try {
59537
- entries = await fs55.readdir(root, { withFileTypes: true });
60013
+ entries = await fs56.readdir(root, { withFileTypes: true });
59538
60014
  } catch (e) {
59539
60015
  throw new WorkspaceError(`Could not read workspaces root ${root}: ${e.message}`);
59540
60016
  }
59541
60017
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
59542
60018
  const workspaces = await Promise.all(names.map(async (name) => {
59543
- const p = path63.join(root, name);
60019
+ const p = path64.join(root, name);
59544
60020
  const { issueId, labels, model } = await readWorkspaceConfig(p);
59545
60021
  return {
59546
60022
  name,
59547
60023
  path: p,
59548
60024
  // vscode://file + an absolute path (already leading-slash) → single slash.
59549
- vscodeUri: `vscode://file${p.split(path63.sep).map(encodeURIComponent).join("/")}`,
60025
+ vscodeUri: `vscode://file${p.split(path64.sep).map(encodeURIComponent).join("/")}`,
59550
60026
  issueId,
59551
60027
  issueUrl: issueId ? issueTracker().url(issueId) : null,
59552
60028
  labels,
@@ -59723,7 +60199,7 @@ async function resolveWorkspaceDir(name) {
59723
60199
  if (!clean || clean.includes("/") || clean.includes("\\") || clean.includes("..")) {
59724
60200
  throw new WorkspaceError(`Invalid workspace name: ${JSON.stringify(name)}`);
59725
60201
  }
59726
- const target = path63.join(workspacesRoot(), clean);
60202
+ const target = path64.join(workspacesRoot(), clean);
59727
60203
  if (!await isDir5(target)) {
59728
60204
  throw new WorkspaceError(`No such workspace: ${clean}`);
59729
60205
  }
@@ -59734,7 +60210,7 @@ function reconcileDirForKey(name) {
59734
60210
  if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..")) {
59735
60211
  throw new WorkspaceError(`Invalid reconcile session: ${JSON.stringify(name)}`);
59736
60212
  }
59737
- return path63.join(reconcileRoot(), slug);
60213
+ return path64.join(reconcileRoot(), slug);
59738
60214
  }
59739
60215
  async function resolveSessionDir(name) {
59740
60216
  if ((name ?? "").startsWith(RECONCILE_PREFIX)) {
@@ -59754,7 +60230,7 @@ async function openWorkspace(name) {
59754
60230
  }
59755
60231
  async function readEnvValue(dir, key) {
59756
60232
  try {
59757
- const text = await fs55.readFile(path63.join(dir, ".env"), "utf-8");
60233
+ const text = await fs56.readFile(path64.join(dir, ".env"), "utf-8");
59758
60234
  for (const line of text.split(/\r?\n/)) {
59759
60235
  const eq = line.indexOf("=");
59760
60236
  if (eq > 0 && line.slice(0, eq).trim() === key)
@@ -59766,7 +60242,7 @@ async function readEnvValue(dir, key) {
59766
60242
  }
59767
60243
  async function readWorkspaceFeature(dir) {
59768
60244
  try {
59769
- 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");
59770
60246
  const m = /^feature: *["']?([^"'\s#]+)/m.exec(text);
59771
60247
  return m ? m[1] : null;
59772
60248
  } catch {
@@ -59774,7 +60250,7 @@ async function readWorkspaceFeature(dir) {
59774
60250
  }
59775
60251
  }
59776
60252
  async function readFileTail(file, maxBytes = 131072) {
59777
- const fh = await fs55.open(file, "r");
60253
+ const fh = await fs56.open(file, "r");
59778
60254
  try {
59779
60255
  const { size } = await fh.stat();
59780
60256
  const start2 = Math.max(0, size - maxBytes);
@@ -59870,7 +60346,7 @@ async function loadPortsDoc() {
59870
60346
  if (!file)
59871
60347
  return null;
59872
60348
  try {
59873
- return (0, import_yaml15.parse)(await fs55.readFile(file, "utf8")) ?? null;
60349
+ return (0, import_yaml15.parse)(await fs56.readFile(file, "utf8")) ?? null;
59874
60350
  } catch {
59875
60351
  return null;
59876
60352
  }
@@ -59881,12 +60357,12 @@ function slotPortLabel(slot, ports) {
59881
60357
  }
59882
60358
  async function getWorkspaceStatuses() {
59883
60359
  const root = workspacesRoot();
59884
- const entries = await fs55.readdir(root, { withFileTypes: true }).catch(() => []);
60360
+ const entries = await fs56.readdir(root, { withFileTypes: true }).catch(() => []);
59885
60361
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
59886
60362
  const projects = await dockerComposeProjects();
59887
60363
  const portsDoc = await loadPortsDoc();
59888
60364
  const statuses = await Promise.all(names.map(async (name) => {
59889
- const dir = path63.join(root, name);
60365
+ const dir = path64.join(root, name);
59890
60366
  let project = await readEnvValue(dir, "COMPOSE_PROJECT_NAME");
59891
60367
  if (!project) {
59892
60368
  const feature2 = await readWorkspaceFeature(dir);
@@ -59965,19 +60441,19 @@ async function getIntercepts() {
59965
60441
  }
59966
60442
  async function hasGit(dir) {
59967
60443
  try {
59968
- await fs55.access(path63.join(dir, ".git"));
60444
+ await fs56.access(path64.join(dir, ".git"));
59969
60445
  return true;
59970
60446
  } catch {
59971
60447
  return false;
59972
60448
  }
59973
60449
  }
59974
60450
  async function listRepoWorktrees(workspaceDir) {
59975
- const entries = await fs55.readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
60451
+ const entries = await fs56.readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
59976
60452
  const out = [];
59977
60453
  for (const e of entries) {
59978
60454
  if (!e.isDirectory() || e.name.startsWith("."))
59979
60455
  continue;
59980
- const dir = path63.join(workspaceDir, e.name);
60456
+ const dir = path64.join(workspaceDir, e.name);
59981
60457
  if (await hasGit(dir))
59982
60458
  out.push({ repo: e.name, dir });
59983
60459
  }
@@ -60019,10 +60495,10 @@ async function ghPrForWorktree(repoDir) {
60019
60495
  }
60020
60496
  async function getWorkspacePRs() {
60021
60497
  const root = workspacesRoot();
60022
- const entries = await fs55.readdir(root, { withFileTypes: true }).catch(() => []);
60498
+ const entries = await fs56.readdir(root, { withFileTypes: true }).catch(() => []);
60023
60499
  const names = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.localeCompare(b));
60024
60500
  return Promise.all(names.map(async (name) => {
60025
- const repos = await listRepoWorktrees(path63.join(root, name));
60501
+ const repos = await listRepoWorktrees(path64.join(root, name));
60026
60502
  const repoPrs = await Promise.all(repos.map(async ({ repo, dir }) => ({ repo, ...await ghPrForWorktree(dir) })));
60027
60503
  return { name, repos: repoPrs };
60028
60504
  }));
@@ -60230,7 +60706,7 @@ async function worktreeSources(worktrees) {
60230
60706
  if (res && res.code === 0) {
60231
60707
  const common2 = stripAnsi(res.stdout).trim();
60232
60708
  if (common2)
60233
- srcs.add(path63.dirname(common2));
60709
+ srcs.add(path64.dirname(common2));
60234
60710
  }
60235
60711
  }
60236
60712
  return [...srcs];
@@ -60239,8 +60715,8 @@ function gitErrorLine(text) {
60239
60715
  const lines = stripAnsi(text).trim().split("\n");
60240
60716
  return (lines.find((l) => /^\s*!\s|^(error|fatal):/.test(l)) ?? lines[0]).trim().replace(/\s+/g, " ");
60241
60717
  }
60242
- async function commitAndPush(dir, message = `chore: handoff ${path63.basename(dir)} before workspace teardown`, paths = []) {
60243
- 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);
60244
60720
  const br = await runCapture("git", ["-C", dir, "rev-parse", "--abbrev-ref", "HEAD"]).catch(() => null);
60245
60721
  const branch = br && br.code === 0 ? stripAnsi(br.stdout).trim() : null;
60246
60722
  if (!branch || branch === "HEAD")
@@ -60322,7 +60798,7 @@ ${parts.join("\n")}`);
60322
60798
  parts.push(detail);
60323
60799
  }
60324
60800
  }
60325
- await fs55.rm(target, { recursive: true, force: true });
60801
+ await fs56.rm(target, { recursive: true, force: true });
60326
60802
  for (const src of sources) {
60327
60803
  await runCapture("git", ["-C", src, "worktree", "prune"]).catch(() => {
60328
60804
  });
@@ -60487,17 +60963,17 @@ function encodeClaudeProjectDir(p) {
60487
60963
  return p.replace(/[^a-zA-Z0-9]/g, "-");
60488
60964
  }
60489
60965
  async function findClaudeProjectDir(workspacePath) {
60490
- const root = path63.join(os20.homedir(), ".claude", "projects");
60966
+ const root = path64.join(os20.homedir(), ".claude", "projects");
60491
60967
  const want = encodeClaudeProjectDir(workspacePath);
60492
60968
  let entries;
60493
60969
  try {
60494
- entries = await fs55.readdir(root, { withFileTypes: true });
60970
+ entries = await fs56.readdir(root, { withFileTypes: true });
60495
60971
  } catch {
60496
60972
  return null;
60497
60973
  }
60498
60974
  for (const e of entries) {
60499
60975
  if (e.isDirectory() && encodeClaudeProjectDir(e.name) === want) {
60500
- return path63.join(root, e.name);
60976
+ return path64.join(root, e.name);
60501
60977
  }
60502
60978
  }
60503
60979
  return null;
@@ -60505,7 +60981,7 @@ async function findClaudeProjectDir(workspacePath) {
60505
60981
  async function listSessionFiles(projectDir) {
60506
60982
  let names;
60507
60983
  try {
60508
- names = await fs55.readdir(projectDir);
60984
+ names = await fs56.readdir(projectDir);
60509
60985
  } catch {
60510
60986
  return [];
60511
60987
  }
@@ -60514,8 +60990,8 @@ async function listSessionFiles(projectDir) {
60514
60990
  if (!n.endsWith(".jsonl"))
60515
60991
  continue;
60516
60992
  try {
60517
- const st = await fs55.stat(path63.join(projectDir, n));
60518
- 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 });
60519
60995
  } catch {
60520
60996
  }
60521
60997
  }
@@ -60525,7 +61001,7 @@ function pickSession(sessions, sessionId) {
60525
61001
  if (!sessions.length)
60526
61002
  return null;
60527
61003
  if (sessionId) {
60528
- const match = sessions.find((s) => path63.basename(s.file, ".jsonl") === sessionId);
61004
+ const match = sessions.find((s) => path64.basename(s.file, ".jsonl") === sessionId);
60529
61005
  if (match)
60530
61006
  return match;
60531
61007
  }
@@ -60568,7 +61044,7 @@ async function listSessions(name) {
60568
61044
  return [];
60569
61045
  const files = await listSessionFiles(projectDir);
60570
61046
  return Promise.all(files.map(async (f, i) => {
60571
- const sessionId = path63.basename(f.file, ".jsonl");
61047
+ const sessionId = path64.basename(f.file, ".jsonl");
60572
61048
  return {
60573
61049
  sessionId,
60574
61050
  title: await readSessionTitle(f.file, sessionId),
@@ -60730,7 +61206,7 @@ async function loadPriceTable() {
60730
61206
  if (!p)
60731
61207
  return DEFAULT_PRICES;
60732
61208
  try {
60733
- 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"));
60734
61210
  return doc?.models && Object.keys(doc.models).length ? doc.models : DEFAULT_PRICES;
60735
61211
  } catch {
60736
61212
  return DEFAULT_PRICES;
@@ -60755,7 +61231,7 @@ async function getWorkspaceUsage(name) {
60755
61231
  for (const f of files) {
60756
61232
  let text;
60757
61233
  try {
60758
- text = await fs55.readFile(f.file, "utf-8");
61234
+ text = await fs56.readFile(f.file, "utf-8");
60759
61235
  } catch {
60760
61236
  continue;
60761
61237
  }
@@ -60776,7 +61252,7 @@ async function getWorkspaceUsage(name) {
60776
61252
  if (usd != null)
60777
61253
  sessionUsd = (sessionUsd ?? 0) + usd;
60778
61254
  }
60779
- const sessionId = path63.basename(f.file, ".jsonl");
61255
+ const sessionId = path64.basename(f.file, ".jsonl");
60780
61256
  sessions.push({
60781
61257
  sessionId,
60782
61258
  title: await readSessionTitle(f.file, sessionId),
@@ -60821,7 +61297,7 @@ async function resolveSessionSource(name, sessionId) {
60821
61297
  workspacePath: target,
60822
61298
  projectDir,
60823
61299
  sessionFile: chosen.file,
60824
- sessionId: path63.basename(chosen.file, ".jsonl"),
61300
+ sessionId: path64.basename(chosen.file, ".jsonl"),
60825
61301
  sessionCount: sessions.length,
60826
61302
  updatedAt: chosen.mtime.toISOString()
60827
61303
  };
@@ -60842,10 +61318,10 @@ async function getClaudeTranscript(name, limit = 20, sessionId) {
60842
61318
  const chosen = pickSession(sessions, sessionId);
60843
61319
  if (!chosen)
60844
61320
  return empty;
60845
- const resolvedId = path63.basename(chosen.file, ".jsonl");
61321
+ const resolvedId = path64.basename(chosen.file, ".jsonl");
60846
61322
  let text;
60847
61323
  try {
60848
- text = await fs55.readFile(chosen.file, "utf-8");
61324
+ text = await fs56.readFile(chosen.file, "utf-8");
60849
61325
  } catch (e) {
60850
61326
  throw new WorkspaceError(`Could not read session file: ${e.message}`);
60851
61327
  }
@@ -60947,7 +61423,7 @@ async function getK8sForwardsHealth() {
60947
61423
  if (!file) {
60948
61424
  throw new WorkspaceError("Could not locate the service registry (~/.sandbox/services.yaml) \u2014 run `sandbox setup`.");
60949
61425
  }
60950
- 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")) ?? {};
60951
61427
  const forwards = await Promise.all(forwardSpecs(doc.services, doc.infrastructure).map(async (d) => {
60952
61428
  const r = await checkForwardHealth(d.listen, d.health);
60953
61429
  return { name: d.name, port: d.listen, healthy: r.ok, detail: r.detail };
@@ -61039,16 +61515,16 @@ async function launchPrReconcile(url, issueRef) {
61039
61515
  }
61040
61516
  const { repo, number } = parseRepoAndNumber(url);
61041
61517
  const slug = `${repo}-${number}`;
61042
- const dir = path63.join(reconcileRoot(), slug);
61043
- await fs55.mkdir(dir, { recursive: true });
61518
+ const dir = path64.join(reconcileRoot(), slug);
61519
+ await fs56.mkdir(dir, { recursive: true });
61044
61520
  const [issue, pr, diffRes] = await Promise.all([
61045
61521
  fetchIssueCard(issueRef || url),
61046
61522
  ghPrView(url),
61047
61523
  runCapture("gh", ["pr", "diff", url]).catch(() => null)
61048
61524
  ]);
61049
61525
  const diff = diffRes && diffRes.code === 0 ? stripAnsi(diffRes.stdout) : "";
61050
- const ctxFile = path63.join(dir, "reconcile-context.md");
61051
- 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");
61052
61528
  await openTerminalRunning(`cd ${shellQuote(dir)} && claude -p --disallowedTools Bash Edit Write --append-system-prompt-file ${shellQuote(ctxFile)} ${shellQuote(RECONCILE_PROMPT)}`, false);
61053
61529
  return { sessionKey: `${RECONCILE_PREFIX}${slug}`, slug, issue };
61054
61530
  }
@@ -61102,7 +61578,7 @@ var init_sandbox = __esm({
61102
61578
  "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
61103
61579
  ].filter((p) => Boolean(p));
61104
61580
  DISTILLED = ["feature", "task", "description", "starting_prompt", "services"];
61105
- reconcileRoot = () => path63.join(dotDir(os20.homedir()), "reconcile");
61581
+ reconcileRoot = () => path64.join(dotDir(os20.homedir()), "reconcile");
61106
61582
  RECONCILE_PREFIX = "reconcile:";
61107
61583
  APPROVAL_LABEL = "ready-for-human-approval";
61108
61584
  FAILING_CONCLUSIONS = /* @__PURE__ */ new Set([
@@ -61392,8 +61868,8 @@ __export(detect_exports, {
61392
61868
  run: () => run10,
61393
61869
  runDetect: () => runDetect
61394
61870
  });
61395
- import * as fs56 from "node:fs";
61396
- import * as path64 from "node:path";
61871
+ import * as fs57 from "node:fs";
61872
+ import * as path65 from "node:path";
61397
61873
  function mark(fv) {
61398
61874
  return fv.confidence === "stated" ? "" : ` ${Colors.YELLOW}[${fv.confidence}]${Colors.RESET}`;
61399
61875
  }
@@ -61446,8 +61922,8 @@ function printPlan(plan) {
61446
61922
  async function runDetect(args, deps) {
61447
61923
  let root;
61448
61924
  if (args.repo) {
61449
- root = path64.resolve(deps.cwd, args.repo);
61450
- if (!fs56.statSync(root, { throwIfNoEntry: false })?.isDirectory()) {
61925
+ root = path65.resolve(deps.cwd, args.repo);
61926
+ if (!fs57.statSync(root, { throwIfNoEntry: false })?.isDirectory()) {
61451
61927
  warn(`${root} is not a directory \u2014 pass the repo path: sandbox detect <repo>`);
61452
61928
  return 2;
61453
61929
  }
@@ -61756,18 +62232,18 @@ var open_exports = {};
61756
62232
  __export(open_exports, {
61757
62233
  run: () => run15
61758
62234
  });
61759
- import * as fs57 from "node:fs";
61760
- import * as path65 from "node:path";
62235
+ import * as fs58 from "node:fs";
62236
+ import * as path66 from "node:path";
61761
62237
  function loadWorkspaceYaml(start2) {
61762
- let dir = path65.resolve(start2);
62238
+ let dir = path66.resolve(start2);
61763
62239
  for (; ; ) {
61764
62240
  for (const name of ["workspace.yml", "workspace.yaml"]) {
61765
- const p = path65.join(dotDir(dir), name);
61766
- 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()) {
61767
62243
  return readWorkspaceYaml(dir);
61768
62244
  }
61769
62245
  }
61770
- const parent = path65.dirname(dir);
62246
+ const parent = path66.dirname(dir);
61771
62247
  if (parent === dir) return {};
61772
62248
  dir = parent;
61773
62249
  }
@@ -61897,8 +62373,8 @@ var init_clean = __esm({
61897
62373
  });
61898
62374
 
61899
62375
  // ../../packages/sandbox-core/dist/stats.js
61900
- import * as fs58 from "node:fs";
61901
- import * as path66 from "node:path";
62376
+ import * as fs59 from "node:fs";
62377
+ import * as path67 from "node:path";
61902
62378
  function sleep4(seconds, signal) {
61903
62379
  return new Promise((resolve13) => {
61904
62380
  if (signal?.aborted)
@@ -61996,12 +62472,12 @@ function pad2(n) {
61996
62472
  return String(n).padStart(2, "0");
61997
62473
  }
61998
62474
  function saveReport(stats, recommendations) {
61999
- const statsDir = path66.join(sandboxRoot(), "stats");
62000
- if (!fs58.existsSync(statsDir))
62001
- fs58.mkdirSync(statsDir);
62475
+ const statsDir = path67.join(sandboxRoot(), "stats");
62476
+ if (!fs59.existsSync(statsDir))
62477
+ fs59.mkdirSync(statsDir);
62002
62478
  const now = /* @__PURE__ */ new Date();
62003
62479
  const timestamp = `${now.getFullYear()}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}-${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
62004
- const csvPath = path66.join(statsDir, `docker-stats-${timestamp}.csv`);
62480
+ const csvPath = path67.join(statsDir, `docker-stats-${timestamp}.csv`);
62005
62481
  const csvRows = [
62006
62482
  ["container", "avg_cpu_pct", "max_cpu_pct", "avg_mem_mib", "max_mem_mib", "samples"].join(",")
62007
62483
  ];
@@ -62016,8 +62492,8 @@ function saveReport(stats, recommendations) {
62016
62492
  String(s.samples)
62017
62493
  ].join(","));
62018
62494
  }
62019
- fs58.writeFileSync(csvPath, csvRows.join("\r\n") + "\r\n");
62020
- 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`);
62021
62497
  const generated = `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())} ${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`;
62022
62498
  let out = "";
62023
62499
  out += "Docker Resource Usage Summary\n";
@@ -62035,7 +62511,7 @@ function saveReport(stats, recommendations) {
62035
62511
  out += ` ${r.name.padEnd(35)} cpus: ${r.recommended_cpus.padStart(5)} memory: ${r.recommended_memory.padStart(5)}
62036
62512
  `;
62037
62513
  }
62038
- fs58.writeFileSync(summaryPath, out);
62514
+ fs59.writeFileSync(summaryPath, out);
62039
62515
  return summaryPath;
62040
62516
  }
62041
62517
  async function runCollection(durationMinutes, intervalSeconds, save2, signal) {
@@ -62487,14 +62963,14 @@ var init_standards2 = __esm({
62487
62963
  });
62488
62964
 
62489
62965
  // ../../packages/sandbox-core/dist/pr_guard.js
62490
- import * as fs59 from "node:fs";
62491
- import * as path67 from "node:path";
62966
+ import * as fs60 from "node:fs";
62967
+ import * as path68 from "node:path";
62492
62968
  function findRecordRoot(start2) {
62493
- let dir = path67.resolve(start2);
62969
+ let dir = path68.resolve(start2);
62494
62970
  for (; ; ) {
62495
- if (fs59.existsSync(path67.join(dotDir(dir), "workspace.yml")))
62971
+ if (fs60.existsSync(path68.join(dotDir(dir), "workspace.yml")))
62496
62972
  return dir;
62497
- const parent = path67.dirname(dir);
62973
+ const parent = path68.dirname(dir);
62498
62974
  if (parent === dir)
62499
62975
  return null;
62500
62976
  dir = parent;
@@ -62502,11 +62978,11 @@ function findRecordRoot(start2) {
62502
62978
  }
62503
62979
  function prFileDirName(root) {
62504
62980
  try {
62505
- const names = fs59.readdirSync(path67.join(root, "stages")).sort();
62981
+ const names = fs60.readdirSync(path68.join(root, "stages")).sort();
62506
62982
  const dir = names.find((n) => /^\d+_propose$/.test(n)) ?? names.find((n) => /^\d+_ship$/.test(n));
62507
62983
  return dir ? `stages/${dir}/output/` : "the propose stage's output/";
62508
62984
  } catch {
62509
- return `${path67.basename(dotDir(root))}/notes/`;
62985
+ return `${path68.basename(dotDir(root))}/notes/`;
62510
62986
  }
62511
62987
  }
62512
62988
  function decide2(input2) {
@@ -62518,13 +62994,13 @@ function decide2(input2) {
62518
62994
  const opensPr = tool === "Bash" && GH_PR_CREATE.test(command) || MCP_CREATE_PR.test(tool);
62519
62995
  if (!opensPr)
62520
62996
  return null;
62521
- 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";
62522
- 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}\`.`;
62523
62999
  }
62524
63000
  function main2() {
62525
63001
  let reason2 = null;
62526
63002
  try {
62527
- const raw2 = process.stdin.isTTY ? "" : fs59.readFileSync(0, "utf8");
63003
+ const raw2 = process.stdin.isTTY ? "" : fs60.readFileSync(0, "utf8");
62528
63004
  const parsed = raw2.trim() ? JSON.parse(raw2) : {};
62529
63005
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed))
62530
63006
  reason2 = decide2(parsed);
@@ -62589,13 +63065,13 @@ __export(init_workspace_exports, {
62589
63065
  readStdinTask: () => readStdinTask,
62590
63066
  run: () => run23
62591
63067
  });
62592
- import * as fs60 from "node:fs";
63068
+ import * as fs61 from "node:fs";
62593
63069
  function readStdinTask() {
62594
63070
  if (process.stdin.isTTY) {
62595
63071
  return "";
62596
63072
  }
62597
63073
  try {
62598
- return fs60.readFileSync(0, "utf8");
63074
+ return fs61.readFileSync(0, "utf8");
62599
63075
  } catch {
62600
63076
  return "";
62601
63077
  }
@@ -62628,8 +63104,8 @@ __export(workspace_exports, {
62628
63104
  browserFormCommand: () => browserFormCommand,
62629
63105
  run: () => run24
62630
63106
  });
62631
- import * as fs61 from "node:fs";
62632
- import * as path68 from "node:path";
63107
+ import * as fs62 from "node:fs";
63108
+ import * as path69 from "node:path";
62633
63109
  async function resolveWorkspaceInputs(args) {
62634
63110
  const base = process.cwd();
62635
63111
  let resolved = resolveInputs(args, base);
@@ -62665,12 +63141,12 @@ async function resolveWorkspaceInputs(args) {
62665
63141
  return { resolved, issue };
62666
63142
  }
62667
63143
  function which2(cmd) {
62668
- const dirs = (process.env.PATH || "").split(path68.delimiter);
63144
+ const dirs = (process.env.PATH || "").split(path69.delimiter);
62669
63145
  for (const dir of dirs) {
62670
63146
  if (!dir) continue;
62671
- const full = path68.join(dir, cmd);
63147
+ const full = path69.join(dir, cmd);
62672
63148
  try {
62673
- fs61.accessSync(full, fs61.constants.X_OK);
63149
+ fs62.accessSync(full, fs62.constants.X_OK);
62674
63150
  return full;
62675
63151
  } catch {
62676
63152
  }
@@ -62691,8 +63167,8 @@ function browserFormCommand(node, cliJs, args) {
62691
63167
  return cmd;
62692
63168
  }
62693
63169
  function runBrowserForm(args) {
62694
- const cliJs = path68.join(getMonorepoRoot(), "apps", "sandbox-dashboard", "dist", "form-cli.js");
62695
- 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())) {
62696
63172
  error(
62697
63173
  `Browser form not built: ${cliJs} is missing. Run \`npm --prefix apps/sandbox-dashboard run build\` (or \`sandbox dashboard\` once) first.`
62698
63174
  );
@@ -62815,7 +63291,7 @@ __export(issue_exports, {
62815
63291
  run: () => run26,
62816
63292
  updateFromArgs: () => updateFromArgs
62817
63293
  });
62818
- import * as fs62 from "node:fs";
63294
+ import * as fs63 from "node:fs";
62819
63295
  function updateFromArgs(args) {
62820
63296
  const status2 = args.status || void 0;
62821
63297
  const comment = args.comment || void 0;
@@ -62823,7 +63299,7 @@ function updateFromArgs(args) {
62823
63299
  return "Nothing to update: pass --status <name> and/or --comment <text>.";
62824
63300
  return { status: status2, comment };
62825
63301
  }
62826
- function newIssueFromArgs(args, read = (p) => fs62.readFileSync(p, "utf8")) {
63302
+ function newIssueFromArgs(args, read = (p) => fs63.readFileSync(p, "utf8")) {
62827
63303
  const project = (args.project ?? "").trim().toUpperCase();
62828
63304
  const type = args.type ?? "";
62829
63305
  const title = (args.title ?? "").trim();
@@ -62863,6 +63339,14 @@ async function run26(args) {
62863
63339
  }
62864
63340
  return;
62865
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
+ }
62866
63350
  const update = args.action === "update" ? updateFromArgs(args) : null;
62867
63351
  if (typeof update === "string") fail3(update);
62868
63352
  let text = args.ref ?? "";
@@ -62902,6 +63386,7 @@ var init_issue = __esm({
62902
63386
  "use strict";
62903
63387
  init_define_SANDBOX_BUILD();
62904
63388
  init_gate();
63389
+ init_issue_status();
62905
63390
  init_issue_tracker();
62906
63391
  init_workspace_prompts();
62907
63392
  init_config2();
@@ -62991,10 +63476,10 @@ var close_out_exports = {};
62991
63476
  __export(close_out_exports, {
62992
63477
  run: () => run29
62993
63478
  });
62994
- import path69 from "node:path";
63479
+ import path70 from "node:path";
62995
63480
  async function run29(args) {
62996
63481
  const root = findRecordRoot(process.cwd());
62997
- const name = args.name ?? (root ? path69.basename(root) : null);
63482
+ const name = args.name ?? (root ? path70.basename(root) : null);
62998
63483
  if (!name) {
62999
63484
  error("No workspace given and not inside one.");
63000
63485
  process.exit(1);
@@ -63046,9 +63531,9 @@ async function run30(args) {
63046
63531
  error("Feature name required: pass it as an argument or set FEATURE in a .env file.");
63047
63532
  process.exit(1);
63048
63533
  }
63049
- const { path: path73 } = await openWorkspace(name);
63534
+ const { path: path74 } = await openWorkspace(name);
63050
63535
  console.log(
63051
- `${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}`
63052
63537
  );
63053
63538
  }
63054
63539
  var init_code = __esm({
@@ -63066,8 +63551,8 @@ var setup_exports = {};
63066
63551
  __export(setup_exports, {
63067
63552
  run: () => run31
63068
63553
  });
63069
- import * as fs63 from "node:fs";
63070
- import * as path70 from "node:path";
63554
+ import * as fs64 from "node:fs";
63555
+ import * as path71 from "node:path";
63071
63556
  function heading(text) {
63072
63557
  console.log(`
63073
63558
  ${Colors.BOLD}${text}${Colors.RESET}`);
@@ -63173,7 +63658,7 @@ async function configureMachine(interactive) {
63173
63658
  interactive
63174
63659
  );
63175
63660
  }
63176
- if (!fs63.existsSync(servicesLocalYamlPath()) && interactive) {
63661
+ if (!fs64.existsSync(servicesLocalYamlPath()) && interactive) {
63177
63662
  k8sContext = await prompt(
63178
63663
  "Kube context for `sandbox intercept` (services.local.yaml k8s_context)",
63179
63664
  k8sContext
@@ -63335,9 +63820,9 @@ async function configureMachine(interactive) {
63335
63820
  info(
63336
63821
  `${zshrcPath()}: ${completionChanged ? "installed tab completion" : "tab completion already installed"}`
63337
63822
  );
63338
- if (fs63.existsSync(desktopConfigPath())) {
63823
+ if (fs64.existsSync(desktopConfigPath())) {
63339
63824
  info(
63340
- `${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"}`
63341
63826
  );
63342
63827
  }
63343
63828
  return {
@@ -63448,8 +63933,8 @@ var config_exports = {};
63448
63933
  __export(config_exports, {
63449
63934
  run: () => run32
63450
63935
  });
63451
- import * as fs64 from "node:fs";
63452
- import * as path71 from "node:path";
63936
+ import * as fs65 from "node:fs";
63937
+ import * as path72 from "node:path";
63453
63938
  function show(cfg) {
63454
63939
  const paths = cfg.paths || {};
63455
63940
  console.log(`${Colors.BOLD}config file:${Colors.RESET} ${configPath()}`);
@@ -63544,9 +64029,9 @@ async function run32(args) {
63544
64029
  info(`Regenerated ${envSh}`);
63545
64030
  info(`${zshenvPath()}: ${zshChanged ? "updated" : "already sources env.sh"}`);
63546
64031
  const desktopPath = desktopConfigPath();
63547
- if (fs64.existsSync(desktopPath)) {
64032
+ if (fs65.existsSync(desktopPath)) {
63548
64033
  info(
63549
- `${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"}`
63550
64035
  );
63551
64036
  }
63552
64037
  info("Open a new shell (or `source ~/.zshenv`) for the exports to take effect.");
@@ -63597,7 +64082,7 @@ var init_registry = __esm({
63597
64082
 
63598
64083
  // src/main.ts
63599
64084
  init_define_SANDBOX_BUILD();
63600
- import path72 from "node:path";
64085
+ import path73 from "node:path";
63601
64086
  import { fileURLToPath } from "node:url";
63602
64087
 
63603
64088
  // ../../node_modules/commander/esm.mjs
@@ -63631,7 +64116,7 @@ import { execFileSync } from "node:child_process";
63631
64116
  import * as fs21 from "node:fs";
63632
64117
  import * as path22 from "node:path";
63633
64118
  var BUILD_STAMP = typeof define_SANDBOX_BUILD_default === "undefined" ? void 0 : define_SANDBOX_BUILD_default;
63634
- var REBUILD_COMMAND = "npm run build --workspace sandbox-core && npm run build --workspace apps/cli";
64119
+ var REBUILD_COMMAND = "npm run build --workspace werkmeister-core && npm run build --workspace apps/cli";
63635
64120
  function staleBuildWarning(build, repoRoot) {
63636
64121
  if (!build?.sha || !fs21.existsSync(path22.join(repoRoot, ".git"))) return null;
63637
64122
  let head;
@@ -63651,7 +64136,7 @@ function staleBuildWarning(build, repoRoot) {
63651
64136
 
63652
64137
  // src/main.ts
63653
64138
  init_interrupt();
63654
- var _REPO_ROOT = path72.resolve(path72.dirname(fileURLToPath(import.meta.url)), "..");
64139
+ var _REPO_ROOT = path73.resolve(path73.dirname(fileURLToPath(import.meta.url)), "..");
63655
64140
  if (!envVar("ROOT")) {
63656
64141
  process.env.SANDBOX_ROOT = _REPO_ROOT;
63657
64142
  }
@@ -63672,11 +64157,15 @@ var SPECS = [
63672
64157
  ).option(
63673
64158
  "-w, --workspace <path>",
63674
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."
63675
64163
  );
63676
64164
  },
63677
64165
  namespace: ([feature2], o) => ({
63678
64166
  feature: feature2 ?? null,
63679
- workspace: o.workspace ?? null
64167
+ workspace: o.workspace ?? null,
64168
+ dev: o.dev ?? false
63680
64169
  })
63681
64170
  },
63682
64171
  {
@@ -63747,9 +64236,16 @@ var SPECS = [
63747
64236
  ).option(
63748
64237
  "--if-idle",
63749
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"
63750
64242
  );
63751
64243
  },
63752
- 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
+ })
63753
64249
  },
63754
64250
  {
63755
64251
  name: "status",
@@ -64085,8 +64581,8 @@ var SPECS = [
64085
64581
  ).addArgument(
64086
64582
  new Argument(
64087
64583
  "<action>",
64088
- "view: print the issue; update: change its status and/or add a comment; create: file a new issue and print its URL (Jira)"
64089
- ).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"])
64090
64586
  ).argument(
64091
64587
  "[ref]",
64092
64588
  "Issue ID; omit inside a workspace to use its .sandbox/workspace.yml issue"
@@ -64366,7 +64862,7 @@ ${_RED}Interrupted${_RESET}`));
64366
64862
  for (const [name, description] of [
64367
64863
  [
64368
64864
  "dashboard",
64369
- "Launch the workspace dashboard's MCP server over stdio (Claude Desktop, the sandbox-tools plugin)"
64865
+ "Launch the workspace dashboard's MCP server over stdio (Claude Desktop, the werkmeister plugin)"
64370
64866
  ],
64371
64867
  [
64372
64868
  "mcp",
@@ -64384,7 +64880,7 @@ ${_RED}Interrupted${_RESET}`));
64384
64880
  return completeLine(program2);
64385
64881
  }
64386
64882
  if (process.argv[2] !== "hooks" && !process.argv.includes("--if-idle")) {
64387
- const warning = staleBuildWarning(BUILD_STAMP, path72.resolve(_REPO_ROOT, "../.."));
64883
+ const warning = staleBuildWarning(BUILD_STAMP, path73.resolve(_REPO_ROOT, "../.."));
64388
64884
  if (warning) console.error(warning);
64389
64885
  }
64390
64886
  if (process.argv.length <= 2) {