@andyqiu/codeforge 0.6.5 → 0.6.6
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 +3 -5
- package/agents/coder-deep.md +1 -1
- package/agents/coder-quick.md +1 -1
- package/agents/coder.md +1 -1
- package/agents/reviewer-lite.md +30 -1
- package/agents/reviewer.md +30 -1
- package/bin/codeforge-doctor.mjs +187 -0
- package/bin/codeforge.mjs +21 -0
- package/dist/index.js +480 -256
- package/install.mjs +97 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ __export(exports_worktree_ops, {
|
|
|
28
28
|
listWorktrees: () => listWorktrees,
|
|
29
29
|
getMergeConflicts: () => getMergeConflicts,
|
|
30
30
|
ensureWorktree: () => ensureWorktree,
|
|
31
|
+
deleteBranchIfExists: () => deleteBranchIfExists,
|
|
31
32
|
commitWorktreeIfDirty: () => commitWorktreeIfDirty
|
|
32
33
|
});
|
|
33
34
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -60,7 +61,35 @@ async function removeWorktree(opts) {
|
|
|
60
61
|
const args = ["worktree", "remove", opts.worktree_path];
|
|
61
62
|
if (opts.force)
|
|
62
63
|
args.push("--force");
|
|
63
|
-
|
|
64
|
+
try {
|
|
65
|
+
await runGit(opts.root, args, opts.git_timeout_ms ?? 5000);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (isWorktreeAbsentError(err))
|
|
68
|
+
return;
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function isWorktreeAbsentError(err) {
|
|
73
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
return /is not a working tree/i.test(msg) || /No such file or directory/i.test(msg) || /not a valid path/i.test(msg);
|
|
75
|
+
}
|
|
76
|
+
async function deleteBranchIfExists(opts) {
|
|
77
|
+
const root = path11.resolve(opts.root);
|
|
78
|
+
const timeout = opts.git_timeout_ms ?? 5000;
|
|
79
|
+
try {
|
|
80
|
+
await runGit(root, ["rev-parse", "--verify", `refs/heads/${opts.branch}`], 3000);
|
|
81
|
+
} catch {
|
|
82
|
+
return { deleted: false };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await runGit(root, ["branch", "-D", opts.branch], timeout);
|
|
86
|
+
return { deleted: true };
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
89
|
+
if (/not found/i.test(msg))
|
|
90
|
+
return { deleted: false };
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
64
93
|
}
|
|
65
94
|
async function listWorktrees(opts) {
|
|
66
95
|
const out = await runGit(opts.root, ["worktree", "list", "--porcelain"], opts.git_timeout_ms ?? 3000);
|
|
@@ -248,9 +277,10 @@ function parseDecision(markdown) {
|
|
|
248
277
|
return { token: null, reason: "Decision section is empty" };
|
|
249
278
|
}
|
|
250
279
|
const cleaned = firstLine.replace(/`/g, "").trim().toUpperCase();
|
|
251
|
-
|
|
280
|
+
const normalized = cleaned === "APPROVE_WITH_NOTES" ? "APPROVE" : cleaned;
|
|
281
|
+
if (VALID_TOKENS.has(normalized)) {
|
|
252
282
|
return {
|
|
253
|
-
token:
|
|
283
|
+
token: normalized,
|
|
254
284
|
reason: firstLine.length > 200 ? firstLine.slice(0, 200) + "…" : firstLine
|
|
255
285
|
};
|
|
256
286
|
}
|
|
@@ -336,17 +366,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
336
366
|
visit.BREAK = BREAK;
|
|
337
367
|
visit.SKIP = SKIP;
|
|
338
368
|
visit.REMOVE = REMOVE;
|
|
339
|
-
function visit_(key, node, visitor,
|
|
340
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
369
|
+
function visit_(key, node, visitor, path20) {
|
|
370
|
+
const ctrl = callVisitor(key, node, visitor, path20);
|
|
341
371
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
342
|
-
replaceNode(key,
|
|
343
|
-
return visit_(key, ctrl, visitor,
|
|
372
|
+
replaceNode(key, path20, ctrl);
|
|
373
|
+
return visit_(key, ctrl, visitor, path20);
|
|
344
374
|
}
|
|
345
375
|
if (typeof ctrl !== "symbol") {
|
|
346
376
|
if (identity.isCollection(node)) {
|
|
347
|
-
|
|
377
|
+
path20 = Object.freeze(path20.concat(node));
|
|
348
378
|
for (let i = 0;i < node.items.length; ++i) {
|
|
349
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
379
|
+
const ci = visit_(i, node.items[i], visitor, path20);
|
|
350
380
|
if (typeof ci === "number")
|
|
351
381
|
i = ci - 1;
|
|
352
382
|
else if (ci === BREAK)
|
|
@@ -357,13 +387,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
357
387
|
}
|
|
358
388
|
}
|
|
359
389
|
} else if (identity.isPair(node)) {
|
|
360
|
-
|
|
361
|
-
const ck = visit_("key", node.key, visitor,
|
|
390
|
+
path20 = Object.freeze(path20.concat(node));
|
|
391
|
+
const ck = visit_("key", node.key, visitor, path20);
|
|
362
392
|
if (ck === BREAK)
|
|
363
393
|
return BREAK;
|
|
364
394
|
else if (ck === REMOVE)
|
|
365
395
|
node.key = null;
|
|
366
|
-
const cv = visit_("value", node.value, visitor,
|
|
396
|
+
const cv = visit_("value", node.value, visitor, path20);
|
|
367
397
|
if (cv === BREAK)
|
|
368
398
|
return BREAK;
|
|
369
399
|
else if (cv === REMOVE)
|
|
@@ -384,17 +414,17 @@ var require_visit = __commonJS((exports) => {
|
|
|
384
414
|
visitAsync.BREAK = BREAK;
|
|
385
415
|
visitAsync.SKIP = SKIP;
|
|
386
416
|
visitAsync.REMOVE = REMOVE;
|
|
387
|
-
async function visitAsync_(key, node, visitor,
|
|
388
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
417
|
+
async function visitAsync_(key, node, visitor, path20) {
|
|
418
|
+
const ctrl = await callVisitor(key, node, visitor, path20);
|
|
389
419
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
390
|
-
replaceNode(key,
|
|
391
|
-
return visitAsync_(key, ctrl, visitor,
|
|
420
|
+
replaceNode(key, path20, ctrl);
|
|
421
|
+
return visitAsync_(key, ctrl, visitor, path20);
|
|
392
422
|
}
|
|
393
423
|
if (typeof ctrl !== "symbol") {
|
|
394
424
|
if (identity.isCollection(node)) {
|
|
395
|
-
|
|
425
|
+
path20 = Object.freeze(path20.concat(node));
|
|
396
426
|
for (let i = 0;i < node.items.length; ++i) {
|
|
397
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
427
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path20);
|
|
398
428
|
if (typeof ci === "number")
|
|
399
429
|
i = ci - 1;
|
|
400
430
|
else if (ci === BREAK)
|
|
@@ -405,13 +435,13 @@ var require_visit = __commonJS((exports) => {
|
|
|
405
435
|
}
|
|
406
436
|
}
|
|
407
437
|
} else if (identity.isPair(node)) {
|
|
408
|
-
|
|
409
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
438
|
+
path20 = Object.freeze(path20.concat(node));
|
|
439
|
+
const ck = await visitAsync_("key", node.key, visitor, path20);
|
|
410
440
|
if (ck === BREAK)
|
|
411
441
|
return BREAK;
|
|
412
442
|
else if (ck === REMOVE)
|
|
413
443
|
node.key = null;
|
|
414
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
444
|
+
const cv = await visitAsync_("value", node.value, visitor, path20);
|
|
415
445
|
if (cv === BREAK)
|
|
416
446
|
return BREAK;
|
|
417
447
|
else if (cv === REMOVE)
|
|
@@ -438,23 +468,23 @@ var require_visit = __commonJS((exports) => {
|
|
|
438
468
|
}
|
|
439
469
|
return visitor;
|
|
440
470
|
}
|
|
441
|
-
function callVisitor(key, node, visitor,
|
|
471
|
+
function callVisitor(key, node, visitor, path20) {
|
|
442
472
|
if (typeof visitor === "function")
|
|
443
|
-
return visitor(key, node,
|
|
473
|
+
return visitor(key, node, path20);
|
|
444
474
|
if (identity.isMap(node))
|
|
445
|
-
return visitor.Map?.(key, node,
|
|
475
|
+
return visitor.Map?.(key, node, path20);
|
|
446
476
|
if (identity.isSeq(node))
|
|
447
|
-
return visitor.Seq?.(key, node,
|
|
477
|
+
return visitor.Seq?.(key, node, path20);
|
|
448
478
|
if (identity.isPair(node))
|
|
449
|
-
return visitor.Pair?.(key, node,
|
|
479
|
+
return visitor.Pair?.(key, node, path20);
|
|
450
480
|
if (identity.isScalar(node))
|
|
451
|
-
return visitor.Scalar?.(key, node,
|
|
481
|
+
return visitor.Scalar?.(key, node, path20);
|
|
452
482
|
if (identity.isAlias(node))
|
|
453
|
-
return visitor.Alias?.(key, node,
|
|
483
|
+
return visitor.Alias?.(key, node, path20);
|
|
454
484
|
return;
|
|
455
485
|
}
|
|
456
|
-
function replaceNode(key,
|
|
457
|
-
const parent =
|
|
486
|
+
function replaceNode(key, path20, node) {
|
|
487
|
+
const parent = path20[path20.length - 1];
|
|
458
488
|
if (identity.isCollection(parent)) {
|
|
459
489
|
parent.items[key] = node;
|
|
460
490
|
} else if (identity.isPair(parent)) {
|
|
@@ -1013,10 +1043,10 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1013
1043
|
var createNode = require_createNode();
|
|
1014
1044
|
var identity = require_identity();
|
|
1015
1045
|
var Node = require_Node();
|
|
1016
|
-
function collectionFromPath(schema,
|
|
1046
|
+
function collectionFromPath(schema, path20, value) {
|
|
1017
1047
|
let v = value;
|
|
1018
|
-
for (let i =
|
|
1019
|
-
const k =
|
|
1048
|
+
for (let i = path20.length - 1;i >= 0; --i) {
|
|
1049
|
+
const k = path20[i];
|
|
1020
1050
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
1021
1051
|
const a = [];
|
|
1022
1052
|
a[k] = v;
|
|
@@ -1035,7 +1065,7 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1035
1065
|
sourceObjects: new Map
|
|
1036
1066
|
});
|
|
1037
1067
|
}
|
|
1038
|
-
var isEmptyPath = (
|
|
1068
|
+
var isEmptyPath = (path20) => path20 == null || typeof path20 === "object" && !!path20[Symbol.iterator]().next().done;
|
|
1039
1069
|
|
|
1040
1070
|
class Collection extends Node.NodeBase {
|
|
1041
1071
|
constructor(type, schema) {
|
|
@@ -1056,11 +1086,11 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1056
1086
|
copy.range = this.range.slice();
|
|
1057
1087
|
return copy;
|
|
1058
1088
|
}
|
|
1059
|
-
addIn(
|
|
1060
|
-
if (isEmptyPath(
|
|
1089
|
+
addIn(path20, value) {
|
|
1090
|
+
if (isEmptyPath(path20))
|
|
1061
1091
|
this.add(value);
|
|
1062
1092
|
else {
|
|
1063
|
-
const [key, ...rest] =
|
|
1093
|
+
const [key, ...rest] = path20;
|
|
1064
1094
|
const node = this.get(key, true);
|
|
1065
1095
|
if (identity.isCollection(node))
|
|
1066
1096
|
node.addIn(rest, value);
|
|
@@ -1070,8 +1100,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1070
1100
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
1071
1101
|
}
|
|
1072
1102
|
}
|
|
1073
|
-
deleteIn(
|
|
1074
|
-
const [key, ...rest] =
|
|
1103
|
+
deleteIn(path20) {
|
|
1104
|
+
const [key, ...rest] = path20;
|
|
1075
1105
|
if (rest.length === 0)
|
|
1076
1106
|
return this.delete(key);
|
|
1077
1107
|
const node = this.get(key, true);
|
|
@@ -1080,8 +1110,8 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1080
1110
|
else
|
|
1081
1111
|
throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
|
|
1082
1112
|
}
|
|
1083
|
-
getIn(
|
|
1084
|
-
const [key, ...rest] =
|
|
1113
|
+
getIn(path20, keepScalar) {
|
|
1114
|
+
const [key, ...rest] = path20;
|
|
1085
1115
|
const node = this.get(key, true);
|
|
1086
1116
|
if (rest.length === 0)
|
|
1087
1117
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -1096,15 +1126,15 @@ var require_Collection = __commonJS((exports) => {
|
|
|
1096
1126
|
return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
|
|
1097
1127
|
});
|
|
1098
1128
|
}
|
|
1099
|
-
hasIn(
|
|
1100
|
-
const [key, ...rest] =
|
|
1129
|
+
hasIn(path20) {
|
|
1130
|
+
const [key, ...rest] = path20;
|
|
1101
1131
|
if (rest.length === 0)
|
|
1102
1132
|
return this.has(key);
|
|
1103
1133
|
const node = this.get(key, true);
|
|
1104
1134
|
return identity.isCollection(node) ? node.hasIn(rest) : false;
|
|
1105
1135
|
}
|
|
1106
|
-
setIn(
|
|
1107
|
-
const [key, ...rest] =
|
|
1136
|
+
setIn(path20, value) {
|
|
1137
|
+
const [key, ...rest] = path20;
|
|
1108
1138
|
if (rest.length === 0) {
|
|
1109
1139
|
this.set(key, value);
|
|
1110
1140
|
} else {
|
|
@@ -3497,9 +3527,9 @@ var require_Document = __commonJS((exports) => {
|
|
|
3497
3527
|
if (assertCollection(this.contents))
|
|
3498
3528
|
this.contents.add(value);
|
|
3499
3529
|
}
|
|
3500
|
-
addIn(
|
|
3530
|
+
addIn(path20, value) {
|
|
3501
3531
|
if (assertCollection(this.contents))
|
|
3502
|
-
this.contents.addIn(
|
|
3532
|
+
this.contents.addIn(path20, value);
|
|
3503
3533
|
}
|
|
3504
3534
|
createAlias(node, name) {
|
|
3505
3535
|
if (!node.anchor) {
|
|
@@ -3548,30 +3578,30 @@ var require_Document = __commonJS((exports) => {
|
|
|
3548
3578
|
delete(key) {
|
|
3549
3579
|
return assertCollection(this.contents) ? this.contents.delete(key) : false;
|
|
3550
3580
|
}
|
|
3551
|
-
deleteIn(
|
|
3552
|
-
if (Collection.isEmptyPath(
|
|
3581
|
+
deleteIn(path20) {
|
|
3582
|
+
if (Collection.isEmptyPath(path20)) {
|
|
3553
3583
|
if (this.contents == null)
|
|
3554
3584
|
return false;
|
|
3555
3585
|
this.contents = null;
|
|
3556
3586
|
return true;
|
|
3557
3587
|
}
|
|
3558
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3588
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path20) : false;
|
|
3559
3589
|
}
|
|
3560
3590
|
get(key, keepScalar) {
|
|
3561
3591
|
return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined;
|
|
3562
3592
|
}
|
|
3563
|
-
getIn(
|
|
3564
|
-
if (Collection.isEmptyPath(
|
|
3593
|
+
getIn(path20, keepScalar) {
|
|
3594
|
+
if (Collection.isEmptyPath(path20))
|
|
3565
3595
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3566
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3596
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path20, keepScalar) : undefined;
|
|
3567
3597
|
}
|
|
3568
3598
|
has(key) {
|
|
3569
3599
|
return identity.isCollection(this.contents) ? this.contents.has(key) : false;
|
|
3570
3600
|
}
|
|
3571
|
-
hasIn(
|
|
3572
|
-
if (Collection.isEmptyPath(
|
|
3601
|
+
hasIn(path20) {
|
|
3602
|
+
if (Collection.isEmptyPath(path20))
|
|
3573
3603
|
return this.contents !== undefined;
|
|
3574
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3604
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path20) : false;
|
|
3575
3605
|
}
|
|
3576
3606
|
set(key, value) {
|
|
3577
3607
|
if (this.contents == null) {
|
|
@@ -3580,13 +3610,13 @@ var require_Document = __commonJS((exports) => {
|
|
|
3580
3610
|
this.contents.set(key, value);
|
|
3581
3611
|
}
|
|
3582
3612
|
}
|
|
3583
|
-
setIn(
|
|
3584
|
-
if (Collection.isEmptyPath(
|
|
3613
|
+
setIn(path20, value) {
|
|
3614
|
+
if (Collection.isEmptyPath(path20)) {
|
|
3585
3615
|
this.contents = value;
|
|
3586
3616
|
} else if (this.contents == null) {
|
|
3587
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3617
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path20), value);
|
|
3588
3618
|
} else if (assertCollection(this.contents)) {
|
|
3589
|
-
this.contents.setIn(
|
|
3619
|
+
this.contents.setIn(path20, value);
|
|
3590
3620
|
}
|
|
3591
3621
|
}
|
|
3592
3622
|
setSchema(version, options = {}) {
|
|
@@ -5481,9 +5511,9 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5481
5511
|
visit.BREAK = BREAK;
|
|
5482
5512
|
visit.SKIP = SKIP;
|
|
5483
5513
|
visit.REMOVE = REMOVE;
|
|
5484
|
-
visit.itemAtPath = (cst,
|
|
5514
|
+
visit.itemAtPath = (cst, path20) => {
|
|
5485
5515
|
let item = cst;
|
|
5486
|
-
for (const [field, index] of
|
|
5516
|
+
for (const [field, index] of path20) {
|
|
5487
5517
|
const tok = item?.[field];
|
|
5488
5518
|
if (tok && "items" in tok) {
|
|
5489
5519
|
item = tok.items[index];
|
|
@@ -5492,23 +5522,23 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5492
5522
|
}
|
|
5493
5523
|
return item;
|
|
5494
5524
|
};
|
|
5495
|
-
visit.parentCollection = (cst,
|
|
5496
|
-
const parent = visit.itemAtPath(cst,
|
|
5497
|
-
const field =
|
|
5525
|
+
visit.parentCollection = (cst, path20) => {
|
|
5526
|
+
const parent = visit.itemAtPath(cst, path20.slice(0, -1));
|
|
5527
|
+
const field = path20[path20.length - 1][0];
|
|
5498
5528
|
const coll = parent?.[field];
|
|
5499
5529
|
if (coll && "items" in coll)
|
|
5500
5530
|
return coll;
|
|
5501
5531
|
throw new Error("Parent collection not found");
|
|
5502
5532
|
};
|
|
5503
|
-
function _visit(
|
|
5504
|
-
let ctrl = visitor(item,
|
|
5533
|
+
function _visit(path20, item, visitor) {
|
|
5534
|
+
let ctrl = visitor(item, path20);
|
|
5505
5535
|
if (typeof ctrl === "symbol")
|
|
5506
5536
|
return ctrl;
|
|
5507
5537
|
for (const field of ["key", "value"]) {
|
|
5508
5538
|
const token = item[field];
|
|
5509
5539
|
if (token && "items" in token) {
|
|
5510
5540
|
for (let i = 0;i < token.items.length; ++i) {
|
|
5511
|
-
const ci = _visit(Object.freeze(
|
|
5541
|
+
const ci = _visit(Object.freeze(path20.concat([[field, i]])), token.items[i], visitor);
|
|
5512
5542
|
if (typeof ci === "number")
|
|
5513
5543
|
i = ci - 1;
|
|
5514
5544
|
else if (ci === BREAK)
|
|
@@ -5519,10 +5549,10 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5519
5549
|
}
|
|
5520
5550
|
}
|
|
5521
5551
|
if (typeof ctrl === "function" && field === "key")
|
|
5522
|
-
ctrl = ctrl(item,
|
|
5552
|
+
ctrl = ctrl(item, path20);
|
|
5523
5553
|
}
|
|
5524
5554
|
}
|
|
5525
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5555
|
+
return typeof ctrl === "function" ? ctrl(item, path20) : ctrl;
|
|
5526
5556
|
}
|
|
5527
5557
|
exports.visit = visit;
|
|
5528
5558
|
});
|
|
@@ -6791,14 +6821,14 @@ var require_parser = __commonJS((exports) => {
|
|
|
6791
6821
|
case "scalar":
|
|
6792
6822
|
case "single-quoted-scalar":
|
|
6793
6823
|
case "double-quoted-scalar": {
|
|
6794
|
-
const
|
|
6824
|
+
const fs16 = this.flowScalar(this.type);
|
|
6795
6825
|
if (atNextItem || it.value) {
|
|
6796
|
-
map.items.push({ start, key:
|
|
6826
|
+
map.items.push({ start, key: fs16, sep: [] });
|
|
6797
6827
|
this.onKeyLine = true;
|
|
6798
6828
|
} else if (it.sep) {
|
|
6799
|
-
this.stack.push(
|
|
6829
|
+
this.stack.push(fs16);
|
|
6800
6830
|
} else {
|
|
6801
|
-
Object.assign(it, { key:
|
|
6831
|
+
Object.assign(it, { key: fs16, sep: [] });
|
|
6802
6832
|
this.onKeyLine = true;
|
|
6803
6833
|
}
|
|
6804
6834
|
return;
|
|
@@ -6926,13 +6956,13 @@ var require_parser = __commonJS((exports) => {
|
|
|
6926
6956
|
case "scalar":
|
|
6927
6957
|
case "single-quoted-scalar":
|
|
6928
6958
|
case "double-quoted-scalar": {
|
|
6929
|
-
const
|
|
6959
|
+
const fs16 = this.flowScalar(this.type);
|
|
6930
6960
|
if (!it || it.value)
|
|
6931
|
-
fc.items.push({ start: [], key:
|
|
6961
|
+
fc.items.push({ start: [], key: fs16, sep: [] });
|
|
6932
6962
|
else if (it.sep)
|
|
6933
|
-
this.stack.push(
|
|
6963
|
+
this.stack.push(fs16);
|
|
6934
6964
|
else
|
|
6935
|
-
Object.assign(it, { key:
|
|
6965
|
+
Object.assign(it, { key: fs16, sep: [] });
|
|
6936
6966
|
return;
|
|
6937
6967
|
}
|
|
6938
6968
|
case "flow-map-end":
|
|
@@ -9298,7 +9328,7 @@ async function resolveAgentForGuard(input, client, log4, opts = {}) {
|
|
|
9298
9328
|
if (!opts.skipCache && chatAgentCacheReader) {
|
|
9299
9329
|
try {
|
|
9300
9330
|
const cached = chatAgentCacheReader(input.sessionID);
|
|
9301
|
-
if (typeof cached === "string"
|
|
9331
|
+
if (typeof cached === "string") {
|
|
9302
9332
|
return cached;
|
|
9303
9333
|
}
|
|
9304
9334
|
} catch (err) {
|
|
@@ -9310,7 +9340,7 @@ async function resolveAgentForGuard(input, client, log4, opts = {}) {
|
|
|
9310
9340
|
}
|
|
9311
9341
|
if (!opts.skipIpcLookup) {
|
|
9312
9342
|
const viaSession = await resolveCurrentAgent(client, input.sessionID, log4);
|
|
9313
|
-
if (typeof viaSession === "string"
|
|
9343
|
+
if (typeof viaSession === "string") {
|
|
9314
9344
|
return viaSession;
|
|
9315
9345
|
}
|
|
9316
9346
|
}
|
|
@@ -10628,15 +10658,19 @@ class ApprovalStore {
|
|
|
10628
10658
|
// tools/review-approval.ts
|
|
10629
10659
|
var description4 = [
|
|
10630
10660
|
"reviewer 专用:写入 APPROVE 审批记录。",
|
|
10631
|
-
"**何时调用**:reviewer 给出 `## Decision\\nAPPROVE`
|
|
10632
|
-
"
|
|
10661
|
+
"**何时调用**:reviewer 给出 `## Decision\\nAPPROVE` 之前必须调本工具。",
|
|
10662
|
+
"**两层语义独立**(ADR:decision-token-vs-approval-verdict-layering):",
|
|
10663
|
+
" - 本工具 `verdict` 字段属审批层,合法值:APPROVE / APPROVE_WITH_NOTES",
|
|
10664
|
+
" - reviewer 输出的 `## Decision` 节首行属协议层,合法值:APPROVE / REQUEST_CHANGES / BLOCK",
|
|
10665
|
+
" - ⚠️ 严禁把 `APPROVE_WITH_NOTES` 字面量写进 `## Decision` 节首行(容错层会归一,但其他变体如 APPROVE_MINOR 会失败 → merge-loop 误判死循环)",
|
|
10666
|
+
' - ✅ 正确:verdict="APPROVE_WITH_NOTES" + `## Decision\\nAPPROVE`(首行写 APPROVE,详情在审批 notes)',
|
|
10633
10667
|
"**pendingIds 格式**:推荐 `session:<sid>` / `plan:<plan_id>` / `decision:<hash>`;旧 `pc-<ts>-NNN` 仍兼容。",
|
|
10634
10668
|
"**何时不调**:REQUEST_CHANGES / BLOCK 不调(无 APPROVE = 无审批记录)。",
|
|
10635
10669
|
"**fallback**:codeforge 解析 reviewer boomerang 见 APPROVE 但无记录 → 自动以 source='codeforge-fallback' 补写。"
|
|
10636
10670
|
].join(`
|
|
10637
10671
|
`);
|
|
10638
10672
|
var ArgsSchema4 = z4.object({
|
|
10639
|
-
verdict: z4.enum(["APPROVE", "APPROVE_WITH_NOTES"]).describe("
|
|
10673
|
+
verdict: z4.enum(["APPROVE", "APPROVE_WITH_NOTES"]).describe("审批层裁决(审计字段,与协议层独立);REQUEST_CHANGES / BLOCK 不应调本工具。⚠️ 不要把本字段值复制到 reviewer 输出的 `## Decision` 节首行 — 那是协议层 3 档。"),
|
|
10640
10674
|
pendingIds: z4.array(z4.string().min(1)).min(1, "pendingIds 至少 1 条").describe("本次 APPROVE 覆盖的 id 列表。推荐 session:<sid> / plan:<plan_id> / decision:<hash>;旧 pc-xxx 兼容"),
|
|
10641
10675
|
notes: z4.string().min(1, "notes 不能为空").max(2000, "notes 过长(> 2000 字),建议拆条").describe("审阅意见摘要(建议 ≤ 500 字)"),
|
|
10642
10676
|
decisionLine: z4.string().optional().describe("`## Decision` 节首行原文(默认 verdict 字面量,机审证据)"),
|
|
@@ -11774,6 +11808,11 @@ function sleep(ms) {
|
|
|
11774
11808
|
// lib/session-worktree.ts
|
|
11775
11809
|
var REGISTRY_VERSION = 1;
|
|
11776
11810
|
var DEFAULT_WORKTREE_SUBDIR = path13.join(".git", "codeforge-worktrees");
|
|
11811
|
+
function debugLog(msg) {
|
|
11812
|
+
if (process.env["CODEFORGE_DEBUG"]) {
|
|
11813
|
+
console.debug(`[session-worktree] ${msg}`);
|
|
11814
|
+
}
|
|
11815
|
+
}
|
|
11777
11816
|
function registryDir(mainRoot) {
|
|
11778
11817
|
return path13.join(runtimeDir(path13.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
11779
11818
|
}
|
|
@@ -11941,13 +11980,12 @@ async function mergeSessionBack(opts) {
|
|
|
11941
11980
|
try {
|
|
11942
11981
|
await removeWorktree({ root: mainRoot, worktree_path: wt, force: true });
|
|
11943
11982
|
} catch (err) {
|
|
11944
|
-
|
|
11945
|
-
}
|
|
11946
|
-
try {
|
|
11947
|
-
await runGit2(mainRoot, ["branch", "-D", branch]);
|
|
11948
|
-
} catch (err) {
|
|
11949
|
-
console.warn(`[session-worktree] branch -D ${branch} 失败 (可能已被 worktree remove 一并删): ${err.message}`);
|
|
11983
|
+
debugLog(`removeWorktree (merge) 非预期失败 (session=${opts.sessionId}): ${err.message}`);
|
|
11950
11984
|
}
|
|
11985
|
+
await deleteBranchIfExists({ root: mainRoot, branch }).catch((err) => {
|
|
11986
|
+
debugLog(`deleteBranchIfExists (merge) 非预期失败: ${err.message}`);
|
|
11987
|
+
return { deleted: false };
|
|
11988
|
+
});
|
|
11951
11989
|
await mutateRegistry(mainRoot, (reg) => {
|
|
11952
11990
|
const e = reg.entries.find((x) => x.sessionId === opts.sessionId);
|
|
11953
11991
|
if (e) {
|
|
@@ -11978,13 +12016,12 @@ async function discardSession(opts) {
|
|
|
11978
12016
|
force: true
|
|
11979
12017
|
});
|
|
11980
12018
|
} catch (err) {
|
|
11981
|
-
|
|
11982
|
-
}
|
|
11983
|
-
try {
|
|
11984
|
-
await runGit2(mainRoot, ["branch", "-D", entry.branch]);
|
|
11985
|
-
} catch (err) {
|
|
11986
|
-
console.warn(`[session-worktree] branch -D ${entry.branch} (discard) 失败: ${err.message}`);
|
|
12019
|
+
debugLog(`removeWorktree (discard) 非预期失败: ${err.message}`);
|
|
11987
12020
|
}
|
|
12021
|
+
await deleteBranchIfExists({ root: mainRoot, branch: entry.branch }).catch((err) => {
|
|
12022
|
+
debugLog(`deleteBranchIfExists (discard) 非预期失败: ${err.message}`);
|
|
12023
|
+
return { deleted: false };
|
|
12024
|
+
});
|
|
11988
12025
|
await mutateRegistry(mainRoot, (reg) => {
|
|
11989
12026
|
const e = reg.entries.find((x) => x.sessionId === opts.sessionId);
|
|
11990
12027
|
if (e) {
|
|
@@ -12125,7 +12162,8 @@ Codeforge-Base: ${baseSha.slice(0, 12)}`;
|
|
|
12125
12162
|
return subject + body + footer;
|
|
12126
12163
|
}
|
|
12127
12164
|
var ORPHAN_GRACE_MS = 60000;
|
|
12128
|
-
var SEMANTIC_ORPHAN_MIN_AGE_MS =
|
|
12165
|
+
var SEMANTIC_ORPHAN_MIN_AGE_MS = 6 * 60 * 60000;
|
|
12166
|
+
var SEMANTIC_ORPHAN_UNKNOWN_TIMEOUT_MS = 72 * 60 * 60000;
|
|
12129
12167
|
async function pruneDiscardedRegistryEntries(mainRoot, opts = {}) {
|
|
12130
12168
|
const keepRecent = opts.keepRecent ?? 50;
|
|
12131
12169
|
if (keepRecent < 0) {
|
|
@@ -12209,6 +12247,7 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
12209
12247
|
});
|
|
12210
12248
|
if (opts.isSessionAlive) {
|
|
12211
12249
|
const minAge = opts.semanticOrphanMinAgeMs ?? SEMANTIC_ORPHAN_MIN_AGE_MS;
|
|
12250
|
+
const unknownTimeout = opts.semanticOrphanUnknownTimeoutMs ?? SEMANTIC_ORPHAN_UNKNOWN_TIMEOUT_MS;
|
|
12212
12251
|
const probe = opts.isSessionAlive;
|
|
12213
12252
|
await mutateRegistry(resolved, async (reg2) => {
|
|
12214
12253
|
const now = Date.now();
|
|
@@ -12228,10 +12267,11 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
12228
12267
|
continue;
|
|
12229
12268
|
}
|
|
12230
12269
|
if (aliveResult.source === "unknown") {
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12270
|
+
if (now - updatedMs < unknownTimeout) {
|
|
12271
|
+
skipped++;
|
|
12272
|
+
continue;
|
|
12273
|
+
}
|
|
12274
|
+
} else if (aliveResult.alive) {
|
|
12235
12275
|
skipped++;
|
|
12236
12276
|
continue;
|
|
12237
12277
|
}
|
|
@@ -12248,15 +12288,14 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
12248
12288
|
});
|
|
12249
12289
|
continue;
|
|
12250
12290
|
}
|
|
12251
|
-
|
|
12252
|
-
await runGit2(resolved, ["branch", "-D", entry.branch]);
|
|
12253
|
-
} catch {}
|
|
12291
|
+
await deleteBranchIfExists({ root: resolved, branch: entry.branch }).catch(() => {});
|
|
12254
12292
|
entry.status = "discarded";
|
|
12255
12293
|
entry.updatedAt = new Date().toISOString();
|
|
12294
|
+
const reasonSource = aliveResult.source === "unknown" ? `unknown-timeout (registry.updatedAt 已老于 ${unknownTimeout / 3600000}h)` : `opencode session ${aliveResult.source}: dead`;
|
|
12256
12295
|
cleaned.push({
|
|
12257
12296
|
sessionId: entry.sessionId,
|
|
12258
12297
|
worktreePath: entry.worktreePath,
|
|
12259
|
-
reason: `D 类语义孤儿 (
|
|
12298
|
+
reason: `D 类语义孤儿 (${reasonSource})`
|
|
12260
12299
|
});
|
|
12261
12300
|
}
|
|
12262
12301
|
});
|
|
@@ -12303,6 +12342,13 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
12303
12342
|
} catch (err) {
|
|
12304
12343
|
lastError = err instanceof Error ? err.message : String(err);
|
|
12305
12344
|
}
|
|
12345
|
+
if (removed) {
|
|
12346
|
+
try {
|
|
12347
|
+
await fs10.stat(candidate);
|
|
12348
|
+
removed = false;
|
|
12349
|
+
lastError = lastError ?? "git worktree remove 返回成功但目录仍存在(C 类 fs-only orphan)";
|
|
12350
|
+
} catch {}
|
|
12351
|
+
}
|
|
12306
12352
|
if (!removed && dirExists) {
|
|
12307
12353
|
try {
|
|
12308
12354
|
await fs10.rm(candidate, { recursive: true, force: true });
|
|
@@ -12326,6 +12372,39 @@ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
|
|
|
12326
12372
|
return { cleaned, failed, skipped, discardedPruned };
|
|
12327
12373
|
}
|
|
12328
12374
|
|
|
12375
|
+
// lib/merge-gate.ts
|
|
12376
|
+
import { promises as fs11 } from "node:fs";
|
|
12377
|
+
import * as path14 from "node:path";
|
|
12378
|
+
var DEFAULT_MERGE_GATE_CONFIG = { enabled: true };
|
|
12379
|
+
var CONFIG_REL = ".codeforge/merge-gate.json";
|
|
12380
|
+
async function loadMergeGate(mainRoot) {
|
|
12381
|
+
const file = path14.join(mainRoot, CONFIG_REL);
|
|
12382
|
+
let raw;
|
|
12383
|
+
try {
|
|
12384
|
+
raw = await fs11.readFile(file, "utf8");
|
|
12385
|
+
} catch (err) {
|
|
12386
|
+
const e = err;
|
|
12387
|
+
if (e.code === "ENOENT")
|
|
12388
|
+
return { ...DEFAULT_MERGE_GATE_CONFIG };
|
|
12389
|
+
console.warn(`[merge-gate] 读取 ${CONFIG_REL} 失败,fail-safe 退化为 enabled=false: ${e.message}`);
|
|
12390
|
+
return { enabled: false };
|
|
12391
|
+
}
|
|
12392
|
+
let parsed;
|
|
12393
|
+
try {
|
|
12394
|
+
parsed = JSON.parse(raw);
|
|
12395
|
+
} catch (err) {
|
|
12396
|
+
console.warn(`[merge-gate] ${CONFIG_REL} JSON 解析失败,fail-safe 退化为 enabled=false: ${err instanceof Error ? err.message : String(err)}`);
|
|
12397
|
+
return { enabled: false };
|
|
12398
|
+
}
|
|
12399
|
+
if (!parsed || typeof parsed !== "object") {
|
|
12400
|
+
console.warn(`[merge-gate] ${CONFIG_REL} 顶层非 object,fail-safe 退化为 enabled=false`);
|
|
12401
|
+
return { enabled: false };
|
|
12402
|
+
}
|
|
12403
|
+
const obj = parsed;
|
|
12404
|
+
const enabled = typeof obj["enabled"] === "boolean" ? obj["enabled"] : DEFAULT_MERGE_GATE_CONFIG.enabled;
|
|
12405
|
+
return { enabled };
|
|
12406
|
+
}
|
|
12407
|
+
|
|
12329
12408
|
// lib/merge-loop.ts
|
|
12330
12409
|
var DEFAULT_MERGE_LOOP_CONFIG = {
|
|
12331
12410
|
maxReviewLoops: 3,
|
|
@@ -12337,6 +12416,8 @@ var DEFAULT_MERGE_LOOP_CONFIG = {
|
|
|
12337
12416
|
async function runMergeLoop(opts) {
|
|
12338
12417
|
const config = { ...DEFAULT_MERGE_LOOP_CONFIG, ...opts.config ?? {} };
|
|
12339
12418
|
const progress = opts.onProgress ?? (() => {});
|
|
12419
|
+
const mergeGateLoader = opts.__testHooks?.loadMergeGate ?? loadMergeGate;
|
|
12420
|
+
const mergeGate = config.mergeGate ?? await mergeGateLoader(opts.mainRoot);
|
|
12340
12421
|
let loops = 0;
|
|
12341
12422
|
let lastReviewSummary;
|
|
12342
12423
|
progress("pre_check", `准备 merge session ${opts.sessionId}`);
|
|
@@ -12405,6 +12486,38 @@ async function runMergeLoop(opts) {
|
|
|
12405
12486
|
if (reviewResult.decision === "APPROVE") {
|
|
12406
12487
|
progress("do_merge", "APPROVE,执行 squash merge");
|
|
12407
12488
|
await maybeAbort(opts, config, entry);
|
|
12489
|
+
if (mergeGate.enabled) {
|
|
12490
|
+
const store = opts.__testHooks?.approvalStore ?? ApprovalStore.forProject(opts.mainRoot);
|
|
12491
|
+
let approval;
|
|
12492
|
+
let approvalKey = `session:${opts.sessionId}`;
|
|
12493
|
+
try {
|
|
12494
|
+
approval = await store.getLatest(approvalKey);
|
|
12495
|
+
if (!approval && opts.planId) {
|
|
12496
|
+
approvalKey = `plan:${opts.planId}`;
|
|
12497
|
+
approval = await store.getLatest(approvalKey);
|
|
12498
|
+
}
|
|
12499
|
+
} catch (err) {
|
|
12500
|
+
console.warn(`[merge-loop] approval-store 查询失败 (session=${opts.sessionId}): ${err instanceof Error ? err.message : String(err)}`);
|
|
12501
|
+
approval = undefined;
|
|
12502
|
+
}
|
|
12503
|
+
const valid = !!approval && (approval.verdict === "APPROVE" || approval.verdict === "APPROVE_WITH_NOTES");
|
|
12504
|
+
if (!valid) {
|
|
12505
|
+
const blockReason = buildApprovalGateBlockReason({
|
|
12506
|
+
sessionId: opts.sessionId,
|
|
12507
|
+
...opts.planId ? { planId: opts.planId } : {},
|
|
12508
|
+
approvalKey,
|
|
12509
|
+
storeVerdict: approval?.verdict ?? "NONE"
|
|
12510
|
+
});
|
|
12511
|
+
progress("block_pause", "approval-store 校验未通过");
|
|
12512
|
+
return {
|
|
12513
|
+
status: "blocked",
|
|
12514
|
+
loops,
|
|
12515
|
+
finalDecision: "APPROVE",
|
|
12516
|
+
blockReason,
|
|
12517
|
+
...lastReviewSummary ? { lastReviewSummary } : {}
|
|
12518
|
+
};
|
|
12519
|
+
}
|
|
12520
|
+
}
|
|
12408
12521
|
const { sha } = await mergeSessionBack({
|
|
12409
12522
|
sessionId: opts.sessionId,
|
|
12410
12523
|
mainRoot: opts.mainRoot
|
|
@@ -12494,6 +12607,22 @@ function buildForceMergeMessage(sessionId, entry) {
|
|
|
12494
12607
|
` + `Codeforge-Base: ${entry.baseSha.slice(0, 12)}
|
|
12495
12608
|
` + `Codeforge-Force-Merge: true`;
|
|
12496
12609
|
}
|
|
12610
|
+
function buildApprovalGateBlockReason(args) {
|
|
12611
|
+
return `reviewer 摘要 APPROVE 但 approval-store 无对应记录
|
|
12612
|
+
` + `(已查 session:${args.sessionId} 与 plan:${args.planId ?? "<无>"} 均无有效 APPROVE;` + `store verdict=${args.storeVerdict})
|
|
12613
|
+
|
|
12614
|
+
` + `可能原因:
|
|
12615
|
+
` + ` - reviewer agent 输出 APPROVE 但漏调 review_approval 工具写记录
|
|
12616
|
+
` + ` - codeforge-fallback 也未补写
|
|
12617
|
+
` + ` - 解析失真:摘要含 APPROVE 字面但实际未通过
|
|
12618
|
+
|
|
12619
|
+
` + `解除路径三选一:
|
|
12620
|
+
` + ` 1. 让 reviewer 重新跑一遍(重派 reviewer,确认调用 review_approval)
|
|
12621
|
+
` + ` 2. codeforge 手动补写:调 review_approval(verdict=APPROVE, pendingIds=["${args.approvalKey}"], source="codeforge-fallback")
|
|
12622
|
+
` + ` 3. 用户拍板:/merge --force 跳过 review(写 escape 审计)
|
|
12623
|
+
|
|
12624
|
+
` + `ADR: session-merge-approval-hard-gate`;
|
|
12625
|
+
}
|
|
12497
12626
|
async function maybeAbort(opts, config, entry) {
|
|
12498
12627
|
if (!opts.signal?.aborted)
|
|
12499
12628
|
return;
|
|
@@ -12783,8 +12912,8 @@ async function execute12(input) {
|
|
|
12783
12912
|
import { z as z13 } from "zod";
|
|
12784
12913
|
|
|
12785
12914
|
// lib/plan-store.ts
|
|
12786
|
-
import { promises as
|
|
12787
|
-
import * as
|
|
12915
|
+
import { promises as fs12 } from "node:fs";
|
|
12916
|
+
import * as path15 from "node:path";
|
|
12788
12917
|
var INDEX_VERSION = 1;
|
|
12789
12918
|
|
|
12790
12919
|
class PlanStore {
|
|
@@ -12793,8 +12922,8 @@ class PlanStore {
|
|
|
12793
12922
|
now;
|
|
12794
12923
|
secondCounters = new Map;
|
|
12795
12924
|
constructor(opts = {}) {
|
|
12796
|
-
this.root =
|
|
12797
|
-
this.base = opts.base ?
|
|
12925
|
+
this.root = path15.resolve(opts.root ?? process.cwd());
|
|
12926
|
+
this.base = opts.base ? path15.resolve(opts.base) : plansDir(this.root);
|
|
12798
12927
|
this.now = opts.now ?? (() => new Date);
|
|
12799
12928
|
}
|
|
12800
12929
|
async write(input) {
|
|
@@ -12804,14 +12933,14 @@ class PlanStore {
|
|
|
12804
12933
|
if (typeof input.content !== "string" || input.content.length === 0) {
|
|
12805
12934
|
throw new Error("PlanStore.write: content 不能为空");
|
|
12806
12935
|
}
|
|
12807
|
-
await
|
|
12936
|
+
await fs12.mkdir(this.base, { recursive: true });
|
|
12808
12937
|
const lockPath = this.lockPath();
|
|
12809
12938
|
return await withFileLock(lockPath, async () => {
|
|
12810
12939
|
const index = await this.readIndexLocked();
|
|
12811
12940
|
const now = this.now();
|
|
12812
12941
|
const planId = this.allocPlanId(now, index);
|
|
12813
12942
|
const filename = this.composeFilename(planId, input.title);
|
|
12814
|
-
const absFile =
|
|
12943
|
+
const absFile = path15.join(this.base, filename);
|
|
12815
12944
|
await this.atomicWriteFile(absFile, input.content);
|
|
12816
12945
|
const entry = {
|
|
12817
12946
|
plan_id: planId,
|
|
@@ -12835,9 +12964,9 @@ class PlanStore {
|
|
|
12835
12964
|
const entry = index.entries.find((e) => e.plan_id === planId);
|
|
12836
12965
|
if (!entry)
|
|
12837
12966
|
return null;
|
|
12838
|
-
const abs =
|
|
12967
|
+
const abs = path15.join(this.base, entry.path);
|
|
12839
12968
|
try {
|
|
12840
|
-
const content = await
|
|
12969
|
+
const content = await fs12.readFile(abs, "utf8");
|
|
12841
12970
|
return { entry, content };
|
|
12842
12971
|
} catch (err) {
|
|
12843
12972
|
const e = err;
|
|
@@ -12896,7 +13025,7 @@ class PlanStore {
|
|
|
12896
13025
|
else if (e.status === "orphan")
|
|
12897
13026
|
shouldDelete = true;
|
|
12898
13027
|
if (shouldDelete) {
|
|
12899
|
-
await
|
|
13028
|
+
await fs12.rm(path15.join(this.base, e.path), { force: true }).catch(() => {});
|
|
12900
13029
|
removed++;
|
|
12901
13030
|
} else {
|
|
12902
13031
|
keep.push(e);
|
|
@@ -12918,9 +13047,9 @@ class PlanStore {
|
|
|
12918
13047
|
knownPaths.add(e.path);
|
|
12919
13048
|
if (e.status !== "active")
|
|
12920
13049
|
continue;
|
|
12921
|
-
const abs =
|
|
13050
|
+
const abs = path15.join(this.base, e.path);
|
|
12922
13051
|
try {
|
|
12923
|
-
await
|
|
13052
|
+
await fs12.stat(abs);
|
|
12924
13053
|
} catch {
|
|
12925
13054
|
e.status = "orphan";
|
|
12926
13055
|
markedOrphan++;
|
|
@@ -12930,23 +13059,23 @@ class PlanStore {
|
|
|
12930
13059
|
await this.writeIndexLocked(index);
|
|
12931
13060
|
let unindexedFiles = [];
|
|
12932
13061
|
try {
|
|
12933
|
-
const all = await
|
|
13062
|
+
const all = await fs12.readdir(this.base);
|
|
12934
13063
|
unindexedFiles = all.filter((f) => f.endsWith(".md")).filter((f) => !knownPaths.has(f));
|
|
12935
13064
|
} catch {}
|
|
12936
13065
|
return { markedOrphan, unindexedFiles };
|
|
12937
13066
|
});
|
|
12938
13067
|
}
|
|
12939
13068
|
indexPath() {
|
|
12940
|
-
return
|
|
13069
|
+
return path15.join(this.base, "index.json");
|
|
12941
13070
|
}
|
|
12942
13071
|
lockPath() {
|
|
12943
|
-
return
|
|
13072
|
+
return path15.join(this.base, "index.lock");
|
|
12944
13073
|
}
|
|
12945
13074
|
async readIndexLocked() {
|
|
12946
13075
|
const file = this.indexPath();
|
|
12947
13076
|
let raw;
|
|
12948
13077
|
try {
|
|
12949
|
-
raw = await
|
|
13078
|
+
raw = await fs12.readFile(file, "utf8");
|
|
12950
13079
|
} catch (err) {
|
|
12951
13080
|
const e = err;
|
|
12952
13081
|
if (e.code === "ENOENT")
|
|
@@ -12968,7 +13097,7 @@ class PlanStore {
|
|
|
12968
13097
|
async archiveCorruptIndex(file) {
|
|
12969
13098
|
const ts = this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
12970
13099
|
const dst = `${file}.corrupt-${ts}`;
|
|
12971
|
-
await
|
|
13100
|
+
await fs12.rename(file, dst).catch(() => {});
|
|
12972
13101
|
}
|
|
12973
13102
|
async readIndex() {
|
|
12974
13103
|
return this.readIndexLocked();
|
|
@@ -13013,15 +13142,15 @@ class PlanStore {
|
|
|
13013
13142
|
const tsPart = m ? `${m[1]}-${m[2]}` : planId;
|
|
13014
13143
|
const nnn = m ? m[3] : "000";
|
|
13015
13144
|
const sample = planFilePath(this.root, title);
|
|
13016
|
-
const base =
|
|
13145
|
+
const base = path15.basename(sample, ".md");
|
|
13017
13146
|
const slug = base.replace(/^\d{8}-\d{6}-?/, "") || "untitled";
|
|
13018
13147
|
return `${tsPart}-${nnn}-${slug}.md`;
|
|
13019
13148
|
}
|
|
13020
13149
|
async atomicWriteFile(file, data) {
|
|
13021
|
-
await
|
|
13150
|
+
await fs12.mkdir(path15.dirname(file), { recursive: true });
|
|
13022
13151
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
13023
|
-
await
|
|
13024
|
-
await
|
|
13152
|
+
await fs12.writeFile(tmp, data, "utf8");
|
|
13153
|
+
await fs12.rename(tmp, file);
|
|
13025
13154
|
}
|
|
13026
13155
|
}
|
|
13027
13156
|
function formatTimestamp(d) {
|
|
@@ -13084,8 +13213,8 @@ async function execute13(input) {
|
|
|
13084
13213
|
}
|
|
13085
13214
|
}
|
|
13086
13215
|
// tools/plan-read.ts
|
|
13087
|
-
import { promises as
|
|
13088
|
-
import * as
|
|
13216
|
+
import { promises as fs13 } from "node:fs";
|
|
13217
|
+
import * as path16 from "node:path";
|
|
13089
13218
|
import { z as z14 } from "zod";
|
|
13090
13219
|
var description14 = [
|
|
13091
13220
|
"读取方案文档内容,支持按 plan_id 或绝对路径查询。",
|
|
@@ -13191,9 +13320,9 @@ async function execute14(input) {
|
|
|
13191
13320
|
};
|
|
13192
13321
|
}
|
|
13193
13322
|
}
|
|
13194
|
-
const abs =
|
|
13323
|
+
const abs = path16.resolve(args.path);
|
|
13195
13324
|
try {
|
|
13196
|
-
const content = await
|
|
13325
|
+
const content = await fs13.readFile(abs, "utf8");
|
|
13197
13326
|
return {
|
|
13198
13327
|
ok: true,
|
|
13199
13328
|
content,
|
|
@@ -13218,16 +13347,16 @@ import { z as z15 } from "zod";
|
|
|
13218
13347
|
// lib/adr-init.ts
|
|
13219
13348
|
import { spawnSync } from "node:child_process";
|
|
13220
13349
|
import { existsSync as existsSync4, promises as fsp } from "node:fs";
|
|
13221
|
-
import * as
|
|
13350
|
+
import * as path17 from "node:path";
|
|
13222
13351
|
import * as url from "node:url";
|
|
13223
13352
|
function resolveAssetsRoot() {
|
|
13224
|
-
const here =
|
|
13353
|
+
const here = path17.dirname(url.fileURLToPath(import.meta.url));
|
|
13225
13354
|
let dir = here;
|
|
13226
13355
|
for (let i = 0;i < 6; i++) {
|
|
13227
|
-
if (existsSync4(
|
|
13228
|
-
return
|
|
13356
|
+
if (existsSync4(path17.join(dir, "package.json")) && existsSync4(path17.join(dir, "assets", "adr-init"))) {
|
|
13357
|
+
return path17.join(dir, "assets", "adr-init");
|
|
13229
13358
|
}
|
|
13230
|
-
const parent =
|
|
13359
|
+
const parent = path17.dirname(dir);
|
|
13231
13360
|
if (parent === dir)
|
|
13232
13361
|
break;
|
|
13233
13362
|
dir = parent;
|
|
@@ -13235,13 +13364,13 @@ function resolveAssetsRoot() {
|
|
|
13235
13364
|
const xdgConfig = process.env["XDG_CONFIG_HOME"];
|
|
13236
13365
|
const homeDir = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "";
|
|
13237
13366
|
const fallbackRoots = [
|
|
13238
|
-
xdgConfig ?
|
|
13239
|
-
|
|
13240
|
-
process.env["APPDATA"] ?
|
|
13241
|
-
process.env["LOCALAPPDATA"] ?
|
|
13367
|
+
xdgConfig ? path17.join(xdgConfig, "opencode") : null,
|
|
13368
|
+
path17.join(homeDir, ".config", "opencode"),
|
|
13369
|
+
process.env["APPDATA"] ? path17.join(process.env["APPDATA"], "opencode") : null,
|
|
13370
|
+
process.env["LOCALAPPDATA"] ? path17.join(process.env["LOCALAPPDATA"], "opencode") : null
|
|
13242
13371
|
].filter(Boolean);
|
|
13243
13372
|
for (const root of fallbackRoots) {
|
|
13244
|
-
const candidate =
|
|
13373
|
+
const candidate = path17.join(root, "assets", "adr-init");
|
|
13245
13374
|
if (existsSync4(candidate)) {
|
|
13246
13375
|
return candidate;
|
|
13247
13376
|
}
|
|
@@ -13278,7 +13407,7 @@ function runGitConfigHooksPath(cwd) {
|
|
|
13278
13407
|
}
|
|
13279
13408
|
}
|
|
13280
13409
|
async function runAdrInit(opts = {}) {
|
|
13281
|
-
const cwd =
|
|
13410
|
+
const cwd = path17.resolve(opts.cwd ?? process.cwd());
|
|
13282
13411
|
const force = !!opts.force;
|
|
13283
13412
|
const dryRun = !!opts.dryRun;
|
|
13284
13413
|
const writePrepare = !!opts.writePrepare;
|
|
@@ -13327,8 +13456,8 @@ async function runAdrInit(opts = {}) {
|
|
|
13327
13456
|
});
|
|
13328
13457
|
}
|
|
13329
13458
|
for (const item of plan) {
|
|
13330
|
-
const srcAbs =
|
|
13331
|
-
const dstAbs =
|
|
13459
|
+
const srcAbs = path17.join(assetsRoot, item.src);
|
|
13460
|
+
const dstAbs = path17.join(cwd, item.dst);
|
|
13332
13461
|
if (!existsSync4(srcAbs)) {
|
|
13333
13462
|
result.warnings.push(`资产缺失:${item.src}(跳过 ${item.dst})`);
|
|
13334
13463
|
continue;
|
|
@@ -13341,7 +13470,7 @@ async function runAdrInit(opts = {}) {
|
|
|
13341
13470
|
const bakRel = `${item.dst}.bak.${ts}`;
|
|
13342
13471
|
if (!dryRun) {
|
|
13343
13472
|
try {
|
|
13344
|
-
await fsp.copyFile(dstAbs,
|
|
13473
|
+
await fsp.copyFile(dstAbs, path17.join(cwd, bakRel));
|
|
13345
13474
|
} catch (e) {
|
|
13346
13475
|
result.ok = false;
|
|
13347
13476
|
result.reason = "io_error";
|
|
@@ -13353,7 +13482,7 @@ async function runAdrInit(opts = {}) {
|
|
|
13353
13482
|
}
|
|
13354
13483
|
if (!dryRun) {
|
|
13355
13484
|
try {
|
|
13356
|
-
await fsp.mkdir(
|
|
13485
|
+
await fsp.mkdir(path17.dirname(dstAbs), { recursive: true });
|
|
13357
13486
|
await fsp.copyFile(srcAbs, dstAbs);
|
|
13358
13487
|
if (item.chmod !== undefined) {
|
|
13359
13488
|
try {
|
|
@@ -13383,7 +13512,7 @@ async function runAdrInit(opts = {}) {
|
|
|
13383
13512
|
} else {
|
|
13384
13513
|
result.suggestions.push("[dry-run] 将运行:git config core.hooksPath .githooks");
|
|
13385
13514
|
}
|
|
13386
|
-
const pkgPath =
|
|
13515
|
+
const pkgPath = path17.join(cwd, "package.json");
|
|
13387
13516
|
const isNpm = existsSync4(pkgPath);
|
|
13388
13517
|
if (isNpm) {
|
|
13389
13518
|
if (writePrepare) {
|
|
@@ -13398,7 +13527,7 @@ async function runAdrInit(opts = {}) {
|
|
|
13398
13527
|
const bakRel = `package.json.bak.${ts}`;
|
|
13399
13528
|
if (!dryRun) {
|
|
13400
13529
|
try {
|
|
13401
|
-
await fsp.copyFile(pkgPath,
|
|
13530
|
+
await fsp.copyFile(pkgPath, path17.join(cwd, bakRel));
|
|
13402
13531
|
} catch (e) {
|
|
13403
13532
|
result.warnings.push(`备份 package.json 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
13404
13533
|
}
|
|
@@ -13777,24 +13906,24 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
|
|
|
13777
13906
|
init_decision_parser();
|
|
13778
13907
|
|
|
13779
13908
|
// lib/parent-map-store.ts
|
|
13780
|
-
import { promises as
|
|
13781
|
-
import * as
|
|
13909
|
+
import { promises as fs14 } from "node:fs";
|
|
13910
|
+
import * as path18 from "node:path";
|
|
13782
13911
|
var PARENT_MAP_VERSION = 1;
|
|
13783
13912
|
var PARENT_MAP_LOCK_TIMEOUT_MS = 2000;
|
|
13784
13913
|
var PARENT_MAP_MAX_ENTRIES = 256;
|
|
13785
13914
|
function parentMapDir(mainRoot) {
|
|
13786
|
-
return
|
|
13915
|
+
return path18.join(runtimeDir(path18.resolve(mainRoot), { ensure: false }), "session-worktrees");
|
|
13787
13916
|
}
|
|
13788
13917
|
function parentMapPath(mainRoot) {
|
|
13789
|
-
return
|
|
13918
|
+
return path18.join(parentMapDir(mainRoot), "parent-map.json");
|
|
13790
13919
|
}
|
|
13791
13920
|
function parentMapLockPath(mainRoot) {
|
|
13792
|
-
return
|
|
13921
|
+
return path18.join(parentMapDir(mainRoot), "parent-map.lock");
|
|
13793
13922
|
}
|
|
13794
13923
|
async function readParentMapFile(mainRoot) {
|
|
13795
13924
|
const file = parentMapPath(mainRoot);
|
|
13796
13925
|
try {
|
|
13797
|
-
const raw = await
|
|
13926
|
+
const raw = await fs14.readFile(file, "utf8");
|
|
13798
13927
|
const parsed = JSON.parse(raw);
|
|
13799
13928
|
if (parsed.version !== PARENT_MAP_VERSION || !Array.isArray(parsed.entries)) {
|
|
13800
13929
|
return { version: PARENT_MAP_VERSION, entries: [] };
|
|
@@ -13809,10 +13938,10 @@ async function readParentMapFile(mainRoot) {
|
|
|
13809
13938
|
}
|
|
13810
13939
|
async function writeParentMapFile(mainRoot, payload) {
|
|
13811
13940
|
const file = parentMapPath(mainRoot);
|
|
13812
|
-
await
|
|
13941
|
+
await fs14.mkdir(path18.dirname(file), { recursive: true });
|
|
13813
13942
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
13814
|
-
await
|
|
13815
|
-
await
|
|
13943
|
+
await fs14.writeFile(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
13944
|
+
await fs14.rename(tmp, file);
|
|
13816
13945
|
}
|
|
13817
13946
|
function capEntriesByTsDesc(entries) {
|
|
13818
13947
|
if (entries.length <= PARENT_MAP_MAX_ENTRIES)
|
|
@@ -13844,7 +13973,7 @@ async function loadParentMap(mainRoot) {
|
|
|
13844
13973
|
}
|
|
13845
13974
|
async function mutateParentMap(mainRoot, mutator, opts = {}) {
|
|
13846
13975
|
const lockPath = parentMapLockPath(mainRoot);
|
|
13847
|
-
await
|
|
13976
|
+
await fs14.mkdir(path18.dirname(lockPath), { recursive: true });
|
|
13848
13977
|
const lockOpts = {
|
|
13849
13978
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
13850
13979
|
...opts
|
|
@@ -13863,7 +13992,7 @@ async function appendParentEntry(mainRoot, childID, parentID, ts, opts = {}) {
|
|
|
13863
13992
|
if (!childID || !parentID)
|
|
13864
13993
|
return;
|
|
13865
13994
|
const lockPath = parentMapLockPath(mainRoot);
|
|
13866
|
-
await
|
|
13995
|
+
await fs14.mkdir(path18.dirname(lockPath), { recursive: true });
|
|
13867
13996
|
const lockOpts = {
|
|
13868
13997
|
timeoutMs: opts.timeoutMs ?? PARENT_MAP_LOCK_TIMEOUT_MS,
|
|
13869
13998
|
...opts
|
|
@@ -14196,8 +14325,8 @@ function clip3(s, max) {
|
|
|
14196
14325
|
}
|
|
14197
14326
|
|
|
14198
14327
|
// lib/codeforge-runtime.ts
|
|
14199
|
-
import { promises as
|
|
14200
|
-
import * as
|
|
14328
|
+
import { promises as fs15 } from "node:fs";
|
|
14329
|
+
import * as path19 from "node:path";
|
|
14201
14330
|
var DEFAULT_RUNTIME = {
|
|
14202
14331
|
autonomy: {
|
|
14203
14332
|
downgrade_on_risky: true
|
|
@@ -14240,10 +14369,10 @@ function loadRuntimeSync(opts = {}) {
|
|
|
14240
14369
|
}
|
|
14241
14370
|
async function loadRuntime(opts = {}) {
|
|
14242
14371
|
const root = opts.root ?? process.cwd();
|
|
14243
|
-
const abs =
|
|
14372
|
+
const abs = path19.resolve(root, opts.file ?? CONFIG_FILE);
|
|
14244
14373
|
let raw;
|
|
14245
14374
|
try {
|
|
14246
|
-
raw = await
|
|
14375
|
+
raw = await fs15.readFile(abs, "utf8");
|
|
14247
14376
|
} catch (e) {
|
|
14248
14377
|
const code = e.code;
|
|
14249
14378
|
if (code === "ENOENT") {
|
|
@@ -14803,7 +14932,7 @@ var codeforgeToolsServer = async (ctx) => {
|
|
|
14803
14932
|
review_approval: tool({
|
|
14804
14933
|
description: description4,
|
|
14805
14934
|
args: {
|
|
14806
|
-
verdict: z16.enum(["APPROVE", "APPROVE_WITH_NOTES"]).describe("
|
|
14935
|
+
verdict: z16.enum(["APPROVE", "APPROVE_WITH_NOTES"]).describe("审批层裁决(审计字段,与协议层独立);REQUEST_CHANGES / BLOCK 不应调本工具。⚠️ 不要把本字段值复制到 reviewer 输出的 `## Decision` 节首行 — 那是协议层 3 档。"),
|
|
14807
14936
|
pendingIds: z16.array(z16.string().min(1)).min(1).describe("本次 APPROVE 覆盖的 pending change id 列表"),
|
|
14808
14937
|
notes: z16.string().min(1).max(2000).describe("审阅意见摘要(建议 ≤ 500 字)"),
|
|
14809
14938
|
decisionLine: z16.string().optional().describe("`## Decision` 节首行原文(默认 verdict 字面量,机审证据)"),
|
|
@@ -14964,7 +15093,7 @@ var handler7 = codeforgeToolsServer;
|
|
|
14964
15093
|
|
|
14965
15094
|
// plugins/discover-spec-suggest.ts
|
|
14966
15095
|
import { readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "node:fs";
|
|
14967
|
-
import { join as
|
|
15096
|
+
import { join as join16 } from "node:path";
|
|
14968
15097
|
|
|
14969
15098
|
// lib/handoff-schema.ts
|
|
14970
15099
|
import { z as z17 } from "zod";
|
|
@@ -15131,9 +15260,9 @@ function validateHandoff(rawYaml, fileSize) {
|
|
|
15131
15260
|
const result = HandoffSchema.safeParse(parsed);
|
|
15132
15261
|
if (!result.success) {
|
|
15133
15262
|
const first = result.error.issues[0];
|
|
15134
|
-
const
|
|
15263
|
+
const path20 = first?.path?.join(".") ?? "(root)";
|
|
15135
15264
|
const msg = first?.message ?? "unknown";
|
|
15136
|
-
return { ok: false, reason: `schema 校验失败:${
|
|
15265
|
+
return { ok: false, reason: `schema 校验失败:${path20}: ${msg}` };
|
|
15137
15266
|
}
|
|
15138
15267
|
return { ok: true, data: result.data, schemaVersion: result.data.schema_version };
|
|
15139
15268
|
}
|
|
@@ -15150,7 +15279,7 @@ var SESSION_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
|
15150
15279
|
var MATCH_THRESHOLD = 0.15;
|
|
15151
15280
|
var MAX_CANDIDATES = 3;
|
|
15152
15281
|
var NUDGE_MAX_LEN = 1500;
|
|
15153
|
-
var SPECS_REL_DIR =
|
|
15282
|
+
var SPECS_REL_DIR = join16("docs", "specs");
|
|
15154
15283
|
var sessionMap = new Map;
|
|
15155
15284
|
function pruneIfOversize2() {
|
|
15156
15285
|
while (sessionMap.size > SESSION_CAP2) {
|
|
@@ -15257,7 +15386,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15257
15386
|
const dirExists = opts.dirExists ?? defaultDirExists;
|
|
15258
15387
|
const statReader = opts.statReader ?? defaultStatReader;
|
|
15259
15388
|
const log6 = makePluginLogger(PLUGIN_NAME8);
|
|
15260
|
-
const specsRoot =
|
|
15389
|
+
const specsRoot = join16(rootDir, SPECS_REL_DIR);
|
|
15261
15390
|
const records = [];
|
|
15262
15391
|
if (!dirExists(specsRoot)) {
|
|
15263
15392
|
log6.info(`specs 目录不存在,plugin 将 no-op`, { specsRoot });
|
|
@@ -15278,7 +15407,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15278
15407
|
log6.info(`跳过非合法 slug 命名的条目`, { entry });
|
|
15279
15408
|
continue;
|
|
15280
15409
|
}
|
|
15281
|
-
const specDir =
|
|
15410
|
+
const specDir = join16(specsRoot, entry);
|
|
15282
15411
|
let dirStat;
|
|
15283
15412
|
try {
|
|
15284
15413
|
dirStat = statReader(specDir);
|
|
@@ -15291,7 +15420,7 @@ function loadSpecs(rootDir, opts = {}) {
|
|
|
15291
15420
|
}
|
|
15292
15421
|
if (!dirStat.isDirectory)
|
|
15293
15422
|
continue;
|
|
15294
|
-
const handoffPath =
|
|
15423
|
+
const handoffPath = join16(specDir, "handoff.yaml");
|
|
15295
15424
|
let fileStat;
|
|
15296
15425
|
try {
|
|
15297
15426
|
fileStat = statReader(handoffPath);
|
|
@@ -15463,14 +15592,14 @@ var discoverSpecSuggestServer = async (ctx) => {
|
|
|
15463
15592
|
var handler8 = discoverSpecSuggestServer;
|
|
15464
15593
|
|
|
15465
15594
|
// lib/memories.ts
|
|
15466
|
-
import { promises as
|
|
15467
|
-
import * as
|
|
15595
|
+
import { promises as fs16 } from "node:fs";
|
|
15596
|
+
import * as path20 from "node:path";
|
|
15468
15597
|
import * as os5 from "node:os";
|
|
15469
15598
|
function resolveConfig(c) {
|
|
15470
15599
|
return {
|
|
15471
15600
|
projectRoot: c.projectRoot,
|
|
15472
15601
|
homeDir: c.homeDir ?? os5.homedir(),
|
|
15473
|
-
projectName: c.projectName ??
|
|
15602
|
+
projectName: c.projectName ?? path20.basename(c.projectRoot),
|
|
15474
15603
|
now: c.now ?? Date.now,
|
|
15475
15604
|
log: c.log ?? (() => {}),
|
|
15476
15605
|
maxPerScope: c.maxPerScope ?? 1000
|
|
@@ -15478,13 +15607,13 @@ function resolveConfig(c) {
|
|
|
15478
15607
|
}
|
|
15479
15608
|
function fileFor(scope, cfg) {
|
|
15480
15609
|
if (scope === "project") {
|
|
15481
|
-
return
|
|
15610
|
+
return path20.join(cfg.projectRoot, ".codeforge", "memories.json");
|
|
15482
15611
|
}
|
|
15483
|
-
return
|
|
15612
|
+
return path20.join(cfg.homeDir, ".codeforge", "memories.json");
|
|
15484
15613
|
}
|
|
15485
15614
|
async function readBank(p) {
|
|
15486
15615
|
try {
|
|
15487
|
-
const raw = await
|
|
15616
|
+
const raw = await fs16.readFile(p, "utf8");
|
|
15488
15617
|
const arr = JSON.parse(raw);
|
|
15489
15618
|
if (!Array.isArray(arr))
|
|
15490
15619
|
return [];
|
|
@@ -15494,10 +15623,10 @@ async function readBank(p) {
|
|
|
15494
15623
|
}
|
|
15495
15624
|
}
|
|
15496
15625
|
async function writeBank(p, items) {
|
|
15497
|
-
await
|
|
15626
|
+
await fs16.mkdir(path20.dirname(p), { recursive: true });
|
|
15498
15627
|
const tmp = `${p}.tmp`;
|
|
15499
|
-
await
|
|
15500
|
-
await
|
|
15628
|
+
await fs16.writeFile(tmp, JSON.stringify(items, null, 2), "utf8");
|
|
15629
|
+
await fs16.rename(tmp, p);
|
|
15501
15630
|
}
|
|
15502
15631
|
function isMemory(x) {
|
|
15503
15632
|
if (!x || typeof x !== "object")
|
|
@@ -16015,7 +16144,7 @@ var handler10 = modelFallbackServer;
|
|
|
16015
16144
|
|
|
16016
16145
|
// plugins/subtask-heartbeat.ts
|
|
16017
16146
|
import { promises as fsPromises } from "node:fs";
|
|
16018
|
-
import * as
|
|
16147
|
+
import * as path21 from "node:path";
|
|
16019
16148
|
var recordSessionParent2 = recordSessionParent;
|
|
16020
16149
|
var lookupParentSessionId2 = lookupParentSessionId;
|
|
16021
16150
|
var deleteSessionParent2 = deleteSessionParent;
|
|
@@ -16362,7 +16491,7 @@ function buildErrorLogLine(errorReason, elapsedMs, now = Date.now()) {
|
|
|
16362
16491
|
}
|
|
16363
16492
|
async function appendSubagentLog(filePath, line, log7) {
|
|
16364
16493
|
try {
|
|
16365
|
-
await fsPromises.mkdir(
|
|
16494
|
+
await fsPromises.mkdir(path21.dirname(filePath), { recursive: true });
|
|
16366
16495
|
await fsPromises.appendFile(filePath, line + `
|
|
16367
16496
|
`, "utf8");
|
|
16368
16497
|
} catch (err) {
|
|
@@ -16730,7 +16859,7 @@ var handler12 = parallelStatusServer;
|
|
|
16730
16859
|
|
|
16731
16860
|
// plugins/parallel-tool-nudge.ts
|
|
16732
16861
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
|
|
16733
|
-
import { join as
|
|
16862
|
+
import { join as join18 } from "node:path";
|
|
16734
16863
|
import { homedir as homedir6 } from "node:os";
|
|
16735
16864
|
var PLUGIN_NAME13 = "parallel-tool-nudge";
|
|
16736
16865
|
logLifecycle(PLUGIN_NAME13, "import", {});
|
|
@@ -16783,10 +16912,10 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16783
16912
|
const reader = opts.reader ?? defaultReader2;
|
|
16784
16913
|
const dirReader = opts.dirReader ?? defaultDirReader2;
|
|
16785
16914
|
const dirExists = opts.dirExists ?? defaultDirExists2;
|
|
16786
|
-
const homeAgentsDir = opts.homeAgentsDir ??
|
|
16915
|
+
const homeAgentsDir = opts.homeAgentsDir ?? join18(homedir6(), ".config", "opencode", "agents");
|
|
16787
16916
|
const candidateDirs = [
|
|
16788
|
-
|
|
16789
|
-
|
|
16917
|
+
join18(rootDir, ".codeforge", "agents"),
|
|
16918
|
+
join18(rootDir, "agents"),
|
|
16790
16919
|
homeAgentsDir
|
|
16791
16920
|
];
|
|
16792
16921
|
const result = new Map;
|
|
@@ -16809,20 +16938,20 @@ function loadAgentToolsMap(rootDir, opts = {}) {
|
|
|
16809
16938
|
for (const entry of entries) {
|
|
16810
16939
|
if (!entry.endsWith(".md"))
|
|
16811
16940
|
continue;
|
|
16812
|
-
const
|
|
16941
|
+
const path22 = join18(dir, entry);
|
|
16813
16942
|
let content;
|
|
16814
16943
|
try {
|
|
16815
|
-
content = reader(
|
|
16944
|
+
content = reader(path22);
|
|
16816
16945
|
} catch (err) {
|
|
16817
16946
|
log8.warn(`agent.md 读取失败(已跳过)`, {
|
|
16818
|
-
path:
|
|
16947
|
+
path: path22,
|
|
16819
16948
|
error: err instanceof Error ? err.message : String(err)
|
|
16820
16949
|
});
|
|
16821
16950
|
continue;
|
|
16822
16951
|
}
|
|
16823
16952
|
const parsed = parseAgentFrontmatter(content);
|
|
16824
16953
|
if (!parsed) {
|
|
16825
|
-
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path:
|
|
16954
|
+
log8.warn(`agent frontmatter 解析失败(已跳过)`, { path: path22 });
|
|
16826
16955
|
continue;
|
|
16827
16956
|
}
|
|
16828
16957
|
if (result.has(parsed.name))
|
|
@@ -17013,18 +17142,18 @@ var handler14 = async (_ctx3) => {
|
|
|
17013
17142
|
};
|
|
17014
17143
|
|
|
17015
17144
|
// lib/event-stream.ts
|
|
17016
|
-
import { promises as
|
|
17017
|
-
import * as
|
|
17145
|
+
import { promises as fs17 } from "node:fs";
|
|
17146
|
+
import * as path22 from "node:path";
|
|
17018
17147
|
async function loadSession(id, opts = {}) {
|
|
17019
17148
|
const file = resolveSessionFile(id, opts);
|
|
17020
|
-
const raw = await
|
|
17149
|
+
const raw = await fs17.readFile(file, "utf8");
|
|
17021
17150
|
return parseJsonl(id, raw);
|
|
17022
17151
|
}
|
|
17023
17152
|
async function listSessions(opts = {}) {
|
|
17024
17153
|
const dir = resolveDir(opts);
|
|
17025
17154
|
let entries;
|
|
17026
17155
|
try {
|
|
17027
|
-
entries = await
|
|
17156
|
+
entries = await fs17.readdir(dir, { withFileTypes: true });
|
|
17028
17157
|
} catch (err) {
|
|
17029
17158
|
if (err.code === "ENOENT")
|
|
17030
17159
|
return [];
|
|
@@ -17034,10 +17163,10 @@ async function listSessions(opts = {}) {
|
|
|
17034
17163
|
for (const e of entries) {
|
|
17035
17164
|
if (!e.isFile() || !e.name.endsWith(".jsonl"))
|
|
17036
17165
|
continue;
|
|
17037
|
-
const file =
|
|
17166
|
+
const file = path22.join(dir, e.name);
|
|
17038
17167
|
const id = e.name.replace(/\.jsonl$/, "");
|
|
17039
17168
|
try {
|
|
17040
|
-
const stat = await
|
|
17169
|
+
const stat = await fs17.stat(file);
|
|
17041
17170
|
const headerLine = await readFirstLine(file);
|
|
17042
17171
|
let started_at = stat.birthtimeMs;
|
|
17043
17172
|
if (headerLine) {
|
|
@@ -17061,11 +17190,11 @@ async function listSessions(opts = {}) {
|
|
|
17061
17190
|
return out;
|
|
17062
17191
|
}
|
|
17063
17192
|
function resolveDir(opts = {}) {
|
|
17064
|
-
const root =
|
|
17065
|
-
return opts.sessions_dir ?
|
|
17193
|
+
const root = path22.resolve(opts.root ?? process.cwd());
|
|
17194
|
+
return opts.sessions_dir ? path22.resolve(root, opts.sessions_dir) : path22.join(runtimeDir(root), "sessions");
|
|
17066
17195
|
}
|
|
17067
17196
|
function resolveSessionFile(id, opts = {}) {
|
|
17068
|
-
return
|
|
17197
|
+
return path22.join(resolveDir(opts), `${id}.jsonl`);
|
|
17069
17198
|
}
|
|
17070
17199
|
function parseJsonl(id, raw) {
|
|
17071
17200
|
const events = [];
|
|
@@ -17100,7 +17229,7 @@ function isEvent(obj) {
|
|
|
17100
17229
|
}
|
|
17101
17230
|
async function readFirstLine(file) {
|
|
17102
17231
|
const buf = Buffer.alloc(4096);
|
|
17103
|
-
const fh = await
|
|
17232
|
+
const fh = await fs17.open(file, "r");
|
|
17104
17233
|
try {
|
|
17105
17234
|
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
17106
17235
|
const s = buf.subarray(0, bytesRead).toString("utf8");
|
|
@@ -17328,11 +17457,11 @@ function isRecoveryWorthShowing(plan) {
|
|
|
17328
17457
|
}
|
|
17329
17458
|
|
|
17330
17459
|
// lib/block-pending.ts
|
|
17331
|
-
import { promises as
|
|
17332
|
-
import * as
|
|
17460
|
+
import { promises as fs18 } from "node:fs";
|
|
17461
|
+
import * as path23 from "node:path";
|
|
17333
17462
|
function blockPendingFilePath(absRoot) {
|
|
17334
17463
|
const rd = runtimeDir(absRoot, { ensure: false });
|
|
17335
|
-
return
|
|
17464
|
+
return path23.join(rd, "sessions", "autonomous-blocks.ndjson");
|
|
17336
17465
|
}
|
|
17337
17466
|
function consumeLockPath(absRoot) {
|
|
17338
17467
|
return blockPendingFilePath(absRoot) + ".consume.lock";
|
|
@@ -17341,7 +17470,7 @@ async function scanBlockPending(absRoot, filterSessionId) {
|
|
|
17341
17470
|
const file = blockPendingFilePath(absRoot);
|
|
17342
17471
|
let raw;
|
|
17343
17472
|
try {
|
|
17344
|
-
raw = await
|
|
17473
|
+
raw = await fs18.readFile(file, "utf8");
|
|
17345
17474
|
} catch {
|
|
17346
17475
|
return [];
|
|
17347
17476
|
}
|
|
@@ -17397,7 +17526,7 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
17397
17526
|
if (entries.length === 0)
|
|
17398
17527
|
return;
|
|
17399
17528
|
const file = blockPendingFilePath(absRoot);
|
|
17400
|
-
await
|
|
17529
|
+
await fs18.mkdir(path23.dirname(file), { recursive: true });
|
|
17401
17530
|
const now = new Date().toISOString();
|
|
17402
17531
|
const lines = entries.map((e) => ({
|
|
17403
17532
|
type: "consume",
|
|
@@ -17408,7 +17537,7 @@ async function markBlocksConsumed(absRoot, entries) {
|
|
|
17408
17537
|
`) + `
|
|
17409
17538
|
`;
|
|
17410
17539
|
await withFileLock(consumeLockPath(absRoot), async () => {
|
|
17411
|
-
await
|
|
17540
|
+
await fs18.appendFile(file, lines, "utf8");
|
|
17412
17541
|
});
|
|
17413
17542
|
}
|
|
17414
17543
|
|
|
@@ -17546,8 +17675,8 @@ var sessionRecoveryServer = async (ctx) => {
|
|
|
17546
17675
|
var handler15 = sessionRecoveryServer;
|
|
17547
17676
|
|
|
17548
17677
|
// plugins/subtasks.ts
|
|
17549
|
-
import { promises as
|
|
17550
|
-
import * as
|
|
17678
|
+
import { promises as fs19 } from "node:fs";
|
|
17679
|
+
import * as path24 from "node:path";
|
|
17551
17680
|
|
|
17552
17681
|
// lib/parallel-merge.ts
|
|
17553
17682
|
init_worktree_ops();
|
|
@@ -18371,7 +18500,7 @@ function sleep2(ms) {
|
|
|
18371
18500
|
// plugins/subtasks.ts
|
|
18372
18501
|
var PLUGIN_NAME16 = "subtasks";
|
|
18373
18502
|
function getLogFile(root = process.cwd()) {
|
|
18374
|
-
return
|
|
18503
|
+
return path24.join(runtimeDir(root), "logs", "subtasks.log");
|
|
18375
18504
|
}
|
|
18376
18505
|
var VERB_RE = /^([a-zA-Z]{3,12})/;
|
|
18377
18506
|
var CN_VERBS = [
|
|
@@ -18676,8 +18805,8 @@ async function writeLog(level, msg, data) {
|
|
|
18676
18805
|
`;
|
|
18677
18806
|
try {
|
|
18678
18807
|
const logFile = getLogFile();
|
|
18679
|
-
await
|
|
18680
|
-
await
|
|
18808
|
+
await fs19.mkdir(path24.dirname(logFile), { recursive: true });
|
|
18809
|
+
await fs19.appendFile(logFile, line, "utf8");
|
|
18681
18810
|
} catch {}
|
|
18682
18811
|
}
|
|
18683
18812
|
logLifecycle(PLUGIN_NAME16, "import");
|
|
@@ -19308,8 +19437,8 @@ var tokenManagerServer = async (ctx) => {
|
|
|
19308
19437
|
var handler18 = tokenManagerServer;
|
|
19309
19438
|
|
|
19310
19439
|
// plugins/tool-policy.ts
|
|
19311
|
-
import { promises as
|
|
19312
|
-
import * as
|
|
19440
|
+
import { promises as fs20 } from "node:fs";
|
|
19441
|
+
import * as path26 from "node:path";
|
|
19313
19442
|
|
|
19314
19443
|
// lib/tool-risk.ts
|
|
19315
19444
|
var RISK_PATTERNS = [
|
|
@@ -19463,7 +19592,7 @@ function buildHaystackFor(args, matchOn) {
|
|
|
19463
19592
|
}
|
|
19464
19593
|
|
|
19465
19594
|
// lib/file-regex-acl.ts
|
|
19466
|
-
import * as
|
|
19595
|
+
import * as path25 from "node:path";
|
|
19467
19596
|
function compileRule(r) {
|
|
19468
19597
|
if (r instanceof RegExp)
|
|
19469
19598
|
return r;
|
|
@@ -19529,7 +19658,7 @@ function normalizePath2(p) {
|
|
|
19529
19658
|
let s = p.replace(/\\/g, "/");
|
|
19530
19659
|
if (s.startsWith("./"))
|
|
19531
19660
|
s = s.slice(2);
|
|
19532
|
-
s =
|
|
19661
|
+
s = path25.posix.normalize(s);
|
|
19533
19662
|
return s;
|
|
19534
19663
|
}
|
|
19535
19664
|
function checkFileAccess(acl, file, op) {
|
|
@@ -19632,11 +19761,11 @@ function decideToolCall(ctx, cfg = {}, currentAgent) {
|
|
|
19632
19761
|
const action = risks.length > 0 || worstAcl === "deny" ? "deny" : "allow";
|
|
19633
19762
|
return { action, reasons, risks, acl: aclResults };
|
|
19634
19763
|
}
|
|
19635
|
-
var POLICY_PATH =
|
|
19764
|
+
var POLICY_PATH = path26.join(".codeforge", "policy.json");
|
|
19636
19765
|
async function loadPolicy(root = process.cwd()) {
|
|
19637
|
-
const file =
|
|
19766
|
+
const file = path26.join(root, POLICY_PATH);
|
|
19638
19767
|
try {
|
|
19639
|
-
const raw = await
|
|
19768
|
+
const raw = await fs20.readFile(file, "utf8");
|
|
19640
19769
|
const data = JSON.parse(raw);
|
|
19641
19770
|
return data;
|
|
19642
19771
|
} catch {
|
|
@@ -19734,7 +19863,7 @@ var handler19 = toolPolicyServer;
|
|
|
19734
19863
|
// plugins/update-checker.ts
|
|
19735
19864
|
import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
19736
19865
|
import { homedir as homedir8 } from "node:os";
|
|
19737
|
-
import { join as
|
|
19866
|
+
import { join as join24 } from "node:path";
|
|
19738
19867
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
19739
19868
|
|
|
19740
19869
|
// lib/update-checker-impl.ts
|
|
@@ -19752,7 +19881,7 @@ import {
|
|
|
19752
19881
|
writeFileSync as writeFileSync2
|
|
19753
19882
|
} from "node:fs";
|
|
19754
19883
|
import { homedir as homedir7, tmpdir } from "node:os";
|
|
19755
|
-
import { dirname as dirname14, join as
|
|
19884
|
+
import { dirname as dirname14, join as join23 } from "node:path";
|
|
19756
19885
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19757
19886
|
import * as https from "node:https";
|
|
19758
19887
|
import * as zlib from "node:zlib";
|
|
@@ -19760,7 +19889,7 @@ import * as zlib from "node:zlib";
|
|
|
19760
19889
|
// lib/version-injected.ts
|
|
19761
19890
|
function getInjectedVersion() {
|
|
19762
19891
|
try {
|
|
19763
|
-
const v = "0.6.
|
|
19892
|
+
const v = "0.6.6";
|
|
19764
19893
|
if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
|
|
19765
19894
|
return v;
|
|
19766
19895
|
}
|
|
@@ -19850,17 +19979,17 @@ function readLocalVersion() {
|
|
|
19850
19979
|
try {
|
|
19851
19980
|
const here = fileURLToPath2(import.meta.url);
|
|
19852
19981
|
const root = dirname14(dirname14(here));
|
|
19853
|
-
const pkg = JSON.parse(readFileSync5(
|
|
19982
|
+
const pkg = JSON.parse(readFileSync5(join23(root, "package.json"), "utf8"));
|
|
19854
19983
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
19855
19984
|
} catch {
|
|
19856
19985
|
return "0.0.0";
|
|
19857
19986
|
}
|
|
19858
19987
|
}
|
|
19859
19988
|
function defaultCacheDir() {
|
|
19860
|
-
return process.env["CODEFORGE_CACHE_DIR"] ??
|
|
19989
|
+
return process.env["CODEFORGE_CACHE_DIR"] ?? join23(homedir7(), ".cache", "codeforge");
|
|
19861
19990
|
}
|
|
19862
19991
|
function defaultCacheFile() {
|
|
19863
|
-
return
|
|
19992
|
+
return join23(defaultCacheDir(), "update-check.json");
|
|
19864
19993
|
}
|
|
19865
19994
|
function readCache(file) {
|
|
19866
19995
|
try {
|
|
@@ -20016,14 +20145,14 @@ function defaultHttpFetcher(url2, timeoutMs) {
|
|
|
20016
20145
|
});
|
|
20017
20146
|
}
|
|
20018
20147
|
async function downloadAndExtractBundle(opts) {
|
|
20019
|
-
const tmpRoot = opts.tmpDir ?? mkdtempSync(
|
|
20148
|
+
const tmpRoot = opts.tmpDir ?? mkdtempSync(join23(tmpdir(), "codeforge-update-"));
|
|
20020
20149
|
mkdirSync3(tmpRoot, { recursive: true });
|
|
20021
20150
|
const fetcher = opts.tarballFetcher ?? defaultBinaryFetcher;
|
|
20022
20151
|
const tarballBuf = await fetcher(opts.tarballUrl);
|
|
20023
20152
|
verifyIntegrity(tarballBuf, opts.expectedIntegrity);
|
|
20024
20153
|
const tarBuf = zlib.gunzipSync(tarballBuf);
|
|
20025
20154
|
extractTarToDir(tarBuf, tmpRoot);
|
|
20026
|
-
const bundlePath =
|
|
20155
|
+
const bundlePath = join23(tmpRoot, "package", "dist", "index.js");
|
|
20027
20156
|
if (!existsSync5(bundlePath)) {
|
|
20028
20157
|
throw new Error(`bundle_not_found: ${bundlePath}`);
|
|
20029
20158
|
}
|
|
@@ -20063,11 +20192,11 @@ function extractTarToDir(tarBuf, destRoot) {
|
|
|
20063
20192
|
offset += 512;
|
|
20064
20193
|
if (typeFlag === "0" || typeFlag === "" || typeFlag === "\x00") {
|
|
20065
20194
|
const fileBuf = tarBuf.subarray(offset, offset + size);
|
|
20066
|
-
const dest =
|
|
20195
|
+
const dest = join23(destRoot, fullName);
|
|
20067
20196
|
mkdirSync3(dirname14(dest), { recursive: true });
|
|
20068
20197
|
writeFileSync2(dest, fileBuf);
|
|
20069
20198
|
} else if (typeFlag === "5") {
|
|
20070
|
-
mkdirSync3(
|
|
20199
|
+
mkdirSync3(join23(destRoot, fullName), { recursive: true });
|
|
20071
20200
|
}
|
|
20072
20201
|
offset += Math.ceil(size / 512) * 512;
|
|
20073
20202
|
}
|
|
@@ -20176,7 +20305,7 @@ function cleanupOldBackups(target, keep) {
|
|
|
20176
20305
|
const base = target.substring(dir.length + 1);
|
|
20177
20306
|
const prefix = `${base}.bak.`;
|
|
20178
20307
|
const all = readdirSync3(dir).filter((f) => f.startsWith(prefix)).map((f) => {
|
|
20179
|
-
const full =
|
|
20308
|
+
const full = join23(dir, f);
|
|
20180
20309
|
let mtimeMs = 0;
|
|
20181
20310
|
try {
|
|
20182
20311
|
mtimeMs = statSync4(full).mtimeMs;
|
|
@@ -20198,7 +20327,7 @@ function loadCompatibility(opts) {
|
|
|
20198
20327
|
const root = opts?.cwd ?? inferPluginRoot();
|
|
20199
20328
|
if (!root)
|
|
20200
20329
|
return null;
|
|
20201
|
-
file =
|
|
20330
|
+
file = join23(root, "compatibility.json");
|
|
20202
20331
|
}
|
|
20203
20332
|
if (!existsSync5(file))
|
|
20204
20333
|
return null;
|
|
@@ -20389,10 +20518,11 @@ var updateCheckerServer = async (ctx) => {
|
|
|
20389
20518
|
expectedIntegrity: npmResult.integrity
|
|
20390
20519
|
});
|
|
20391
20520
|
try {
|
|
20392
|
-
const installMjs =
|
|
20521
|
+
const installMjs = join24(extractDir, "package", "install.mjs");
|
|
20393
20522
|
if (existsSync6(installMjs)) {
|
|
20394
|
-
const
|
|
20395
|
-
|
|
20523
|
+
const nodeBin = resolveNodeBin();
|
|
20524
|
+
const r = spawnSync2(nodeBin, [installMjs, "--global", "--skip-build"], {
|
|
20525
|
+
cwd: join24(extractDir, "package"),
|
|
20396
20526
|
stdio: "pipe",
|
|
20397
20527
|
encoding: "utf8"
|
|
20398
20528
|
});
|
|
@@ -20411,6 +20541,7 @@ var updateCheckerServer = async (ctx) => {
|
|
|
20411
20541
|
safeWriteLog(PLUGIN_NAME20, {
|
|
20412
20542
|
level: "warn",
|
|
20413
20543
|
msg: "install_mjs_failed",
|
|
20544
|
+
nodeBin,
|
|
20414
20545
|
status: r.status,
|
|
20415
20546
|
stderr: r.stderr?.slice(0, 500),
|
|
20416
20547
|
stdout: r.stdout?.slice(0, 500)
|
|
@@ -20449,6 +20580,41 @@ var updateCheckerServer = async (ctx) => {
|
|
|
20449
20580
|
});
|
|
20450
20581
|
return {};
|
|
20451
20582
|
};
|
|
20583
|
+
function resolveNodeBin() {
|
|
20584
|
+
const IS_WIN = process.platform === "win32";
|
|
20585
|
+
try {
|
|
20586
|
+
const r = spawnSync2(IS_WIN ? "where" : "which", ["node"], {
|
|
20587
|
+
encoding: "utf8",
|
|
20588
|
+
stdio: "pipe"
|
|
20589
|
+
});
|
|
20590
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
20591
|
+
const first = r.stdout.trim().split(/\r?\n/)[0].trim();
|
|
20592
|
+
if (first)
|
|
20593
|
+
return first;
|
|
20594
|
+
}
|
|
20595
|
+
} catch {}
|
|
20596
|
+
const candidates = IS_WIN ? [
|
|
20597
|
+
"C:\\Program Files\\nodejs\\node.exe",
|
|
20598
|
+
"C:\\Program Files (x86)\\nodejs\\node.exe"
|
|
20599
|
+
] : [
|
|
20600
|
+
"/usr/local/bin/node",
|
|
20601
|
+
"/usr/bin/node",
|
|
20602
|
+
"/opt/homebrew/bin/node",
|
|
20603
|
+
"/opt/homebrew/opt/node/bin/node"
|
|
20604
|
+
];
|
|
20605
|
+
for (const c of candidates) {
|
|
20606
|
+
try {
|
|
20607
|
+
const t = spawnSync2(c, ["--version"], {
|
|
20608
|
+
encoding: "utf8",
|
|
20609
|
+
stdio: "pipe",
|
|
20610
|
+
timeout: 2000
|
|
20611
|
+
});
|
|
20612
|
+
if (t.status === 0)
|
|
20613
|
+
return c;
|
|
20614
|
+
} catch {}
|
|
20615
|
+
}
|
|
20616
|
+
return process.execPath;
|
|
20617
|
+
}
|
|
20452
20618
|
function detectOpencodeVersion() {
|
|
20453
20619
|
const env = process.env["OPENCODE_VERSION"];
|
|
20454
20620
|
if (env && env.trim().length > 0)
|
|
@@ -20457,14 +20623,14 @@ function detectOpencodeVersion() {
|
|
|
20457
20623
|
}
|
|
20458
20624
|
function getOpencodeBundlePath() {
|
|
20459
20625
|
const candidates = [];
|
|
20460
|
-
candidates.push(
|
|
20626
|
+
candidates.push(join24(homedir8(), ".config", "opencode", "codeforge", "index.js"));
|
|
20461
20627
|
if (process.platform === "win32") {
|
|
20462
20628
|
const appData = process.env["APPDATA"];
|
|
20463
20629
|
if (appData)
|
|
20464
|
-
candidates.push(
|
|
20630
|
+
candidates.push(join24(appData, "opencode", "codeforge", "index.js"));
|
|
20465
20631
|
const localAppData = process.env["LOCALAPPDATA"];
|
|
20466
20632
|
if (localAppData)
|
|
20467
|
-
candidates.push(
|
|
20633
|
+
candidates.push(join24(localAppData, "opencode", "codeforge", "index.js"));
|
|
20468
20634
|
}
|
|
20469
20635
|
for (const c of candidates) {
|
|
20470
20636
|
if (existsSync6(c))
|
|
@@ -20525,11 +20691,11 @@ async function postToast(ctx, message) {
|
|
|
20525
20691
|
var handler20 = updateCheckerServer;
|
|
20526
20692
|
|
|
20527
20693
|
// plugins/workflow-engine.ts
|
|
20528
|
-
import * as
|
|
20694
|
+
import * as path28 from "node:path";
|
|
20529
20695
|
|
|
20530
20696
|
// lib/workflow-loader.ts
|
|
20531
|
-
import { promises as
|
|
20532
|
-
import * as
|
|
20697
|
+
import { promises as fs21 } from "node:fs";
|
|
20698
|
+
import * as path27 from "node:path";
|
|
20533
20699
|
import { z as z18 } from "zod";
|
|
20534
20700
|
var ActionSchema = z18.object({
|
|
20535
20701
|
tool: z18.string().min(1, "action.tool 不能为空"),
|
|
@@ -20615,7 +20781,7 @@ function parseWorkflowYaml(yaml, sourcePath = "<inline>") {
|
|
|
20615
20781
|
async function loadWorkflowFromFile(filePath) {
|
|
20616
20782
|
let txt;
|
|
20617
20783
|
try {
|
|
20618
|
-
txt = await
|
|
20784
|
+
txt = await fs21.readFile(filePath, "utf8");
|
|
20619
20785
|
} catch (err) {
|
|
20620
20786
|
return {
|
|
20621
20787
|
ok: false,
|
|
@@ -20630,7 +20796,7 @@ async function loadWorkflowsFromDir(dir) {
|
|
|
20630
20796
|
const failed = [];
|
|
20631
20797
|
let entries;
|
|
20632
20798
|
try {
|
|
20633
|
-
entries = await
|
|
20799
|
+
entries = await fs21.readdir(dir);
|
|
20634
20800
|
} catch (err) {
|
|
20635
20801
|
const e = err;
|
|
20636
20802
|
if (e.code === "ENOENT")
|
|
@@ -20642,7 +20808,7 @@ async function loadWorkflowsFromDir(dir) {
|
|
|
20642
20808
|
continue;
|
|
20643
20809
|
if (!/\.ya?ml$/i.test(name))
|
|
20644
20810
|
continue;
|
|
20645
|
-
const full =
|
|
20811
|
+
const full = path27.join(dir, name);
|
|
20646
20812
|
const r = await loadWorkflowFromFile(full);
|
|
20647
20813
|
if (r.ok)
|
|
20648
20814
|
loaded.push(r);
|
|
@@ -21032,7 +21198,7 @@ async function handleCommandInvoked(raw, workflowsDir = "workflows") {
|
|
|
21032
21198
|
}
|
|
21033
21199
|
var workflowEngineServer = async (ctx) => {
|
|
21034
21200
|
const directory = ctx.directory ?? process.cwd();
|
|
21035
|
-
const workflowsDir =
|
|
21201
|
+
const workflowsDir = path28.join(directory, "workflows");
|
|
21036
21202
|
ensureRegistry(workflowsDir).catch((err) => fallbackLog2.warn(`[${PLUGIN_NAME21}] preload workflows failed`, {
|
|
21037
21203
|
error: err instanceof Error ? err.message : String(err)
|
|
21038
21204
|
}));
|
|
@@ -21076,7 +21242,8 @@ var workflowEngineServer = async (ctx) => {
|
|
|
21076
21242
|
var handler21 = workflowEngineServer;
|
|
21077
21243
|
|
|
21078
21244
|
// plugins/session-worktree-guard.ts
|
|
21079
|
-
import
|
|
21245
|
+
import path29 from "node:path";
|
|
21246
|
+
import { stat } from "node:fs/promises";
|
|
21080
21247
|
var PLUGIN_NAME22 = "session-worktree-guard";
|
|
21081
21248
|
logLifecycle(PLUGIN_NAME22, "import", {});
|
|
21082
21249
|
var WRITE_INTENT_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
|
|
@@ -21188,23 +21355,23 @@ var MERGE_CALLER_WHITELIST = new Set([
|
|
|
21188
21355
|
var FORCE_MERGE_CALLER_WHITELIST = new Set([
|
|
21189
21356
|
"codeforge"
|
|
21190
21357
|
]);
|
|
21191
|
-
var CODEFORGE_WORKTREE_DIR_NAME =
|
|
21358
|
+
var CODEFORGE_WORKTREE_DIR_NAME = path29.join(".git", "codeforge-worktrees");
|
|
21192
21359
|
function worktreesRoot(mainRoot) {
|
|
21193
|
-
return
|
|
21360
|
+
return path29.join(mainRoot, CODEFORGE_WORKTREE_DIR_NAME);
|
|
21194
21361
|
}
|
|
21195
21362
|
function isInsideAnyWorktreeDir(absPath, mainRoot) {
|
|
21196
|
-
if (!
|
|
21363
|
+
if (!path29.isAbsolute(absPath))
|
|
21197
21364
|
return false;
|
|
21198
21365
|
const root = worktreesRoot(mainRoot);
|
|
21199
21366
|
if (absPath === root)
|
|
21200
21367
|
return false;
|
|
21201
|
-
const prefix = root.endsWith(
|
|
21368
|
+
const prefix = root.endsWith(path29.sep) ? root : root + path29.sep;
|
|
21202
21369
|
return absPath.startsWith(prefix);
|
|
21203
21370
|
}
|
|
21204
21371
|
function rewritePath(value, mainRoot, worktreeRoot) {
|
|
21205
21372
|
if (!value)
|
|
21206
21373
|
return null;
|
|
21207
|
-
const resolved =
|
|
21374
|
+
const resolved = path29.isAbsolute(value) ? value : path29.resolve(mainRoot, value);
|
|
21208
21375
|
const wtPrefix2 = worktreeRoot.endsWith("/") ? worktreeRoot : worktreeRoot + "/";
|
|
21209
21376
|
if (resolved === worktreeRoot || resolved.startsWith(wtPrefix2)) {
|
|
21210
21377
|
return null;
|
|
@@ -21242,7 +21409,7 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
|
|
|
21242
21409
|
}
|
|
21243
21410
|
}
|
|
21244
21411
|
const wtRoot = worktreesRoot(mainRoot);
|
|
21245
|
-
const wtRootPrefix = wtRoot +
|
|
21412
|
+
const wtRootPrefix = wtRoot + path29.sep;
|
|
21246
21413
|
const escapedWtRootPrefix = escapeRegex(wtRootPrefix);
|
|
21247
21414
|
const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
|
|
21248
21415
|
const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
|
|
@@ -21297,12 +21464,24 @@ function collectWritePaths(toolName, argsObj, worktreeRoot) {
|
|
|
21297
21464
|
const candidate = toolName === "write" || toolName === "edit" ? argsObj["filePath"] : toolName === "ast_edit" ? argsObj["target"] : undefined;
|
|
21298
21465
|
if (typeof candidate !== "string" || candidate.length === 0)
|
|
21299
21466
|
return out;
|
|
21300
|
-
const abs =
|
|
21301
|
-
const rel =
|
|
21467
|
+
const abs = path29.isAbsolute(candidate) ? candidate : path29.resolve(worktreeRoot, candidate);
|
|
21468
|
+
const rel = path29.relative(worktreeRoot, abs).split(path29.sep).join("/");
|
|
21302
21469
|
out.push(rel);
|
|
21303
21470
|
return out;
|
|
21304
21471
|
}
|
|
21305
21472
|
var log13 = makePluginLogger(PLUGIN_NAME22);
|
|
21473
|
+
async function isCodeforgeManagedProject(mainRoot, opts = {}) {
|
|
21474
|
+
const env = opts.env ?? process.env;
|
|
21475
|
+
const force = env["CODEFORGE_FORCE_WORKTREE_GUARD"];
|
|
21476
|
+
if (force === "1" || force === "true" || force === "yes")
|
|
21477
|
+
return true;
|
|
21478
|
+
try {
|
|
21479
|
+
const st = await stat(path29.join(mainRoot, ".codeforge"));
|
|
21480
|
+
return st.isDirectory();
|
|
21481
|
+
} catch {
|
|
21482
|
+
return false;
|
|
21483
|
+
}
|
|
21484
|
+
}
|
|
21306
21485
|
function resolveMainRoot2(rawDir) {
|
|
21307
21486
|
const worktreeMarker = "/.git/codeforge-worktrees/";
|
|
21308
21487
|
const idx = rawDir.indexOf(worktreeMarker);
|
|
@@ -21325,6 +21504,17 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21325
21504
|
return {};
|
|
21326
21505
|
}
|
|
21327
21506
|
const mainRoot = resolveMainRoot2(ctx.directory ?? process.cwd());
|
|
21507
|
+
if (!await isCodeforgeManagedProject(mainRoot)) {
|
|
21508
|
+
log13.info("[guard] 当前项目无 .codeforge/ 目录,session-worktree-guard 不启用;" + "如需在此项目启用 worktree 隔离:mkdir .codeforge", { mainRoot });
|
|
21509
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
21510
|
+
hook: "activate",
|
|
21511
|
+
action: "skip",
|
|
21512
|
+
source: "non-codeforge-project",
|
|
21513
|
+
mainRoot
|
|
21514
|
+
});
|
|
21515
|
+
logLifecycle(PLUGIN_NAME22, "activate", { skipped: "non-codeforge-project", mainRoot });
|
|
21516
|
+
return {};
|
|
21517
|
+
}
|
|
21328
21518
|
let policyCfg = {};
|
|
21329
21519
|
try {
|
|
21330
21520
|
policyCfg = await loadPolicy(mainRoot);
|
|
@@ -21463,8 +21653,31 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21463
21653
|
if (toolName === "session_merge") {
|
|
21464
21654
|
const action = argsObj["action"];
|
|
21465
21655
|
if (action === "merge") {
|
|
21466
|
-
const caller = input.agent;
|
|
21467
|
-
|
|
21656
|
+
const caller = await resolveAgentForGuard({ sessionID: input.sessionID, agent: input.agent }, ctx.client, log13);
|
|
21657
|
+
const wantsForce = argsObj["force"] === true;
|
|
21658
|
+
if (caller === null && wantsForce) {
|
|
21659
|
+
const reason = `[session-worktree-guard] DENIED: session_merge action=merge force=true 且 agent 解析失败 (null);` + `force 旁路无 approval backstop,fail-closed 拒绝 (ADR:subagent-force-merge-hard-deny)`;
|
|
21660
|
+
log13.warn(reason, {
|
|
21661
|
+
sessionId,
|
|
21662
|
+
tool: toolName,
|
|
21663
|
+
action,
|
|
21664
|
+
caller: null,
|
|
21665
|
+
force: true
|
|
21666
|
+
});
|
|
21667
|
+
safeWriteLog(PLUGIN_NAME22, {
|
|
21668
|
+
hook: "tool.execute.before",
|
|
21669
|
+
tool: toolName,
|
|
21670
|
+
sessionID: input.sessionID,
|
|
21671
|
+
action: "deny",
|
|
21672
|
+
source: "merge-caller-null-force-fail-closed",
|
|
21673
|
+
caller: null,
|
|
21674
|
+
merge_action: "merge",
|
|
21675
|
+
force: true
|
|
21676
|
+
});
|
|
21677
|
+
denied = new DeniedError(reason);
|
|
21678
|
+
return;
|
|
21679
|
+
}
|
|
21680
|
+
if (caller !== null && !MERGE_CALLER_WHITELIST.has(caller)) {
|
|
21468
21681
|
const reason = `[session-worktree-guard] DENIED: session_merge action=merge 仅 codeforge orchestrator / discover / 用户可调;当前 caller=${caller}`;
|
|
21469
21682
|
log13.warn(reason, {
|
|
21470
21683
|
sessionId,
|
|
@@ -21484,8 +21697,7 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21484
21697
|
denied = new DeniedError(reason);
|
|
21485
21698
|
return;
|
|
21486
21699
|
}
|
|
21487
|
-
|
|
21488
|
-
if (wantsForce && caller !== undefined && !FORCE_MERGE_CALLER_WHITELIST.has(caller)) {
|
|
21700
|
+
if (wantsForce && caller !== null && !FORCE_MERGE_CALLER_WHITELIST.has(caller)) {
|
|
21489
21701
|
const reason = `[session-worktree-guard] DENIED: caller=${caller} 禁止 force=true;` + `跳过 review 闭环是破坏性操作,仅用户经 codeforge orchestrator 显式 /merge --force 才允许 ` + `(ADR:discover-self-merge-permission)`;
|
|
21490
21702
|
log13.warn(reason, {
|
|
21491
21703
|
sessionId,
|
|
@@ -21712,12 +21924,12 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
|
|
|
21712
21924
|
var handler22 = sessionWorktreeGuardPlugin;
|
|
21713
21925
|
|
|
21714
21926
|
// lib/opencode-session-probe.ts
|
|
21715
|
-
import * as
|
|
21927
|
+
import * as path30 from "node:path";
|
|
21716
21928
|
import * as os6 from "node:os";
|
|
21717
21929
|
import { createRequire as createRequire2 } from "node:module";
|
|
21718
21930
|
var requireFromHere = createRequire2(import.meta.url);
|
|
21719
21931
|
var DEFAULT_LIVENESS_MS = 6 * 60 * 60000;
|
|
21720
|
-
var DEFAULT_DB_PATH =
|
|
21932
|
+
var DEFAULT_DB_PATH = path30.join(os6.homedir(), ".local/share/opencode/opencode.db");
|
|
21721
21933
|
function createSessionProbe(opts = {}) {
|
|
21722
21934
|
const dbPath = opts.dbPath ?? DEFAULT_DB_PATH;
|
|
21723
21935
|
const httpBaseUrl = opts.httpBaseUrl ?? process.env["OPENCODE_SERVER_URL"];
|
|
@@ -21821,7 +22033,8 @@ var _pruneTimer;
|
|
|
21821
22033
|
var _probe = null;
|
|
21822
22034
|
var log14 = makePluginLogger(PLUGIN_NAME23);
|
|
21823
22035
|
var worktreeLifecyclePlugin = async (ctx) => {
|
|
21824
|
-
const
|
|
22036
|
+
const rawDir = ctx.directory ?? process.cwd();
|
|
22037
|
+
const mainRoot = resolveMainRoot2(rawDir);
|
|
21825
22038
|
logLifecycle(PLUGIN_NAME23, "activate", {
|
|
21826
22039
|
mainRoot: mainRoot ?? "(not set)",
|
|
21827
22040
|
idle_threshold_ms: IDLE_TOAST_THROTTLE_MS
|
|
@@ -21829,6 +22042,17 @@ var worktreeLifecyclePlugin = async (ctx) => {
|
|
|
21829
22042
|
if (!mainRoot) {
|
|
21830
22043
|
return {};
|
|
21831
22044
|
}
|
|
22045
|
+
if (!await isCodeforgeManagedProject(mainRoot)) {
|
|
22046
|
+
log14.info("[lifecycle] 当前项目无 .codeforge/ 目录,worktree-lifecycle 不启用", { mainRoot });
|
|
22047
|
+
safeWriteLog(PLUGIN_NAME23, {
|
|
22048
|
+
hook: "activate",
|
|
22049
|
+
action: "skip",
|
|
22050
|
+
source: "non-codeforge-project",
|
|
22051
|
+
mainRoot
|
|
22052
|
+
});
|
|
22053
|
+
logLifecycle(PLUGIN_NAME23, "activate", { skipped: "non-codeforge-project", mainRoot });
|
|
22054
|
+
return {};
|
|
22055
|
+
}
|
|
21832
22056
|
const client = ctx.client;
|
|
21833
22057
|
if (_probe) {
|
|
21834
22058
|
try {
|