@andyqiu/codeforge 0.8.2 → 0.8.4
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/bin/codeforge-doctor.mjs +41 -1
- package/dist/index.js +964 -319
- package/install.mjs +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -372,17 +372,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
372
372
|
visit.BREAK = BREAK;
|
|
373
373
|
visit.SKIP = SKIP;
|
|
374
374
|
visit.REMOVE = REMOVE;
|
|
375
|
-
function visit_(key, node, visitor,
|
|
376
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
375
|
+
function visit_(key, node, visitor, path21) {
|
|
376
|
+
const ctrl = callVisitor(key, node, visitor, path21);
|
|
377
377
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
378
|
-
replaceNode(key,
|
|
379
|
-
return visit_(key, ctrl, visitor,
|
|
378
|
+
replaceNode(key, path21, ctrl);
|
|
379
|
+
return visit_(key, ctrl, visitor, path21);
|
|
380
380
|
}
|
|
381
381
|
if (typeof ctrl !== "symbol") {
|
|
382
382
|
if (identity.isCollection(node)) {
|
|
383
|
-
|
|
383
|
+
path21 = Object.freeze(path21.concat(node));
|
|
384
384
|
for (let i = 0;i < node.items.length; ++i) {
|
|
385
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
385
|
+
const ci = visit_(i, node.items[i], visitor, path21);
|
|
386
386
|
if (typeof ci === "number")
|
|
387
387
|
i = ci - 1;
|
|
388
388
|
else if (ci === BREAK)
|
|
@@ -393,13 +393,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
} else if (identity.isPair(node)) {
|
|
396
|
-
|
|
397
|
-
const ck = visit_("key", node.key, visitor,
|
|
396
|
+
path21 = Object.freeze(path21.concat(node));
|
|
397
|
+
const ck = visit_("key", node.key, visitor, path21);
|
|
398
398
|
if (ck === BREAK)
|
|
399
399
|
return BREAK;
|
|
400
400
|
else if (ck === REMOVE)
|
|
401
401
|
node.key = null;
|
|
402
|
-
const cv = visit_("value", node.value, visitor,
|
|
402
|
+
const cv = visit_("value", node.value, visitor, path21);
|
|
403
403
|
if (cv === BREAK)
|
|
404
404
|
return BREAK;
|
|
405
405
|
else if (cv === REMOVE)
|
|
@@ -420,17 +420,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
420
420
|
visitAsync.BREAK = BREAK;
|
|
421
421
|
visitAsync.SKIP = SKIP;
|
|
422
422
|
visitAsync.REMOVE = REMOVE;
|
|
423
|
-
async function visitAsync_(key, node, visitor,
|
|
424
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
423
|
+
async function visitAsync_(key, node, visitor, path21) {
|
|
424
|
+
const ctrl = await callVisitor(key, node, visitor, path21);
|
|
425
425
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
426
|
-
replaceNode(key,
|
|
427
|
-
return visitAsync_(key, ctrl, visitor,
|
|
426
|
+
replaceNode(key, path21, ctrl);
|
|
427
|
+
return visitAsync_(key, ctrl, visitor, path21);
|
|
428
428
|
}
|
|
429
429
|
if (typeof ctrl !== "symbol") {
|
|
430
430
|
if (identity.isCollection(node)) {
|
|
431
|
-
|
|
431
|
+
path21 = Object.freeze(path21.concat(node));
|
|
432
432
|
for (let i = 0;i < node.items.length; ++i) {
|
|
433
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
433
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path21);
|
|
434
434
|
if (typeof ci === "number")
|
|
435
435
|
i = ci - 1;
|
|
436
436
|
else if (ci === BREAK)
|
|
@@ -441,13 +441,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
} else if (identity.isPair(node)) {
|
|
444
|
-
|
|
445
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
444
|
+
path21 = Object.freeze(path21.concat(node));
|
|
445
|
+
const ck = await visitAsync_("key", node.key, visitor, path21);
|
|
446
446
|
if (ck === BREAK)
|
|
447
447
|
return BREAK;
|
|
448
448
|
else if (ck === REMOVE)
|
|
449
449
|
node.key = null;
|
|
450
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
450
|
+
const cv = await visitAsync_("value", node.value, visitor, path21);
|
|
451
451
|
if (cv === BREAK)
|
|
452
452
|
return BREAK;
|
|
453
453
|
else if (cv === REMOVE)
|
|
@@ -474,23 +474,23 @@ var require_visit = __commonJS((exports) => {
|
|
|
474
474
|
}
|
|
475
475
|
return visitor;
|
|
476
476
|
}
|
|
477
|
-
function callVisitor(key, node, visitor,
|
|
477
|
+
function callVisitor(key, node, visitor, path21) {
|
|
478
478
|
if (typeof visitor === "function")
|
|
479
|
-
return visitor(key, node,
|
|
479
|
+
return visitor(key, node, path21);
|
|
480
480
|
if (identity.isMap(node))
|
|
481
|
-
return visitor.Map?.(key, node,
|
|
481
|
+
return visitor.Map?.(key, node, path21);
|
|
482
482
|
if (identity.isSeq(node))
|
|
483
|
-
return visitor.Seq?.(key, node,
|
|
483
|
+
return visitor.Seq?.(key, node, path21);
|
|
484
484
|
if (identity.isPair(node))
|
|
485
|
-
return visitor.Pair?.(key, node,
|
|
485
|
+
return visitor.Pair?.(key, node, path21);
|
|
486
486
|
if (identity.isScalar(node))
|
|
487
|
-
return visitor.Scalar?.(key, node,
|
|
487
|
+
return visitor.Scalar?.(key, node, path21);
|
|
488
488
|
if (identity.isAlias(node))
|
|
489
|
-
return visitor.Alias?.(key, node,
|
|
489
|
+
return visitor.Alias?.(key, node, path21);
|
|
490
490
|
return;
|
|
491
491
|
}
|
|
492
|
-
function replaceNode(key,
|
|
493
|
-
const parent =
|
|
492
|
+
function replaceNode(key, path21, node) {
|
|
493
|
+
const parent = path21[path21.length - 1];
|
|
494
494
|
if (identity.isCollection(parent)) {
|
|
495
495
|
parent.items[key] = node;
|
|
496
496
|
} else if (identity.isPair(parent)) {
|
|
@@ -1049,10 +1049,10 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1049
1049
|
var createNode = require_createNode();
|
|
1050
1050
|
var identity = require_identity();
|
|
1051
1051
|
var Node = require_Node();
|
|
1052
|
-
function collectionFromPath(schema,
|
|
1052
|
+
function collectionFromPath(schema, path21, value) {
|
|
1053
1053
|
let v = value;
|
|
1054
|
-
for (let i =
|
|
1055
|
-
const k =
|
|
1054
|
+
for (let i = path21.length - 1;i >= 0; --i) {
|
|
1055
|
+
const k = path21[i];
|
|
1056
1056
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
1057
1057
|
const a = [];
|
|
1058
1058
|
a[k] = v;
|
|
@@ -1071,7 +1071,7 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1071
1071
|
sourceObjects: new Map
|
|
1072
1072
|
});
|
|
1073
1073
|
}
|
|
1074
|
-
var isEmptyPath = (
|
|
1074
|
+
var isEmptyPath = (path21) => path21 == null || typeof path21 === "object" && !!path21[Symbol.iterator]().next().done;
|
|
1075
1075
|
|
|
1076
1076
|
class Collection extends Node.NodeBase {
|
|
1077
1077
|
constructor(type, schema) {
|
|
@@ -1092,11 +1092,11 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1092
1092
|
copy.range = this.range.slice();
|
|
1093
1093
|
return copy;
|
|
1094
1094
|
}
|
|
1095
|
-
addIn(
|
|
1096
|
-
if (isEmptyPath(
|
|
1095
|
+
addIn(path21, value) {
|
|
1096
|
+
if (isEmptyPath(path21))
|
|
1097
1097
|
this.add(value);
|
|
1098
1098
|
else {
|
|
1099
|
-
const [key, ...rest] =
|
|
1099
|
+
const [key, ...rest] = path21;
|
|
1100
1100
|
const node = this.get(key, true);
|
|
1101
1101
|
if (identity.isCollection(node))
|
|
1102
1102
|
node.addIn(rest, value);
|
|
@@ -1106,8 +1106,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1106
1106
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
1107
1107
|
}
|
|
1108
1108
|
}
|
|
1109
|
-
deleteIn(
|
|
1110
|
-
const [key, ...rest] =
|
|
1109
|
+
deleteIn(path21) {
|
|
1110
|
+
const [key, ...rest] = path21;
|
|
1111
1111
|
if (rest.length === 0)
|
|
1112
1112
|
return this.delete(key);
|
|
1113
1113
|
const node = this.get(key, true);
|
|
@@ -1116,8 +1116,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1116
1116
|
else
|
|
1117
1117
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
1118
1118
|
}
|
|
1119
|
-
getIn(
|
|
1120
|
-
const [key, ...rest] =
|
|
1119
|
+
getIn(path21, keepScalar) {
|
|
1120
|
+
const [key, ...rest] = path21;
|
|
1121
1121
|
const node = this.get(key, true);
|
|
1122
1122
|
if (rest.length === 0)
|
|
1123
1123
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -1132,15 +1132,15 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1132
1132
|
return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
|
|
1133
1133
|
});
|
|
1134
1134
|
}
|
|
1135
|
-
hasIn(
|
|
1136
|
-
const [key, ...rest] =
|
|
1135
|
+
hasIn(path21) {
|
|
1136
|
+
const [key, ...rest] = path21;
|
|
1137
1137
|
if (rest.length === 0)
|
|
1138
1138
|
return this.has(key);
|
|
1139
1139
|
const node = this.get(key, true);
|
|
1140
1140
|
return identity.isCollection(node) ? node.hasIn(rest) : false;
|
|
1141
1141
|
}
|
|
1142
|
-
setIn(
|
|
1143
|
-
const [key, ...rest] =
|
|
1142
|
+
setIn(path21, value) {
|
|
1143
|
+
const [key, ...rest] = path21;
|
|
1144
1144
|
if (rest.length === 0) {
|
|
1145
1145
|
this.set(key, value);
|
|
1146
1146
|
} else {
|
|
@@ -3533,9 +3533,9 @@ var require_Document = __commonJS((exports) => {
|
|
|
3533
3533
|
if (assertCollection(this.contents))
|
|
3534
3534
|
this.contents.add(value);
|
|
3535
3535
|
}
|
|
3536
|
-
addIn(
|
|
3536
|
+
addIn(path21, value) {
|
|
3537
3537
|
if (assertCollection(this.contents))
|
|
3538
|
-
this.contents.addIn(
|
|
3538
|
+
this.contents.addIn(path21, value);
|
|
3539
3539
|
}
|
|
3540
3540
|
createAlias(node, name) {
|
|
3541
3541
|
if (!node.anchor) {
|
|
@@ -3584,30 +3584,30 @@ var require_Document = __commonJS((exports) => {
|
|
|
3584
3584
|
delete(key) {
|
|
3585
3585
|
return assertCollection(this.contents) ? this.contents.delete(key) : false;
|
|
3586
3586
|
}
|
|
3587
|
-
deleteIn(
|
|
3588
|
-
if (Collection.isEmptyPath(
|
|
3587
|
+
deleteIn(path21) {
|
|
3588
|
+
if (Collection.isEmptyPath(path21)) {
|
|
3589
3589
|
if (this.contents == null)
|
|
3590
3590
|
return false;
|
|
3591
3591
|
this.contents = null;
|
|
3592
3592
|
return true;
|
|
3593
3593
|
}
|
|
3594
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3594
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path21) : false;
|
|
3595
3595
|
}
|
|
3596
3596
|
get(key, keepScalar) {
|
|
3597
3597
|
return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined;
|
|
3598
3598
|
}
|
|
3599
|
-
getIn(
|
|
3600
|
-
if (Collection.isEmptyPath(
|
|
3599
|
+
getIn(path21, keepScalar) {
|
|
3600
|
+
if (Collection.isEmptyPath(path21))
|
|
3601
3601
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3602
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3602
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path21, keepScalar) : undefined;
|
|
3603
3603
|
}
|
|
3604
3604
|
has(key) {
|
|
3605
3605
|
return identity.isCollection(this.contents) ? this.contents.has(key) : false;
|
|
3606
3606
|
}
|
|
3607
|
-
hasIn(
|
|
3608
|
-
if (Collection.isEmptyPath(
|
|
3607
|
+
hasIn(path21) {
|
|
3608
|
+
if (Collection.isEmptyPath(path21))
|
|
3609
3609
|
return this.contents !== undefined;
|
|
3610
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3610
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path21) : false;
|
|
3611
3611
|
}
|
|
3612
3612
|
set(key, value) {
|
|
3613
3613
|
if (this.contents == null) {
|
|
@@ -3616,13 +3616,13 @@ var require_Document = __commonJS((exports) => {
|
|
|
3616
3616
|
this.contents.set(key, value);
|
|
3617
3617
|
}
|
|
3618
3618
|
}
|
|
3619
|
-
setIn(
|
|
3620
|
-
if (Collection.isEmptyPath(
|
|
3619
|
+
setIn(path21, value) {
|
|
3620
|
+
if (Collection.isEmptyPath(path21)) {
|
|
3621
3621
|
this.contents = value;
|
|
3622
3622
|
} else if (this.contents == null) {
|
|
3623
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3623
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path21), value);
|
|
3624
3624
|
} else if (assertCollection(this.contents)) {
|
|
3625
|
-
this.contents.setIn(
|
|
3625
|
+
this.contents.setIn(path21, value);
|
|
3626
3626
|
}
|
|
3627
3627
|
}
|
|
3628
3628
|
setSchema(version, options = {}) {
|
|
@@ -5517,9 +5517,9 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5517
5517
|
visit.BREAK = BREAK;
|
|
5518
5518
|
visit.SKIP = SKIP;
|
|
5519
5519
|
visit.REMOVE = REMOVE;
|
|
5520
|
-
visit.itemAtPath = (cst,
|
|
5520
|
+
visit.itemAtPath = (cst, path21) => {
|
|
5521
5521
|
let item = cst;
|
|
5522
|
-
for (const [field, index] of
|
|
5522
|
+
for (const [field, index] of path21) {
|
|
5523
5523
|
const tok = item?.[field];
|
|
5524
5524
|
if (tok && "items" in tok) {
|
|
5525
5525
|
item = tok.items[index];
|
|
@@ -5528,23 +5528,23 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5528
5528
|
}
|
|
5529
5529
|
return item;
|
|
5530
5530
|
};
|
|
5531
|
-
visit.parentCollection = (cst,
|
|
5532
|
-
const parent = visit.itemAtPath(cst,
|
|
5533
|
-
const field =
|
|
5531
|
+
visit.parentCollection = (cst, path21) => {
|
|
5532
|
+
const parent = visit.itemAtPath(cst, path21.slice(0, -1));
|
|
5533
|
+
const field = path21[path21.length - 1][0];
|
|
5534
5534
|
const coll = parent?.[field];
|
|
5535
5535
|
if (coll && "items" in coll)
|
|
5536
5536
|
return coll;
|
|
5537
5537
|
throw new Error("Parent collection not found");
|
|
5538
5538
|
};
|
|
5539
|
-
function _visit(
|
|
5540
|
-
let ctrl = visitor(item,
|
|
5539
|
+
function _visit(path21, item, visitor) {
|
|
5540
|
+
let ctrl = visitor(item, path21);
|
|
5541
5541
|
if (typeof ctrl === "symbol")
|
|
5542
5542
|
return ctrl;
|
|
5543
5543
|
for (const field of ["key", "value"]) {
|
|
5544
5544
|
const token = item[field];
|
|
5545
5545
|
if (token && "items" in token) {
|
|
5546
5546
|
for (let i = 0;i < token.items.length; ++i) {
|
|
5547
|
-
const ci = _visit(Object.freeze(
|
|
5547
|
+
const ci = _visit(Object.freeze(path21.concat([[field, i]])), token.items[i], visitor);
|
|
5548
5548
|
if (typeof ci === "number")
|
|
5549
5549
|
i = ci - 1;
|
|
5550
5550
|
else if (ci === BREAK)
|
|
@@ -5555,10 +5555,10 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5555
5555
|
}
|
|
5556
5556
|
}
|
|
5557
5557
|
if (typeof ctrl === "function" && field === "key")
|
|
5558
|
-
ctrl = ctrl(item,
|
|
5558
|
+
ctrl = ctrl(item, path21);
|
|
5559
5559
|
}
|
|
5560
5560
|
}
|
|
5561
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5561
|
+
return typeof ctrl === "function" ? ctrl(item, path21) : ctrl;
|
|
5562
5562
|
}
|
|
5563
5563
|
exports.visit = visit;
|
|
5564
5564
|
});
|
|
@@ -6827,14 +6827,14 @@ var require_parser = __commonJS((exports) => {
|
|
|
6827
6827
|
case "scalar":
|
|
6828
6828
|
case "single-quoted-scalar":
|
|
6829
6829
|
case "double-quoted-scalar": {
|
|
6830
|
-
const
|
|
6830
|
+
const fs16 = this.flowScalar(this.type);
|
|
6831
6831
|
if (atNextItem || it.value) {
|
|
6832
|
-
map.items.push({ start, key:
|
|
6832
|
+
map.items.push({ start, key: fs16, sep: [] });
|
|
6833
6833
|
this.onKeyLine = true;
|
|
6834
6834
|
} else if (it.sep) {
|
|
6835
|
-
this.stack.push(
|
|
6835
|
+
this.stack.push(fs16);
|
|
6836
6836
|
} else {
|
|
6837
|
-
Object.assign(it, { key:
|
|
6837
|
+
Object.assign(it, { key: fs16, sep: [] });
|
|
6838
6838
|
this.onKeyLine = true;
|
|
6839
6839
|
}
|
|
6840
6840
|
return;
|
|
@@ -6962,13 +6962,13 @@ var require_parser = __commonJS((exports) => {
|
|
|
6962
6962
|
case "scalar":
|
|
6963
6963
|
case "single-quoted-scalar":
|
|
6964
6964
|
case "double-quoted-scalar": {
|
|
6965
|
-
const
|
|
6965
|
+
const fs16 = this.flowScalar(this.type);
|
|
6966
6966
|
if (!it || it.value)
|
|
6967
|
-
fc.items.push({ start: [], key:
|
|
6967
|
+
fc.items.push({ start: [], key: fs16, sep: [] });
|
|
6968
6968
|
else if (it.sep)
|
|
6969
|
-
this.stack.push(
|
|
6969
|
+
this.stack.push(fs16);
|
|
6970
6970
|
else
|
|
6971
|
-
Object.assign(it, { key:
|
|
6971
|
+
Object.assign(it, { key: fs16, sep: [] });
|
|
6972
6972
|
return;
|
|
6973
6973
|
}
|
|
6974
6974
|
case "flow-map-end":
|
|
@@ -7314,11 +7314,11 @@ function shouldStopByStuck(history, cfg) {
|
|
|
7314
7314
|
async function withTimeout3(p, timeoutMs) {
|
|
7315
7315
|
if (timeoutMs <= 0)
|
|
7316
7316
|
return await p;
|
|
7317
|
-
return await new Promise((
|
|
7317
|
+
return await new Promise((resolve17, reject) => {
|
|
7318
7318
|
const timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
7319
7319
|
Promise.resolve(p).then((v) => {
|
|
7320
7320
|
clearTimeout(timer);
|
|
7321
|
-
|
|
7321
|
+
resolve17(v);
|
|
7322
7322
|
}, (err) => {
|
|
7323
7323
|
clearTimeout(timer);
|
|
7324
7324
|
reject(err);
|
|
@@ -10679,8 +10679,8 @@ class ApprovalStore {
|
|
|
10679
10679
|
// lib/session-worktree.ts
|
|
10680
10680
|
init_worktree_ops();
|
|
10681
10681
|
import { execFile as execFile3 } from "node:child_process";
|
|
10682
|
-
import { promises as
|
|
10683
|
-
import * as
|
|
10682
|
+
import { promises as fs11 } from "node:fs";
|
|
10683
|
+
import * as path13 from "node:path";
|
|
10684
10684
|
|
|
10685
10685
|
// lib/file-lock.ts
|
|
10686
10686
|
import { promises as fs8 } from "node:fs";
|
|
@@ -10796,25 +10796,120 @@ function sleep(ms) {
|
|
|
10796
10796
|
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
10797
10797
|
}
|
|
10798
10798
|
|
|
10799
|
-
// lib/
|
|
10799
|
+
// lib/merge-journal.ts
|
|
10800
10800
|
import { promises as fs9 } from "node:fs";
|
|
10801
10801
|
import * as path11 from "node:path";
|
|
10802
|
+
var MERGE_JOURNAL_VERSION = 1;
|
|
10803
|
+
var MERGE_PHASE_RANK = {
|
|
10804
|
+
started: 1,
|
|
10805
|
+
committed: 2,
|
|
10806
|
+
cleaned: 3,
|
|
10807
|
+
done: 4,
|
|
10808
|
+
aborted: 0
|
|
10809
|
+
};
|
|
10810
|
+
function isCommittedOrLater(phase) {
|
|
10811
|
+
return MERGE_PHASE_RANK[phase] >= MERGE_PHASE_RANK.committed;
|
|
10812
|
+
}
|
|
10813
|
+
function journalDir(mainRoot) {
|
|
10814
|
+
return path11.join(runtimeDir(path11.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
10815
|
+
}
|
|
10816
|
+
function mergeJournalPath(mainRoot) {
|
|
10817
|
+
return path11.join(journalDir(mainRoot), "merge-journal.json");
|
|
10818
|
+
}
|
|
10819
|
+
function mergeJournalLockPath(mainRoot) {
|
|
10820
|
+
return path11.join(journalDir(mainRoot), "merge-journal.lock");
|
|
10821
|
+
}
|
|
10822
|
+
async function readJournalFile(mainRoot) {
|
|
10823
|
+
const file = mergeJournalPath(mainRoot);
|
|
10824
|
+
try {
|
|
10825
|
+
const raw = await fs9.readFile(file, "utf8");
|
|
10826
|
+
const parsed = JSON.parse(raw);
|
|
10827
|
+
if (parsed.version !== MERGE_JOURNAL_VERSION || !Array.isArray(parsed.entries)) {
|
|
10828
|
+
return { version: MERGE_JOURNAL_VERSION, entries: [] };
|
|
10829
|
+
}
|
|
10830
|
+
return parsed;
|
|
10831
|
+
} catch (err) {
|
|
10832
|
+
const e = err;
|
|
10833
|
+
if (e.code === "ENOENT")
|
|
10834
|
+
return { version: MERGE_JOURNAL_VERSION, entries: [] };
|
|
10835
|
+
return { version: MERGE_JOURNAL_VERSION, entries: [] };
|
|
10836
|
+
}
|
|
10837
|
+
}
|
|
10838
|
+
async function writeJournalFile(mainRoot, payload) {
|
|
10839
|
+
const file = mergeJournalPath(mainRoot);
|
|
10840
|
+
await fs9.mkdir(path11.dirname(file), { recursive: true });
|
|
10841
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
10842
|
+
await fs9.writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
10843
|
+
await fs9.rename(tmp, file);
|
|
10844
|
+
}
|
|
10845
|
+
async function listMergeJournalEntries(mainRoot) {
|
|
10846
|
+
const f = await readJournalFile(mainRoot);
|
|
10847
|
+
return f.entries;
|
|
10848
|
+
}
|
|
10849
|
+
async function getMergeJournalEntry(mainRoot, sessionId) {
|
|
10850
|
+
const f = await readJournalFile(mainRoot);
|
|
10851
|
+
return f.entries.find((e) => e.sessionId === sessionId) ?? null;
|
|
10852
|
+
}
|
|
10853
|
+
async function recordMergeJournalPhase(mainRoot, input) {
|
|
10854
|
+
const lockPath = mergeJournalLockPath(mainRoot);
|
|
10855
|
+
await fs9.mkdir(path11.dirname(lockPath), { recursive: true });
|
|
10856
|
+
await withFileLock(lockPath, async () => {
|
|
10857
|
+
const f = await readJournalFile(mainRoot);
|
|
10858
|
+
const now = new Date().toISOString();
|
|
10859
|
+
const idx = f.entries.findIndex((e) => e.sessionId === input.sessionId);
|
|
10860
|
+
if (idx < 0) {
|
|
10861
|
+
const entry = {
|
|
10862
|
+
sessionId: input.sessionId,
|
|
10863
|
+
approvalSha: input.approvalSha,
|
|
10864
|
+
phase: input.phase,
|
|
10865
|
+
updatedAt: now,
|
|
10866
|
+
...input.mergedCommitSha ? { mergedCommitSha: input.mergedCommitSha } : {}
|
|
10867
|
+
};
|
|
10868
|
+
f.entries.push(entry);
|
|
10869
|
+
} else {
|
|
10870
|
+
const prev = f.entries[idx];
|
|
10871
|
+
if (prev.approvalSha !== input.approvalSha) {
|
|
10872
|
+
f.entries[idx] = {
|
|
10873
|
+
sessionId: input.sessionId,
|
|
10874
|
+
approvalSha: input.approvalSha,
|
|
10875
|
+
phase: input.phase,
|
|
10876
|
+
updatedAt: now,
|
|
10877
|
+
...input.mergedCommitSha ? { mergedCommitSha: input.mergedCommitSha } : {}
|
|
10878
|
+
};
|
|
10879
|
+
} else {
|
|
10880
|
+
const mergedCommitSha = input.mergedCommitSha ?? prev.mergedCommitSha;
|
|
10881
|
+
f.entries[idx] = {
|
|
10882
|
+
sessionId: input.sessionId,
|
|
10883
|
+
approvalSha: input.approvalSha,
|
|
10884
|
+
phase: input.phase,
|
|
10885
|
+
updatedAt: now,
|
|
10886
|
+
...mergedCommitSha ? { mergedCommitSha } : {}
|
|
10887
|
+
};
|
|
10888
|
+
}
|
|
10889
|
+
}
|
|
10890
|
+
await writeJournalFile(mainRoot, f);
|
|
10891
|
+
});
|
|
10892
|
+
}
|
|
10893
|
+
|
|
10894
|
+
// lib/parent-map-store.ts
|
|
10895
|
+
import { promises as fs10 } from "node:fs";
|
|
10896
|
+
import * as path12 from "node:path";
|
|
10802
10897
|
var PARENT_MAP_VERSION = 1;
|
|
10803
10898
|
var PARENT_MAP_LOCK_TIMEOUT_MS = 2000;
|
|
10804
10899
|
var PARENT_MAP_MAX_ENTRIES = 256;
|
|
10805
10900
|
function parentMapDir(mainRoot) {
|
|
10806
|
-
return
|
|
10901
|
+
return path12.join(runtimeDir(path12.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
10807
10902
|
}
|
|
10808
10903
|
function parentMapPath(mainRoot) {
|
|
10809
|
-
return
|
|
10904
|
+
return path12.join(parentMapDir(mainRoot), "parent-map.json");
|
|
10810
10905
|
}
|
|
10811
10906
|
function parentMapLockPath(mainRoot) {
|
|
10812
|
-
return
|
|
10907
|
+
return path12.join(parentMapDir(mainRoot), "parent-map.lock");
|
|
10813
10908
|
}
|
|
10814
10909
|
async function readParentMapFile(mainRoot) {
|
|
10815
10910
|
const file = parentMapPath(mainRoot);
|
|
10816
10911
|
try {
|
|
10817
|
-
const raw = await
|
|
10912
|
+
const raw = await fs10.readFile(file, "utf8");
|
|
10818
10913
|
const parsed = JSON.parse(raw);
|
|
10819
10914
|
if (parsed.version !== PARENT_MAP_VERSION || !Array.isArray(parsed.entries)) {
|
|
10820
10915
|
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
@@ -10829,10 +10924,10 @@ async function readParentMapFile(mainRoot) {
|
|
|
10829
10924
|
}
|
|
10830
10925
|
async function writeParentMapFile(mainRoot, payload) {
|
|
10831
10926
|
const file = parentMapPath(mainRoot);
|
|
10832
|
-
await
|
|
10927
|
+
await fs10.mkdir(path12.dirname(file), { recursive: true });
|
|
10833
10928
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
10834
|
-
await
|
|
10835
|
-
await
|
|
10929
|
+
await fs10.writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
10930
|
+
await fs10.rename(tmp, file);
|
|
10836
10931
|
}
|
|
10837
10932
|
function capEntriesByTsDesc(entries) {
|
|
10838
10933
|
if (entries.length <= PARENT_MAP_MAX_ENTRIES)
|
|
@@ -10864,7 +10959,7 @@ async function loadParentMap(mainRoot) {
|
|
|
10864
10959
|
}
|
|
10865
10960
|
async function mutateParentMap(mainRoot, mutator, opts = {}) {
|
|
10866
10961
|
const lockPath = parentMapLockPath(mainRoot);
|
|
10867
|
-
await
|
|
10962
|
+
await fs10.mkdir(path12.dirname(lockPath), { recursive: true });
|
|
10868
10963
|
const lockOpts = {
|
|
10869
10964
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
10870
10965
|
...opts
|
|
@@ -10883,7 +10978,7 @@ async function appendParentEntry(mainRoot, childID, parentID, ts, opts = {}) {
|
|
|
10883
10978
|
if (!childID || !parentID)
|
|
10884
10979
|
return;
|
|
10885
10980
|
const lockPath = parentMapLockPath(mainRoot);
|
|
10886
|
-
await
|
|
10981
|
+
await fs10.mkdir(path12.dirname(lockPath), { recursive: true });
|
|
10887
10982
|
const lockOpts = {
|
|
10888
10983
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
10889
10984
|
...opts
|
|
@@ -11002,20 +11097,20 @@ function sweepExpiredSessionParents(now = Date.now()) {
|
|
|
11002
11097
|
|
|
11003
11098
|
// lib/session-worktree.ts
|
|
11004
11099
|
var REGISTRY_VERSION = 1;
|
|
11005
|
-
var DEFAULT_WORKTREE_SUBDIR =
|
|
11100
|
+
var DEFAULT_WORKTREE_SUBDIR = path13.join(".git", "codeforge-worktrees");
|
|
11006
11101
|
function debugLog(msg) {
|
|
11007
11102
|
if (process.env["CODEFORGE_DEBUG"]) {
|
|
11008
11103
|
console.debug(`[session-worktree] ${msg}`);
|
|
11009
11104
|
}
|
|
11010
11105
|
}
|
|
11011
11106
|
function registryDir(mainRoot) {
|
|
11012
|
-
return
|
|
11107
|
+
return path13.join(runtimeDir(path13.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
11013
11108
|
}
|
|
11014
11109
|
function registryPath(mainRoot) {
|
|
11015
|
-
return
|
|
11110
|
+
return path13.join(registryDir(mainRoot), "registry.json");
|
|
11016
11111
|
}
|
|
11017
11112
|
function registryLockPath(mainRoot) {
|
|
11018
|
-
return
|
|
11113
|
+
return path13.join(registryDir(mainRoot), "registry.lock");
|
|
11019
11114
|
}
|
|
11020
11115
|
|
|
11021
11116
|
class RegistryCorruptError extends Error {
|
|
@@ -11046,7 +11141,7 @@ async function readRegistryResult(mainRoot) {
|
|
|
11046
11141
|
const file = registryPath(mainRoot);
|
|
11047
11142
|
let raw;
|
|
11048
11143
|
try {
|
|
11049
|
-
raw = await
|
|
11144
|
+
raw = await fs11.readFile(file, "utf8");
|
|
11050
11145
|
} catch (err) {
|
|
11051
11146
|
const e = err;
|
|
11052
11147
|
if (e.code === "ENOENT")
|
|
@@ -11094,24 +11189,24 @@ async function readRegistry(mainRoot) {
|
|
|
11094
11189
|
}
|
|
11095
11190
|
async function writeRegistry(mainRoot, reg) {
|
|
11096
11191
|
const file = registryPath(mainRoot);
|
|
11097
|
-
await
|
|
11192
|
+
await fs11.mkdir(path13.dirname(file), { recursive: true });
|
|
11098
11193
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
11099
|
-
await
|
|
11100
|
-
await
|
|
11194
|
+
await fs11.writeFile(tmp, JSON.stringify(reg, null, 2), "utf8");
|
|
11195
|
+
await fs11.rename(tmp, file);
|
|
11101
11196
|
}
|
|
11102
11197
|
async function backupCorruptRegistry(mainRoot) {
|
|
11103
11198
|
const file = registryPath(mainRoot);
|
|
11104
|
-
const dir =
|
|
11105
|
-
const base =
|
|
11199
|
+
const dir = path13.dirname(file);
|
|
11200
|
+
const base = path13.basename(file);
|
|
11106
11201
|
try {
|
|
11107
|
-
const names = await
|
|
11202
|
+
const names = await fs11.readdir(dir);
|
|
11108
11203
|
const existing = names.find((n) => n.startsWith(`${base}.corrupt.`));
|
|
11109
11204
|
if (existing)
|
|
11110
|
-
return
|
|
11205
|
+
return path13.join(dir, existing);
|
|
11111
11206
|
} catch {}
|
|
11112
11207
|
const backup = `${file}.corrupt.${Date.now()}`;
|
|
11113
11208
|
try {
|
|
11114
|
-
await
|
|
11209
|
+
await fs11.copyFile(file, backup);
|
|
11115
11210
|
return backup;
|
|
11116
11211
|
} catch {
|
|
11117
11212
|
return;
|
|
@@ -11131,7 +11226,7 @@ async function loadRegistryForMutation(mainRoot) {
|
|
|
11131
11226
|
}
|
|
11132
11227
|
async function mutateRegistry(mainRoot, fn) {
|
|
11133
11228
|
const lockPath = registryLockPath(mainRoot);
|
|
11134
|
-
await
|
|
11229
|
+
await fs11.mkdir(path13.dirname(lockPath), { recursive: true });
|
|
11135
11230
|
return await withFileLock(lockPath, async () => {
|
|
11136
11231
|
const reg = await loadRegistryForMutation(mainRoot);
|
|
11137
11232
|
const result = await fn(reg);
|
|
@@ -11143,11 +11238,11 @@ async function bindSessionWorktree(opts) {
|
|
|
11143
11238
|
if (!opts.sessionId || opts.sessionId.trim() === "") {
|
|
11144
11239
|
throw new Error("bindSessionWorktree: sessionId 不能为空");
|
|
11145
11240
|
}
|
|
11146
|
-
const mainRoot =
|
|
11241
|
+
const mainRoot = path13.resolve(opts.mainRoot);
|
|
11147
11242
|
const branch = opts.branchName ?? `codeforge/session-${opts.sessionId}`;
|
|
11148
|
-
const worktreesDir = opts.worktrees_dir ??
|
|
11243
|
+
const worktreesDir = opts.worktrees_dir ?? path13.join(mainRoot, DEFAULT_WORKTREE_SUBDIR);
|
|
11149
11244
|
const lockPath = registryLockPath(mainRoot);
|
|
11150
|
-
await
|
|
11245
|
+
await fs11.mkdir(path13.dirname(lockPath), { recursive: true });
|
|
11151
11246
|
return await withFileLock(lockPath, async () => {
|
|
11152
11247
|
const reg = await loadRegistryForMutation(mainRoot);
|
|
11153
11248
|
const existing = reg.entries.find((e) => e.sessionId === opts.sessionId);
|
|
@@ -11198,7 +11293,7 @@ async function registryHasEntries(mainRoot) {
|
|
|
11198
11293
|
var OWNER_RESOLVE_MAX_DEPTH = 16;
|
|
11199
11294
|
async function resolveWorktreeOwner(opts) {
|
|
11200
11295
|
const sid = opts.sessionId;
|
|
11201
|
-
const mainRoot =
|
|
11296
|
+
const mainRoot = path13.resolve(opts.mainRoot);
|
|
11202
11297
|
const snap = await readRegistryResult(mainRoot);
|
|
11203
11298
|
if (snap.kind === "corrupt" || snap.kind === "version_too_new") {
|
|
11204
11299
|
return {
|
|
@@ -11212,8 +11307,8 @@ async function resolveWorktreeOwner(opts) {
|
|
|
11212
11307
|
const entries = snap.kind === "ok" ? snap.registry.entries : [];
|
|
11213
11308
|
const activeById = (id) => entries.find((e) => e.sessionId === id && e.status === "active") ?? null;
|
|
11214
11309
|
if (opts.worktreePath) {
|
|
11215
|
-
const target =
|
|
11216
|
-
const byPath = entries.find((e) => e.status === "active" &&
|
|
11310
|
+
const target = path13.resolve(opts.worktreePath);
|
|
11311
|
+
const byPath = entries.find((e) => e.status === "active" && path13.resolve(e.worktreePath) === target);
|
|
11217
11312
|
if (byPath) {
|
|
11218
11313
|
return { ok: true, ownerSessionId: byPath.sessionId, entry: byPath, via: "worktree-path" };
|
|
11219
11314
|
}
|
|
@@ -11257,7 +11352,7 @@ async function touchEntryUpdatedAt(opts) {
|
|
|
11257
11352
|
});
|
|
11258
11353
|
}
|
|
11259
11354
|
async function mergeSessionBack(opts) {
|
|
11260
|
-
const mainRoot =
|
|
11355
|
+
const mainRoot = path13.resolve(opts.mainRoot);
|
|
11261
11356
|
const entry = await getSessionWorktree(opts.sessionId, mainRoot);
|
|
11262
11357
|
if (!entry) {
|
|
11263
11358
|
throw new Error(`mergeSessionBack: session ${opts.sessionId} 没有绑定 worktree`);
|
|
@@ -11271,6 +11366,38 @@ async function mergeSessionBack(opts) {
|
|
|
11271
11366
|
const wt = entry.worktreePath;
|
|
11272
11367
|
const branch = entry.branch;
|
|
11273
11368
|
const baseSha = entry.baseSha;
|
|
11369
|
+
const approvalSha = (await runGit2(wt, ["rev-parse", "HEAD"])).trim();
|
|
11370
|
+
{
|
|
11371
|
+
const existing = await getMergeJournalEntry(mainRoot, opts.sessionId).catch(() => null);
|
|
11372
|
+
let recoveredSha;
|
|
11373
|
+
if (existing && existing.approvalSha === approvalSha && isCommittedOrLater(existing.phase) && existing.mergedCommitSha) {
|
|
11374
|
+
recoveredSha = existing.mergedCommitSha;
|
|
11375
|
+
} else {
|
|
11376
|
+
const footerSha = await findMergeCommitBySession(mainRoot, opts.sessionId);
|
|
11377
|
+
if (footerSha)
|
|
11378
|
+
recoveredSha = footerSha;
|
|
11379
|
+
}
|
|
11380
|
+
if (recoveredSha) {
|
|
11381
|
+
await finalizeMergeIdempotent({
|
|
11382
|
+
mainRoot,
|
|
11383
|
+
sessionId: opts.sessionId,
|
|
11384
|
+
worktreePath: wt,
|
|
11385
|
+
branch,
|
|
11386
|
+
approvalSha,
|
|
11387
|
+
mergedCommitSha: recoveredSha,
|
|
11388
|
+
...opts.planStore ? { planStore: opts.planStore } : {},
|
|
11389
|
+
...entry.requiredPlanId ? { requiredPlanId: entry.requiredPlanId } : {}
|
|
11390
|
+
});
|
|
11391
|
+
return { sha: recoveredSha, squashedCommits: [] };
|
|
11392
|
+
}
|
|
11393
|
+
}
|
|
11394
|
+
await recordMergeJournalPhase(mainRoot, {
|
|
11395
|
+
sessionId: opts.sessionId,
|
|
11396
|
+
approvalSha,
|
|
11397
|
+
phase: "started"
|
|
11398
|
+
}).catch((err) => {
|
|
11399
|
+
debugLog(`recordMergeJournalPhase(started) 失败 (${opts.sessionId}): ${err.message}`);
|
|
11400
|
+
});
|
|
11274
11401
|
const wtStatus = (await runGit2(wt, ["status", "--porcelain"])).trim();
|
|
11275
11402
|
if (wtStatus.length > 0) {
|
|
11276
11403
|
await runGit2(wt, ["add", "-A"]);
|
|
@@ -11310,11 +11437,27 @@ async function mergeSessionBack(opts) {
|
|
|
11310
11437
|
} else {}
|
|
11311
11438
|
const squashedRaw = await runGit2(wt, ["log", "--format=%s", `${baseSha}..HEAD`]);
|
|
11312
11439
|
const squashedCommits = squashedRaw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
11313
|
-
const message = opts.commitMessage ?? buildMergeMessage(
|
|
11440
|
+
const message = opts.commitMessage ?? await buildMergeMessage({
|
|
11441
|
+
sessionId: opts.sessionId,
|
|
11442
|
+
branch,
|
|
11443
|
+
baseSha,
|
|
11444
|
+
squashed: squashedCommits,
|
|
11445
|
+
...opts.planStore ? { planStore: opts.planStore } : {},
|
|
11446
|
+
...entry.requiredPlanId ? { requiredPlanId: entry.requiredPlanId } : {},
|
|
11447
|
+
...opts.summary ? { summary: opts.summary } : {}
|
|
11448
|
+
});
|
|
11314
11449
|
await runGitWithEnv(mainRoot, ["commit", "-m", message], {
|
|
11315
11450
|
SKIP_DEV_SYNC_CHECK: "1"
|
|
11316
11451
|
});
|
|
11317
11452
|
const newSha = (await runGit2(mainRoot, ["rev-parse", "HEAD"])).trim();
|
|
11453
|
+
await recordMergeJournalPhase(mainRoot, {
|
|
11454
|
+
sessionId: opts.sessionId,
|
|
11455
|
+
approvalSha,
|
|
11456
|
+
phase: "committed",
|
|
11457
|
+
mergedCommitSha: newSha
|
|
11458
|
+
}).catch((err) => {
|
|
11459
|
+
debugLog(`recordMergeJournalPhase(committed) 失败 (${opts.sessionId}): ${err.message}`);
|
|
11460
|
+
});
|
|
11318
11461
|
try {
|
|
11319
11462
|
await removeWorktree({ root: mainRoot, worktree_path: wt, force: true });
|
|
11320
11463
|
} catch (err) {
|
|
@@ -11324,6 +11467,14 @@ async function mergeSessionBack(opts) {
|
|
|
11324
11467
|
debugLog(`deleteBranchIfExists (merge) 非预期失败: ${err.message}`);
|
|
11325
11468
|
return { deleted: false };
|
|
11326
11469
|
});
|
|
11470
|
+
await recordMergeJournalPhase(mainRoot, {
|
|
11471
|
+
sessionId: opts.sessionId,
|
|
11472
|
+
approvalSha,
|
|
11473
|
+
phase: "cleaned",
|
|
11474
|
+
mergedCommitSha: newSha
|
|
11475
|
+
}).catch((err) => {
|
|
11476
|
+
debugLog(`recordMergeJournalPhase(cleaned) 失败 (${opts.sessionId}): ${err.message}`);
|
|
11477
|
+
});
|
|
11327
11478
|
await mutateRegistry(mainRoot, (reg) => {
|
|
11328
11479
|
const e = reg.entries.find((x) => x.sessionId === opts.sessionId);
|
|
11329
11480
|
if (e) {
|
|
@@ -11331,6 +11482,14 @@ async function mergeSessionBack(opts) {
|
|
|
11331
11482
|
e.updatedAt = new Date().toISOString();
|
|
11332
11483
|
}
|
|
11333
11484
|
});
|
|
11485
|
+
await recordMergeJournalPhase(mainRoot, {
|
|
11486
|
+
sessionId: opts.sessionId,
|
|
11487
|
+
approvalSha,
|
|
11488
|
+
phase: "done",
|
|
11489
|
+
mergedCommitSha: newSha
|
|
11490
|
+
}).catch((err) => {
|
|
11491
|
+
debugLog(`recordMergeJournalPhase(done) 失败 (${opts.sessionId}): ${err.message}`);
|
|
11492
|
+
});
|
|
11334
11493
|
if (opts.planStore && entry.requiredPlanId) {
|
|
11335
11494
|
await opts.planStore.markMerged(entry.requiredPlanId, opts.sessionId).catch((err) => {
|
|
11336
11495
|
console.warn(`[session-worktree] planStore.markMerged(${entry.requiredPlanId}) 失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -11338,8 +11497,162 @@ async function mergeSessionBack(opts) {
|
|
|
11338
11497
|
}
|
|
11339
11498
|
return { sha: newSha, squashedCommits };
|
|
11340
11499
|
}
|
|
11500
|
+
async function findMergeCommitBySession(mainRoot, sessionId) {
|
|
11501
|
+
try {
|
|
11502
|
+
const out = await runGit2(path13.resolve(mainRoot), [
|
|
11503
|
+
"log",
|
|
11504
|
+
"-F",
|
|
11505
|
+
`--grep=Codeforge-Session: ${sessionId}`,
|
|
11506
|
+
"--format=%H",
|
|
11507
|
+
"-n",
|
|
11508
|
+
"1"
|
|
11509
|
+
]);
|
|
11510
|
+
return out.trim().split(/\r?\n/)[0]?.trim() ?? "";
|
|
11511
|
+
} catch {
|
|
11512
|
+
return "";
|
|
11513
|
+
}
|
|
11514
|
+
}
|
|
11515
|
+
async function finalizeMergeIdempotent(opts) {
|
|
11516
|
+
const { mainRoot, sessionId, worktreePath, branch } = opts;
|
|
11517
|
+
try {
|
|
11518
|
+
await removeWorktree({ root: mainRoot, worktree_path: worktreePath, force: true });
|
|
11519
|
+
} catch (err) {
|
|
11520
|
+
debugLog(`finalizeMergeIdempotent removeWorktree 非预期失败 (${sessionId}): ${err.message}`);
|
|
11521
|
+
}
|
|
11522
|
+
await deleteBranchIfExists({ root: mainRoot, branch }).catch(() => ({ deleted: false }));
|
|
11523
|
+
await recordMergeJournalPhase(mainRoot, {
|
|
11524
|
+
sessionId,
|
|
11525
|
+
approvalSha: opts.approvalSha,
|
|
11526
|
+
phase: "cleaned",
|
|
11527
|
+
mergedCommitSha: opts.mergedCommitSha
|
|
11528
|
+
}).catch(() => {});
|
|
11529
|
+
await mutateRegistry(mainRoot, (reg) => {
|
|
11530
|
+
const e = reg.entries.find((x) => x.sessionId === sessionId);
|
|
11531
|
+
if (e && e.status !== "merged") {
|
|
11532
|
+
e.status = "merged";
|
|
11533
|
+
e.updatedAt = new Date().toISOString();
|
|
11534
|
+
}
|
|
11535
|
+
});
|
|
11536
|
+
await recordMergeJournalPhase(mainRoot, {
|
|
11537
|
+
sessionId,
|
|
11538
|
+
approvalSha: opts.approvalSha,
|
|
11539
|
+
phase: "done",
|
|
11540
|
+
mergedCommitSha: opts.mergedCommitSha
|
|
11541
|
+
}).catch(() => {});
|
|
11542
|
+
if (opts.planStore && opts.requiredPlanId) {
|
|
11543
|
+
await opts.planStore.markMerged(opts.requiredPlanId, sessionId).catch((err) => {
|
|
11544
|
+
console.warn(`[session-worktree] finalizeMergeIdempotent planStore.markMerged(${opts.requiredPlanId}) 失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
11545
|
+
});
|
|
11546
|
+
}
|
|
11547
|
+
}
|
|
11548
|
+
async function reconcileInterruptedMerges(mainRoot, opts = {}) {
|
|
11549
|
+
const resolved = path13.resolve(mainRoot);
|
|
11550
|
+
const result = {
|
|
11551
|
+
recoveredMerged: [],
|
|
11552
|
+
rolledBack: [],
|
|
11553
|
+
conflicts: []
|
|
11554
|
+
};
|
|
11555
|
+
const snap = await readRegistryResult(resolved);
|
|
11556
|
+
if (snap.kind === "corrupt") {
|
|
11557
|
+
result.registryUnreadable = { reason: "corrupt", detail: snap.reason, path: snap.path };
|
|
11558
|
+
return result;
|
|
11559
|
+
}
|
|
11560
|
+
if (snap.kind === "version_too_new") {
|
|
11561
|
+
result.registryUnreadable = {
|
|
11562
|
+
reason: "version_too_new",
|
|
11563
|
+
detail: `v${snap.foundVersion} > 支持 v${snap.supportedVersion}`,
|
|
11564
|
+
path: snap.path
|
|
11565
|
+
};
|
|
11566
|
+
return result;
|
|
11567
|
+
}
|
|
11568
|
+
const entries = snap.kind === "ok" ? snap.registry.entries : [];
|
|
11569
|
+
const journal = await listMergeJournalEntries(resolved).catch(() => []);
|
|
11570
|
+
if (journal.length === 0)
|
|
11571
|
+
return result;
|
|
11572
|
+
const hasStaged = opts.hasStagedOverride ?? (async (root) => {
|
|
11573
|
+
try {
|
|
11574
|
+
await runGit2(root, ["diff", "--cached", "--quiet"]);
|
|
11575
|
+
return false;
|
|
11576
|
+
} catch {
|
|
11577
|
+
return true;
|
|
11578
|
+
}
|
|
11579
|
+
});
|
|
11580
|
+
for (const j of journal) {
|
|
11581
|
+
if (j.phase === "done" || j.phase === "aborted")
|
|
11582
|
+
continue;
|
|
11583
|
+
const regEntry = entries.find((e) => e.sessionId === j.sessionId);
|
|
11584
|
+
const footerSha = await findMergeCommitBySession(resolved, j.sessionId);
|
|
11585
|
+
const footerHit = footerSha.length > 0;
|
|
11586
|
+
const journalCommitted = isCommittedOrLater(j.phase);
|
|
11587
|
+
const conflictA = journalCommitted && !footerHit;
|
|
11588
|
+
const conflictB = !journalCommitted && footerHit;
|
|
11589
|
+
const conflictC = journalCommitted && footerHit && !!j.mergedCommitSha && j.mergedCommitSha !== footerSha;
|
|
11590
|
+
if (conflictA || conflictB || conflictC) {
|
|
11591
|
+
result.conflicts.push({
|
|
11592
|
+
sessionId: j.sessionId,
|
|
11593
|
+
state: "D",
|
|
11594
|
+
action: conflictA ? "journal=committed 但主仓无 merge commit(footer 未命中)→ 保守不动,需人工核查" : conflictB ? "journal=started 但主仓已有 merge commit(footer 命中)→ 保守不动,需人工核查" : "journal.mergedCommitSha 与主仓 footer sha 不一致 → 保守不动,需人工核查"
|
|
11595
|
+
});
|
|
11596
|
+
continue;
|
|
11597
|
+
}
|
|
11598
|
+
if (journalCommitted && footerHit) {
|
|
11599
|
+
if (regEntry && regEntry.status === "merged") {
|
|
11600
|
+
await recordMergeJournalPhase(resolved, {
|
|
11601
|
+
sessionId: j.sessionId,
|
|
11602
|
+
approvalSha: j.approvalSha,
|
|
11603
|
+
phase: "done",
|
|
11604
|
+
mergedCommitSha: footerSha
|
|
11605
|
+
}).catch(() => {});
|
|
11606
|
+
continue;
|
|
11607
|
+
}
|
|
11608
|
+
let worktreeExists = false;
|
|
11609
|
+
if (regEntry?.worktreePath) {
|
|
11610
|
+
try {
|
|
11611
|
+
const st = await fs11.stat(regEntry.worktreePath);
|
|
11612
|
+
worktreeExists = st.isDirectory();
|
|
11613
|
+
} catch {
|
|
11614
|
+
worktreeExists = false;
|
|
11615
|
+
}
|
|
11616
|
+
}
|
|
11617
|
+
await finalizeMergeIdempotent({
|
|
11618
|
+
mainRoot: resolved,
|
|
11619
|
+
sessionId: j.sessionId,
|
|
11620
|
+
worktreePath: regEntry?.worktreePath ?? "",
|
|
11621
|
+
branch: regEntry?.branch ?? `codeforge/session-${j.sessionId}`,
|
|
11622
|
+
approvalSha: j.approvalSha,
|
|
11623
|
+
mergedCommitSha: footerSha
|
|
11624
|
+
});
|
|
11625
|
+
result.recoveredMerged.push({
|
|
11626
|
+
sessionId: j.sessionId,
|
|
11627
|
+
state: worktreeExists ? "A" : "B",
|
|
11628
|
+
action: worktreeExists ? "主仓已有 merge commit,registry 未标 merged → 补标 merged + 清 worktree(绝不重 merge)" : "worktree 已删但 registry 未标 merged + journal committed → 仅补标 merged(绝不重 merge)"
|
|
11629
|
+
});
|
|
11630
|
+
continue;
|
|
11631
|
+
}
|
|
11632
|
+
if (!journalCommitted && !footerHit) {
|
|
11633
|
+
const staged = await hasStaged(resolved);
|
|
11634
|
+
if (staged) {
|
|
11635
|
+
await runGit2(resolved, ["reset", "--hard", "HEAD"]).catch((err) => {
|
|
11636
|
+
debugLog(`reconcileInterruptedMerges 态 C reset 失败 (${j.sessionId}): ${err.message}`);
|
|
11637
|
+
});
|
|
11638
|
+
await recordMergeJournalPhase(resolved, {
|
|
11639
|
+
sessionId: j.sessionId,
|
|
11640
|
+
approvalSha: j.approvalSha,
|
|
11641
|
+
phase: "aborted"
|
|
11642
|
+
}).catch(() => {});
|
|
11643
|
+
result.rolledBack.push({
|
|
11644
|
+
sessionId: j.sessionId,
|
|
11645
|
+
state: "C",
|
|
11646
|
+
action: "崩在 squash commit 之前,主仓有 staged → git reset --hard HEAD 还原(改动仍在 worktree 可重跑)"
|
|
11647
|
+
});
|
|
11648
|
+
}
|
|
11649
|
+
continue;
|
|
11650
|
+
}
|
|
11651
|
+
}
|
|
11652
|
+
return result;
|
|
11653
|
+
}
|
|
11341
11654
|
async function discardSession(opts) {
|
|
11342
|
-
const mainRoot =
|
|
11655
|
+
const mainRoot = path13.resolve(opts.mainRoot);
|
|
11343
11656
|
const entry = await getSessionWorktree(opts.sessionId, mainRoot);
|
|
11344
11657
|
if (!entry) {
|
|
11345
11658
|
return;
|
|
@@ -11390,8 +11703,34 @@ async function markInterruptedDirty(opts) {
|
|
|
11390
11703
|
});
|
|
11391
11704
|
}
|
|
11392
11705
|
var SALVAGE_BRANCH_PREFIX = "codeforge/salvage/";
|
|
11706
|
+
var TRANSITIONAL_STALE_MS = 72 * 60 * 60000;
|
|
11707
|
+
async function markAbandoned(opts) {
|
|
11708
|
+
return await mutateRegistry(opts.mainRoot, (reg) => {
|
|
11709
|
+
const e = reg.entries.find((x) => x.sessionId === opts.sessionId);
|
|
11710
|
+
if (!e)
|
|
11711
|
+
return false;
|
|
11712
|
+
if (e.status === "merged" || e.status === "discarded" || e.status === "abandoned") {
|
|
11713
|
+
return false;
|
|
11714
|
+
}
|
|
11715
|
+
e.status = "abandoned";
|
|
11716
|
+
e.updatedAt = new Date().toISOString();
|
|
11717
|
+
return true;
|
|
11718
|
+
});
|
|
11719
|
+
}
|
|
11720
|
+
async function createSalvageBranch(opts) {
|
|
11721
|
+
const mainRoot = path13.resolve(opts.mainRoot);
|
|
11722
|
+
const ts = new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
11723
|
+
const branch = `${SALVAGE_BRANCH_PREFIX}${opts.sessionId}-${ts}`;
|
|
11724
|
+
try {
|
|
11725
|
+
await runGit2(mainRoot, ["branch", branch, opts.fromRef]);
|
|
11726
|
+
return branch;
|
|
11727
|
+
} catch (err) {
|
|
11728
|
+
debugLog(`createSalvageBranch 失败 (session=${opts.sessionId}): ${err.message}`);
|
|
11729
|
+
return null;
|
|
11730
|
+
}
|
|
11731
|
+
}
|
|
11393
11732
|
async function listSalvageBranches(mainRoot) {
|
|
11394
|
-
const resolved =
|
|
11733
|
+
const resolved = path13.resolve(mainRoot);
|
|
11395
11734
|
try {
|
|
11396
11735
|
const out = await runGit2(resolved, [
|
|
11397
11736
|
"for-each-ref",
|
|
@@ -11409,12 +11748,17 @@ async function listSalvageBranches(mainRoot) {
|
|
|
11409
11748
|
return [];
|
|
11410
11749
|
}
|
|
11411
11750
|
}
|
|
11412
|
-
async function reconcileTransitionalEntries(mainRoot) {
|
|
11413
|
-
const resolved =
|
|
11751
|
+
async function reconcileTransitionalEntries(mainRoot, opts = {}) {
|
|
11752
|
+
const resolved = path13.resolve(mainRoot);
|
|
11753
|
+
const staleMs = opts.transitionalStaleMs ?? TRANSITIONAL_STALE_MS;
|
|
11754
|
+
const now = opts.now ?? Date.now();
|
|
11414
11755
|
const result = {
|
|
11415
11756
|
cleanedCreating: [],
|
|
11416
11757
|
finishedRemoving: [],
|
|
11417
|
-
keptConservative: []
|
|
11758
|
+
keptConservative: [],
|
|
11759
|
+
staleSalvaged: [],
|
|
11760
|
+
staleCleaned: [],
|
|
11761
|
+
staleKept: []
|
|
11418
11762
|
};
|
|
11419
11763
|
const snap = await readRegistryResult(resolved);
|
|
11420
11764
|
if (snap.kind === "corrupt") {
|
|
@@ -11433,17 +11777,40 @@ async function reconcileTransitionalEntries(mainRoot) {
|
|
|
11433
11777
|
const hasTransitional = entries.some((e) => e.status === "creating" || e.status === "removing");
|
|
11434
11778
|
if (!hasTransitional)
|
|
11435
11779
|
return result;
|
|
11780
|
+
const staleSnapshots = [];
|
|
11436
11781
|
await mutateRegistry(resolved, async (reg) => {
|
|
11437
11782
|
for (const entry of reg.entries) {
|
|
11438
11783
|
if (entry.status !== "creating" && entry.status !== "removing")
|
|
11439
11784
|
continue;
|
|
11440
11785
|
let dirExists = true;
|
|
11441
11786
|
try {
|
|
11442
|
-
const st = await
|
|
11787
|
+
const st = await fs11.stat(entry.worktreePath);
|
|
11443
11788
|
dirExists = st.isDirectory();
|
|
11444
11789
|
} catch {
|
|
11445
11790
|
dirExists = false;
|
|
11446
11791
|
}
|
|
11792
|
+
const parsedAt = Date.parse(entry.updatedAt);
|
|
11793
|
+
const isStale = Number.isFinite(parsedAt) && now - parsedAt > staleMs;
|
|
11794
|
+
if (isStale && dirExists) {
|
|
11795
|
+
staleSnapshots.push({
|
|
11796
|
+
sessionId: entry.sessionId,
|
|
11797
|
+
branch: entry.branch,
|
|
11798
|
+
worktreePath: entry.worktreePath,
|
|
11799
|
+
baseSha: entry.baseSha,
|
|
11800
|
+
status: entry.status,
|
|
11801
|
+
dirExists
|
|
11802
|
+
});
|
|
11803
|
+
continue;
|
|
11804
|
+
}
|
|
11805
|
+
if (isStale && !dirExists && entry.status === "removing") {
|
|
11806
|
+
await deleteBranchIfExists({ root: resolved, branch: entry.branch }).catch(() => ({
|
|
11807
|
+
deleted: false
|
|
11808
|
+
}));
|
|
11809
|
+
entry.status = "abandoned";
|
|
11810
|
+
entry.updatedAt = new Date().toISOString();
|
|
11811
|
+
result.staleCleaned.push(entry.sessionId);
|
|
11812
|
+
continue;
|
|
11813
|
+
}
|
|
11447
11814
|
if (entry.status === "creating") {
|
|
11448
11815
|
if (!dirExists) {
|
|
11449
11816
|
entry.status = "discarded";
|
|
@@ -11482,15 +11849,88 @@ async function reconcileTransitionalEntries(mainRoot) {
|
|
|
11482
11849
|
}
|
|
11483
11850
|
}
|
|
11484
11851
|
});
|
|
11852
|
+
for (const snap2 of staleSnapshots) {
|
|
11853
|
+
await reconcileOneStaleTransitional(resolved, snap2, result);
|
|
11854
|
+
}
|
|
11485
11855
|
return result;
|
|
11486
11856
|
}
|
|
11857
|
+
var STALE_SALVAGE_CHECKPOINT_MSG = "[codeforge-checkpoint] stale-transitional-salvage";
|
|
11858
|
+
async function reconcileOneStaleTransitional(mainRoot, snap, result) {
|
|
11859
|
+
let hasChanges;
|
|
11860
|
+
if (!snap.baseSha) {
|
|
11861
|
+
hasChanges = true;
|
|
11862
|
+
} else {
|
|
11863
|
+
const dirty = await isWorktreeDirty(snap.worktreePath);
|
|
11864
|
+
if (dirty) {
|
|
11865
|
+
hasChanges = true;
|
|
11866
|
+
} else {
|
|
11867
|
+
const head = await getCurrentWorktreeHead(snap.worktreePath);
|
|
11868
|
+
hasChanges = head.length === 0 || head !== snap.baseSha;
|
|
11869
|
+
}
|
|
11870
|
+
}
|
|
11871
|
+
if (!hasChanges) {
|
|
11872
|
+
await markAbandoned({ sessionId: snap.sessionId, mainRoot });
|
|
11873
|
+
try {
|
|
11874
|
+
await removeWorktree({ root: mainRoot, worktree_path: snap.worktreePath, force: true });
|
|
11875
|
+
} catch (err) {
|
|
11876
|
+
debugLog(`stale 清理 worktree 失败 (${snap.sessionId}): ${err.message}`);
|
|
11877
|
+
}
|
|
11878
|
+
await deleteBranchIfExists({ root: mainRoot, branch: snap.branch }).catch(() => ({
|
|
11879
|
+
deleted: false
|
|
11880
|
+
}));
|
|
11881
|
+
result.staleCleaned.push(snap.sessionId);
|
|
11882
|
+
return;
|
|
11883
|
+
}
|
|
11884
|
+
let salvageOk = true;
|
|
11885
|
+
try {
|
|
11886
|
+
if (await isWorktreeDirty(snap.worktreePath)) {
|
|
11887
|
+
await checkpointCommit({
|
|
11888
|
+
worktreePath: snap.worktreePath,
|
|
11889
|
+
message: STALE_SALVAGE_CHECKPOINT_MSG
|
|
11890
|
+
});
|
|
11891
|
+
}
|
|
11892
|
+
} catch (err) {
|
|
11893
|
+
debugLog(`stale checkpoint commit 失败 (${snap.sessionId}): ${err.message}`);
|
|
11894
|
+
salvageOk = false;
|
|
11895
|
+
}
|
|
11896
|
+
let salvageBranch = null;
|
|
11897
|
+
if (salvageOk) {
|
|
11898
|
+
const head = await getCurrentWorktreeHead(snap.worktreePath);
|
|
11899
|
+
if (head.length === 0) {
|
|
11900
|
+
salvageOk = false;
|
|
11901
|
+
} else {
|
|
11902
|
+
salvageBranch = await createSalvageBranch({
|
|
11903
|
+
mainRoot,
|
|
11904
|
+
sessionId: snap.sessionId,
|
|
11905
|
+
fromRef: head
|
|
11906
|
+
});
|
|
11907
|
+
if (!salvageBranch)
|
|
11908
|
+
salvageOk = false;
|
|
11909
|
+
}
|
|
11910
|
+
}
|
|
11911
|
+
if (!salvageOk) {
|
|
11912
|
+
debugLog(`stale salvage 失败,保留 worktree 待重试 (${snap.sessionId})`);
|
|
11913
|
+
result.staleKept.push(snap.sessionId);
|
|
11914
|
+
return;
|
|
11915
|
+
}
|
|
11916
|
+
await markAbandoned({ sessionId: snap.sessionId, mainRoot });
|
|
11917
|
+
try {
|
|
11918
|
+
await removeWorktree({ root: mainRoot, worktree_path: snap.worktreePath, force: true });
|
|
11919
|
+
} catch (err) {
|
|
11920
|
+
debugLog(`stale salvage 后清理 worktree 失败 (${snap.sessionId}): ${err.message}`);
|
|
11921
|
+
}
|
|
11922
|
+
result.staleSalvaged.push(snap.sessionId);
|
|
11923
|
+
}
|
|
11487
11924
|
function summarizeReconcileDigest(result, prune, maxList = 3) {
|
|
11488
11925
|
const unreadable = result.registryUnreadable ?? prune?.registryUnreadable;
|
|
11489
11926
|
if (unreadable) {
|
|
11490
11927
|
const hint = unreadable.reason === "version_too_new" ? "版本过高,请升级 CodeForge 后再操作" : "文件损坏,已备份留痕,请检查后删除让系统重建";
|
|
11491
11928
|
return `[codeforge] ⚠️ session 追踪文件不可读(${unreadable.reason}:${unreadable.detail}),本轮已跳过 worktree 清理(fail-closed,未删除任何 worktree)。${hint}。`;
|
|
11492
11929
|
}
|
|
11493
|
-
const
|
|
11930
|
+
const staleSalvaged = result.staleSalvaged?.length ?? 0;
|
|
11931
|
+
const staleCleaned = result.staleCleaned?.length ?? 0;
|
|
11932
|
+
const staleKept = result.staleKept?.length ?? 0;
|
|
11933
|
+
const totalReconcile = result.cleanedCreating.length + result.finishedRemoving.length + result.keptConservative.length + staleSalvaged + staleCleaned + staleKept;
|
|
11494
11934
|
const cleanedPrune = prune?.cleaned.length ?? 0;
|
|
11495
11935
|
const failedPrune = prune?.failed.length ?? 0;
|
|
11496
11936
|
if (totalReconcile === 0 && cleanedPrune === 0 && failedPrune === 0)
|
|
@@ -11505,6 +11945,12 @@ function summarizeReconcileDigest(result, prune, maxList = 3) {
|
|
|
11505
11945
|
if (result.keptConservative.length > 0) {
|
|
11506
11946
|
parts.push(`保守保留 ${result.keptConservative.length}`);
|
|
11507
11947
|
}
|
|
11948
|
+
if (staleSalvaged > 0)
|
|
11949
|
+
parts.push(`超期留痕 ${staleSalvaged}`);
|
|
11950
|
+
if (staleCleaned > 0)
|
|
11951
|
+
parts.push(`超期清理 ${staleCleaned}`);
|
|
11952
|
+
if (staleKept > 0)
|
|
11953
|
+
parts.push(`超期保留 ${staleKept}`);
|
|
11508
11954
|
if (cleanedPrune > 0)
|
|
11509
11955
|
parts.push(`清理僵尸 worktree ${cleanedPrune}`);
|
|
11510
11956
|
if (failedPrune > 0)
|
|
@@ -11512,6 +11958,8 @@ function summarizeReconcileDigest(result, prune, maxList = 3) {
|
|
|
11512
11958
|
const sample = [
|
|
11513
11959
|
...result.cleanedCreating,
|
|
11514
11960
|
...result.finishedRemoving,
|
|
11961
|
+
...result.staleSalvaged ?? [],
|
|
11962
|
+
...result.staleCleaned ?? [],
|
|
11515
11963
|
...prune?.cleaned ?? []
|
|
11516
11964
|
];
|
|
11517
11965
|
const uniq = [...new Set(sample)].slice(0, maxList);
|
|
@@ -11519,9 +11967,127 @@ function summarizeReconcileDigest(result, prune, maxList = 3) {
|
|
|
11519
11967
|
const sampleStr = uniq.length > 0 ? `(${uniq.join(", ")}${more})` : "";
|
|
11520
11968
|
return `[codeforge] worktree 收敛:${parts.join(",")}${sampleStr}`;
|
|
11521
11969
|
}
|
|
11970
|
+
function buildHealthDigest(input) {
|
|
11971
|
+
const signals = [];
|
|
11972
|
+
if (input.registry.kind === "corrupt") {
|
|
11973
|
+
signals.push({
|
|
11974
|
+
kind: "registry_corrupt",
|
|
11975
|
+
count: 1,
|
|
11976
|
+
nextStep: "session 追踪文件损坏,已备份留痕,请检查后删除让系统重建(这不代表 worktree 丢了)。"
|
|
11977
|
+
});
|
|
11978
|
+
} else if (input.registry.kind === "version_too_new") {
|
|
11979
|
+
signals.push({
|
|
11980
|
+
kind: "registry_version_too_new",
|
|
11981
|
+
count: 1,
|
|
11982
|
+
nextStep: "session 追踪文件版本过高,请运行 `bash install.sh --global` 升级 CodeForge 后再操作。"
|
|
11983
|
+
});
|
|
11984
|
+
}
|
|
11985
|
+
const entries = input.registry.kind === "ok" ? input.registry.registry.entries : [];
|
|
11986
|
+
const now = Date.now();
|
|
11987
|
+
const staleCount = entries.filter((e) => {
|
|
11988
|
+
if (e.status !== "creating" && e.status !== "removing")
|
|
11989
|
+
return false;
|
|
11990
|
+
const ts = Date.parse(e.updatedAt);
|
|
11991
|
+
return Number.isFinite(ts) && now - ts > TRANSITIONAL_STALE_MS;
|
|
11992
|
+
}).length;
|
|
11993
|
+
if (staleCount > 0) {
|
|
11994
|
+
signals.push({
|
|
11995
|
+
kind: "transitional_stale",
|
|
11996
|
+
count: staleCount,
|
|
11997
|
+
nextStep: `${staleCount} 个 worktree 超期未收尾,重启 opencode 触发自动收尾,或用 /discard-session 放弃。`
|
|
11998
|
+
});
|
|
11999
|
+
}
|
|
12000
|
+
const salvageCount = input.salvageBranches.length;
|
|
12001
|
+
if (salvageCount > 0) {
|
|
12002
|
+
signals.push({
|
|
12003
|
+
kind: "abandoned_salvage",
|
|
12004
|
+
count: salvageCount,
|
|
12005
|
+
nextStep: `${salvageCount} 个放弃的改动已留痕,可用 \`git cherry-pick <ref>\` 找回,或忽略让系统稍后清理。`
|
|
12006
|
+
});
|
|
12007
|
+
}
|
|
12008
|
+
if (input.pendingReviewCount > 0) {
|
|
12009
|
+
signals.push({
|
|
12010
|
+
kind: "pending_review",
|
|
12011
|
+
count: input.pendingReviewCount,
|
|
12012
|
+
nextStep: `${input.pendingReviewCount} 个 session 待审,先 /review 通过后再 /merge 合入。`
|
|
12013
|
+
});
|
|
12014
|
+
}
|
|
12015
|
+
const laneConflictCount = input.laneConflictCount ?? 0;
|
|
12016
|
+
if (laneConflictCount > 0) {
|
|
12017
|
+
signals.push({
|
|
12018
|
+
kind: "lane_conflict",
|
|
12019
|
+
count: laneConflictCount,
|
|
12020
|
+
nextStep: `${laneConflictCount} 路并行改动有冲突需人工解,用 session_merge action=status 查看详情。`
|
|
12021
|
+
});
|
|
12022
|
+
}
|
|
12023
|
+
const awaitingCount = input.journal.filter((j) => isCommittedOrLater(j.phase) && j.phase !== "done").length;
|
|
12024
|
+
if (awaitingCount > 0) {
|
|
12025
|
+
signals.push({
|
|
12026
|
+
kind: "awaiting_confirm",
|
|
12027
|
+
count: awaitingCount,
|
|
12028
|
+
nextStep: `${awaitingCount} 个合并已提交但未收尾,触发 GC 或重启 opencode 后会自动补完,无需手动干预。`
|
|
12029
|
+
});
|
|
12030
|
+
}
|
|
12031
|
+
const ownerAliasCount = input.ownerAliasCount ?? 0;
|
|
12032
|
+
if (ownerAliasCount > 0) {
|
|
12033
|
+
signals.push({
|
|
12034
|
+
kind: "owner_alias",
|
|
12035
|
+
count: ownerAliasCount,
|
|
12036
|
+
nextStep: `${ownerAliasCount} 个子 session 审批已归一到父 worktree,无需操作(信息性)。`,
|
|
12037
|
+
informational: true
|
|
12038
|
+
});
|
|
12039
|
+
}
|
|
12040
|
+
const rank = (s) => {
|
|
12041
|
+
if (s.kind === "registry_corrupt" || s.kind === "registry_version_too_new")
|
|
12042
|
+
return 0;
|
|
12043
|
+
if (s.informational)
|
|
12044
|
+
return 2;
|
|
12045
|
+
return 1;
|
|
12046
|
+
};
|
|
12047
|
+
signals.sort((a, b) => rank(a) - rank(b));
|
|
12048
|
+
const actionable = signals.filter((s) => !s.informational);
|
|
12049
|
+
const hasIssues = actionable.length > 0;
|
|
12050
|
+
let summary = "";
|
|
12051
|
+
if (hasIssues) {
|
|
12052
|
+
const head = actionable[0];
|
|
12053
|
+
const rest = actionable.length - 1;
|
|
12054
|
+
const restStr = rest > 0 ? ` 另有 ${rest} 类待处理(详见 session_merge status / codeforge doctor --runtime)。` : "";
|
|
12055
|
+
summary = `[codeforge] worktree 健康:${head.nextStep}${restStr}`;
|
|
12056
|
+
}
|
|
12057
|
+
return { hasIssues, signals, summary };
|
|
12058
|
+
}
|
|
12059
|
+
async function collectHealthDigest(mainRoot) {
|
|
12060
|
+
const resolved = path13.resolve(mainRoot);
|
|
12061
|
+
const [registry, journal, salvageBranches] = await Promise.all([
|
|
12062
|
+
readRegistryResult(resolved).catch(() => ({ kind: "missing" })),
|
|
12063
|
+
listMergeJournalEntries(resolved).catch(() => []),
|
|
12064
|
+
listSalvageBranches(resolved).catch(() => [])
|
|
12065
|
+
]);
|
|
12066
|
+
const entries = registry.kind === "ok" ? registry.registry.entries : [];
|
|
12067
|
+
const activeEntries = entries.filter((e) => e.status === "active");
|
|
12068
|
+
let pendingReviewCount = 0;
|
|
12069
|
+
if (activeEntries.length > 0) {
|
|
12070
|
+
const store = ApprovalStore.forProject(resolved);
|
|
12071
|
+
const checks = await Promise.all(activeEntries.map(async (e) => {
|
|
12072
|
+
try {
|
|
12073
|
+
const latest = await store.getLatest(`session:${e.sessionId}`);
|
|
12074
|
+
return latest ? 0 : 1;
|
|
12075
|
+
} catch {
|
|
12076
|
+
return 0;
|
|
12077
|
+
}
|
|
12078
|
+
}));
|
|
12079
|
+
pendingReviewCount = checks.reduce((a, b) => a + b, 0);
|
|
12080
|
+
}
|
|
12081
|
+
return buildHealthDigest({
|
|
12082
|
+
registry,
|
|
12083
|
+
journal,
|
|
12084
|
+
salvageBranches,
|
|
12085
|
+
pendingReviewCount
|
|
12086
|
+
});
|
|
12087
|
+
}
|
|
11522
12088
|
async function getCurrentWorktreeHead(worktreePath) {
|
|
11523
12089
|
try {
|
|
11524
|
-
return (await runGit2(
|
|
12090
|
+
return (await runGit2(path13.resolve(worktreePath), ["rev-parse", "HEAD"])).trim();
|
|
11525
12091
|
} catch {
|
|
11526
12092
|
return "";
|
|
11527
12093
|
}
|
|
@@ -11540,7 +12106,7 @@ async function checkpointCommit(opts) {
|
|
|
11540
12106
|
}
|
|
11541
12107
|
var CHECKPOINT_MESSAGE_PREFIX = "[codeforge-checkpoint] pre-lane-dispatch";
|
|
11542
12108
|
async function checkpointSessionWorktree(opts) {
|
|
11543
|
-
const mainRoot =
|
|
12109
|
+
const mainRoot = path13.resolve(opts.mainRoot);
|
|
11544
12110
|
await loadRegistryForMutation(mainRoot);
|
|
11545
12111
|
const entry = await getSessionWorktree(opts.sessionId, mainRoot);
|
|
11546
12112
|
if (!entry) {
|
|
@@ -11563,19 +12129,19 @@ async function checkpointSessionWorktree(opts) {
|
|
|
11563
12129
|
return { sessionId: opts.sessionId, worktreePath: wt, committed: true, head };
|
|
11564
12130
|
}
|
|
11565
12131
|
function runGit2(cwd, args, timeoutMs = 1e4) {
|
|
11566
|
-
return new Promise((
|
|
12132
|
+
return new Promise((resolve12, reject) => {
|
|
11567
12133
|
execFile3("git", args, { cwd, timeout: timeoutMs, windowsHide: true, encoding: "utf8" }, (err, stdout, stderr) => {
|
|
11568
12134
|
if (err) {
|
|
11569
12135
|
reject(new Error(`git ${args.join(" ")} (cwd=${cwd}) 失败: ${stderr?.trim() || err.message}`));
|
|
11570
12136
|
return;
|
|
11571
12137
|
}
|
|
11572
|
-
|
|
12138
|
+
resolve12(stdout);
|
|
11573
12139
|
});
|
|
11574
12140
|
});
|
|
11575
12141
|
}
|
|
11576
12142
|
function runGitWithEnv(cwd, args, envOverrides, timeoutMs = 1e4) {
|
|
11577
12143
|
const inheritedEnv = process["env"];
|
|
11578
|
-
return new Promise((
|
|
12144
|
+
return new Promise((resolve12, reject) => {
|
|
11579
12145
|
execFile3("git", args, {
|
|
11580
12146
|
cwd,
|
|
11581
12147
|
timeout: timeoutMs,
|
|
@@ -11587,7 +12153,7 @@ function runGitWithEnv(cwd, args, envOverrides, timeoutMs = 1e4) {
|
|
|
11587
12153
|
reject(new Error(`git ${args.join(" ")} (cwd=${cwd}) 失败: ${stderr?.trim() || err.message}`));
|
|
11588
12154
|
return;
|
|
11589
12155
|
}
|
|
11590
|
-
|
|
12156
|
+
resolve12(stdout);
|
|
11591
12157
|
});
|
|
11592
12158
|
});
|
|
11593
12159
|
}
|
|
@@ -11605,7 +12171,7 @@ async function getBuildScript(mainRoot) {
|
|
|
11605
12171
|
async function shouldSkipDevOnce(mainRoot, stagedPaths, worktreePath) {
|
|
11606
12172
|
let distMtimeSec;
|
|
11607
12173
|
try {
|
|
11608
|
-
const st = await
|
|
12174
|
+
const st = await fs11.stat(path13.join(mainRoot, "dist/index.js"));
|
|
11609
12175
|
distMtimeSec = Math.floor(st.mtimeMs / 1000);
|
|
11610
12176
|
} catch {
|
|
11611
12177
|
return false;
|
|
@@ -11625,39 +12191,81 @@ async function shouldSkipDevOnce(mainRoot, stagedPaths, worktreePath) {
|
|
|
11625
12191
|
async function statSourceMtime(rel, mainRoot, worktreePath) {
|
|
11626
12192
|
if (worktreePath) {
|
|
11627
12193
|
try {
|
|
11628
|
-
const st = await
|
|
12194
|
+
const st = await fs11.stat(path13.join(worktreePath, rel));
|
|
11629
12195
|
return Math.floor(st.mtimeMs / 1000);
|
|
11630
12196
|
} catch {}
|
|
11631
12197
|
}
|
|
11632
12198
|
try {
|
|
11633
|
-
const st = await
|
|
12199
|
+
const st = await fs11.stat(path13.join(mainRoot, rel));
|
|
11634
12200
|
return Math.floor(st.mtimeMs / 1000);
|
|
11635
12201
|
} catch {
|
|
11636
12202
|
return null;
|
|
11637
12203
|
}
|
|
11638
12204
|
}
|
|
11639
12205
|
function runCmd(cmd, args, cwd, timeoutMs = 300000) {
|
|
11640
|
-
return new Promise((
|
|
12206
|
+
return new Promise((resolve12, reject) => {
|
|
11641
12207
|
execFile3(cmd, args, { cwd, timeout: timeoutMs, windowsHide: true, encoding: "utf8" }, (err, stdout, stderr) => {
|
|
11642
12208
|
if (err) {
|
|
11643
12209
|
reject(new Error(`${cmd} ${args.join(" ")} (cwd=${cwd}) 失败: ${stderr?.trim() || err.message}`));
|
|
11644
12210
|
return;
|
|
11645
12211
|
}
|
|
11646
|
-
|
|
12212
|
+
resolve12(stdout);
|
|
11647
12213
|
});
|
|
11648
12214
|
});
|
|
11649
12215
|
}
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
12216
|
+
var MERGE_MESSAGE_NOISE_PREFIXES = [
|
|
12217
|
+
"[codeforge-checkpoint]",
|
|
12218
|
+
"[checkpoint]",
|
|
12219
|
+
"auto-commit before merge",
|
|
12220
|
+
"chore(release)",
|
|
12221
|
+
"merge-sync",
|
|
12222
|
+
"sync-to-main",
|
|
12223
|
+
"release sync"
|
|
12224
|
+
];
|
|
12225
|
+
function filterSquashedCommits(commits, noisePrefixes = MERGE_MESSAGE_NOISE_PREFIXES) {
|
|
12226
|
+
const kept = [];
|
|
12227
|
+
let dropped = 0;
|
|
12228
|
+
for (const c of commits) {
|
|
12229
|
+
const isNoise = noisePrefixes.some((p) => c.includes(p));
|
|
12230
|
+
if (isNoise)
|
|
12231
|
+
dropped++;
|
|
12232
|
+
else
|
|
12233
|
+
kept.push(c);
|
|
12234
|
+
}
|
|
12235
|
+
return { kept, dropped };
|
|
12236
|
+
}
|
|
12237
|
+
async function buildMergeMessage(args) {
|
|
12238
|
+
const { sessionId, branch, baseSha, squashed } = args;
|
|
12239
|
+
const sid8 = sessionId.slice(0, 8);
|
|
12240
|
+
let subject;
|
|
12241
|
+
if (args.planStore && args.requiredPlanId) {
|
|
12242
|
+
try {
|
|
12243
|
+
const planRead = await args.planStore.read(args.requiredPlanId);
|
|
12244
|
+
const title = planRead?.entry.title?.trim();
|
|
12245
|
+
if (title)
|
|
12246
|
+
subject = `${title} (session ${sid8})`;
|
|
12247
|
+
} catch {}
|
|
12248
|
+
}
|
|
12249
|
+
if (!subject) {
|
|
12250
|
+
const summary = args.summary?.trim();
|
|
12251
|
+
if (summary)
|
|
12252
|
+
subject = `${summary} (session ${sid8})`;
|
|
12253
|
+
}
|
|
12254
|
+
if (!subject)
|
|
12255
|
+
subject = `session(${sessionId}): merge ${branch}`;
|
|
12256
|
+
const { kept, dropped } = filterSquashedCommits(squashed);
|
|
12257
|
+
const body = kept.length > 0 ? `
|
|
11653
12258
|
|
|
11654
12259
|
Squashed commits:
|
|
11655
|
-
${
|
|
12260
|
+
${kept.map((s) => ` - ${s}`).join(`
|
|
11656
12261
|
`)}` : "";
|
|
11657
|
-
|
|
12262
|
+
let footer = `
|
|
11658
12263
|
|
|
11659
12264
|
Codeforge-Session: ${sessionId}
|
|
11660
12265
|
Codeforge-Base: ${baseSha.slice(0, 12)}`;
|
|
12266
|
+
if (dropped > 0)
|
|
12267
|
+
footer += `
|
|
12268
|
+
Codeforge-Squash-Dropped: ${dropped}`;
|
|
11661
12269
|
return subject + body + footer;
|
|
11662
12270
|
}
|
|
11663
12271
|
var ORPHAN_GRACE_MS = 60000;
|
|
@@ -11668,7 +12276,7 @@ async function pruneDiscardedRegistryEntries(mainRoot, opts = {}) {
|
|
|
11668
12276
|
if (keepRecent < 0) {
|
|
11669
12277
|
throw new Error(`pruneDiscardedRegistryEntries: keepRecent 必须 ≥ 0,收到 ${keepRecent}`);
|
|
11670
12278
|
}
|
|
11671
|
-
return await mutateRegistry(
|
|
12279
|
+
return await mutateRegistry(path13.resolve(mainRoot), (reg) => {
|
|
11672
12280
|
const discarded = [];
|
|
11673
12281
|
const others = [];
|
|
11674
12282
|
for (const e of reg.entries) {
|
|
@@ -11685,7 +12293,7 @@ async function pruneDiscardedRegistryEntries(mainRoot, opts = {}) {
|
|
|
11685
12293
|
});
|
|
11686
12294
|
}
|
|
11687
12295
|
async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
11688
|
-
const resolved =
|
|
12296
|
+
const resolved = path13.resolve(mainRoot);
|
|
11689
12297
|
const cleaned = [];
|
|
11690
12298
|
const failed = [];
|
|
11691
12299
|
let skipped = 0;
|
|
@@ -11710,7 +12318,7 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
11710
12318
|
} catch {
|
|
11711
12319
|
gitWorktrees = [];
|
|
11712
12320
|
}
|
|
11713
|
-
const gitWorktreePaths = new Set(gitWorktrees.map((w) =>
|
|
12321
|
+
const gitWorktreePaths = new Set(gitWorktrees.map((w) => path13.resolve(w.path)));
|
|
11714
12322
|
await mutateRegistry(resolved, async (reg2) => {
|
|
11715
12323
|
const now = Date.now();
|
|
11716
12324
|
for (const entry of reg2.entries) {
|
|
@@ -11720,7 +12328,7 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
11720
12328
|
let dirExists = true;
|
|
11721
12329
|
let dirMtimeMs = 0;
|
|
11722
12330
|
try {
|
|
11723
|
-
const st = await
|
|
12331
|
+
const st = await fs11.stat(wt);
|
|
11724
12332
|
dirExists = st.isDirectory();
|
|
11725
12333
|
dirMtimeMs = st.mtimeMs;
|
|
11726
12334
|
} catch {
|
|
@@ -11813,12 +12421,12 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
11813
12421
|
}
|
|
11814
12422
|
});
|
|
11815
12423
|
}
|
|
11816
|
-
const codeforgeWorktreeRoot =
|
|
12424
|
+
const codeforgeWorktreeRoot = path13.resolve(path13.join(resolved, DEFAULT_WORKTREE_SUBDIR));
|
|
11817
12425
|
const fsWorktreePaths = [];
|
|
11818
12426
|
try {
|
|
11819
|
-
const names = await
|
|
12427
|
+
const names = await fs11.readdir(codeforgeWorktreeRoot);
|
|
11820
12428
|
for (const name of names) {
|
|
11821
|
-
fsWorktreePaths.push(
|
|
12429
|
+
fsWorktreePaths.push(path13.resolve(path13.join(codeforgeWorktreeRoot, name)));
|
|
11822
12430
|
}
|
|
11823
12431
|
} catch {}
|
|
11824
12432
|
const candidatePaths = new Set([
|
|
@@ -11826,16 +12434,16 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
11826
12434
|
...fsWorktreePaths
|
|
11827
12435
|
]);
|
|
11828
12436
|
const reg = await readRegistry(resolved);
|
|
11829
|
-
const knownPaths = new Set(reg.entries.map((e) =>
|
|
12437
|
+
const knownPaths = new Set(reg.entries.map((e) => path13.resolve(e.worktreePath)));
|
|
11830
12438
|
for (const candidate of candidatePaths) {
|
|
11831
12439
|
if (knownPaths.has(candidate))
|
|
11832
12440
|
continue;
|
|
11833
|
-
if (candidate !== codeforgeWorktreeRoot && !candidate.startsWith(codeforgeWorktreeRoot +
|
|
12441
|
+
if (candidate !== codeforgeWorktreeRoot && !candidate.startsWith(codeforgeWorktreeRoot + path13.sep)) {
|
|
11834
12442
|
continue;
|
|
11835
12443
|
}
|
|
11836
12444
|
let dirExists = true;
|
|
11837
12445
|
try {
|
|
11838
|
-
const st = await
|
|
12446
|
+
const st = await fs11.stat(candidate);
|
|
11839
12447
|
if (Date.now() - st.mtimeMs < ORPHAN_GRACE_MS) {
|
|
11840
12448
|
skipped++;
|
|
11841
12449
|
continue;
|
|
@@ -11857,14 +12465,14 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
11857
12465
|
}
|
|
11858
12466
|
if (removed) {
|
|
11859
12467
|
try {
|
|
11860
|
-
await
|
|
12468
|
+
await fs11.stat(candidate);
|
|
11861
12469
|
removed = false;
|
|
11862
12470
|
lastError = lastError ?? "git worktree remove 返回成功但目录仍存在(C 类 fs-only orphan)";
|
|
11863
12471
|
} catch {}
|
|
11864
12472
|
}
|
|
11865
12473
|
if (!removed && dirExists) {
|
|
11866
12474
|
try {
|
|
11867
|
-
await
|
|
12475
|
+
await fs11.rm(candidate, { recursive: true, force: true });
|
|
11868
12476
|
removed = true;
|
|
11869
12477
|
} catch (err) {
|
|
11870
12478
|
lastError = `git remove 失败: ${lastError}; fs.rm 也失败: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -12040,7 +12648,7 @@ async function execute4(input) {
|
|
|
12040
12648
|
import { z as z5 } from "zod";
|
|
12041
12649
|
|
|
12042
12650
|
// lib/browser-control.ts
|
|
12043
|
-
import * as
|
|
12651
|
+
import * as path14 from "node:path";
|
|
12044
12652
|
var DEFAULT_CONFIG2 = {
|
|
12045
12653
|
enabled: false,
|
|
12046
12654
|
headless: true,
|
|
@@ -12052,7 +12660,7 @@ var DEFAULT_CONFIG2 = {
|
|
|
12052
12660
|
bufferLimit: 500
|
|
12053
12661
|
};
|
|
12054
12662
|
function defaultScreenshotDir(root = process.cwd()) {
|
|
12055
|
-
return
|
|
12663
|
+
return path14.join(runtimeDir(root), "browser", "screenshots");
|
|
12056
12664
|
}
|
|
12057
12665
|
function checkUrl(url, cfg = DEFAULT_CONFIG2) {
|
|
12058
12666
|
if (typeof url !== "string" || url.trim() === "") {
|
|
@@ -12177,14 +12785,14 @@ async function tryCreatePlaywrightController(cfg = DEFAULT_CONFIG2, resolver = d
|
|
|
12177
12785
|
async screenshot(opts) {
|
|
12178
12786
|
try {
|
|
12179
12787
|
const dir = cfg.screenshotDir && cfg.screenshotDir.trim().length > 0 ? cfg.screenshotDir : defaultScreenshotDir();
|
|
12180
|
-
const
|
|
12788
|
+
const path15 = `${dir}/${Date.now()}.png`;
|
|
12181
12789
|
if (opts?.selector) {
|
|
12182
12790
|
const el = await page.locator(opts.selector).first();
|
|
12183
|
-
await el.screenshot({ path:
|
|
12791
|
+
await el.screenshot({ path: path15 });
|
|
12184
12792
|
} else {
|
|
12185
|
-
await page.screenshot({ path:
|
|
12793
|
+
await page.screenshot({ path: path15, fullPage: opts?.fullPage });
|
|
12186
12794
|
}
|
|
12187
|
-
return { ok: true, path:
|
|
12795
|
+
return { ok: true, path: path15 };
|
|
12188
12796
|
} catch (err) {
|
|
12189
12797
|
return { ok: false, error: describe3(err) };
|
|
12190
12798
|
}
|
|
@@ -12501,8 +13109,8 @@ async function execute10(input) {
|
|
|
12501
13109
|
import { z as z11 } from "zod";
|
|
12502
13110
|
|
|
12503
13111
|
// lib/model-config.ts
|
|
12504
|
-
import { promises as
|
|
12505
|
-
import * as
|
|
13112
|
+
import { promises as fs12 } from "node:fs";
|
|
13113
|
+
import * as path15 from "node:path";
|
|
12506
13114
|
|
|
12507
13115
|
// lib/model-tier.ts
|
|
12508
13116
|
var TIER_ORDER = ["quick", "balanced", "deep", "ultra"];
|
|
@@ -12519,12 +13127,12 @@ var PROVIDER_MODEL_RE = /^[a-z0-9-]+\/[a-zA-Z0-9._-]+$/;
|
|
|
12519
13127
|
function findConfigFileSync(opts = {}) {
|
|
12520
13128
|
const root = opts.root ?? process.cwd();
|
|
12521
13129
|
const fsSync = __require("node:fs");
|
|
12522
|
-
const abs =
|
|
13130
|
+
const abs = path15.resolve(root, opts.file ?? CONFIG_FILE);
|
|
12523
13131
|
return fsSync.existsSync(abs) ? abs : null;
|
|
12524
13132
|
}
|
|
12525
13133
|
function loadModelConfigSync(opts = {}) {
|
|
12526
13134
|
const root = opts.root ?? process.cwd();
|
|
12527
|
-
const abs =
|
|
13135
|
+
const abs = path15.resolve(root, opts.file ?? CONFIG_FILE);
|
|
12528
13136
|
const fsSync = __require("node:fs");
|
|
12529
13137
|
if (!fsSync.existsSync(abs)) {
|
|
12530
13138
|
return { ok: false, warnings: [], error: `config_not_found: ${abs}` };
|
|
@@ -12539,10 +13147,10 @@ function loadModelConfigSync(opts = {}) {
|
|
|
12539
13147
|
}
|
|
12540
13148
|
async function loadModelConfig(opts = {}) {
|
|
12541
13149
|
const root = opts.root ?? process.cwd();
|
|
12542
|
-
const abs =
|
|
13150
|
+
const abs = path15.resolve(root, opts.file ?? CONFIG_FILE);
|
|
12543
13151
|
let raw;
|
|
12544
13152
|
try {
|
|
12545
|
-
raw = await
|
|
13153
|
+
raw = await fs12.readFile(abs, "utf8");
|
|
12546
13154
|
} catch (e) {
|
|
12547
13155
|
const code = e.code;
|
|
12548
13156
|
if (code === "ENOENT") {
|
|
@@ -13016,8 +13624,8 @@ function toEntry(r) {
|
|
|
13016
13624
|
import { z as z12 } from "zod";
|
|
13017
13625
|
|
|
13018
13626
|
// lib/merge-gate.ts
|
|
13019
|
-
import { promises as
|
|
13020
|
-
import * as
|
|
13627
|
+
import { promises as fs13 } from "node:fs";
|
|
13628
|
+
import * as path16 from "node:path";
|
|
13021
13629
|
var DEFAULT_MERGE_GATE_CONFIG = {
|
|
13022
13630
|
enabled: true,
|
|
13023
13631
|
approvalPreCheck: true,
|
|
@@ -13025,10 +13633,10 @@ var DEFAULT_MERGE_GATE_CONFIG = {
|
|
|
13025
13633
|
};
|
|
13026
13634
|
var CONFIG_REL = ".codeforge/merge-gate.json";
|
|
13027
13635
|
async function loadMergeGate(mainRoot) {
|
|
13028
|
-
const file =
|
|
13636
|
+
const file = path16.join(mainRoot, CONFIG_REL);
|
|
13029
13637
|
let raw;
|
|
13030
13638
|
try {
|
|
13031
|
-
raw = await
|
|
13639
|
+
raw = await fs13.readFile(file, "utf8");
|
|
13032
13640
|
} catch (err) {
|
|
13033
13641
|
const e = err;
|
|
13034
13642
|
if (e.code === "ENOENT")
|
|
@@ -13130,7 +13738,8 @@ async function runMergeLoop(opts) {
|
|
|
13130
13738
|
progress("approval_pre_check", `skip_review | reviewTarget=${hit.reviewTarget} | coveredSha=${hit.coveredSha.slice(0, 12)} | ttlOk`);
|
|
13131
13739
|
const { sha } = await mergeSessionBack({
|
|
13132
13740
|
sessionId: opts.sessionId,
|
|
13133
|
-
mainRoot: opts.mainRoot
|
|
13741
|
+
mainRoot: opts.mainRoot,
|
|
13742
|
+
...opts.summary ? { summary: opts.summary } : {}
|
|
13134
13743
|
});
|
|
13135
13744
|
return {
|
|
13136
13745
|
status: "skipped_by_approval",
|
|
@@ -13222,7 +13831,8 @@ async function runMergeLoop(opts) {
|
|
|
13222
13831
|
}
|
|
13223
13832
|
const { sha } = await mergeSessionBack({
|
|
13224
13833
|
sessionId: opts.sessionId,
|
|
13225
|
-
mainRoot: opts.mainRoot
|
|
13834
|
+
mainRoot: opts.mainRoot,
|
|
13835
|
+
...opts.summary ? { summary: opts.summary } : {}
|
|
13226
13836
|
});
|
|
13227
13837
|
return {
|
|
13228
13838
|
status: "merged",
|
|
@@ -13443,7 +14053,7 @@ function isAbortError(err) {
|
|
|
13443
14053
|
return err instanceof Error && err.name === "AbortError";
|
|
13444
14054
|
}
|
|
13445
14055
|
function withTimeout2(p, ms, label, signal, hbOpts) {
|
|
13446
|
-
return new Promise((
|
|
14056
|
+
return new Promise((resolve13, reject) => {
|
|
13447
14057
|
const startedAt = Date.now();
|
|
13448
14058
|
let hbTimer = null;
|
|
13449
14059
|
let timer;
|
|
@@ -13483,7 +14093,7 @@ function withTimeout2(p, ms, label, signal, hbOpts) {
|
|
|
13483
14093
|
}
|
|
13484
14094
|
p.then((v) => {
|
|
13485
14095
|
cleanup();
|
|
13486
|
-
|
|
14096
|
+
resolve13(v);
|
|
13487
14097
|
}, (e) => {
|
|
13488
14098
|
cleanup();
|
|
13489
14099
|
reject(e);
|
|
@@ -13512,7 +14122,8 @@ var ArgsSchema12 = z12.discriminatedUnion("action", [
|
|
|
13512
14122
|
action: z12.literal("merge"),
|
|
13513
14123
|
session_id: z12.string().optional().describe("默认当前 session"),
|
|
13514
14124
|
plan_id: PlanIdSchema,
|
|
13515
|
-
force: z12.boolean().optional().describe("跳过 review 直接 squash merge(写审计)")
|
|
14125
|
+
force: z12.boolean().optional().describe("跳过 review 直接 squash merge(写审计)"),
|
|
14126
|
+
summary: z12.string().optional().describe("merge commit subject 兜底摘要(plan title 缺失时用)")
|
|
13516
14127
|
}),
|
|
13517
14128
|
z12.object({
|
|
13518
14129
|
action: z12.literal("status"),
|
|
@@ -13534,9 +14145,10 @@ var ArgsSchema12 = z12.discriminatedUnion("action", [
|
|
|
13534
14145
|
})
|
|
13535
14146
|
]);
|
|
13536
14147
|
async function buildStatusSummary(entry, mainRoot) {
|
|
13537
|
-
const [salvageBranches, allEntries] = await Promise.all([
|
|
14148
|
+
const [salvageBranches, allEntries, health] = await Promise.all([
|
|
13538
14149
|
listSalvageBranches(mainRoot).catch(() => []),
|
|
13539
|
-
listEntries(mainRoot).catch(() => [])
|
|
14150
|
+
listEntries(mainRoot).catch(() => []),
|
|
14151
|
+
collectHealthDigest(mainRoot).catch(() => null)
|
|
13540
14152
|
]);
|
|
13541
14153
|
const abandonedCount = allEntries.filter((e) => e.status === "abandoned").length;
|
|
13542
14154
|
const pendingCount = allEntries.filter((e) => e.status === "interrupted_dirty" || e.status === "creating" || e.status === "removing").length;
|
|
@@ -13564,6 +14176,11 @@ async function buildStatusSummary(entry, mainRoot) {
|
|
|
13564
14176
|
default:
|
|
13565
14177
|
nextStep = "未知状态,保守不处理;如有疑问请人工检查 worktree。";
|
|
13566
14178
|
}
|
|
14179
|
+
const healthDigest = health && health.hasIssues ? health : null;
|
|
14180
|
+
if (healthDigest && healthDigest.summary) {
|
|
14181
|
+
nextStep = `${nextStep}
|
|
14182
|
+
${healthDigest.summary}`;
|
|
14183
|
+
}
|
|
13567
14184
|
return {
|
|
13568
14185
|
worktreePath: entry.worktreePath,
|
|
13569
14186
|
status: entry.status,
|
|
@@ -13571,7 +14188,8 @@ async function buildStatusSummary(entry, mainRoot) {
|
|
|
13571
14188
|
salvageBranches,
|
|
13572
14189
|
abandonedCount,
|
|
13573
14190
|
pendingCount,
|
|
13574
|
-
nextStep
|
|
14191
|
+
nextStep,
|
|
14192
|
+
health: healthDigest
|
|
13575
14193
|
};
|
|
13576
14194
|
}
|
|
13577
14195
|
var _ctx = {};
|
|
@@ -13748,6 +14366,7 @@ async function execute12(input) {
|
|
|
13748
14366
|
mainRoot,
|
|
13749
14367
|
...mergeArgs.plan_id ? { planId: mergeArgs.plan_id } : {},
|
|
13750
14368
|
...mergeArgs.force ? { force: true } : {},
|
|
14369
|
+
...mergeArgs.summary ? { summary: mergeArgs.summary } : {},
|
|
13751
14370
|
spawner: _ctx.spawner,
|
|
13752
14371
|
...sendProgress ? {
|
|
13753
14372
|
onProgress: (state, detail) => {
|
|
@@ -13768,8 +14387,8 @@ async function execute12(input) {
|
|
|
13768
14387
|
import { z as z13 } from "zod";
|
|
13769
14388
|
|
|
13770
14389
|
// lib/plan-store.ts
|
|
13771
|
-
import { promises as
|
|
13772
|
-
import * as
|
|
14390
|
+
import { promises as fs14 } from "node:fs";
|
|
14391
|
+
import * as path17 from "node:path";
|
|
13773
14392
|
var INDEX_VERSION = 1;
|
|
13774
14393
|
|
|
13775
14394
|
class PlanStore {
|
|
@@ -13778,8 +14397,8 @@ class PlanStore {
|
|
|
13778
14397
|
now;
|
|
13779
14398
|
secondCounters = new Map;
|
|
13780
14399
|
constructor(opts = {}) {
|
|
13781
|
-
this.root =
|
|
13782
|
-
this.base = opts.base ?
|
|
14400
|
+
this.root = path17.resolve(opts.root ?? process.cwd());
|
|
14401
|
+
this.base = opts.base ? path17.resolve(opts.base) : plansDir(this.root);
|
|
13783
14402
|
this.now = opts.now ?? (() => new Date);
|
|
13784
14403
|
}
|
|
13785
14404
|
async write(input) {
|
|
@@ -13789,14 +14408,14 @@ class PlanStore {
|
|
|
13789
14408
|
if (typeof input.content !== "string" || input.content.length === 0) {
|
|
13790
14409
|
throw new Error("PlanStore.write: content 不能为空");
|
|
13791
14410
|
}
|
|
13792
|
-
await
|
|
14411
|
+
await fs14.mkdir(this.base, { recursive: true });
|
|
13793
14412
|
const lockPath = this.lockPath();
|
|
13794
14413
|
return await withFileLock(lockPath, async () => {
|
|
13795
14414
|
const index = await this.readIndexLocked();
|
|
13796
14415
|
const now = this.now();
|
|
13797
14416
|
const planId = this.allocPlanId(now, index);
|
|
13798
14417
|
const filename = this.composeFilename(planId, input.title);
|
|
13799
|
-
const absFile =
|
|
14418
|
+
const absFile = path17.join(this.base, filename);
|
|
13800
14419
|
await this.atomicWriteFile(absFile, input.content);
|
|
13801
14420
|
const entry = {
|
|
13802
14421
|
plan_id: planId,
|
|
@@ -13820,9 +14439,9 @@ class PlanStore {
|
|
|
13820
14439
|
const entry = index.entries.find((e) => e.plan_id === planId);
|
|
13821
14440
|
if (!entry)
|
|
13822
14441
|
return null;
|
|
13823
|
-
const abs =
|
|
14442
|
+
const abs = path17.join(this.base, entry.path);
|
|
13824
14443
|
try {
|
|
13825
|
-
const content = await
|
|
14444
|
+
const content = await fs14.readFile(abs, "utf8");
|
|
13826
14445
|
return { entry, content };
|
|
13827
14446
|
} catch (err) {
|
|
13828
14447
|
const e = err;
|
|
@@ -13881,7 +14500,7 @@ class PlanStore {
|
|
|
13881
14500
|
else if (e.status === "orphan")
|
|
13882
14501
|
shouldDelete = true;
|
|
13883
14502
|
if (shouldDelete) {
|
|
13884
|
-
await
|
|
14503
|
+
await fs14.rm(path17.join(this.base, e.path), { force: true }).catch(() => {});
|
|
13885
14504
|
removed++;
|
|
13886
14505
|
} else {
|
|
13887
14506
|
keep.push(e);
|
|
@@ -13903,9 +14522,9 @@ class PlanStore {
|
|
|
13903
14522
|
knownPaths.add(e.path);
|
|
13904
14523
|
if (e.status !== "active")
|
|
13905
14524
|
continue;
|
|
13906
|
-
const abs =
|
|
14525
|
+
const abs = path17.join(this.base, e.path);
|
|
13907
14526
|
try {
|
|
13908
|
-
await
|
|
14527
|
+
await fs14.stat(abs);
|
|
13909
14528
|
} catch {
|
|
13910
14529
|
e.status = "orphan";
|
|
13911
14530
|
markedOrphan++;
|
|
@@ -13915,23 +14534,23 @@ class PlanStore {
|
|
|
13915
14534
|
await this.writeIndexLocked(index);
|
|
13916
14535
|
let unindexedFiles = [];
|
|
13917
14536
|
try {
|
|
13918
|
-
const all = await
|
|
14537
|
+
const all = await fs14.readdir(this.base);
|
|
13919
14538
|
unindexedFiles = all.filter((f) => f.endsWith(".md")).filter((f) => !knownPaths.has(f));
|
|
13920
14539
|
} catch {}
|
|
13921
14540
|
return { markedOrphan, unindexedFiles };
|
|
13922
14541
|
});
|
|
13923
14542
|
}
|
|
13924
14543
|
indexPath() {
|
|
13925
|
-
return
|
|
14544
|
+
return path17.join(this.base, "index.json");
|
|
13926
14545
|
}
|
|
13927
14546
|
lockPath() {
|
|
13928
|
-
return
|
|
14547
|
+
return path17.join(this.base, "index.lock");
|
|
13929
14548
|
}
|
|
13930
14549
|
async readIndexLocked() {
|
|
13931
14550
|
const file = this.indexPath();
|
|
13932
14551
|
let raw;
|
|
13933
14552
|
try {
|
|
13934
|
-
raw = await
|
|
14553
|
+
raw = await fs14.readFile(file, "utf8");
|
|
13935
14554
|
} catch (err) {
|
|
13936
14555
|
const e = err;
|
|
13937
14556
|
if (e.code === "ENOENT")
|
|
@@ -13953,7 +14572,7 @@ class PlanStore {
|
|
|
13953
14572
|
async archiveCorruptIndex(file) {
|
|
13954
14573
|
const ts = this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
13955
14574
|
const dst = `${file}.corrupt-${ts}`;
|
|
13956
|
-
await
|
|
14575
|
+
await fs14.rename(file, dst).catch(() => {});
|
|
13957
14576
|
}
|
|
13958
14577
|
async readIndex() {
|
|
13959
14578
|
return this.readIndexLocked();
|
|
@@ -13998,15 +14617,15 @@ class PlanStore {
|
|
|
13998
14617
|
const tsPart = m ? `${m[1]}-${m[2]}` : planId;
|
|
13999
14618
|
const nnn = m ? m[3] : "000";
|
|
14000
14619
|
const sample = planFilePath(this.root, title);
|
|
14001
|
-
const base =
|
|
14620
|
+
const base = path17.basename(sample, ".md");
|
|
14002
14621
|
const slug = base.replace(/^\d{8}-\d{6}-?/, "") || "untitled";
|
|
14003
14622
|
return `${tsPart}-${nnn}-${slug}.md`;
|
|
14004
14623
|
}
|
|
14005
14624
|
async atomicWriteFile(file, data) {
|
|
14006
|
-
await
|
|
14625
|
+
await fs14.mkdir(path17.dirname(file), { recursive: true });
|
|
14007
14626
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
14008
|
-
await
|
|
14009
|
-
await
|
|
14627
|
+
await fs14.writeFile(tmp, data, "utf8");
|
|
14628
|
+
await fs14.rename(tmp, file);
|
|
14010
14629
|
}
|
|
14011
14630
|
}
|
|
14012
14631
|
function formatTimestamp(d) {
|
|
@@ -14069,8 +14688,8 @@ async function execute13(input) {
|
|
|
14069
14688
|
}
|
|
14070
14689
|
}
|
|
14071
14690
|
// tools/plan-read.ts
|
|
14072
|
-
import { promises as
|
|
14073
|
-
import * as
|
|
14691
|
+
import { promises as fs15 } from "node:fs";
|
|
14692
|
+
import * as path18 from "node:path";
|
|
14074
14693
|
import { z as z14 } from "zod";
|
|
14075
14694
|
var description14 = [
|
|
14076
14695
|
"读取方案文档内容,支持按 plan_id 或绝对路径查询。",
|
|
@@ -14176,9 +14795,9 @@ async function execute14(input) {
|
|
|
14176
14795
|
};
|
|
14177
14796
|
}
|
|
14178
14797
|
}
|
|
14179
|
-
const abs =
|
|
14798
|
+
const abs = path18.resolve(args.path);
|
|
14180
14799
|
try {
|
|
14181
|
-
const content = await
|
|
14800
|
+
const content = await fs15.readFile(abs, "utf8");
|
|
14182
14801
|
return {
|
|
14183
14802
|
ok: true,
|
|
14184
14803
|
content,
|
|
@@ -14203,16 +14822,16 @@ import { z as z15 } from "zod";
|
|
|
14203
14822
|
// lib/adr-init.ts
|
|
14204
14823
|
import { spawnSync } from "node:child_process";
|
|
14205
14824
|
import { existsSync as existsSync4, promises as fsp } from "node:fs";
|
|
14206
|
-
import * as
|
|
14825
|
+
import * as path19 from "node:path";
|
|
14207
14826
|
import * as url from "node:url";
|
|
14208
14827
|
function resolveAssetsRoot() {
|
|
14209
|
-
const here =
|
|
14828
|
+
const here = path19.dirname(url.fileURLToPath(import.meta.url));
|
|
14210
14829
|
let dir = here;
|
|
14211
14830
|
for (let i = 0;i < 6; i++) {
|
|
14212
|
-
if (existsSync4(
|
|
14213
|
-
return
|
|
14831
|
+
if (existsSync4(path19.join(dir, "package.json")) && existsSync4(path19.join(dir, "assets", "adr-init"))) {
|
|
14832
|
+
return path19.join(dir, "assets", "adr-init");
|
|
14214
14833
|
}
|
|
14215
|
-
const parent =
|
|
14834
|
+
const parent = path19.dirname(dir);
|
|
14216
14835
|
if (parent === dir)
|
|
14217
14836
|
break;
|
|
14218
14837
|
dir = parent;
|
|
@@ -14220,13 +14839,13 @@ function resolveAssetsRoot() {
|
|
|
14220
14839
|
const xdgConfig = process.env["XDG_CONFIG_HOME"];
|
|
14221
14840
|
const homeDir = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "";
|
|
14222
14841
|
const fallbackRoots = [
|
|
14223
|
-
xdgConfig ?
|
|
14224
|
-
|
|
14225
|
-
process.env["APPDATA"] ?
|
|
14226
|
-
process.env["LOCALAPPDATA"] ?
|
|
14842
|
+
xdgConfig ? path19.join(xdgConfig, "opencode") : null,
|
|
14843
|
+
path19.join(homeDir, ".config", "opencode"),
|
|
14844
|
+
process.env["APPDATA"] ? path19.join(process.env["APPDATA"], "opencode") : null,
|
|
14845
|
+
process.env["LOCALAPPDATA"] ? path19.join(process.env["LOCALAPPDATA"], "opencode") : null
|
|
14227
14846
|
].filter(Boolean);
|
|
14228
14847
|
for (const root of fallbackRoots) {
|
|
14229
|
-
const candidate =
|
|
14848
|
+
const candidate = path19.join(root, "assets", "adr-init");
|
|
14230
14849
|
if (existsSync4(candidate)) {
|
|
14231
14850
|
return candidate;
|
|
14232
14851
|
}
|
|
@@ -14263,7 +14882,7 @@ function runGitConfigHooksPath(cwd) {
|
|
|
14263
14882
|
}
|
|
14264
14883
|
}
|
|
14265
14884
|
async function runAdrInit(opts = {}) {
|
|
14266
|
-
const cwd =
|
|
14885
|
+
const cwd = path19.resolve(opts.cwd ?? process.cwd());
|
|
14267
14886
|
const force = !!opts.force;
|
|
14268
14887
|
const dryRun = !!opts.dryRun;
|
|
14269
14888
|
const writePrepare = !!opts.writePrepare;
|
|
@@ -14312,8 +14931,8 @@ async function runAdrInit(opts = {}) {
|
|
|
14312
14931
|
});
|
|
14313
14932
|
}
|
|
14314
14933
|
for (const item of plan) {
|
|
14315
|
-
const srcAbs =
|
|
14316
|
-
const dstAbs =
|
|
14934
|
+
const srcAbs = path19.join(assetsRoot, item.src);
|
|
14935
|
+
const dstAbs = path19.join(cwd, item.dst);
|
|
14317
14936
|
if (!existsSync4(srcAbs)) {
|
|
14318
14937
|
result.warnings.push(`资产缺失:${item.src}(跳过 ${item.dst})`);
|
|
14319
14938
|
continue;
|
|
@@ -14326,7 +14945,7 @@ async function runAdrInit(opts = {}) {
|
|
|
14326
14945
|
const bakRel = `${item.dst}.bak.${ts}`;
|
|
14327
14946
|
if (!dryRun) {
|
|
14328
14947
|
try {
|
|
14329
|
-
await fsp.copyFile(dstAbs,
|
|
14948
|
+
await fsp.copyFile(dstAbs, path19.join(cwd, bakRel));
|
|
14330
14949
|
} catch (e) {
|
|
14331
14950
|
result.ok = false;
|
|
14332
14951
|
result.reason = "io_error";
|
|
@@ -14338,7 +14957,7 @@ async function runAdrInit(opts = {}) {
|
|
|
14338
14957
|
}
|
|
14339
14958
|
if (!dryRun) {
|
|
14340
14959
|
try {
|
|
14341
|
-
await fsp.mkdir(
|
|
14960
|
+
await fsp.mkdir(path19.dirname(dstAbs), { recursive: true });
|
|
14342
14961
|
await fsp.copyFile(srcAbs, dstAbs);
|
|
14343
14962
|
if (item.chmod !== undefined) {
|
|
14344
14963
|
try {
|
|
@@ -14368,7 +14987,7 @@ async function runAdrInit(opts = {}) {
|
|
|
14368
14987
|
} else {
|
|
14369
14988
|
result.suggestions.push("[dry-run] 将运行:git config core.hooksPath .githooks");
|
|
14370
14989
|
}
|
|
14371
|
-
const pkgPath =
|
|
14990
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
14372
14991
|
const isNpm = existsSync4(pkgPath);
|
|
14373
14992
|
if (isNpm) {
|
|
14374
14993
|
if (writePrepare) {
|
|
@@ -14383,7 +15002,7 @@ async function runAdrInit(opts = {}) {
|
|
|
14383
15002
|
const bakRel = `package.json.bak.${ts}`;
|
|
14384
15003
|
if (!dryRun) {
|
|
14385
15004
|
try {
|
|
14386
|
-
await fsp.copyFile(pkgPath,
|
|
15005
|
+
await fsp.copyFile(pkgPath, path19.join(cwd, bakRel));
|
|
14387
15006
|
} catch (e) {
|
|
14388
15007
|
result.warnings.push(`备份 package.json 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
14389
15008
|
}
|
|
@@ -14506,12 +15125,12 @@ async function execute15(input) {
|
|
|
14506
15125
|
import { z as z16 } from "zod";
|
|
14507
15126
|
|
|
14508
15127
|
// lib/opencode-session-probe.ts
|
|
14509
|
-
import * as
|
|
15128
|
+
import * as path20 from "node:path";
|
|
14510
15129
|
import * as os5 from "node:os";
|
|
14511
15130
|
import { createRequire as createRequire2 } from "node:module";
|
|
14512
15131
|
var requireFromHere = createRequire2(import.meta.url);
|
|
14513
15132
|
var DEFAULT_LIVENESS_MS = 6 * 60 * 60000;
|
|
14514
|
-
var DEFAULT_DB_PATH =
|
|
15133
|
+
var DEFAULT_DB_PATH = path20.join(os5.homedir(), ".local/share/opencode/opencode.db");
|
|
14515
15134
|
function createSessionProbe(opts = {}) {
|
|
14516
15135
|
const dbPath = opts.dbPath ?? DEFAULT_DB_PATH;
|
|
14517
15136
|
const httpBaseUrl = opts.httpBaseUrl ?? process.env["OPENCODE_SERVER_URL"];
|
|
@@ -14611,11 +15230,13 @@ var description16 = [
|
|
|
14611
15230
|
"**模式**:",
|
|
14612
15231
|
"- 默认(保守):沿用自动 GC 的时间护栏(6h/72h),只清确凿僵尸",
|
|
14613
15232
|
"- force=true(高风险):时间护栏归零,连 probe 判 unknown 的也立即清,可能误删活跃 session 的 worktree",
|
|
15233
|
+
"- dryRun=true:不实际清理,先返回当前 worktree 健康摘要(让用户在执行前看到影响)",
|
|
14614
15234
|
"**何时不需要**:自动 30min interval 正常跑时无需手动调。"
|
|
14615
15235
|
].join(`
|
|
14616
15236
|
`);
|
|
14617
15237
|
var ArgsSchema16 = z16.object({
|
|
14618
|
-
force: z16.boolean().optional().describe("高风险:时间护栏归零,连 probe unknown 的 worktree 也立即清,可能误删活跃 session worktree。默认 false(保守)")
|
|
15238
|
+
force: z16.boolean().optional().describe("高风险:时间护栏归零,连 probe unknown 的 worktree 也立即清,可能误删活跃 session worktree。默认 false(保守)"),
|
|
15239
|
+
dryRun: z16.boolean().optional().describe("true=只预览不清理:返回当前 worktree 健康摘要(健康度信号 + 下一步提示),让用户执行 gc 前看到影响")
|
|
14619
15240
|
});
|
|
14620
15241
|
var _ctx3 = {};
|
|
14621
15242
|
function __setContext3(ctx) {
|
|
@@ -14633,6 +15254,15 @@ async function execute16(input) {
|
|
|
14633
15254
|
};
|
|
14634
15255
|
}
|
|
14635
15256
|
const force = parsed.data.force === true;
|
|
15257
|
+
const dryRun = parsed.data.dryRun === true;
|
|
15258
|
+
if (dryRun) {
|
|
15259
|
+
try {
|
|
15260
|
+
const health = await collectHealthDigest(getMainRoot2());
|
|
15261
|
+
return { ok: true, mode: "dry-run", health };
|
|
15262
|
+
} catch (err) {
|
|
15263
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
15264
|
+
}
|
|
15265
|
+
}
|
|
14636
15266
|
const probe = createSessionProbe();
|
|
14637
15267
|
try {
|
|
14638
15268
|
const result = await pruneOrphanWorktrees(getMainRoot2(), {
|
|
@@ -14856,7 +15486,7 @@ async function raceAbortTimeout(p, signal, timeoutMs, label) {
|
|
|
14856
15486
|
e.name = "AbortError";
|
|
14857
15487
|
throw e;
|
|
14858
15488
|
}
|
|
14859
|
-
return await new Promise((
|
|
15489
|
+
return await new Promise((resolve16, reject) => {
|
|
14860
15490
|
let settled = false;
|
|
14861
15491
|
const timer = setTimeout(() => {
|
|
14862
15492
|
if (settled)
|
|
@@ -14881,7 +15511,7 @@ async function raceAbortTimeout(p, signal, timeoutMs, label) {
|
|
|
14881
15511
|
settled = true;
|
|
14882
15512
|
clearTimeout(timer);
|
|
14883
15513
|
signal?.removeEventListener("abort", onAbort);
|
|
14884
|
-
|
|
15514
|
+
resolve16(v);
|
|
14885
15515
|
}, (e) => {
|
|
14886
15516
|
if (settled)
|
|
14887
15517
|
return;
|
|
@@ -15716,7 +16346,7 @@ var handler7 = codeforgeToolsServer;
|
|
|
15716
16346
|
|
|
15717
16347
|
// plugins/discover-spec-suggest.ts
|
|
15718
16348
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "node:fs";
|
|
15719
|
-
import { join as
|
|
16349
|
+
import { join as join18 } from "node:path";
|
|
15720
16350
|
|
|
15721
16351
|
// lib/handoff-schema.ts
|
|
15722
16352
|
import { z as z18 } from "zod";
|
|
@@ -15883,9 +16513,9 @@ function validateHandoff(rawYaml, fileSize) {
|
|
|
15883
16513
|
const result = HandoffSchema.safeParse(parsed);
|
|
15884
16514
|
if (!result.success) {
|
|
15885
16515
|
const first = result.error.issues[0];
|
|
15886
|
-
const
|
|
16516
|
+
const path21 = first?.path?.join(".") ?? "(root)";
|
|
15887
16517
|
const msg = first?.message ?? "unknown";
|
|
15888
|
-
return { ok: false, reason: `schema 校验失败:${
|
|
16518
|
+
return { ok: false, reason: `schema 校验失败:${path21}: ${msg}` };
|
|
15889
16519
|
}
|
|
15890
16520
|
return { ok: true, data: result.data, schemaVersion: result.data.schema_version };
|
|
15891
16521
|
}
|
|
@@ -15902,7 +16532,7 @@ var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
|
15902
16532
|
var MATCH_THRESHOLD = 0.15;
|
|
15903
16533
|
var MAX_CANDIDATES = 3;
|
|
15904
16534
|
var NUDGE_MAX_LEN = 1500;
|
|
15905
|
-
var SPECS_REL_DIR =
|
|
16535
|
+
var SPECS_REL_DIR = join18("docs", "specs");
|
|
15906
16536
|
var sessionMap = new Map;
|
|
15907
16537
|
function pruneIfOversize2() {
|
|
15908
16538
|
while (sessionMap.size > SESSION_CAP2) {
|
|
@@ -16009,7 +16639,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16009
16639
|
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
16010
16640
|
const statReader = opts.statReader ?? defaultStatReader;
|
|
16011
16641
|
const log6 = makePluginLogger(PLUGIN_NAME8);
|
|
16012
|
-
const specsRoot =
|
|
16642
|
+
const specsRoot = join18(rootDir, SPECS_REL_DIR);
|
|
16013
16643
|
const records = [];
|
|
16014
16644
|
if (!dirExists(specsRoot)) {
|
|
16015
16645
|
log6.info(`specs 目录不存在,plugin 将 no-op`, { specsRoot });
|
|
@@ -16030,7 +16660,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16030
16660
|
log6.info(`跳过非合法 slug 命名的条目`, { entry });
|
|
16031
16661
|
continue;
|
|
16032
16662
|
}
|
|
16033
|
-
const specDir =
|
|
16663
|
+
const specDir = join18(specsRoot, entry);
|
|
16034
16664
|
let dirStat;
|
|
16035
16665
|
try {
|
|
16036
16666
|
dirStat = statReader(specDir);
|
|
@@ -16043,7 +16673,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
16043
16673
|
}
|
|
16044
16674
|
if (!dirStat.isDirectory)
|
|
16045
16675
|
continue;
|
|
16046
|
-
const handoffPath =
|
|
16676
|
+
const handoffPath = join18(specDir, "handoff.yaml");
|
|
16047
16677
|
let fileStat;
|
|
16048
16678
|
try {
|
|
16049
16679
|
fileStat = statReader(handoffPath);
|
|
@@ -16215,14 +16845,14 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
16215
16845
|
var handler8 = discoverSpecSuggestServer;
|
|
16216
16846
|
|
|
16217
16847
|
// lib/memories.ts
|
|
16218
|
-
import { promises as
|
|
16219
|
-
import * as
|
|
16848
|
+
import { promises as fs16 } from "node:fs";
|
|
16849
|
+
import * as path21 from "node:path";
|
|
16220
16850
|
import * as os6 from "node:os";
|
|
16221
16851
|
function resolveConfig(c) {
|
|
16222
16852
|
return {
|
|
16223
16853
|
projectRoot: c.projectRoot,
|
|
16224
16854
|
homeDir: c.homeDir ?? os6.homedir(),
|
|
16225
|
-
projectName: c.projectName ??
|
|
16855
|
+
projectName: c.projectName ?? path21.basename(c.projectRoot),
|
|
16226
16856
|
now: c.now ?? Date.now,
|
|
16227
16857
|
log: c.log ?? (() => {}),
|
|
16228
16858
|
maxPerScope: c.maxPerScope ?? 1000
|
|
@@ -16230,13 +16860,13 @@ function resolveConfig(c) {
|
|
|
16230
16860
|
}
|
|
16231
16861
|
function fileFor(scope, cfg) {
|
|
16232
16862
|
if (scope === "project") {
|
|
16233
|
-
return
|
|
16863
|
+
return path21.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
16234
16864
|
}
|
|
16235
|
-
return
|
|
16865
|
+
return path21.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
16236
16866
|
}
|
|
16237
16867
|
async function readBank(p) {
|
|
16238
16868
|
try {
|
|
16239
|
-
const raw = await
|
|
16869
|
+
const raw = await fs16.readFile(p, "utf8");
|
|
16240
16870
|
const arr = JSON.parse(raw);
|
|
16241
16871
|
if (!Array.isArray(arr))
|
|
16242
16872
|
return [];
|
|
@@ -16246,10 +16876,10 @@ async function readBank(p) {
|
|
|
16246
16876
|
}
|
|
16247
16877
|
}
|
|
16248
16878
|
async function writeBank(p, items) {
|
|
16249
|
-
await
|
|
16879
|
+
await fs16.mkdir(path21.dirname(p), { recursive: true });
|
|
16250
16880
|
const tmp = `${p}.tmp`;
|
|
16251
|
-
await
|
|
16252
|
-
await
|
|
16881
|
+
await fs16.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
16882
|
+
await fs16.rename(tmp, p);
|
|
16253
16883
|
}
|
|
16254
16884
|
function isMemory(x) {
|
|
16255
16885
|
if (!x || typeof x !== "object")
|
|
@@ -16767,7 +17397,7 @@ var handler10 = modelFallbackServer;
|
|
|
16767
17397
|
|
|
16768
17398
|
// plugins/parallel-tool-nudge.ts
|
|
16769
17399
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
|
|
16770
|
-
import { join as
|
|
17400
|
+
import { join as join20 } from "node:path";
|
|
16771
17401
|
import { homedir as homedir7 } from "node:os";
|
|
16772
17402
|
var PLUGIN_NAME11 = "parallel-tool-nudge";
|
|
16773
17403
|
logLifecycle(PLUGIN_NAME11, "import", {});
|
|
@@ -16820,10 +17450,10 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16820
17450
|
const reader = opts.reader ?? defaultReader2;
|
|
16821
17451
|
const dirReader = opts.dirReader ?? defaultDirReader2;
|
|
16822
17452
|
const dirExists = opts.dirExists ?? defaultDirExists2;
|
|
16823
|
-
const homeAgentsDir = opts.homeAgentsDir ??
|
|
17453
|
+
const homeAgentsDir = opts.homeAgentsDir ?? join20(homedir7(), ".config", "opencode", "agents");
|
|
16824
17454
|
const candidateDirs = [
|
|
16825
|
-
|
|
16826
|
-
|
|
17455
|
+
join20(rootDir, ".codeforge", "agents"),
|
|
17456
|
+
join20(rootDir, "agents"),
|
|
16827
17457
|
homeAgentsDir
|
|
16828
17458
|
];
|
|
16829
17459
|
const result = new Map;
|
|
@@ -16846,20 +17476,20 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16846
17476
|
for (const entry of entries) {
|
|
16847
17477
|
if (!entry.endsWith(".md"))
|
|
16848
17478
|
continue;
|
|
16849
|
-
const
|
|
17479
|
+
const path22 = join20(dir, entry);
|
|
16850
17480
|
let content;
|
|
16851
17481
|
try {
|
|
16852
|
-
content = reader(
|
|
17482
|
+
content = reader(path22);
|
|
16853
17483
|
} catch (err) {
|
|
16854
17484
|
log7.warn(`agent.md 读取失败(已跳过)`, {
|
|
16855
|
-
path:
|
|
17485
|
+
path: path22,
|
|
16856
17486
|
error: err instanceof Error ? err.message : String(err)
|
|
16857
17487
|
});
|
|
16858
17488
|
continue;
|
|
16859
17489
|
}
|
|
16860
17490
|
const parsed = parseAgentFrontmatter(content);
|
|
16861
17491
|
if (!parsed) {
|
|
16862
|
-
log7.warn(`agent frontmatter 解析失败(已跳过)`, { path:
|
|
17492
|
+
log7.warn(`agent frontmatter 解析失败(已跳过)`, { path: path22 });
|
|
16863
17493
|
continue;
|
|
16864
17494
|
}
|
|
16865
17495
|
if (result.has(parsed.name))
|
|
@@ -17050,18 +17680,18 @@ var handler12 = async (_ctx4) => {
|
|
|
17050
17680
|
};
|
|
17051
17681
|
|
|
17052
17682
|
// lib/event-stream.ts
|
|
17053
|
-
import { promises as
|
|
17054
|
-
import * as
|
|
17683
|
+
import { promises as fs17 } from "node:fs";
|
|
17684
|
+
import * as path22 from "node:path";
|
|
17055
17685
|
async function loadSession(id, opts = {}) {
|
|
17056
17686
|
const file = resolveSessionFile(id, opts);
|
|
17057
|
-
const raw = await
|
|
17687
|
+
const raw = await fs17.readFile(file, "utf8");
|
|
17058
17688
|
return parseJsonl(id, raw);
|
|
17059
17689
|
}
|
|
17060
17690
|
async function listSessions(opts = {}) {
|
|
17061
17691
|
const dir = resolveDir(opts);
|
|
17062
17692
|
let entries;
|
|
17063
17693
|
try {
|
|
17064
|
-
entries = await
|
|
17694
|
+
entries = await fs17.readdir(dir, { withFileTypes: true });
|
|
17065
17695
|
} catch (err) {
|
|
17066
17696
|
if (err.code === "ENOENT")
|
|
17067
17697
|
return [];
|
|
@@ -17071,10 +17701,10 @@ async function listSessions(opts = {}) {
|
|
|
17071
17701
|
for (const e of entries) {
|
|
17072
17702
|
if (!e.isFile() || !e.name.endsWith(".jsonl"))
|
|
17073
17703
|
continue;
|
|
17074
|
-
const file =
|
|
17704
|
+
const file = path22.join(dir, e.name);
|
|
17075
17705
|
const id = e.name.replace(/\.jsonl$/, "");
|
|
17076
17706
|
try {
|
|
17077
|
-
const stat = await
|
|
17707
|
+
const stat = await fs17.stat(file);
|
|
17078
17708
|
const headerLine = await readFirstLine(file);
|
|
17079
17709
|
let started_at = stat.birthtimeMs;
|
|
17080
17710
|
if (headerLine) {
|
|
@@ -17098,11 +17728,11 @@ async function listSessions(opts = {}) {
|
|
|
17098
17728
|
return out;
|
|
17099
17729
|
}
|
|
17100
17730
|
function resolveDir(opts = {}) {
|
|
17101
|
-
const root =
|
|
17102
|
-
return opts.sessions_dir ?
|
|
17731
|
+
const root = path22.resolve(opts.root ?? process.cwd());
|
|
17732
|
+
return opts.sessions_dir ? path22.resolve(root, opts.sessions_dir) : path22.join(runtimeDir(root), "sessions");
|
|
17103
17733
|
}
|
|
17104
17734
|
function resolveSessionFile(id, opts = {}) {
|
|
17105
|
-
return
|
|
17735
|
+
return path22.join(resolveDir(opts), `${id}.jsonl`);
|
|
17106
17736
|
}
|
|
17107
17737
|
function parseJsonl(id, raw) {
|
|
17108
17738
|
const events = [];
|
|
@@ -17137,7 +17767,7 @@ function isEvent(obj) {
|
|
|
17137
17767
|
}
|
|
17138
17768
|
async function readFirstLine(file) {
|
|
17139
17769
|
const buf = Buffer.alloc(4096);
|
|
17140
|
-
const fh = await
|
|
17770
|
+
const fh = await fs17.open(file, "r");
|
|
17141
17771
|
try {
|
|
17142
17772
|
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
17143
17773
|
const s = buf.subarray(0, bytesRead).toString("utf8");
|
|
@@ -17365,11 +17995,11 @@ function isRecoveryWorthShowing(plan) {
|
|
|
17365
17995
|
}
|
|
17366
17996
|
|
|
17367
17997
|
// lib/block-pending.ts
|
|
17368
|
-
import { promises as
|
|
17369
|
-
import * as
|
|
17998
|
+
import { promises as fs18 } from "node:fs";
|
|
17999
|
+
import * as path23 from "node:path";
|
|
17370
18000
|
function blockPendingFilePath(absRoot) {
|
|
17371
18001
|
const rd = runtimeDir(absRoot, { ensure: false });
|
|
17372
|
-
return
|
|
18002
|
+
return path23.join(rd, "sessions", "autonomous-blocks.ndjson");
|
|
17373
18003
|
}
|
|
17374
18004
|
function consumeLockPath(absRoot) {
|
|
17375
18005
|
return blockPendingFilePath(absRoot) + ".consume.lock";
|
|
@@ -17378,7 +18008,7 @@ async function scanBlockPending(absRoot, filterSessionId) {
|
|
|
17378
18008
|
const file = blockPendingFilePath(absRoot);
|
|
17379
18009
|
let raw;
|
|
17380
18010
|
try {
|
|
17381
|
-
raw = await
|
|
18011
|
+
raw = await fs18.readFile(file, "utf8");
|
|
17382
18012
|
} catch {
|
|
17383
18013
|
return [];
|
|
17384
18014
|
}
|
|
@@ -17434,7 +18064,7 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
17434
18064
|
if (entries.length === 0)
|
|
17435
18065
|
return;
|
|
17436
18066
|
const file = blockPendingFilePath(absRoot);
|
|
17437
|
-
await
|
|
18067
|
+
await fs18.mkdir(path23.dirname(file), { recursive: true });
|
|
17438
18068
|
const now = new Date().toISOString();
|
|
17439
18069
|
const lines = entries.map((e) => ({
|
|
17440
18070
|
type: "consume",
|
|
@@ -17445,7 +18075,7 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
17445
18075
|
`) + `
|
|
17446
18076
|
`;
|
|
17447
18077
|
await withFileLock(consumeLockPath(absRoot), async () => {
|
|
17448
|
-
await
|
|
18078
|
+
await fs18.appendFile(file, lines, "utf8");
|
|
17449
18079
|
});
|
|
17450
18080
|
}
|
|
17451
18081
|
|
|
@@ -17584,7 +18214,7 @@ var handler13 = sessionRecoveryServer;
|
|
|
17584
18214
|
|
|
17585
18215
|
// plugins/subtask-heartbeat.ts
|
|
17586
18216
|
import { promises as fsPromises } from "node:fs";
|
|
17587
|
-
import * as
|
|
18217
|
+
import * as path24 from "node:path";
|
|
17588
18218
|
var recordSessionParent2 = recordSessionParent;
|
|
17589
18219
|
var lookupParentSessionId2 = lookupParentSessionId;
|
|
17590
18220
|
var deleteSessionParent2 = deleteSessionParent;
|
|
@@ -17925,7 +18555,7 @@ function buildErrorLogLine(errorReason, elapsedMs, now = Date.now()) {
|
|
|
17925
18555
|
}
|
|
17926
18556
|
async function appendSubagentLog(filePath, line, log9) {
|
|
17927
18557
|
try {
|
|
17928
|
-
await fsPromises.mkdir(
|
|
18558
|
+
await fsPromises.mkdir(path24.dirname(filePath), { recursive: true });
|
|
17929
18559
|
await fsPromises.appendFile(filePath, line + `
|
|
17930
18560
|
`, "utf8");
|
|
17931
18561
|
} catch (err) {
|
|
@@ -18227,8 +18857,8 @@ var subtaskHeartbeatServer = async (ctx) => {
|
|
|
18227
18857
|
var handler14 = subtaskHeartbeatServer;
|
|
18228
18858
|
|
|
18229
18859
|
// plugins/subtasks.ts
|
|
18230
|
-
import { promises as
|
|
18231
|
-
import * as
|
|
18860
|
+
import { promises as fs19 } from "node:fs";
|
|
18861
|
+
import * as path25 from "node:path";
|
|
18232
18862
|
|
|
18233
18863
|
// lib/parallel-merge.ts
|
|
18234
18864
|
init_worktree_ops();
|
|
@@ -18236,7 +18866,7 @@ init_worktree_ops();
|
|
|
18236
18866
|
// plugins/subtasks.ts
|
|
18237
18867
|
var PLUGIN_NAME15 = "subtasks";
|
|
18238
18868
|
function getLogFile(root = process.cwd()) {
|
|
18239
|
-
return
|
|
18869
|
+
return path25.join(runtimeDir(root), "logs", "subtasks.log");
|
|
18240
18870
|
}
|
|
18241
18871
|
async function writeLog(level, msg, data) {
|
|
18242
18872
|
const line = JSON.stringify({
|
|
@@ -18249,8 +18879,8 @@ async function writeLog(level, msg, data) {
|
|
|
18249
18879
|
`;
|
|
18250
18880
|
try {
|
|
18251
18881
|
const logFile = getLogFile();
|
|
18252
|
-
await
|
|
18253
|
-
await
|
|
18882
|
+
await fs19.mkdir(path25.dirname(logFile), { recursive: true });
|
|
18883
|
+
await fs19.appendFile(logFile, line, "utf8");
|
|
18254
18884
|
} catch {}
|
|
18255
18885
|
}
|
|
18256
18886
|
logLifecycle(PLUGIN_NAME15, "import");
|
|
@@ -18824,8 +19454,8 @@ var tokenManagerServer = async (ctx) => {
|
|
|
18824
19454
|
var handler17 = tokenManagerServer;
|
|
18825
19455
|
|
|
18826
19456
|
// plugins/tool-policy.ts
|
|
18827
|
-
import { promises as
|
|
18828
|
-
import * as
|
|
19457
|
+
import { promises as fs20 } from "node:fs";
|
|
19458
|
+
import * as path27 from "node:path";
|
|
18829
19459
|
|
|
18830
19460
|
// lib/tool-risk.ts
|
|
18831
19461
|
var RISK_PATTERNS = [
|
|
@@ -18979,7 +19609,7 @@ function buildHaystackFor(args, matchOn) {
|
|
|
18979
19609
|
}
|
|
18980
19610
|
|
|
18981
19611
|
// lib/file-regex-acl.ts
|
|
18982
|
-
import * as
|
|
19612
|
+
import * as path26 from "node:path";
|
|
18983
19613
|
function compileRule(r) {
|
|
18984
19614
|
if (r instanceof RegExp)
|
|
18985
19615
|
return r;
|
|
@@ -19045,7 +19675,7 @@ function normalizePath(p) {
|
|
|
19045
19675
|
let s = p.replace(/\\/g, "/");
|
|
19046
19676
|
if (s.startsWith("./"))
|
|
19047
19677
|
s = s.slice(2);
|
|
19048
|
-
s =
|
|
19678
|
+
s = path26.posix.normalize(s);
|
|
19049
19679
|
return s;
|
|
19050
19680
|
}
|
|
19051
19681
|
function checkFileAccess(acl, file, op) {
|
|
@@ -19148,11 +19778,11 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
|
19148
19778
|
const action = risks.length > 0 || worstAcl === "deny" ? "deny" : "allow";
|
|
19149
19779
|
return { action, reasons, risks, acl: aclResults };
|
|
19150
19780
|
}
|
|
19151
|
-
var POLICY_PATH =
|
|
19781
|
+
var POLICY_PATH = path27.join(".codeforge", "policy.json");
|
|
19152
19782
|
async function loadPolicy(root = process.cwd()) {
|
|
19153
|
-
const file =
|
|
19783
|
+
const file = path27.join(root, POLICY_PATH);
|
|
19154
19784
|
try {
|
|
19155
|
-
const raw = await
|
|
19785
|
+
const raw = await fs20.readFile(file, "utf8");
|
|
19156
19786
|
const data = JSON.parse(raw);
|
|
19157
19787
|
return data;
|
|
19158
19788
|
} catch {
|
|
@@ -19250,19 +19880,19 @@ var handler18 = toolPolicyServer;
|
|
|
19250
19880
|
// plugins/update-checker.ts
|
|
19251
19881
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
19252
19882
|
import { homedir as homedir8 } from "node:os";
|
|
19253
|
-
import { dirname as
|
|
19883
|
+
import { dirname as dirname16, join as join26 } from "node:path";
|
|
19254
19884
|
import { spawn } from "node:child_process";
|
|
19255
19885
|
|
|
19256
19886
|
// lib/update-checker-impl.ts
|
|
19257
19887
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
19258
|
-
import { dirname as
|
|
19888
|
+
import { dirname as dirname15, join as join25 } from "node:path";
|
|
19259
19889
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19260
19890
|
import * as https from "node:https";
|
|
19261
19891
|
|
|
19262
19892
|
// lib/version-injected.ts
|
|
19263
19893
|
function getInjectedVersion() {
|
|
19264
19894
|
try {
|
|
19265
|
-
const v = "0.8.
|
|
19895
|
+
const v = "0.8.4";
|
|
19266
19896
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
19267
19897
|
return v;
|
|
19268
19898
|
}
|
|
@@ -19309,8 +19939,8 @@ function readLocalVersion() {
|
|
|
19309
19939
|
return injected;
|
|
19310
19940
|
try {
|
|
19311
19941
|
const here = fileURLToPath2(import.meta.url);
|
|
19312
|
-
const root =
|
|
19313
|
-
const pkg = JSON.parse(readFileSync5(
|
|
19942
|
+
const root = dirname15(dirname15(here));
|
|
19943
|
+
const pkg = JSON.parse(readFileSync5(join25(root, "package.json"), "utf8"));
|
|
19314
19944
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
19315
19945
|
} catch {
|
|
19316
19946
|
return "0.0.0";
|
|
@@ -19346,7 +19976,7 @@ async function fetchLatestFromNpm(opts) {
|
|
|
19346
19976
|
return { version, tarballUrl, integrity };
|
|
19347
19977
|
}
|
|
19348
19978
|
function defaultHttpFetcher(url2, timeoutMs) {
|
|
19349
|
-
return new Promise((
|
|
19979
|
+
return new Promise((resolve17, reject) => {
|
|
19350
19980
|
const u = new URL(url2);
|
|
19351
19981
|
const headers = {
|
|
19352
19982
|
"User-Agent": "codeforge-update-checker",
|
|
@@ -19363,7 +19993,7 @@ function defaultHttpFetcher(url2, timeoutMs) {
|
|
|
19363
19993
|
const status = res.statusCode ?? 0;
|
|
19364
19994
|
if (status === 404) {
|
|
19365
19995
|
res.resume();
|
|
19366
|
-
|
|
19996
|
+
resolve17(null);
|
|
19367
19997
|
return;
|
|
19368
19998
|
}
|
|
19369
19999
|
if (status >= 400) {
|
|
@@ -19374,7 +20004,7 @@ function defaultHttpFetcher(url2, timeoutMs) {
|
|
|
19374
20004
|
let body = "";
|
|
19375
20005
|
res.setEncoding("utf8");
|
|
19376
20006
|
res.on("data", (chunk) => body += chunk);
|
|
19377
|
-
res.on("end", () =>
|
|
20007
|
+
res.on("end", () => resolve17(body));
|
|
19378
20008
|
});
|
|
19379
20009
|
req.on("timeout", () => {
|
|
19380
20010
|
req.destroy();
|
|
@@ -19390,7 +20020,7 @@ var PLUGIN_NAME19 = "update-checker";
|
|
|
19390
20020
|
var PLUGIN_VERSION = "3.0.0";
|
|
19391
20021
|
var _updateCheckStarted = false;
|
|
19392
20022
|
function getCacheFile() {
|
|
19393
|
-
return
|
|
20023
|
+
return join26(process.env["CODEFORGE_CACHE_DIR"] ?? join26(homedir8(), ".cache", "codeforge"), "update-check.json");
|
|
19394
20024
|
}
|
|
19395
20025
|
function readLastInstalledVersion() {
|
|
19396
20026
|
try {
|
|
@@ -19406,12 +20036,12 @@ function readLastInstalledVersion() {
|
|
|
19406
20036
|
function writeLastInstalledVersion(v) {
|
|
19407
20037
|
try {
|
|
19408
20038
|
const f = getCacheFile();
|
|
19409
|
-
mkdirSync3(
|
|
20039
|
+
mkdirSync3(dirname16(f), { recursive: true });
|
|
19410
20040
|
writeFileSync2(f, JSON.stringify({ installedVersion: v }, null, 2), "utf8");
|
|
19411
20041
|
} catch {}
|
|
19412
20042
|
}
|
|
19413
20043
|
function spawnAsync(cmd, args, opts = {}) {
|
|
19414
|
-
return new Promise((
|
|
20044
|
+
return new Promise((resolve17) => {
|
|
19415
20045
|
const chunks = [];
|
|
19416
20046
|
const errChunks = [];
|
|
19417
20047
|
const proc = spawn(cmd, args, {
|
|
@@ -19425,13 +20055,13 @@ function spawnAsync(cmd, args, opts = {}) {
|
|
|
19425
20055
|
if (opts.timeout) {
|
|
19426
20056
|
timer = setTimeout(() => {
|
|
19427
20057
|
proc.kill();
|
|
19428
|
-
|
|
20058
|
+
resolve17({ status: -1, stdout: "", stderr: "timeout" });
|
|
19429
20059
|
}, opts.timeout);
|
|
19430
20060
|
}
|
|
19431
20061
|
proc.on("close", (code) => {
|
|
19432
20062
|
if (timer)
|
|
19433
20063
|
clearTimeout(timer);
|
|
19434
|
-
|
|
20064
|
+
resolve17({
|
|
19435
20065
|
status: code ?? -1,
|
|
19436
20066
|
stdout: Buffer.concat(chunks).toString("utf8"),
|
|
19437
20067
|
stderr: Buffer.concat(errChunks).toString("utf8")
|
|
@@ -19440,7 +20070,7 @@ function spawnAsync(cmd, args, opts = {}) {
|
|
|
19440
20070
|
proc.on("error", () => {
|
|
19441
20071
|
if (timer)
|
|
19442
20072
|
clearTimeout(timer);
|
|
19443
|
-
|
|
20073
|
+
resolve17({ status: -1, stdout: "", stderr: "spawn error" });
|
|
19444
20074
|
});
|
|
19445
20075
|
});
|
|
19446
20076
|
}
|
|
@@ -19571,17 +20201,18 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19571
20201
|
}
|
|
19572
20202
|
safeWriteLog(PLUGIN_NAME19, { level: "info", msg: "npm_install_success", remote });
|
|
19573
20203
|
try {
|
|
19574
|
-
const
|
|
20204
|
+
const cacheRoot = process.env["XDG_CACHE_HOME"] ?? join26(homedir8(), ".cache");
|
|
20205
|
+
const opencodeCache = join26(cacheRoot, "opencode", "packages", "@andyqiu", "codeforge@latest");
|
|
19575
20206
|
if (existsSync5(opencodeCache)) {
|
|
19576
20207
|
rmSync(opencodeCache, { recursive: true, force: true });
|
|
19577
|
-
safeWriteLog(PLUGIN_NAME19, { level: "info", msg: "
|
|
20208
|
+
safeWriteLog(PLUGIN_NAME19, { level: "info", msg: "opencode_plugin_cache_cleared", path: opencodeCache });
|
|
19578
20209
|
}
|
|
19579
20210
|
} catch (e) {
|
|
19580
|
-
safeWriteLog(PLUGIN_NAME19, { level: "warn", msg: "
|
|
20211
|
+
safeWriteLog(PLUGIN_NAME19, { level: "warn", msg: "opencode_plugin_cache_clear_failed", error: e.message });
|
|
19581
20212
|
}
|
|
19582
20213
|
const npmRoot = await getNpmGlobalRoot(npmBin);
|
|
19583
|
-
const pkgRoot = npmRoot ?
|
|
19584
|
-
const installMjs = pkgRoot ?
|
|
20214
|
+
const pkgRoot = npmRoot ? join26(npmRoot, "@andyqiu", "codeforge") : null;
|
|
20215
|
+
const installMjs = pkgRoot ? join26(pkgRoot, "install.mjs") : null;
|
|
19585
20216
|
if (!installMjs || !existsSync5(installMjs)) {
|
|
19586
20217
|
safeWriteLog(PLUGIN_NAME19, { level: "warn", msg: "install_mjs_not_found", path: installMjs ?? "null" });
|
|
19587
20218
|
await postToast(ctx, `[codeforge] ⚠ npm 包已升级 ${local} → ${remote},但资产部署未完成。下次启动将重试,或手动运行:codeforge upgrade`);
|
|
@@ -19598,26 +20229,31 @@ var updateCheckerServer = async (ctx) => {
|
|
|
19598
20229
|
}
|
|
19599
20230
|
writeLastInstalledVersion(remote);
|
|
19600
20231
|
safeWriteLog(PLUGIN_NAME19, { level: "info", msg: "auto_install_full_success", local, remote, nodeBin, npmBin });
|
|
19601
|
-
await postToast(ctx, `[
|
|
19602
|
-
回滚:npm install -g ${u.package}@${local}`);
|
|
20232
|
+
await postToast(ctx, `[Codeforge] 已升级 ${remote},重启 opencode 生效`);
|
|
19603
20233
|
});
|
|
19604
20234
|
});
|
|
19605
20235
|
});
|
|
19606
20236
|
return {};
|
|
19607
20237
|
};
|
|
19608
20238
|
async function postToast(ctx, message) {
|
|
19609
|
-
await safeAsync(PLUGIN_NAME19, "
|
|
19610
|
-
|
|
20239
|
+
await safeAsync(PLUGIN_NAME19, "tui.showToast", async () => {
|
|
20240
|
+
const tui = ctx.client.tui;
|
|
20241
|
+
if (!tui || typeof tui.showToast !== "function") {
|
|
20242
|
+
safeWriteLog(PLUGIN_NAME19, { level: "info", msg: "tui_show_toast_unavailable_fallback", message });
|
|
20243
|
+
return;
|
|
20244
|
+
}
|
|
20245
|
+
await tui.showToast({ body: { message, variant: "info", duration: 8000 } });
|
|
20246
|
+
safeWriteLog(PLUGIN_NAME19, { level: "info", msg: "tui_show_toast_ok" });
|
|
19611
20247
|
});
|
|
19612
20248
|
}
|
|
19613
20249
|
var handler19 = updateCheckerServer;
|
|
19614
20250
|
|
|
19615
20251
|
// plugins/workflow-engine.ts
|
|
19616
|
-
import * as
|
|
20252
|
+
import * as path29 from "node:path";
|
|
19617
20253
|
|
|
19618
20254
|
// lib/workflow-loader.ts
|
|
19619
|
-
import { promises as
|
|
19620
|
-
import * as
|
|
20255
|
+
import { promises as fs21 } from "node:fs";
|
|
20256
|
+
import * as path28 from "node:path";
|
|
19621
20257
|
import { z as z19 } from "zod";
|
|
19622
20258
|
var ActionSchema = z19.object({
|
|
19623
20259
|
tool: z19.string().min(1, "action.tool 不能为空"),
|
|
@@ -19703,7 +20339,7 @@ function parseWorkflowYaml(yaml, sourcePath = "<inline>") {
|
|
|
19703
20339
|
async function loadWorkflowFromFile(filePath) {
|
|
19704
20340
|
let txt;
|
|
19705
20341
|
try {
|
|
19706
|
-
txt = await
|
|
20342
|
+
txt = await fs21.readFile(filePath, "utf8");
|
|
19707
20343
|
} catch (err) {
|
|
19708
20344
|
return {
|
|
19709
20345
|
ok: false,
|
|
@@ -19718,7 +20354,7 @@ async function loadWorkflowsFromDir(dir) {
|
|
|
19718
20354
|
const failed = [];
|
|
19719
20355
|
let entries;
|
|
19720
20356
|
try {
|
|
19721
|
-
entries = await
|
|
20357
|
+
entries = await fs21.readdir(dir);
|
|
19722
20358
|
} catch (err) {
|
|
19723
20359
|
const e = err;
|
|
19724
20360
|
if (e.code === "ENOENT")
|
|
@@ -19730,7 +20366,7 @@ async function loadWorkflowsFromDir(dir) {
|
|
|
19730
20366
|
continue;
|
|
19731
20367
|
if (!/\.ya?ml$/i.test(name))
|
|
19732
20368
|
continue;
|
|
19733
|
-
const full =
|
|
20369
|
+
const full = path28.join(dir, name);
|
|
19734
20370
|
const r = await loadWorkflowFromFile(full);
|
|
19735
20371
|
if (r.ok)
|
|
19736
20372
|
loaded.push(r);
|
|
@@ -20120,7 +20756,7 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
20120
20756
|
}
|
|
20121
20757
|
var workflowEngineServer = async (ctx) => {
|
|
20122
20758
|
const directory = ctx.directory ?? process.cwd();
|
|
20123
|
-
const workflowsDir =
|
|
20759
|
+
const workflowsDir = path29.join(directory, "workflows");
|
|
20124
20760
|
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME20}] preload workflows failed`, {
|
|
20125
20761
|
error: err instanceof Error ? err.message : String(err)
|
|
20126
20762
|
}));
|
|
@@ -20164,7 +20800,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
20164
20800
|
var handler20 = workflowEngineServer;
|
|
20165
20801
|
|
|
20166
20802
|
// plugins/session-worktree-guard.ts
|
|
20167
|
-
import
|
|
20803
|
+
import path30 from "node:path";
|
|
20168
20804
|
import { stat } from "node:fs/promises";
|
|
20169
20805
|
var PLUGIN_NAME21 = "session-worktree-guard";
|
|
20170
20806
|
logLifecycle(PLUGIN_NAME21, "import", {});
|
|
@@ -20302,28 +20938,28 @@ var MERGE_CALLER_WHITELIST = new Set([
|
|
|
20302
20938
|
var FORCE_MERGE_CALLER_WHITELIST = new Set([
|
|
20303
20939
|
"codeforge"
|
|
20304
20940
|
]);
|
|
20305
|
-
var CODEFORGE_WORKTREE_DIR_NAME =
|
|
20941
|
+
var CODEFORGE_WORKTREE_DIR_NAME = path30.join(".git", "codeforge-worktrees");
|
|
20306
20942
|
function worktreesRoot(mainRoot) {
|
|
20307
|
-
return
|
|
20943
|
+
return path30.join(mainRoot, CODEFORGE_WORKTREE_DIR_NAME);
|
|
20308
20944
|
}
|
|
20309
20945
|
function isInsideAnyWorktreeDir(absPath, mainRoot) {
|
|
20310
|
-
if (!
|
|
20946
|
+
if (!path30.isAbsolute(absPath))
|
|
20311
20947
|
return false;
|
|
20312
20948
|
const root = worktreesRoot(mainRoot);
|
|
20313
20949
|
if (absPath === root)
|
|
20314
20950
|
return false;
|
|
20315
|
-
const prefix = root.endsWith(
|
|
20951
|
+
const prefix = root.endsWith(path30.sep) ? root : root + path30.sep;
|
|
20316
20952
|
return absPath.startsWith(prefix);
|
|
20317
20953
|
}
|
|
20318
20954
|
function worktreeRootOf(absPath, mainRoot) {
|
|
20319
20955
|
const root = worktreesRoot(mainRoot);
|
|
20320
|
-
const prefix = root.endsWith(
|
|
20956
|
+
const prefix = root.endsWith(path30.sep) ? root : root + path30.sep;
|
|
20321
20957
|
if (!absPath.startsWith(prefix))
|
|
20322
20958
|
return null;
|
|
20323
|
-
const seg = absPath.slice(prefix.length).split(
|
|
20959
|
+
const seg = absPath.slice(prefix.length).split(path30.sep)[0];
|
|
20324
20960
|
if (!seg || seg === "..")
|
|
20325
20961
|
return null;
|
|
20326
|
-
return
|
|
20962
|
+
return path30.join(root, seg);
|
|
20327
20963
|
}
|
|
20328
20964
|
function stripPairedQuotes(raw) {
|
|
20329
20965
|
if (raw.length >= 2) {
|
|
@@ -20354,7 +20990,7 @@ function resolveExplicitWorktreeTarget(argsObj, mainRoot) {
|
|
|
20354
20990
|
for (const cand of candidates) {
|
|
20355
20991
|
if (!cand)
|
|
20356
20992
|
continue;
|
|
20357
|
-
const abs =
|
|
20993
|
+
const abs = path30.resolve(mainRoot, cand);
|
|
20358
20994
|
if (isInsideAnyWorktreeDir(abs, mainRoot)) {
|
|
20359
20995
|
const wtRoot = worktreeRootOf(abs, mainRoot);
|
|
20360
20996
|
if (wtRoot)
|
|
@@ -20367,14 +21003,14 @@ function resolveExplicitWorktreeTarget(argsObj, mainRoot) {
|
|
|
20367
21003
|
}
|
|
20368
21004
|
}
|
|
20369
21005
|
function stripSharedGitDirRef(command, mainRoot) {
|
|
20370
|
-
const escGitDir = escapeRegex(
|
|
21006
|
+
const escGitDir = escapeRegex(path30.join(mainRoot, ".git"));
|
|
20371
21007
|
const re = new RegExp(`--git-dir(?:=|\\s+)['"]?${escGitDir}(?:/[^\\s'"\\x60)]*)?['"]?`, "g");
|
|
20372
21008
|
return command.replace(re, " ");
|
|
20373
21009
|
}
|
|
20374
21010
|
function rewritePath(value, mainRoot, worktreeRoot) {
|
|
20375
21011
|
if (!value)
|
|
20376
21012
|
return null;
|
|
20377
|
-
const resolved =
|
|
21013
|
+
const resolved = path30.isAbsolute(value) ? value : path30.resolve(mainRoot, value);
|
|
20378
21014
|
const wtPrefix2 = worktreeRoot.endsWith("/") ? worktreeRoot : worktreeRoot + "/";
|
|
20379
21015
|
if (resolved === worktreeRoot || resolved.startsWith(wtPrefix2)) {
|
|
20380
21016
|
return null;
|
|
@@ -20412,7 +21048,7 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
|
|
|
20412
21048
|
}
|
|
20413
21049
|
}
|
|
20414
21050
|
const wtRoot = worktreesRoot(mainRoot);
|
|
20415
|
-
const wtRootPrefix = wtRoot +
|
|
21051
|
+
const wtRootPrefix = wtRoot + path30.sep;
|
|
20416
21052
|
const escapedWtRootPrefix = escapeRegex(wtRootPrefix);
|
|
20417
21053
|
const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
|
|
20418
21054
|
const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
|
|
@@ -20467,8 +21103,8 @@ function collectWritePaths(toolName, argsObj, worktreeRoot) {
|
|
|
20467
21103
|
const candidate = toolName === "write" || toolName === "edit" ? argsObj["filePath"] : toolName === "ast_edit" ? argsObj["target"] : undefined;
|
|
20468
21104
|
if (typeof candidate !== "string" || candidate.length === 0)
|
|
20469
21105
|
return out;
|
|
20470
|
-
const abs =
|
|
20471
|
-
const rel =
|
|
21106
|
+
const abs = path30.isAbsolute(candidate) ? candidate : path30.resolve(worktreeRoot, candidate);
|
|
21107
|
+
const rel = path30.relative(worktreeRoot, abs).split(path30.sep).join("/");
|
|
20472
21108
|
out.push(rel);
|
|
20473
21109
|
return out;
|
|
20474
21110
|
}
|
|
@@ -20479,7 +21115,7 @@ async function isCodeforgeManagedProject(mainRoot, opts = {}) {
|
|
|
20479
21115
|
if (force === "1" || force === "true" || force === "yes")
|
|
20480
21116
|
return true;
|
|
20481
21117
|
try {
|
|
20482
|
-
const st = await stat(
|
|
21118
|
+
const st = await stat(path30.join(mainRoot, ".codeforge"));
|
|
20483
21119
|
return st.isDirectory();
|
|
20484
21120
|
} catch {
|
|
20485
21121
|
return false;
|
|
@@ -20509,11 +21145,11 @@ var STALE_MERGE_HEAD_MS = 24 * 60 * 60000;
|
|
|
20509
21145
|
var _staleMergeHeadWarned = false;
|
|
20510
21146
|
var _mergeBypassToastShown = false;
|
|
20511
21147
|
async function isMainRepoMidMerge(mainRoot, opts = {}) {
|
|
20512
|
-
const gitDir =
|
|
21148
|
+
const gitDir = path30.join(mainRoot, ".git");
|
|
20513
21149
|
const markers = [
|
|
20514
|
-
|
|
20515
|
-
|
|
20516
|
-
|
|
21150
|
+
path30.join(gitDir, "MERGE_HEAD"),
|
|
21151
|
+
path30.join(gitDir, "rebase-merge"),
|
|
21152
|
+
path30.join(gitDir, "CHERRY_PICK_HEAD")
|
|
20517
21153
|
];
|
|
20518
21154
|
let freshest = -1;
|
|
20519
21155
|
for (const m of markers) {
|
|
@@ -21172,6 +21808,10 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
21172
21808
|
if (rec.cleanedCreating.length > 0 || rec.finishedRemoving.length > 0 || rec.keptConservative.length > 0) {
|
|
21173
21809
|
safeWriteLog(PLUGIN_NAME22, { hook: "activate.reconcile", ...rec });
|
|
21174
21810
|
}
|
|
21811
|
+
const merc = await reconcileInterruptedMerges(mainRoot);
|
|
21812
|
+
if (merc.recoveredMerged.length > 0 || merc.rolledBack.length > 0 || merc.conflicts.length > 0 || merc.registryUnreadable) {
|
|
21813
|
+
safeWriteLog(PLUGIN_NAME22, { hook: "activate.reconcileMerge", ...merc });
|
|
21814
|
+
}
|
|
21175
21815
|
const result = await pruneOrphanWorktrees(mainRoot, {
|
|
21176
21816
|
isSessionAlive: _probe.isSessionAlive
|
|
21177
21817
|
});
|
|
@@ -21203,6 +21843,10 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
21203
21843
|
if (rec.cleanedCreating.length > 0 || rec.finishedRemoving.length > 0 || rec.keptConservative.length > 0) {
|
|
21204
21844
|
safeWriteLog(PLUGIN_NAME22, { hook: "interval.reconcile", ...rec });
|
|
21205
21845
|
}
|
|
21846
|
+
const merc = await reconcileInterruptedMerges(mainRoot);
|
|
21847
|
+
if (merc.recoveredMerged.length > 0 || merc.rolledBack.length > 0 || merc.conflicts.length > 0 || merc.registryUnreadable) {
|
|
21848
|
+
safeWriteLog(PLUGIN_NAME22, { hook: "interval.reconcileMerge", ...merc });
|
|
21849
|
+
}
|
|
21206
21850
|
const result = await pruneOrphanWorktrees(mainRoot, {
|
|
21207
21851
|
isSessionAlive: _probe.isSessionAlive
|
|
21208
21852
|
});
|
|
@@ -21476,6 +22120,7 @@ var src_default = pluginModule;
|
|
|
21476
22120
|
export {
|
|
21477
22121
|
src_default as default,
|
|
21478
22122
|
createCodeforgeServer,
|
|
22123
|
+
collectHealthDigest,
|
|
21479
22124
|
codeforgeServer,
|
|
21480
22125
|
codeforgeDevServer
|
|
21481
22126
|
};
|