@andyqiu/codeforge 0.6.8 → 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 +689 -489
- 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
|
});
|
|
@@ -12373,7 +12379,13 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
12373
12379
|
const r = await pruneDiscardedRegistryEntries(resolved);
|
|
12374
12380
|
discardedPruned = r.pruned;
|
|
12375
12381
|
} catch {}
|
|
12376
|
-
|
|
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 };
|
|
12377
12389
|
}
|
|
12378
12390
|
|
|
12379
12391
|
// lib/merge-gate.ts
|
|
@@ -13740,6 +13752,159 @@ async function execute15(input) {
|
|
|
13740
13752
|
};
|
|
13741
13753
|
}
|
|
13742
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
|
+
}
|
|
13743
13908
|
// lib/opencode-runner.ts
|
|
13744
13909
|
function makeOpencodeRunner(opts) {
|
|
13745
13910
|
const log4 = opts.log ?? (() => {});
|
|
@@ -14001,18 +14166,18 @@ init_decision_parser();
|
|
|
14001
14166
|
|
|
14002
14167
|
// lib/parent-map-store.ts
|
|
14003
14168
|
import { promises as fs14 } from "node:fs";
|
|
14004
|
-
import * as
|
|
14169
|
+
import * as path19 from "node:path";
|
|
14005
14170
|
var PARENT_MAP_VERSION = 1;
|
|
14006
14171
|
var PARENT_MAP_LOCK_TIMEOUT_MS = 2000;
|
|
14007
14172
|
var PARENT_MAP_MAX_ENTRIES = 256;
|
|
14008
14173
|
function parentMapDir(mainRoot) {
|
|
14009
|
-
return
|
|
14174
|
+
return path19.join(runtimeDir(path19.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
14010
14175
|
}
|
|
14011
14176
|
function parentMapPath(mainRoot) {
|
|
14012
|
-
return
|
|
14177
|
+
return path19.join(parentMapDir(mainRoot), "parent-map.json");
|
|
14013
14178
|
}
|
|
14014
14179
|
function parentMapLockPath(mainRoot) {
|
|
14015
|
-
return
|
|
14180
|
+
return path19.join(parentMapDir(mainRoot), "parent-map.lock");
|
|
14016
14181
|
}
|
|
14017
14182
|
async function readParentMapFile(mainRoot) {
|
|
14018
14183
|
const file = parentMapPath(mainRoot);
|
|
@@ -14032,7 +14197,7 @@ async function readParentMapFile(mainRoot) {
|
|
|
14032
14197
|
}
|
|
14033
14198
|
async function writeParentMapFile(mainRoot, payload) {
|
|
14034
14199
|
const file = parentMapPath(mainRoot);
|
|
14035
|
-
await fs14.mkdir(
|
|
14200
|
+
await fs14.mkdir(path19.dirname(file), { recursive: true });
|
|
14036
14201
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
14037
14202
|
await fs14.writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
14038
14203
|
await fs14.rename(tmp, file);
|
|
@@ -14067,7 +14232,7 @@ async function loadParentMap(mainRoot) {
|
|
|
14067
14232
|
}
|
|
14068
14233
|
async function mutateParentMap(mainRoot, mutator, opts = {}) {
|
|
14069
14234
|
const lockPath = parentMapLockPath(mainRoot);
|
|
14070
|
-
await fs14.mkdir(
|
|
14235
|
+
await fs14.mkdir(path19.dirname(lockPath), { recursive: true });
|
|
14071
14236
|
const lockOpts = {
|
|
14072
14237
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
14073
14238
|
...opts
|
|
@@ -14086,7 +14251,7 @@ async function appendParentEntry(mainRoot, childID, parentID, ts, opts = {}) {
|
|
|
14086
14251
|
if (!childID || !parentID)
|
|
14087
14252
|
return;
|
|
14088
14253
|
const lockPath = parentMapLockPath(mainRoot);
|
|
14089
|
-
await fs14.mkdir(
|
|
14254
|
+
await fs14.mkdir(path19.dirname(lockPath), { recursive: true });
|
|
14090
14255
|
const lockOpts = {
|
|
14091
14256
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
14092
14257
|
...opts
|
|
@@ -14420,7 +14585,7 @@ function clip3(s, max) {
|
|
|
14420
14585
|
|
|
14421
14586
|
// lib/codeforge-runtime.ts
|
|
14422
14587
|
import { promises as fs15 } from "node:fs";
|
|
14423
|
-
import * as
|
|
14588
|
+
import * as path20 from "node:path";
|
|
14424
14589
|
var DEFAULT_RUNTIME = {
|
|
14425
14590
|
autonomy: {
|
|
14426
14591
|
downgrade_on_risky: true
|
|
@@ -14463,7 +14628,7 @@ function loadRuntimeSync(opts = {}) {
|
|
|
14463
14628
|
}
|
|
14464
14629
|
async function loadRuntime(opts = {}) {
|
|
14465
14630
|
const root = opts.root ?? process.cwd();
|
|
14466
|
-
const abs =
|
|
14631
|
+
const abs = path20.resolve(root, opts.file ?? CONFIG_FILE);
|
|
14467
14632
|
let raw;
|
|
14468
14633
|
try {
|
|
14469
14634
|
raw = await fs15.readFile(abs, "utf8");
|
|
@@ -14747,7 +14912,7 @@ var toolHeartbeatServer = async (ctx) => {
|
|
|
14747
14912
|
var handler6 = toolHeartbeatServer;
|
|
14748
14913
|
|
|
14749
14914
|
// plugins/codeforge-tools.ts
|
|
14750
|
-
var
|
|
14915
|
+
var z17 = tool.schema;
|
|
14751
14916
|
var PLUGIN_NAME7 = "codeforge-tools";
|
|
14752
14917
|
logLifecycle(PLUGIN_NAME7, "import");
|
|
14753
14918
|
function wrap(output, metadata) {
|
|
@@ -14794,7 +14959,7 @@ function buildBrowserTools() {
|
|
|
14794
14959
|
browser_navigate: tool({
|
|
14795
14960
|
description: description5,
|
|
14796
14961
|
args: {
|
|
14797
|
-
url:
|
|
14962
|
+
url: z17.string().min(1).describe("要打开的 URL;必须是 http(s)")
|
|
14798
14963
|
},
|
|
14799
14964
|
async execute(args) {
|
|
14800
14965
|
return await runSafe("browser_navigate", async () => {
|
|
@@ -14809,7 +14974,7 @@ function buildBrowserTools() {
|
|
|
14809
14974
|
browser_click: tool({
|
|
14810
14975
|
description: description6,
|
|
14811
14976
|
args: {
|
|
14812
|
-
selector:
|
|
14977
|
+
selector: z17.string().min(1).describe("CSS / Playwright locator,必须能唯一定位")
|
|
14813
14978
|
},
|
|
14814
14979
|
async execute(args) {
|
|
14815
14980
|
return await runSafe("browser_click", async () => {
|
|
@@ -14824,8 +14989,8 @@ function buildBrowserTools() {
|
|
|
14824
14989
|
browser_fill: tool({
|
|
14825
14990
|
description: description7,
|
|
14826
14991
|
args: {
|
|
14827
|
-
selector:
|
|
14828
|
-
value:
|
|
14992
|
+
selector: z17.string().min(1).describe("CSS / Playwright locator"),
|
|
14993
|
+
value: z17.string().describe("要填入的文本;原样写入不转义")
|
|
14829
14994
|
},
|
|
14830
14995
|
async execute(args) {
|
|
14831
14996
|
return await runSafe("browser_fill", async () => {
|
|
@@ -14840,8 +15005,8 @@ function buildBrowserTools() {
|
|
|
14840
15005
|
browser_screenshot: tool({
|
|
14841
15006
|
description: description8,
|
|
14842
15007
|
args: {
|
|
14843
|
-
fullPage:
|
|
14844
|
-
selector:
|
|
15008
|
+
fullPage: z17.boolean().optional().describe("是否截全长页面(默认仅可视区)"),
|
|
15009
|
+
selector: z17.string().optional().describe("CSS 选择器;指定时只截该元素")
|
|
14845
15010
|
},
|
|
14846
15011
|
async execute(args) {
|
|
14847
15012
|
return await runSafe("browser_screenshot", async () => {
|
|
@@ -14856,8 +15021,8 @@ function buildBrowserTools() {
|
|
|
14856
15021
|
browser_console: tool({
|
|
14857
15022
|
description: description9,
|
|
14858
15023
|
args: {
|
|
14859
|
-
level:
|
|
14860
|
-
sinceTs:
|
|
15024
|
+
level: z17.enum(["log", "info", "warn", "error", "debug"]).optional().describe("过滤级别"),
|
|
15025
|
+
sinceTs: z17.number().optional().describe("timestamp >= sinceTs 的条目")
|
|
14861
15026
|
},
|
|
14862
15027
|
async execute(args) {
|
|
14863
15028
|
return await runSafe("browser_console", async () => {
|
|
@@ -14872,8 +15037,8 @@ function buildBrowserTools() {
|
|
|
14872
15037
|
browser_network: tool({
|
|
14873
15038
|
description: description10,
|
|
14874
15039
|
args: {
|
|
14875
|
-
method:
|
|
14876
|
-
sinceTs:
|
|
15040
|
+
method: z17.string().optional().describe("HTTP 方法过滤(GET/POST/...)"),
|
|
15041
|
+
sinceTs: z17.number().optional().describe("start_ts >= sinceTs 的请求")
|
|
14877
15042
|
},
|
|
14878
15043
|
async execute(args) {
|
|
14879
15044
|
return await runSafe("browser_network", async () => {
|
|
@@ -14896,7 +15061,8 @@ var CORE_TOOL_NAMES = [
|
|
|
14896
15061
|
"session_merge",
|
|
14897
15062
|
"plan_write",
|
|
14898
15063
|
"plan_read",
|
|
14899
|
-
"adr_init"
|
|
15064
|
+
"adr_init",
|
|
15065
|
+
"worktrees_gc"
|
|
14900
15066
|
];
|
|
14901
15067
|
var BROWSER_TOOL_NAMES = [
|
|
14902
15068
|
"browser_navigate",
|
|
@@ -14933,31 +15099,32 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14933
15099
|
spawner,
|
|
14934
15100
|
resolveCurrentSessionId: () => process.env["CODEFORGE_SESSION_ID"] ?? ""
|
|
14935
15101
|
});
|
|
15102
|
+
__setContext3({ mainRoot: ctx.directory ?? process.cwd() });
|
|
14936
15103
|
const browserTools = browserEnabled ? buildBrowserTools() : {};
|
|
14937
15104
|
return {
|
|
14938
15105
|
tool: {
|
|
14939
15106
|
ast_edit: tool({
|
|
14940
15107
|
description,
|
|
14941
15108
|
args: {
|
|
14942
|
-
action:
|
|
15109
|
+
action: z17.enum([
|
|
14943
15110
|
"replace_anchor",
|
|
14944
15111
|
"insert_after_anchor",
|
|
14945
15112
|
"insert_before_anchor",
|
|
14946
15113
|
"delete_range",
|
|
14947
15114
|
"rename_symbol"
|
|
14948
15115
|
]).describe("编辑类型;不同 action 需要的字段不同"),
|
|
14949
|
-
target:
|
|
14950
|
-
before_hash:
|
|
14951
|
-
auto_stage:
|
|
14952
|
-
description:
|
|
14953
|
-
anchor:
|
|
14954
|
-
regex:
|
|
14955
|
-
occurrence:
|
|
14956
|
-
payload:
|
|
14957
|
-
start:
|
|
14958
|
-
end:
|
|
14959
|
-
old_name:
|
|
14960
|
-
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 用:新标识符")
|
|
14961
15128
|
},
|
|
14962
15129
|
async execute(args) {
|
|
14963
15130
|
return await runSafeTracked("ast_edit", async () => {
|
|
@@ -14980,10 +15147,10 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14980
15147
|
repo_map: tool({
|
|
14981
15148
|
description: description2,
|
|
14982
15149
|
args: {
|
|
14983
|
-
root:
|
|
14984
|
-
top:
|
|
14985
|
-
focus:
|
|
14986
|
-
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")
|
|
14987
15154
|
},
|
|
14988
15155
|
async execute(args) {
|
|
14989
15156
|
return await runSafeTracked("repo_map", async () => {
|
|
@@ -15004,14 +15171,14 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15004
15171
|
rules_debug: tool({
|
|
15005
15172
|
description: description3,
|
|
15006
15173
|
args: {
|
|
15007
|
-
current_agent:
|
|
15008
|
-
root:
|
|
15009
|
-
home_dir:
|
|
15010
|
-
project_dir:
|
|
15011
|
-
skip_personal:
|
|
15012
|
-
skip_project:
|
|
15013
|
-
skip_agent:
|
|
15014
|
-
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 摘要")
|
|
15015
15182
|
},
|
|
15016
15183
|
async execute(args) {
|
|
15017
15184
|
return await runSafe("rules_debug", async () => {
|
|
@@ -15026,14 +15193,14 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15026
15193
|
review_approval: tool({
|
|
15027
15194
|
description: description4,
|
|
15028
15195
|
args: {
|
|
15029
|
-
verdict:
|
|
15030
|
-
pendingIds:
|
|
15031
|
-
notes:
|
|
15032
|
-
decisionLine:
|
|
15033
|
-
source:
|
|
15034
|
-
reviewerAgent:
|
|
15035
|
-
sessionId:
|
|
15036
|
-
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(审计用,可选)")
|
|
15037
15204
|
},
|
|
15038
15205
|
async execute(args) {
|
|
15039
15206
|
return await runSafe("review_approval", async () => {
|
|
@@ -15054,10 +15221,10 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15054
15221
|
model_chain: tool({
|
|
15055
15222
|
description: description11,
|
|
15056
15223
|
args: {
|
|
15057
|
-
agent:
|
|
15058
|
-
current:
|
|
15059
|
-
root:
|
|
15060
|
-
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")
|
|
15061
15228
|
},
|
|
15062
15229
|
async execute(args) {
|
|
15063
15230
|
return await runSafe("model_chain", async () => {
|
|
@@ -15078,11 +15245,11 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15078
15245
|
session_merge: tool({
|
|
15079
15246
|
description: description12,
|
|
15080
15247
|
args: {
|
|
15081
|
-
action:
|
|
15082
|
-
session_id:
|
|
15083
|
-
plan_id:
|
|
15084
|
-
force:
|
|
15085
|
-
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)")
|
|
15086
15253
|
},
|
|
15087
15254
|
async execute(args, input) {
|
|
15088
15255
|
return await runSafe("session_merge", async () => {
|
|
@@ -15118,9 +15285,9 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15118
15285
|
plan_write: tool({
|
|
15119
15286
|
description: description13,
|
|
15120
15287
|
args: {
|
|
15121
|
-
title:
|
|
15122
|
-
content:
|
|
15123
|
-
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"]')
|
|
15124
15291
|
},
|
|
15125
15292
|
async execute(args) {
|
|
15126
15293
|
return await runSafeTracked("plan_write", async () => {
|
|
@@ -15139,8 +15306,8 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15139
15306
|
plan_read: tool({
|
|
15140
15307
|
description: description14,
|
|
15141
15308
|
args: {
|
|
15142
|
-
plan_id:
|
|
15143
|
-
path:
|
|
15309
|
+
plan_id: z17.string().optional().describe("方案 ID(推荐),格式 plan-YYYYMMDD-HHmmss-NNN"),
|
|
15310
|
+
path: z17.string().optional().describe("方案绝对路径(兜底用,没有 plan_id 元数据时退而求其次)")
|
|
15144
15311
|
},
|
|
15145
15312
|
async execute(args) {
|
|
15146
15313
|
return await runSafe("plan_read", async () => {
|
|
@@ -15163,11 +15330,11 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15163
15330
|
adr_init: tool({
|
|
15164
15331
|
description: description15,
|
|
15165
15332
|
args: {
|
|
15166
|
-
cwd:
|
|
15167
|
-
force:
|
|
15168
|
-
dryRun:
|
|
15169
|
-
writePrepare:
|
|
15170
|
-
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")
|
|
15171
15338
|
},
|
|
15172
15339
|
async execute(args) {
|
|
15173
15340
|
return await runSafe("adr_init", async () => {
|
|
@@ -15179,6 +15346,27 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
15179
15346
|
return wrap(result, { title: "adr_init" });
|
|
15180
15347
|
});
|
|
15181
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
|
+
}
|
|
15182
15370
|
})
|
|
15183
15371
|
}
|
|
15184
15372
|
};
|
|
@@ -15187,10 +15375,10 @@ var handler7 = codeforgeToolsServer;
|
|
|
15187
15375
|
|
|
15188
15376
|
// plugins/discover-spec-suggest.ts
|
|
15189
15377
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "node:fs";
|
|
15190
|
-
import { join as
|
|
15378
|
+
import { join as join17 } from "node:path";
|
|
15191
15379
|
|
|
15192
15380
|
// lib/handoff-schema.ts
|
|
15193
|
-
import { z as
|
|
15381
|
+
import { z as z18 } from "zod";
|
|
15194
15382
|
|
|
15195
15383
|
// node_modules/yaml/dist/index.js
|
|
15196
15384
|
var composer = require_composer();
|
|
@@ -15243,92 +15431,92 @@ var MAX_HANDOFF_SIZE = 100 * 1024;
|
|
|
15243
15431
|
var SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,49}$/;
|
|
15244
15432
|
var SCORE_DIMENSIONS = ["functional", "ux", "technical", "constraints", "edge_cases"];
|
|
15245
15433
|
var COMBO_VALUES = ["A", "B", "C", "D"];
|
|
15246
|
-
var NeedSchema =
|
|
15247
|
-
id:
|
|
15248
|
-
type:
|
|
15249
|
-
statement:
|
|
15250
|
-
rationale:
|
|
15251
|
-
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()
|
|
15252
15440
|
});
|
|
15253
|
-
var BoundarySchema =
|
|
15254
|
-
excluded:
|
|
15255
|
-
reason:
|
|
15441
|
+
var BoundarySchema = z18.object({
|
|
15442
|
+
excluded: z18.string().min(1),
|
|
15443
|
+
reason: z18.string().min(1)
|
|
15256
15444
|
});
|
|
15257
|
-
var AssumptionSchema =
|
|
15258
|
-
statement:
|
|
15259
|
-
confidence:
|
|
15260
|
-
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()
|
|
15261
15449
|
});
|
|
15262
|
-
var OpenIssueSchema =
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
id:
|
|
15266
|
-
question:
|
|
15267
|
-
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()
|
|
15268
15456
|
})
|
|
15269
15457
|
]);
|
|
15270
|
-
var RedFlagsSchema =
|
|
15271
|
-
raised:
|
|
15272
|
-
combos:
|
|
15273
|
-
reasons:
|
|
15274
|
-
user_persisted_rounds:
|
|
15275
|
-
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()
|
|
15276
15464
|
}).superRefine((rf, ctx) => {
|
|
15277
15465
|
if (rf.raised) {
|
|
15278
15466
|
if (rf.combos.length === 0) {
|
|
15279
15467
|
ctx.addIssue({
|
|
15280
|
-
code:
|
|
15468
|
+
code: z18.ZodIssueCode.custom,
|
|
15281
15469
|
message: "red_flags.raised=true 时 combos 必须 >=1",
|
|
15282
15470
|
path: ["combos"]
|
|
15283
15471
|
});
|
|
15284
15472
|
}
|
|
15285
15473
|
if (rf.reasons.length === 0) {
|
|
15286
15474
|
ctx.addIssue({
|
|
15287
|
-
code:
|
|
15475
|
+
code: z18.ZodIssueCode.custom,
|
|
15288
15476
|
message: "red_flags.raised=true 时 reasons 必须 >=1",
|
|
15289
15477
|
path: ["reasons"]
|
|
15290
15478
|
});
|
|
15291
15479
|
}
|
|
15292
15480
|
}
|
|
15293
15481
|
});
|
|
15294
|
-
var ScoresSchema =
|
|
15295
|
-
var PreCodingBlockerSchema =
|
|
15296
|
-
id:
|
|
15297
|
-
blocker:
|
|
15298
|
-
source:
|
|
15299
|
-
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"])
|
|
15300
15488
|
});
|
|
15301
|
-
var KhRefSchema =
|
|
15302
|
-
kh_id:
|
|
15303
|
-
title:
|
|
15304
|
-
relevance:
|
|
15489
|
+
var KhRefSchema = z18.object({
|
|
15490
|
+
kh_id: z18.string(),
|
|
15491
|
+
title: z18.string(),
|
|
15492
|
+
relevance: z18.enum(["positive", "negative", "neutral"]).optional()
|
|
15305
15493
|
});
|
|
15306
|
-
var HandoffSchema =
|
|
15307
|
-
schema_version:
|
|
15308
|
-
slug:
|
|
15309
|
-
title:
|
|
15310
|
-
created_at:
|
|
15311
|
-
discover_session_id:
|
|
15312
|
-
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),
|
|
15313
15501
|
scores: ScoresSchema,
|
|
15314
|
-
needs:
|
|
15315
|
-
boundaries:
|
|
15316
|
-
assumptions:
|
|
15317
|
-
open_issues:
|
|
15318
|
-
rejected_alternatives:
|
|
15319
|
-
acceptance_criteria:
|
|
15320
|
-
id:
|
|
15321
|
-
description:
|
|
15322
|
-
measurable:
|
|
15323
|
-
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()
|
|
15324
15512
|
})).default([]),
|
|
15325
15513
|
red_flags: RedFlagsSchema,
|
|
15326
|
-
pre_coding_blockers:
|
|
15327
|
-
kh_references:
|
|
15328
|
-
adr_refs:
|
|
15329
|
-
related_artifacts:
|
|
15330
|
-
prd:
|
|
15331
|
-
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()
|
|
15332
15520
|
}).optional()
|
|
15333
15521
|
});
|
|
15334
15522
|
function validateHandoff(rawYaml, fileSize) {
|
|
@@ -15354,9 +15542,9 @@ function validateHandoff(rawYaml, fileSize) {
|
|
|
15354
15542
|
const result = HandoffSchema.safeParse(parsed);
|
|
15355
15543
|
if (!result.success) {
|
|
15356
15544
|
const first = result.error.issues[0];
|
|
15357
|
-
const
|
|
15545
|
+
const path21 = first?.path?.join(".") ?? "(root)";
|
|
15358
15546
|
const msg = first?.message ?? "unknown";
|
|
15359
|
-
return { ok: false, reason: `schema 校验失败:${
|
|
15547
|
+
return { ok: false, reason: `schema 校验失败:${path21}: ${msg}` };
|
|
15360
15548
|
}
|
|
15361
15549
|
return { ok: true, data: result.data, schemaVersion: result.data.schema_version };
|
|
15362
15550
|
}
|
|
@@ -15373,7 +15561,7 @@ var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
|
15373
15561
|
var MATCH_THRESHOLD = 0.15;
|
|
15374
15562
|
var MAX_CANDIDATES = 3;
|
|
15375
15563
|
var NUDGE_MAX_LEN = 1500;
|
|
15376
|
-
var SPECS_REL_DIR =
|
|
15564
|
+
var SPECS_REL_DIR = join17("docs", "specs");
|
|
15377
15565
|
var sessionMap = new Map;
|
|
15378
15566
|
function pruneIfOversize2() {
|
|
15379
15567
|
while (sessionMap.size > SESSION_CAP2) {
|
|
@@ -15480,7 +15668,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15480
15668
|
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
15481
15669
|
const statReader = opts.statReader ?? defaultStatReader;
|
|
15482
15670
|
const log6 = makePluginLogger(PLUGIN_NAME8);
|
|
15483
|
-
const specsRoot =
|
|
15671
|
+
const specsRoot = join17(rootDir, SPECS_REL_DIR);
|
|
15484
15672
|
const records = [];
|
|
15485
15673
|
if (!dirExists(specsRoot)) {
|
|
15486
15674
|
log6.info(`specs 目录不存在,plugin 将 no-op`, { specsRoot });
|
|
@@ -15501,7 +15689,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15501
15689
|
log6.info(`跳过非合法 slug 命名的条目`, { entry });
|
|
15502
15690
|
continue;
|
|
15503
15691
|
}
|
|
15504
|
-
const specDir =
|
|
15692
|
+
const specDir = join17(specsRoot, entry);
|
|
15505
15693
|
let dirStat;
|
|
15506
15694
|
try {
|
|
15507
15695
|
dirStat = statReader(specDir);
|
|
@@ -15514,7 +15702,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15514
15702
|
}
|
|
15515
15703
|
if (!dirStat.isDirectory)
|
|
15516
15704
|
continue;
|
|
15517
|
-
const handoffPath =
|
|
15705
|
+
const handoffPath = join17(specDir, "handoff.yaml");
|
|
15518
15706
|
let fileStat;
|
|
15519
15707
|
try {
|
|
15520
15708
|
fileStat = statReader(handoffPath);
|
|
@@ -15687,13 +15875,13 @@ var handler8 = discoverSpecSuggestServer;
|
|
|
15687
15875
|
|
|
15688
15876
|
// lib/memories.ts
|
|
15689
15877
|
import { promises as fs16 } from "node:fs";
|
|
15690
|
-
import * as
|
|
15691
|
-
import * as
|
|
15878
|
+
import * as path21 from "node:path";
|
|
15879
|
+
import * as os6 from "node:os";
|
|
15692
15880
|
function resolveConfig(c) {
|
|
15693
15881
|
return {
|
|
15694
15882
|
projectRoot: c.projectRoot,
|
|
15695
|
-
homeDir: c.homeDir ??
|
|
15696
|
-
projectName: c.projectName ??
|
|
15883
|
+
homeDir: c.homeDir ?? os6.homedir(),
|
|
15884
|
+
projectName: c.projectName ?? path21.basename(c.projectRoot),
|
|
15697
15885
|
now: c.now ?? Date.now,
|
|
15698
15886
|
log: c.log ?? (() => {}),
|
|
15699
15887
|
maxPerScope: c.maxPerScope ?? 1000
|
|
@@ -15701,9 +15889,9 @@ function resolveConfig(c) {
|
|
|
15701
15889
|
}
|
|
15702
15890
|
function fileFor(scope, cfg) {
|
|
15703
15891
|
if (scope === "project") {
|
|
15704
|
-
return
|
|
15892
|
+
return path21.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
15705
15893
|
}
|
|
15706
|
-
return
|
|
15894
|
+
return path21.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
15707
15895
|
}
|
|
15708
15896
|
async function readBank(p) {
|
|
15709
15897
|
try {
|
|
@@ -15717,7 +15905,7 @@ async function readBank(p) {
|
|
|
15717
15905
|
}
|
|
15718
15906
|
}
|
|
15719
15907
|
async function writeBank(p, items) {
|
|
15720
|
-
await fs16.mkdir(
|
|
15908
|
+
await fs16.mkdir(path21.dirname(p), { recursive: true });
|
|
15721
15909
|
const tmp = `${p}.tmp`;
|
|
15722
15910
|
await fs16.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
15723
15911
|
await fs16.rename(tmp, p);
|
|
@@ -16238,7 +16426,7 @@ var handler10 = modelFallbackServer;
|
|
|
16238
16426
|
|
|
16239
16427
|
// plugins/subtask-heartbeat.ts
|
|
16240
16428
|
import { promises as fsPromises } from "node:fs";
|
|
16241
|
-
import * as
|
|
16429
|
+
import * as path22 from "node:path";
|
|
16242
16430
|
var recordSessionParent2 = recordSessionParent;
|
|
16243
16431
|
var lookupParentSessionId2 = lookupParentSessionId;
|
|
16244
16432
|
var deleteSessionParent2 = deleteSessionParent;
|
|
@@ -16362,11 +16550,11 @@ function extractTaskArgs(args) {
|
|
|
16362
16550
|
const a = args;
|
|
16363
16551
|
const rawDesc = typeof a["description"] === "string" ? a["description"] : null;
|
|
16364
16552
|
const rawPrompt = typeof a["prompt"] === "string" ? a["prompt"] : null;
|
|
16365
|
-
const
|
|
16553
|
+
const description17 = rawDesc ?? (rawPrompt ? rawPrompt.slice(0, 60) : null);
|
|
16366
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;
|
|
16367
|
-
if (!
|
|
16555
|
+
if (!description17 && !subagentType)
|
|
16368
16556
|
return null;
|
|
16369
|
-
return { description:
|
|
16557
|
+
return { description: description17, subagentType };
|
|
16370
16558
|
}
|
|
16371
16559
|
function enqueuePendingTask(parentID, entry, now = Date.now()) {
|
|
16372
16560
|
const ts = entry.ts ?? now;
|
|
@@ -16585,7 +16773,7 @@ function buildErrorLogLine(errorReason, elapsedMs, now = Date.now()) {
|
|
|
16585
16773
|
}
|
|
16586
16774
|
async function appendSubagentLog(filePath, line, log7) {
|
|
16587
16775
|
try {
|
|
16588
|
-
await fsPromises.mkdir(
|
|
16776
|
+
await fsPromises.mkdir(path22.dirname(filePath), { recursive: true });
|
|
16589
16777
|
await fsPromises.appendFile(filePath, line + `
|
|
16590
16778
|
`, "utf8");
|
|
16591
16779
|
} catch (err) {
|
|
@@ -16953,8 +17141,8 @@ var handler12 = parallelStatusServer;
|
|
|
16953
17141
|
|
|
16954
17142
|
// plugins/parallel-tool-nudge.ts
|
|
16955
17143
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
|
|
16956
|
-
import { join as
|
|
16957
|
-
import { homedir as
|
|
17144
|
+
import { join as join19 } from "node:path";
|
|
17145
|
+
import { homedir as homedir7 } from "node:os";
|
|
16958
17146
|
var PLUGIN_NAME13 = "parallel-tool-nudge";
|
|
16959
17147
|
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
16960
17148
|
var PARALLEL_SAFE_TOOLS = [
|
|
@@ -17006,10 +17194,10 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
17006
17194
|
const reader = opts.reader ?? defaultReader2;
|
|
17007
17195
|
const dirReader = opts.dirReader ?? defaultDirReader2;
|
|
17008
17196
|
const dirExists = opts.dirExists ?? defaultDirExists2;
|
|
17009
|
-
const homeAgentsDir = opts.homeAgentsDir ??
|
|
17197
|
+
const homeAgentsDir = opts.homeAgentsDir ?? join19(homedir7(), ".config", "opencode", "agents");
|
|
17010
17198
|
const candidateDirs = [
|
|
17011
|
-
|
|
17012
|
-
|
|
17199
|
+
join19(rootDir, ".codeforge", "agents"),
|
|
17200
|
+
join19(rootDir, "agents"),
|
|
17013
17201
|
homeAgentsDir
|
|
17014
17202
|
];
|
|
17015
17203
|
const result = new Map;
|
|
@@ -17032,20 +17220,20 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
17032
17220
|
for (const entry of entries) {
|
|
17033
17221
|
if (!entry.endsWith(".md"))
|
|
17034
17222
|
continue;
|
|
17035
|
-
const
|
|
17223
|
+
const path23 = join19(dir, entry);
|
|
17036
17224
|
let content;
|
|
17037
17225
|
try {
|
|
17038
|
-
content = reader(
|
|
17226
|
+
content = reader(path23);
|
|
17039
17227
|
} catch (err) {
|
|
17040
17228
|
log8.warn(`agent.md 读取失败(已跳过)`, {
|
|
17041
|
-
path:
|
|
17229
|
+
path: path23,
|
|
17042
17230
|
error: err instanceof Error ? err.message : String(err)
|
|
17043
17231
|
});
|
|
17044
17232
|
continue;
|
|
17045
17233
|
}
|
|
17046
17234
|
const parsed = parseAgentFrontmatter(content);
|
|
17047
17235
|
if (!parsed) {
|
|
17048
|
-
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path:
|
|
17236
|
+
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path: path23 });
|
|
17049
17237
|
continue;
|
|
17050
17238
|
}
|
|
17051
17239
|
if (result.has(parsed.name))
|
|
@@ -17205,7 +17393,7 @@ function prependUtf8Prelude(command) {
|
|
|
17205
17393
|
return command;
|
|
17206
17394
|
return PRELUDE + command;
|
|
17207
17395
|
}
|
|
17208
|
-
var handler14 = async (
|
|
17396
|
+
var handler14 = async (_ctx4) => {
|
|
17209
17397
|
const enabled = process.platform === "win32" && process.env.CODEFORGE_DISABLE_PWSH_UTF8 !== "1";
|
|
17210
17398
|
const reason = enabled ? "win32" : process.platform !== "win32" ? "non-win32" : "disabled-by-env";
|
|
17211
17399
|
logLifecycle(PLUGIN_NAME14, "activate", { enabled, platform: process.platform, reason });
|
|
@@ -17237,7 +17425,7 @@ var handler14 = async (_ctx3) => {
|
|
|
17237
17425
|
|
|
17238
17426
|
// lib/event-stream.ts
|
|
17239
17427
|
import { promises as fs17 } from "node:fs";
|
|
17240
|
-
import * as
|
|
17428
|
+
import * as path23 from "node:path";
|
|
17241
17429
|
async function loadSession(id, opts = {}) {
|
|
17242
17430
|
const file = resolveSessionFile(id, opts);
|
|
17243
17431
|
const raw = await fs17.readFile(file, "utf8");
|
|
@@ -17257,7 +17445,7 @@ async function listSessions(opts = {}) {
|
|
|
17257
17445
|
for (const e of entries) {
|
|
17258
17446
|
if (!e.isFile() || !e.name.endsWith(".jsonl"))
|
|
17259
17447
|
continue;
|
|
17260
|
-
const file =
|
|
17448
|
+
const file = path23.join(dir, e.name);
|
|
17261
17449
|
const id = e.name.replace(/\.jsonl$/, "");
|
|
17262
17450
|
try {
|
|
17263
17451
|
const stat = await fs17.stat(file);
|
|
@@ -17284,11 +17472,11 @@ async function listSessions(opts = {}) {
|
|
|
17284
17472
|
return out;
|
|
17285
17473
|
}
|
|
17286
17474
|
function resolveDir(opts = {}) {
|
|
17287
|
-
const root =
|
|
17288
|
-
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");
|
|
17289
17477
|
}
|
|
17290
17478
|
function resolveSessionFile(id, opts = {}) {
|
|
17291
|
-
return
|
|
17479
|
+
return path23.join(resolveDir(opts), `${id}.jsonl`);
|
|
17292
17480
|
}
|
|
17293
17481
|
function parseJsonl(id, raw) {
|
|
17294
17482
|
const events = [];
|
|
@@ -17552,10 +17740,10 @@ function isRecoveryWorthShowing(plan) {
|
|
|
17552
17740
|
|
|
17553
17741
|
// lib/block-pending.ts
|
|
17554
17742
|
import { promises as fs18 } from "node:fs";
|
|
17555
|
-
import * as
|
|
17743
|
+
import * as path24 from "node:path";
|
|
17556
17744
|
function blockPendingFilePath(absRoot) {
|
|
17557
17745
|
const rd = runtimeDir(absRoot, { ensure: false });
|
|
17558
|
-
return
|
|
17746
|
+
return path24.join(rd, "sessions", "autonomous-blocks.ndjson");
|
|
17559
17747
|
}
|
|
17560
17748
|
function consumeLockPath(absRoot) {
|
|
17561
17749
|
return blockPendingFilePath(absRoot) + ".consume.lock";
|
|
@@ -17620,7 +17808,7 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
17620
17808
|
if (entries.length === 0)
|
|
17621
17809
|
return;
|
|
17622
17810
|
const file = blockPendingFilePath(absRoot);
|
|
17623
|
-
await fs18.mkdir(
|
|
17811
|
+
await fs18.mkdir(path24.dirname(file), { recursive: true });
|
|
17624
17812
|
const now = new Date().toISOString();
|
|
17625
17813
|
const lines = entries.map((e) => ({
|
|
17626
17814
|
type: "consume",
|
|
@@ -17770,7 +17958,7 @@ var handler15 = sessionRecoveryServer;
|
|
|
17770
17958
|
|
|
17771
17959
|
// plugins/subtasks.ts
|
|
17772
17960
|
import { promises as fs19 } from "node:fs";
|
|
17773
|
-
import * as
|
|
17961
|
+
import * as path25 from "node:path";
|
|
17774
17962
|
|
|
17775
17963
|
// lib/parallel-merge.ts
|
|
17776
17964
|
init_worktree_ops();
|
|
@@ -18385,7 +18573,7 @@ function buildSystemPrompt(maxSubtasks) {
|
|
|
18385
18573
|
].join(`
|
|
18386
18574
|
`);
|
|
18387
18575
|
}
|
|
18388
|
-
async function decomposeTask(
|
|
18576
|
+
async function decomposeTask(description17, opts) {
|
|
18389
18577
|
const log10 = opts.log ?? (() => {});
|
|
18390
18578
|
if (opts.mockResponse) {
|
|
18391
18579
|
return validateAndFinalize(opts.mockResponse, undefined, log10, opts.maxSubtasks ?? DEFAULT_MAX_SUBTASKS);
|
|
@@ -18395,7 +18583,7 @@ async function decomposeTask(description16, opts) {
|
|
|
18395
18583
|
let childSessionId;
|
|
18396
18584
|
try {
|
|
18397
18585
|
const created = await opts.client.session.create({
|
|
18398
|
-
body: { title: `decompose:${clip5(
|
|
18586
|
+
body: { title: `decompose:${clip5(description17, 60)}` },
|
|
18399
18587
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
18400
18588
|
});
|
|
18401
18589
|
if (created.error || !created.data?.id) {
|
|
@@ -18412,7 +18600,7 @@ async function decomposeTask(description16, opts) {
|
|
|
18412
18600
|
path: { id: childSessionId },
|
|
18413
18601
|
body: {
|
|
18414
18602
|
system: systemText,
|
|
18415
|
-
parts: [{ type: "text", text:
|
|
18603
|
+
parts: [{ type: "text", text: description17 }]
|
|
18416
18604
|
},
|
|
18417
18605
|
query: opts.directory ? { directory: opts.directory } : undefined
|
|
18418
18606
|
}));
|
|
@@ -18594,7 +18782,7 @@ function sleep2(ms) {
|
|
|
18594
18782
|
// plugins/subtasks.ts
|
|
18595
18783
|
var PLUGIN_NAME16 = "subtasks";
|
|
18596
18784
|
function getLogFile(root = process.cwd()) {
|
|
18597
|
-
return
|
|
18785
|
+
return path25.join(runtimeDir(root), "logs", "subtasks.log");
|
|
18598
18786
|
}
|
|
18599
18787
|
var VERB_RE = /^([a-zA-Z]{3,12})/;
|
|
18600
18788
|
var CN_VERBS = [
|
|
@@ -18899,7 +19087,7 @@ async function writeLog(level, msg, data) {
|
|
|
18899
19087
|
`;
|
|
18900
19088
|
try {
|
|
18901
19089
|
const logFile = getLogFile();
|
|
18902
|
-
await fs19.mkdir(
|
|
19090
|
+
await fs19.mkdir(path25.dirname(logFile), { recursive: true });
|
|
18903
19091
|
await fs19.appendFile(logFile, line, "utf8");
|
|
18904
19092
|
} catch {}
|
|
18905
19093
|
}
|
|
@@ -18916,8 +19104,8 @@ var subtasksServer = async (ctx) => {
|
|
|
18916
19104
|
try {
|
|
18917
19105
|
if (input?.command !== "parallel")
|
|
18918
19106
|
return;
|
|
18919
|
-
const
|
|
18920
|
-
if (!
|
|
19107
|
+
const description17 = (input.arguments ?? "").trim();
|
|
19108
|
+
if (!description17) {
|
|
18921
19109
|
return;
|
|
18922
19110
|
}
|
|
18923
19111
|
let autoMerge = false;
|
|
@@ -18956,7 +19144,7 @@ var subtasksServer = async (ctx) => {
|
|
|
18956
19144
|
} : undefined;
|
|
18957
19145
|
const replyLines = [];
|
|
18958
19146
|
const messageCtx = {
|
|
18959
|
-
content: `/parallel ${
|
|
19147
|
+
content: `/parallel ${description17}`,
|
|
18960
19148
|
reply: (s) => {
|
|
18961
19149
|
replyLines.push(s);
|
|
18962
19150
|
return Promise.resolve();
|
|
@@ -19532,7 +19720,7 @@ var handler18 = tokenManagerServer;
|
|
|
19532
19720
|
|
|
19533
19721
|
// plugins/tool-policy.ts
|
|
19534
19722
|
import { promises as fs20 } from "node:fs";
|
|
19535
|
-
import * as
|
|
19723
|
+
import * as path27 from "node:path";
|
|
19536
19724
|
|
|
19537
19725
|
// lib/tool-risk.ts
|
|
19538
19726
|
var RISK_PATTERNS = [
|
|
@@ -19686,7 +19874,7 @@ function buildHaystackFor(args, matchOn) {
|
|
|
19686
19874
|
}
|
|
19687
19875
|
|
|
19688
19876
|
// lib/file-regex-acl.ts
|
|
19689
|
-
import * as
|
|
19877
|
+
import * as path26 from "node:path";
|
|
19690
19878
|
function compileRule(r) {
|
|
19691
19879
|
if (r instanceof RegExp)
|
|
19692
19880
|
return r;
|
|
@@ -19752,7 +19940,7 @@ function normalizePath2(p) {
|
|
|
19752
19940
|
let s = p.replace(/\\/g, "/");
|
|
19753
19941
|
if (s.startsWith("./"))
|
|
19754
19942
|
s = s.slice(2);
|
|
19755
|
-
s =
|
|
19943
|
+
s = path26.posix.normalize(s);
|
|
19756
19944
|
return s;
|
|
19757
19945
|
}
|
|
19758
19946
|
function checkFileAccess(acl, file, op) {
|
|
@@ -19855,9 +20043,9 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
|
19855
20043
|
const action = risks.length > 0 || worstAcl === "deny" ? "deny" : "allow";
|
|
19856
20044
|
return { action, reasons, risks, acl: aclResults };
|
|
19857
20045
|
}
|
|
19858
|
-
var POLICY_PATH =
|
|
20046
|
+
var POLICY_PATH = path27.join(".codeforge", "policy.json");
|
|
19859
20047
|
async function loadPolicy(root = process.cwd()) {
|
|
19860
|
-
const file =
|
|
20048
|
+
const file = path27.join(root, POLICY_PATH);
|
|
19861
20049
|
try {
|
|
19862
20050
|
const raw = await fs20.readFile(file, "utf8");
|
|
19863
20051
|
const data = JSON.parse(raw);
|
|
@@ -19956,8 +20144,8 @@ var handler19 = toolPolicyServer;
|
|
|
19956
20144
|
|
|
19957
20145
|
// plugins/update-checker.ts
|
|
19958
20146
|
import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
19959
|
-
import { homedir as
|
|
19960
|
-
import { join as
|
|
20147
|
+
import { homedir as homedir9 } from "node:os";
|
|
20148
|
+
import { join as join25 } from "node:path";
|
|
19961
20149
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
19962
20150
|
|
|
19963
20151
|
// lib/update-checker-impl.ts
|
|
@@ -19974,8 +20162,8 @@ import {
|
|
|
19974
20162
|
unlinkSync,
|
|
19975
20163
|
writeFileSync as writeFileSync2
|
|
19976
20164
|
} from "node:fs";
|
|
19977
|
-
import { homedir as
|
|
19978
|
-
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";
|
|
19979
20167
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19980
20168
|
import * as https from "node:https";
|
|
19981
20169
|
import * as zlib from "node:zlib";
|
|
@@ -19983,7 +20171,7 @@ import * as zlib from "node:zlib";
|
|
|
19983
20171
|
// lib/version-injected.ts
|
|
19984
20172
|
function getInjectedVersion() {
|
|
19985
20173
|
try {
|
|
19986
|
-
const v = "0.6.
|
|
20174
|
+
const v = "0.6.12";
|
|
19987
20175
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
19988
20176
|
return v;
|
|
19989
20177
|
}
|
|
@@ -20073,17 +20261,17 @@ function readLocalVersion() {
|
|
|
20073
20261
|
try {
|
|
20074
20262
|
const here = fileURLToPath2(import.meta.url);
|
|
20075
20263
|
const root = dirname14(dirname14(here));
|
|
20076
|
-
const pkg = JSON.parse(readFileSync5(
|
|
20264
|
+
const pkg = JSON.parse(readFileSync5(join24(root, "package.json"), "utf8"));
|
|
20077
20265
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
20078
20266
|
} catch {
|
|
20079
20267
|
return "0.0.0";
|
|
20080
20268
|
}
|
|
20081
20269
|
}
|
|
20082
20270
|
function defaultCacheDir() {
|
|
20083
|
-
return process.env["CODEFORGE_CACHE_DIR"] ??
|
|
20271
|
+
return process.env["CODEFORGE_CACHE_DIR"] ?? join24(homedir8(), ".cache", "codeforge");
|
|
20084
20272
|
}
|
|
20085
20273
|
function defaultCacheFile() {
|
|
20086
|
-
return
|
|
20274
|
+
return join24(defaultCacheDir(), "update-check.json");
|
|
20087
20275
|
}
|
|
20088
20276
|
function readCache(file) {
|
|
20089
20277
|
try {
|
|
@@ -20239,14 +20427,14 @@ function defaultHttpFetcher(url2, timeoutMs) {
|
|
|
20239
20427
|
});
|
|
20240
20428
|
}
|
|
20241
20429
|
async function downloadAndExtractBundle(opts) {
|
|
20242
|
-
const tmpRoot = opts.tmpDir ?? mkdtempSync(
|
|
20430
|
+
const tmpRoot = opts.tmpDir ?? mkdtempSync(join24(tmpdir(), "codeforge-update-"));
|
|
20243
20431
|
mkdirSync3(tmpRoot, { recursive: true });
|
|
20244
20432
|
const fetcher = opts.tarballFetcher ?? defaultBinaryFetcher;
|
|
20245
20433
|
const tarballBuf = await fetcher(opts.tarballUrl);
|
|
20246
20434
|
verifyIntegrity(tarballBuf, opts.expectedIntegrity);
|
|
20247
20435
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
20248
20436
|
extractTarToDir(tarBuf, tmpRoot);
|
|
20249
|
-
const bundlePath =
|
|
20437
|
+
const bundlePath = join24(tmpRoot, "package", "dist", "index.js");
|
|
20250
20438
|
if (!existsSync5(bundlePath)) {
|
|
20251
20439
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
20252
20440
|
}
|
|
@@ -20286,11 +20474,11 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
20286
20474
|
offset += 512;
|
|
20287
20475
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
20288
20476
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
20289
|
-
const dest =
|
|
20477
|
+
const dest = join24(destRoot, fullName);
|
|
20290
20478
|
mkdirSync3(dirname14(dest), { recursive: true });
|
|
20291
20479
|
writeFileSync2(dest, fileBuf);
|
|
20292
20480
|
} else if (typeFlag === "5") {
|
|
20293
|
-
mkdirSync3(
|
|
20481
|
+
mkdirSync3(join24(destRoot, fullName), { recursive: true });
|
|
20294
20482
|
}
|
|
20295
20483
|
offset += Math.ceil(size / 512) * 512;
|
|
20296
20484
|
}
|
|
@@ -20399,7 +20587,7 @@ function cleanupOldBackups(target, keep) {
|
|
|
20399
20587
|
const base = target.substring(dir.length + 1);
|
|
20400
20588
|
const prefix = `${base}.bak.`;
|
|
20401
20589
|
const all = readdirSync3(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
20402
|
-
const full =
|
|
20590
|
+
const full = join24(dir, f);
|
|
20403
20591
|
let mtimeMs = 0;
|
|
20404
20592
|
try {
|
|
20405
20593
|
mtimeMs = statSync4(full).mtimeMs;
|
|
@@ -20421,7 +20609,7 @@ function loadCompatibility(opts) {
|
|
|
20421
20609
|
const root = opts?.cwd ?? inferPluginRoot();
|
|
20422
20610
|
if (!root)
|
|
20423
20611
|
return null;
|
|
20424
|
-
file =
|
|
20612
|
+
file = join24(root, "compatibility.json");
|
|
20425
20613
|
}
|
|
20426
20614
|
if (!existsSync5(file))
|
|
20427
20615
|
return null;
|
|
@@ -20612,11 +20800,11 @@ var updateCheckerServer = async (ctx) => {
|
|
|
20612
20800
|
expectedIntegrity: npmResult.integrity
|
|
20613
20801
|
});
|
|
20614
20802
|
try {
|
|
20615
|
-
const installMjs =
|
|
20803
|
+
const installMjs = join25(extractDir, "package", "install.mjs");
|
|
20616
20804
|
if (existsSync6(installMjs)) {
|
|
20617
20805
|
const nodeBin = resolveNodeBin();
|
|
20618
20806
|
const r = spawnSync2(nodeBin, [installMjs, "--global", "--skip-build"], {
|
|
20619
|
-
cwd:
|
|
20807
|
+
cwd: join25(extractDir, "package"),
|
|
20620
20808
|
stdio: "pipe",
|
|
20621
20809
|
encoding: "utf8"
|
|
20622
20810
|
});
|
|
@@ -20717,14 +20905,14 @@ function detectOpencodeVersion() {
|
|
|
20717
20905
|
}
|
|
20718
20906
|
function getOpencodeBundlePath() {
|
|
20719
20907
|
const candidates = [];
|
|
20720
|
-
candidates.push(
|
|
20908
|
+
candidates.push(join25(homedir9(), ".config", "opencode", "codeforge", "index.js"));
|
|
20721
20909
|
if (process.platform === "win32") {
|
|
20722
20910
|
const appData = process.env["APPDATA"];
|
|
20723
20911
|
if (appData)
|
|
20724
|
-
candidates.push(
|
|
20912
|
+
candidates.push(join25(appData, "opencode", "codeforge", "index.js"));
|
|
20725
20913
|
const localAppData = process.env["LOCALAPPDATA"];
|
|
20726
20914
|
if (localAppData)
|
|
20727
|
-
candidates.push(
|
|
20915
|
+
candidates.push(join25(localAppData, "opencode", "codeforge", "index.js"));
|
|
20728
20916
|
}
|
|
20729
20917
|
for (const c of candidates) {
|
|
20730
20918
|
if (existsSync6(c))
|
|
@@ -20785,60 +20973,60 @@ async function postToast(ctx, message) {
|
|
|
20785
20973
|
var handler20 = updateCheckerServer;
|
|
20786
20974
|
|
|
20787
20975
|
// plugins/workflow-engine.ts
|
|
20788
|
-
import * as
|
|
20976
|
+
import * as path29 from "node:path";
|
|
20789
20977
|
|
|
20790
20978
|
// lib/workflow-loader.ts
|
|
20791
20979
|
import { promises as fs21 } from "node:fs";
|
|
20792
|
-
import * as
|
|
20793
|
-
import { z as
|
|
20794
|
-
var ActionSchema =
|
|
20795
|
-
tool:
|
|
20796
|
-
args:
|
|
20797
|
-
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()
|
|
20798
20986
|
});
|
|
20799
|
-
var StepSchema =
|
|
20800
|
-
name:
|
|
20801
|
-
agent:
|
|
20802
|
-
description:
|
|
20803
|
-
inject_context:
|
|
20804
|
-
requires_human_approval:
|
|
20805
|
-
actions:
|
|
20806
|
-
on_error:
|
|
20807
|
-
max_retries:
|
|
20808
|
-
timeout:
|
|
20809
|
-
auto_feedback:
|
|
20810
|
-
test_cmd:
|
|
20811
|
-
lint_cmd:
|
|
20812
|
-
max_retries:
|
|
20813
|
-
error_excerpt_lines:
|
|
20814
|
-
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")
|
|
20815
21003
|
}).refine((d) => Boolean(d.test_cmd || d.lint_cmd), { message: "auto_feedback 必须至少配置 test_cmd 或 lint_cmd 之一" }).optional(),
|
|
20816
|
-
on_decision:
|
|
20817
|
-
APPROVE:
|
|
20818
|
-
|
|
20819
|
-
|
|
20820
|
-
|
|
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) })
|
|
20821
21009
|
]).optional(),
|
|
20822
|
-
REQUEST_CHANGES:
|
|
20823
|
-
|
|
20824
|
-
|
|
20825
|
-
|
|
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) })
|
|
20826
21014
|
]).optional(),
|
|
20827
|
-
BLOCK:
|
|
20828
|
-
|
|
20829
|
-
|
|
20830
|
-
|
|
21015
|
+
BLOCK: z19.union([
|
|
21016
|
+
z19.literal("continue"),
|
|
21017
|
+
z19.literal("abort"),
|
|
21018
|
+
z19.object({ action: z19.literal("goto"), target: z19.string().min(1) })
|
|
20831
21019
|
]).optional()
|
|
20832
21020
|
}).refine((d) => Boolean(d.APPROVE || d.REQUEST_CHANGES || d.BLOCK), { message: "on_decision 必须至少配置 APPROVE / REQUEST_CHANGES / BLOCK 之一" }).optional()
|
|
20833
21021
|
}).strict();
|
|
20834
|
-
var WorkflowSchema =
|
|
20835
|
-
name:
|
|
20836
|
-
description:
|
|
20837
|
-
version:
|
|
20838
|
-
trigger:
|
|
20839
|
-
context_template:
|
|
20840
|
-
max_loops:
|
|
20841
|
-
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 不能为空")
|
|
20842
21030
|
}).strict();
|
|
20843
21031
|
function parseWorkflowYaml(yaml, sourcePath = "<inline>") {
|
|
20844
21032
|
let raw;
|
|
@@ -20902,7 +21090,7 @@ async function loadWorkflowsFromDir(dir) {
|
|
|
20902
21090
|
continue;
|
|
20903
21091
|
if (!/\.ya?ml$/i.test(name))
|
|
20904
21092
|
continue;
|
|
20905
|
-
const full =
|
|
21093
|
+
const full = path28.join(dir, name);
|
|
20906
21094
|
const r = await loadWorkflowFromFile(full);
|
|
20907
21095
|
if (r.ok)
|
|
20908
21096
|
loaded.push(r);
|
|
@@ -21292,7 +21480,7 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
21292
21480
|
}
|
|
21293
21481
|
var workflowEngineServer = async (ctx) => {
|
|
21294
21482
|
const directory = ctx.directory ?? process.cwd();
|
|
21295
|
-
const workflowsDir =
|
|
21483
|
+
const workflowsDir = path29.join(directory, "workflows");
|
|
21296
21484
|
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME21}] preload workflows failed`, {
|
|
21297
21485
|
error: err instanceof Error ? err.message : String(err)
|
|
21298
21486
|
}));
|
|
@@ -21336,7 +21524,7 @@ var workflowEngineServer = async (ctx) => {
|
|
|
21336
21524
|
var handler21 = workflowEngineServer;
|
|
21337
21525
|
|
|
21338
21526
|
// plugins/session-worktree-guard.ts
|
|
21339
|
-
import
|
|
21527
|
+
import path30 from "node:path";
|
|
21340
21528
|
import { stat } from "node:fs/promises";
|
|
21341
21529
|
var PLUGIN_NAME22 = "session-worktree-guard";
|
|
21342
21530
|
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
@@ -21372,6 +21560,31 @@ function buildGitVcsWriteRegex(mainRoot) {
|
|
|
21372
21560
|
var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
|
|
21373
21561
|
var TOUCH_THROTTLE_MS = 5 * 60000;
|
|
21374
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
|
+
}
|
|
21375
21588
|
var _sessionIdMissingWarned = false;
|
|
21376
21589
|
var _bindFailNotified = new Set;
|
|
21377
21590
|
function formatLazyBindDenyReason(input) {
|
|
@@ -21448,23 +21661,23 @@ var MERGE_CALLER_WHITELIST = new Set([
|
|
|
21448
21661
|
var FORCE_MERGE_CALLER_WHITELIST = new Set([
|
|
21449
21662
|
"codeforge"
|
|
21450
21663
|
]);
|
|
21451
|
-
var CODEFORGE_WORKTREE_DIR_NAME =
|
|
21664
|
+
var CODEFORGE_WORKTREE_DIR_NAME = path30.join(".git", "codeforge-worktrees");
|
|
21452
21665
|
function worktreesRoot(mainRoot) {
|
|
21453
|
-
return
|
|
21666
|
+
return path30.join(mainRoot, CODEFORGE_WORKTREE_DIR_NAME);
|
|
21454
21667
|
}
|
|
21455
21668
|
function isInsideAnyWorktreeDir(absPath, mainRoot) {
|
|
21456
|
-
if (!
|
|
21669
|
+
if (!path30.isAbsolute(absPath))
|
|
21457
21670
|
return false;
|
|
21458
21671
|
const root = worktreesRoot(mainRoot);
|
|
21459
21672
|
if (absPath === root)
|
|
21460
21673
|
return false;
|
|
21461
|
-
const prefix = root.endsWith(
|
|
21674
|
+
const prefix = root.endsWith(path30.sep) ? root : root + path30.sep;
|
|
21462
21675
|
return absPath.startsWith(prefix);
|
|
21463
21676
|
}
|
|
21464
21677
|
function rewritePath(value, mainRoot, worktreeRoot) {
|
|
21465
21678
|
if (!value)
|
|
21466
21679
|
return null;
|
|
21467
|
-
const resolved =
|
|
21680
|
+
const resolved = path30.isAbsolute(value) ? value : path30.resolve(mainRoot, value);
|
|
21468
21681
|
const wtPrefix2 = worktreeRoot.endsWith("/") ? worktreeRoot : worktreeRoot + "/";
|
|
21469
21682
|
if (resolved === worktreeRoot || resolved.startsWith(wtPrefix2)) {
|
|
21470
21683
|
return null;
|
|
@@ -21502,7 +21715,7 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
|
|
|
21502
21715
|
}
|
|
21503
21716
|
}
|
|
21504
21717
|
const wtRoot = worktreesRoot(mainRoot);
|
|
21505
|
-
const wtRootPrefix = wtRoot +
|
|
21718
|
+
const wtRootPrefix = wtRoot + path30.sep;
|
|
21506
21719
|
const escapedWtRootPrefix = escapeRegex(wtRootPrefix);
|
|
21507
21720
|
const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
|
|
21508
21721
|
const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
|
|
@@ -21557,8 +21770,8 @@ function collectWritePaths(toolName, argsObj, worktreeRoot) {
|
|
|
21557
21770
|
const candidate = toolName === "write" || toolName === "edit" ? argsObj["filePath"] : toolName === "ast_edit" ? argsObj["target"] : undefined;
|
|
21558
21771
|
if (typeof candidate !== "string" || candidate.length === 0)
|
|
21559
21772
|
return out;
|
|
21560
|
-
const abs =
|
|
21561
|
-
const rel =
|
|
21773
|
+
const abs = path30.isAbsolute(candidate) ? candidate : path30.resolve(worktreeRoot, candidate);
|
|
21774
|
+
const rel = path30.relative(worktreeRoot, abs).split(path30.sep).join("/");
|
|
21562
21775
|
out.push(rel);
|
|
21563
21776
|
return out;
|
|
21564
21777
|
}
|
|
@@ -21569,7 +21782,7 @@ async function isCodeforgeManagedProject(mainRoot, opts = {}) {
|
|
|
21569
21782
|
if (force === "1" || force === "true" || force === "yes")
|
|
21570
21783
|
return true;
|
|
21571
21784
|
try {
|
|
21572
|
-
const st = await stat(
|
|
21785
|
+
const st = await stat(path30.join(mainRoot, ".codeforge"));
|
|
21573
21786
|
return st.isDirectory();
|
|
21574
21787
|
} catch {
|
|
21575
21788
|
return false;
|
|
@@ -21583,6 +21796,37 @@ function resolveMainRoot2(rawDir) {
|
|
|
21583
21796
|
}
|
|
21584
21797
|
return rawDir;
|
|
21585
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
|
+
}
|
|
21586
21830
|
var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
21587
21831
|
const disableEnv = process.env["CODEFORGE_DISABLE_WORKTREE_GUARD"];
|
|
21588
21832
|
if (disableEnv === "1" || disableEnv === "true" || disableEnv === "yes") {
|
|
@@ -21647,6 +21891,29 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21647
21891
|
await safeAsync(PLUGIN_NAME22, "tool.execute.before", async () => {
|
|
21648
21892
|
const toolName = input.tool;
|
|
21649
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
|
+
};
|
|
21650
21917
|
let entry = null;
|
|
21651
21918
|
try {
|
|
21652
21919
|
entry = await getSessionWorktree(sessionId, mainRoot);
|
|
@@ -21690,6 +21957,12 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21690
21957
|
if (!isWriteOperation(toolName, argsObj, mainRoot)) {
|
|
21691
21958
|
return;
|
|
21692
21959
|
}
|
|
21960
|
+
if (midMerge) {
|
|
21961
|
+
emitMergeBypassNotice("lazy-bind", {
|
|
21962
|
+
reason: "mid-merge: skip lazy-bind, passthrough to main repo"
|
|
21963
|
+
});
|
|
21964
|
+
return;
|
|
21965
|
+
}
|
|
21693
21966
|
try {
|
|
21694
21967
|
const parentId = lookupParentSessionId2(sessionId);
|
|
21695
21968
|
if (parentId) {
|
|
@@ -21706,6 +21979,18 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21706
21979
|
parent_branch: parentEntry.branch,
|
|
21707
21980
|
parent_worktree: parentEntry.worktreePath
|
|
21708
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
|
+
});
|
|
21709
21994
|
}
|
|
21710
21995
|
}
|
|
21711
21996
|
} catch (lookupErr) {
|
|
@@ -21935,6 +22220,11 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21935
22220
|
if (toolName === "bash") {
|
|
21936
22221
|
const command = argsObj["command"];
|
|
21937
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
|
+
}
|
|
21938
22228
|
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
|
|
21939
22229
|
if (caller !== null && CLASS_B_CALLER_WHITELIST.has(caller)) {
|
|
21940
22230
|
log13.debug?.(`[class-b-whitelist] allow caller=${caller}`, { sessionId, tool: toolName, command: command.slice(0, 200) });
|
|
@@ -21966,182 +22256,85 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21966
22256
|
}
|
|
21967
22257
|
}
|
|
21968
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
|
+
};
|
|
21969
22280
|
if (toolName === "write" || toolName === "edit") {
|
|
21970
22281
|
const filePath = argsObj["filePath"];
|
|
21971
|
-
if (typeof filePath === "string")
|
|
21972
|
-
|
|
21973
|
-
if (newPath !== null) {
|
|
21974
|
-
log13.info(`rewrote ${toolName}.filePath: ${filePath} → ${newPath}`);
|
|
21975
|
-
safeWriteLog(PLUGIN_NAME22, {
|
|
21976
|
-
hook: "tool.execute.before",
|
|
21977
|
-
tool: toolName,
|
|
21978
|
-
field: "filePath",
|
|
21979
|
-
before: filePath,
|
|
21980
|
-
after: newPath,
|
|
21981
|
-
action: "rewrite"
|
|
21982
|
-
});
|
|
21983
|
-
output.args["filePath"] = newPath;
|
|
21984
|
-
}
|
|
21985
|
-
}
|
|
22282
|
+
if (typeof filePath === "string")
|
|
22283
|
+
applyRewrite("filePath", filePath);
|
|
21986
22284
|
}
|
|
21987
22285
|
if (toolName === "ast_edit") {
|
|
21988
22286
|
const target = argsObj["target"];
|
|
21989
|
-
if (typeof target === "string")
|
|
21990
|
-
|
|
21991
|
-
if (newTarget !== null) {
|
|
21992
|
-
log13.info(`rewrote ast_edit.target: ${target} → ${newTarget}`);
|
|
21993
|
-
safeWriteLog(PLUGIN_NAME22, {
|
|
21994
|
-
hook: "tool.execute.before",
|
|
21995
|
-
tool: toolName,
|
|
21996
|
-
field: "target",
|
|
21997
|
-
before: target,
|
|
21998
|
-
after: newTarget,
|
|
21999
|
-
action: "rewrite"
|
|
22000
|
-
});
|
|
22001
|
-
output.args["target"] = newTarget;
|
|
22002
|
-
}
|
|
22003
|
-
}
|
|
22287
|
+
if (typeof target === "string")
|
|
22288
|
+
applyRewrite("target", target);
|
|
22004
22289
|
const root = argsObj["root"];
|
|
22005
|
-
if (typeof root === "string")
|
|
22006
|
-
|
|
22007
|
-
if (newRoot !== null) {
|
|
22008
|
-
log13.info(`rewrote ast_edit.root: ${root} → ${newRoot}`);
|
|
22009
|
-
safeWriteLog(PLUGIN_NAME22, {
|
|
22010
|
-
hook: "tool.execute.before",
|
|
22011
|
-
tool: toolName,
|
|
22012
|
-
field: "root",
|
|
22013
|
-
before: root,
|
|
22014
|
-
after: newRoot,
|
|
22015
|
-
action: "rewrite"
|
|
22016
|
-
});
|
|
22017
|
-
output.args["root"] = newRoot;
|
|
22018
|
-
}
|
|
22019
|
-
}
|
|
22290
|
+
if (typeof root === "string")
|
|
22291
|
+
applyRewrite("root", root);
|
|
22020
22292
|
}
|
|
22021
22293
|
if (toolName === "bash") {
|
|
22022
22294
|
const workdir = argsObj["workdir"];
|
|
22023
|
-
if (typeof workdir === "string")
|
|
22024
|
-
|
|
22025
|
-
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
|
|
22033
|
-
action: "rewrite"
|
|
22034
|
-
});
|
|
22035
|
-
output.args["workdir"] = newWorkdir;
|
|
22036
|
-
}
|
|
22037
|
-
}
|
|
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
|
+
});
|
|
22038
22305
|
}
|
|
22039
22306
|
});
|
|
22040
22307
|
if (denied)
|
|
22041
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
|
+
});
|
|
22042
22331
|
}
|
|
22043
22332
|
};
|
|
22044
22333
|
};
|
|
22045
22334
|
var handler22 = sessionWorktreeGuardPlugin;
|
|
22046
22335
|
|
|
22047
|
-
// lib/opencode-session-probe.ts
|
|
22048
|
-
import * as path30 from "node:path";
|
|
22049
|
-
import * as os6 from "node:os";
|
|
22050
|
-
import { createRequire as createRequire2 } from "node:module";
|
|
22051
|
-
var requireFromHere = createRequire2(import.meta.url);
|
|
22052
|
-
var DEFAULT_LIVENESS_MS = 6 * 60 * 60000;
|
|
22053
|
-
var DEFAULT_DB_PATH = path30.join(os6.homedir(), ".local/share/opencode/opencode.db");
|
|
22054
|
-
function createSessionProbe(opts = {}) {
|
|
22055
|
-
const dbPath = opts.dbPath ?? DEFAULT_DB_PATH;
|
|
22056
|
-
const httpBaseUrl = opts.httpBaseUrl ?? process.env["OPENCODE_SERVER_URL"];
|
|
22057
|
-
const livenessWindowMs = opts.livenessWindowMs ?? DEFAULT_LIVENESS_MS;
|
|
22058
|
-
const timeoutMs = opts.timeoutMs ?? 1500;
|
|
22059
|
-
let db = null;
|
|
22060
|
-
let dbInitTried = false;
|
|
22061
|
-
let dbStmt = null;
|
|
22062
|
-
async function tryOpenDb() {
|
|
22063
|
-
if (dbInitTried)
|
|
22064
|
-
return db != null;
|
|
22065
|
-
dbInitTried = true;
|
|
22066
|
-
try {
|
|
22067
|
-
const mod = requireFromHere("node:sqlite");
|
|
22068
|
-
try {
|
|
22069
|
-
db = new mod.DatabaseSync(dbPath, { readOnly: true });
|
|
22070
|
-
} catch {
|
|
22071
|
-
db = null;
|
|
22072
|
-
dbStmt = null;
|
|
22073
|
-
return false;
|
|
22074
|
-
}
|
|
22075
|
-
dbStmt = db.prepare("SELECT time_updated, time_archived FROM session WHERE id = ? LIMIT 1");
|
|
22076
|
-
return true;
|
|
22077
|
-
} catch {
|
|
22078
|
-
db = null;
|
|
22079
|
-
dbStmt = null;
|
|
22080
|
-
return false;
|
|
22081
|
-
}
|
|
22082
|
-
}
|
|
22083
|
-
async function probeHttp(sessionId) {
|
|
22084
|
-
if (!httpBaseUrl)
|
|
22085
|
-
return null;
|
|
22086
|
-
try {
|
|
22087
|
-
const ctrl = new AbortController;
|
|
22088
|
-
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
22089
|
-
const res = await fetch(`${httpBaseUrl.replace(/\/$/, "")}/session`, {
|
|
22090
|
-
signal: ctrl.signal
|
|
22091
|
-
}).finally(() => clearTimeout(t));
|
|
22092
|
-
if (!res.ok)
|
|
22093
|
-
return null;
|
|
22094
|
-
const list = await res.json();
|
|
22095
|
-
const hit = Array.isArray(list) && list.some((s) => s.id === sessionId);
|
|
22096
|
-
return { alive: hit, source: "http" };
|
|
22097
|
-
} catch {
|
|
22098
|
-
return null;
|
|
22099
|
-
}
|
|
22100
|
-
}
|
|
22101
|
-
async function probeSqlite(sessionId) {
|
|
22102
|
-
if (!await tryOpenDb() || !dbStmt)
|
|
22103
|
-
return null;
|
|
22104
|
-
try {
|
|
22105
|
-
const row = dbStmt.get(sessionId);
|
|
22106
|
-
if (!row) {
|
|
22107
|
-
return { alive: false, source: "sqlite" };
|
|
22108
|
-
}
|
|
22109
|
-
if (row.time_archived != null) {
|
|
22110
|
-
return {
|
|
22111
|
-
alive: false,
|
|
22112
|
-
source: "sqlite",
|
|
22113
|
-
time_archived: row.time_archived
|
|
22114
|
-
};
|
|
22115
|
-
}
|
|
22116
|
-
const now = Date.now();
|
|
22117
|
-
const tu = Number(row.time_updated) || 0;
|
|
22118
|
-
const alive = now - tu < livenessWindowMs;
|
|
22119
|
-
return { alive, source: "sqlite", time_updated: tu, time_archived: null };
|
|
22120
|
-
} catch {
|
|
22121
|
-
return null;
|
|
22122
|
-
}
|
|
22123
|
-
}
|
|
22124
|
-
return {
|
|
22125
|
-
async isSessionAlive(sessionId) {
|
|
22126
|
-
const http = await probeHttp(sessionId);
|
|
22127
|
-
if (http)
|
|
22128
|
-
return http;
|
|
22129
|
-
const sql = await probeSqlite(sessionId);
|
|
22130
|
-
if (sql)
|
|
22131
|
-
return sql;
|
|
22132
|
-
return { alive: true, source: "unknown" };
|
|
22133
|
-
},
|
|
22134
|
-
close() {
|
|
22135
|
-
try {
|
|
22136
|
-
db?.close?.();
|
|
22137
|
-
} catch {}
|
|
22138
|
-
db = null;
|
|
22139
|
-
dbStmt = null;
|
|
22140
|
-
}
|
|
22141
|
-
};
|
|
22142
|
-
}
|
|
22143
|
-
|
|
22144
22336
|
// plugins/worktree-lifecycle.ts
|
|
22337
|
+
init_worktree_ops();
|
|
22145
22338
|
var PLUGIN_NAME23 = "worktree-lifecycle";
|
|
22146
22339
|
logLifecycle(PLUGIN_NAME23, "import", {});
|
|
22147
22340
|
var IDLE_TOAST_THROTTLE_MS = 60 * 60000;
|
|
@@ -22298,10 +22491,17 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
22298
22491
|
worktreePath: entry.worktreePath
|
|
22299
22492
|
});
|
|
22300
22493
|
} catch (err) {
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
|
|
22304
|
-
|
|
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
|
+
}
|
|
22305
22505
|
}
|
|
22306
22506
|
lastIdleToastAt.delete(ended.sessionID);
|
|
22307
22507
|
return;
|