@andyqiu/codeforge 0.6.7 → 0.6.12
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/agents/codeforge.md +7 -1
- package/agents/coder-deep.md +9 -2
- package/agents/coder-quick.md +9 -2
- package/agents/coder.md +9 -2
- package/commands/gc-worktrees.md +71 -0
- package/dist/index.js +791 -497
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26,6 +26,8 @@ __export(exports_worktree_ops, {
|
|
|
26
26
|
mergeCommit: () => mergeCommit,
|
|
27
27
|
mergeAbort: () => mergeAbort,
|
|
28
28
|
listWorktrees: () => listWorktrees,
|
|
29
|
+
isWorktreeAbsentError: () => isWorktreeAbsentError,
|
|
30
|
+
isBranchAbsentError: () => isBranchAbsentError,
|
|
29
31
|
getMergeConflicts: () => getMergeConflicts,
|
|
30
32
|
ensureWorktree: () => ensureWorktree,
|
|
31
33
|
deleteBranchIfExists: () => deleteBranchIfExists,
|
|
@@ -91,6 +93,10 @@ async function deleteBranchIfExists(opts) {
|
|
|
91
93
|
throw err;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
96
|
+
function isBranchAbsentError(err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
return /not found/i.test(msg);
|
|
99
|
+
}
|
|
94
100
|
async function listWorktrees(opts) {
|
|
95
101
|
const out = await runGit(opts.root, ["worktree", "list", "--porcelain"], opts.git_timeout_ms ?? 3000);
|
|
96
102
|
return parseWorktreeList(out);
|
|
@@ -366,17 +372,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
366
372
|
visit.BREAK = BREAK;
|
|
367
373
|
visit.SKIP = SKIP;
|
|
368
374
|
visit.REMOVE = REMOVE;
|
|
369
|
-
function visit_(key, node, visitor,
|
|
370
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
375
|
+
function visit_(key, node, visitor, path21) {
|
|
376
|
+
const ctrl = callVisitor(key, node, visitor, path21);
|
|
371
377
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
372
|
-
replaceNode(key,
|
|
373
|
-
return visit_(key, ctrl, visitor,
|
|
378
|
+
replaceNode(key, path21, ctrl);
|
|
379
|
+
return visit_(key, ctrl, visitor, path21);
|
|
374
380
|
}
|
|
375
381
|
if (typeof ctrl !== "symbol") {
|
|
376
382
|
if (identity.isCollection(node)) {
|
|
377
|
-
|
|
383
|
+
path21 = Object.freeze(path21.concat(node));
|
|
378
384
|
for (let i = 0;i < node.items.length; ++i) {
|
|
379
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
385
|
+
const ci = visit_(i, node.items[i], visitor, path21);
|
|
380
386
|
if (typeof ci === "number")
|
|
381
387
|
i = ci - 1;
|
|
382
388
|
else if (ci === BREAK)
|
|
@@ -387,13 +393,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
387
393
|
}
|
|
388
394
|
}
|
|
389
395
|
} else if (identity.isPair(node)) {
|
|
390
|
-
|
|
391
|
-
const ck = visit_("key", node.key, visitor,
|
|
396
|
+
path21 = Object.freeze(path21.concat(node));
|
|
397
|
+
const ck = visit_("key", node.key, visitor, path21);
|
|
392
398
|
if (ck === BREAK)
|
|
393
399
|
return BREAK;
|
|
394
400
|
else if (ck === REMOVE)
|
|
395
401
|
node.key = null;
|
|
396
|
-
const cv = visit_("value", node.value, visitor,
|
|
402
|
+
const cv = visit_("value", node.value, visitor, path21);
|
|
397
403
|
if (cv === BREAK)
|
|
398
404
|
return BREAK;
|
|
399
405
|
else if (cv === REMOVE)
|
|
@@ -414,17 +420,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
414
420
|
visitAsync.BREAK = BREAK;
|
|
415
421
|
visitAsync.SKIP = SKIP;
|
|
416
422
|
visitAsync.REMOVE = REMOVE;
|
|
417
|
-
async function visitAsync_(key, node, visitor,
|
|
418
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
423
|
+
async function visitAsync_(key, node, visitor, path21) {
|
|
424
|
+
const ctrl = await callVisitor(key, node, visitor, path21);
|
|
419
425
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
420
|
-
replaceNode(key,
|
|
421
|
-
return visitAsync_(key, ctrl, visitor,
|
|
426
|
+
replaceNode(key, path21, ctrl);
|
|
427
|
+
return visitAsync_(key, ctrl, visitor, path21);
|
|
422
428
|
}
|
|
423
429
|
if (typeof ctrl !== "symbol") {
|
|
424
430
|
if (identity.isCollection(node)) {
|
|
425
|
-
|
|
431
|
+
path21 = Object.freeze(path21.concat(node));
|
|
426
432
|
for (let i = 0;i < node.items.length; ++i) {
|
|
427
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
433
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path21);
|
|
428
434
|
if (typeof ci === "number")
|
|
429
435
|
i = ci - 1;
|
|
430
436
|
else if (ci === BREAK)
|
|
@@ -435,13 +441,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
435
441
|
}
|
|
436
442
|
}
|
|
437
443
|
} else if (identity.isPair(node)) {
|
|
438
|
-
|
|
439
|
-
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);
|
|
440
446
|
if (ck === BREAK)
|
|
441
447
|
return BREAK;
|
|
442
448
|
else if (ck === REMOVE)
|
|
443
449
|
node.key = null;
|
|
444
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
450
|
+
const cv = await visitAsync_("value", node.value, visitor, path21);
|
|
445
451
|
if (cv === BREAK)
|
|
446
452
|
return BREAK;
|
|
447
453
|
else if (cv === REMOVE)
|
|
@@ -468,23 +474,23 @@ var require_visit = __commonJS((exports) => {
|
|
|
468
474
|
}
|
|
469
475
|
return visitor;
|
|
470
476
|
}
|
|
471
|
-
function callVisitor(key, node, visitor,
|
|
477
|
+
function callVisitor(key, node, visitor, path21) {
|
|
472
478
|
if (typeof visitor === "function")
|
|
473
|
-
return visitor(key, node,
|
|
479
|
+
return visitor(key, node, path21);
|
|
474
480
|
if (identity.isMap(node))
|
|
475
|
-
return visitor.Map?.(key, node,
|
|
481
|
+
return visitor.Map?.(key, node, path21);
|
|
476
482
|
if (identity.isSeq(node))
|
|
477
|
-
return visitor.Seq?.(key, node,
|
|
483
|
+
return visitor.Seq?.(key, node, path21);
|
|
478
484
|
if (identity.isPair(node))
|
|
479
|
-
return visitor.Pair?.(key, node,
|
|
485
|
+
return visitor.Pair?.(key, node, path21);
|
|
480
486
|
if (identity.isScalar(node))
|
|
481
|
-
return visitor.Scalar?.(key, node,
|
|
487
|
+
return visitor.Scalar?.(key, node, path21);
|
|
482
488
|
if (identity.isAlias(node))
|
|
483
|
-
return visitor.Alias?.(key, node,
|
|
489
|
+
return visitor.Alias?.(key, node, path21);
|
|
484
490
|
return;
|
|
485
491
|
}
|
|
486
|
-
function replaceNode(key,
|
|
487
|
-
const parent =
|
|
492
|
+
function replaceNode(key, path21, node) {
|
|
493
|
+
const parent = path21[path21.length - 1];
|
|
488
494
|
if (identity.isCollection(parent)) {
|
|
489
495
|
parent.items[key] = node;
|
|
490
496
|
} else if (identity.isPair(parent)) {
|
|
@@ -1043,10 +1049,10 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1043
1049
|
var createNode = require_createNode();
|
|
1044
1050
|
var identity = require_identity();
|
|
1045
1051
|
var Node = require_Node();
|
|
1046
|
-
function collectionFromPath(schema,
|
|
1052
|
+
function collectionFromPath(schema, path21, value) {
|
|
1047
1053
|
let v = value;
|
|
1048
|
-
for (let i =
|
|
1049
|
-
const k =
|
|
1054
|
+
for (let i = path21.length - 1;i >= 0; --i) {
|
|
1055
|
+
const k = path21[i];
|
|
1050
1056
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
1051
1057
|
const a = [];
|
|
1052
1058
|
a[k] = v;
|
|
@@ -1065,7 +1071,7 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1065
1071
|
sourceObjects: new Map
|
|
1066
1072
|
});
|
|
1067
1073
|
}
|
|
1068
|
-
var isEmptyPath = (
|
|
1074
|
+
var isEmptyPath = (path21) => path21 == null || typeof path21 === "object" && !!path21[Symbol.iterator]().next().done;
|
|
1069
1075
|
|
|
1070
1076
|
class Collection extends Node.NodeBase {
|
|
1071
1077
|
constructor(type, schema) {
|
|
@@ -1086,11 +1092,11 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1086
1092
|
copy.range = this.range.slice();
|
|
1087
1093
|
return copy;
|
|
1088
1094
|
}
|
|
1089
|
-
addIn(
|
|
1090
|
-
if (isEmptyPath(
|
|
1095
|
+
addIn(path21, value) {
|
|
1096
|
+
if (isEmptyPath(path21))
|
|
1091
1097
|
this.add(value);
|
|
1092
1098
|
else {
|
|
1093
|
-
const [key, ...rest] =
|
|
1099
|
+
const [key, ...rest] = path21;
|
|
1094
1100
|
const node = this.get(key, true);
|
|
1095
1101
|
if (identity.isCollection(node))
|
|
1096
1102
|
node.addIn(rest, value);
|
|
@@ -1100,8 +1106,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1100
1106
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
1101
1107
|
}
|
|
1102
1108
|
}
|
|
1103
|
-
deleteIn(
|
|
1104
|
-
const [key, ...rest] =
|
|
1109
|
+
deleteIn(path21) {
|
|
1110
|
+
const [key, ...rest] = path21;
|
|
1105
1111
|
if (rest.length === 0)
|
|
1106
1112
|
return this.delete(key);
|
|
1107
1113
|
const node = this.get(key, true);
|
|
@@ -1110,8 +1116,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1110
1116
|
else
|
|
1111
1117
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
1112
1118
|
}
|
|
1113
|
-
getIn(
|
|
1114
|
-
const [key, ...rest] =
|
|
1119
|
+
getIn(path21, keepScalar) {
|
|
1120
|
+
const [key, ...rest] = path21;
|
|
1115
1121
|
const node = this.get(key, true);
|
|
1116
1122
|
if (rest.length === 0)
|
|
1117
1123
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -1126,15 +1132,15 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1126
1132
|
return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
|
|
1127
1133
|
});
|
|
1128
1134
|
}
|
|
1129
|
-
hasIn(
|
|
1130
|
-
const [key, ...rest] =
|
|
1135
|
+
hasIn(path21) {
|
|
1136
|
+
const [key, ...rest] = path21;
|
|
1131
1137
|
if (rest.length === 0)
|
|
1132
1138
|
return this.has(key);
|
|
1133
1139
|
const node = this.get(key, true);
|
|
1134
1140
|
return identity.isCollection(node) ? node.hasIn(rest) : false;
|
|
1135
1141
|
}
|
|
1136
|
-
setIn(
|
|
1137
|
-
const [key, ...rest] =
|
|
1142
|
+
setIn(path21, value) {
|
|
1143
|
+
const [key, ...rest] = path21;
|
|
1138
1144
|
if (rest.length === 0) {
|
|
1139
1145
|
this.set(key, value);
|
|
1140
1146
|
} else {
|
|
@@ -3527,9 +3533,9 @@ var require_Document = __commonJS((exports) => {
|
|
|
3527
3533
|
if (assertCollection(this.contents))
|
|
3528
3534
|
this.contents.add(value);
|
|
3529
3535
|
}
|
|
3530
|
-
addIn(
|
|
3536
|
+
addIn(path21, value) {
|
|
3531
3537
|
if (assertCollection(this.contents))
|
|
3532
|
-
this.contents.addIn(
|
|
3538
|
+
this.contents.addIn(path21, value);
|
|
3533
3539
|
}
|
|
3534
3540
|
createAlias(node, name) {
|
|
3535
3541
|
if (!node.anchor) {
|
|
@@ -3578,30 +3584,30 @@ var require_Document = __commonJS((exports) => {
|
|
|
3578
3584
|
delete(key) {
|
|
3579
3585
|
return assertCollection(this.contents) ? this.contents.delete(key) : false;
|
|
3580
3586
|
}
|
|
3581
|
-
deleteIn(
|
|
3582
|
-
if (Collection.isEmptyPath(
|
|
3587
|
+
deleteIn(path21) {
|
|
3588
|
+
if (Collection.isEmptyPath(path21)) {
|
|
3583
3589
|
if (this.contents == null)
|
|
3584
3590
|
return false;
|
|
3585
3591
|
this.contents = null;
|
|
3586
3592
|
return true;
|
|
3587
3593
|
}
|
|
3588
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3594
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path21) : false;
|
|
3589
3595
|
}
|
|
3590
3596
|
get(key, keepScalar) {
|
|
3591
3597
|
return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined;
|
|
3592
3598
|
}
|
|
3593
|
-
getIn(
|
|
3594
|
-
if (Collection.isEmptyPath(
|
|
3599
|
+
getIn(path21, keepScalar) {
|
|
3600
|
+
if (Collection.isEmptyPath(path21))
|
|
3595
3601
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3596
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3602
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path21, keepScalar) : undefined;
|
|
3597
3603
|
}
|
|
3598
3604
|
has(key) {
|
|
3599
3605
|
return identity.isCollection(this.contents) ? this.contents.has(key) : false;
|
|
3600
3606
|
}
|
|
3601
|
-
hasIn(
|
|
3602
|
-
if (Collection.isEmptyPath(
|
|
3607
|
+
hasIn(path21) {
|
|
3608
|
+
if (Collection.isEmptyPath(path21))
|
|
3603
3609
|
return this.contents !== undefined;
|
|
3604
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3610
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path21) : false;
|
|
3605
3611
|
}
|
|
3606
3612
|
set(key, value) {
|
|
3607
3613
|
if (this.contents == null) {
|
|
@@ -3610,13 +3616,13 @@ var require_Document = __commonJS((exports) => {
|
|
|
3610
3616
|
this.contents.set(key, value);
|
|
3611
3617
|
}
|
|
3612
3618
|
}
|
|
3613
|
-
setIn(
|
|
3614
|
-
if (Collection.isEmptyPath(
|
|
3619
|
+
setIn(path21, value) {
|
|
3620
|
+
if (Collection.isEmptyPath(path21)) {
|
|
3615
3621
|
this.contents = value;
|
|
3616
3622
|
} else if (this.contents == null) {
|
|
3617
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3623
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path21), value);
|
|
3618
3624
|
} else if (assertCollection(this.contents)) {
|
|
3619
|
-
this.contents.setIn(
|
|
3625
|
+
this.contents.setIn(path21, value);
|
|
3620
3626
|
}
|
|
3621
3627
|
}
|
|
3622
3628
|
setSchema(version, options = {}) {
|
|
@@ -5511,9 +5517,9 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5511
5517
|
visit.BREAK = BREAK;
|
|
5512
5518
|
visit.SKIP = SKIP;
|
|
5513
5519
|
visit.REMOVE = REMOVE;
|
|
5514
|
-
visit.itemAtPath = (cst,
|
|
5520
|
+
visit.itemAtPath = (cst, path21) => {
|
|
5515
5521
|
let item = cst;
|
|
5516
|
-
for (const [field, index] of
|
|
5522
|
+
for (const [field, index] of path21) {
|
|
5517
5523
|
const tok = item?.[field];
|
|
5518
5524
|
if (tok && "items" in tok) {
|
|
5519
5525
|
item = tok.items[index];
|
|
@@ -5522,23 +5528,23 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5522
5528
|
}
|
|
5523
5529
|
return item;
|
|
5524
5530
|
};
|
|
5525
|
-
visit.parentCollection = (cst,
|
|
5526
|
-
const parent = visit.itemAtPath(cst,
|
|
5527
|
-
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];
|
|
5528
5534
|
const coll = parent?.[field];
|
|
5529
5535
|
if (coll && "items" in coll)
|
|
5530
5536
|
return coll;
|
|
5531
5537
|
throw new Error("Parent collection not found");
|
|
5532
5538
|
};
|
|
5533
|
-
function _visit(
|
|
5534
|
-
let ctrl = visitor(item,
|
|
5539
|
+
function _visit(path21, item, visitor) {
|
|
5540
|
+
let ctrl = visitor(item, path21);
|
|
5535
5541
|
if (typeof ctrl === "symbol")
|
|
5536
5542
|
return ctrl;
|
|
5537
5543
|
for (const field of ["key", "value"]) {
|
|
5538
5544
|
const token = item[field];
|
|
5539
5545
|
if (token && "items" in token) {
|
|
5540
5546
|
for (let i = 0;i < token.items.length; ++i) {
|
|
5541
|
-
const ci = _visit(Object.freeze(
|
|
5547
|
+
const ci = _visit(Object.freeze(path21.concat([[field, i]])), token.items[i], visitor);
|
|
5542
5548
|
if (typeof ci === "number")
|
|
5543
5549
|
i = ci - 1;
|
|
5544
5550
|
else if (ci === BREAK)
|
|
@@ -5549,10 +5555,10 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5549
5555
|
}
|
|
5550
5556
|
}
|
|
5551
5557
|
if (typeof ctrl === "function" && field === "key")
|
|
5552
|
-
ctrl = ctrl(item,
|
|
5558
|
+
ctrl = ctrl(item, path21);
|
|
5553
5559
|
}
|
|
5554
5560
|
}
|
|
5555
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5561
|
+
return typeof ctrl === "function" ? ctrl(item, path21) : ctrl;
|
|
5556
5562
|
}
|
|
5557
5563
|
exports.visit = visit;
|
|
5558
5564
|
});
|
|
@@ -10677,7 +10683,9 @@ var ArgsSchema4 = z4.object({
|
|
|
10677
10683
|
source: z4.enum(["reviewer", "codeforge-fallback"]).optional().describe("写入来源;默认 'reviewer',codeforge 补写时传 'codeforge-fallback'"),
|
|
10678
10684
|
reviewerAgent: z4.string().optional().describe("写入 agent name(默认 'reviewer';fallback 时为 'codeforge')"),
|
|
10679
10685
|
sessionId: z4.string().optional().describe("reviewer 子 session id(boomerang 溯源用,可选)"),
|
|
10680
|
-
model: z4.string().optional().describe("审批模型 id(审计用,可选)")
|
|
10686
|
+
model: z4.string().optional().describe("审批模型 id(审计用,可选)"),
|
|
10687
|
+
coveredSha: z4.string().optional().describe("approval 写入时 worktree HEAD sha;reviewer 调此工具时传入(git -C <worktreePath> rev-parse HEAD)。pre-check 强绑定核心字段,缺失则 pre-check 不命中。ADR:merge-approval-pre-check"),
|
|
10688
|
+
reviewTarget: z4.string().optional().describe("本次审阅的 review_target 值(reviewer.md 词表:code / code:typescript / code:python / code:csharp-lua-c / plan_only / adr / docs / decision_only)。pre-check 仅放行 startsWith('code') 的值;缺失或其他值均不命中。ADR:merge-approval-pre-check")
|
|
10681
10689
|
});
|
|
10682
10690
|
var _approvalStore = null;
|
|
10683
10691
|
function getApprovalStore() {
|
|
@@ -10711,6 +10719,8 @@ async function execute4(input) {
|
|
|
10711
10719
|
decisionLine: args.decisionLine ?? args.verdict,
|
|
10712
10720
|
notes: args.notes,
|
|
10713
10721
|
createdAt: now,
|
|
10722
|
+
...args.coveredSha ? { coveredSha: args.coveredSha } : {},
|
|
10723
|
+
...args.reviewTarget ? { reviewTarget: args.reviewTarget } : {},
|
|
10714
10724
|
escapeHatch: null
|
|
10715
10725
|
};
|
|
10716
10726
|
const file = await approvals.record(meta);
|
|
@@ -12369,13 +12379,23 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
12369
12379
|
const r = await pruneDiscardedRegistryEntries(resolved);
|
|
12370
12380
|
discardedPruned = r.pruned;
|
|
12371
12381
|
} catch {}
|
|
12372
|
-
|
|
12382
|
+
let gitAdminPruned = 0;
|
|
12383
|
+
try {
|
|
12384
|
+
const out = await runGit2(resolved, ["worktree", "prune", "--verbose"]);
|
|
12385
|
+
gitAdminPruned = out.split(`
|
|
12386
|
+
`).filter((l) => /\bRemoving\b/i.test(l)).length;
|
|
12387
|
+
} catch {}
|
|
12388
|
+
return { cleaned, failed, skipped, discardedPruned, gitAdminPruned };
|
|
12373
12389
|
}
|
|
12374
12390
|
|
|
12375
12391
|
// lib/merge-gate.ts
|
|
12376
12392
|
import { promises as fs11 } from "node:fs";
|
|
12377
12393
|
import * as path14 from "node:path";
|
|
12378
|
-
var DEFAULT_MERGE_GATE_CONFIG = {
|
|
12394
|
+
var DEFAULT_MERGE_GATE_CONFIG = {
|
|
12395
|
+
enabled: true,
|
|
12396
|
+
approvalPreCheck: true,
|
|
12397
|
+
preCheckTtlSeconds: 3600
|
|
12398
|
+
};
|
|
12379
12399
|
var CONFIG_REL = ".codeforge/merge-gate.json";
|
|
12380
12400
|
async function loadMergeGate(mainRoot) {
|
|
12381
12401
|
const file = path14.join(mainRoot, CONFIG_REL);
|
|
@@ -12387,29 +12407,40 @@ async function loadMergeGate(mainRoot) {
|
|
|
12387
12407
|
if (e.code === "ENOENT")
|
|
12388
12408
|
return { ...DEFAULT_MERGE_GATE_CONFIG };
|
|
12389
12409
|
console.warn(`[merge-gate] 读取 ${CONFIG_REL} 失败,fail-safe 退化为 enabled=false: ${e.message}`);
|
|
12390
|
-
return { enabled: false };
|
|
12410
|
+
return { enabled: false, approvalPreCheck: false };
|
|
12391
12411
|
}
|
|
12392
12412
|
let parsed;
|
|
12393
12413
|
try {
|
|
12394
12414
|
parsed = JSON.parse(raw);
|
|
12395
12415
|
} catch (err) {
|
|
12396
12416
|
console.warn(`[merge-gate] ${CONFIG_REL} JSON 解析失败,fail-safe 退化为 enabled=false: ${err instanceof Error ? err.message : String(err)}`);
|
|
12397
|
-
return { enabled: false };
|
|
12417
|
+
return { enabled: false, approvalPreCheck: false };
|
|
12398
12418
|
}
|
|
12399
12419
|
if (!parsed || typeof parsed !== "object") {
|
|
12400
12420
|
console.warn(`[merge-gate] ${CONFIG_REL} 顶层非 object,fail-safe 退化为 enabled=false`);
|
|
12401
|
-
return { enabled: false };
|
|
12421
|
+
return { enabled: false, approvalPreCheck: false };
|
|
12402
12422
|
}
|
|
12403
12423
|
const obj = parsed;
|
|
12404
12424
|
const enabled = typeof obj["enabled"] === "boolean" ? obj["enabled"] : DEFAULT_MERGE_GATE_CONFIG.enabled;
|
|
12405
|
-
|
|
12425
|
+
const approvalPreCheck = typeof obj["approvalPreCheck"] === "boolean" ? obj["approvalPreCheck"] : DEFAULT_MERGE_GATE_CONFIG.approvalPreCheck ?? false;
|
|
12426
|
+
let preCheckTtlSeconds = DEFAULT_MERGE_GATE_CONFIG.preCheckTtlSeconds ?? 3600;
|
|
12427
|
+
const rawTtl = obj["preCheckTtlSeconds"];
|
|
12428
|
+
if (typeof rawTtl === "number" && rawTtl > 0) {
|
|
12429
|
+
if (rawTtl > 86400) {
|
|
12430
|
+
console.warn(`[merge-gate] preCheckTtlSeconds=${rawTtl} 超过 24h 上界,截断为 86400`);
|
|
12431
|
+
preCheckTtlSeconds = 86400;
|
|
12432
|
+
} else {
|
|
12433
|
+
preCheckTtlSeconds = rawTtl;
|
|
12434
|
+
}
|
|
12435
|
+
}
|
|
12436
|
+
return { enabled, approvalPreCheck, preCheckTtlSeconds };
|
|
12406
12437
|
}
|
|
12407
12438
|
|
|
12408
12439
|
// lib/merge-loop.ts
|
|
12409
12440
|
var DEFAULT_MERGE_LOOP_CONFIG = {
|
|
12410
12441
|
maxReviewLoops: 3,
|
|
12411
12442
|
autoCoder: true,
|
|
12412
|
-
reviewTimeoutMs:
|
|
12443
|
+
reviewTimeoutMs: 360000,
|
|
12413
12444
|
coderTimeoutMs: 600000,
|
|
12414
12445
|
abortDirtyStrategy: "checkpoint"
|
|
12415
12446
|
};
|
|
@@ -12447,6 +12478,43 @@ async function runMergeLoop(opts) {
|
|
|
12447
12478
|
});
|
|
12448
12479
|
return { status: "force_merged", commitSha: sha, loops: 0 };
|
|
12449
12480
|
}
|
|
12481
|
+
if (mergeGate.enabled && mergeGate.approvalPreCheck) {
|
|
12482
|
+
progress("approval_pre_check", "检查既有 approval 是否可跳过 reviewer");
|
|
12483
|
+
const preStore = opts.__testHooks?.approvalStore ?? ApprovalStore.forProject(opts.mainRoot);
|
|
12484
|
+
const hit = await tryApprovalPreCheck({ opts, entry, mergeGate, store: preStore });
|
|
12485
|
+
if (hit.ok) {
|
|
12486
|
+
await maybeAbort(opts, config, entry);
|
|
12487
|
+
if (typeof preStore.recordEscape === "function") {
|
|
12488
|
+
await preStore.recordEscape({
|
|
12489
|
+
pendingId: `session:${opts.sessionId}`,
|
|
12490
|
+
timestamp: new Date().toISOString(),
|
|
12491
|
+
agent: "codeforge-pre-check",
|
|
12492
|
+
sessionId: opts.sessionId,
|
|
12493
|
+
reason: `approval-pre-check: skipped reviewer (coveredSha=${hit.coveredSha})`,
|
|
12494
|
+
pendingMeta: {
|
|
12495
|
+
target: "worktree",
|
|
12496
|
+
sourceHash: hit.coveredSha,
|
|
12497
|
+
newSize: 0
|
|
12498
|
+
}
|
|
12499
|
+
}).catch((err) => {
|
|
12500
|
+
console.warn(`[merge-loop] pre-check recordEscape 失败 (session=${opts.sessionId}): ${err instanceof Error ? err.message : String(err)}`);
|
|
12501
|
+
});
|
|
12502
|
+
}
|
|
12503
|
+
progress("approval_pre_check", `skip_review | reviewTarget=${hit.reviewTarget} | coveredSha=${hit.coveredSha.slice(0, 12)} | ttlOk`);
|
|
12504
|
+
const { sha } = await mergeSessionBack({
|
|
12505
|
+
sessionId: opts.sessionId,
|
|
12506
|
+
mainRoot: opts.mainRoot
|
|
12507
|
+
});
|
|
12508
|
+
return {
|
|
12509
|
+
status: "skipped_by_approval",
|
|
12510
|
+
commitSha: sha,
|
|
12511
|
+
loops: 0,
|
|
12512
|
+
finalDecision: "APPROVE",
|
|
12513
|
+
lastReviewSummary: `approval-pre-check 命中:coveredSha=${hit.coveredSha}, reviewTarget=${hit.reviewTarget}`
|
|
12514
|
+
};
|
|
12515
|
+
}
|
|
12516
|
+
progress("approval_pre_check", `未命中(${hit.reason}),走 review-loop`);
|
|
12517
|
+
}
|
|
12450
12518
|
while (true) {
|
|
12451
12519
|
await maybeAbort(opts, config, entry);
|
|
12452
12520
|
loops += 1;
|
|
@@ -12667,6 +12735,44 @@ async function handleAbortDirty(opts, config, entry) {
|
|
|
12667
12735
|
console.warn(`[merge-loop] abort-dirty 处理失败 (session=${opts.sessionId}): ${err instanceof Error ? err.message : String(err)}`);
|
|
12668
12736
|
}
|
|
12669
12737
|
}
|
|
12738
|
+
async function tryApprovalPreCheck(args) {
|
|
12739
|
+
const { opts, entry, mergeGate, store } = args;
|
|
12740
|
+
const isDirty = opts.__testHooks?.isWorktreeDirty ?? isWorktreeDirty;
|
|
12741
|
+
const headOf = opts.__testHooks?.getCurrentWorktreeHead ?? getCurrentWorktreeHead;
|
|
12742
|
+
let dirty;
|
|
12743
|
+
try {
|
|
12744
|
+
dirty = await isDirty(entry.worktreePath);
|
|
12745
|
+
} catch {
|
|
12746
|
+
return { ok: false, reason: "dirty-check-failed" };
|
|
12747
|
+
}
|
|
12748
|
+
if (dirty)
|
|
12749
|
+
return { ok: false, reason: "dirty" };
|
|
12750
|
+
let approval;
|
|
12751
|
+
try {
|
|
12752
|
+
approval = await store.getLatest(`session:${opts.sessionId}`);
|
|
12753
|
+
} catch (err) {
|
|
12754
|
+
console.warn(`[merge-loop] pre-check getLatest 失败 (session=${opts.sessionId}): ${err instanceof Error ? err.message : String(err)}`);
|
|
12755
|
+
return { ok: false, reason: "no-approval" };
|
|
12756
|
+
}
|
|
12757
|
+
if (!approval)
|
|
12758
|
+
return { ok: false, reason: "no-approval" };
|
|
12759
|
+
if (approval.verdict !== "APPROVE" && approval.verdict !== "APPROVE_WITH_NOTES") {
|
|
12760
|
+
return { ok: false, reason: "verdict" };
|
|
12761
|
+
}
|
|
12762
|
+
if (approval.reviewTarget?.startsWith("code") !== true) {
|
|
12763
|
+
return { ok: false, reason: "target" };
|
|
12764
|
+
}
|
|
12765
|
+
if (!approval.coveredSha)
|
|
12766
|
+
return { ok: false, reason: "no-covered-sha" };
|
|
12767
|
+
const head = await headOf(entry.worktreePath);
|
|
12768
|
+
if (approval.coveredSha !== head)
|
|
12769
|
+
return { ok: false, reason: "sha-mismatch" };
|
|
12770
|
+
const ttlSeconds = Math.min(mergeGate.preCheckTtlSeconds ?? 3600, 86400);
|
|
12771
|
+
const age = Date.now() - Date.parse(approval.createdAt);
|
|
12772
|
+
if (!(age <= ttlSeconds * 1000))
|
|
12773
|
+
return { ok: false, reason: "ttl-expired" };
|
|
12774
|
+
return { ok: true, coveredSha: approval.coveredSha, reviewTarget: approval.reviewTarget };
|
|
12775
|
+
}
|
|
12670
12776
|
function isAbortError(err) {
|
|
12671
12777
|
return err instanceof Error && err.name === "AbortError";
|
|
12672
12778
|
}
|
|
@@ -13646,6 +13752,159 @@ async function execute15(input) {
|
|
|
13646
13752
|
};
|
|
13647
13753
|
}
|
|
13648
13754
|
}
|
|
13755
|
+
// tools/worktrees-gc.ts
|
|
13756
|
+
import { z as z16 } from "zod";
|
|
13757
|
+
|
|
13758
|
+
// lib/opencode-session-probe.ts
|
|
13759
|
+
import * as path18 from "node:path";
|
|
13760
|
+
import * as os5 from "node:os";
|
|
13761
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
13762
|
+
var requireFromHere = createRequire2(import.meta.url);
|
|
13763
|
+
var DEFAULT_LIVENESS_MS = 6 * 60 * 60000;
|
|
13764
|
+
var DEFAULT_DB_PATH = path18.join(os5.homedir(), ".local/share/opencode/opencode.db");
|
|
13765
|
+
function createSessionProbe(opts = {}) {
|
|
13766
|
+
const dbPath = opts.dbPath ?? DEFAULT_DB_PATH;
|
|
13767
|
+
const httpBaseUrl = opts.httpBaseUrl ?? process.env["OPENCODE_SERVER_URL"];
|
|
13768
|
+
const livenessWindowMs = opts.livenessWindowMs ?? DEFAULT_LIVENESS_MS;
|
|
13769
|
+
const timeoutMs = opts.timeoutMs ?? 1500;
|
|
13770
|
+
let db = null;
|
|
13771
|
+
let dbInitTried = false;
|
|
13772
|
+
let dbStmt = null;
|
|
13773
|
+
async function tryOpenDb() {
|
|
13774
|
+
if (dbInitTried)
|
|
13775
|
+
return db != null;
|
|
13776
|
+
dbInitTried = true;
|
|
13777
|
+
try {
|
|
13778
|
+
const mod = requireFromHere("node:sqlite");
|
|
13779
|
+
try {
|
|
13780
|
+
db = new mod.DatabaseSync(dbPath, { readOnly: true });
|
|
13781
|
+
} catch {
|
|
13782
|
+
db = null;
|
|
13783
|
+
dbStmt = null;
|
|
13784
|
+
return false;
|
|
13785
|
+
}
|
|
13786
|
+
dbStmt = db.prepare("SELECT time_updated, time_archived FROM session WHERE id = ? LIMIT 1");
|
|
13787
|
+
return true;
|
|
13788
|
+
} catch {
|
|
13789
|
+
db = null;
|
|
13790
|
+
dbStmt = null;
|
|
13791
|
+
return false;
|
|
13792
|
+
}
|
|
13793
|
+
}
|
|
13794
|
+
async function probeHttp(sessionId) {
|
|
13795
|
+
if (!httpBaseUrl)
|
|
13796
|
+
return null;
|
|
13797
|
+
try {
|
|
13798
|
+
const ctrl = new AbortController;
|
|
13799
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
13800
|
+
const res = await fetch(`${httpBaseUrl.replace(/\/$/, "")}/session`, {
|
|
13801
|
+
signal: ctrl.signal
|
|
13802
|
+
}).finally(() => clearTimeout(t));
|
|
13803
|
+
if (!res.ok)
|
|
13804
|
+
return null;
|
|
13805
|
+
const list = await res.json();
|
|
13806
|
+
const hit = Array.isArray(list) && list.some((s) => s.id === sessionId);
|
|
13807
|
+
return { alive: hit, source: "http" };
|
|
13808
|
+
} catch {
|
|
13809
|
+
return null;
|
|
13810
|
+
}
|
|
13811
|
+
}
|
|
13812
|
+
async function probeSqlite(sessionId) {
|
|
13813
|
+
if (!await tryOpenDb() || !dbStmt)
|
|
13814
|
+
return null;
|
|
13815
|
+
try {
|
|
13816
|
+
const row = dbStmt.get(sessionId);
|
|
13817
|
+
if (!row) {
|
|
13818
|
+
return { alive: false, source: "sqlite" };
|
|
13819
|
+
}
|
|
13820
|
+
if (row.time_archived != null) {
|
|
13821
|
+
return {
|
|
13822
|
+
alive: false,
|
|
13823
|
+
source: "sqlite",
|
|
13824
|
+
time_archived: row.time_archived
|
|
13825
|
+
};
|
|
13826
|
+
}
|
|
13827
|
+
const now = Date.now();
|
|
13828
|
+
const tu = Number(row.time_updated) || 0;
|
|
13829
|
+
const alive = now - tu < livenessWindowMs;
|
|
13830
|
+
return { alive, source: "sqlite", time_updated: tu, time_archived: null };
|
|
13831
|
+
} catch {
|
|
13832
|
+
return null;
|
|
13833
|
+
}
|
|
13834
|
+
}
|
|
13835
|
+
return {
|
|
13836
|
+
async isSessionAlive(sessionId) {
|
|
13837
|
+
const http = await probeHttp(sessionId);
|
|
13838
|
+
if (http)
|
|
13839
|
+
return http;
|
|
13840
|
+
const sql = await probeSqlite(sessionId);
|
|
13841
|
+
if (sql)
|
|
13842
|
+
return sql;
|
|
13843
|
+
return { alive: true, source: "unknown" };
|
|
13844
|
+
},
|
|
13845
|
+
close() {
|
|
13846
|
+
try {
|
|
13847
|
+
db?.close?.();
|
|
13848
|
+
} catch {}
|
|
13849
|
+
db = null;
|
|
13850
|
+
dbStmt = null;
|
|
13851
|
+
}
|
|
13852
|
+
};
|
|
13853
|
+
}
|
|
13854
|
+
|
|
13855
|
+
// tools/worktrees-gc.ts
|
|
13856
|
+
var description16 = [
|
|
13857
|
+
"手动触发 worktree 垃圾回收(清理僵尸 / 孤儿 worktree + dangling git 元数据)。",
|
|
13858
|
+
"**何时调用**:",
|
|
13859
|
+
"- 用户发现 worktree 堆积(git worktree list 一大堆 codeforge-session-*)",
|
|
13860
|
+
"- 自动 GC 疑似停跑(lifecycle 因项目无 .codeforge/ skip 过一段时间)",
|
|
13861
|
+
"**模式**:",
|
|
13862
|
+
"- 默认(保守):沿用自动 GC 的时间护栏(6h/72h),只清确凿僵尸",
|
|
13863
|
+
"- force=true(高风险):时间护栏归零,连 probe 判 unknown 的也立即清,可能误删活跃 session 的 worktree",
|
|
13864
|
+
"**何时不需要**:自动 30min interval 正常跑时无需手动调。"
|
|
13865
|
+
].join(`
|
|
13866
|
+
`);
|
|
13867
|
+
var ArgsSchema16 = z16.object({
|
|
13868
|
+
force: z16.boolean().optional().describe("高风险:时间护栏归零,连 probe unknown 的 worktree 也立即清,可能误删活跃 session worktree。默认 false(保守)")
|
|
13869
|
+
});
|
|
13870
|
+
var _ctx3 = {};
|
|
13871
|
+
function __setContext3(ctx) {
|
|
13872
|
+
_ctx3 = ctx;
|
|
13873
|
+
}
|
|
13874
|
+
function getMainRoot2() {
|
|
13875
|
+
return _ctx3.mainRoot ?? process.cwd();
|
|
13876
|
+
}
|
|
13877
|
+
async function execute16(input) {
|
|
13878
|
+
const parsed = ArgsSchema16.safeParse(input);
|
|
13879
|
+
if (!parsed.success) {
|
|
13880
|
+
return {
|
|
13881
|
+
ok: false,
|
|
13882
|
+
error: parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")
|
|
13883
|
+
};
|
|
13884
|
+
}
|
|
13885
|
+
const force = parsed.data.force === true;
|
|
13886
|
+
const probe = createSessionProbe();
|
|
13887
|
+
try {
|
|
13888
|
+
const result = await pruneOrphanWorktrees(getMainRoot2(), {
|
|
13889
|
+
isSessionAlive: probe.isSessionAlive,
|
|
13890
|
+
...force ? { semanticOrphanMinAgeMs: 0, semanticOrphanUnknownTimeoutMs: 0 } : {}
|
|
13891
|
+
});
|
|
13892
|
+
return {
|
|
13893
|
+
ok: true,
|
|
13894
|
+
mode: force ? "force" : "conservative",
|
|
13895
|
+
cleaned: result.cleaned,
|
|
13896
|
+
failed: result.failed,
|
|
13897
|
+
skipped: result.skipped,
|
|
13898
|
+
discardedPruned: result.discardedPruned ?? 0,
|
|
13899
|
+
gitAdminPruned: result.gitAdminPruned ?? 0,
|
|
13900
|
+
cleanedCount: result.cleaned.length
|
|
13901
|
+
};
|
|
13902
|
+
} catch (err) {
|
|
13903
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
13904
|
+
} finally {
|
|
13905
|
+
probe.close();
|
|
13906
|
+
}
|
|
13907
|
+
}
|
|
13649
13908
|
// lib/opencode-runner.ts
|
|
13650
13909
|
function makeOpencodeRunner(opts) {
|
|
13651
13910
|
const log4 = opts.log ?? (() => {});
|
|
@@ -13907,18 +14166,18 @@ init_decision_parser();
|
|
|
13907
14166
|
|
|
13908
14167
|
// lib/parent-map-store.ts
|
|
13909
14168
|
import { promises as fs14 } from "node:fs";
|
|
13910
|
-
import * as
|
|
14169
|
+
import * as path19 from "node:path";
|
|
13911
14170
|
var PARENT_MAP_VERSION = 1;
|
|
13912
14171
|
var PARENT_MAP_LOCK_TIMEOUT_MS = 2000;
|
|
13913
14172
|
var PARENT_MAP_MAX_ENTRIES = 256;
|
|
13914
14173
|
function parentMapDir(mainRoot) {
|
|
13915
|
-
return
|
|
14174
|
+
return path19.join(runtimeDir(path19.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
13916
14175
|
}
|
|
13917
14176
|
function parentMapPath(mainRoot) {
|
|
13918
|
-
return
|
|
14177
|
+
return path19.join(parentMapDir(mainRoot), "parent-map.json");
|
|
13919
14178
|
}
|
|
13920
14179
|
function parentMapLockPath(mainRoot) {
|
|
13921
|
-
return
|
|
14180
|
+
return path19.join(parentMapDir(mainRoot), "parent-map.lock");
|
|
13922
14181
|
}
|
|
13923
14182
|
async function readParentMapFile(mainRoot) {
|
|
13924
14183
|
const file = parentMapPath(mainRoot);
|
|
@@ -13938,7 +14197,7 @@ async function readParentMapFile(mainRoot) {
|
|
|
13938
14197
|
}
|
|
13939
14198
|
async function writeParentMapFile(mainRoot, payload) {
|
|
13940
14199
|
const file = parentMapPath(mainRoot);
|
|
13941
|
-
await fs14.mkdir(
|
|
14200
|
+
await fs14.mkdir(path19.dirname(file), { recursive: true });
|
|
13942
14201
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
13943
14202
|
await fs14.writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
13944
14203
|
await fs14.rename(tmp, file);
|
|
@@ -13973,7 +14232,7 @@ async function loadParentMap(mainRoot) {
|
|
|
13973
14232
|
}
|
|
13974
14233
|
async function mutateParentMap(mainRoot, mutator, opts = {}) {
|
|
13975
14234
|
const lockPath = parentMapLockPath(mainRoot);
|
|
13976
|
-
await fs14.mkdir(
|
|
14235
|
+
await fs14.mkdir(path19.dirname(lockPath), { recursive: true });
|
|
13977
14236
|
const lockOpts = {
|
|
13978
14237
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
13979
14238
|
...opts
|
|
@@ -13992,7 +14251,7 @@ async function appendParentEntry(mainRoot, childID, parentID, ts, opts = {}) {
|
|
|
13992
14251
|
if (!childID || !parentID)
|
|
13993
14252
|
return;
|
|
13994
14253
|
const lockPath = parentMapLockPath(mainRoot);
|
|
13995
|
-
await fs14.mkdir(
|
|
14254
|
+
await fs14.mkdir(path19.dirname(lockPath), { recursive: true });
|
|
13996
14255
|
const lockOpts = {
|
|
13997
14256
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
13998
14257
|
...opts
|
|
@@ -14124,7 +14383,7 @@ class ProductionSpawner {
|
|
|
14124
14383
|
prompt,
|
|
14125
14384
|
title: `[merge-review] sess=${args.sessionId.slice(0, 8)} r=${args.round}/${args.maxRounds}`,
|
|
14126
14385
|
...args.signal ? { signal: args.signal } : {},
|
|
14127
|
-
timeoutMs: this.opts.reviewerTimeoutMs ??
|
|
14386
|
+
timeoutMs: this.opts.reviewerTimeoutMs ?? 360000
|
|
14128
14387
|
}, args.sessionId);
|
|
14129
14388
|
} catch (err) {
|
|
14130
14389
|
throw err;
|
|
@@ -14326,7 +14585,7 @@ function clip3(s, max) {
|
|
|
14326
14585
|
|
|
14327
14586
|
// lib/codeforge-runtime.ts
|
|
14328
14587
|
import { promises as fs15 } from "node:fs";
|
|
14329
|
-
import * as
|
|
14588
|
+
import * as path20 from "node:path";
|
|
14330
14589
|
var DEFAULT_RUNTIME = {
|
|
14331
14590
|
autonomy: {
|
|
14332
14591
|
downgrade_on_risky: true
|
|
@@ -14369,7 +14628,7 @@ function loadRuntimeSync(opts = {}) {
|
|
|
14369
14628
|
}
|
|
14370
14629
|
async function loadRuntime(opts = {}) {
|
|
14371
14630
|
const root = opts.root ?? process.cwd();
|
|
14372
|
-
const abs =
|
|
14631
|
+
const abs = path20.resolve(root, opts.file ?? CONFIG_FILE);
|
|
14373
14632
|
let raw;
|
|
14374
14633
|
try {
|
|
14375
14634
|
raw = await fs15.readFile(abs, "utf8");
|
|
@@ -14653,7 +14912,7 @@ var toolHeartbeatServer = async (ctx) => {
|
|
|
14653
14912
|
var handler6 = toolHeartbeatServer;
|
|
14654
14913
|
|
|
14655
14914
|
// plugins/codeforge-tools.ts
|
|
14656
|
-
var
|
|
14915
|
+
var z17 = tool.schema;
|
|
14657
14916
|
var PLUGIN_NAME7 = "codeforge-tools";
|
|
14658
14917
|
logLifecycle(PLUGIN_NAME7, "import");
|
|
14659
14918
|
function wrap(output, metadata) {
|
|
@@ -14700,7 +14959,7 @@ function buildBrowserTools() {
|
|
|
14700
14959
|
browser_navigate: tool({
|
|
14701
14960
|
description: description5,
|
|
14702
14961
|
args: {
|
|
14703
|
-
url:
|
|
14962
|
+
url: z17.string().min(1).describe("要打开的 URL;必须是 http(s)")
|
|
14704
14963
|
},
|
|
14705
14964
|
async execute(args) {
|
|
14706
14965
|
return await runSafe("browser_navigate", async () => {
|
|
@@ -14715,7 +14974,7 @@ function buildBrowserTools() {
|
|
|
14715
14974
|
browser_click: tool({
|
|
14716
14975
|
description: description6,
|
|
14717
14976
|
args: {
|
|
14718
|
-
selector:
|
|
14977
|
+
selector: z17.string().min(1).describe("CSS / Playwright locator,必须能唯一定位")
|
|
14719
14978
|
},
|
|
14720
14979
|
async execute(args) {
|
|
14721
14980
|
return await runSafe("browser_click", async () => {
|
|
@@ -14730,8 +14989,8 @@ function buildBrowserTools() {
|
|
|
14730
14989
|
browser_fill: tool({
|
|
14731
14990
|
description: description7,
|
|
14732
14991
|
args: {
|
|
14733
|
-
selector:
|
|
14734
|
-
value:
|
|
14992
|
+
selector: z17.string().min(1).describe("CSS / Playwright locator"),
|
|
14993
|
+
value: z17.string().describe("要填入的文本;原样写入不转义")
|
|
14735
14994
|
},
|
|
14736
14995
|
async execute(args) {
|
|
14737
14996
|
return await runSafe("browser_fill", async () => {
|
|
@@ -14746,8 +15005,8 @@ function buildBrowserTools() {
|
|
|
14746
15005
|
browser_screenshot: tool({
|
|
14747
15006
|
description: description8,
|
|
14748
15007
|
args: {
|
|
14749
|
-
fullPage:
|
|
14750
|
-
selector:
|
|
15008
|
+
fullPage: z17.boolean().optional().describe("是否截全长页面(默认仅可视区)"),
|
|
15009
|
+
selector: z17.string().optional().describe("CSS 选择器;指定时只截该元素")
|
|
14751
15010
|
},
|
|
14752
15011
|
async execute(args) {
|
|
14753
15012
|
return await runSafe("browser_screenshot", async () => {
|
|
@@ -14762,8 +15021,8 @@ function buildBrowserTools() {
|
|
|
14762
15021
|
browser_console: tool({
|
|
14763
15022
|
description: description9,
|
|
14764
15023
|
args: {
|
|
14765
|
-
level:
|
|
14766
|
-
sinceTs:
|
|
15024
|
+
level: z17.enum(["log", "info", "warn", "error", "debug"]).optional().describe("过滤级别"),
|
|
15025
|
+
sinceTs: z17.number().optional().describe("timestamp >= sinceTs 的条目")
|
|
14767
15026
|
},
|
|
14768
15027
|
async execute(args) {
|
|
14769
15028
|
return await runSafe("browser_console", async () => {
|
|
@@ -14778,8 +15037,8 @@ function buildBrowserTools() {
|
|
|
14778
15037
|
browser_network: tool({
|
|
14779
15038
|
description: description10,
|
|
14780
15039
|
args: {
|
|
14781
|
-
method:
|
|
14782
|
-
sinceTs:
|
|
15040
|
+
method: z17.string().optional().describe("HTTP 方法过滤(GET/POST/...)"),
|
|
15041
|
+
sinceTs: z17.number().optional().describe("start_ts >= sinceTs 的请求")
|
|
14783
15042
|
},
|
|
14784
15043
|
async execute(args) {
|
|
14785
15044
|
return await runSafe("browser_network", async () => {
|
|
@@ -14802,7 +15061,8 @@ var CORE_TOOL_NAMES = [
|
|
|
14802
15061
|
"session_merge",
|
|
14803
15062
|
"plan_write",
|
|
14804
15063
|
"plan_read",
|
|
14805
|
-
"adr_init"
|
|
15064
|
+
"adr_init",
|
|
15065
|
+
"worktrees_gc"
|
|
14806
15066
|
];
|
|
14807
15067
|
var BROWSER_TOOL_NAMES = [
|
|
14808
15068
|
"browser_navigate",
|
|
@@ -14839,31 +15099,32 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14839
15099
|
spawner,
|
|
14840
15100
|
resolveCurrentSessionId: () => process.env["CODEFORGE_SESSION_ID"] ?? ""
|
|
14841
15101
|
});
|
|
15102
|
+
__setContext3({ mainRoot: ctx.directory ?? process.cwd() });
|
|
14842
15103
|
const browserTools = browserEnabled ? buildBrowserTools() : {};
|
|
14843
15104
|
return {
|
|
14844
15105
|
tool: {
|
|
14845
15106
|
ast_edit: tool({
|
|
14846
15107
|
description,
|
|
14847
15108
|
args: {
|
|
14848
|
-
action:
|
|
15109
|
+
action: z17.enum([
|
|
14849
15110
|
"replace_anchor",
|
|
14850
15111
|
"insert_after_anchor",
|
|
14851
15112
|
"insert_before_anchor",
|
|
14852
15113
|
"delete_range",
|
|
14853
15114
|
"rename_symbol"
|
|
14854
15115
|
]).describe("编辑类型;不同 action 需要的字段不同"),
|
|
14855
|
-
target:
|
|
14856
|
-
before_hash:
|
|
14857
|
-
auto_stage:
|
|
14858
|
-
description:
|
|
14859
|
-
anchor:
|
|
14860
|
-
regex:
|
|
14861
|
-
occurrence:
|
|
14862
|
-
payload:
|
|
14863
|
-
start:
|
|
14864
|
-
end:
|
|
14865
|
-
old_name:
|
|
14866
|
-
new_name:
|
|
15116
|
+
target: z17.string().min(1).describe("目标文件路径(相对 cwd 或绝对)"),
|
|
15117
|
+
before_hash: z17.string().optional().describe("操作前的 sha256 hex(强烈建议传,新文件传 null/省略)"),
|
|
15118
|
+
auto_stage: z17.boolean().optional().describe("已废弃(保留兼容字段):Phase 5 后 ast_edit 直接写入 session worktree,无需独立 stage"),
|
|
15119
|
+
description: z17.string().optional().describe("可选变更说明(写入 audit log)"),
|
|
15120
|
+
anchor: z17.string().optional().describe("anchor 类用:anchor 文本或 regex 源"),
|
|
15121
|
+
regex: z17.boolean().optional().describe("anchor 是否按 RegExp 解释,默认 false"),
|
|
15122
|
+
occurrence: z17.number().int().min(1).optional().describe("第几次匹配(1-based),默认 1"),
|
|
15123
|
+
payload: z17.string().optional().describe("anchor 类用:要写入的内容"),
|
|
15124
|
+
start: z17.number().int().min(1).optional().describe("delete_range 用:起始行(1-based)"),
|
|
15125
|
+
end: z17.number().int().min(1).optional().describe("delete_range 用:结束行(1-based)"),
|
|
15126
|
+
old_name: z17.string().optional().describe("rename_symbol 用:旧标识符"),
|
|
15127
|
+
new_name: z17.string().optional().describe("rename_symbol 用:新标识符")
|
|
14867
15128
|
},
|
|
14868
15129
|
async execute(args) {
|
|
14869
15130
|
return await runSafeTracked("ast_edit", async () => {
|
|
@@ -14886,10 +15147,10 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14886
15147
|
repo_map: tool({
|
|
14887
15148
|
description: description2,
|
|
14888
15149
|
args: {
|
|
14889
|
-
root:
|
|
14890
|
-
top:
|
|
14891
|
-
focus:
|
|
14892
|
-
max_files:
|
|
15150
|
+
root: z17.string().optional().describe("扫描根目录,默认 cwd"),
|
|
15151
|
+
top: z17.number().int().min(1).max(100).optional().describe("展示 top N 文件,默认 20"),
|
|
15152
|
+
focus: z17.string().optional().describe("聚焦文件(POSIX 相对路径)"),
|
|
15153
|
+
max_files: z17.number().int().min(10).max(5000).optional().describe("扫描上限,默认 500")
|
|
14893
15154
|
},
|
|
14894
15155
|
async execute(args) {
|
|
14895
15156
|
return await runSafeTracked("repo_map", async () => {
|
|
@@ -14910,14 +15171,14 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14910
15171
|
rules_debug: tool({
|
|
14911
15172
|
description: description3,
|
|
14912
15173
|
args: {
|
|
14913
|
-
current_agent:
|
|
14914
|
-
root:
|
|
14915
|
-
home_dir:
|
|
14916
|
-
project_dir:
|
|
14917
|
-
skip_personal:
|
|
14918
|
-
skip_project:
|
|
14919
|
-
skip_agent:
|
|
14920
|
-
markdown:
|
|
15174
|
+
current_agent: z17.string().optional().describe("当前 agent 名"),
|
|
15175
|
+
root: z17.string().optional().describe("项目根目录"),
|
|
15176
|
+
home_dir: z17.string().optional().describe("覆盖个人规则目录"),
|
|
15177
|
+
project_dir: z17.string().optional().describe("覆盖项目规则目录"),
|
|
15178
|
+
skip_personal: z17.boolean().optional(),
|
|
15179
|
+
skip_project: z17.boolean().optional(),
|
|
15180
|
+
skip_agent: z17.boolean().optional(),
|
|
15181
|
+
markdown: z17.boolean().optional().describe("默认 true:渲染 markdown 摘要")
|
|
14921
15182
|
},
|
|
14922
15183
|
async execute(args) {
|
|
14923
15184
|
return await runSafe("rules_debug", async () => {
|
|
@@ -14932,14 +15193,14 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14932
15193
|
review_approval: tool({
|
|
14933
15194
|
description: description4,
|
|
14934
15195
|
args: {
|
|
14935
|
-
verdict:
|
|
14936
|
-
pendingIds:
|
|
14937
|
-
notes:
|
|
14938
|
-
decisionLine:
|
|
14939
|
-
source:
|
|
14940
|
-
reviewerAgent:
|
|
14941
|
-
sessionId:
|
|
14942
|
-
model:
|
|
15196
|
+
verdict: z17.enum(["APPROVE", "APPROVE_WITH_NOTES"]).describe("审批层裁决(审计字段,与协议层独立);REQUEST_CHANGES / BLOCK 不应调本工具。⚠️ 不要把本字段值复制到 reviewer 输出的 `## Decision` 节首行 — 那是协议层 3 档。"),
|
|
15197
|
+
pendingIds: z17.array(z17.string().min(1)).min(1).describe("本次 APPROVE 覆盖的 pending change id 列表"),
|
|
15198
|
+
notes: z17.string().min(1).max(2000).describe("审阅意见摘要(建议 ≤ 500 字)"),
|
|
15199
|
+
decisionLine: z17.string().optional().describe("`## Decision` 节首行原文(默认 verdict 字面量,机审证据)"),
|
|
15200
|
+
source: z17.enum(["reviewer", "codeforge-fallback"]).optional().describe("写入来源;默认 'reviewer',codeforge 补写时传 'codeforge-fallback'"),
|
|
15201
|
+
reviewerAgent: z17.string().optional().describe("写入 agent name(默认 'reviewer';fallback 时为 'codeforge')"),
|
|
15202
|
+
sessionId: z17.string().optional().describe("reviewer 子 session id(boomerang 溯源用,可选)"),
|
|
15203
|
+
model: z17.string().optional().describe("审批模型 id(审计用,可选)")
|
|
14943
15204
|
},
|
|
14944
15205
|
async execute(args) {
|
|
14945
15206
|
return await runSafe("review_approval", async () => {
|
|
@@ -14960,10 +15221,10 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14960
15221
|
model_chain: tool({
|
|
14961
15222
|
description: description11,
|
|
14962
15223
|
args: {
|
|
14963
|
-
agent:
|
|
14964
|
-
current:
|
|
14965
|
-
root:
|
|
14966
|
-
config_file:
|
|
15224
|
+
agent: z17.string().optional().describe("查指定 agent;不传 → 列出全部"),
|
|
15225
|
+
current: z17.string().optional().describe("当前已用过的模型(<provider>/<id>),用于算下一档"),
|
|
15226
|
+
root: z17.string().optional().describe("项目根目录,默认 cwd"),
|
|
15227
|
+
config_file: z17.string().optional().describe("配置文件名;默认 codeforge.json")
|
|
14967
15228
|
},
|
|
14968
15229
|
async execute(args) {
|
|
14969
15230
|
return await runSafe("model_chain", async () => {
|
|
@@ -14984,11 +15245,11 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14984
15245
|
session_merge: tool({
|
|
14985
15246
|
description: description12,
|
|
14986
15247
|
args: {
|
|
14987
|
-
action:
|
|
14988
|
-
session_id:
|
|
14989
|
-
plan_id:
|
|
14990
|
-
force:
|
|
14991
|
-
stat:
|
|
15248
|
+
action: z17.enum(["merge", "status", "discard", "diff"]).describe("操作类型:merge=合并到主仓(orchestrator 专用)/ status=查询状态 / discard=放弃 / diff=查看 worktree 改动"),
|
|
15249
|
+
session_id: z17.string().optional().describe("目标 session id;不传则用当前 session"),
|
|
15250
|
+
plan_id: z17.string().optional().describe("关联的 plan_id(reviewer 校验时用),格式 plan-YYYYMMDD-HHmmss-NNN"),
|
|
15251
|
+
force: z17.boolean().optional().describe("action=merge 时跳过 review 直接 squash merge(写审计)"),
|
|
15252
|
+
stat: z17.boolean().optional().describe("action=diff 时:true=只显示文件列表+统计,false=完整 diff(默认 false)")
|
|
14992
15253
|
},
|
|
14993
15254
|
async execute(args, input) {
|
|
14994
15255
|
return await runSafe("session_merge", async () => {
|
|
@@ -15024,9 +15285,9 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15024
15285
|
plan_write: tool({
|
|
15025
15286
|
description: description13,
|
|
15026
15287
|
args: {
|
|
15027
|
-
title:
|
|
15028
|
-
content:
|
|
15029
|
-
tags:
|
|
15288
|
+
title: z17.string().min(2).max(80).describe('方案简短标题(2-80 字),如 "实现 worktree session 隔离 Phase 1"'),
|
|
15289
|
+
content: z17.string().min(10).describe("方案 markdown 全文,建议 ≥ 50 行"),
|
|
15290
|
+
tags: z17.array(z17.string().min(1)).optional().describe('标签(可选),如 ["phase:1", "arch:worktree"]')
|
|
15030
15291
|
},
|
|
15031
15292
|
async execute(args) {
|
|
15032
15293
|
return await runSafeTracked("plan_write", async () => {
|
|
@@ -15045,8 +15306,8 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15045
15306
|
plan_read: tool({
|
|
15046
15307
|
description: description14,
|
|
15047
15308
|
args: {
|
|
15048
|
-
plan_id:
|
|
15049
|
-
path:
|
|
15309
|
+
plan_id: z17.string().optional().describe("方案 ID(推荐),格式 plan-YYYYMMDD-HHmmss-NNN"),
|
|
15310
|
+
path: z17.string().optional().describe("方案绝对路径(兜底用,没有 plan_id 元数据时退而求其次)")
|
|
15050
15311
|
},
|
|
15051
15312
|
async execute(args) {
|
|
15052
15313
|
return await runSafe("plan_read", async () => {
|
|
@@ -15069,11 +15330,11 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15069
15330
|
adr_init: tool({
|
|
15070
15331
|
description: description15,
|
|
15071
15332
|
args: {
|
|
15072
|
-
cwd:
|
|
15073
|
-
force:
|
|
15074
|
-
dryRun:
|
|
15075
|
-
writePrepare:
|
|
15076
|
-
installPrePush:
|
|
15333
|
+
cwd: z17.string().optional().describe("目标项目根目录,默认 process.cwd();通常无需传"),
|
|
15334
|
+
force: z17.boolean().optional().describe("已存在文件覆盖;覆盖前自动 .bak.<ts> 备份"),
|
|
15335
|
+
dryRun: z17.boolean().optional().describe("只输出将要执行的写入计划,不实际写盘"),
|
|
15336
|
+
writePrepare: z17.boolean().optional().describe("(npm 项目)自动合并 git config core.hooksPath 到 package.json scripts.prepare;写前自动 backup"),
|
|
15337
|
+
installPrePush: z17.boolean().optional().describe("是否同时生成 .githooks/pre-push hook,默认 true")
|
|
15077
15338
|
},
|
|
15078
15339
|
async execute(args) {
|
|
15079
15340
|
return await runSafe("adr_init", async () => {
|
|
@@ -15085,6 +15346,27 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15085
15346
|
return wrap(result, { title: "adr_init" });
|
|
15086
15347
|
});
|
|
15087
15348
|
}
|
|
15349
|
+
}),
|
|
15350
|
+
worktrees_gc: tool({
|
|
15351
|
+
description: description16,
|
|
15352
|
+
args: {
|
|
15353
|
+
force: z17.boolean().optional().describe("高风险:时间护栏归零,连 probe unknown 的 worktree 也立即清,可能误删活跃 session worktree。默认 false(保守)")
|
|
15354
|
+
},
|
|
15355
|
+
async execute(args) {
|
|
15356
|
+
return await runSafe("worktrees_gc", async () => {
|
|
15357
|
+
const v = projectValidate("worktrees_gc", ArgsSchema16, args);
|
|
15358
|
+
if (!v.ok)
|
|
15359
|
+
return wrap(JSON.parse(v.output));
|
|
15360
|
+
const result = await execute16(v.data);
|
|
15361
|
+
const meta = { title: "worktrees_gc" };
|
|
15362
|
+
const r = result;
|
|
15363
|
+
if (r.ok) {
|
|
15364
|
+
meta["cleaned"] = r.cleanedCount ?? 0;
|
|
15365
|
+
meta["gitAdminPruned"] = r.gitAdminPruned ?? 0;
|
|
15366
|
+
}
|
|
15367
|
+
return wrap(result, meta);
|
|
15368
|
+
});
|
|
15369
|
+
}
|
|
15088
15370
|
})
|
|
15089
15371
|
}
|
|
15090
15372
|
};
|
|
@@ -15093,10 +15375,10 @@ var handler7 = codeforgeToolsServer;
|
|
|
15093
15375
|
|
|
15094
15376
|
// plugins/discover-spec-suggest.ts
|
|
15095
15377
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "node:fs";
|
|
15096
|
-
import { join as
|
|
15378
|
+
import { join as join17 } from "node:path";
|
|
15097
15379
|
|
|
15098
15380
|
// lib/handoff-schema.ts
|
|
15099
|
-
import { z as
|
|
15381
|
+
import { z as z18 } from "zod";
|
|
15100
15382
|
|
|
15101
15383
|
// node_modules/yaml/dist/index.js
|
|
15102
15384
|
var composer = require_composer();
|
|
@@ -15149,92 +15431,92 @@ var MAX_HANDOFF_SIZE = 100 * 1024;
|
|
|
15149
15431
|
var SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,49}$/;
|
|
15150
15432
|
var SCORE_DIMENSIONS = ["functional", "ux", "technical", "constraints", "edge_cases"];
|
|
15151
15433
|
var COMBO_VALUES = ["A", "B", "C", "D"];
|
|
15152
|
-
var NeedSchema =
|
|
15153
|
-
id:
|
|
15154
|
-
type:
|
|
15155
|
-
statement:
|
|
15156
|
-
rationale:
|
|
15157
|
-
acceptance:
|
|
15434
|
+
var NeedSchema = z18.object({
|
|
15435
|
+
id: z18.string().min(1),
|
|
15436
|
+
type: z18.enum(["must", "should", "nice-to-have"]),
|
|
15437
|
+
statement: z18.string().min(1),
|
|
15438
|
+
rationale: z18.string().optional(),
|
|
15439
|
+
acceptance: z18.array(z18.string()).optional()
|
|
15158
15440
|
});
|
|
15159
|
-
var BoundarySchema =
|
|
15160
|
-
excluded:
|
|
15161
|
-
reason:
|
|
15441
|
+
var BoundarySchema = z18.object({
|
|
15442
|
+
excluded: z18.string().min(1),
|
|
15443
|
+
reason: z18.string().min(1)
|
|
15162
15444
|
});
|
|
15163
|
-
var AssumptionSchema =
|
|
15164
|
-
statement:
|
|
15165
|
-
confidence:
|
|
15166
|
-
needs_validation_by:
|
|
15445
|
+
var AssumptionSchema = z18.object({
|
|
15446
|
+
statement: z18.string().min(1),
|
|
15447
|
+
confidence: z18.enum(["verified", "speculation", "high-risk-unknown"]),
|
|
15448
|
+
needs_validation_by: z18.enum(["plan", "coder", "runtime"]).optional()
|
|
15167
15449
|
});
|
|
15168
|
-
var OpenIssueSchema =
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
id:
|
|
15172
|
-
question:
|
|
15173
|
-
blocking:
|
|
15450
|
+
var OpenIssueSchema = z18.union([
|
|
15451
|
+
z18.string().min(1),
|
|
15452
|
+
z18.object({
|
|
15453
|
+
id: z18.string().optional(),
|
|
15454
|
+
question: z18.string().min(1),
|
|
15455
|
+
blocking: z18.boolean().optional()
|
|
15174
15456
|
})
|
|
15175
15457
|
]);
|
|
15176
|
-
var RedFlagsSchema =
|
|
15177
|
-
raised:
|
|
15178
|
-
combos:
|
|
15179
|
-
reasons:
|
|
15180
|
-
user_persisted_rounds:
|
|
15181
|
-
downstream_advisory:
|
|
15458
|
+
var RedFlagsSchema = z18.object({
|
|
15459
|
+
raised: z18.boolean(),
|
|
15460
|
+
combos: z18.array(z18.enum(COMBO_VALUES)).default([]),
|
|
15461
|
+
reasons: z18.array(z18.string()).default([]),
|
|
15462
|
+
user_persisted_rounds: z18.number().int().nonnegative().default(0),
|
|
15463
|
+
downstream_advisory: z18.string().nullable().optional()
|
|
15182
15464
|
}).superRefine((rf, ctx) => {
|
|
15183
15465
|
if (rf.raised) {
|
|
15184
15466
|
if (rf.combos.length === 0) {
|
|
15185
15467
|
ctx.addIssue({
|
|
15186
|
-
code:
|
|
15468
|
+
code: z18.ZodIssueCode.custom,
|
|
15187
15469
|
message: "red_flags.raised=true 时 combos 必须 >=1",
|
|
15188
15470
|
path: ["combos"]
|
|
15189
15471
|
});
|
|
15190
15472
|
}
|
|
15191
15473
|
if (rf.reasons.length === 0) {
|
|
15192
15474
|
ctx.addIssue({
|
|
15193
|
-
code:
|
|
15475
|
+
code: z18.ZodIssueCode.custom,
|
|
15194
15476
|
message: "red_flags.raised=true 时 reasons 必须 >=1",
|
|
15195
15477
|
path: ["reasons"]
|
|
15196
15478
|
});
|
|
15197
15479
|
}
|
|
15198
15480
|
}
|
|
15199
15481
|
});
|
|
15200
|
-
var ScoresSchema =
|
|
15201
|
-
var PreCodingBlockerSchema =
|
|
15202
|
-
id:
|
|
15203
|
-
blocker:
|
|
15204
|
-
source:
|
|
15205
|
-
must_resolve_by:
|
|
15482
|
+
var ScoresSchema = z18.object(Object.fromEntries(SCORE_DIMENSIONS.map((d) => [d, z18.number().min(0).max(1)])));
|
|
15483
|
+
var PreCodingBlockerSchema = z18.object({
|
|
15484
|
+
id: z18.string().regex(/^PRE-\d+$/, "id 必须形如 PRE-1 / PRE-2"),
|
|
15485
|
+
blocker: z18.string().min(1),
|
|
15486
|
+
source: z18.enum(["assumption", "red_flag", "open_issue"]),
|
|
15487
|
+
must_resolve_by: z18.enum(["user", "codeforge"])
|
|
15206
15488
|
});
|
|
15207
|
-
var KhRefSchema =
|
|
15208
|
-
kh_id:
|
|
15209
|
-
title:
|
|
15210
|
-
relevance:
|
|
15489
|
+
var KhRefSchema = z18.object({
|
|
15490
|
+
kh_id: z18.string(),
|
|
15491
|
+
title: z18.string(),
|
|
15492
|
+
relevance: z18.enum(["positive", "negative", "neutral"]).optional()
|
|
15211
15493
|
});
|
|
15212
|
-
var HandoffSchema =
|
|
15213
|
-
schema_version:
|
|
15214
|
-
slug:
|
|
15215
|
-
title:
|
|
15216
|
-
created_at:
|
|
15217
|
-
discover_session_id:
|
|
15218
|
-
weighted_score:
|
|
15494
|
+
var HandoffSchema = z18.object({
|
|
15495
|
+
schema_version: z18.string().regex(/^\d+\.\d+\.\d+$/, "schema_version 必须形如 1.1.0 / 1.2.0"),
|
|
15496
|
+
slug: z18.string().regex(SLUG_REGEX, "slug 仅允许 [a-z0-9-],长度 1-50,首字符为字母数字"),
|
|
15497
|
+
title: z18.string().min(1).max(200),
|
|
15498
|
+
created_at: z18.string().optional(),
|
|
15499
|
+
discover_session_id: z18.string().optional(),
|
|
15500
|
+
weighted_score: z18.number().min(0).max(1),
|
|
15219
15501
|
scores: ScoresSchema,
|
|
15220
|
-
needs:
|
|
15221
|
-
boundaries:
|
|
15222
|
-
assumptions:
|
|
15223
|
-
open_issues:
|
|
15224
|
-
rejected_alternatives:
|
|
15225
|
-
acceptance_criteria:
|
|
15226
|
-
id:
|
|
15227
|
-
description:
|
|
15228
|
-
measurable:
|
|
15229
|
-
metric:
|
|
15502
|
+
needs: z18.array(NeedSchema).min(1, "needs 必须 >=1"),
|
|
15503
|
+
boundaries: z18.array(BoundarySchema).min(1, "boundaries 必须 >=1"),
|
|
15504
|
+
assumptions: z18.array(AssumptionSchema),
|
|
15505
|
+
open_issues: z18.array(OpenIssueSchema).default([]),
|
|
15506
|
+
rejected_alternatives: z18.array(z18.object({ option: z18.string(), reason: z18.string() })).default([]),
|
|
15507
|
+
acceptance_criteria: z18.array(z18.object({
|
|
15508
|
+
id: z18.string().regex(/^AC-/, "AC id 必须以 AC- 开头"),
|
|
15509
|
+
description: z18.string().min(1),
|
|
15510
|
+
measurable: z18.boolean().optional(),
|
|
15511
|
+
metric: z18.string().optional()
|
|
15230
15512
|
})).default([]),
|
|
15231
15513
|
red_flags: RedFlagsSchema,
|
|
15232
|
-
pre_coding_blockers:
|
|
15233
|
-
kh_references:
|
|
15234
|
-
adr_refs:
|
|
15235
|
-
related_artifacts:
|
|
15236
|
-
prd:
|
|
15237
|
-
transcript:
|
|
15514
|
+
pre_coding_blockers: z18.array(PreCodingBlockerSchema).default([]),
|
|
15515
|
+
kh_references: z18.array(KhRefSchema).default([]),
|
|
15516
|
+
adr_refs: z18.array(z18.string()).default([]),
|
|
15517
|
+
related_artifacts: z18.object({
|
|
15518
|
+
prd: z18.string().optional(),
|
|
15519
|
+
transcript: z18.string().optional()
|
|
15238
15520
|
}).optional()
|
|
15239
15521
|
});
|
|
15240
15522
|
function validateHandoff(rawYaml, fileSize) {
|
|
@@ -15260,9 +15542,9 @@ function validateHandoff(rawYaml, fileSize) {
|
|
|
15260
15542
|
const result = HandoffSchema.safeParse(parsed);
|
|
15261
15543
|
if (!result.success) {
|
|
15262
15544
|
const first = result.error.issues[0];
|
|
15263
|
-
const
|
|
15545
|
+
const path21 = first?.path?.join(".") ?? "(root)";
|
|
15264
15546
|
const msg = first?.message ?? "unknown";
|
|
15265
|
-
return { ok: false, reason: `schema 校验失败:${
|
|
15547
|
+
return { ok: false, reason: `schema 校验失败:${path21}: ${msg}` };
|
|
15266
15548
|
}
|
|
15267
15549
|
return { ok: true, data: result.data, schemaVersion: result.data.schema_version };
|
|
15268
15550
|
}
|
|
@@ -15279,7 +15561,7 @@ var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
|
15279
15561
|
var MATCH_THRESHOLD = 0.15;
|
|
15280
15562
|
var MAX_CANDIDATES = 3;
|
|
15281
15563
|
var NUDGE_MAX_LEN = 1500;
|
|
15282
|
-
var SPECS_REL_DIR =
|
|
15564
|
+
var SPECS_REL_DIR = join17("docs", "specs");
|
|
15283
15565
|
var sessionMap = new Map;
|
|
15284
15566
|
function pruneIfOversize2() {
|
|
15285
15567
|
while (sessionMap.size > SESSION_CAP2) {
|
|
@@ -15386,7 +15668,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15386
15668
|
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
15387
15669
|
const statReader = opts.statReader ?? defaultStatReader;
|
|
15388
15670
|
const log6 = makePluginLogger(PLUGIN_NAME8);
|
|
15389
|
-
const specsRoot =
|
|
15671
|
+
const specsRoot = join17(rootDir, SPECS_REL_DIR);
|
|
15390
15672
|
const records = [];
|
|
15391
15673
|
if (!dirExists(specsRoot)) {
|
|
15392
15674
|
log6.info(`specs 目录不存在,plugin 将 no-op`, { specsRoot });
|
|
@@ -15407,7 +15689,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15407
15689
|
log6.info(`跳过非合法 slug 命名的条目`, { entry });
|
|
15408
15690
|
continue;
|
|
15409
15691
|
}
|
|
15410
|
-
const specDir =
|
|
15692
|
+
const specDir = join17(specsRoot, entry);
|
|
15411
15693
|
let dirStat;
|
|
15412
15694
|
try {
|
|
15413
15695
|
dirStat = statReader(specDir);
|
|
@@ -15420,7 +15702,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15420
15702
|
}
|
|
15421
15703
|
if (!dirStat.isDirectory)
|
|
15422
15704
|
continue;
|
|
15423
|
-
const handoffPath =
|
|
15705
|
+
const handoffPath = join17(specDir, "handoff.yaml");
|
|
15424
15706
|
let fileStat;
|
|
15425
15707
|
try {
|
|
15426
15708
|
fileStat = statReader(handoffPath);
|
|
@@ -15593,13 +15875,13 @@ var handler8 = discoverSpecSuggestServer;
|
|
|
15593
15875
|
|
|
15594
15876
|
// lib/memories.ts
|
|
15595
15877
|
import { promises as fs16 } from "node:fs";
|
|
15596
|
-
import * as
|
|
15597
|
-
import * as
|
|
15878
|
+
import * as path21 from "node:path";
|
|
15879
|
+
import * as os6 from "node:os";
|
|
15598
15880
|
function resolveConfig(c) {
|
|
15599
15881
|
return {
|
|
15600
15882
|
projectRoot: c.projectRoot,
|
|
15601
|
-
homeDir: c.homeDir ??
|
|
15602
|
-
projectName: c.projectName ??
|
|
15883
|
+
homeDir: c.homeDir ?? os6.homedir(),
|
|
15884
|
+
projectName: c.projectName ?? path21.basename(c.projectRoot),
|
|
15603
15885
|
now: c.now ?? Date.now,
|
|
15604
15886
|
log: c.log ?? (() => {}),
|
|
15605
15887
|
maxPerScope: c.maxPerScope ?? 1000
|
|
@@ -15607,9 +15889,9 @@ function resolveConfig(c) {
|
|
|
15607
15889
|
}
|
|
15608
15890
|
function fileFor(scope, cfg) {
|
|
15609
15891
|
if (scope === "project") {
|
|
15610
|
-
return
|
|
15892
|
+
return path21.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
15611
15893
|
}
|
|
15612
|
-
return
|
|
15894
|
+
return path21.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
15613
15895
|
}
|
|
15614
15896
|
async function readBank(p) {
|
|
15615
15897
|
try {
|
|
@@ -15623,7 +15905,7 @@ async function readBank(p) {
|
|
|
15623
15905
|
}
|
|
15624
15906
|
}
|
|
15625
15907
|
async function writeBank(p, items) {
|
|
15626
|
-
await fs16.mkdir(
|
|
15908
|
+
await fs16.mkdir(path21.dirname(p), { recursive: true });
|
|
15627
15909
|
const tmp = `${p}.tmp`;
|
|
15628
15910
|
await fs16.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
15629
15911
|
await fs16.rename(tmp, p);
|
|
@@ -16144,7 +16426,7 @@ var handler10 = modelFallbackServer;
|
|
|
16144
16426
|
|
|
16145
16427
|
// plugins/subtask-heartbeat.ts
|
|
16146
16428
|
import { promises as fsPromises } from "node:fs";
|
|
16147
|
-
import * as
|
|
16429
|
+
import * as path22 from "node:path";
|
|
16148
16430
|
var recordSessionParent2 = recordSessionParent;
|
|
16149
16431
|
var lookupParentSessionId2 = lookupParentSessionId;
|
|
16150
16432
|
var deleteSessionParent2 = deleteSessionParent;
|
|
@@ -16268,11 +16550,11 @@ function extractTaskArgs(args) {
|
|
|
16268
16550
|
const a = args;
|
|
16269
16551
|
const rawDesc = typeof a["description"] === "string" ? a["description"] : null;
|
|
16270
16552
|
const rawPrompt = typeof a["prompt"] === "string" ? a["prompt"] : null;
|
|
16271
|
-
const
|
|
16553
|
+
const description17 = rawDesc ?? (rawPrompt ? rawPrompt.slice(0, 60) : null);
|
|
16272
16554
|
const subagentType = typeof a["subagent_type"] === "string" && a["subagent_type"] || typeof a["agent"] === "string" && a["agent"] || typeof a["agentType"] === "string" && a["agentType"] || typeof a["agent_type"] === "string" && a["agent_type"] || null;
|
|
16273
|
-
if (!
|
|
16555
|
+
if (!description17 && !subagentType)
|
|
16274
16556
|
return null;
|
|
16275
|
-
return { description:
|
|
16557
|
+
return { description: description17, subagentType };
|
|
16276
16558
|
}
|
|
16277
16559
|
function enqueuePendingTask(parentID, entry, now = Date.now()) {
|
|
16278
16560
|
const ts = entry.ts ?? now;
|
|
@@ -16491,7 +16773,7 @@ function buildErrorLogLine(errorReason, elapsedMs, now = Date.now()) {
|
|
|
16491
16773
|
}
|
|
16492
16774
|
async function appendSubagentLog(filePath, line, log7) {
|
|
16493
16775
|
try {
|
|
16494
|
-
await fsPromises.mkdir(
|
|
16776
|
+
await fsPromises.mkdir(path22.dirname(filePath), { recursive: true });
|
|
16495
16777
|
await fsPromises.appendFile(filePath, line + `
|
|
16496
16778
|
`, "utf8");
|
|
16497
16779
|
} catch (err) {
|
|
@@ -16859,8 +17141,8 @@ var handler12 = parallelStatusServer;
|
|
|
16859
17141
|
|
|
16860
17142
|
// plugins/parallel-tool-nudge.ts
|
|
16861
17143
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
|
|
16862
|
-
import { join as
|
|
16863
|
-
import { homedir as
|
|
17144
|
+
import { join as join19 } from "node:path";
|
|
17145
|
+
import { homedir as homedir7 } from "node:os";
|
|
16864
17146
|
var PLUGIN_NAME13 = "parallel-tool-nudge";
|
|
16865
17147
|
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
16866
17148
|
var PARALLEL_SAFE_TOOLS = [
|
|
@@ -16912,10 +17194,10 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16912
17194
|
const reader = opts.reader ?? defaultReader2;
|
|
16913
17195
|
const dirReader = opts.dirReader ?? defaultDirReader2;
|
|
16914
17196
|
const dirExists = opts.dirExists ?? defaultDirExists2;
|
|
16915
|
-
const homeAgentsDir = opts.homeAgentsDir ??
|
|
17197
|
+
const homeAgentsDir = opts.homeAgentsDir ?? join19(homedir7(), ".config", "opencode", "agents");
|
|
16916
17198
|
const candidateDirs = [
|
|
16917
|
-
|
|
16918
|
-
|
|
17199
|
+
join19(rootDir, ".codeforge", "agents"),
|
|
17200
|
+
join19(rootDir, "agents"),
|
|
16919
17201
|
homeAgentsDir
|
|
16920
17202
|
];
|
|
16921
17203
|
const result = new Map;
|
|
@@ -16938,20 +17220,20 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16938
17220
|
for (const entry of entries) {
|
|
16939
17221
|
if (!entry.endsWith(".md"))
|
|
16940
17222
|
continue;
|
|
16941
|
-
const
|
|
17223
|
+
const path23 = join19(dir, entry);
|
|
16942
17224
|
let content;
|
|
16943
17225
|
try {
|
|
16944
|
-
content = reader(
|
|
17226
|
+
content = reader(path23);
|
|
16945
17227
|
} catch (err) {
|
|
16946
17228
|
log8.warn(`agent.md 读取失败(已跳过)`, {
|
|
16947
|
-
path:
|
|
17229
|
+
path: path23,
|
|
16948
17230
|
error: err instanceof Error ? err.message : String(err)
|
|
16949
17231
|
});
|
|
16950
17232
|
continue;
|
|
16951
17233
|
}
|
|
16952
17234
|
const parsed = parseAgentFrontmatter(content);
|
|
16953
17235
|
if (!parsed) {
|
|
16954
|
-
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path:
|
|
17236
|
+
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path: path23 });
|
|
16955
17237
|
continue;
|
|
16956
17238
|
}
|
|
16957
17239
|
if (result.has(parsed.name))
|
|
@@ -17111,7 +17393,7 @@ function prependUtf8Prelude(command) {
|
|
|
17111
17393
|
return command;
|
|
17112
17394
|
return PRELUDE + command;
|
|
17113
17395
|
}
|
|
17114
|
-
var handler14 = async (
|
|
17396
|
+
var handler14 = async (_ctx4) => {
|
|
17115
17397
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
17116
17398
|
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
17117
17399
|
logLifecycle(PLUGIN_NAME14, "activate", { enabled, platform: process.platform, reason });
|
|
@@ -17143,7 +17425,7 @@ var handler14 = async (_ctx3) => {
|
|
|
17143
17425
|
|
|
17144
17426
|
// lib/event-stream.ts
|
|
17145
17427
|
import { promises as fs17 } from "node:fs";
|
|
17146
|
-
import * as
|
|
17428
|
+
import * as path23 from "node:path";
|
|
17147
17429
|
async function loadSession(id, opts = {}) {
|
|
17148
17430
|
const file = resolveSessionFile(id, opts);
|
|
17149
17431
|
const raw = await fs17.readFile(file, "utf8");
|
|
@@ -17163,7 +17445,7 @@ async function listSessions(opts = {}) {
|
|
|
17163
17445
|
for (const e of entries) {
|
|
17164
17446
|
if (!e.isFile() || !e.name.endsWith(".jsonl"))
|
|
17165
17447
|
continue;
|
|
17166
|
-
const file =
|
|
17448
|
+
const file = path23.join(dir, e.name);
|
|
17167
17449
|
const id = e.name.replace(/\.jsonl$/, "");
|
|
17168
17450
|
try {
|
|
17169
17451
|
const stat = await fs17.stat(file);
|
|
@@ -17190,11 +17472,11 @@ async function listSessions(opts = {}) {
|
|
|
17190
17472
|
return out;
|
|
17191
17473
|
}
|
|
17192
17474
|
function resolveDir(opts = {}) {
|
|
17193
|
-
const root =
|
|
17194
|
-
return opts.sessions_dir ?
|
|
17475
|
+
const root = path23.resolve(opts.root ?? process.cwd());
|
|
17476
|
+
return opts.sessions_dir ? path23.resolve(root, opts.sessions_dir) : path23.join(runtimeDir(root), "sessions");
|
|
17195
17477
|
}
|
|
17196
17478
|
function resolveSessionFile(id, opts = {}) {
|
|
17197
|
-
return
|
|
17479
|
+
return path23.join(resolveDir(opts), `${id}.jsonl`);
|
|
17198
17480
|
}
|
|
17199
17481
|
function parseJsonl(id, raw) {
|
|
17200
17482
|
const events = [];
|
|
@@ -17458,10 +17740,10 @@ function isRecoveryWorthShowing(plan) {
|
|
|
17458
17740
|
|
|
17459
17741
|
// lib/block-pending.ts
|
|
17460
17742
|
import { promises as fs18 } from "node:fs";
|
|
17461
|
-
import * as
|
|
17743
|
+
import * as path24 from "node:path";
|
|
17462
17744
|
function blockPendingFilePath(absRoot) {
|
|
17463
17745
|
const rd = runtimeDir(absRoot, { ensure: false });
|
|
17464
|
-
return
|
|
17746
|
+
return path24.join(rd, "sessions", "autonomous-blocks.ndjson");
|
|
17465
17747
|
}
|
|
17466
17748
|
function consumeLockPath(absRoot) {
|
|
17467
17749
|
return blockPendingFilePath(absRoot) + ".consume.lock";
|
|
@@ -17526,7 +17808,7 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
17526
17808
|
if (entries.length === 0)
|
|
17527
17809
|
return;
|
|
17528
17810
|
const file = blockPendingFilePath(absRoot);
|
|
17529
|
-
await fs18.mkdir(
|
|
17811
|
+
await fs18.mkdir(path24.dirname(file), { recursive: true });
|
|
17530
17812
|
const now = new Date().toISOString();
|
|
17531
17813
|
const lines = entries.map((e) => ({
|
|
17532
17814
|
type: "consume",
|
|
@@ -17676,7 +17958,7 @@ var handler15 = sessionRecoveryServer;
|
|
|
17676
17958
|
|
|
17677
17959
|
// plugins/subtasks.ts
|
|
17678
17960
|
import { promises as fs19 } from "node:fs";
|
|
17679
|
-
import * as
|
|
17961
|
+
import * as path25 from "node:path";
|
|
17680
17962
|
|
|
17681
17963
|
// lib/parallel-merge.ts
|
|
17682
17964
|
init_worktree_ops();
|
|
@@ -18291,7 +18573,7 @@ function buildSystemPrompt(maxSubtasks) {
|
|
|
18291
18573
|
].join(`
|
|
18292
18574
|
`);
|
|
18293
18575
|
}
|
|
18294
|
-
async function decomposeTask(
|
|
18576
|
+
async function decomposeTask(description17, opts) {
|
|
18295
18577
|
const log10 = opts.log ?? (() => {});
|
|
18296
18578
|
if (opts.mockResponse) {
|
|
18297
18579
|
return validateAndFinalize(opts.mockResponse, undefined, log10, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
|
|
@@ -18301,7 +18583,7 @@ async function decomposeTask(description16, opts) {
|
|
|
18301
18583
|
let childSessionId;
|
|
18302
18584
|
try {
|
|
18303
18585
|
const created = await opts.client.session.create({
|
|
18304
|
-
body: { title: `decompose:${clip5(
|
|
18586
|
+
body: { title: `decompose:${clip5(description17, 60)}` },
|
|
18305
18587
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
18306
18588
|
});
|
|
18307
18589
|
if (created.error || !created.data?.id) {
|
|
@@ -18318,7 +18600,7 @@ async function decomposeTask(description16, opts) {
|
|
|
18318
18600
|
path: { id: childSessionId },
|
|
18319
18601
|
body: {
|
|
18320
18602
|
system: systemText,
|
|
18321
|
-
parts: [{ type: "text", text:
|
|
18603
|
+
parts: [{ type: "text", text: description17 }]
|
|
18322
18604
|
},
|
|
18323
18605
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
18324
18606
|
}));
|
|
@@ -18500,7 +18782,7 @@ function sleep2(ms) {
|
|
|
18500
18782
|
// plugins/subtasks.ts
|
|
18501
18783
|
var PLUGIN_NAME16 = "subtasks";
|
|
18502
18784
|
function getLogFile(root = process.cwd()) {
|
|
18503
|
-
return
|
|
18785
|
+
return path25.join(runtimeDir(root), "logs", "subtasks.log");
|
|
18504
18786
|
}
|
|
18505
18787
|
var VERB_RE = /^([a-zA-Z]{3,12})/;
|
|
18506
18788
|
var CN_VERBS = [
|
|
@@ -18805,7 +19087,7 @@ async function writeLog(level, msg, data) {
|
|
|
18805
19087
|
`;
|
|
18806
19088
|
try {
|
|
18807
19089
|
const logFile = getLogFile();
|
|
18808
|
-
await fs19.mkdir(
|
|
19090
|
+
await fs19.mkdir(path25.dirname(logFile), { recursive: true });
|
|
18809
19091
|
await fs19.appendFile(logFile, line, "utf8");
|
|
18810
19092
|
} catch {}
|
|
18811
19093
|
}
|
|
@@ -18822,8 +19104,8 @@ var subtasksServer = async (ctx) => {
|
|
|
18822
19104
|
try {
|
|
18823
19105
|
if (input?.command !== "parallel")
|
|
18824
19106
|
return;
|
|
18825
|
-
const
|
|
18826
|
-
if (!
|
|
19107
|
+
const description17 = (input.arguments ?? "").trim();
|
|
19108
|
+
if (!description17) {
|
|
18827
19109
|
return;
|
|
18828
19110
|
}
|
|
18829
19111
|
let autoMerge = false;
|
|
@@ -18862,7 +19144,7 @@ var subtasksServer = async (ctx) => {
|
|
|
18862
19144
|
} : undefined;
|
|
18863
19145
|
const replyLines = [];
|
|
18864
19146
|
const messageCtx = {
|
|
18865
|
-
content: `/parallel ${
|
|
19147
|
+
content: `/parallel ${description17}`,
|
|
18866
19148
|
reply: (s) => {
|
|
18867
19149
|
replyLines.push(s);
|
|
18868
19150
|
return Promise.resolve();
|
|
@@ -19438,7 +19720,7 @@ var handler18 = tokenManagerServer;
|
|
|
19438
19720
|
|
|
19439
19721
|
// plugins/tool-policy.ts
|
|
19440
19722
|
import { promises as fs20 } from "node:fs";
|
|
19441
|
-
import * as
|
|
19723
|
+
import * as path27 from "node:path";
|
|
19442
19724
|
|
|
19443
19725
|
// lib/tool-risk.ts
|
|
19444
19726
|
var RISK_PATTERNS = [
|
|
@@ -19592,7 +19874,7 @@ function buildHaystackFor(args, matchOn) {
|
|
|
19592
19874
|
}
|
|
19593
19875
|
|
|
19594
19876
|
// lib/file-regex-acl.ts
|
|
19595
|
-
import * as
|
|
19877
|
+
import * as path26 from "node:path";
|
|
19596
19878
|
function compileRule(r) {
|
|
19597
19879
|
if (r instanceof RegExp)
|
|
19598
19880
|
return r;
|
|
@@ -19658,7 +19940,7 @@ function normalizePath2(p) {
|
|
|
19658
19940
|
let s = p.replace(/\\/g, "/");
|
|
19659
19941
|
if (s.startsWith("./"))
|
|
19660
19942
|
s = s.slice(2);
|
|
19661
|
-
s =
|
|
19943
|
+
s = path26.posix.normalize(s);
|
|
19662
19944
|
return s;
|
|
19663
19945
|
}
|
|
19664
19946
|
function checkFileAccess(acl, file, op) {
|
|
@@ -19761,9 +20043,9 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
|
19761
20043
|
const action = risks.length > 0 || worstAcl === "deny" ? "deny" : "allow";
|
|
19762
20044
|
return { action, reasons, risks, acl: aclResults };
|
|
19763
20045
|
}
|
|
19764
|
-
var POLICY_PATH =
|
|
20046
|
+
var POLICY_PATH = path27.join(".codeforge", "policy.json");
|
|
19765
20047
|
async function loadPolicy(root = process.cwd()) {
|
|
19766
|
-
const file =
|
|
20048
|
+
const file = path27.join(root, POLICY_PATH);
|
|
19767
20049
|
try {
|
|
19768
20050
|
const raw = await fs20.readFile(file, "utf8");
|
|
19769
20051
|
const data = JSON.parse(raw);
|
|
@@ -19862,8 +20144,8 @@ var handler19 = toolPolicyServer;
|
|
|
19862
20144
|
|
|
19863
20145
|
// plugins/update-checker.ts
|
|
19864
20146
|
import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
19865
|
-
import { homedir as
|
|
19866
|
-
import { join as
|
|
20147
|
+
import { homedir as homedir9 } from "node:os";
|
|
20148
|
+
import { join as join25 } from "node:path";
|
|
19867
20149
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
19868
20150
|
|
|
19869
20151
|
// lib/update-checker-impl.ts
|
|
@@ -19880,8 +20162,8 @@ import {
|
|
|
19880
20162
|
unlinkSync,
|
|
19881
20163
|
writeFileSync as writeFileSync2
|
|
19882
20164
|
} from "node:fs";
|
|
19883
|
-
import { homedir as
|
|
19884
|
-
import { dirname as dirname14, join as
|
|
20165
|
+
import { homedir as homedir8, tmpdir } from "node:os";
|
|
20166
|
+
import { dirname as dirname14, join as join24 } from "node:path";
|
|
19885
20167
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19886
20168
|
import * as https from "node:https";
|
|
19887
20169
|
import * as zlib from "node:zlib";
|
|
@@ -19889,7 +20171,7 @@ import * as zlib from "node:zlib";
|
|
|
19889
20171
|
// lib/version-injected.ts
|
|
19890
20172
|
function getInjectedVersion() {
|
|
19891
20173
|
try {
|
|
19892
|
-
const v = "0.6.
|
|
20174
|
+
const v = "0.6.12";
|
|
19893
20175
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
19894
20176
|
return v;
|
|
19895
20177
|
}
|
|
@@ -19979,17 +20261,17 @@ function readLocalVersion() {
|
|
|
19979
20261
|
try {
|
|
19980
20262
|
const here = fileURLToPath2(import.meta.url);
|
|
19981
20263
|
const root = dirname14(dirname14(here));
|
|
19982
|
-
const pkg = JSON.parse(readFileSync5(
|
|
20264
|
+
const pkg = JSON.parse(readFileSync5(join24(root, "package.json"), "utf8"));
|
|
19983
20265
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
19984
20266
|
} catch {
|
|
19985
20267
|
return "0.0.0";
|
|
19986
20268
|
}
|
|
19987
20269
|
}
|
|
19988
20270
|
function defaultCacheDir() {
|
|
19989
|
-
return process.env["CODEFORGE_CACHE_DIR"] ??
|
|
20271
|
+
return process.env["CODEFORGE_CACHE_DIR"] ?? join24(homedir8(), ".cache", "codeforge");
|
|
19990
20272
|
}
|
|
19991
20273
|
function defaultCacheFile() {
|
|
19992
|
-
return
|
|
20274
|
+
return join24(defaultCacheDir(), "update-check.json");
|
|
19993
20275
|
}
|
|
19994
20276
|
function readCache(file) {
|
|
19995
20277
|
try {
|
|
@@ -20145,14 +20427,14 @@ function defaultHttpFetcher(url2, timeoutMs) {
|
|
|
20145
20427
|
});
|
|
20146
20428
|
}
|
|
20147
20429
|
async function downloadAndExtractBundle(opts) {
|
|
20148
|
-
const tmpRoot = opts.tmpDir ?? mkdtempSync(
|
|
20430
|
+
const tmpRoot = opts.tmpDir ?? mkdtempSync(join24(tmpdir(), "codeforge-update-"));
|
|
20149
20431
|
mkdirSync3(tmpRoot, { recursive: true });
|
|
20150
20432
|
const fetcher = opts.tarballFetcher ?? defaultBinaryFetcher;
|
|
20151
20433
|
const tarballBuf = await fetcher(opts.tarballUrl);
|
|
20152
20434
|
verifyIntegrity(tarballBuf, opts.expectedIntegrity);
|
|
20153
20435
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
20154
20436
|
extractTarToDir(tarBuf, tmpRoot);
|
|
20155
|
-
const bundlePath =
|
|
20437
|
+
const bundlePath = join24(tmpRoot, "package", "dist", "index.js");
|
|
20156
20438
|
if (!existsSync5(bundlePath)) {
|
|
20157
20439
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
20158
20440
|
}
|
|
@@ -20192,11 +20474,11 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
20192
20474
|
offset += 512;
|
|
20193
20475
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
20194
20476
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
20195
|
-
const dest =
|
|
20477
|
+
const dest = join24(destRoot, fullName);
|
|
20196
20478
|
mkdirSync3(dirname14(dest), { recursive: true });
|
|
20197
20479
|
writeFileSync2(dest, fileBuf);
|
|
20198
20480
|
} else if (typeFlag === "5") {
|
|
20199
|
-
mkdirSync3(
|
|
20481
|
+
mkdirSync3(join24(destRoot, fullName), { recursive: true });
|
|
20200
20482
|
}
|
|
20201
20483
|
offset += Math.ceil(size / 512) * 512;
|
|
20202
20484
|
}
|
|
@@ -20305,7 +20587,7 @@ function cleanupOldBackups(target, keep) {
|
|
|
20305
20587
|
const base = target.substring(dir.length + 1);
|
|
20306
20588
|
const prefix = `${base}.bak.`;
|
|
20307
20589
|
const all = readdirSync3(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
20308
|
-
const full =
|
|
20590
|
+
const full = join24(dir, f);
|
|
20309
20591
|
let mtimeMs = 0;
|
|
20310
20592
|
try {
|
|
20311
20593
|
mtimeMs = statSync4(full).mtimeMs;
|
|
@@ -20327,7 +20609,7 @@ function loadCompatibility(opts) {
|
|
|
20327
20609
|
const root = opts?.cwd ?? inferPluginRoot();
|
|
20328
20610
|
if (!root)
|
|
20329
20611
|
return null;
|
|
20330
|
-
file =
|
|
20612
|
+
file = join24(root, "compatibility.json");
|
|
20331
20613
|
}
|
|
20332
20614
|
if (!existsSync5(file))
|
|
20333
20615
|
return null;
|
|
@@ -20518,11 +20800,11 @@ var updateCheckerServer = async (ctx) => {
|
|
|
20518
20800
|
expectedIntegrity: npmResult.integrity
|
|
20519
20801
|
});
|
|
20520
20802
|
try {
|
|
20521
|
-
const installMjs =
|
|
20803
|
+
const installMjs = join25(extractDir, "package", "install.mjs");
|
|
20522
20804
|
if (existsSync6(installMjs)) {
|
|
20523
20805
|
const nodeBin = resolveNodeBin();
|
|
20524
20806
|
const r = spawnSync2(nodeBin, [installMjs, "--global", "--skip-build"], {
|
|
20525
|
-
cwd:
|
|
20807
|
+
cwd: join25(extractDir, "package"),
|
|
20526
20808
|
stdio: "pipe",
|
|
20527
20809
|
encoding: "utf8"
|
|
20528
20810
|
});
|
|
@@ -20623,14 +20905,14 @@ function detectOpencodeVersion() {
|
|
|
20623
20905
|
}
|
|
20624
20906
|
function getOpencodeBundlePath() {
|
|
20625
20907
|
const candidates = [];
|
|
20626
|
-
candidates.push(
|
|
20908
|
+
candidates.push(join25(homedir9(), ".config", "opencode", "codeforge", "index.js"));
|
|
20627
20909
|
if (process.platform === "win32") {
|
|
20628
20910
|
const appData = process.env["APPDATA"];
|
|
20629
20911
|
if (appData)
|
|
20630
|
-
candidates.push(
|
|
20912
|
+
candidates.push(join25(appData, "opencode", "codeforge", "index.js"));
|
|
20631
20913
|
const localAppData = process.env["LOCALAPPDATA"];
|
|
20632
20914
|
if (localAppData)
|
|
20633
|
-
candidates.push(
|
|
20915
|
+
candidates.push(join25(localAppData, "opencode", "codeforge", "index.js"));
|
|
20634
20916
|
}
|
|
20635
20917
|
for (const c of candidates) {
|
|
20636
20918
|
if (existsSync6(c))
|
|
@@ -20691,60 +20973,60 @@ async function postToast(ctx, message) {
|
|
|
20691
20973
|
var handler20 = updateCheckerServer;
|
|
20692
20974
|
|
|
20693
20975
|
// plugins/workflow-engine.ts
|
|
20694
|
-
import * as
|
|
20976
|
+
import * as path29 from "node:path";
|
|
20695
20977
|
|
|
20696
20978
|
// lib/workflow-loader.ts
|
|
20697
20979
|
import { promises as fs21 } from "node:fs";
|
|
20698
|
-
import * as
|
|
20699
|
-
import { z as
|
|
20700
|
-
var ActionSchema =
|
|
20701
|
-
tool:
|
|
20702
|
-
args:
|
|
20703
|
-
on_error:
|
|
20980
|
+
import * as path28 from "node:path";
|
|
20981
|
+
import { z as z19 } from "zod";
|
|
20982
|
+
var ActionSchema = z19.object({
|
|
20983
|
+
tool: z19.string().min(1, "action.tool 不能为空"),
|
|
20984
|
+
args: z19.record(z19.string(), z19.unknown()).optional().default({}),
|
|
20985
|
+
on_error: z19.enum(["retry", "skip", "abort"]).optional()
|
|
20704
20986
|
});
|
|
20705
|
-
var StepSchema =
|
|
20706
|
-
name:
|
|
20707
|
-
agent:
|
|
20708
|
-
description:
|
|
20709
|
-
inject_context:
|
|
20710
|
-
requires_human_approval:
|
|
20711
|
-
actions:
|
|
20712
|
-
on_error:
|
|
20713
|
-
max_retries:
|
|
20714
|
-
timeout:
|
|
20715
|
-
auto_feedback:
|
|
20716
|
-
test_cmd:
|
|
20717
|
-
lint_cmd:
|
|
20718
|
-
max_retries:
|
|
20719
|
-
error_excerpt_lines:
|
|
20720
|
-
escalate_to:
|
|
20987
|
+
var StepSchema = z19.object({
|
|
20988
|
+
name: z19.string().min(1, "step.name 不能为空"),
|
|
20989
|
+
agent: z19.string().min(1, "step.agent 不能为空").describe("agent 名(与 agents/<name>.md 对应)"),
|
|
20990
|
+
description: z19.string().optional(),
|
|
20991
|
+
inject_context: z19.record(z19.string(), z19.unknown()).optional(),
|
|
20992
|
+
requires_human_approval: z19.boolean().optional().default(false),
|
|
20993
|
+
actions: z19.array(ActionSchema).optional().default([]),
|
|
20994
|
+
on_error: z19.enum(["retry", "skip", "abort"]).optional().default("abort"),
|
|
20995
|
+
max_retries: z19.number().int().min(0).max(10).optional().default(2),
|
|
20996
|
+
timeout: z19.string().regex(/^\d+(?:ms|s|m|h)$/, "timeout 必须是 数字+单位(ms/s/m/h),如 5m").optional(),
|
|
20997
|
+
auto_feedback: z19.object({
|
|
20998
|
+
test_cmd: z19.string().optional().describe("测试命令,如 npm test"),
|
|
20999
|
+
lint_cmd: z19.string().optional().describe("lint 命令,如 npm run lint"),
|
|
21000
|
+
max_retries: z19.number().int().min(1).max(10).optional().default(3),
|
|
21001
|
+
error_excerpt_lines: z19.number().int().min(1).max(50).optional().default(5),
|
|
21002
|
+
escalate_to: z19.string().optional().default("reviewer").describe("超上限后兜底 agent,默认 reviewer")
|
|
20721
21003
|
}).refine((d) => Boolean(d.test_cmd || d.lint_cmd), { message: "auto_feedback 必须至少配置 test_cmd 或 lint_cmd 之一" }).optional(),
|
|
20722
|
-
on_decision:
|
|
20723
|
-
APPROVE:
|
|
20724
|
-
|
|
20725
|
-
|
|
20726
|
-
|
|
21004
|
+
on_decision: z19.object({
|
|
21005
|
+
APPROVE: z19.union([
|
|
21006
|
+
z19.literal("continue"),
|
|
21007
|
+
z19.literal("abort"),
|
|
21008
|
+
z19.object({ action: z19.literal("goto"), target: z19.string().min(1) })
|
|
20727
21009
|
]).optional(),
|
|
20728
|
-
REQUEST_CHANGES:
|
|
20729
|
-
|
|
20730
|
-
|
|
20731
|
-
|
|
21010
|
+
REQUEST_CHANGES: z19.union([
|
|
21011
|
+
z19.literal("continue"),
|
|
21012
|
+
z19.literal("abort"),
|
|
21013
|
+
z19.object({ action: z19.literal("goto"), target: z19.string().min(1) })
|
|
20732
21014
|
]).optional(),
|
|
20733
|
-
BLOCK:
|
|
20734
|
-
|
|
20735
|
-
|
|
20736
|
-
|
|
21015
|
+
BLOCK: z19.union([
|
|
21016
|
+
z19.literal("continue"),
|
|
21017
|
+
z19.literal("abort"),
|
|
21018
|
+
z19.object({ action: z19.literal("goto"), target: z19.string().min(1) })
|
|
20737
21019
|
]).optional()
|
|
20738
21020
|
}).refine((d) => Boolean(d.APPROVE || d.REQUEST_CHANGES || d.BLOCK), { message: "on_decision 必须至少配置 APPROVE / REQUEST_CHANGES / BLOCK 之一" }).optional()
|
|
20739
21021
|
}).strict();
|
|
20740
|
-
var WorkflowSchema =
|
|
20741
|
-
name:
|
|
20742
|
-
description:
|
|
20743
|
-
version:
|
|
20744
|
-
trigger:
|
|
20745
|
-
context_template:
|
|
20746
|
-
max_loops:
|
|
20747
|
-
steps:
|
|
21022
|
+
var WorkflowSchema = z19.object({
|
|
21023
|
+
name: z19.string().min(1, "workflow.name 不能为空"),
|
|
21024
|
+
description: z19.string().optional().default(""),
|
|
21025
|
+
version: z19.string().optional().default("1.0.0"),
|
|
21026
|
+
trigger: z19.string().min(1).refine((v) => /^\/[a-z][\w-]*$/.test(v) || /^event:[a-z][\w.-]+$/.test(v), "trigger 必须是 /command-name 或 event:xxx 形式"),
|
|
21027
|
+
context_template: z19.string().optional(),
|
|
21028
|
+
max_loops: z19.number().int().min(1).max(10).optional().default(3),
|
|
21029
|
+
steps: z19.array(StepSchema).min(1, "workflow.steps 不能为空")
|
|
20748
21030
|
}).strict();
|
|
20749
21031
|
function parseWorkflowYaml(yaml, sourcePath = "<inline>") {
|
|
20750
21032
|
let raw;
|
|
@@ -20808,7 +21090,7 @@ async function loadWorkflowsFromDir(dir) {
|
|
|
20808
21090
|
continue;
|
|
20809
21091
|
if (!/\.ya?ml$/i.test(name))
|
|
20810
21092
|
continue;
|
|
20811
|
-
const full =
|
|
21093
|
+
const full = path28.join(dir, name);
|
|
20812
21094
|
const r = await loadWorkflowFromFile(full);
|
|
20813
21095
|
if (r.ok)
|
|
20814
21096
|
loaded.push(r);
|
|
@@ -21198,7 +21480,7 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
21198
21480
|
}
|
|
21199
21481
|
var workflowEngineServer = async (ctx) => {
|
|
21200
21482
|
const directory = ctx.directory ?? process.cwd();
|
|
21201
|
-
const workflowsDir =
|
|
21483
|
+
const workflowsDir = path29.join(directory, "workflows");
|
|
21202
21484
|
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME21}] preload workflows failed`, {
|
|
21203
21485
|
error: err instanceof Error ? err.message : String(err)
|
|
21204
21486
|
}));
|
|
@@ -21242,7 +21524,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
21242
21524
|
var handler21 = workflowEngineServer;
|
|
21243
21525
|
|
|
21244
21526
|
// plugins/session-worktree-guard.ts
|
|
21245
|
-
import
|
|
21527
|
+
import path30 from "node:path";
|
|
21246
21528
|
import { stat } from "node:fs/promises";
|
|
21247
21529
|
var PLUGIN_NAME22 = "session-worktree-guard";
|
|
21248
21530
|
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
@@ -21278,6 +21560,31 @@ function buildGitVcsWriteRegex(mainRoot) {
|
|
|
21278
21560
|
var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
|
|
21279
21561
|
var TOUCH_THROTTLE_MS = 5 * 60000;
|
|
21280
21562
|
var _touchCache = new Map;
|
|
21563
|
+
var REWRITE_NOTICE_CAP = 200;
|
|
21564
|
+
var _rewriteNotices = new Map;
|
|
21565
|
+
function recordRewriteNotice(callID, notice) {
|
|
21566
|
+
if (!_rewriteNotices.has(callID) && _rewriteNotices.size >= REWRITE_NOTICE_CAP) {
|
|
21567
|
+
const oldest = _rewriteNotices.keys().next().value;
|
|
21568
|
+
if (oldest !== undefined)
|
|
21569
|
+
_rewriteNotices.delete(oldest);
|
|
21570
|
+
}
|
|
21571
|
+
_rewriteNotices.set(callID, notice);
|
|
21572
|
+
}
|
|
21573
|
+
function consumeRewriteNotice(callID) {
|
|
21574
|
+
const notice = _rewriteNotices.get(callID);
|
|
21575
|
+
if (!notice)
|
|
21576
|
+
return;
|
|
21577
|
+
_rewriteNotices.delete(callID);
|
|
21578
|
+
return notice;
|
|
21579
|
+
}
|
|
21580
|
+
function formatRewriteWarning(notice) {
|
|
21581
|
+
const lines = notice.rewrites.map((r) => ` - ${r.field}: ${r.before} → ${r.after}`).join(`
|
|
21582
|
+
`);
|
|
21583
|
+
return `⚠️ [codeforge worktree 隔离] 本次 ${notice.tool} 的写入路径已被自动重定向到 session worktree,文件**不在主仓**:
|
|
21584
|
+
` + `${lines}
|
|
21585
|
+
` + `worktree=${notice.worktreePath}
|
|
21586
|
+
` + `如需进主仓,请走 /merge(review-fix-review 闭环)。直接读主仓路径看不到本次改动。`;
|
|
21587
|
+
}
|
|
21281
21588
|
var _sessionIdMissingWarned = false;
|
|
21282
21589
|
var _bindFailNotified = new Set;
|
|
21283
21590
|
function formatLazyBindDenyReason(input) {
|
|
@@ -21354,23 +21661,23 @@ var MERGE_CALLER_WHITELIST = new Set([
|
|
|
21354
21661
|
var FORCE_MERGE_CALLER_WHITELIST = new Set([
|
|
21355
21662
|
"codeforge"
|
|
21356
21663
|
]);
|
|
21357
|
-
var CODEFORGE_WORKTREE_DIR_NAME =
|
|
21664
|
+
var CODEFORGE_WORKTREE_DIR_NAME = path30.join(".git", "codeforge-worktrees");
|
|
21358
21665
|
function worktreesRoot(mainRoot) {
|
|
21359
|
-
return
|
|
21666
|
+
return path30.join(mainRoot, CODEFORGE_WORKTREE_DIR_NAME);
|
|
21360
21667
|
}
|
|
21361
21668
|
function isInsideAnyWorktreeDir(absPath, mainRoot) {
|
|
21362
|
-
if (!
|
|
21669
|
+
if (!path30.isAbsolute(absPath))
|
|
21363
21670
|
return false;
|
|
21364
21671
|
const root = worktreesRoot(mainRoot);
|
|
21365
21672
|
if (absPath === root)
|
|
21366
21673
|
return false;
|
|
21367
|
-
const prefix = root.endsWith(
|
|
21674
|
+
const prefix = root.endsWith(path30.sep) ? root : root + path30.sep;
|
|
21368
21675
|
return absPath.startsWith(prefix);
|
|
21369
21676
|
}
|
|
21370
21677
|
function rewritePath(value, mainRoot, worktreeRoot) {
|
|
21371
21678
|
if (!value)
|
|
21372
21679
|
return null;
|
|
21373
|
-
const resolved =
|
|
21680
|
+
const resolved = path30.isAbsolute(value) ? value : path30.resolve(mainRoot, value);
|
|
21374
21681
|
const wtPrefix2 = worktreeRoot.endsWith("/") ? worktreeRoot : worktreeRoot + "/";
|
|
21375
21682
|
if (resolved === worktreeRoot || resolved.startsWith(wtPrefix2)) {
|
|
21376
21683
|
return null;
|
|
@@ -21408,7 +21715,7 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
|
|
|
21408
21715
|
}
|
|
21409
21716
|
}
|
|
21410
21717
|
const wtRoot = worktreesRoot(mainRoot);
|
|
21411
|
-
const wtRootPrefix = wtRoot +
|
|
21718
|
+
const wtRootPrefix = wtRoot + path30.sep;
|
|
21412
21719
|
const escapedWtRootPrefix = escapeRegex(wtRootPrefix);
|
|
21413
21720
|
const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
|
|
21414
21721
|
const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
|
|
@@ -21463,8 +21770,8 @@ function collectWritePaths(toolName, argsObj, worktreeRoot) {
|
|
|
21463
21770
|
const candidate = toolName === "write" || toolName === "edit" ? argsObj["filePath"] : toolName === "ast_edit" ? argsObj["target"] : undefined;
|
|
21464
21771
|
if (typeof candidate !== "string" || candidate.length === 0)
|
|
21465
21772
|
return out;
|
|
21466
|
-
const abs =
|
|
21467
|
-
const rel =
|
|
21773
|
+
const abs = path30.isAbsolute(candidate) ? candidate : path30.resolve(worktreeRoot, candidate);
|
|
21774
|
+
const rel = path30.relative(worktreeRoot, abs).split(path30.sep).join("/");
|
|
21468
21775
|
out.push(rel);
|
|
21469
21776
|
return out;
|
|
21470
21777
|
}
|
|
@@ -21475,7 +21782,7 @@ async function isCodeforgeManagedProject(mainRoot, opts = {}) {
|
|
|
21475
21782
|
if (force === "1" || force === "true" || force === "yes")
|
|
21476
21783
|
return true;
|
|
21477
21784
|
try {
|
|
21478
|
-
const st = await stat(
|
|
21785
|
+
const st = await stat(path30.join(mainRoot, ".codeforge"));
|
|
21479
21786
|
return st.isDirectory();
|
|
21480
21787
|
} catch {
|
|
21481
21788
|
return false;
|
|
@@ -21489,6 +21796,37 @@ function resolveMainRoot2(rawDir) {
|
|
|
21489
21796
|
}
|
|
21490
21797
|
return rawDir;
|
|
21491
21798
|
}
|
|
21799
|
+
var STALE_MERGE_HEAD_MS = 24 * 60 * 60000;
|
|
21800
|
+
var _staleMergeHeadWarned = false;
|
|
21801
|
+
var _mergeBypassToastShown = false;
|
|
21802
|
+
async function isMainRepoMidMerge(mainRoot, opts = {}) {
|
|
21803
|
+
const gitDir = path30.join(mainRoot, ".git");
|
|
21804
|
+
const markers = [
|
|
21805
|
+
path30.join(gitDir, "MERGE_HEAD"),
|
|
21806
|
+
path30.join(gitDir, "rebase-merge"),
|
|
21807
|
+
path30.join(gitDir, "CHERRY_PICK_HEAD")
|
|
21808
|
+
];
|
|
21809
|
+
let freshest = -1;
|
|
21810
|
+
for (const m of markers) {
|
|
21811
|
+
try {
|
|
21812
|
+
const st = await stat(m);
|
|
21813
|
+
if (st.mtimeMs > freshest)
|
|
21814
|
+
freshest = st.mtimeMs;
|
|
21815
|
+
} catch {}
|
|
21816
|
+
}
|
|
21817
|
+
if (freshest < 0)
|
|
21818
|
+
return false;
|
|
21819
|
+
const now = opts.now ?? Date.now();
|
|
21820
|
+
const staleMs = opts.staleMs ?? STALE_MERGE_HEAD_MS;
|
|
21821
|
+
if (now - freshest > staleMs) {
|
|
21822
|
+
if (!_staleMergeHeadWarned) {
|
|
21823
|
+
_staleMergeHeadWarned = true;
|
|
21824
|
+
opts.log?.warn(`[guard] 检测到陈旧 merge marker(mtime 超过 ${staleMs / 3600000}h),` + `不启用 merge 冲突 bypass;若确在解冲突请 git merge --continue/--abort 刷新状态`, { mainRoot, freshestMtimeMs: freshest });
|
|
21825
|
+
}
|
|
21826
|
+
return false;
|
|
21827
|
+
}
|
|
21828
|
+
return true;
|
|
21829
|
+
}
|
|
21492
21830
|
var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
21493
21831
|
const disableEnv = process.env["CODEFORGE_DISABLE_WORKTREE_GUARD"];
|
|
21494
21832
|
if (disableEnv === "1" || disableEnv === "true" || disableEnv === "yes") {
|
|
@@ -21553,6 +21891,29 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21553
21891
|
await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
|
|
21554
21892
|
const toolName = input.tool;
|
|
21555
21893
|
const argsObj = output.args ?? {};
|
|
21894
|
+
const midMerge = isWriteOperation(toolName, argsObj, mainRoot) || toolName === "bash" ? await isMainRepoMidMerge(mainRoot, { log: log13 }) : false;
|
|
21895
|
+
const emitMergeBypassNotice = (subClass, detail) => {
|
|
21896
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
21897
|
+
hook: "tool.execute.before",
|
|
21898
|
+
tool: toolName,
|
|
21899
|
+
sessionID: input.sessionID,
|
|
21900
|
+
action: `merge-conflict-bypass-${subClass}`,
|
|
21901
|
+
...detail
|
|
21902
|
+
});
|
|
21903
|
+
log13.warn(`[merge-conflict-bypass] ${subClass} 放行主仓直写`, {
|
|
21904
|
+
tool: toolName,
|
|
21905
|
+
...detail
|
|
21906
|
+
});
|
|
21907
|
+
if (!_mergeBypassToastShown) {
|
|
21908
|
+
_mergeBypassToastShown = true;
|
|
21909
|
+
showToast2(ctx.client, {
|
|
21910
|
+
message: "⚠️ 主仓处于 merge 冲突态,session-worktree-guard 已临时放行主仓直写(解冲突期)",
|
|
21911
|
+
variant: "warning",
|
|
21912
|
+
duration: 8000,
|
|
21913
|
+
title: "CodeForge"
|
|
21914
|
+
}, log13).catch(() => {});
|
|
21915
|
+
}
|
|
21916
|
+
};
|
|
21556
21917
|
let entry = null;
|
|
21557
21918
|
try {
|
|
21558
21919
|
entry = await getSessionWorktree(sessionId, mainRoot);
|
|
@@ -21596,6 +21957,12 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21596
21957
|
if (!isWriteOperation(toolName, argsObj, mainRoot)) {
|
|
21597
21958
|
return;
|
|
21598
21959
|
}
|
|
21960
|
+
if (midMerge) {
|
|
21961
|
+
emitMergeBypassNotice("lazy-bind", {
|
|
21962
|
+
reason: "mid-merge: skip lazy-bind, passthrough to main repo"
|
|
21963
|
+
});
|
|
21964
|
+
return;
|
|
21965
|
+
}
|
|
21599
21966
|
try {
|
|
21600
21967
|
const parentId = lookupParentSessionId2(sessionId);
|
|
21601
21968
|
if (parentId) {
|
|
@@ -21612,6 +21979,18 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21612
21979
|
parent_branch: parentEntry.branch,
|
|
21613
21980
|
parent_worktree: parentEntry.worktreePath
|
|
21614
21981
|
});
|
|
21982
|
+
} else if (!parentEntry) {
|
|
21983
|
+
entry = await bindSessionWorktree({ sessionId: parentId, mainRoot });
|
|
21984
|
+
log13.info(`[child-inherit-bind-root] session ${sessionId} 触发父 ${parentId} 的 worktree lazy-bind`, { parentSessionId: parentId, branch: entry.branch, worktreePath: entry.worktreePath });
|
|
21985
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
21986
|
+
hook: "tool.execute.before",
|
|
21987
|
+
tool: toolName,
|
|
21988
|
+
sessionID: input.sessionID,
|
|
21989
|
+
action: "child-inherit-bind-root",
|
|
21990
|
+
parent_session_id: parentId,
|
|
21991
|
+
branch: entry.branch,
|
|
21992
|
+
worktreePath: entry.worktreePath
|
|
21993
|
+
});
|
|
21615
21994
|
}
|
|
21616
21995
|
}
|
|
21617
21996
|
} catch (lookupErr) {
|
|
@@ -21841,6 +22220,11 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21841
22220
|
if (toolName === "bash") {
|
|
21842
22221
|
const command = argsObj["command"];
|
|
21843
22222
|
if (typeof command === "string" && commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePath) && detectBashWriteIntent(command, mainRoot)) {
|
|
22223
|
+
if (midMerge) {
|
|
22224
|
+
const snippet = command.length > 60 ? command.slice(0, 60) + "…" : command;
|
|
22225
|
+
emitMergeBypassNotice("classB", { command: snippet });
|
|
22226
|
+
return;
|
|
22227
|
+
}
|
|
21844
22228
|
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
|
|
21845
22229
|
if (caller !== null && CLASS_B_CALLER_WHITELIST.has(caller)) {
|
|
21846
22230
|
log13.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
|
|
@@ -21872,182 +22256,85 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21872
22256
|
}
|
|
21873
22257
|
}
|
|
21874
22258
|
}
|
|
22259
|
+
const rewritesThisCall = [];
|
|
22260
|
+
const applyRewrite = (field, value) => {
|
|
22261
|
+
const newPath = rewritePath(value, mainRoot, worktreePath);
|
|
22262
|
+
if (newPath === null)
|
|
22263
|
+
return;
|
|
22264
|
+
if (midMerge) {
|
|
22265
|
+
emitMergeBypassNotice("classA", { field, before: value, after: newPath });
|
|
22266
|
+
return;
|
|
22267
|
+
}
|
|
22268
|
+
log13.info(`rewrote ${toolName}.${field}: ${value} → ${newPath}`);
|
|
22269
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
22270
|
+
hook: "tool.execute.before",
|
|
22271
|
+
tool: toolName,
|
|
22272
|
+
field,
|
|
22273
|
+
before: value,
|
|
22274
|
+
after: newPath,
|
|
22275
|
+
action: "rewrite"
|
|
22276
|
+
});
|
|
22277
|
+
output.args[field] = newPath;
|
|
22278
|
+
rewritesThisCall.push({ field, before: value, after: newPath });
|
|
22279
|
+
};
|
|
21875
22280
|
if (toolName === "write" || toolName === "edit") {
|
|
21876
22281
|
const filePath = argsObj["filePath"];
|
|
21877
|
-
if (typeof filePath === "string")
|
|
21878
|
-
|
|
21879
|
-
if (newPath !== null) {
|
|
21880
|
-
log13.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
|
|
21881
|
-
safeWriteLog(PLUGIN_NAME22, {
|
|
21882
|
-
hook: "tool.execute.before",
|
|
21883
|
-
tool: toolName,
|
|
21884
|
-
field: "filePath",
|
|
21885
|
-
before: filePath,
|
|
21886
|
-
after: newPath,
|
|
21887
|
-
action: "rewrite"
|
|
21888
|
-
});
|
|
21889
|
-
output.args["filePath"] = newPath;
|
|
21890
|
-
}
|
|
21891
|
-
}
|
|
22282
|
+
if (typeof filePath === "string")
|
|
22283
|
+
applyRewrite("filePath", filePath);
|
|
21892
22284
|
}
|
|
21893
22285
|
if (toolName === "ast_edit") {
|
|
21894
22286
|
const target = argsObj["target"];
|
|
21895
|
-
if (typeof target === "string")
|
|
21896
|
-
|
|
21897
|
-
if (newTarget !== null) {
|
|
21898
|
-
log13.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
|
|
21899
|
-
safeWriteLog(PLUGIN_NAME22, {
|
|
21900
|
-
hook: "tool.execute.before",
|
|
21901
|
-
tool: toolName,
|
|
21902
|
-
field: "target",
|
|
21903
|
-
before: target,
|
|
21904
|
-
after: newTarget,
|
|
21905
|
-
action: "rewrite"
|
|
21906
|
-
});
|
|
21907
|
-
output.args["target"] = newTarget;
|
|
21908
|
-
}
|
|
21909
|
-
}
|
|
22287
|
+
if (typeof target === "string")
|
|
22288
|
+
applyRewrite("target", target);
|
|
21910
22289
|
const root = argsObj["root"];
|
|
21911
|
-
if (typeof root === "string")
|
|
21912
|
-
|
|
21913
|
-
if (newRoot !== null) {
|
|
21914
|
-
log13.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
|
|
21915
|
-
safeWriteLog(PLUGIN_NAME22, {
|
|
21916
|
-
hook: "tool.execute.before",
|
|
21917
|
-
tool: toolName,
|
|
21918
|
-
field: "root",
|
|
21919
|
-
before: root,
|
|
21920
|
-
after: newRoot,
|
|
21921
|
-
action: "rewrite"
|
|
21922
|
-
});
|
|
21923
|
-
output.args["root"] = newRoot;
|
|
21924
|
-
}
|
|
21925
|
-
}
|
|
22290
|
+
if (typeof root === "string")
|
|
22291
|
+
applyRewrite("root", root);
|
|
21926
22292
|
}
|
|
21927
22293
|
if (toolName === "bash") {
|
|
21928
22294
|
const workdir = argsObj["workdir"];
|
|
21929
|
-
if (typeof workdir === "string")
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
action: "rewrite"
|
|
21940
|
-
});
|
|
21941
|
-
output.args["workdir"] = newWorkdir;
|
|
21942
|
-
}
|
|
21943
|
-
}
|
|
22295
|
+
if (typeof workdir === "string")
|
|
22296
|
+
applyRewrite("workdir", workdir);
|
|
22297
|
+
}
|
|
22298
|
+
const _afterCallID = input.callID;
|
|
22299
|
+
if (_afterCallID && rewritesThisCall.length > 0) {
|
|
22300
|
+
recordRewriteNotice(_afterCallID, {
|
|
22301
|
+
tool: toolName,
|
|
22302
|
+
rewrites: rewritesThisCall,
|
|
22303
|
+
worktreePath
|
|
22304
|
+
});
|
|
21944
22305
|
}
|
|
21945
22306
|
});
|
|
21946
22307
|
if (denied)
|
|
21947
22308
|
throw denied;
|
|
22309
|
+
},
|
|
22310
|
+
"tool.execute.after": async (input, output) => {
|
|
22311
|
+
await safeAsync(PLUGIN_NAME22, "tool.execute.after", async () => {
|
|
22312
|
+
const callID = input.callID;
|
|
22313
|
+
if (!callID)
|
|
22314
|
+
return;
|
|
22315
|
+
const notice = consumeRewriteNotice(callID);
|
|
22316
|
+
if (!notice)
|
|
22317
|
+
return;
|
|
22318
|
+
const warning = formatRewriteWarning(notice);
|
|
22319
|
+
const prev = typeof output.output === "string" ? output.output : "";
|
|
22320
|
+
output.output = warning + `
|
|
22321
|
+
|
|
22322
|
+
` + prev;
|
|
22323
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
22324
|
+
hook: "tool.execute.after",
|
|
22325
|
+
tool: notice.tool,
|
|
22326
|
+
sessionID: input.sessionID,
|
|
22327
|
+
action: "inject-rewrite-notice",
|
|
22328
|
+
rewrites: notice.rewrites.length
|
|
22329
|
+
});
|
|
22330
|
+
});
|
|
21948
22331
|
}
|
|
21949
22332
|
};
|
|
21950
22333
|
};
|
|
21951
22334
|
var handler22 = sessionWorktreeGuardPlugin;
|
|
21952
22335
|
|
|
21953
|
-
// lib/opencode-session-probe.ts
|
|
21954
|
-
import * as path30 from "node:path";
|
|
21955
|
-
import * as os6 from "node:os";
|
|
21956
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
21957
|
-
var requireFromHere = createRequire2(import.meta.url);
|
|
21958
|
-
var DEFAULT_LIVENESS_MS = 6 * 60 * 60000;
|
|
21959
|
-
var DEFAULT_DB_PATH = path30.join(os6.homedir(), ".local/share/opencode/opencode.db");
|
|
21960
|
-
function createSessionProbe(opts = {}) {
|
|
21961
|
-
const dbPath = opts.dbPath ?? DEFAULT_DB_PATH;
|
|
21962
|
-
const httpBaseUrl = opts.httpBaseUrl ?? process.env["OPENCODE_SERVER_URL"];
|
|
21963
|
-
const livenessWindowMs = opts.livenessWindowMs ?? DEFAULT_LIVENESS_MS;
|
|
21964
|
-
const timeoutMs = opts.timeoutMs ?? 1500;
|
|
21965
|
-
let db = null;
|
|
21966
|
-
let dbInitTried = false;
|
|
21967
|
-
let dbStmt = null;
|
|
21968
|
-
async function tryOpenDb() {
|
|
21969
|
-
if (dbInitTried)
|
|
21970
|
-
return db != null;
|
|
21971
|
-
dbInitTried = true;
|
|
21972
|
-
try {
|
|
21973
|
-
const mod = requireFromHere("node:sqlite");
|
|
21974
|
-
try {
|
|
21975
|
-
db = new mod.DatabaseSync(dbPath, { readOnly: true });
|
|
21976
|
-
} catch {
|
|
21977
|
-
db = null;
|
|
21978
|
-
dbStmt = null;
|
|
21979
|
-
return false;
|
|
21980
|
-
}
|
|
21981
|
-
dbStmt = db.prepare("SELECT time_updated, time_archived FROM session WHERE id = ? LIMIT 1");
|
|
21982
|
-
return true;
|
|
21983
|
-
} catch {
|
|
21984
|
-
db = null;
|
|
21985
|
-
dbStmt = null;
|
|
21986
|
-
return false;
|
|
21987
|
-
}
|
|
21988
|
-
}
|
|
21989
|
-
async function probeHttp(sessionId) {
|
|
21990
|
-
if (!httpBaseUrl)
|
|
21991
|
-
return null;
|
|
21992
|
-
try {
|
|
21993
|
-
const ctrl = new AbortController;
|
|
21994
|
-
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
21995
|
-
const res = await fetch(`${httpBaseUrl.replace(/\/$/, "")}/session`, {
|
|
21996
|
-
signal: ctrl.signal
|
|
21997
|
-
}).finally(() => clearTimeout(t));
|
|
21998
|
-
if (!res.ok)
|
|
21999
|
-
return null;
|
|
22000
|
-
const list = await res.json();
|
|
22001
|
-
const hit = Array.isArray(list) && list.some((s) => s.id === sessionId);
|
|
22002
|
-
return { alive: hit, source: "http" };
|
|
22003
|
-
} catch {
|
|
22004
|
-
return null;
|
|
22005
|
-
}
|
|
22006
|
-
}
|
|
22007
|
-
async function probeSqlite(sessionId) {
|
|
22008
|
-
if (!await tryOpenDb() || !dbStmt)
|
|
22009
|
-
return null;
|
|
22010
|
-
try {
|
|
22011
|
-
const row = dbStmt.get(sessionId);
|
|
22012
|
-
if (!row) {
|
|
22013
|
-
return { alive: false, source: "sqlite" };
|
|
22014
|
-
}
|
|
22015
|
-
if (row.time_archived != null) {
|
|
22016
|
-
return {
|
|
22017
|
-
alive: false,
|
|
22018
|
-
source: "sqlite",
|
|
22019
|
-
time_archived: row.time_archived
|
|
22020
|
-
};
|
|
22021
|
-
}
|
|
22022
|
-
const now = Date.now();
|
|
22023
|
-
const tu = Number(row.time_updated) || 0;
|
|
22024
|
-
const alive = now - tu < livenessWindowMs;
|
|
22025
|
-
return { alive, source: "sqlite", time_updated: tu, time_archived: null };
|
|
22026
|
-
} catch {
|
|
22027
|
-
return null;
|
|
22028
|
-
}
|
|
22029
|
-
}
|
|
22030
|
-
return {
|
|
22031
|
-
async isSessionAlive(sessionId) {
|
|
22032
|
-
const http = await probeHttp(sessionId);
|
|
22033
|
-
if (http)
|
|
22034
|
-
return http;
|
|
22035
|
-
const sql = await probeSqlite(sessionId);
|
|
22036
|
-
if (sql)
|
|
22037
|
-
return sql;
|
|
22038
|
-
return { alive: true, source: "unknown" };
|
|
22039
|
-
},
|
|
22040
|
-
close() {
|
|
22041
|
-
try {
|
|
22042
|
-
db?.close?.();
|
|
22043
|
-
} catch {}
|
|
22044
|
-
db = null;
|
|
22045
|
-
dbStmt = null;
|
|
22046
|
-
}
|
|
22047
|
-
};
|
|
22048
|
-
}
|
|
22049
|
-
|
|
22050
22336
|
// plugins/worktree-lifecycle.ts
|
|
22337
|
+
init_worktree_ops();
|
|
22051
22338
|
var PLUGIN_NAME23 = "worktree-lifecycle";
|
|
22052
22339
|
logLifecycle(PLUGIN_NAME23, "import", {});
|
|
22053
22340
|
var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
|
|
@@ -22204,10 +22491,17 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
22204
22491
|
worktreePath: entry.worktreePath
|
|
22205
22492
|
});
|
|
22206
22493
|
} catch (err) {
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22210
|
-
|
|
22494
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
22495
|
+
if (isWorktreeAbsentError(err) || isBranchAbsentError(err)) {
|
|
22496
|
+
if (process.env["CODEFORGE_DEBUG"]) {
|
|
22497
|
+
console.debug(`[worktree-lifecycle] discardSession absent-class silenced: ${errMsg}`);
|
|
22498
|
+
}
|
|
22499
|
+
} else {
|
|
22500
|
+
log14.warn(`[lifecycle] discardSession failed`, {
|
|
22501
|
+
sessionId: ended.sessionID,
|
|
22502
|
+
error: errMsg
|
|
22503
|
+
});
|
|
22504
|
+
}
|
|
22211
22505
|
}
|
|
22212
22506
|
lastIdleToastAt.delete(ended.sessionID);
|
|
22213
22507
|
return;
|