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