@defend-tech/opencode-optima 0.1.76 → 0.1.77
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_Common.md +2 -1
- package/Agents_Common.prompt.md +2 -1
- package/assets/agents/workflow_product_manager.md +1 -1
- package/dist/index.js +1262 -1202
- package/dist/sanitize_cli.js +1293 -1233
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -108,17 +108,17 @@ var require_visit = __commonJS({
|
|
|
108
108
|
visit.BREAK = BREAK;
|
|
109
109
|
visit.SKIP = SKIP;
|
|
110
110
|
visit.REMOVE = REMOVE;
|
|
111
|
-
function visit_(key, node, visitor,
|
|
112
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
111
|
+
function visit_(key, node, visitor, path9) {
|
|
112
|
+
const ctrl = callVisitor(key, node, visitor, path9);
|
|
113
113
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
114
|
-
replaceNode(key,
|
|
115
|
-
return visit_(key, ctrl, visitor,
|
|
114
|
+
replaceNode(key, path9, ctrl);
|
|
115
|
+
return visit_(key, ctrl, visitor, path9);
|
|
116
116
|
}
|
|
117
117
|
if (typeof ctrl !== "symbol") {
|
|
118
118
|
if (identity.isCollection(node)) {
|
|
119
|
-
|
|
119
|
+
path9 = Object.freeze(path9.concat(node));
|
|
120
120
|
for (let i = 0; i < node.items.length; ++i) {
|
|
121
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
121
|
+
const ci = visit_(i, node.items[i], visitor, path9);
|
|
122
122
|
if (typeof ci === "number")
|
|
123
123
|
i = ci - 1;
|
|
124
124
|
else if (ci === BREAK)
|
|
@@ -129,13 +129,13 @@ var require_visit = __commonJS({
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
} else if (identity.isPair(node)) {
|
|
132
|
-
|
|
133
|
-
const ck = visit_("key", node.key, visitor,
|
|
132
|
+
path9 = Object.freeze(path9.concat(node));
|
|
133
|
+
const ck = visit_("key", node.key, visitor, path9);
|
|
134
134
|
if (ck === BREAK)
|
|
135
135
|
return BREAK;
|
|
136
136
|
else if (ck === REMOVE)
|
|
137
137
|
node.key = null;
|
|
138
|
-
const cv = visit_("value", node.value, visitor,
|
|
138
|
+
const cv = visit_("value", node.value, visitor, path9);
|
|
139
139
|
if (cv === BREAK)
|
|
140
140
|
return BREAK;
|
|
141
141
|
else if (cv === REMOVE)
|
|
@@ -156,17 +156,17 @@ var require_visit = __commonJS({
|
|
|
156
156
|
visitAsync.BREAK = BREAK;
|
|
157
157
|
visitAsync.SKIP = SKIP;
|
|
158
158
|
visitAsync.REMOVE = REMOVE;
|
|
159
|
-
async function visitAsync_(key, node, visitor,
|
|
160
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
159
|
+
async function visitAsync_(key, node, visitor, path9) {
|
|
160
|
+
const ctrl = await callVisitor(key, node, visitor, path9);
|
|
161
161
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
162
|
-
replaceNode(key,
|
|
163
|
-
return visitAsync_(key, ctrl, visitor,
|
|
162
|
+
replaceNode(key, path9, ctrl);
|
|
163
|
+
return visitAsync_(key, ctrl, visitor, path9);
|
|
164
164
|
}
|
|
165
165
|
if (typeof ctrl !== "symbol") {
|
|
166
166
|
if (identity.isCollection(node)) {
|
|
167
|
-
|
|
167
|
+
path9 = Object.freeze(path9.concat(node));
|
|
168
168
|
for (let i = 0; i < node.items.length; ++i) {
|
|
169
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
169
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path9);
|
|
170
170
|
if (typeof ci === "number")
|
|
171
171
|
i = ci - 1;
|
|
172
172
|
else if (ci === BREAK)
|
|
@@ -177,13 +177,13 @@ var require_visit = __commonJS({
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
} else if (identity.isPair(node)) {
|
|
180
|
-
|
|
181
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
180
|
+
path9 = Object.freeze(path9.concat(node));
|
|
181
|
+
const ck = await visitAsync_("key", node.key, visitor, path9);
|
|
182
182
|
if (ck === BREAK)
|
|
183
183
|
return BREAK;
|
|
184
184
|
else if (ck === REMOVE)
|
|
185
185
|
node.key = null;
|
|
186
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
186
|
+
const cv = await visitAsync_("value", node.value, visitor, path9);
|
|
187
187
|
if (cv === BREAK)
|
|
188
188
|
return BREAK;
|
|
189
189
|
else if (cv === REMOVE)
|
|
@@ -210,23 +210,23 @@ var require_visit = __commonJS({
|
|
|
210
210
|
}
|
|
211
211
|
return visitor;
|
|
212
212
|
}
|
|
213
|
-
function callVisitor(key, node, visitor,
|
|
213
|
+
function callVisitor(key, node, visitor, path9) {
|
|
214
214
|
if (typeof visitor === "function")
|
|
215
|
-
return visitor(key, node,
|
|
215
|
+
return visitor(key, node, path9);
|
|
216
216
|
if (identity.isMap(node))
|
|
217
|
-
return visitor.Map?.(key, node,
|
|
217
|
+
return visitor.Map?.(key, node, path9);
|
|
218
218
|
if (identity.isSeq(node))
|
|
219
|
-
return visitor.Seq?.(key, node,
|
|
219
|
+
return visitor.Seq?.(key, node, path9);
|
|
220
220
|
if (identity.isPair(node))
|
|
221
|
-
return visitor.Pair?.(key, node,
|
|
221
|
+
return visitor.Pair?.(key, node, path9);
|
|
222
222
|
if (identity.isScalar(node))
|
|
223
|
-
return visitor.Scalar?.(key, node,
|
|
223
|
+
return visitor.Scalar?.(key, node, path9);
|
|
224
224
|
if (identity.isAlias(node))
|
|
225
|
-
return visitor.Alias?.(key, node,
|
|
225
|
+
return visitor.Alias?.(key, node, path9);
|
|
226
226
|
return void 0;
|
|
227
227
|
}
|
|
228
|
-
function replaceNode(key,
|
|
229
|
-
const parent =
|
|
228
|
+
function replaceNode(key, path9, node) {
|
|
229
|
+
const parent = path9[path9.length - 1];
|
|
230
230
|
if (identity.isCollection(parent)) {
|
|
231
231
|
parent.items[key] = node;
|
|
232
232
|
} else if (identity.isPair(parent)) {
|
|
@@ -834,10 +834,10 @@ var require_Collection = __commonJS({
|
|
|
834
834
|
var createNode = require_createNode();
|
|
835
835
|
var identity = require_identity();
|
|
836
836
|
var Node = require_Node();
|
|
837
|
-
function collectionFromPath(schema,
|
|
837
|
+
function collectionFromPath(schema, path9, value) {
|
|
838
838
|
let v = value;
|
|
839
|
-
for (let i =
|
|
840
|
-
const k =
|
|
839
|
+
for (let i = path9.length - 1; i >= 0; --i) {
|
|
840
|
+
const k = path9[i];
|
|
841
841
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
842
842
|
const a = [];
|
|
843
843
|
a[k] = v;
|
|
@@ -856,7 +856,7 @@ var require_Collection = __commonJS({
|
|
|
856
856
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
857
857
|
});
|
|
858
858
|
}
|
|
859
|
-
var isEmptyPath = (
|
|
859
|
+
var isEmptyPath = (path9) => path9 == null || typeof path9 === "object" && !!path9[Symbol.iterator]().next().done;
|
|
860
860
|
var Collection = class extends Node.NodeBase {
|
|
861
861
|
constructor(type, schema) {
|
|
862
862
|
super(type);
|
|
@@ -886,11 +886,11 @@ var require_Collection = __commonJS({
|
|
|
886
886
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
887
887
|
* that already exists in the map.
|
|
888
888
|
*/
|
|
889
|
-
addIn(
|
|
890
|
-
if (isEmptyPath(
|
|
889
|
+
addIn(path9, value) {
|
|
890
|
+
if (isEmptyPath(path9))
|
|
891
891
|
this.add(value);
|
|
892
892
|
else {
|
|
893
|
-
const [key, ...rest] =
|
|
893
|
+
const [key, ...rest] = path9;
|
|
894
894
|
const node = this.get(key, true);
|
|
895
895
|
if (identity.isCollection(node))
|
|
896
896
|
node.addIn(rest, value);
|
|
@@ -904,8 +904,8 @@ var require_Collection = __commonJS({
|
|
|
904
904
|
* Removes a value from the collection.
|
|
905
905
|
* @returns `true` if the item was found and removed.
|
|
906
906
|
*/
|
|
907
|
-
deleteIn(
|
|
908
|
-
const [key, ...rest] =
|
|
907
|
+
deleteIn(path9) {
|
|
908
|
+
const [key, ...rest] = path9;
|
|
909
909
|
if (rest.length === 0)
|
|
910
910
|
return this.delete(key);
|
|
911
911
|
const node = this.get(key, true);
|
|
@@ -919,8 +919,8 @@ var require_Collection = __commonJS({
|
|
|
919
919
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
920
920
|
* `true` (collections are always returned intact).
|
|
921
921
|
*/
|
|
922
|
-
getIn(
|
|
923
|
-
const [key, ...rest] =
|
|
922
|
+
getIn(path9, keepScalar) {
|
|
923
|
+
const [key, ...rest] = path9;
|
|
924
924
|
const node = this.get(key, true);
|
|
925
925
|
if (rest.length === 0)
|
|
926
926
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -938,8 +938,8 @@ var require_Collection = __commonJS({
|
|
|
938
938
|
/**
|
|
939
939
|
* Checks if the collection includes a value with the key `key`.
|
|
940
940
|
*/
|
|
941
|
-
hasIn(
|
|
942
|
-
const [key, ...rest] =
|
|
941
|
+
hasIn(path9) {
|
|
942
|
+
const [key, ...rest] = path9;
|
|
943
943
|
if (rest.length === 0)
|
|
944
944
|
return this.has(key);
|
|
945
945
|
const node = this.get(key, true);
|
|
@@ -949,8 +949,8 @@ var require_Collection = __commonJS({
|
|
|
949
949
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
950
950
|
* boolean to add/remove the item from the set.
|
|
951
951
|
*/
|
|
952
|
-
setIn(
|
|
953
|
-
const [key, ...rest] =
|
|
952
|
+
setIn(path9, value) {
|
|
953
|
+
const [key, ...rest] = path9;
|
|
954
954
|
if (rest.length === 0) {
|
|
955
955
|
this.set(key, value);
|
|
956
956
|
} else {
|
|
@@ -3454,9 +3454,9 @@ var require_Document = __commonJS({
|
|
|
3454
3454
|
this.contents.add(value);
|
|
3455
3455
|
}
|
|
3456
3456
|
/** Adds a value to the document. */
|
|
3457
|
-
addIn(
|
|
3457
|
+
addIn(path9, value) {
|
|
3458
3458
|
if (assertCollection(this.contents))
|
|
3459
|
-
this.contents.addIn(
|
|
3459
|
+
this.contents.addIn(path9, value);
|
|
3460
3460
|
}
|
|
3461
3461
|
/**
|
|
3462
3462
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -3531,14 +3531,14 @@ var require_Document = __commonJS({
|
|
|
3531
3531
|
* Removes a value from the document.
|
|
3532
3532
|
* @returns `true` if the item was found and removed.
|
|
3533
3533
|
*/
|
|
3534
|
-
deleteIn(
|
|
3535
|
-
if (Collection.isEmptyPath(
|
|
3534
|
+
deleteIn(path9) {
|
|
3535
|
+
if (Collection.isEmptyPath(path9)) {
|
|
3536
3536
|
if (this.contents == null)
|
|
3537
3537
|
return false;
|
|
3538
3538
|
this.contents = null;
|
|
3539
3539
|
return true;
|
|
3540
3540
|
}
|
|
3541
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3541
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path9) : false;
|
|
3542
3542
|
}
|
|
3543
3543
|
/**
|
|
3544
3544
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -3553,10 +3553,10 @@ var require_Document = __commonJS({
|
|
|
3553
3553
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
3554
3554
|
* `true` (collections are always returned intact).
|
|
3555
3555
|
*/
|
|
3556
|
-
getIn(
|
|
3557
|
-
if (Collection.isEmptyPath(
|
|
3556
|
+
getIn(path9, keepScalar) {
|
|
3557
|
+
if (Collection.isEmptyPath(path9))
|
|
3558
3558
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3559
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3559
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path9, keepScalar) : void 0;
|
|
3560
3560
|
}
|
|
3561
3561
|
/**
|
|
3562
3562
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -3567,10 +3567,10 @@ var require_Document = __commonJS({
|
|
|
3567
3567
|
/**
|
|
3568
3568
|
* Checks if the document includes a value at `path`.
|
|
3569
3569
|
*/
|
|
3570
|
-
hasIn(
|
|
3571
|
-
if (Collection.isEmptyPath(
|
|
3570
|
+
hasIn(path9) {
|
|
3571
|
+
if (Collection.isEmptyPath(path9))
|
|
3572
3572
|
return this.contents !== void 0;
|
|
3573
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3573
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path9) : false;
|
|
3574
3574
|
}
|
|
3575
3575
|
/**
|
|
3576
3576
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -3587,13 +3587,13 @@ var require_Document = __commonJS({
|
|
|
3587
3587
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
3588
3588
|
* boolean to add/remove the item from the set.
|
|
3589
3589
|
*/
|
|
3590
|
-
setIn(
|
|
3591
|
-
if (Collection.isEmptyPath(
|
|
3590
|
+
setIn(path9, value) {
|
|
3591
|
+
if (Collection.isEmptyPath(path9)) {
|
|
3592
3592
|
this.contents = value;
|
|
3593
3593
|
} else if (this.contents == null) {
|
|
3594
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3594
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path9), value);
|
|
3595
3595
|
} else if (assertCollection(this.contents)) {
|
|
3596
|
-
this.contents.setIn(
|
|
3596
|
+
this.contents.setIn(path9, value);
|
|
3597
3597
|
}
|
|
3598
3598
|
}
|
|
3599
3599
|
/**
|
|
@@ -5545,9 +5545,9 @@ var require_cst_visit = __commonJS({
|
|
|
5545
5545
|
visit.BREAK = BREAK;
|
|
5546
5546
|
visit.SKIP = SKIP;
|
|
5547
5547
|
visit.REMOVE = REMOVE;
|
|
5548
|
-
visit.itemAtPath = (cst,
|
|
5548
|
+
visit.itemAtPath = (cst, path9) => {
|
|
5549
5549
|
let item = cst;
|
|
5550
|
-
for (const [field, index] of
|
|
5550
|
+
for (const [field, index] of path9) {
|
|
5551
5551
|
const tok = item?.[field];
|
|
5552
5552
|
if (tok && "items" in tok) {
|
|
5553
5553
|
item = tok.items[index];
|
|
@@ -5556,23 +5556,23 @@ var require_cst_visit = __commonJS({
|
|
|
5556
5556
|
}
|
|
5557
5557
|
return item;
|
|
5558
5558
|
};
|
|
5559
|
-
visit.parentCollection = (cst,
|
|
5560
|
-
const parent = visit.itemAtPath(cst,
|
|
5561
|
-
const field =
|
|
5559
|
+
visit.parentCollection = (cst, path9) => {
|
|
5560
|
+
const parent = visit.itemAtPath(cst, path9.slice(0, -1));
|
|
5561
|
+
const field = path9[path9.length - 1][0];
|
|
5562
5562
|
const coll = parent?.[field];
|
|
5563
5563
|
if (coll && "items" in coll)
|
|
5564
5564
|
return coll;
|
|
5565
5565
|
throw new Error("Parent collection not found");
|
|
5566
5566
|
};
|
|
5567
|
-
function _visit(
|
|
5568
|
-
let ctrl = visitor(item,
|
|
5567
|
+
function _visit(path9, item, visitor) {
|
|
5568
|
+
let ctrl = visitor(item, path9);
|
|
5569
5569
|
if (typeof ctrl === "symbol")
|
|
5570
5570
|
return ctrl;
|
|
5571
5571
|
for (const field of ["key", "value"]) {
|
|
5572
5572
|
const token = item[field];
|
|
5573
5573
|
if (token && "items" in token) {
|
|
5574
5574
|
for (let i = 0; i < token.items.length; ++i) {
|
|
5575
|
-
const ci = _visit(Object.freeze(
|
|
5575
|
+
const ci = _visit(Object.freeze(path9.concat([[field, i]])), token.items[i], visitor);
|
|
5576
5576
|
if (typeof ci === "number")
|
|
5577
5577
|
i = ci - 1;
|
|
5578
5578
|
else if (ci === BREAK)
|
|
@@ -5583,10 +5583,10 @@ var require_cst_visit = __commonJS({
|
|
|
5583
5583
|
}
|
|
5584
5584
|
}
|
|
5585
5585
|
if (typeof ctrl === "function" && field === "key")
|
|
5586
|
-
ctrl = ctrl(item,
|
|
5586
|
+
ctrl = ctrl(item, path9);
|
|
5587
5587
|
}
|
|
5588
5588
|
}
|
|
5589
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5589
|
+
return typeof ctrl === "function" ? ctrl(item, path9) : ctrl;
|
|
5590
5590
|
}
|
|
5591
5591
|
exports.visit = visit;
|
|
5592
5592
|
}
|
|
@@ -6871,14 +6871,14 @@ var require_parser = __commonJS({
|
|
|
6871
6871
|
case "scalar":
|
|
6872
6872
|
case "single-quoted-scalar":
|
|
6873
6873
|
case "double-quoted-scalar": {
|
|
6874
|
-
const
|
|
6874
|
+
const fs9 = this.flowScalar(this.type);
|
|
6875
6875
|
if (atNextItem || it.value) {
|
|
6876
|
-
map.items.push({ start, key:
|
|
6876
|
+
map.items.push({ start, key: fs9, sep: [] });
|
|
6877
6877
|
this.onKeyLine = true;
|
|
6878
6878
|
} else if (it.sep) {
|
|
6879
|
-
this.stack.push(
|
|
6879
|
+
this.stack.push(fs9);
|
|
6880
6880
|
} else {
|
|
6881
|
-
Object.assign(it, { key:
|
|
6881
|
+
Object.assign(it, { key: fs9, sep: [] });
|
|
6882
6882
|
this.onKeyLine = true;
|
|
6883
6883
|
}
|
|
6884
6884
|
return;
|
|
@@ -7006,13 +7006,13 @@ var require_parser = __commonJS({
|
|
|
7006
7006
|
case "scalar":
|
|
7007
7007
|
case "single-quoted-scalar":
|
|
7008
7008
|
case "double-quoted-scalar": {
|
|
7009
|
-
const
|
|
7009
|
+
const fs9 = this.flowScalar(this.type);
|
|
7010
7010
|
if (!it || it.value)
|
|
7011
|
-
fc.items.push({ start: [], key:
|
|
7011
|
+
fc.items.push({ start: [], key: fs9, sep: [] });
|
|
7012
7012
|
else if (it.sep)
|
|
7013
|
-
this.stack.push(
|
|
7013
|
+
this.stack.push(fs9);
|
|
7014
7014
|
else
|
|
7015
|
-
Object.assign(it, { key:
|
|
7015
|
+
Object.assign(it, { key: fs9, sep: [] });
|
|
7016
7016
|
return;
|
|
7017
7017
|
}
|
|
7018
7018
|
case "flow-map-end":
|
|
@@ -7550,17 +7550,17 @@ var require_ignore = __commonJS({
|
|
|
7550
7550
|
var throwError = (message, Ctor) => {
|
|
7551
7551
|
throw new Ctor(message);
|
|
7552
7552
|
};
|
|
7553
|
-
var checkPath = (
|
|
7554
|
-
if (!isString(
|
|
7553
|
+
var checkPath = (path9, originalPath, doThrow) => {
|
|
7554
|
+
if (!isString(path9)) {
|
|
7555
7555
|
return doThrow(
|
|
7556
7556
|
`path must be a string, but got \`${originalPath}\``,
|
|
7557
7557
|
TypeError
|
|
7558
7558
|
);
|
|
7559
7559
|
}
|
|
7560
|
-
if (!
|
|
7560
|
+
if (!path9) {
|
|
7561
7561
|
return doThrow(`path must not be empty`, TypeError);
|
|
7562
7562
|
}
|
|
7563
|
-
if (checkPath.isNotRelative(
|
|
7563
|
+
if (checkPath.isNotRelative(path9)) {
|
|
7564
7564
|
const r = "`path.relative()`d";
|
|
7565
7565
|
return doThrow(
|
|
7566
7566
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -7569,7 +7569,7 @@ var require_ignore = __commonJS({
|
|
|
7569
7569
|
}
|
|
7570
7570
|
return true;
|
|
7571
7571
|
};
|
|
7572
|
-
var isNotRelative = (
|
|
7572
|
+
var isNotRelative = (path9) => REGEX_TEST_INVALID_PATH.test(path9);
|
|
7573
7573
|
checkPath.isNotRelative = isNotRelative;
|
|
7574
7574
|
checkPath.convert = (p) => p;
|
|
7575
7575
|
var Ignore = class {
|
|
@@ -7628,7 +7628,7 @@ var require_ignore = __commonJS({
|
|
|
7628
7628
|
// setting `checkUnignored` to `false` could reduce additional
|
|
7629
7629
|
// path matching.
|
|
7630
7630
|
// @returns {TestResult} true if a file is ignored
|
|
7631
|
-
_testOne(
|
|
7631
|
+
_testOne(path9, checkUnignored) {
|
|
7632
7632
|
let ignored = false;
|
|
7633
7633
|
let unignored = false;
|
|
7634
7634
|
this._rules.forEach((rule) => {
|
|
@@ -7636,7 +7636,7 @@ var require_ignore = __commonJS({
|
|
|
7636
7636
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
7637
7637
|
return;
|
|
7638
7638
|
}
|
|
7639
|
-
const matched = rule.regex.test(
|
|
7639
|
+
const matched = rule.regex.test(path9);
|
|
7640
7640
|
if (matched) {
|
|
7641
7641
|
ignored = !negative;
|
|
7642
7642
|
unignored = negative;
|
|
@@ -7649,24 +7649,24 @@ var require_ignore = __commonJS({
|
|
|
7649
7649
|
}
|
|
7650
7650
|
// @returns {TestResult}
|
|
7651
7651
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
7652
|
-
const
|
|
7652
|
+
const path9 = originalPath && checkPath.convert(originalPath);
|
|
7653
7653
|
checkPath(
|
|
7654
|
-
|
|
7654
|
+
path9,
|
|
7655
7655
|
originalPath,
|
|
7656
7656
|
this._allowRelativePaths ? RETURN_FALSE : throwError
|
|
7657
7657
|
);
|
|
7658
|
-
return this._t(
|
|
7658
|
+
return this._t(path9, cache, checkUnignored, slices);
|
|
7659
7659
|
}
|
|
7660
|
-
_t(
|
|
7661
|
-
if (
|
|
7662
|
-
return cache[
|
|
7660
|
+
_t(path9, cache, checkUnignored, slices) {
|
|
7661
|
+
if (path9 in cache) {
|
|
7662
|
+
return cache[path9];
|
|
7663
7663
|
}
|
|
7664
7664
|
if (!slices) {
|
|
7665
|
-
slices =
|
|
7665
|
+
slices = path9.split(SLASH);
|
|
7666
7666
|
}
|
|
7667
7667
|
slices.pop();
|
|
7668
7668
|
if (!slices.length) {
|
|
7669
|
-
return cache[
|
|
7669
|
+
return cache[path9] = this._testOne(path9, checkUnignored);
|
|
7670
7670
|
}
|
|
7671
7671
|
const parent = this._t(
|
|
7672
7672
|
slices.join(SLASH) + SLASH,
|
|
@@ -7674,24 +7674,24 @@ var require_ignore = __commonJS({
|
|
|
7674
7674
|
checkUnignored,
|
|
7675
7675
|
slices
|
|
7676
7676
|
);
|
|
7677
|
-
return cache[
|
|
7677
|
+
return cache[path9] = parent.ignored ? parent : this._testOne(path9, checkUnignored);
|
|
7678
7678
|
}
|
|
7679
|
-
ignores(
|
|
7680
|
-
return this._test(
|
|
7679
|
+
ignores(path9) {
|
|
7680
|
+
return this._test(path9, this._ignoreCache, false).ignored;
|
|
7681
7681
|
}
|
|
7682
7682
|
createFilter() {
|
|
7683
|
-
return (
|
|
7683
|
+
return (path9) => !this.ignores(path9);
|
|
7684
7684
|
}
|
|
7685
7685
|
filter(paths) {
|
|
7686
7686
|
return makeArray(paths).filter(this.createFilter());
|
|
7687
7687
|
}
|
|
7688
7688
|
// @returns {TestResult}
|
|
7689
|
-
test(
|
|
7690
|
-
return this._test(
|
|
7689
|
+
test(path9) {
|
|
7690
|
+
return this._test(path9, this._testCache, true);
|
|
7691
7691
|
}
|
|
7692
7692
|
};
|
|
7693
7693
|
var factory = (options) => new Ignore(options);
|
|
7694
|
-
var isPathValid = (
|
|
7694
|
+
var isPathValid = (path9) => checkPath(path9 && checkPath.convert(path9), path9, RETURN_FALSE);
|
|
7695
7695
|
factory.isPathValid = isPathValid;
|
|
7696
7696
|
factory.default = factory;
|
|
7697
7697
|
module.exports = factory;
|
|
@@ -7702,7 +7702,7 @@ var require_ignore = __commonJS({
|
|
|
7702
7702
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
7703
7703
|
checkPath.convert = makePosix;
|
|
7704
7704
|
const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
7705
|
-
checkPath.isNotRelative = (
|
|
7705
|
+
checkPath.isNotRelative = (path9) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path9) || isNotRelative(path9);
|
|
7706
7706
|
}
|
|
7707
7707
|
}
|
|
7708
7708
|
});
|
|
@@ -7710,13 +7710,45 @@ var require_ignore = __commonJS({
|
|
|
7710
7710
|
// src/index.js
|
|
7711
7711
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
7712
7712
|
var import_ignore3 = __toESM(require_ignore(), 1);
|
|
7713
|
-
import
|
|
7714
|
-
import
|
|
7713
|
+
import crypto2 from "node:crypto";
|
|
7714
|
+
import fs8 from "node:fs";
|
|
7715
7715
|
import http from "node:http";
|
|
7716
|
-
import
|
|
7717
|
-
import
|
|
7716
|
+
import os2 from "node:os";
|
|
7717
|
+
import path8 from "node:path";
|
|
7718
7718
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
7719
7719
|
|
|
7720
|
+
// src/clickup_comments.js
|
|
7721
|
+
var COMMENT_TEXT_KEYS = /* @__PURE__ */ new Set(["comment", "body", "description"]);
|
|
7722
|
+
function normalizeClickUpMarkdown(value = "") {
|
|
7723
|
+
return String(value ?? "").replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
7724
|
+
}
|
|
7725
|
+
function formatClickUpStatusComment({ title = "", summary = "", sections = [], context = [] } = {}) {
|
|
7726
|
+
const lines = [];
|
|
7727
|
+
const heading = normalizeClickUpMarkdown(title);
|
|
7728
|
+
if (heading) lines.push(`## ${heading}`, "");
|
|
7729
|
+
const intro = normalizeClickUpMarkdown(summary);
|
|
7730
|
+
if (intro) lines.push(intro, "");
|
|
7731
|
+
for (const section of sections) {
|
|
7732
|
+
const sectionTitle = normalizeClickUpMarkdown(section?.title);
|
|
7733
|
+
const sectionBody = normalizeClickUpMarkdown(section?.body);
|
|
7734
|
+
if (!sectionTitle || !sectionBody) continue;
|
|
7735
|
+
lines.push(`### ${sectionTitle}`, sectionBody, "");
|
|
7736
|
+
}
|
|
7737
|
+
const contextLines = context.map(normalizeClickUpMarkdown).filter(Boolean);
|
|
7738
|
+
if (contextLines.length > 0) lines.push("### Context", contextLines.map((item) => item.startsWith("- ") ? item : `- ${item}`).join("\n"));
|
|
7739
|
+
return normalizeClickUpMarkdown(lines.join("\n"));
|
|
7740
|
+
}
|
|
7741
|
+
function normalizeClickUpPayloadComments(value) {
|
|
7742
|
+
if (Array.isArray(value)) return value.map(normalizeClickUpPayloadComments);
|
|
7743
|
+
if (!value || typeof value !== "object") return value;
|
|
7744
|
+
return Object.fromEntries(
|
|
7745
|
+
Object.entries(value).map(([key, entry]) => [
|
|
7746
|
+
key,
|
|
7747
|
+
COMMENT_TEXT_KEYS.has(key) && typeof entry === "string" ? normalizeClickUpMarkdown(entry) : normalizeClickUpPayloadComments(entry)
|
|
7748
|
+
])
|
|
7749
|
+
);
|
|
7750
|
+
}
|
|
7751
|
+
|
|
7720
7752
|
// src/git_utils.js
|
|
7721
7753
|
import fs from "node:fs";
|
|
7722
7754
|
import path from "node:path";
|
|
@@ -7795,224 +7827,691 @@ function stageGitAwareMerge(sourcePath, destinationPath, gitState) {
|
|
|
7795
7827
|
}
|
|
7796
7828
|
}
|
|
7797
7829
|
|
|
7798
|
-
// src/
|
|
7830
|
+
// src/github.js
|
|
7831
|
+
import crypto from "node:crypto";
|
|
7832
|
+
import fs3 from "node:fs";
|
|
7833
|
+
import path3 from "node:path";
|
|
7834
|
+
|
|
7835
|
+
// src/secrets.js
|
|
7799
7836
|
import fs2 from "node:fs";
|
|
7837
|
+
import os from "node:os";
|
|
7800
7838
|
import path2 from "node:path";
|
|
7801
|
-
function
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
|
|
7807
|
-
const resolvedCandidate = path2.resolve(candidate);
|
|
7808
|
-
const resolvedRoot = path2.resolve(root);
|
|
7809
|
-
const relative = path2.relative(resolvedRoot, resolvedCandidate);
|
|
7810
|
-
return relative === "" || !!relative && !relative.startsWith("..") && !path2.isAbsolute(relative);
|
|
7811
|
-
}
|
|
7812
|
-
function resolveWithin(baseDir, relativePath) {
|
|
7813
|
-
if (!relativePath) return null;
|
|
7814
|
-
if (path2.isAbsolute(relativePath)) return null;
|
|
7815
|
-
const resolved = path2.resolve(baseDir, relativePath);
|
|
7816
|
-
return isSameOrNestedPath(resolved, baseDir) ? resolved : null;
|
|
7817
|
-
}
|
|
7818
|
-
function withCompactPreference(paths, options = {}) {
|
|
7819
|
-
if (!options.preferCompactPromptDocs) return paths;
|
|
7820
|
-
const compactPaths = paths.map((filePath) => filePath ? compactPromptPath(filePath) : null).filter(Boolean);
|
|
7821
|
-
return [...compactPaths, ...paths];
|
|
7839
|
+
function expandHomePath(value = "") {
|
|
7840
|
+
const input = String(value || "").trim();
|
|
7841
|
+
if (input === "~") return os.homedir();
|
|
7842
|
+
if (input.startsWith("~/")) return path2.join(os.homedir(), input.slice(2));
|
|
7843
|
+
return input;
|
|
7822
7844
|
}
|
|
7823
|
-
function
|
|
7824
|
-
const
|
|
7825
|
-
const
|
|
7826
|
-
|
|
7827
|
-
const
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7834
|
-
}
|
|
7835
|
-
return null;
|
|
7836
|
-
}
|
|
7837
|
-
if (scope === "repo") {
|
|
7838
|
-
for (const filePath of withCompactPreference([resolveWithin(optimaRoot, target)], options)) {
|
|
7839
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7840
|
-
}
|
|
7841
|
-
return null;
|
|
7842
|
-
}
|
|
7843
|
-
if (scope === "policy") {
|
|
7844
|
-
const candidates2 = withCompactPreference([
|
|
7845
|
-
resolveWithin(repoPolicyRoot, target),
|
|
7846
|
-
resolveWithin(bundlePolicyRoot, target)
|
|
7847
|
-
], options);
|
|
7848
|
-
for (const filePath of candidates2) {
|
|
7849
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7845
|
+
function resolveSecretReference(value = "") {
|
|
7846
|
+
const raw = String(value || "").trim();
|
|
7847
|
+
const envMatch = raw.match(/^\{env:([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
7848
|
+
if (envMatch) return process.env[envMatch[1]] || "";
|
|
7849
|
+
const fileMatch = raw.match(/^\{file:(.+)\}$/);
|
|
7850
|
+
if (fileMatch) {
|
|
7851
|
+
try {
|
|
7852
|
+
return fs2.readFileSync(expandHomePath(fileMatch[1]), "utf8").trim();
|
|
7853
|
+
} catch {
|
|
7854
|
+
return "";
|
|
7850
7855
|
}
|
|
7851
|
-
return null;
|
|
7852
7856
|
}
|
|
7853
|
-
|
|
7854
|
-
resolveWithin(repoRoot, target),
|
|
7855
|
-
resolveWithin(bundleRoot, target)
|
|
7856
|
-
], options);
|
|
7857
|
-
for (const filePath of candidates) {
|
|
7858
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7859
|
-
}
|
|
7860
|
-
return null;
|
|
7857
|
+
return raw;
|
|
7861
7858
|
}
|
|
7862
|
-
function resolveIncludes(text, repoRoot, bundleRoot, options = {}) {
|
|
7863
|
-
const includeDepth = Number(options.includeDepth || 0);
|
|
7864
|
-
const maxIncludeDepth = Number(options.maxIncludeDepth || 20);
|
|
7865
|
-
if (includeDepth > maxIncludeDepth) {
|
|
7866
|
-
return "\n\n# ERROR: Include recursion limit exceeded.\n\n";
|
|
7867
|
-
}
|
|
7868
|
-
const includeRegex = /<include:(.*?)>/g;
|
|
7869
|
-
return String(text || "").replace(includeRegex, (match, includeRef) => {
|
|
7870
|
-
const filePath = resolveIncludeFile(includeRef, repoRoot, bundleRoot, options);
|
|
7871
|
-
if (!filePath) {
|
|
7872
|
-
console.warn(`[Optima] Include file not found: ${includeRef}`);
|
|
7873
|
-
return `
|
|
7874
7859
|
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
}
|
|
7879
|
-
const content = fs2.readFileSync(filePath, "utf8");
|
|
7880
|
-
return resolveIncludes(content, repoRoot, bundleRoot, { ...options, includeDepth: includeDepth + 1, maxIncludeDepth });
|
|
7881
|
-
});
|
|
7860
|
+
// src/github.js
|
|
7861
|
+
function base64UrlJson(value) {
|
|
7862
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
7882
7863
|
}
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
".jsx",
|
|
7894
|
-
".dart",
|
|
7895
|
-
".py",
|
|
7896
|
-
".go",
|
|
7897
|
-
".rs",
|
|
7898
|
-
".java",
|
|
7899
|
-
".c",
|
|
7900
|
-
".cpp",
|
|
7901
|
-
".cs",
|
|
7902
|
-
".php",
|
|
7903
|
-
".rb",
|
|
7904
|
-
".swift",
|
|
7905
|
-
".kt",
|
|
7906
|
-
".m",
|
|
7907
|
-
".sh",
|
|
7908
|
-
".sql",
|
|
7909
|
-
".yaml",
|
|
7910
|
-
".yml",
|
|
7911
|
-
".json",
|
|
7912
|
-
".md"
|
|
7913
|
-
]);
|
|
7914
|
-
var LEGACY_OPERATIONAL_ROOTS = /* @__PURE__ */ new Set([
|
|
7915
|
-
"tasks",
|
|
7916
|
-
"evidences",
|
|
7917
|
-
".orbita",
|
|
7918
|
-
".staticeng",
|
|
7919
|
-
".nomadworks",
|
|
7920
|
-
".nomadwork",
|
|
7921
|
-
"nomadworks",
|
|
7922
|
-
".codenomad"
|
|
7923
|
-
]);
|
|
7924
|
-
var OPTIMA_OPERATIONAL_FOLDERS = [".optima", "templates", "dist"];
|
|
7925
|
-
function relPath(worktree, targetPath) {
|
|
7926
|
-
return path3.relative(worktree, targetPath).split(path3.sep).join("/") || ".";
|
|
7864
|
+
function resolveGitHubAppPrivateKey(app = {}) {
|
|
7865
|
+
const direct = resolveSecretReference(app.privateKey || "");
|
|
7866
|
+
if (direct) return direct.replace(/\\n/g, "\n");
|
|
7867
|
+
const file = expandHomePath(app.privateKeyFile || "");
|
|
7868
|
+
if (!file) return "";
|
|
7869
|
+
try {
|
|
7870
|
+
return fs3.readFileSync(file, "utf8").trim().replace(/\\n/g, "\n");
|
|
7871
|
+
} catch {
|
|
7872
|
+
return "";
|
|
7873
|
+
}
|
|
7927
7874
|
}
|
|
7928
|
-
function
|
|
7929
|
-
|
|
7875
|
+
function createGitHubAppJwt({ appId, privateKey, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
7876
|
+
const key = String(privateKey || "").trim();
|
|
7877
|
+
const issuer = String(appId || "").trim();
|
|
7878
|
+
if (!issuer || !key) throw new Error("GitHub App app_id and private key are required");
|
|
7879
|
+
const nowSeconds = Math.floor(now().getTime() / 1e3);
|
|
7880
|
+
const header = base64UrlJson({ alg: "RS256", typ: "JWT" });
|
|
7881
|
+
const payload = base64UrlJson({ iat: nowSeconds - 60, exp: nowSeconds + 540, iss: issuer });
|
|
7882
|
+
const unsigned = `${header}.${payload}`;
|
|
7883
|
+
const signature = crypto.createSign("RSA-SHA256").update(unsigned).end().sign(key, "base64url");
|
|
7884
|
+
return `${unsigned}.${signature}`;
|
|
7930
7885
|
}
|
|
7931
|
-
function
|
|
7932
|
-
return
|
|
7886
|
+
function encodeGitHubPathSegment(value) {
|
|
7887
|
+
return encodeURIComponent(String(value || ""));
|
|
7933
7888
|
}
|
|
7934
|
-
function
|
|
7935
|
-
|
|
7936
|
-
const normalized = normalizeCodemapRelPath(relPathValue);
|
|
7937
|
-
const firstSegment = firstPathSegment(normalized);
|
|
7938
|
-
if (LEGACY_OPERATIONAL_ROOTS.has(firstSegment)) return true;
|
|
7939
|
-
return OPTIMA_OPERATIONAL_FOLDERS.some((folder) => normalized === folder || normalized.startsWith(`${folder}/`));
|
|
7889
|
+
function encodeGitHubBranchRef(branch = "") {
|
|
7890
|
+
return String(branch || "").split("/").map(encodeGitHubPathSegment).join("/");
|
|
7940
7891
|
}
|
|
7941
|
-
function
|
|
7942
|
-
|
|
7943
|
-
return normalizeCodemapRelPath(relPathValue).split("/").some((part) => part.startsWith("."));
|
|
7892
|
+
function parseNullSeparatedGitOutput(output = "") {
|
|
7893
|
+
return String(output || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
7944
7894
|
}
|
|
7945
|
-
function
|
|
7946
|
-
const
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
if (fs3.existsSync(gitignorePath)) ig.add(fs3.readFileSync(gitignorePath, "utf8"));
|
|
7950
|
-
return ig;
|
|
7895
|
+
function listWorktreeChangedPaths(worktree, runGitFn = runGit) {
|
|
7896
|
+
const tracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["diff", "--name-only", "-z", "HEAD", "--"]));
|
|
7897
|
+
const untracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["ls-files", "--others", "--exclude-standard", "-z", "--"]));
|
|
7898
|
+
return [.../* @__PURE__ */ new Set([...tracked, ...untracked])].filter((item) => item && !item.startsWith("../") && !path3.isAbsolute(item));
|
|
7951
7899
|
}
|
|
7952
|
-
function
|
|
7953
|
-
|
|
7954
|
-
if (value && typeof value === "object") return Object.values(value);
|
|
7955
|
-
return [];
|
|
7900
|
+
function currentGitBranch(worktree, runGitFn = runGit) {
|
|
7901
|
+
return String(runGitFn(worktree, ["rev-parse", "--abbrev-ref", "HEAD"]) || "").trim();
|
|
7956
7902
|
}
|
|
7957
|
-
function
|
|
7958
|
-
const
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7903
|
+
function treeEntryForWorktreePath(worktree, gitPath) {
|
|
7904
|
+
const absolutePath = path3.join(worktree, ...String(gitPath || "").split("/"));
|
|
7905
|
+
if (!fs3.existsSync(absolutePath)) return { path: gitPath, mode: "100644", type: "blob", sha: null, deleted: true };
|
|
7906
|
+
const stat = fs3.lstatSync(absolutePath);
|
|
7907
|
+
if (stat.isDirectory()) return null;
|
|
7908
|
+
if (stat.isSymbolicLink()) {
|
|
7909
|
+
return {
|
|
7910
|
+
path: gitPath,
|
|
7911
|
+
mode: "120000",
|
|
7912
|
+
type: "blob",
|
|
7913
|
+
content: fs3.readlinkSync(absolutePath),
|
|
7914
|
+
encoding: "utf-8"
|
|
7915
|
+
};
|
|
7963
7916
|
}
|
|
7964
|
-
return
|
|
7917
|
+
return {
|
|
7918
|
+
path: gitPath,
|
|
7919
|
+
mode: stat.mode & 73 ? "100755" : "100644",
|
|
7920
|
+
type: "blob",
|
|
7921
|
+
content: fs3.readFileSync(absolutePath).toString("base64"),
|
|
7922
|
+
encoding: "base64"
|
|
7923
|
+
};
|
|
7965
7924
|
}
|
|
7966
|
-
function
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
if (
|
|
7970
|
-
|
|
7971
|
-
return null;
|
|
7972
|
-
}
|
|
7973
|
-
return parsed;
|
|
7974
|
-
} catch {
|
|
7975
|
-
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap has invalid YAML; manual repair required." });
|
|
7976
|
-
return null;
|
|
7925
|
+
function appendGitHubQuery(pathname, params = {}) {
|
|
7926
|
+
const query = new URLSearchParams();
|
|
7927
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
7928
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
7929
|
+
query.set(key, String(value));
|
|
7977
7930
|
}
|
|
7931
|
+
const suffix = query.toString();
|
|
7932
|
+
return suffix ? `${pathname}?${suffix}` : pathname;
|
|
7978
7933
|
}
|
|
7979
|
-
function
|
|
7980
|
-
|
|
7934
|
+
function timestampMs(value = "") {
|
|
7935
|
+
const parsed = Date.parse(String(value || ""));
|
|
7936
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
7981
7937
|
}
|
|
7982
|
-
function
|
|
7983
|
-
|
|
7984
|
-
|
|
7938
|
+
function sortNewestFirst(items = []) {
|
|
7939
|
+
return [...items].sort((a, b) => {
|
|
7940
|
+
const bTime = Math.max(timestampMs(b?.updated_at), timestampMs(b?.created_at));
|
|
7941
|
+
const aTime = Math.max(timestampMs(a?.updated_at), timestampMs(a?.created_at));
|
|
7942
|
+
return bTime - aTime;
|
|
7943
|
+
});
|
|
7985
7944
|
}
|
|
7986
|
-
function
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
return
|
|
7945
|
+
function matchesVercelStatusContext(context = "", expected = "") {
|
|
7946
|
+
const actual = String(context || "").trim();
|
|
7947
|
+
const wanted = String(expected || "").trim();
|
|
7948
|
+
if (wanted && actual === wanted) return true;
|
|
7949
|
+
const lower = actual.toLowerCase();
|
|
7950
|
+
if (!lower.includes("vercel")) return false;
|
|
7951
|
+
if (!wanted) return lower.includes("preproduction") || lower.includes("defend-preproduction");
|
|
7952
|
+
const wantedLower = wanted.toLowerCase();
|
|
7953
|
+
return wantedLower.split(/\s+|–|-/).filter((part) => part && part !== "vercel").every((part) => lower.includes(part));
|
|
7995
7954
|
}
|
|
7996
|
-
function
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
if (
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
7955
|
+
function selectFunctionalDeploymentUrl(status = {}, deployment = {}) {
|
|
7956
|
+
for (const candidate of [
|
|
7957
|
+
status?.environment_url,
|
|
7958
|
+
deployment?.environment_url,
|
|
7959
|
+
status?.target_url,
|
|
7960
|
+
deployment?.target_url
|
|
7961
|
+
]) {
|
|
7962
|
+
const value = String(candidate || "").trim();
|
|
7963
|
+
if (!value) continue;
|
|
7964
|
+
try {
|
|
7965
|
+
const url = new URL(value);
|
|
7966
|
+
if (url.protocol === "http:" || url.protocol === "https:") return value;
|
|
7967
|
+
} catch {
|
|
7968
|
+
}
|
|
7969
|
+
}
|
|
7970
|
+
return "";
|
|
7971
|
+
}
|
|
7972
|
+
async function checkFunctionalDeploymentUrl(url, fetchImpl = globalThis.fetch) {
|
|
7973
|
+
const target = String(url || "").trim();
|
|
7974
|
+
if (!target) return { ok: false, reason: "url_missing" };
|
|
7975
|
+
if (typeof fetchImpl !== "function") return { ok: false, reason: "fetch_unavailable", url: target };
|
|
7976
|
+
const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(8e3) : void 0;
|
|
7977
|
+
const probe = async (method) => {
|
|
7978
|
+
const response = await fetchImpl(target, { method, redirect: "follow", signal });
|
|
7979
|
+
return {
|
|
7980
|
+
ok: response.status >= 200 && response.status < 400,
|
|
7981
|
+
status: response.status,
|
|
7982
|
+
statusText: response.statusText || "",
|
|
7983
|
+
url: response.url || target,
|
|
7984
|
+
method
|
|
7985
|
+
};
|
|
7986
|
+
};
|
|
7987
|
+
try {
|
|
7988
|
+
const head = await probe("HEAD");
|
|
7989
|
+
if (head.ok) return head;
|
|
7990
|
+
const get = await probe("GET");
|
|
7991
|
+
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
7992
|
+
} catch (error) {
|
|
7993
|
+
try {
|
|
7994
|
+
const get = await probe("GET");
|
|
7995
|
+
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
7996
|
+
} catch (secondError) {
|
|
7997
|
+
return { ok: false, reason: "request_failed", url: target, error: secondError.message || error.message };
|
|
7998
|
+
}
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
8002
|
+
const staticToken = resolveSecretReference(config?.apiToken);
|
|
8003
|
+
const appConfig = typeof config?.app === "object" && config.app !== null ? config.app : {};
|
|
8004
|
+
const appId = resolveSecretReference(appConfig.appId || appConfig.app_id || "");
|
|
8005
|
+
const installationId = resolveSecretReference(appConfig.installationId || appConfig.installation_id || "");
|
|
8006
|
+
const appEnabled = appConfig.enabled === true || Boolean(appId && installationId && (appConfig.privateKey || appConfig.privateKeyFile));
|
|
8007
|
+
const privateKey = resolveGitHubAppPrivateKey(appConfig);
|
|
8008
|
+
const owner = String(config?.owner || "").trim();
|
|
8009
|
+
const repo = String(config?.repo || "").trim();
|
|
8010
|
+
let installationToken = null;
|
|
8011
|
+
let installationTokenExpiresAt = 0;
|
|
8012
|
+
const requestJson = async (url, { method = "GET", headers = {}, body = void 0 } = {}) => {
|
|
8013
|
+
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
|
|
8014
|
+
const response = await fetchImpl(url, {
|
|
8015
|
+
method,
|
|
8016
|
+
headers: {
|
|
8017
|
+
Accept: "application/vnd.github+json",
|
|
8018
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
8019
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
8020
|
+
...headers
|
|
8021
|
+
},
|
|
8022
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
8023
|
+
});
|
|
8024
|
+
if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
|
|
8025
|
+
return response.status === 204 ? null : response.json();
|
|
8026
|
+
};
|
|
8027
|
+
const getAuthToken = async () => {
|
|
8028
|
+
if (!appEnabled) return staticToken || "";
|
|
8029
|
+
const nowMs = Date.now();
|
|
8030
|
+
if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
|
|
8031
|
+
const jwt = createGitHubAppJwt({ appId, privateKey });
|
|
8032
|
+
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(installationId)}/access_tokens`, {
|
|
8033
|
+
method: "POST",
|
|
8034
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
8035
|
+
});
|
|
8036
|
+
installationToken = String(installation?.token || "").trim();
|
|
8037
|
+
installationTokenExpiresAt = Date.parse(installation?.expires_at || "") || nowMs + 3e6;
|
|
8038
|
+
if (!installationToken) throw new Error("GitHub App installation token response did not include a token");
|
|
8039
|
+
return installationToken;
|
|
8040
|
+
};
|
|
8041
|
+
const request = async (pathname, { method = "GET", body = void 0 } = {}) => {
|
|
8042
|
+
if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
|
|
8043
|
+
const token = await getAuthToken();
|
|
8044
|
+
return requestJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
|
|
8045
|
+
method,
|
|
8046
|
+
body,
|
|
8047
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
8048
|
+
});
|
|
8049
|
+
};
|
|
8050
|
+
const getRef = async (branch) => request(`/git/ref/heads/${encodeGitHubBranchRef(branch)}`);
|
|
8051
|
+
const getCommitObject = async (sha) => request(`/git/commits/${encodeURIComponent(sha)}`);
|
|
8052
|
+
const createBlob = async ({ content, encoding }) => request("/git/blobs", { method: "POST", body: { content, encoding } });
|
|
8053
|
+
const createTree = async ({ baseTree, tree }) => request("/git/trees", { method: "POST", body: { base_tree: baseTree, tree } });
|
|
8054
|
+
const createCommit = async ({ message, treeSha, parents }) => request("/git/commits", { method: "POST", body: { message, tree: treeSha, parents } });
|
|
8055
|
+
const updateRef = async ({ branch, sha, force = false }) => request(`/git/refs/heads/${encodeGitHubBranchRef(branch)}`, { method: "PATCH", body: { sha, force } });
|
|
8056
|
+
return {
|
|
8057
|
+
async getPullRequest(number) {
|
|
8058
|
+
return request(`/pulls/${encodeURIComponent(number)}`);
|
|
8059
|
+
},
|
|
8060
|
+
async createPullRequest({ title, head, base, body = "", draft = false, maintainerCanModify = true }) {
|
|
8061
|
+
return request("/pulls", {
|
|
8062
|
+
method: "POST",
|
|
8063
|
+
body: {
|
|
8064
|
+
title: String(title || ""),
|
|
8065
|
+
head: String(head || ""),
|
|
8066
|
+
base: String(base || ""),
|
|
8067
|
+
body: String(body || ""),
|
|
8068
|
+
draft: draft === true,
|
|
8069
|
+
maintainer_can_modify: maintainerCanModify !== false
|
|
8070
|
+
}
|
|
8071
|
+
});
|
|
8072
|
+
},
|
|
8073
|
+
async createIssueComment({ issueNumber, body }) {
|
|
8074
|
+
return request(`/issues/${encodeURIComponent(issueNumber)}/comments`, { method: "POST", body: { body: String(body || "") } });
|
|
8075
|
+
},
|
|
8076
|
+
async replyToReviewComment({ commentId, body }) {
|
|
8077
|
+
return request(`/pulls/comments/${encodeURIComponent(commentId)}/replies`, { method: "POST", body: { body: String(body || "") } });
|
|
8078
|
+
},
|
|
8079
|
+
async createPullRequestReview({ pullNumber, body, event = "COMMENT" }) {
|
|
8080
|
+
return request(`/pulls/${encodeURIComponent(pullNumber)}/reviews`, { method: "POST", body: { body: String(body || ""), event: String(event || "COMMENT").toUpperCase() } });
|
|
8081
|
+
},
|
|
8082
|
+
async mergePullRequest({ pullNumber, commitTitle = "", commitMessage = "", mergeMethod = "squash" }) {
|
|
8083
|
+
const body = { merge_method: String(mergeMethod || "squash") };
|
|
8084
|
+
if (commitTitle) body.commit_title = String(commitTitle);
|
|
8085
|
+
if (commitMessage) body.commit_message = String(commitMessage);
|
|
8086
|
+
return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
|
|
8087
|
+
},
|
|
8088
|
+
async getCombinedStatus(ref) {
|
|
8089
|
+
return request(`/commits/${encodeURIComponent(String(ref || ""))}/status`);
|
|
8090
|
+
},
|
|
8091
|
+
async listDeployments({ sha = "", ref = "", environment = "", perPage = 30 } = {}) {
|
|
8092
|
+
return request(appendGitHubQuery("/deployments", { sha, ref, environment, per_page: perPage }));
|
|
8093
|
+
},
|
|
8094
|
+
async listDeploymentStatuses(deploymentId) {
|
|
8095
|
+
return request(`/deployments/${encodeURIComponent(deploymentId)}/statuses`);
|
|
8096
|
+
},
|
|
8097
|
+
async verifyVercelPullRequestDeployment({
|
|
8098
|
+
pullNumber,
|
|
8099
|
+
context = "Vercel \u2013 defend-preproduction",
|
|
8100
|
+
environment = "Preview \u2013 defend-preproduction",
|
|
8101
|
+
requireFunctionalUrl = true
|
|
8102
|
+
} = {}) {
|
|
8103
|
+
const pr = await this.getPullRequest(pullNumber);
|
|
8104
|
+
const headSha = String(pr?.head?.sha || "").trim();
|
|
8105
|
+
if (!headSha) return { ok: true, ready: false, reason: "pr_head_sha_missing", pull_request: { number: pullNumber } };
|
|
8106
|
+
const combined = await this.getCombinedStatus(headSha);
|
|
8107
|
+
const statuses = Array.isArray(combined?.statuses) ? combined.statuses : [];
|
|
8108
|
+
const selectedStatus = sortNewestFirst(statuses.filter((status) => matchesVercelStatusContext(status?.context, context)))[0] || null;
|
|
8109
|
+
if (!selectedStatus) {
|
|
8110
|
+
return {
|
|
8111
|
+
ok: true,
|
|
8112
|
+
ready: false,
|
|
8113
|
+
reason: "vercel_status_missing",
|
|
8114
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8115
|
+
required_context: context,
|
|
8116
|
+
combined_state: combined?.state || null
|
|
8117
|
+
};
|
|
8118
|
+
}
|
|
8119
|
+
if (String(selectedStatus.state || "").toLowerCase() !== "success") {
|
|
8120
|
+
return {
|
|
8121
|
+
ok: true,
|
|
8122
|
+
ready: false,
|
|
8123
|
+
reason: "vercel_status_not_success",
|
|
8124
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8125
|
+
required_context: context,
|
|
8126
|
+
status: selectedStatus,
|
|
8127
|
+
combined_state: combined?.state || null
|
|
8128
|
+
};
|
|
8129
|
+
}
|
|
8130
|
+
let deployments = await this.listDeployments({ sha: headSha, environment });
|
|
8131
|
+
if (!Array.isArray(deployments) || deployments.length === 0) deployments = await this.listDeployments({ sha: headSha });
|
|
8132
|
+
const newestDeployments = sortNewestFirst(Array.isArray(deployments) ? deployments : []);
|
|
8133
|
+
const selectedDeployment = newestDeployments.find((deployment) => !environment || String(deployment?.environment || "") === environment) || newestDeployments[0] || null;
|
|
8134
|
+
if (!selectedDeployment?.id) {
|
|
8135
|
+
return {
|
|
8136
|
+
ok: true,
|
|
8137
|
+
ready: false,
|
|
8138
|
+
reason: "vercel_deployment_missing",
|
|
8139
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8140
|
+
required_environment: environment,
|
|
8141
|
+
status: selectedStatus
|
|
8142
|
+
};
|
|
8143
|
+
}
|
|
8144
|
+
const deploymentStatuses = await this.listDeploymentStatuses(selectedDeployment.id);
|
|
8145
|
+
const selectedDeploymentStatus = sortNewestFirst(Array.isArray(deploymentStatuses) ? deploymentStatuses : [])[0] || null;
|
|
8146
|
+
if (!selectedDeploymentStatus || String(selectedDeploymentStatus.state || "").toLowerCase() !== "success") {
|
|
8147
|
+
return {
|
|
8148
|
+
ok: true,
|
|
8149
|
+
ready: false,
|
|
8150
|
+
reason: "vercel_deployment_not_success",
|
|
8151
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8152
|
+
required_environment: environment,
|
|
8153
|
+
status: selectedStatus,
|
|
8154
|
+
deployment: selectedDeployment,
|
|
8155
|
+
deployment_status: selectedDeploymentStatus
|
|
8156
|
+
};
|
|
8157
|
+
}
|
|
8158
|
+
const url = selectFunctionalDeploymentUrl(selectedDeploymentStatus, selectedDeployment);
|
|
8159
|
+
const urlCheck = requireFunctionalUrl ? await checkFunctionalDeploymentUrl(url, fetchImpl) : { ok: Boolean(url), reason: url ? void 0 : "url_missing", url };
|
|
8160
|
+
if (requireFunctionalUrl && !urlCheck.ok) {
|
|
8161
|
+
return {
|
|
8162
|
+
ok: true,
|
|
8163
|
+
ready: false,
|
|
8164
|
+
reason: "vercel_url_not_functional",
|
|
8165
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8166
|
+
required_environment: environment,
|
|
8167
|
+
status: selectedStatus,
|
|
8168
|
+
deployment: selectedDeployment,
|
|
8169
|
+
deployment_status: selectedDeploymentStatus,
|
|
8170
|
+
url,
|
|
8171
|
+
url_check: urlCheck
|
|
8172
|
+
};
|
|
8173
|
+
}
|
|
8174
|
+
return {
|
|
8175
|
+
ok: true,
|
|
8176
|
+
ready: true,
|
|
8177
|
+
reason: "vercel_pr_deployment_ready",
|
|
8178
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8179
|
+
required_context: context,
|
|
8180
|
+
required_environment: environment,
|
|
8181
|
+
status: selectedStatus,
|
|
8182
|
+
deployment: selectedDeployment,
|
|
8183
|
+
deployment_status: selectedDeploymentStatus,
|
|
8184
|
+
url,
|
|
8185
|
+
url_check: urlCheck
|
|
8186
|
+
};
|
|
8187
|
+
},
|
|
8188
|
+
async commitWorktree({ worktree, branch = "", message = "", runGitFn = runGit, syncLocal = false } = {}) {
|
|
8189
|
+
const directory = path3.resolve(String(worktree || ""));
|
|
8190
|
+
if (!directory || !fs3.existsSync(directory)) throw new Error("worktree does not exist");
|
|
8191
|
+
const targetBranch = String(branch || currentGitBranch(directory, runGitFn)).trim();
|
|
8192
|
+
if (!targetBranch || targetBranch === "HEAD") throw new Error("target branch is required for GitHub API commit");
|
|
8193
|
+
const commitMessage = String(message || "").trim();
|
|
8194
|
+
if (!commitMessage) throw new Error("commit message is required");
|
|
8195
|
+
const changedPaths = listWorktreeChangedPaths(directory, runGitFn);
|
|
8196
|
+
if (changedPaths.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
8197
|
+
const ref = await getRef(targetBranch);
|
|
8198
|
+
const headSha = ref?.object?.sha;
|
|
8199
|
+
if (!headSha) throw new Error(`GitHub ref for ${targetBranch} did not include a head sha`);
|
|
8200
|
+
const headCommit = await getCommitObject(headSha);
|
|
8201
|
+
const baseTree = headCommit?.tree?.sha;
|
|
8202
|
+
if (!baseTree) throw new Error(`GitHub commit ${headSha} did not include a tree sha`);
|
|
8203
|
+
const tree = [];
|
|
8204
|
+
for (const gitPath of changedPaths) {
|
|
8205
|
+
const entry = treeEntryForWorktreePath(directory, gitPath);
|
|
8206
|
+
if (!entry) continue;
|
|
8207
|
+
if (entry.deleted) {
|
|
8208
|
+
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: null });
|
|
8209
|
+
continue;
|
|
8210
|
+
}
|
|
8211
|
+
const blob = await createBlob({ content: entry.content, encoding: entry.encoding });
|
|
8212
|
+
if (!blob?.sha) throw new Error(`GitHub blob creation failed for ${gitPath}`);
|
|
8213
|
+
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: blob.sha });
|
|
8214
|
+
}
|
|
8215
|
+
if (tree.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
8216
|
+
const nextTree = await createTree({ baseTree, tree });
|
|
8217
|
+
const nextCommit = await createCommit({ message: commitMessage, treeSha: nextTree.sha, parents: [headSha] });
|
|
8218
|
+
if (!nextCommit?.sha) throw new Error("GitHub commit creation did not return a sha");
|
|
8219
|
+
const updatedRef = await updateRef({ branch: targetBranch, sha: nextCommit.sha, force: false });
|
|
8220
|
+
if (syncLocal) {
|
|
8221
|
+
runGitFn(directory, ["fetch", "origin", targetBranch]);
|
|
8222
|
+
runGitFn(directory, ["reset", "--hard", "FETCH_HEAD"]);
|
|
8223
|
+
}
|
|
8224
|
+
return {
|
|
8225
|
+
ok: true,
|
|
8226
|
+
action: "committed",
|
|
8227
|
+
branch: targetBranch,
|
|
8228
|
+
before: headSha,
|
|
8229
|
+
after: nextCommit.sha,
|
|
8230
|
+
changedPaths,
|
|
8231
|
+
treeEntries: tree.length,
|
|
8232
|
+
verification: nextCommit.verification || null,
|
|
8233
|
+
ref: updatedRef
|
|
8234
|
+
};
|
|
8235
|
+
},
|
|
8236
|
+
async authMode() {
|
|
8237
|
+
if (appEnabled) return { mode: "github_app", appId, installationId };
|
|
8238
|
+
if (staticToken) return { mode: "token" };
|
|
8239
|
+
return { mode: "anonymous" };
|
|
8240
|
+
}
|
|
8241
|
+
};
|
|
8242
|
+
}
|
|
8243
|
+
|
|
8244
|
+
// src/include_resolver.js
|
|
8245
|
+
import fs4 from "node:fs";
|
|
8246
|
+
import path4 from "node:path";
|
|
8247
|
+
function compactPromptPath(filePath) {
|
|
8248
|
+
if (!filePath.endsWith(".md") || filePath.endsWith(".prompt.md")) return null;
|
|
8249
|
+
return filePath.replace(/\.md$/, ".prompt.md");
|
|
8250
|
+
}
|
|
8251
|
+
function isSameOrNestedPath(candidate, root) {
|
|
8252
|
+
if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
|
|
8253
|
+
const resolvedCandidate = path4.resolve(candidate);
|
|
8254
|
+
const resolvedRoot = path4.resolve(root);
|
|
8255
|
+
const relative = path4.relative(resolvedRoot, resolvedCandidate);
|
|
8256
|
+
return relative === "" || !!relative && !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
8257
|
+
}
|
|
8258
|
+
function resolveWithin(baseDir, relativePath) {
|
|
8259
|
+
if (!relativePath) return null;
|
|
8260
|
+
if (path4.isAbsolute(relativePath)) return null;
|
|
8261
|
+
const resolved = path4.resolve(baseDir, relativePath);
|
|
8262
|
+
return isSameOrNestedPath(resolved, baseDir) ? resolved : null;
|
|
8263
|
+
}
|
|
8264
|
+
function withCompactPreference(paths, options = {}) {
|
|
8265
|
+
if (!options.preferCompactPromptDocs) return paths;
|
|
8266
|
+
const compactPaths = paths.map((filePath) => filePath ? compactPromptPath(filePath) : null).filter(Boolean);
|
|
8267
|
+
return [...compactPaths, ...paths];
|
|
8268
|
+
}
|
|
8269
|
+
function resolveIncludeFile(includeRef, repoRoot, bundleRoot, options = {}) {
|
|
8270
|
+
const trimmed = String(includeRef || "").trim();
|
|
8271
|
+
const scopedMatch = trimmed.match(/^([a-z]+):(.*)$/i);
|
|
8272
|
+
const scope = scopedMatch?.[1]?.toLowerCase();
|
|
8273
|
+
const target = scopedMatch ? scopedMatch[2].trim() : trimmed;
|
|
8274
|
+
const optimaRoot = options.optimaRoot || path4.join(repoRoot, ".optima");
|
|
8275
|
+
const repoPolicyRoot = options.repoPolicyRoot || path4.join(optimaRoot, "policies");
|
|
8276
|
+
const bundlePolicyRoot = options.bundlePolicyRoot || path4.join(bundleRoot, "assets", "policies");
|
|
8277
|
+
if (scope === "plugin") {
|
|
8278
|
+
for (const filePath of withCompactPreference([resolveWithin(bundleRoot, target)], options)) {
|
|
8279
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8280
|
+
}
|
|
8281
|
+
return null;
|
|
8282
|
+
}
|
|
8283
|
+
if (scope === "repo") {
|
|
8284
|
+
for (const filePath of withCompactPreference([resolveWithin(optimaRoot, target)], options)) {
|
|
8285
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8286
|
+
}
|
|
8287
|
+
return null;
|
|
8288
|
+
}
|
|
8289
|
+
if (scope === "policy") {
|
|
8290
|
+
const candidates2 = withCompactPreference([
|
|
8291
|
+
resolveWithin(repoPolicyRoot, target),
|
|
8292
|
+
resolveWithin(bundlePolicyRoot, target)
|
|
8293
|
+
], options);
|
|
8294
|
+
for (const filePath of candidates2) {
|
|
8295
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8296
|
+
}
|
|
8297
|
+
return null;
|
|
8298
|
+
}
|
|
8299
|
+
const candidates = withCompactPreference([
|
|
8300
|
+
resolveWithin(repoRoot, target),
|
|
8301
|
+
resolveWithin(bundleRoot, target)
|
|
8302
|
+
], options);
|
|
8303
|
+
for (const filePath of candidates) {
|
|
8304
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8305
|
+
}
|
|
8306
|
+
return null;
|
|
8307
|
+
}
|
|
8308
|
+
function resolveIncludes(text, repoRoot, bundleRoot, options = {}) {
|
|
8309
|
+
const includeDepth = Number(options.includeDepth || 0);
|
|
8310
|
+
const maxIncludeDepth = Number(options.maxIncludeDepth || 20);
|
|
8311
|
+
if (includeDepth > maxIncludeDepth) {
|
|
8312
|
+
return "\n\n# ERROR: Include recursion limit exceeded.\n\n";
|
|
8313
|
+
}
|
|
8314
|
+
const includeRegex = /<include:(.*?)>/g;
|
|
8315
|
+
return String(text || "").replace(includeRegex, (match, includeRef) => {
|
|
8316
|
+
const filePath = resolveIncludeFile(includeRef, repoRoot, bundleRoot, options);
|
|
8317
|
+
if (!filePath) {
|
|
8318
|
+
console.warn(`[Optima] Include file not found: ${includeRef}`);
|
|
8319
|
+
return `
|
|
8320
|
+
|
|
8321
|
+
# ERROR: Include file not found: ${includeRef}
|
|
8322
|
+
|
|
8323
|
+
`;
|
|
8324
|
+
}
|
|
8325
|
+
const content = fs4.readFileSync(filePath, "utf8");
|
|
8326
|
+
return resolveIncludes(content, repoRoot, bundleRoot, { ...options, includeDepth: includeDepth + 1, maxIncludeDepth });
|
|
8327
|
+
});
|
|
8328
|
+
}
|
|
8329
|
+
|
|
8330
|
+
// src/markdown_artifacts.js
|
|
8331
|
+
import fs5 from "node:fs";
|
|
8332
|
+
var CLICKUP_REQUIRED_SUMMARY_SECTIONS = [
|
|
8333
|
+
"Summary",
|
|
8334
|
+
"Work Performed",
|
|
8335
|
+
"AC Coverage",
|
|
8336
|
+
"Verification Results",
|
|
8337
|
+
"Documentation Impact",
|
|
8338
|
+
"Open Risks",
|
|
8339
|
+
"Recommended Next Step"
|
|
8340
|
+
];
|
|
8341
|
+
var RAW_LOG_SECTION_NAMES = /* @__PURE__ */ new Set(["Raw Logs", "Logs", "Full Logs", "Command Output", "Transcript"]);
|
|
8342
|
+
function parseMarkdownSections(markdown = "") {
|
|
8343
|
+
const sections = {};
|
|
8344
|
+
let current = null;
|
|
8345
|
+
let buffer = [];
|
|
8346
|
+
const flush = () => {
|
|
8347
|
+
if (!current) return;
|
|
8348
|
+
sections[current] = buffer.join("\n").trim();
|
|
8349
|
+
};
|
|
8350
|
+
for (const line of String(markdown).split(/\r?\n/)) {
|
|
8351
|
+
const heading = /^(#{2,3})\s+(.+?)\s*$/.exec(line);
|
|
8352
|
+
if (heading) {
|
|
8353
|
+
flush();
|
|
8354
|
+
current = heading[2].trim();
|
|
8355
|
+
buffer = [];
|
|
8356
|
+
continue;
|
|
8357
|
+
}
|
|
8358
|
+
if (current) buffer.push(line);
|
|
8359
|
+
}
|
|
8360
|
+
flush();
|
|
8361
|
+
return sections;
|
|
8362
|
+
}
|
|
8363
|
+
function parseMarkdownArtifact(markdown = "", { requiredSections = [] } = {}) {
|
|
8364
|
+
const sections = parseMarkdownSections(markdown);
|
|
8365
|
+
const missing = requiredSections.filter((section) => !sections[section]);
|
|
8366
|
+
return {
|
|
8367
|
+
ok: missing.length === 0,
|
|
8368
|
+
sections,
|
|
8369
|
+
missing,
|
|
8370
|
+
message: missing.length ? `Missing required section(s): ${missing.join(", ")}` : "ok"
|
|
8371
|
+
};
|
|
8372
|
+
}
|
|
8373
|
+
function readMarkdownArtifact(filePath, options = {}) {
|
|
8374
|
+
const markdown = fs5.readFileSync(filePath, "utf8");
|
|
8375
|
+
return parseMarkdownArtifact(markdown, options);
|
|
8376
|
+
}
|
|
8377
|
+
function stripRawLogSections(sections = {}) {
|
|
8378
|
+
return Object.fromEntries(
|
|
8379
|
+
Object.entries(sections).filter(([name]) => !RAW_LOG_SECTION_NAMES.has(name))
|
|
8380
|
+
);
|
|
8381
|
+
}
|
|
8382
|
+
|
|
8383
|
+
// src/repair.js
|
|
8384
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
8385
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
8386
|
+
import fs6 from "node:fs";
|
|
8387
|
+
import path5 from "node:path";
|
|
8388
|
+
var CODEMAP_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8389
|
+
".js",
|
|
8390
|
+
".ts",
|
|
8391
|
+
".tsx",
|
|
8392
|
+
".jsx",
|
|
8393
|
+
".dart",
|
|
8394
|
+
".py",
|
|
8395
|
+
".go",
|
|
8396
|
+
".rs",
|
|
8397
|
+
".java",
|
|
8398
|
+
".c",
|
|
8399
|
+
".cpp",
|
|
8400
|
+
".cs",
|
|
8401
|
+
".php",
|
|
8402
|
+
".rb",
|
|
8403
|
+
".swift",
|
|
8404
|
+
".kt",
|
|
8405
|
+
".m",
|
|
8406
|
+
".sh",
|
|
8407
|
+
".sql",
|
|
8408
|
+
".yaml",
|
|
8409
|
+
".yml",
|
|
8410
|
+
".json",
|
|
8411
|
+
".md"
|
|
8412
|
+
]);
|
|
8413
|
+
var LEGACY_OPERATIONAL_ROOTS = /* @__PURE__ */ new Set([
|
|
8414
|
+
"tasks",
|
|
8415
|
+
"evidences",
|
|
8416
|
+
".orbita",
|
|
8417
|
+
".staticeng",
|
|
8418
|
+
".nomadworks",
|
|
8419
|
+
".nomadwork",
|
|
8420
|
+
"nomadworks",
|
|
8421
|
+
".codenomad"
|
|
8422
|
+
]);
|
|
8423
|
+
var OPTIMA_OPERATIONAL_FOLDERS = [".optima", "templates", "dist"];
|
|
8424
|
+
function relPath(worktree, targetPath) {
|
|
8425
|
+
return path5.relative(worktree, targetPath).split(path5.sep).join("/") || ".";
|
|
8426
|
+
}
|
|
8427
|
+
function normalizeCodemapRelPath(relPathValue) {
|
|
8428
|
+
return path5.normalize(relPathValue).replace(/\\/g, "/").replace(/\/$/, "");
|
|
8429
|
+
}
|
|
8430
|
+
function firstPathSegment(relPathValue) {
|
|
8431
|
+
return normalizeCodemapRelPath(relPathValue).split("/").filter(Boolean)[0] || "";
|
|
8432
|
+
}
|
|
8433
|
+
function isOperationalRelPath(relPathValue) {
|
|
8434
|
+
if (!relPathValue) return false;
|
|
8435
|
+
const normalized = normalizeCodemapRelPath(relPathValue);
|
|
8436
|
+
const firstSegment = firstPathSegment(normalized);
|
|
8437
|
+
if (LEGACY_OPERATIONAL_ROOTS.has(firstSegment)) return true;
|
|
8438
|
+
return OPTIMA_OPERATIONAL_FOLDERS.some((folder) => normalized === folder || normalized.startsWith(`${folder}/`));
|
|
8439
|
+
}
|
|
8440
|
+
function isHiddenTree(relPathValue) {
|
|
8441
|
+
if (!relPathValue) return false;
|
|
8442
|
+
return normalizeCodemapRelPath(relPathValue).split("/").some((part) => part.startsWith("."));
|
|
8443
|
+
}
|
|
8444
|
+
function loadGitIgnoreMatcher(worktree) {
|
|
8445
|
+
const ig = (0, import_ignore.default)();
|
|
8446
|
+
ig.add(".git");
|
|
8447
|
+
const gitignorePath = path5.join(worktree, ".gitignore");
|
|
8448
|
+
if (fs6.existsSync(gitignorePath)) ig.add(fs6.readFileSync(gitignorePath, "utf8"));
|
|
8449
|
+
return ig;
|
|
8450
|
+
}
|
|
8451
|
+
function codemapSectionEntries(value) {
|
|
8452
|
+
if (Array.isArray(value)) return value;
|
|
8453
|
+
if (value && typeof value === "object") return Object.values(value);
|
|
8454
|
+
return [];
|
|
8455
|
+
}
|
|
8456
|
+
function codemapIndexedPaths(map) {
|
|
8457
|
+
const indexed = /* @__PURE__ */ new Set();
|
|
8458
|
+
for (const section of ["modules", "entrypoints", "sources_of_truth", "links", "internals"]) {
|
|
8459
|
+
for (const item of codemapSectionEntries(map?.[section])) {
|
|
8460
|
+
if (item?.path) indexed.add(normalizeCodemapRelPath(item.path));
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
return indexed;
|
|
8464
|
+
}
|
|
8465
|
+
function readCodemap(filePath, unresolved, worktree) {
|
|
8466
|
+
try {
|
|
8467
|
+
const parsed = import_yaml.default.parse(fs6.readFileSync(filePath, "utf8"));
|
|
8468
|
+
if (!parsed || typeof parsed !== "object") {
|
|
8469
|
+
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap is empty or invalid; manual repair required." });
|
|
8470
|
+
return null;
|
|
8471
|
+
}
|
|
8472
|
+
return parsed;
|
|
8473
|
+
} catch {
|
|
8474
|
+
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap has invalid YAML; manual repair required." });
|
|
8475
|
+
return null;
|
|
8476
|
+
}
|
|
8477
|
+
}
|
|
8478
|
+
function writeCodemap(filePath, map) {
|
|
8479
|
+
fs6.writeFileSync(filePath, import_yaml.default.stringify(map), "utf8");
|
|
8480
|
+
}
|
|
8481
|
+
function isIgnoredPath(ig, relativePath) {
|
|
8482
|
+
const normalized = normalizeCodemapRelPath(relativePath);
|
|
8483
|
+
return Boolean(normalized) && ig.ignores(normalized);
|
|
8484
|
+
}
|
|
8485
|
+
function hasImmediateSourceFile(dirPath, ig, worktree) {
|
|
8486
|
+
if (!fs6.existsSync(dirPath)) return false;
|
|
8487
|
+
for (const item of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8488
|
+
const childPath = path5.join(dirPath, item.name);
|
|
8489
|
+
const relative = path5.relative(worktree, childPath);
|
|
8490
|
+
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative)) continue;
|
|
8491
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) return true;
|
|
8492
|
+
}
|
|
8493
|
+
return false;
|
|
8494
|
+
}
|
|
8495
|
+
function containsSource(dirPath, ig, worktree) {
|
|
8496
|
+
if (!fs6.existsSync(dirPath)) return false;
|
|
8497
|
+
const dirRelPath = path5.relative(worktree, dirPath);
|
|
8498
|
+
if (isOperationalRelPath(dirRelPath) || isHiddenTree(dirRelPath)) return false;
|
|
8499
|
+
for (const item of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8500
|
+
const childPath = path5.join(dirPath, item.name);
|
|
8501
|
+
const relative = path5.relative(worktree, childPath);
|
|
8502
|
+
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative)) continue;
|
|
8503
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) return true;
|
|
8504
|
+
if (item.isDirectory() && containsSource(childPath, ig, worktree)) return true;
|
|
8505
|
+
}
|
|
8506
|
+
return false;
|
|
8008
8507
|
}
|
|
8009
8508
|
function createModuleCodemap(dirPath, worktree, deps) {
|
|
8010
|
-
const entries =
|
|
8509
|
+
const entries = fs6.readdirSync(dirPath, { withFileTypes: true });
|
|
8011
8510
|
const entrypoints = [];
|
|
8012
8511
|
const internals = [];
|
|
8013
|
-
const relToRoot =
|
|
8512
|
+
const relToRoot = path5.relative(dirPath, deps.optimaCodemapPath(worktree)).split(path5.sep).join("/");
|
|
8014
8513
|
for (const item of entries) {
|
|
8015
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
8514
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) continue;
|
|
8016
8515
|
const bucket = /^index\.(js|ts|tsx|jsx)$|^main\.(js|ts|tsx|jsx|py|go|rs|java)$/.test(item.name) ? entrypoints : internals;
|
|
8017
8516
|
bucket.push({ path: item.name });
|
|
8018
8517
|
}
|
|
@@ -8025,7 +8524,7 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8025
8524
|
const map = readCodemap(codemapPath, unresolved, worktree);
|
|
8026
8525
|
if (!map) return;
|
|
8027
8526
|
const isRootMap = codemapPath === deps.optimaCodemapPath(worktree);
|
|
8028
|
-
const mapDir =
|
|
8527
|
+
const mapDir = path5.dirname(codemapPath);
|
|
8029
8528
|
const pathBase = isRootMap ? worktree : mapDir;
|
|
8030
8529
|
let changed = false;
|
|
8031
8530
|
for (const section of ["modules", "entrypoints", "sources_of_truth", "links", "internals"]) {
|
|
@@ -8036,13 +8535,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8036
8535
|
}
|
|
8037
8536
|
const nextEntries = [];
|
|
8038
8537
|
for (const entry of originalEntries) {
|
|
8039
|
-
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") ||
|
|
8538
|
+
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") || path5.isAbsolute(entry.path)) {
|
|
8040
8539
|
nextEntries.push(entry);
|
|
8041
8540
|
continue;
|
|
8042
8541
|
}
|
|
8043
8542
|
const normalizedPath = normalizeCodemapRelPath(entry.path);
|
|
8044
|
-
const absPath =
|
|
8045
|
-
if (!
|
|
8543
|
+
const absPath = path5.join(pathBase, normalizedPath);
|
|
8544
|
+
if (!fs6.existsSync(absPath)) {
|
|
8046
8545
|
registerAction({ category: "codemap", action: apply ? "removed" : "would_remove", path: relPath(worktree, codemapPath), detail: `Remove broken ${section.slice(0, -1)} reference '${entry.path}'.` });
|
|
8047
8546
|
changed = true;
|
|
8048
8547
|
continue;
|
|
@@ -8051,8 +8550,8 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8051
8550
|
const maxParts = isRootMap ? 2 : 1;
|
|
8052
8551
|
if (parts.length > maxParts) {
|
|
8053
8552
|
const localPath = isRootMap ? parts.slice(0, 2).join("/") : parts[0];
|
|
8054
|
-
const localAbs =
|
|
8055
|
-
if (
|
|
8553
|
+
const localAbs = path5.join(pathBase, localPath);
|
|
8554
|
+
if (fs6.existsSync(localAbs)) {
|
|
8056
8555
|
nextEntries.push({ ...entry, path: localPath });
|
|
8057
8556
|
changed = true;
|
|
8058
8557
|
registerAction({ category: "codemap", action: apply ? "updated" : "would_update", path: relPath(worktree, codemapPath), detail: `Shorten '${entry.path}' to local path '${localPath}'.` });
|
|
@@ -8066,11 +8565,11 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8066
8565
|
}
|
|
8067
8566
|
if (Array.isArray(map[section])) map[section] = nextEntries;
|
|
8068
8567
|
}
|
|
8069
|
-
if (map.scope === "module" && !isOperationalRelPath(
|
|
8568
|
+
if (map.scope === "module" && !isOperationalRelPath(path5.relative(worktree, mapDir))) {
|
|
8070
8569
|
const indexed = codemapIndexedPaths(map);
|
|
8071
8570
|
const internals = Array.isArray(map.internals) ? map.internals : [];
|
|
8072
|
-
for (const item of
|
|
8073
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
8571
|
+
for (const item of fs6.readdirSync(mapDir, { withFileTypes: true })) {
|
|
8572
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) continue;
|
|
8074
8573
|
if (indexed.has(item.name)) continue;
|
|
8075
8574
|
internals.push({ path: item.name });
|
|
8076
8575
|
indexed.add(item.name);
|
|
@@ -8084,13 +8583,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8084
8583
|
function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
8085
8584
|
const ig = loadGitIgnoreMatcher(worktree);
|
|
8086
8585
|
const rootCodemapPath = deps.optimaCodemapPath(worktree);
|
|
8087
|
-
const rootCodemapMissing = !
|
|
8586
|
+
const rootCodemapMissing = !fs6.existsSync(rootCodemapPath);
|
|
8088
8587
|
if (rootCodemapMissing) {
|
|
8089
8588
|
actions.push({ category: "codemap", action: apply ? "created" : "would_create", path: ".optima/codemap.yml", detail: "Create missing root CodeMap from template." });
|
|
8090
8589
|
if (apply) deps.scaffoldOptimaRootCodemap(worktree);
|
|
8091
8590
|
}
|
|
8092
|
-
if (
|
|
8093
|
-
const rootMap =
|
|
8591
|
+
if (fs6.existsSync(rootCodemapPath)) repairSingleCodemap(rootCodemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
8592
|
+
const rootMap = fs6.existsSync(rootCodemapPath) ? readCodemap(rootCodemapPath, unresolved, worktree) : null;
|
|
8094
8593
|
let rootChanged = false;
|
|
8095
8594
|
const rootModulesRepairable = rootMap && (rootMap.modules === void 0 || Array.isArray(rootMap.modules));
|
|
8096
8595
|
if (rootMap) {
|
|
@@ -8102,10 +8601,10 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
8102
8601
|
}
|
|
8103
8602
|
}
|
|
8104
8603
|
function walk(dirPath) {
|
|
8105
|
-
const relative =
|
|
8604
|
+
const relative = path5.relative(worktree, dirPath);
|
|
8106
8605
|
if (relative && (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative))) return;
|
|
8107
|
-
const codemapPath =
|
|
8108
|
-
const hasCodemap =
|
|
8606
|
+
const codemapPath = path5.join(dirPath, "codemap.yml");
|
|
8607
|
+
const hasCodemap = fs6.existsSync(codemapPath);
|
|
8109
8608
|
const sourceDir = relative && containsSource(dirPath, ig, worktree);
|
|
8110
8609
|
if (sourceDir && !hasCodemap && dirPath !== worktree) {
|
|
8111
8610
|
if (hasImmediateSourceFile(dirPath, ig, worktree)) {
|
|
@@ -8124,19 +8623,19 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
8124
8623
|
actions.push({ category: "codemap", action: apply ? "updated" : "would_update", path: ".optima/codemap.yml", detail: `Register top-level source module '${normalizeCodemapRelPath(relative)}'.` });
|
|
8125
8624
|
}
|
|
8126
8625
|
}
|
|
8127
|
-
if (
|
|
8128
|
-
for (const item of
|
|
8129
|
-
if (item.isDirectory()) walk(
|
|
8626
|
+
if (fs6.existsSync(codemapPath) && codemapPath !== rootCodemapPath) repairSingleCodemap(codemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
8627
|
+
for (const item of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8628
|
+
if (item.isDirectory()) walk(path5.join(dirPath, item.name));
|
|
8130
8629
|
}
|
|
8131
8630
|
}
|
|
8132
|
-
if (
|
|
8631
|
+
if (fs6.existsSync(worktree)) walk(worktree);
|
|
8133
8632
|
if (rootMap && rootChanged && apply) writeCodemap(rootCodemapPath, rootMap);
|
|
8134
8633
|
}
|
|
8135
8634
|
function walkMarkdownFiles(dirPath) {
|
|
8136
|
-
if (!
|
|
8635
|
+
if (!fs6.existsSync(dirPath)) return [];
|
|
8137
8636
|
const files = [];
|
|
8138
|
-
for (const entry of
|
|
8139
|
-
const entryPath =
|
|
8637
|
+
for (const entry of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8638
|
+
const entryPath = path5.join(dirPath, entry.name);
|
|
8140
8639
|
if (entry.isDirectory()) {
|
|
8141
8640
|
files.push(...walkMarkdownFiles(entryPath));
|
|
8142
8641
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -8175,7 +8674,7 @@ function rewriteOptimaMarkdownReferences(content, evidenceBasePath = null) {
|
|
|
8175
8674
|
}
|
|
8176
8675
|
function optimaEvidenceBaseForFile(worktree, filePath, deps) {
|
|
8177
8676
|
const evidenceRoot = deps.optimaEvidencesDir(worktree);
|
|
8178
|
-
const relative =
|
|
8677
|
+
const relative = path5.relative(evidenceRoot, filePath).split(path5.sep).join("/");
|
|
8179
8678
|
const [taskId] = relative.split("/");
|
|
8180
8679
|
if (!taskId || taskId === ".." || relative.startsWith("../")) return null;
|
|
8181
8680
|
return `.optima/evidences/${taskId}`;
|
|
@@ -8188,11 +8687,11 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
8188
8687
|
];
|
|
8189
8688
|
const changed = [];
|
|
8190
8689
|
for (const filePath of scanRoots.flatMap(walkMarkdownFiles)) {
|
|
8191
|
-
const raw =
|
|
8192
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
8690
|
+
const raw = fs6.readFileSync(filePath, "utf8");
|
|
8691
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path5.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
8193
8692
|
const rewritten = rewriteOptimaMarkdownReferences(raw, evidenceBasePath);
|
|
8194
8693
|
if (rewritten !== raw) {
|
|
8195
|
-
|
|
8694
|
+
fs6.writeFileSync(filePath, rewritten, "utf8");
|
|
8196
8695
|
changed.push(relPath(worktree, filePath));
|
|
8197
8696
|
}
|
|
8198
8697
|
}
|
|
@@ -8200,8 +8699,8 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
8200
8699
|
}
|
|
8201
8700
|
function missingOptimaGitignoreRules(worktree, deps) {
|
|
8202
8701
|
if (!deps.isGitRepository(worktree)) return [];
|
|
8203
|
-
const gitignorePath =
|
|
8204
|
-
const existing =
|
|
8702
|
+
const gitignorePath = path5.join(worktree, ".gitignore");
|
|
8703
|
+
const existing = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
|
|
8205
8704
|
const existingRules = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
8206
8705
|
return deps.optimaGitignoreRules.filter((rule) => !existingRules.has(rule));
|
|
8207
8706
|
}
|
|
@@ -8218,11 +8717,11 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
8218
8717
|
const apply = mode === "apply";
|
|
8219
8718
|
const actions = [];
|
|
8220
8719
|
const unresolved = [];
|
|
8221
|
-
const hadOptima =
|
|
8720
|
+
const hadOptima = fs6.existsSync(deps.optimaDir(worktree));
|
|
8222
8721
|
const missingGitignoreRules = missingOptimaGitignoreRules(worktree, deps);
|
|
8223
8722
|
const markdownBefore = apply ? [] : walkMarkdownFiles(deps.optimaDir(worktree)).filter((filePath) => {
|
|
8224
|
-
const raw =
|
|
8225
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
8723
|
+
const raw = fs6.readFileSync(filePath, "utf8");
|
|
8724
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path5.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
8226
8725
|
return rewriteOptimaMarkdownReferences(raw, evidenceBasePath) !== raw;
|
|
8227
8726
|
}).map((filePath) => relPath(worktree, filePath));
|
|
8228
8727
|
if (!hadOptima) pushRepairAction(actions, "operational", apply ? "created" : "would_create", ".optima/", "Create Optima operational root.");
|
|
@@ -8231,31 +8730,31 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
8231
8730
|
[".staticeng/", deps.legacyStaticEngDir(worktree)],
|
|
8232
8731
|
[".nomadwork/", deps.legacyNomadworkDir(worktree)],
|
|
8233
8732
|
[".nomadworks/", deps.legacyNomadworksDir(worktree)],
|
|
8234
|
-
["tasks/",
|
|
8235
|
-
["evidences/",
|
|
8236
|
-
["docs/scrs/",
|
|
8237
|
-
["codemap.yml",
|
|
8238
|
-
["codemap.yaml",
|
|
8733
|
+
["tasks/", path5.join(worktree, "tasks")],
|
|
8734
|
+
["evidences/", path5.join(worktree, "evidences")],
|
|
8735
|
+
["docs/scrs/", path5.join(worktree, "docs", "scrs")],
|
|
8736
|
+
["codemap.yml", path5.join(worktree, "codemap.yml")],
|
|
8737
|
+
["codemap.yaml", path5.join(worktree, "codemap.yaml")]
|
|
8239
8738
|
];
|
|
8240
|
-
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/",
|
|
8739
|
+
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/", path5.join(worktree, "policies")]);
|
|
8241
8740
|
for (const [display, sourcePath] of legacySources) {
|
|
8242
|
-
if (
|
|
8741
|
+
if (fs6.existsSync(sourcePath)) pushRepairAction(actions, "legacy", apply ? "migrated" : "would_migrate", display, "Move or merge legacy/root Optima artifact into .optima using preservation safeguards.");
|
|
8243
8742
|
}
|
|
8244
8743
|
if (apply) deps.migrateLegacyOptimaLayout(worktree);
|
|
8245
|
-
const configMissing = !
|
|
8744
|
+
const configMissing = !fs6.existsSync(deps.repoConfigPath(worktree));
|
|
8246
8745
|
if (configMissing) pushRepairAction(actions, "config", apply ? "created" : "would_create", ".optima/.config/optima.yaml", "Create missing local Optima config from template.");
|
|
8247
8746
|
if (apply) deps.scaffoldOptimaConfig(worktree, args.team_mode || "full");
|
|
8248
8747
|
const requiredFiles = [
|
|
8249
|
-
[
|
|
8250
|
-
[
|
|
8251
|
-
[
|
|
8252
|
-
[
|
|
8253
|
-
[
|
|
8254
|
-
[
|
|
8255
|
-
[
|
|
8748
|
+
[path5.join(deps.optimaTasksDir(worktree), "current.md"), "registry", ".optima/tasks/current.md", deps.currentTasksRegistryContent()],
|
|
8749
|
+
[path5.join(deps.optimaTasksDir(worktree), "done.md"), "registry", ".optima/tasks/done.md", deps.doneTasksRegistryContent()],
|
|
8750
|
+
[path5.join(deps.optimaScrsDir(worktree), "current.md"), "registry", ".optima/docs/scrs/current.md", deps.currentScrRegistryContent()],
|
|
8751
|
+
[path5.join(deps.optimaScrsDir(worktree), "done.md"), "registry", ".optima/docs/scrs/done.md", deps.doneScrRegistryContent()],
|
|
8752
|
+
[path5.join(deps.optimaTasksDir(worktree), "task-template.md"), "template", ".optima/tasks/task-template.md", deps.taskTemplateContent()],
|
|
8753
|
+
[path5.join(deps.optimaTasksDir(worktree), "subtask-template.md"), "template", ".optima/tasks/subtask-template.md", deps.subtaskTemplateContent()],
|
|
8754
|
+
[path5.join(deps.repoPoliciesDir(worktree), "README.md"), "policy", ".optima/policies/README.md", deps.repoLocalPoliciesReadme]
|
|
8256
8755
|
];
|
|
8257
8756
|
for (const [filePath, category, displayPath, content] of requiredFiles) {
|
|
8258
|
-
if (!
|
|
8757
|
+
if (!fs6.existsSync(filePath)) {
|
|
8259
8758
|
pushRepairAction(actions, category, apply ? "created" : "would_create", displayPath, "Create missing Optima scaffold file without overwriting existing content.");
|
|
8260
8759
|
if (apply) deps.ensureFileIfMissing(filePath, content);
|
|
8261
8760
|
}
|
|
@@ -8309,18 +8808,18 @@ function formatRepairResult(plan, validationResult = null, formatValidationResul
|
|
|
8309
8808
|
// src/validate_logic.js
|
|
8310
8809
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
8311
8810
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
8312
|
-
import
|
|
8313
|
-
import
|
|
8811
|
+
import fs7 from "node:fs";
|
|
8812
|
+
import path6 from "node:path";
|
|
8314
8813
|
async function optima_validate_logic(worktree) {
|
|
8315
|
-
const rootCodemapPath =
|
|
8316
|
-
if (!
|
|
8814
|
+
const rootCodemapPath = path6.join(worktree, ".optima", "codemap.yml");
|
|
8815
|
+
if (!fs7.existsSync(rootCodemapPath)) return { ok: false, errors: [".optima/codemap.yml not found."], warnings: [] };
|
|
8317
8816
|
const errors = [];
|
|
8318
8817
|
const warnings = [];
|
|
8319
8818
|
const ig = (0, import_ignore2.default)();
|
|
8320
8819
|
ig.add(".git");
|
|
8321
|
-
const gitignorePath =
|
|
8322
|
-
if (
|
|
8323
|
-
ig.add(
|
|
8820
|
+
const gitignorePath = path6.join(worktree, ".gitignore");
|
|
8821
|
+
if (fs7.existsSync(gitignorePath)) {
|
|
8822
|
+
ig.add(fs7.readFileSync(gitignorePath, "utf8"));
|
|
8324
8823
|
}
|
|
8325
8824
|
const sourceExtensions = [
|
|
8326
8825
|
".js",
|
|
@@ -8360,30 +8859,30 @@ async function optima_validate_logic(worktree) {
|
|
|
8360
8859
|
const operationalFolders = [".optima", "templates", "dist"];
|
|
8361
8860
|
const isHiddenTree2 = (relPath2) => {
|
|
8362
8861
|
if (!relPath2) return false;
|
|
8363
|
-
return relPath2.split(
|
|
8862
|
+
return relPath2.split(path6.sep).some((part) => part.startsWith("."));
|
|
8364
8863
|
};
|
|
8365
|
-
const firstPathSegment2 = (relPath2) => relPath2.split(
|
|
8864
|
+
const firstPathSegment2 = (relPath2) => relPath2.split(path6.sep).filter(Boolean)[0] || "";
|
|
8366
8865
|
const isOperationalRelPath2 = (relPath2) => {
|
|
8367
8866
|
if (!relPath2) return false;
|
|
8368
8867
|
const firstSegment = firstPathSegment2(relPath2);
|
|
8369
8868
|
if (legacyOperationalRoots.has(firstSegment)) return true;
|
|
8370
|
-
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f +
|
|
8869
|
+
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f + path6.sep));
|
|
8371
8870
|
};
|
|
8372
8871
|
const getSectionEntries = (value) => {
|
|
8373
8872
|
if (Array.isArray(value)) return value;
|
|
8374
8873
|
if (value && typeof value === "object") return Object.values(value);
|
|
8375
8874
|
return [];
|
|
8376
8875
|
};
|
|
8377
|
-
const normalizeRelativePath = (relPath2) =>
|
|
8876
|
+
const normalizeRelativePath = (relPath2) => path6.normalize(relPath2).replace(/\\/g, "/").replace(/\/$/, "");
|
|
8378
8877
|
const isSourceDir = (dirPath) => {
|
|
8379
|
-
const dirRelPath =
|
|
8878
|
+
const dirRelPath = path6.relative(worktree, dirPath);
|
|
8380
8879
|
if (isOperationalRelPath2(dirRelPath)) return false;
|
|
8381
|
-
const items =
|
|
8880
|
+
const items = fs7.readdirSync(dirPath, { withFileTypes: true });
|
|
8382
8881
|
for (const item of items) {
|
|
8383
|
-
const childPath =
|
|
8384
|
-
const relPath2 =
|
|
8882
|
+
const childPath = path6.join(dirPath, item.name);
|
|
8883
|
+
const relPath2 = path6.relative(worktree, childPath);
|
|
8385
8884
|
if (ig.ignores(relPath2) || isOperationalRelPath2(relPath2)) continue;
|
|
8386
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
8885
|
+
if (item.isFile() && sourceExtensions.includes(path6.extname(item.name))) return true;
|
|
8387
8886
|
if (item.isDirectory()) {
|
|
8388
8887
|
if (isSourceDir(childPath)) return true;
|
|
8389
8888
|
}
|
|
@@ -8391,7 +8890,7 @@ async function optima_validate_logic(worktree) {
|
|
|
8391
8890
|
return false;
|
|
8392
8891
|
};
|
|
8393
8892
|
function validateMap(filePath) {
|
|
8394
|
-
const content =
|
|
8893
|
+
const content = fs7.readFileSync(filePath, "utf8");
|
|
8395
8894
|
let map;
|
|
8396
8895
|
try {
|
|
8397
8896
|
map = import_yaml2.default.parse(content);
|
|
@@ -8403,32 +8902,32 @@ async function optima_validate_logic(worktree) {
|
|
|
8403
8902
|
errors.push(`${filePath}: Invalid YAML.`);
|
|
8404
8903
|
return;
|
|
8405
8904
|
}
|
|
8406
|
-
const dir =
|
|
8905
|
+
const dir = path6.dirname(filePath);
|
|
8407
8906
|
const pathBase = filePath === rootCodemapPath ? worktree : dir;
|
|
8408
8907
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
8409
8908
|
const sectionsToVerify = ["modules", "entrypoints", "sources_of_truth", "links", "internals"];
|
|
8410
8909
|
for (const section of sectionsToVerify) {
|
|
8411
8910
|
for (const item of getSectionEntries(map[section])) {
|
|
8412
8911
|
if (item?.path) {
|
|
8413
|
-
indexedPaths.add(
|
|
8912
|
+
indexedPaths.add(path6.normalize(item.path));
|
|
8414
8913
|
if (section === "links" && (item.path.startsWith("http://") || item.path.startsWith("https://"))) {
|
|
8415
8914
|
continue;
|
|
8416
8915
|
}
|
|
8417
|
-
const absPath =
|
|
8418
|
-
if (!
|
|
8916
|
+
const absPath = path6.isAbsolute(item.path) ? item.path : path6.join(pathBase, item.path);
|
|
8917
|
+
if (!fs7.existsSync(absPath)) {
|
|
8419
8918
|
errors.push(`${filePath}: ${section.slice(0, -1)} path does not exist: ${item.path}`);
|
|
8420
8919
|
}
|
|
8421
8920
|
}
|
|
8422
8921
|
}
|
|
8423
8922
|
}
|
|
8424
|
-
const relDir =
|
|
8923
|
+
const relDir = path6.relative(worktree, dir);
|
|
8425
8924
|
const isOperational = isOperationalRelPath2(relDir);
|
|
8426
8925
|
if (map.scope === "module" && !isOperational) {
|
|
8427
|
-
const items =
|
|
8926
|
+
const items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
8428
8927
|
for (const item of items) {
|
|
8429
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
8928
|
+
if (item.isFile() && sourceExtensions.includes(path6.extname(item.name))) {
|
|
8430
8929
|
if (item.name === "codemap.yml") continue;
|
|
8431
|
-
if (!indexedPaths.has(
|
|
8930
|
+
if (!indexedPaths.has(path6.normalize(item.name))) {
|
|
8432
8931
|
errors.push(`${filePath}: Unindexed source file found: '${item.name}'. Every source file must be categorized in a section (e.g., 'internals').`);
|
|
8433
8932
|
}
|
|
8434
8933
|
}
|
|
@@ -8438,7 +8937,7 @@ async function optima_validate_logic(worktree) {
|
|
|
8438
8937
|
for (const key of pathKeys) {
|
|
8439
8938
|
for (const entry of getSectionEntries(map[key])) {
|
|
8440
8939
|
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://")) continue;
|
|
8441
|
-
if (
|
|
8940
|
+
if (path6.isAbsolute(entry.path)) continue;
|
|
8442
8941
|
const normalizedPath = normalizeRelativePath(entry.path);
|
|
8443
8942
|
if (!normalizedPath || normalizedPath === ".") continue;
|
|
8444
8943
|
const parts = normalizedPath.split("/").filter((p) => p && p !== ".");
|
|
@@ -8450,18 +8949,18 @@ async function optima_validate_logic(worktree) {
|
|
|
8450
8949
|
}
|
|
8451
8950
|
}
|
|
8452
8951
|
const walk = (dir) => {
|
|
8453
|
-
const relDir =
|
|
8952
|
+
const relDir = path6.relative(worktree, dir);
|
|
8454
8953
|
if (relDir && ig.ignores(relDir)) return;
|
|
8455
8954
|
if (isOperationalRelPath2(relDir)) return;
|
|
8456
8955
|
if (isHiddenTree2(relDir)) return;
|
|
8457
|
-
const hasCodemap =
|
|
8458
|
-
const items =
|
|
8459
|
-
if (!relDir.startsWith(
|
|
8956
|
+
const hasCodemap = fs7.existsSync(path6.join(dir, "codemap.yml"));
|
|
8957
|
+
const items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
8958
|
+
if (!relDir.startsWith(path6.join(".optima", "tasks", "done"))) {
|
|
8460
8959
|
for (const item of items) {
|
|
8461
8960
|
if (item.isFile() && item.name.endsWith(".md")) {
|
|
8462
|
-
const content =
|
|
8961
|
+
const content = fs7.readFileSync(path6.join(dir, item.name), "utf8");
|
|
8463
8962
|
if (content.includes("[To be defined]") || content.includes("[Insert ")) {
|
|
8464
|
-
const relFilePath =
|
|
8963
|
+
const relFilePath = path6.join(relDir, item.name);
|
|
8465
8964
|
errors.push(`Documentation Placeholder found: '${relFilePath}' still contains [To be defined] or [Insert ...] placeholders.`);
|
|
8466
8965
|
}
|
|
8467
8966
|
}
|
|
@@ -8471,9 +8970,9 @@ async function optima_validate_logic(worktree) {
|
|
|
8471
8970
|
if (relDir !== "" && !hasCodemap && isSourceDir(dir) && !isOperational) {
|
|
8472
8971
|
errors.push(`Missing CodeMap: Directory '${relDir}' contains source but has no codemap.yml.`);
|
|
8473
8972
|
}
|
|
8474
|
-
if (hasCodemap) validateMap(
|
|
8973
|
+
if (hasCodemap) validateMap(path6.join(dir, "codemap.yml"));
|
|
8475
8974
|
for (const item of items) {
|
|
8476
|
-
if (item.isDirectory()) walk(
|
|
8975
|
+
if (item.isDirectory()) walk(path6.join(dir, item.name));
|
|
8477
8976
|
}
|
|
8478
8977
|
};
|
|
8479
8978
|
validateMap(rootCodemapPath);
|
|
@@ -8486,14 +8985,14 @@ async function optima_validate_logic(worktree) {
|
|
|
8486
8985
|
}
|
|
8487
8986
|
|
|
8488
8987
|
// src/constants.js
|
|
8489
|
-
import
|
|
8988
|
+
import path7 from "node:path";
|
|
8490
8989
|
import { fileURLToPath } from "node:url";
|
|
8491
|
-
var PKG_ROOT =
|
|
8492
|
-
var BUNDLE_ASSETS_DIR =
|
|
8493
|
-
var BUNDLE_AGENTS_DIR =
|
|
8494
|
-
var BUNDLE_POLICIES_DIR =
|
|
8495
|
-
var TEMPLATES_DIR =
|
|
8496
|
-
var HUMANS_REGISTRY_PATH =
|
|
8990
|
+
var PKG_ROOT = path7.resolve(path7.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8991
|
+
var BUNDLE_ASSETS_DIR = path7.join(PKG_ROOT, "assets");
|
|
8992
|
+
var BUNDLE_AGENTS_DIR = path7.join(BUNDLE_ASSETS_DIR, "agents");
|
|
8993
|
+
var BUNDLE_POLICIES_DIR = path7.join(BUNDLE_ASSETS_DIR, "policies");
|
|
8994
|
+
var TEMPLATES_DIR = path7.join(PKG_ROOT, "templates");
|
|
8995
|
+
var HUMANS_REGISTRY_PATH = path7.join(PKG_ROOT, "docs", "core", "humans.md");
|
|
8497
8996
|
var MANDATORY_AGENTS = /* @__PURE__ */ new Set(["product_manager", "business_analyst", "tech_lead"]);
|
|
8498
8997
|
var MINI_MODE_AGENTS = /* @__PURE__ */ new Set(["product_manager", "business_analyst", "tech_lead"]);
|
|
8499
8998
|
var CLICKUP_IGNORED_TASK_TYPES = ["Idea", "Backlog", "Hito", "Nota de reuni\xF3n", "Respuesta del formulario"];
|
|
@@ -8631,18 +9130,18 @@ var GITHUB_WEBHOOK_EVENTS = ["pull_request", "pull_request_review", "pull_reques
|
|
|
8631
9130
|
var OPTIMA_GITHUB_COMMITTER_NAME = "Optima Product Manager";
|
|
8632
9131
|
var OPTIMA_GITHUB_COMMITTER_EMAIL = "optima-product-manager[bot]@users.noreply.github.com";
|
|
8633
9132
|
function isRootDirectory(candidate) {
|
|
8634
|
-
const resolved =
|
|
8635
|
-
return resolved ===
|
|
9133
|
+
const resolved = path8.resolve(candidate);
|
|
9134
|
+
return resolved === path8.parse(resolved).root;
|
|
8636
9135
|
}
|
|
8637
9136
|
function isSafeWritableDirectory(candidate) {
|
|
8638
9137
|
if (typeof candidate !== "string" || !candidate.trim()) return false;
|
|
8639
|
-
if (!
|
|
8640
|
-
const resolved =
|
|
9138
|
+
if (!path8.isAbsolute(candidate)) return false;
|
|
9139
|
+
const resolved = path8.resolve(candidate);
|
|
8641
9140
|
if (isRootDirectory(resolved)) return false;
|
|
8642
9141
|
try {
|
|
8643
|
-
const stat =
|
|
9142
|
+
const stat = fs8.statSync(resolved);
|
|
8644
9143
|
if (!stat.isDirectory()) return false;
|
|
8645
|
-
|
|
9144
|
+
fs8.accessSync(resolved, fs8.constants.W_OK);
|
|
8646
9145
|
return true;
|
|
8647
9146
|
} catch {
|
|
8648
9147
|
return false;
|
|
@@ -8654,11 +9153,11 @@ function resolveSafeWorktree(context = {}, pluginWorktree = null) {
|
|
|
8654
9153
|
context?.directory,
|
|
8655
9154
|
pluginWorktree,
|
|
8656
9155
|
process.cwd(),
|
|
8657
|
-
|
|
9156
|
+
os2.homedir()
|
|
8658
9157
|
];
|
|
8659
9158
|
for (const candidate of candidates) {
|
|
8660
9159
|
if (typeof candidate !== "string" || !candidate.trim()) continue;
|
|
8661
|
-
const resolved =
|
|
9160
|
+
const resolved = path8.resolve(candidate);
|
|
8662
9161
|
if (isSafeWritableDirectory(resolved)) return resolved;
|
|
8663
9162
|
}
|
|
8664
9163
|
throw new Error(SAFE_WORKTREE_FAILURE);
|
|
@@ -8673,13 +9172,13 @@ function safeWorktreeOrFailure(context, pluginWorktree) {
|
|
|
8673
9172
|
function explicitSafeInputWorktree(input = {}) {
|
|
8674
9173
|
for (const candidate of [input?.worktree, input?.directory]) {
|
|
8675
9174
|
if (typeof candidate !== "string" || !candidate.trim()) continue;
|
|
8676
|
-
const resolved =
|
|
9175
|
+
const resolved = path8.resolve(candidate);
|
|
8677
9176
|
if (isSafeWritableDirectory(resolved)) return resolved;
|
|
8678
9177
|
}
|
|
8679
9178
|
return null;
|
|
8680
9179
|
}
|
|
8681
9180
|
function isGitRepository(worktree) {
|
|
8682
|
-
return
|
|
9181
|
+
return fs8.existsSync(path8.join(worktree, ".git"));
|
|
8683
9182
|
}
|
|
8684
9183
|
function normalizeLooseToken(value) {
|
|
8685
9184
|
return String(value ?? "").trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -8713,8 +9212,8 @@ function parseHumansRegistry(markdown = "") {
|
|
|
8713
9212
|
return roles;
|
|
8714
9213
|
}
|
|
8715
9214
|
function loadHumansRegistry(registryPath = HUMANS_REGISTRY_PATH) {
|
|
8716
|
-
if (!
|
|
8717
|
-
return parseHumansRegistry(
|
|
9215
|
+
if (!fs8.existsSync(registryPath)) return {};
|
|
9216
|
+
return parseHumansRegistry(fs8.readFileSync(registryPath, "utf8"));
|
|
8718
9217
|
}
|
|
8719
9218
|
function resolveHumanRoles(roles = CLICKUP_FINAL_APPROVER_ROLES, registry = loadHumansRegistry()) {
|
|
8720
9219
|
return [...new Set(roles.map((role) => registry[String(role ?? "").trim()] || "").filter(Boolean))];
|
|
@@ -8890,74 +9389,24 @@ function validateMainWorkspaceBranchSafety({ currentBranch, requiredBranch = "de
|
|
|
8890
9389
|
const normalized = String(currentBranch ?? "").trim();
|
|
8891
9390
|
if (!normalized) {
|
|
8892
9391
|
return { ok: false, branch: normalized, message: "Unable to determine the current branch; expected main workspace on dev." };
|
|
8893
|
-
}
|
|
8894
|
-
if (normalized === forbiddenBranch) {
|
|
8895
|
-
return { ok: false, branch: normalized, message: "Unsafe workspace branch: main is never allowed for Optima delivery work." };
|
|
8896
|
-
}
|
|
8897
|
-
if (normalized !== requiredBranch) {
|
|
8898
|
-
return { ok: false, branch: normalized, message: `Unsafe workspace branch: expected ${requiredBranch}, got ${normalized}.` };
|
|
8899
|
-
}
|
|
8900
|
-
return { ok: true, branch: normalized, message: `Workspace branch is safe on ${requiredBranch}.` };
|
|
8901
|
-
}
|
|
8902
|
-
var CLICKUP_REQUIRED_SUMMARY_SECTIONS = [
|
|
8903
|
-
"Summary",
|
|
8904
|
-
"Work Performed",
|
|
8905
|
-
"AC Coverage",
|
|
8906
|
-
"Verification Results",
|
|
8907
|
-
"Documentation Impact",
|
|
8908
|
-
"Open Risks",
|
|
8909
|
-
"Recommended Next Step"
|
|
8910
|
-
];
|
|
8911
|
-
var CLICKUP_RAW_LOG_SECTION_NAMES = /* @__PURE__ */ new Set(["Raw Logs", "Logs", "Full Logs", "Command Output", "Transcript"]);
|
|
8912
|
-
var CLICKUP_TRANSITIONS = /* @__PURE__ */ new Map([
|
|
8913
|
-
["plan->in progress", { status: "in progress", comment: "Plan complete; moving to implementation without generic CTO/PO assignment." }],
|
|
8914
|
-
["in progress->validation", { status: "validation", comment: "Implementation complete; ready for validation." }],
|
|
8915
|
-
["validation->merge", { status: "merge", assignFinalApprovers: true, parentOnlyFinalApproval: true, comment: "Parent validation passed with a functional preview URL; ready for CTO/PO approval flow." }],
|
|
8916
|
-
["validation->in progress", { status: "in progress", comment: "Validation failed; returning to implementation." }],
|
|
8917
|
-
["merge->completed", { status: "completed", comment: "Merge complete; closing delivery task." }],
|
|
8918
|
-
["completed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }],
|
|
8919
|
-
["closed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }]
|
|
8920
|
-
]);
|
|
8921
|
-
function parseMarkdownSections(markdown = "") {
|
|
8922
|
-
const sections = {};
|
|
8923
|
-
let current = null;
|
|
8924
|
-
let buffer = [];
|
|
8925
|
-
const flush = () => {
|
|
8926
|
-
if (!current) return;
|
|
8927
|
-
sections[current] = buffer.join("\n").trim();
|
|
8928
|
-
};
|
|
8929
|
-
for (const line of String(markdown).split(/\r?\n/)) {
|
|
8930
|
-
const heading = /^(#{2,3})\s+(.+?)\s*$/.exec(line);
|
|
8931
|
-
if (heading) {
|
|
8932
|
-
flush();
|
|
8933
|
-
current = heading[2].trim();
|
|
8934
|
-
buffer = [];
|
|
8935
|
-
continue;
|
|
8936
|
-
}
|
|
8937
|
-
if (current) buffer.push(line);
|
|
8938
|
-
}
|
|
8939
|
-
flush();
|
|
8940
|
-
return sections;
|
|
8941
|
-
}
|
|
8942
|
-
function parseMarkdownArtifact(markdown = "", { requiredSections = [] } = {}) {
|
|
8943
|
-
const sections = parseMarkdownSections(markdown);
|
|
8944
|
-
const missing = requiredSections.filter((section) => !sections[section]);
|
|
8945
|
-
return {
|
|
8946
|
-
ok: missing.length === 0,
|
|
8947
|
-
sections,
|
|
8948
|
-
missing,
|
|
8949
|
-
message: missing.length ? `Missing required section(s): ${missing.join(", ")}` : "ok"
|
|
8950
|
-
};
|
|
8951
|
-
}
|
|
8952
|
-
function readMarkdownArtifact(filePath, options = {}) {
|
|
8953
|
-
const markdown = fs5.readFileSync(filePath, "utf8");
|
|
8954
|
-
return parseMarkdownArtifact(markdown, options);
|
|
8955
|
-
}
|
|
8956
|
-
function stripRawLogSections(sections = {}) {
|
|
8957
|
-
return Object.fromEntries(
|
|
8958
|
-
Object.entries(sections).filter(([name]) => !CLICKUP_RAW_LOG_SECTION_NAMES.has(name))
|
|
8959
|
-
);
|
|
9392
|
+
}
|
|
9393
|
+
if (normalized === forbiddenBranch) {
|
|
9394
|
+
return { ok: false, branch: normalized, message: "Unsafe workspace branch: main is never allowed for Optima delivery work." };
|
|
9395
|
+
}
|
|
9396
|
+
if (normalized !== requiredBranch) {
|
|
9397
|
+
return { ok: false, branch: normalized, message: `Unsafe workspace branch: expected ${requiredBranch}, got ${normalized}.` };
|
|
9398
|
+
}
|
|
9399
|
+
return { ok: true, branch: normalized, message: `Workspace branch is safe on ${requiredBranch}.` };
|
|
8960
9400
|
}
|
|
9401
|
+
var CLICKUP_TRANSITIONS = /* @__PURE__ */ new Map([
|
|
9402
|
+
["plan->in progress", { status: "in progress", comment: "Plan complete; moving to implementation without generic CTO/PO assignment." }],
|
|
9403
|
+
["in progress->validation", { status: "validation", comment: "Implementation complete; ready for validation." }],
|
|
9404
|
+
["validation->merge", { status: "merge", assignFinalApprovers: true, parentOnlyFinalApproval: true, comment: "Parent validation passed with a functional preview URL; ready for CTO/PO approval flow." }],
|
|
9405
|
+
["validation->in progress", { status: "in progress", comment: "Validation failed; returning to implementation." }],
|
|
9406
|
+
["merge->completed", { status: "completed", comment: "Merge complete; closing delivery task." }],
|
|
9407
|
+
["completed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }],
|
|
9408
|
+
["closed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }]
|
|
9409
|
+
]);
|
|
8961
9410
|
var CLICKUP_SUBTASK_REQUIRED_FIELDS = ["Type", "Owner Role", "Story Points", "Slice", "Acceptance Criteria"];
|
|
8962
9411
|
var CLICKUP_SUBTASK_OPTIONAL_FIELDS = ["Depends On", "Branch", "Description", "Definition", "Documentation"];
|
|
8963
9412
|
var CLICKUP_SUBTASK_KNOWN_FIELDS = /* @__PURE__ */ new Set([...CLICKUP_SUBTASK_REQUIRED_FIELDS, ...CLICKUP_SUBTASK_OPTIONAL_FIELDS]);
|
|
@@ -9161,7 +9610,7 @@ function buildClickUpApplyPayloadResult({ payload, apply = false } = {}) {
|
|
|
9161
9610
|
noop: true,
|
|
9162
9611
|
applied: false,
|
|
9163
9612
|
message: "Dry-run only. Payload validated; no ClickUp calls were made.",
|
|
9164
|
-
payload: parsed
|
|
9613
|
+
payload: normalizeClickUpPayloadComments(parsed)
|
|
9165
9614
|
};
|
|
9166
9615
|
}
|
|
9167
9616
|
return {
|
|
@@ -9171,7 +9620,7 @@ function buildClickUpApplyPayloadResult({ payload, apply = false } = {}) {
|
|
|
9171
9620
|
applyRequested: true,
|
|
9172
9621
|
applied: false,
|
|
9173
9622
|
message: "Live ClickUp execution is not implemented/configured in this safe boundary; no ClickUp calls were made.",
|
|
9174
|
-
payload: parsed
|
|
9623
|
+
payload: normalizeClickUpPayloadComments(parsed)
|
|
9175
9624
|
};
|
|
9176
9625
|
}
|
|
9177
9626
|
function sectionValue(sections, names) {
|
|
@@ -9181,7 +9630,7 @@ function sectionValue(sections, names) {
|
|
|
9181
9630
|
return "";
|
|
9182
9631
|
}
|
|
9183
9632
|
function compactMarkdownValue(value = "") {
|
|
9184
|
-
return
|
|
9633
|
+
return normalizeClickUpMarkdown(value);
|
|
9185
9634
|
}
|
|
9186
9635
|
function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", taskMarkdown = "", taskPath = "", branch = "", worktree = "", pr = "" } = {}) {
|
|
9187
9636
|
const parsedSummary = parseMarkdownArtifact(summaryMarkdown, { requiredSections: CLICKUP_REQUIRED_SUMMARY_SECTIONS });
|
|
@@ -9206,13 +9655,12 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
9206
9655
|
deliveryPath ? `- Delivery evidence: ${compactMarkdownValue(deliveryPath)}` : null,
|
|
9207
9656
|
prValue ? `- PR: ${compactMarkdownValue(prValue)}` : null
|
|
9208
9657
|
].filter(Boolean);
|
|
9209
|
-
const comment =
|
|
9210
|
-
"
|
|
9211
|
-
"",
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
].filter((part) => part !== null).join("\n").trim();
|
|
9658
|
+
const comment = formatClickUpStatusComment({
|
|
9659
|
+
title: "Optima Delivery Summary",
|
|
9660
|
+
summary: "ClickUp sync payload generated from Optima Markdown artifacts.",
|
|
9661
|
+
sections: commentParts.map(([title, value]) => ({ title, body: compactMarkdownValue(value) || "Not specified." })),
|
|
9662
|
+
context: contextParts
|
|
9663
|
+
});
|
|
9216
9664
|
return {
|
|
9217
9665
|
ok: true,
|
|
9218
9666
|
mode: "payload",
|
|
@@ -9238,7 +9686,7 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
9238
9686
|
function deriveClickUpWorktree({ baseWorktree = "", taskId, taskType, parentTaskId, subtaskId } = {}) {
|
|
9239
9687
|
const branch = deriveClickUpBranchName({ taskType, parentTaskId, subtaskId, taskId });
|
|
9240
9688
|
const root = baseWorktree || process.cwd();
|
|
9241
|
-
return
|
|
9689
|
+
return path8.join(path8.dirname(root), `${path8.basename(root)}-${branch.replace(/\//g, "-")}`);
|
|
9242
9690
|
}
|
|
9243
9691
|
function clickUpCustomFieldValue(task = {}, names = []) {
|
|
9244
9692
|
const fields = Array.isArray(task.custom_fields) ? task.custom_fields : [];
|
|
@@ -9262,12 +9710,12 @@ function safeExistingClickUpWorktree({ metadata = {}, branch = "" } = {}) {
|
|
|
9262
9710
|
const taskMetadata = metadataTaskRouting(metadata);
|
|
9263
9711
|
const existingWorktree = typeof taskMetadata.worktree === "string" ? taskMetadata.worktree.trim() : "";
|
|
9264
9712
|
const existingBranch = typeof taskMetadata.branch === "string" ? taskMetadata.branch.trim() : "";
|
|
9265
|
-
if (!existingWorktree || !
|
|
9713
|
+
if (!existingWorktree || !path8.isAbsolute(existingWorktree) || !fs8.existsSync(existingWorktree)) return null;
|
|
9266
9714
|
try {
|
|
9267
|
-
const stat =
|
|
9715
|
+
const stat = fs8.statSync(existingWorktree);
|
|
9268
9716
|
if (!stat.isDirectory()) return null;
|
|
9269
9717
|
if (branch && existingBranch && existingBranch !== branch) return null;
|
|
9270
|
-
return { branch: existingBranch || branch, worktree:
|
|
9718
|
+
return { branch: existingBranch || branch, worktree: path8.resolve(existingWorktree), reused: true };
|
|
9271
9719
|
} catch {
|
|
9272
9720
|
return null;
|
|
9273
9721
|
}
|
|
@@ -9301,7 +9749,7 @@ function normalizeOptimaGitIdentity(identity = {}) {
|
|
|
9301
9749
|
};
|
|
9302
9750
|
}
|
|
9303
9751
|
function configureOptimaWorktreeGitIdentity({ worktreePath, identity = {}, runGitFn = runGit } = {}) {
|
|
9304
|
-
if (!worktreePath || !
|
|
9752
|
+
if (!worktreePath || !fs8.existsSync(worktreePath)) return { configured: false, reason: "worktree_missing" };
|
|
9305
9753
|
const normalized = normalizeOptimaGitIdentity(identity);
|
|
9306
9754
|
if (!normalized.name || !normalized.email) return { configured: false, reason: "identity_missing" };
|
|
9307
9755
|
try {
|
|
@@ -9387,17 +9835,17 @@ function openChamberEntryBranch(entry) {
|
|
|
9387
9835
|
return String(entry?.branch || entry?.branchName || entry?.branch_name || entry?.worktree?.branch || "").replace(/^refs\/heads\//, "");
|
|
9388
9836
|
}
|
|
9389
9837
|
function openChamberListIncludesDirectory(list, directory) {
|
|
9390
|
-
const resolved =
|
|
9838
|
+
const resolved = path8.resolve(directory);
|
|
9391
9839
|
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9392
9840
|
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9393
|
-
return entryDirectory &&
|
|
9841
|
+
return entryDirectory && path8.resolve(entryDirectory) === resolved;
|
|
9394
9842
|
});
|
|
9395
9843
|
}
|
|
9396
9844
|
function openChamberListIncludesBranch(list, directory, branch) {
|
|
9397
|
-
const resolved =
|
|
9845
|
+
const resolved = path8.resolve(directory);
|
|
9398
9846
|
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9399
9847
|
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9400
|
-
if (!entryDirectory ||
|
|
9848
|
+
if (!entryDirectory || path8.resolve(entryDirectory) !== resolved) return false;
|
|
9401
9849
|
const entryBranch = openChamberEntryBranch(entry);
|
|
9402
9850
|
return !entryBranch || entryBranch === branch;
|
|
9403
9851
|
});
|
|
@@ -9405,8 +9853,8 @@ function openChamberListIncludesBranch(list, directory, branch) {
|
|
|
9405
9853
|
async function findOpenChamberProject({ opencodeBaseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
|
|
9406
9854
|
const projects = await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
|
|
9407
9855
|
if (!Array.isArray(projects)) return null;
|
|
9408
|
-
const resolvedBase =
|
|
9409
|
-
return projects.find((project) =>
|
|
9856
|
+
const resolvedBase = path8.resolve(baseWorktree);
|
|
9857
|
+
return projects.find((project) => path8.resolve(String(project?.worktree || project?.path || "")) === resolvedBase) || null;
|
|
9410
9858
|
}
|
|
9411
9859
|
async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
|
|
9412
9860
|
if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
|
|
@@ -9461,18 +9909,18 @@ async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, b
|
|
|
9461
9909
|
}
|
|
9462
9910
|
const createdDirectory = openChamberEntryDirectory(created);
|
|
9463
9911
|
const createdBranch = openChamberEntryBranch(created);
|
|
9464
|
-
if (!createdDirectory || !
|
|
9912
|
+
if (!createdDirectory || !path8.isAbsolute(createdDirectory)) {
|
|
9465
9913
|
throw new Error(`OpenChamber did not return an absolute worktree path for ${branch}.`);
|
|
9466
9914
|
}
|
|
9467
9915
|
if (createdBranch !== branch) {
|
|
9468
9916
|
throw new Error(`OpenChamber created unexpected branch ${createdBranch || "<unknown>"}; expected ${branch}.`);
|
|
9469
9917
|
}
|
|
9470
9918
|
const verified = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
|
|
9471
|
-
return { created, worktree:
|
|
9919
|
+
return { created, worktree: path8.resolve(createdDirectory), branch: createdBranch, verified };
|
|
9472
9920
|
}
|
|
9473
9921
|
async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
|
|
9474
9922
|
const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
|
|
9475
|
-
return { branch, worktree:
|
|
9923
|
+
return { branch, worktree: path8.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
|
|
9476
9924
|
}
|
|
9477
9925
|
async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, openchamberBaseUrl = "", opencodeBaseUrl = "", baseUrl = "", fetchImpl = globalThis.fetch, log = null, gitIdentity = {} } = {}) {
|
|
9478
9926
|
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
@@ -9490,7 +9938,7 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
|
|
|
9490
9938
|
return { ...registered, parentBranch: parentBranch || void 0, prTarget, gitIdentity: identity2 };
|
|
9491
9939
|
}
|
|
9492
9940
|
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
9493
|
-
if (
|
|
9941
|
+
if (fs8.existsSync(worktreePath)) {
|
|
9494
9942
|
const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
|
|
9495
9943
|
const identity2 = configureOptimaWorktreeGitIdentity({ worktreePath: registered.worktree, identity: gitIdentity, runGitFn });
|
|
9496
9944
|
log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "existing_directory", visibility: registered.openChamber.visibility });
|
|
@@ -9499,7 +9947,7 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
|
|
|
9499
9947
|
let parentBootstrap = null;
|
|
9500
9948
|
if (isSubtask) {
|
|
9501
9949
|
const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
|
|
9502
|
-
if (
|
|
9950
|
+
if (fs8.existsSync(parentWorktree)) {
|
|
9503
9951
|
const registeredParent = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
|
|
9504
9952
|
const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: registeredParent.worktree, identity: gitIdentity, runGitFn });
|
|
9505
9953
|
parentBootstrap = { branch: parentBranch, worktree: registeredParent.worktree, reused: true, provider: "openchamber", visibility: registeredParent.openChamber.visibility };
|
|
@@ -9542,33 +9990,33 @@ function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tare
|
|
|
9542
9990
|
const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
|
|
9543
9991
|
if (existing) return { ...existing, branch, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: existing.worktree, identity: gitIdentity, runGitFn }) };
|
|
9544
9992
|
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
9545
|
-
if (
|
|
9546
|
-
const resolvedWorktree2 =
|
|
9993
|
+
if (fs8.existsSync(worktreePath)) {
|
|
9994
|
+
const resolvedWorktree2 = path8.resolve(worktreePath);
|
|
9547
9995
|
return { branch, worktree: resolvedWorktree2, reused: true, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: resolvedWorktree2, identity: gitIdentity, runGitFn }) };
|
|
9548
9996
|
}
|
|
9549
9997
|
if (allowNonGitFallback) {
|
|
9550
|
-
|
|
9551
|
-
return { branch, worktree:
|
|
9998
|
+
fs8.mkdirSync(worktreePath, { recursive: true });
|
|
9999
|
+
return { branch, worktree: path8.resolve(worktreePath), reused: false, fallback: "non_git", parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", startPoint: parentBranch || "dev" };
|
|
9552
10000
|
}
|
|
9553
10001
|
let parentBootstrap = null;
|
|
9554
10002
|
if (isSubtask) {
|
|
9555
10003
|
const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
|
|
9556
|
-
if (!
|
|
10004
|
+
if (!fs8.existsSync(parentWorktree)) {
|
|
9557
10005
|
const parentStartPoint = resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9558
10006
|
const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
|
|
9559
10007
|
addClickUpWorktreeForBranch({ baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, runGitFn });
|
|
9560
|
-
parentBootstrap = { branch: parentBranch, worktree:
|
|
10008
|
+
parentBootstrap = { branch: parentBranch, worktree: path8.resolve(parentWorktree), startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists };
|
|
9561
10009
|
const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: parentBootstrap.worktree, identity: gitIdentity, runGitFn });
|
|
9562
10010
|
if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
|
|
9563
10011
|
} else {
|
|
9564
|
-
parentBootstrap = { branch: parentBranch, worktree:
|
|
10012
|
+
parentBootstrap = { branch: parentBranch, worktree: path8.resolve(parentWorktree), reused: true };
|
|
9565
10013
|
const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: parentBootstrap.worktree, identity: gitIdentity, runGitFn });
|
|
9566
10014
|
if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
|
|
9567
10015
|
}
|
|
9568
10016
|
}
|
|
9569
10017
|
const startPoint = isSubtask ? parentBranch : resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9570
10018
|
addClickUpWorktreeForBranch({ baseWorktree, branch, worktreePath, startPoint, runGitFn });
|
|
9571
|
-
const resolvedWorktree =
|
|
10019
|
+
const resolvedWorktree = path8.resolve(worktreePath);
|
|
9572
10020
|
return { branch, worktree: resolvedWorktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", parentBootstrap: parentBootstrap || void 0, gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: resolvedWorktree, identity: gitIdentity, runGitFn }) };
|
|
9573
10021
|
}
|
|
9574
10022
|
function normalizeClickUpDefinitionDocParent(parent = {}) {
|
|
@@ -9651,7 +10099,17 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
9651
10099
|
wouldCreate: true
|
|
9652
10100
|
},
|
|
9653
10101
|
clickup: {
|
|
9654
|
-
comment:
|
|
10102
|
+
comment: formatClickUpStatusComment({
|
|
10103
|
+
title: "Task Workspace Ready",
|
|
10104
|
+
summary: "Optima prepared the task workspace and validation target.",
|
|
10105
|
+
context: [
|
|
10106
|
+
`Branch: ${branch}`,
|
|
10107
|
+
`Start from: ${startFrom}`,
|
|
10108
|
+
`Worktree: ${worktree}`,
|
|
10109
|
+
`Validation PR target: ${requiredPullRequest.targetBranch}`,
|
|
10110
|
+
`Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}`
|
|
10111
|
+
]
|
|
10112
|
+
}),
|
|
9655
10113
|
description,
|
|
9656
10114
|
fields: {
|
|
9657
10115
|
Definition: definitionContent,
|
|
@@ -9737,7 +10195,18 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
|
|
|
9737
10195
|
remove: removalTargets,
|
|
9738
10196
|
objective: assignsFinalApprovers ? "zero_product_manager_assigned_tasks" : "preserve_existing_owner_policy"
|
|
9739
10197
|
},
|
|
9740
|
-
comment:
|
|
10198
|
+
comment: formatClickUpStatusComment({
|
|
10199
|
+
title: "Workflow Status Update",
|
|
10200
|
+
summary: rule.comment,
|
|
10201
|
+
context: [
|
|
10202
|
+
`From: ${from}`,
|
|
10203
|
+
`To: ${rule.status}`,
|
|
10204
|
+
requiredPullRequest?.prUrl ? `PR: ${requiredPullRequest.prUrl}` : null,
|
|
10205
|
+
requiredPullRequest?.prNumber ? `PR number: ${requiredPullRequest.prNumber}` : null,
|
|
10206
|
+
requiredPullRequest?.sourceBranch ? `Source branch: ${requiredPullRequest.sourceBranch}` : null,
|
|
10207
|
+
requiredPullRequest?.targetBranch ? `Target branch: ${requiredPullRequest.targetBranch}` : null
|
|
10208
|
+
].filter(Boolean)
|
|
10209
|
+
}),
|
|
9741
10210
|
description,
|
|
9742
10211
|
fields,
|
|
9743
10212
|
definition_doc: definitionContent ? {
|
|
@@ -9750,8 +10219,8 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
|
|
|
9750
10219
|
}
|
|
9751
10220
|
function ensureOptimaGitignoreRules(worktree) {
|
|
9752
10221
|
if (!isGitRepository(worktree)) return { touched: false, added: [] };
|
|
9753
|
-
const gitignorePath =
|
|
9754
|
-
const existing =
|
|
10222
|
+
const gitignorePath = path8.join(worktree, ".gitignore");
|
|
10223
|
+
const existing = fs8.existsSync(gitignorePath) ? fs8.readFileSync(gitignorePath, "utf8") : "";
|
|
9755
10224
|
const existingRules = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
9756
10225
|
const missingRules = OPTIMA_GITIGNORE_RULES.filter((rule) => !existingRules.has(rule));
|
|
9757
10226
|
if (missingRules.length === 0) return { touched: false, added: [] };
|
|
@@ -9761,45 +10230,45 @@ function ensureOptimaGitignoreRules(worktree) {
|
|
|
9761
10230
|
"# Optima local/private state",
|
|
9762
10231
|
...missingRules
|
|
9763
10232
|
].join("\n");
|
|
9764
|
-
|
|
10233
|
+
fs8.writeFileSync(gitignorePath, `${existing}${prefix}${separator}${block}
|
|
9765
10234
|
`, "utf8");
|
|
9766
10235
|
return { touched: true, added: missingRules };
|
|
9767
10236
|
}
|
|
9768
10237
|
function optimaDir(worktree) {
|
|
9769
|
-
return
|
|
10238
|
+
return path8.join(worktree, OPTIMA_DIRNAME);
|
|
9770
10239
|
}
|
|
9771
10240
|
function optimaLocalConfigDir(worktree) {
|
|
9772
|
-
return
|
|
10241
|
+
return path8.join(optimaDir(worktree), ".config");
|
|
9773
10242
|
}
|
|
9774
10243
|
function optimaConfigDir(worktree) {
|
|
9775
10244
|
return optimaLocalConfigDir(worktree);
|
|
9776
10245
|
}
|
|
9777
10246
|
function optimaCodemapPath(worktree) {
|
|
9778
|
-
return
|
|
10247
|
+
return path8.join(optimaDir(worktree), "codemap.yml");
|
|
9779
10248
|
}
|
|
9780
10249
|
function optimaTasksDir(worktree) {
|
|
9781
|
-
return
|
|
10250
|
+
return path8.join(optimaDir(worktree), "tasks");
|
|
9782
10251
|
}
|
|
9783
10252
|
function optimaEvidencesDir(worktree) {
|
|
9784
|
-
return
|
|
10253
|
+
return path8.join(optimaDir(worktree), "evidences");
|
|
9785
10254
|
}
|
|
9786
10255
|
function optimaScrsDir(worktree) {
|
|
9787
|
-
return
|
|
10256
|
+
return path8.join(optimaDir(worktree), "docs", "scrs");
|
|
9788
10257
|
}
|
|
9789
10258
|
function legacyNomadworkDir(worktree) {
|
|
9790
|
-
return
|
|
10259
|
+
return path8.join(worktree, LEGACY_NOMADWORK_DIRNAME);
|
|
9791
10260
|
}
|
|
9792
10261
|
function legacyNomadworksDir(worktree) {
|
|
9793
|
-
return
|
|
10262
|
+
return path8.join(worktree, LEGACY_NOMADWORKS_DIRNAME);
|
|
9794
10263
|
}
|
|
9795
10264
|
function legacyOrbitaDir(worktree) {
|
|
9796
|
-
return
|
|
10265
|
+
return path8.join(worktree, LEGACY_ORBITA_DIRNAME);
|
|
9797
10266
|
}
|
|
9798
10267
|
function legacyStaticEngDir(worktree) {
|
|
9799
|
-
return
|
|
10268
|
+
return path8.join(worktree, LEGACY_STATICENG_DIRNAME);
|
|
9800
10269
|
}
|
|
9801
10270
|
function repoConfigPath(worktree) {
|
|
9802
|
-
return
|
|
10271
|
+
return path8.join(optimaConfigDir(worktree), "optima.yaml");
|
|
9803
10272
|
}
|
|
9804
10273
|
function normalizeLegacyDiscussionEntry(entry) {
|
|
9805
10274
|
if (!entry || typeof entry !== "object") return entry;
|
|
@@ -9812,25 +10281,25 @@ function normalizeLegacyDiscussionEntry(entry) {
|
|
|
9812
10281
|
return next;
|
|
9813
10282
|
}
|
|
9814
10283
|
function repoPoliciesDir(worktree) {
|
|
9815
|
-
return
|
|
10284
|
+
return path8.join(optimaDir(worktree), "policies");
|
|
9816
10285
|
}
|
|
9817
10286
|
function generatedPoliciesDir(worktree) {
|
|
9818
|
-
return
|
|
10287
|
+
return path8.join(optimaLocalConfigDir(worktree), "generated", "policies");
|
|
9819
10288
|
}
|
|
9820
10289
|
function generatedAgentsDir(worktree) {
|
|
9821
|
-
return
|
|
10290
|
+
return path8.join(optimaLocalConfigDir(worktree), "generated", "agents");
|
|
9822
10291
|
}
|
|
9823
10292
|
function repoAgentsDir(worktree) {
|
|
9824
|
-
return
|
|
10293
|
+
return path8.join(optimaDir(worktree), "agents");
|
|
9825
10294
|
}
|
|
9826
10295
|
function repoAgentAdditionsDir(worktree) {
|
|
9827
|
-
return
|
|
10296
|
+
return path8.join(optimaDir(worktree), "agent-additions");
|
|
9828
10297
|
}
|
|
9829
10298
|
function legacyRepoAgentsDir(worktree) {
|
|
9830
|
-
return
|
|
10299
|
+
return path8.join(legacyNomadworksDir(worktree), "agents");
|
|
9831
10300
|
}
|
|
9832
10301
|
function runtimeDiscussionRegistryPath(worktree) {
|
|
9833
|
-
return
|
|
10302
|
+
return path8.join(optimaLocalConfigDir(worktree), "runtime", "discussions.json");
|
|
9834
10303
|
}
|
|
9835
10304
|
function resolveConfigPath(worktree) {
|
|
9836
10305
|
return repoConfigPath(worktree);
|
|
@@ -9873,32 +10342,32 @@ function mergeClickUpAgentMetadata(existing, update = {}) {
|
|
|
9873
10342
|
return JSON.stringify(sortJsonValue(merged), null, 2);
|
|
9874
10343
|
}
|
|
9875
10344
|
function optimaRuntimeDir(worktree) {
|
|
9876
|
-
return
|
|
10345
|
+
return path8.join(optimaLocalConfigDir(worktree), "runtime");
|
|
9877
10346
|
}
|
|
9878
10347
|
function clickUpWebhookStatePath(worktree) {
|
|
9879
|
-
return
|
|
10348
|
+
return path8.join(optimaRuntimeDir(worktree), "clickup-webhook.json");
|
|
9880
10349
|
}
|
|
9881
10350
|
function clickUpCommentLedgerPath(worktree) {
|
|
9882
|
-
return
|
|
10351
|
+
return path8.join(optimaRuntimeDir(worktree), "clickup-comment-ledger.jsonl");
|
|
9883
10352
|
}
|
|
9884
10353
|
function clickUpWebhookLogPath(worktree) {
|
|
9885
|
-
return
|
|
10354
|
+
return path8.join(optimaRuntimeDir(worktree), "clickup-webhook.log.jsonl");
|
|
9886
10355
|
}
|
|
9887
10356
|
function normalizeClickUpWebhookLogLevel(value) {
|
|
9888
10357
|
const level = String(value || "info").trim().toLowerCase();
|
|
9889
10358
|
return CLICKUP_WEBHOOK_LOG_LEVELS.has(level) ? level : "info";
|
|
9890
10359
|
}
|
|
9891
10360
|
function clickUpWebhookAuditLogDir() {
|
|
9892
|
-
const dataHome = process.env.XDG_DATA_HOME &&
|
|
9893
|
-
return
|
|
10361
|
+
const dataHome = process.env.XDG_DATA_HOME && path8.isAbsolute(process.env.XDG_DATA_HOME) ? process.env.XDG_DATA_HOME : path8.join(os2.homedir(), ".local", "share", "opencode");
|
|
10362
|
+
return path8.join(dataHome, "opencode-optima");
|
|
9894
10363
|
}
|
|
9895
10364
|
function clickUpWebhookAuditLogPath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
|
|
9896
|
-
return
|
|
10365
|
+
return path8.join(logDir, `clickup-webhook-${at.toISOString().slice(0, 10)}.jsonl`);
|
|
9897
10366
|
}
|
|
9898
10367
|
function clickUpWebhookRequestFilePath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
|
|
9899
10368
|
const stamp = at.toISOString().replace(/:/g, "-").replace(/\./g, "-");
|
|
9900
|
-
const shortId =
|
|
9901
|
-
return
|
|
10369
|
+
const shortId = crypto2.randomBytes(4).toString("hex");
|
|
10370
|
+
return path8.join(logDir, "requests", `clickup-webhook-${stamp}-${shortId}.json`);
|
|
9902
10371
|
}
|
|
9903
10372
|
function findOptimaPluginTupleOptions(pluginEntries = []) {
|
|
9904
10373
|
if (!Array.isArray(pluginEntries)) return null;
|
|
@@ -10058,7 +10527,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
10058
10527
|
};
|
|
10059
10528
|
const errors = [];
|
|
10060
10529
|
if (!config.enabled) errors.push("clickup.enabled must be true");
|
|
10061
|
-
if (!config.basePath || !
|
|
10530
|
+
if (!config.basePath || !path8.isAbsolute(config.basePath)) errors.push("clickup.base_path must be an absolute path");
|
|
10062
10531
|
if (!config.teamId) errors.push("clickup.team_id is required");
|
|
10063
10532
|
if (!config.apiToken) errors.push("clickup.api_token is required");
|
|
10064
10533
|
if (!config.webhook.publicUrl) errors.push("clickup.webhook.public_url is required");
|
|
@@ -10121,503 +10590,94 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
|
|
|
10121
10590
|
}
|
|
10122
10591
|
function readClickUpWebhookState(worktree, config = null) {
|
|
10123
10592
|
const statePath = clickUpWebhookStatePath(worktree);
|
|
10124
|
-
if (!
|
|
10593
|
+
if (!fs8.existsSync(statePath)) return sanitizeClickUpWebhookState({}, config);
|
|
10125
10594
|
try {
|
|
10126
|
-
return sanitizeClickUpWebhookState(JSON.parse(
|
|
10595
|
+
return sanitizeClickUpWebhookState(JSON.parse(fs8.readFileSync(statePath, "utf8")), config);
|
|
10127
10596
|
} catch {
|
|
10128
10597
|
return sanitizeClickUpWebhookState({}, config);
|
|
10129
10598
|
}
|
|
10130
10599
|
}
|
|
10131
10600
|
function writeClickUpWebhookState(worktree, state, config = null) {
|
|
10132
10601
|
const statePath = clickUpWebhookStatePath(worktree);
|
|
10133
|
-
|
|
10602
|
+
fs8.mkdirSync(path8.dirname(statePath), { recursive: true, mode: 448 });
|
|
10134
10603
|
const next = sanitizeClickUpWebhookState(state, config);
|
|
10135
|
-
|
|
10604
|
+
fs8.writeFileSync(statePath, `${JSON.stringify(next, null, 2)}
|
|
10136
10605
|
`, { encoding: "utf8", mode: 384 });
|
|
10137
10606
|
try {
|
|
10138
|
-
|
|
10607
|
+
fs8.chmodSync(statePath, 384);
|
|
10139
10608
|
} catch {
|
|
10140
10609
|
}
|
|
10141
10610
|
return next;
|
|
10142
10611
|
}
|
|
10143
10612
|
function isClickUpWebhookStateActive(state, config) {
|
|
10144
|
-
const expectedEvents = new Set(config?.webhook?.events || []);
|
|
10145
|
-
const actualEvents = new Set(state?.events || []);
|
|
10146
|
-
const hasEvents = [...expectedEvents].every((event) => actualEvents.has(event));
|
|
10147
|
-
return Boolean(
|
|
10148
|
-
state?.active && state?.webhookId && state?.secret && state?.publicUrl === config?.webhook?.publicUrl && hasEvents
|
|
10149
|
-
);
|
|
10150
|
-
}
|
|
10151
|
-
function
|
|
10152
|
-
const
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
}
|
|
10157
|
-
function resolveSecretReference(value = "") {
|
|
10158
|
-
const raw = String(value || "").trim();
|
|
10159
|
-
const envMatch = raw.match(/^\{env:([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
10160
|
-
if (envMatch) return process.env[envMatch[1]] || "";
|
|
10161
|
-
const fileMatch = raw.match(/^\{file:(.+)\}$/);
|
|
10162
|
-
if (fileMatch) {
|
|
10163
|
-
try {
|
|
10164
|
-
return fs5.readFileSync(expandHomePath(fileMatch[1]), "utf8").trim();
|
|
10165
|
-
} catch {
|
|
10166
|
-
return "";
|
|
10167
|
-
}
|
|
10168
|
-
}
|
|
10169
|
-
return raw;
|
|
10170
|
-
}
|
|
10171
|
-
function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
|
|
10172
|
-
const token = resolveSecretReference(config?.apiToken);
|
|
10173
|
-
const request = async (url, options = {}) => {
|
|
10174
|
-
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a ClickUp client for live webhook setup");
|
|
10175
|
-
if (!token) throw new Error("ClickUp API token is not available");
|
|
10176
|
-
const response = await fetchImpl(url, {
|
|
10177
|
-
...options,
|
|
10178
|
-
headers: {
|
|
10179
|
-
Authorization: token,
|
|
10180
|
-
"Content-Type": "application/json",
|
|
10181
|
-
...options.headers || {}
|
|
10182
|
-
}
|
|
10183
|
-
});
|
|
10184
|
-
if (!response.ok) throw new Error(`ClickUp API request failed: ${response.status}`);
|
|
10185
|
-
return response.json();
|
|
10186
|
-
};
|
|
10187
|
-
return {
|
|
10188
|
-
async createWebhook({ endpoint, events, location }) {
|
|
10189
|
-
const body = { endpoint, events, ...Object.fromEntries(Object.entries(location || {}).filter(([, value]) => value)) };
|
|
10190
|
-
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`, {
|
|
10191
|
-
method: "POST",
|
|
10192
|
-
body: JSON.stringify(body)
|
|
10193
|
-
});
|
|
10194
|
-
},
|
|
10195
|
-
async listWebhooks() {
|
|
10196
|
-
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`);
|
|
10197
|
-
},
|
|
10198
|
-
async deleteWebhook(webhookId) {
|
|
10199
|
-
return request(`https://api.clickup.com/api/v2/webhook/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
|
|
10200
|
-
},
|
|
10201
|
-
async getAuthorizedUser() {
|
|
10202
|
-
return request("https://api.clickup.com/api/v2/user");
|
|
10203
|
-
},
|
|
10204
|
-
async getTask(taskId) {
|
|
10205
|
-
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}`);
|
|
10206
|
-
},
|
|
10207
|
-
async updateTaskMetadata({ taskId, fieldId, value }) {
|
|
10208
|
-
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/field/${encodeURIComponent(fieldId)}`, {
|
|
10209
|
-
method: "POST",
|
|
10210
|
-
body: JSON.stringify({ value })
|
|
10211
|
-
});
|
|
10212
|
-
},
|
|
10213
|
-
async postTaskComment() {
|
|
10214
|
-
throw new Error("Optima runtime does not post ClickUp task comments; use local logs for process failures.");
|
|
10215
|
-
},
|
|
10216
|
-
async addTaskTag() {
|
|
10217
|
-
throw new Error("Optima runtime does not add ClickUp task tags; use local logs for process failures.");
|
|
10218
|
-
},
|
|
10219
|
-
async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10220
|
-
const params = new URLSearchParams();
|
|
10221
|
-
if (assigneeId) params.append("assignees[]", assigneeId);
|
|
10222
|
-
for (const status of statuses) if (status) params.append("statuses[]", status);
|
|
10223
|
-
if (limit) params.append("limit", String(limit));
|
|
10224
|
-
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/task?${params.toString()}`);
|
|
10225
|
-
},
|
|
10226
|
-
async getTaskComments({ taskId, start, limit = CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT } = {}) {
|
|
10227
|
-
const params = new URLSearchParams();
|
|
10228
|
-
if (start) params.set("start", String(start));
|
|
10229
|
-
if (limit) params.set("limit", String(limit));
|
|
10230
|
-
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
10231
|
-
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/comment${suffix}`);
|
|
10232
|
-
}
|
|
10233
|
-
};
|
|
10234
|
-
}
|
|
10235
|
-
function base64UrlJson(value) {
|
|
10236
|
-
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
10237
|
-
}
|
|
10238
|
-
function resolveGitHubAppPrivateKey(app = {}) {
|
|
10239
|
-
const direct = resolveSecretReference(app.privateKey || "");
|
|
10240
|
-
if (direct) return direct.replace(/\\n/g, "\n");
|
|
10241
|
-
const file = expandHomePath(app.privateKeyFile || "");
|
|
10242
|
-
if (!file) return "";
|
|
10243
|
-
try {
|
|
10244
|
-
return fs5.readFileSync(file, "utf8").trim().replace(/\\n/g, "\n");
|
|
10245
|
-
} catch {
|
|
10246
|
-
return "";
|
|
10247
|
-
}
|
|
10248
|
-
}
|
|
10249
|
-
function createGitHubAppJwt({ appId, privateKey, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
10250
|
-
const key = String(privateKey || "").trim();
|
|
10251
|
-
const issuer = String(appId || "").trim();
|
|
10252
|
-
if (!issuer || !key) throw new Error("GitHub App app_id and private key are required");
|
|
10253
|
-
const nowSeconds = Math.floor(now().getTime() / 1e3);
|
|
10254
|
-
const header = base64UrlJson({ alg: "RS256", typ: "JWT" });
|
|
10255
|
-
const payload = base64UrlJson({ iat: nowSeconds - 60, exp: nowSeconds + 540, iss: issuer });
|
|
10256
|
-
const unsigned = `${header}.${payload}`;
|
|
10257
|
-
const signature = crypto.createSign("RSA-SHA256").update(unsigned).end().sign(key, "base64url");
|
|
10258
|
-
return `${unsigned}.${signature}`;
|
|
10259
|
-
}
|
|
10260
|
-
function encodeGitHubPathSegment(value) {
|
|
10261
|
-
return encodeURIComponent(String(value || ""));
|
|
10262
|
-
}
|
|
10263
|
-
function encodeGitHubBranchRef(branch = "") {
|
|
10264
|
-
return String(branch || "").split("/").map(encodeGitHubPathSegment).join("/");
|
|
10265
|
-
}
|
|
10266
|
-
function parseNullSeparatedGitOutput(output = "") {
|
|
10267
|
-
return String(output || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
10268
|
-
}
|
|
10269
|
-
function listWorktreeChangedPaths(worktree, runGitFn = runGit) {
|
|
10270
|
-
const tracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["diff", "--name-only", "-z", "HEAD", "--"]));
|
|
10271
|
-
const untracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["ls-files", "--others", "--exclude-standard", "-z", "--"]));
|
|
10272
|
-
return [.../* @__PURE__ */ new Set([...tracked, ...untracked])].filter((item) => item && !item.startsWith("../") && !path6.isAbsolute(item));
|
|
10273
|
-
}
|
|
10274
|
-
function currentGitBranch(worktree, runGitFn = runGit) {
|
|
10275
|
-
return String(runGitFn(worktree, ["rev-parse", "--abbrev-ref", "HEAD"]) || "").trim();
|
|
10276
|
-
}
|
|
10277
|
-
function treeEntryForWorktreePath(worktree, gitPath) {
|
|
10278
|
-
const absolutePath = path6.join(worktree, ...String(gitPath || "").split("/"));
|
|
10279
|
-
if (!fs5.existsSync(absolutePath)) return { path: gitPath, mode: "100644", type: "blob", sha: null, deleted: true };
|
|
10280
|
-
const stat = fs5.lstatSync(absolutePath);
|
|
10281
|
-
if (stat.isDirectory()) return null;
|
|
10282
|
-
if (stat.isSymbolicLink()) {
|
|
10283
|
-
return {
|
|
10284
|
-
path: gitPath,
|
|
10285
|
-
mode: "120000",
|
|
10286
|
-
type: "blob",
|
|
10287
|
-
content: fs5.readlinkSync(absolutePath),
|
|
10288
|
-
encoding: "utf-8"
|
|
10289
|
-
};
|
|
10290
|
-
}
|
|
10291
|
-
return {
|
|
10292
|
-
path: gitPath,
|
|
10293
|
-
mode: stat.mode & 73 ? "100755" : "100644",
|
|
10294
|
-
type: "blob",
|
|
10295
|
-
content: fs5.readFileSync(absolutePath).toString("base64"),
|
|
10296
|
-
encoding: "base64"
|
|
10297
|
-
};
|
|
10298
|
-
}
|
|
10299
|
-
function appendGitHubQuery(pathname, params = {}) {
|
|
10300
|
-
const query = new URLSearchParams();
|
|
10301
|
-
for (const [key, value] of Object.entries(params || {})) {
|
|
10302
|
-
if (value === void 0 || value === null || value === "") continue;
|
|
10303
|
-
query.set(key, String(value));
|
|
10304
|
-
}
|
|
10305
|
-
const suffix = query.toString();
|
|
10306
|
-
return suffix ? `${pathname}?${suffix}` : pathname;
|
|
10307
|
-
}
|
|
10308
|
-
function timestampMs(value = "") {
|
|
10309
|
-
const parsed = Date.parse(String(value || ""));
|
|
10310
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
10311
|
-
}
|
|
10312
|
-
function sortNewestFirst(items = []) {
|
|
10313
|
-
return [...items].sort((a, b) => {
|
|
10314
|
-
const bTime = Math.max(timestampMs(b?.updated_at), timestampMs(b?.created_at));
|
|
10315
|
-
const aTime = Math.max(timestampMs(a?.updated_at), timestampMs(a?.created_at));
|
|
10316
|
-
return bTime - aTime;
|
|
10317
|
-
});
|
|
10318
|
-
}
|
|
10319
|
-
function matchesVercelStatusContext(context = "", expected = "") {
|
|
10320
|
-
const actual = String(context || "").trim();
|
|
10321
|
-
const wanted = String(expected || "").trim();
|
|
10322
|
-
if (wanted && actual === wanted) return true;
|
|
10323
|
-
const lower = actual.toLowerCase();
|
|
10324
|
-
if (!lower.includes("vercel")) return false;
|
|
10325
|
-
if (!wanted) return lower.includes("preproduction") || lower.includes("defend-preproduction");
|
|
10326
|
-
const wantedLower = wanted.toLowerCase();
|
|
10327
|
-
return wantedLower.split(/\s+|–|-/).filter((part) => part && part !== "vercel").every((part) => lower.includes(part));
|
|
10328
|
-
}
|
|
10329
|
-
function selectFunctionalDeploymentUrl(status = {}, deployment = {}) {
|
|
10330
|
-
for (const candidate of [
|
|
10331
|
-
status?.environment_url,
|
|
10332
|
-
deployment?.environment_url,
|
|
10333
|
-
status?.target_url,
|
|
10334
|
-
deployment?.target_url
|
|
10335
|
-
]) {
|
|
10336
|
-
const value = String(candidate || "").trim();
|
|
10337
|
-
if (!value) continue;
|
|
10338
|
-
try {
|
|
10339
|
-
const url = new URL(value);
|
|
10340
|
-
if (url.protocol === "http:" || url.protocol === "https:") return value;
|
|
10341
|
-
} catch {
|
|
10342
|
-
}
|
|
10343
|
-
}
|
|
10344
|
-
return "";
|
|
10345
|
-
}
|
|
10346
|
-
async function checkFunctionalDeploymentUrl(url, fetchImpl = globalThis.fetch) {
|
|
10347
|
-
const target = String(url || "").trim();
|
|
10348
|
-
if (!target) return { ok: false, reason: "url_missing" };
|
|
10349
|
-
if (typeof fetchImpl !== "function") return { ok: false, reason: "fetch_unavailable", url: target };
|
|
10350
|
-
const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(8e3) : void 0;
|
|
10351
|
-
const probe = async (method) => {
|
|
10352
|
-
const response = await fetchImpl(target, { method, redirect: "follow", signal });
|
|
10353
|
-
return {
|
|
10354
|
-
ok: response.status >= 200 && response.status < 400,
|
|
10355
|
-
status: response.status,
|
|
10356
|
-
statusText: response.statusText || "",
|
|
10357
|
-
url: response.url || target,
|
|
10358
|
-
method
|
|
10359
|
-
};
|
|
10360
|
-
};
|
|
10361
|
-
try {
|
|
10362
|
-
const head = await probe("HEAD");
|
|
10363
|
-
if (head.ok) return head;
|
|
10364
|
-
const get = await probe("GET");
|
|
10365
|
-
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
10366
|
-
} catch (error) {
|
|
10367
|
-
try {
|
|
10368
|
-
const get = await probe("GET");
|
|
10369
|
-
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
10370
|
-
} catch (secondError) {
|
|
10371
|
-
return { ok: false, reason: "request_failed", url: target, error: secondError.message || error.message };
|
|
10372
|
-
}
|
|
10373
|
-
}
|
|
10374
|
-
}
|
|
10375
|
-
function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
10376
|
-
const staticToken = resolveSecretReference(config?.apiToken);
|
|
10377
|
-
const appConfig = isPlainObject(config?.app) ? config.app : {};
|
|
10378
|
-
const appId = resolveSecretReference(appConfig.appId || appConfig.app_id || "");
|
|
10379
|
-
const installationId = resolveSecretReference(appConfig.installationId || appConfig.installation_id || "");
|
|
10380
|
-
const appEnabled = appConfig.enabled === true || Boolean(appId && installationId && (appConfig.privateKey || appConfig.privateKeyFile));
|
|
10381
|
-
const privateKey = resolveGitHubAppPrivateKey(appConfig);
|
|
10382
|
-
const owner = String(config?.owner || "").trim();
|
|
10383
|
-
const repo = String(config?.repo || "").trim();
|
|
10384
|
-
let installationToken = null;
|
|
10385
|
-
let installationTokenExpiresAt = 0;
|
|
10386
|
-
const requestJson = async (url, { method = "GET", headers = {}, body = void 0 } = {}) => {
|
|
10387
|
-
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
|
|
10613
|
+
const expectedEvents = new Set(config?.webhook?.events || []);
|
|
10614
|
+
const actualEvents = new Set(state?.events || []);
|
|
10615
|
+
const hasEvents = [...expectedEvents].every((event) => actualEvents.has(event));
|
|
10616
|
+
return Boolean(
|
|
10617
|
+
state?.active && state?.webhookId && state?.secret && state?.publicUrl === config?.webhook?.publicUrl && hasEvents
|
|
10618
|
+
);
|
|
10619
|
+
}
|
|
10620
|
+
function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
|
|
10621
|
+
const token = resolveSecretReference(config?.apiToken);
|
|
10622
|
+
const request = async (url, options = {}) => {
|
|
10623
|
+
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a ClickUp client for live webhook setup");
|
|
10624
|
+
if (!token) throw new Error("ClickUp API token is not available");
|
|
10388
10625
|
const response = await fetchImpl(url, {
|
|
10389
|
-
|
|
10626
|
+
...options,
|
|
10390
10627
|
headers: {
|
|
10391
|
-
|
|
10392
|
-
"
|
|
10393
|
-
...
|
|
10394
|
-
|
|
10395
|
-
},
|
|
10396
|
-
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
10397
|
-
});
|
|
10398
|
-
if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
|
|
10399
|
-
return response.status === 204 ? null : response.json();
|
|
10400
|
-
};
|
|
10401
|
-
const getAuthToken = async () => {
|
|
10402
|
-
if (!appEnabled) return staticToken || "";
|
|
10403
|
-
const nowMs = Date.now();
|
|
10404
|
-
if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
|
|
10405
|
-
const jwt = createGitHubAppJwt({ appId, privateKey });
|
|
10406
|
-
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(installationId)}/access_tokens`, {
|
|
10407
|
-
method: "POST",
|
|
10408
|
-
headers: { Authorization: `Bearer ${jwt}` }
|
|
10409
|
-
});
|
|
10410
|
-
installationToken = String(installation?.token || "").trim();
|
|
10411
|
-
installationTokenExpiresAt = Date.parse(installation?.expires_at || "") || nowMs + 3e6;
|
|
10412
|
-
if (!installationToken) throw new Error("GitHub App installation token response did not include a token");
|
|
10413
|
-
return installationToken;
|
|
10414
|
-
};
|
|
10415
|
-
const request = async (pathname, { method = "GET", body = void 0 } = {}) => {
|
|
10416
|
-
if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
|
|
10417
|
-
const token = await getAuthToken();
|
|
10418
|
-
return requestJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
|
|
10419
|
-
method,
|
|
10420
|
-
body,
|
|
10421
|
-
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
10628
|
+
Authorization: token,
|
|
10629
|
+
"Content-Type": "application/json",
|
|
10630
|
+
...options.headers || {}
|
|
10631
|
+
}
|
|
10422
10632
|
});
|
|
10633
|
+
if (!response.ok) throw new Error(`ClickUp API request failed: ${response.status}`);
|
|
10634
|
+
return response.json();
|
|
10423
10635
|
};
|
|
10424
|
-
const getRef = async (branch) => request(`/git/ref/heads/${encodeGitHubBranchRef(branch)}`);
|
|
10425
|
-
const getCommitObject = async (sha) => request(`/git/commits/${encodeURIComponent(sha)}`);
|
|
10426
|
-
const createBlob = async ({ content, encoding }) => request("/git/blobs", { method: "POST", body: { content, encoding } });
|
|
10427
|
-
const createTree = async ({ baseTree, tree }) => request("/git/trees", { method: "POST", body: { base_tree: baseTree, tree } });
|
|
10428
|
-
const createCommit = async ({ message, treeSha, parents }) => request("/git/commits", { method: "POST", body: { message, tree: treeSha, parents } });
|
|
10429
|
-
const updateRef = async ({ branch, sha, force = false }) => request(`/git/refs/heads/${encodeGitHubBranchRef(branch)}`, { method: "PATCH", body: { sha, force } });
|
|
10430
10636
|
return {
|
|
10431
|
-
async
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
async createPullRequest({ title, head, base, body = "", draft = false, maintainerCanModify = true }) {
|
|
10435
|
-
return request("/pulls", {
|
|
10637
|
+
async createWebhook({ endpoint, events, location }) {
|
|
10638
|
+
const body = { endpoint, events, ...Object.fromEntries(Object.entries(location || {}).filter(([, value]) => value)) };
|
|
10639
|
+
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`, {
|
|
10436
10640
|
method: "POST",
|
|
10437
|
-
body:
|
|
10438
|
-
title: String(title || ""),
|
|
10439
|
-
head: String(head || ""),
|
|
10440
|
-
base: String(base || ""),
|
|
10441
|
-
body: String(body || ""),
|
|
10442
|
-
draft: draft === true,
|
|
10443
|
-
maintainer_can_modify: maintainerCanModify !== false
|
|
10444
|
-
}
|
|
10641
|
+
body: JSON.stringify(body)
|
|
10445
10642
|
});
|
|
10446
10643
|
},
|
|
10447
|
-
async
|
|
10448
|
-
return request(
|
|
10449
|
-
},
|
|
10450
|
-
async replyToReviewComment({ commentId, body }) {
|
|
10451
|
-
return request(`/pulls/comments/${encodeURIComponent(commentId)}/replies`, { method: "POST", body: { body: String(body || "") } });
|
|
10644
|
+
async listWebhooks() {
|
|
10645
|
+
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`);
|
|
10452
10646
|
},
|
|
10453
|
-
async
|
|
10454
|
-
return request(
|
|
10647
|
+
async deleteWebhook(webhookId) {
|
|
10648
|
+
return request(`https://api.clickup.com/api/v2/webhook/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
|
|
10455
10649
|
},
|
|
10456
|
-
async
|
|
10457
|
-
|
|
10458
|
-
merge_method: String(mergeMethod || "squash")
|
|
10459
|
-
};
|
|
10460
|
-
if (commitTitle) body.commit_title = String(commitTitle);
|
|
10461
|
-
if (commitMessage) body.commit_message = String(commitMessage);
|
|
10462
|
-
return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
|
|
10650
|
+
async getAuthorizedUser() {
|
|
10651
|
+
return request("https://api.clickup.com/api/v2/user");
|
|
10463
10652
|
},
|
|
10464
|
-
async
|
|
10465
|
-
return request(
|
|
10653
|
+
async getTask(taskId) {
|
|
10654
|
+
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}`);
|
|
10466
10655
|
},
|
|
10467
|
-
async
|
|
10468
|
-
return request(
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
per_page: perPage
|
|
10473
|
-
}));
|
|
10656
|
+
async updateTaskMetadata({ taskId, fieldId, value }) {
|
|
10657
|
+
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/field/${encodeURIComponent(fieldId)}`, {
|
|
10658
|
+
method: "POST",
|
|
10659
|
+
body: JSON.stringify({ value })
|
|
10660
|
+
});
|
|
10474
10661
|
},
|
|
10475
|
-
async
|
|
10476
|
-
|
|
10662
|
+
async postTaskComment() {
|
|
10663
|
+
throw new Error("Optima runtime does not post ClickUp task comments; use local logs for process failures.");
|
|
10477
10664
|
},
|
|
10478
|
-
async
|
|
10479
|
-
|
|
10480
|
-
context = "Vercel \u2013 defend-preproduction",
|
|
10481
|
-
environment = "Preview \u2013 defend-preproduction",
|
|
10482
|
-
requireFunctionalUrl = true
|
|
10483
|
-
} = {}) {
|
|
10484
|
-
const pr = await this.getPullRequest(pullNumber);
|
|
10485
|
-
const headSha = String(pr?.head?.sha || "").trim();
|
|
10486
|
-
if (!headSha) return { ok: true, ready: false, reason: "pr_head_sha_missing", pull_request: { number: pullNumber } };
|
|
10487
|
-
const combined = await this.getCombinedStatus(headSha);
|
|
10488
|
-
const statuses = Array.isArray(combined?.statuses) ? combined.statuses : [];
|
|
10489
|
-
const matchingStatuses = statuses.filter((status) => matchesVercelStatusContext(status?.context, context));
|
|
10490
|
-
const selectedStatus = sortNewestFirst(matchingStatuses)[0] || null;
|
|
10491
|
-
if (!selectedStatus) {
|
|
10492
|
-
return {
|
|
10493
|
-
ok: true,
|
|
10494
|
-
ready: false,
|
|
10495
|
-
reason: "vercel_status_missing",
|
|
10496
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10497
|
-
required_context: context,
|
|
10498
|
-
combined_state: combined?.state || null
|
|
10499
|
-
};
|
|
10500
|
-
}
|
|
10501
|
-
if (String(selectedStatus.state || "").toLowerCase() !== "success") {
|
|
10502
|
-
return {
|
|
10503
|
-
ok: true,
|
|
10504
|
-
ready: false,
|
|
10505
|
-
reason: "vercel_status_not_success",
|
|
10506
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10507
|
-
required_context: context,
|
|
10508
|
-
status: selectedStatus,
|
|
10509
|
-
combined_state: combined?.state || null
|
|
10510
|
-
};
|
|
10511
|
-
}
|
|
10512
|
-
let deployments = await this.listDeployments({ sha: headSha, environment });
|
|
10513
|
-
if (!Array.isArray(deployments) || deployments.length === 0) deployments = await this.listDeployments({ sha: headSha });
|
|
10514
|
-
const selectedDeployment = sortNewestFirst(Array.isArray(deployments) ? deployments : []).find((deployment) => !environment || String(deployment?.environment || "") === environment) || sortNewestFirst(Array.isArray(deployments) ? deployments : [])[0] || null;
|
|
10515
|
-
if (!selectedDeployment?.id) {
|
|
10516
|
-
return {
|
|
10517
|
-
ok: true,
|
|
10518
|
-
ready: false,
|
|
10519
|
-
reason: "vercel_deployment_missing",
|
|
10520
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10521
|
-
required_environment: environment,
|
|
10522
|
-
status: selectedStatus
|
|
10523
|
-
};
|
|
10524
|
-
}
|
|
10525
|
-
const deploymentStatuses = await this.listDeploymentStatuses(selectedDeployment.id);
|
|
10526
|
-
const selectedDeploymentStatus = sortNewestFirst(Array.isArray(deploymentStatuses) ? deploymentStatuses : [])[0] || null;
|
|
10527
|
-
if (!selectedDeploymentStatus || String(selectedDeploymentStatus.state || "").toLowerCase() !== "success") {
|
|
10528
|
-
return {
|
|
10529
|
-
ok: true,
|
|
10530
|
-
ready: false,
|
|
10531
|
-
reason: "vercel_deployment_not_success",
|
|
10532
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10533
|
-
required_environment: environment,
|
|
10534
|
-
status: selectedStatus,
|
|
10535
|
-
deployment: selectedDeployment,
|
|
10536
|
-
deployment_status: selectedDeploymentStatus
|
|
10537
|
-
};
|
|
10538
|
-
}
|
|
10539
|
-
const url = selectFunctionalDeploymentUrl(selectedDeploymentStatus, selectedDeployment);
|
|
10540
|
-
const urlCheck = requireFunctionalUrl ? await checkFunctionalDeploymentUrl(url, fetchImpl) : { ok: Boolean(url), reason: url ? void 0 : "url_missing", url };
|
|
10541
|
-
if (requireFunctionalUrl && !urlCheck.ok) {
|
|
10542
|
-
return {
|
|
10543
|
-
ok: true,
|
|
10544
|
-
ready: false,
|
|
10545
|
-
reason: "vercel_url_not_functional",
|
|
10546
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10547
|
-
required_environment: environment,
|
|
10548
|
-
status: selectedStatus,
|
|
10549
|
-
deployment: selectedDeployment,
|
|
10550
|
-
deployment_status: selectedDeploymentStatus,
|
|
10551
|
-
url,
|
|
10552
|
-
url_check: urlCheck
|
|
10553
|
-
};
|
|
10554
|
-
}
|
|
10555
|
-
return {
|
|
10556
|
-
ok: true,
|
|
10557
|
-
ready: true,
|
|
10558
|
-
reason: "vercel_pr_deployment_ready",
|
|
10559
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10560
|
-
required_context: context,
|
|
10561
|
-
required_environment: environment,
|
|
10562
|
-
status: selectedStatus,
|
|
10563
|
-
deployment: selectedDeployment,
|
|
10564
|
-
deployment_status: selectedDeploymentStatus,
|
|
10565
|
-
url,
|
|
10566
|
-
url_check: urlCheck
|
|
10567
|
-
};
|
|
10665
|
+
async addTaskTag() {
|
|
10666
|
+
throw new Error("Optima runtime does not add ClickUp task tags; use local logs for process failures.");
|
|
10568
10667
|
},
|
|
10569
|
-
async
|
|
10570
|
-
const
|
|
10571
|
-
if (
|
|
10572
|
-
const
|
|
10573
|
-
if (
|
|
10574
|
-
|
|
10575
|
-
if (!commitMessage) throw new Error("commit message is required");
|
|
10576
|
-
const changedPaths = listWorktreeChangedPaths(directory, runGitFn);
|
|
10577
|
-
if (changedPaths.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
10578
|
-
const ref = await getRef(targetBranch);
|
|
10579
|
-
const headSha = ref?.object?.sha;
|
|
10580
|
-
if (!headSha) throw new Error(`GitHub ref for ${targetBranch} did not include a head sha`);
|
|
10581
|
-
const headCommit = await getCommitObject(headSha);
|
|
10582
|
-
const baseTree = headCommit?.tree?.sha;
|
|
10583
|
-
if (!baseTree) throw new Error(`GitHub commit ${headSha} did not include a tree sha`);
|
|
10584
|
-
const tree = [];
|
|
10585
|
-
for (const gitPath of changedPaths) {
|
|
10586
|
-
const entry = treeEntryForWorktreePath(directory, gitPath);
|
|
10587
|
-
if (!entry) continue;
|
|
10588
|
-
if (entry.deleted) {
|
|
10589
|
-
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: null });
|
|
10590
|
-
continue;
|
|
10591
|
-
}
|
|
10592
|
-
const blob = await createBlob({ content: entry.content, encoding: entry.encoding });
|
|
10593
|
-
if (!blob?.sha) throw new Error(`GitHub blob creation failed for ${gitPath}`);
|
|
10594
|
-
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: blob.sha });
|
|
10595
|
-
}
|
|
10596
|
-
if (tree.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
10597
|
-
const nextTree = await createTree({ baseTree, tree });
|
|
10598
|
-
const nextCommit = await createCommit({ message: commitMessage, treeSha: nextTree.sha, parents: [headSha] });
|
|
10599
|
-
if (!nextCommit?.sha) throw new Error("GitHub commit creation did not return a sha");
|
|
10600
|
-
const updatedRef = await updateRef({ branch: targetBranch, sha: nextCommit.sha, force: false });
|
|
10601
|
-
if (syncLocal) {
|
|
10602
|
-
runGitFn(directory, ["fetch", "origin", targetBranch]);
|
|
10603
|
-
runGitFn(directory, ["reset", "--hard", "FETCH_HEAD"]);
|
|
10604
|
-
}
|
|
10605
|
-
return {
|
|
10606
|
-
ok: true,
|
|
10607
|
-
action: "committed",
|
|
10608
|
-
branch: targetBranch,
|
|
10609
|
-
before: headSha,
|
|
10610
|
-
after: nextCommit.sha,
|
|
10611
|
-
changedPaths,
|
|
10612
|
-
treeEntries: tree.length,
|
|
10613
|
-
verification: nextCommit.verification || null,
|
|
10614
|
-
ref: updatedRef
|
|
10615
|
-
};
|
|
10668
|
+
async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10669
|
+
const params = new URLSearchParams();
|
|
10670
|
+
if (assigneeId) params.append("assignees[]", assigneeId);
|
|
10671
|
+
for (const status of statuses) if (status) params.append("statuses[]", status);
|
|
10672
|
+
if (limit) params.append("limit", String(limit));
|
|
10673
|
+
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/task?${params.toString()}`);
|
|
10616
10674
|
},
|
|
10617
|
-
async
|
|
10618
|
-
|
|
10619
|
-
if (
|
|
10620
|
-
|
|
10675
|
+
async getTaskComments({ taskId, start, limit = CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT } = {}) {
|
|
10676
|
+
const params = new URLSearchParams();
|
|
10677
|
+
if (start) params.set("start", String(start));
|
|
10678
|
+
if (limit) params.set("limit", String(limit));
|
|
10679
|
+
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
10680
|
+
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/comment${suffix}`);
|
|
10621
10681
|
}
|
|
10622
10682
|
};
|
|
10623
10683
|
}
|
|
@@ -10846,10 +10906,10 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
|
|
|
10846
10906
|
function verifyClickUpSignature(rawBody, signatureHeader, secret) {
|
|
10847
10907
|
const signature = String(signatureHeader || "").replace(/^sha256=/i, "").trim();
|
|
10848
10908
|
if (!signature || !secret) return false;
|
|
10849
|
-
const expected =
|
|
10909
|
+
const expected = crypto2.createHmac("sha256", secret).update(Buffer.isBuffer(rawBody) ? rawBody : String(rawBody || ""), "utf8").digest("hex");
|
|
10850
10910
|
const given = Buffer.from(signature, "hex");
|
|
10851
10911
|
const wanted = Buffer.from(expected, "hex");
|
|
10852
|
-
return given.length === wanted.length &&
|
|
10912
|
+
return given.length === wanted.length && crypto2.timingSafeEqual(given, wanted);
|
|
10853
10913
|
}
|
|
10854
10914
|
function verifyGitHubSignature(rawBody, signatureHeader, secret) {
|
|
10855
10915
|
return verifyClickUpSignature(rawBody, signatureHeader, secret);
|
|
@@ -10903,9 +10963,9 @@ function rememberClickUpWebhookEvent(state = {}, eventKey, limit = 200) {
|
|
|
10903
10963
|
return { duplicate: false, state: { ...state, recentEventKeys: [...recent, eventKey].slice(-limit) } };
|
|
10904
10964
|
}
|
|
10905
10965
|
function readClickUpCommentLedgerEntries(ledgerPath) {
|
|
10906
|
-
if (!ledgerPath || !
|
|
10966
|
+
if (!ledgerPath || !fs8.existsSync(ledgerPath)) return [];
|
|
10907
10967
|
try {
|
|
10908
|
-
const raw =
|
|
10968
|
+
const raw = fs8.readFileSync(ledgerPath, "utf8");
|
|
10909
10969
|
const entries = [];
|
|
10910
10970
|
for (const [index, line] of raw.split(/\r?\n/).entries()) {
|
|
10911
10971
|
if (!line.trim()) continue;
|
|
@@ -10929,11 +10989,11 @@ function readClickUpCommentLedger(ledgerPath) {
|
|
|
10929
10989
|
}
|
|
10930
10990
|
function appendClickUpCommentLedgerEntry(ledgerPath, entry = {}) {
|
|
10931
10991
|
if (!ledgerPath || !entry.key) return;
|
|
10932
|
-
|
|
10933
|
-
|
|
10992
|
+
fs8.mkdirSync(path8.dirname(ledgerPath), { recursive: true, mode: 448 });
|
|
10993
|
+
fs8.appendFileSync(ledgerPath, `${JSON.stringify(entry)}
|
|
10934
10994
|
`, { encoding: "utf8", mode: 384 });
|
|
10935
10995
|
try {
|
|
10936
|
-
|
|
10996
|
+
fs8.chmodSync(ledgerPath, 384);
|
|
10937
10997
|
} catch {
|
|
10938
10998
|
}
|
|
10939
10999
|
}
|
|
@@ -10952,7 +11012,7 @@ function stableClickUpCommentVersionMarker(value) {
|
|
|
10952
11012
|
const direct = value.version ?? value.date_updated ?? value.updated_at ?? value.revision ?? value.modified_at;
|
|
10953
11013
|
const directMarker = stableClickUpCommentVersionMarker(direct);
|
|
10954
11014
|
if (directMarker) return directMarker;
|
|
10955
|
-
return `json:${
|
|
11015
|
+
return `json:${crypto2.createHash("sha256").update(JSON.stringify(value, Object.keys(value).sort())).digest("hex").slice(0, 16)}`;
|
|
10956
11016
|
}
|
|
10957
11017
|
function clickUpCommentLedgerKey({ taskId, eventType, payload }) {
|
|
10958
11018
|
const history = Array.isArray(payload?.history_items) ? payload.history_items[0] : payload?.history_item;
|
|
@@ -10962,7 +11022,7 @@ function clickUpCommentLedgerKey({ taskId, eventType, payload }) {
|
|
|
10962
11022
|
const explicitVersion = stableClickUpCommentVersionMarker(
|
|
10963
11023
|
comment?.date_updated || comment?.dateUpdated || comment?.updated_at || comment?.updatedAt || comment?._version_vector || comment?.version_vector || comment?.versionVector || comment?.version || comment?.revision || comment?.modified_at || comment?.modifiedAt || ""
|
|
10964
11024
|
);
|
|
10965
|
-
const contentVersion =
|
|
11025
|
+
const contentVersion = crypto2.createHash("sha256").update(JSON.stringify({ text: clickUpCommentText(comment), parts: Array.isArray(comment.comment) ? comment.comment : null })).digest("hex").slice(0, 16);
|
|
10966
11026
|
return [String(taskId || "").trim(), "comment", commentId, explicitVersion || `sha256-${contentVersion}`].filter(Boolean).join(":");
|
|
10967
11027
|
}
|
|
10968
11028
|
function isClickUpCommentVersionProcessed({ ledgerPath, key, ledger = null, worktree = process.cwd() } = {}) {
|
|
@@ -11492,7 +11552,7 @@ function clampOpenCodeSessionText(text = "", maxLength = 12e3) {
|
|
|
11492
11552
|
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
11493
11553
|
}
|
|
11494
11554
|
function hashOpenCodeSessionText(text = "") {
|
|
11495
|
-
return
|
|
11555
|
+
return crypto2.createHash("sha256").update(String(text ?? "")).digest("hex");
|
|
11496
11556
|
}
|
|
11497
11557
|
async function readOpenCodeSessionMessages(client, { sessionId, directory, limit = 20 } = {}) {
|
|
11498
11558
|
if (typeof client?.session?.messages !== "function") return null;
|
|
@@ -11730,7 +11790,7 @@ async function readOpenCodeSessionControl(client, { sessionId, directory, limit
|
|
|
11730
11790
|
};
|
|
11731
11791
|
}
|
|
11732
11792
|
async function probeOpenCodeSessionControl(client, { directory, agent, omitAgentOnPrompt = false, text = "" } = {}) {
|
|
11733
|
-
const requestedMarker = text || `optima-session-probe-${
|
|
11793
|
+
const requestedMarker = text || `optima-session-probe-${crypto2.randomUUID()}`;
|
|
11734
11794
|
const marker = clampOpenCodeSessionText(requestedMarker);
|
|
11735
11795
|
const create = await createOpenCodeSessionControl(client, {
|
|
11736
11796
|
directory,
|
|
@@ -11958,9 +12018,9 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
|
|
|
11958
12018
|
}
|
|
11959
12019
|
function appendClickUpWebhookLocalLog(worktree, entry) {
|
|
11960
12020
|
const logPath = clickUpWebhookLogPath(worktree);
|
|
11961
|
-
|
|
12021
|
+
fs8.mkdirSync(path8.dirname(logPath), { recursive: true });
|
|
11962
12022
|
const safeEntry = { ...entry, at: entry.at || (/* @__PURE__ */ new Date()).toISOString() };
|
|
11963
|
-
|
|
12023
|
+
fs8.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
|
|
11964
12024
|
`, "utf8");
|
|
11965
12025
|
}
|
|
11966
12026
|
function clickUpWebhookLifecycleLog(worktree, entry) {
|
|
@@ -11989,7 +12049,7 @@ function closeClickUpWebhookServer(server) {
|
|
|
11989
12049
|
});
|
|
11990
12050
|
}
|
|
11991
12051
|
function managedClickUpWebhookKey({ worktree, state, config } = {}) {
|
|
11992
|
-
return [
|
|
12052
|
+
return [path8.resolve(worktree || process.cwd()), state?.webhookId || "", config?.webhook?.publicUrl || ""].join("|");
|
|
11993
12053
|
}
|
|
11994
12054
|
async function deleteClickUpWebhookBestEffort({ webhookId, clickupClient, worktree, reason = "cleanup" } = {}) {
|
|
11995
12055
|
const id = String(webhookId || "").trim();
|
|
@@ -12089,7 +12149,7 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
12089
12149
|
const failed = Boolean(error || !handled?.ok || (handled?.status || 500) >= 400);
|
|
12090
12150
|
if (level === "error" && !failed) return;
|
|
12091
12151
|
const logDir = clickUpWebhookAuditLogDir();
|
|
12092
|
-
|
|
12152
|
+
fs8.mkdirSync(logDir, { recursive: true });
|
|
12093
12153
|
const secretValues = [
|
|
12094
12154
|
resolveSecretReference(config?.apiToken),
|
|
12095
12155
|
resolveSecretReference(config?.github?.apiToken),
|
|
@@ -12100,8 +12160,8 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
12100
12160
|
let requestFile;
|
|
12101
12161
|
if (level === "verbose") {
|
|
12102
12162
|
const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
|
|
12103
|
-
|
|
12104
|
-
requestFile =
|
|
12163
|
+
fs8.mkdirSync(path8.dirname(absoluteRequestFile), { recursive: true });
|
|
12164
|
+
requestFile = path8.relative(logDir, absoluteRequestFile).split(path8.sep).join("/");
|
|
12105
12165
|
const parsedBody = payload || (() => {
|
|
12106
12166
|
try {
|
|
12107
12167
|
return JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || ""));
|
|
@@ -12117,11 +12177,11 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
12117
12177
|
headers,
|
|
12118
12178
|
body: parsedBody === void 0 ? Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || "") : parsedBody
|
|
12119
12179
|
}, secretValues);
|
|
12120
|
-
|
|
12180
|
+
fs8.writeFileSync(absoluteRequestFile, `${JSON.stringify(requestArtifact, null, 2)}
|
|
12121
12181
|
`, "utf8");
|
|
12122
12182
|
}
|
|
12123
12183
|
const summary = redactClickUpWebhookAuditValue(buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at }), secretValues);
|
|
12124
|
-
|
|
12184
|
+
fs8.appendFileSync(clickUpWebhookAuditLogPath(at, logDir), `${JSON.stringify(summary)}
|
|
12125
12185
|
`, "utf8");
|
|
12126
12186
|
} catch {
|
|
12127
12187
|
}
|
|
@@ -12139,7 +12199,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
|
|
|
12139
12199
|
if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
|
|
12140
12200
|
}
|
|
12141
12201
|
}
|
|
12142
|
-
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktreeForWebhook, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host =
|
|
12202
|
+
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktreeForWebhook, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os2.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
|
|
12143
12203
|
const eventType = clickUpEventType(payload);
|
|
12144
12204
|
const eventKey = clickUpWebhookEventKey(payload);
|
|
12145
12205
|
const isStartupAssignmentReconciliation = payload?.startup_reconciliation === true && eventType === "taskAssigneeUpdated";
|
|
@@ -12908,17 +12968,17 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, g
|
|
|
12908
12968
|
return result;
|
|
12909
12969
|
}
|
|
12910
12970
|
function legacyVariantPath(destinationPath) {
|
|
12911
|
-
const parsed =
|
|
12971
|
+
const parsed = path8.parse(destinationPath);
|
|
12912
12972
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
|
|
12913
|
-
if (parsed.ext) return
|
|
12914
|
-
return
|
|
12973
|
+
if (parsed.ext) return path8.join(parsed.dir, `${parsed.name}.legacy-${stamp}${parsed.ext}`);
|
|
12974
|
+
return path8.join(parsed.dir, `${parsed.base}.legacy-${stamp}`);
|
|
12915
12975
|
}
|
|
12916
12976
|
function normalizeWorkflowTaskPath(taskPath) {
|
|
12917
12977
|
if (typeof taskPath !== "string") return { ok: false, message: "Error: task_path is required." };
|
|
12918
12978
|
const trimmed = taskPath.trim();
|
|
12919
12979
|
if (!trimmed) return { ok: false, message: "Error: task_path is required." };
|
|
12920
12980
|
const normalized = trimmed.replace(/\\/g, "/");
|
|
12921
|
-
if (
|
|
12981
|
+
if (path8.isAbsolute(trimmed)) return { ok: true, taskPath: trimmed };
|
|
12922
12982
|
if (normalized === "tasks" || normalized.startsWith("tasks/")) {
|
|
12923
12983
|
return {
|
|
12924
12984
|
ok: false,
|
|
@@ -12936,78 +12996,78 @@ function normalizeWorkflowTaskPath(taskPath) {
|
|
|
12936
12996
|
function mergeFileIntoDestination(sourcePath, destinationPath, relativeSource, gitState = null) {
|
|
12937
12997
|
const sourceWasTracked = isGitTracked(gitState, sourcePath);
|
|
12938
12998
|
if (gitAwareMoveIfTracked(sourcePath, destinationPath, gitState)) return;
|
|
12939
|
-
if (!
|
|
12940
|
-
|
|
12941
|
-
|
|
12999
|
+
if (!fs8.existsSync(destinationPath)) {
|
|
13000
|
+
fs8.mkdirSync(path8.dirname(destinationPath), { recursive: true });
|
|
13001
|
+
fs8.renameSync(sourcePath, destinationPath);
|
|
12942
13002
|
if (sourceWasTracked) stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12943
13003
|
return;
|
|
12944
13004
|
}
|
|
12945
|
-
const ext =
|
|
13005
|
+
const ext = path8.extname(destinationPath).toLowerCase();
|
|
12946
13006
|
if ([".yaml", ".yml", ".json"].includes(ext)) {
|
|
12947
|
-
const sourceRaw =
|
|
12948
|
-
const destRaw =
|
|
13007
|
+
const sourceRaw = fs8.readFileSync(sourcePath, "utf8");
|
|
13008
|
+
const destRaw = fs8.readFileSync(destinationPath, "utf8");
|
|
12949
13009
|
const parser = ext === ".json" ? JSON : import_yaml3.default;
|
|
12950
13010
|
const sourceValue = parser.parse(sourceRaw) || {};
|
|
12951
13011
|
const destValue = parser.parse(destRaw) || {};
|
|
12952
13012
|
const merged = mergeStructuredValues(sourceValue, destValue);
|
|
12953
13013
|
const serialized = ext === ".json" ? `${JSON.stringify(merged, null, 2)}
|
|
12954
13014
|
` : import_yaml3.default.stringify(merged);
|
|
12955
|
-
|
|
12956
|
-
|
|
13015
|
+
fs8.writeFileSync(destinationPath, serialized, "utf8");
|
|
13016
|
+
fs8.unlinkSync(sourcePath);
|
|
12957
13017
|
stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12958
13018
|
return;
|
|
12959
13019
|
}
|
|
12960
13020
|
if (ext === ".md") {
|
|
12961
|
-
const sourceRaw =
|
|
12962
|
-
const destRaw =
|
|
13021
|
+
const sourceRaw = fs8.readFileSync(sourcePath, "utf8").trimEnd();
|
|
13022
|
+
const destRaw = fs8.readFileSync(destinationPath, "utf8").trimEnd();
|
|
12963
13023
|
if (sourceRaw && !destRaw.includes(sourceRaw)) {
|
|
12964
13024
|
const marker = `## Legacy Content From ${relativeSource}`;
|
|
12965
|
-
|
|
13025
|
+
fs8.writeFileSync(destinationPath, `${destRaw}
|
|
12966
13026
|
|
|
12967
13027
|
${marker}
|
|
12968
13028
|
|
|
12969
13029
|
${sourceRaw}
|
|
12970
13030
|
`, "utf8");
|
|
12971
13031
|
}
|
|
12972
|
-
|
|
13032
|
+
fs8.unlinkSync(sourcePath);
|
|
12973
13033
|
stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12974
13034
|
return;
|
|
12975
13035
|
}
|
|
12976
13036
|
const preservedPath = legacyVariantPath(destinationPath);
|
|
12977
|
-
|
|
13037
|
+
fs8.renameSync(sourcePath, preservedPath);
|
|
12978
13038
|
stageGitAwareMerge(sourcePath, preservedPath, gitState);
|
|
12979
13039
|
}
|
|
12980
|
-
function mergePathIntoDestination(sourcePath, destinationPath, relativeSource =
|
|
12981
|
-
if (!
|
|
12982
|
-
const stat =
|
|
13040
|
+
function mergePathIntoDestination(sourcePath, destinationPath, relativeSource = path8.basename(sourcePath), gitState = null) {
|
|
13041
|
+
if (!fs8.existsSync(sourcePath)) return;
|
|
13042
|
+
const stat = fs8.statSync(sourcePath);
|
|
12983
13043
|
if (stat.isDirectory()) {
|
|
12984
13044
|
const sourceWasTracked = isGitTracked(gitState, sourcePath) || hasGitTrackedChildren(gitState, sourcePath);
|
|
12985
13045
|
if (gitAwareMoveIfTracked(sourcePath, destinationPath, gitState)) return;
|
|
12986
|
-
if (!
|
|
12987
|
-
|
|
12988
|
-
|
|
13046
|
+
if (!fs8.existsSync(destinationPath)) {
|
|
13047
|
+
fs8.mkdirSync(path8.dirname(destinationPath), { recursive: true });
|
|
13048
|
+
fs8.renameSync(sourcePath, destinationPath);
|
|
12989
13049
|
if (sourceWasTracked) stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12990
13050
|
return;
|
|
12991
13051
|
}
|
|
12992
|
-
|
|
12993
|
-
for (const entry of
|
|
13052
|
+
fs8.mkdirSync(destinationPath, { recursive: true });
|
|
13053
|
+
for (const entry of fs8.readdirSync(sourcePath)) {
|
|
12994
13054
|
mergePathIntoDestination(
|
|
12995
|
-
|
|
12996
|
-
|
|
12997
|
-
|
|
13055
|
+
path8.join(sourcePath, entry),
|
|
13056
|
+
path8.join(destinationPath, entry),
|
|
13057
|
+
path8.join(relativeSource, entry),
|
|
12998
13058
|
gitState
|
|
12999
13059
|
);
|
|
13000
13060
|
}
|
|
13001
|
-
|
|
13061
|
+
fs8.rmSync(sourcePath, { recursive: true, force: true });
|
|
13002
13062
|
return;
|
|
13003
13063
|
}
|
|
13004
13064
|
mergeFileIntoDestination(sourcePath, destinationPath, relativeSource, gitState);
|
|
13005
13065
|
}
|
|
13006
13066
|
function isOptimaPluginPackageWorktree(worktree) {
|
|
13007
|
-
const packageJsonPath =
|
|
13008
|
-
if (!
|
|
13067
|
+
const packageJsonPath = path8.join(worktree, "package.json");
|
|
13068
|
+
if (!fs8.existsSync(packageJsonPath)) return false;
|
|
13009
13069
|
try {
|
|
13010
|
-
const pkg = JSON.parse(
|
|
13070
|
+
const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
|
|
13011
13071
|
return pkg?.name === "@defend-tech/opencode-optima";
|
|
13012
13072
|
} catch {
|
|
13013
13073
|
return false;
|
|
@@ -13016,59 +13076,59 @@ function isOptimaPluginPackageWorktree(worktree) {
|
|
|
13016
13076
|
function migrateLegacyOptimaLayout(worktree) {
|
|
13017
13077
|
const gitState = gitMigrationState(worktree);
|
|
13018
13078
|
const migrations = [
|
|
13019
|
-
[
|
|
13020
|
-
[
|
|
13021
|
-
[
|
|
13022
|
-
[
|
|
13023
|
-
[
|
|
13024
|
-
[
|
|
13079
|
+
[path8.join(legacyOrbitaDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".orbita", "orbita.yaml")],
|
|
13080
|
+
[path8.join(legacyOrbitaDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".orbita", "staticeng.yaml")],
|
|
13081
|
+
[path8.join(legacyOrbitaDir(worktree), ".config"), optimaLocalConfigDir(worktree), path8.join(".orbita", ".config")],
|
|
13082
|
+
[path8.join(legacyOrbitaDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".orbita", "config")],
|
|
13083
|
+
[path8.join(legacyOrbitaDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".orbita", "runtime")],
|
|
13084
|
+
[path8.join(legacyOrbitaDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".orbita", "generated")],
|
|
13025
13085
|
[legacyOrbitaDir(worktree), optimaDir(worktree), ".orbita"],
|
|
13026
|
-
[
|
|
13027
|
-
[
|
|
13028
|
-
[
|
|
13029
|
-
[
|
|
13030
|
-
[
|
|
13031
|
-
[
|
|
13086
|
+
[path8.join(legacyStaticEngDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".staticeng", "staticeng.yaml")],
|
|
13087
|
+
[path8.join(legacyStaticEngDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".staticeng", "orbita.yaml")],
|
|
13088
|
+
[path8.join(legacyStaticEngDir(worktree), ".config"), optimaLocalConfigDir(worktree), path8.join(".staticeng", ".config")],
|
|
13089
|
+
[path8.join(legacyStaticEngDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".staticeng", "config")],
|
|
13090
|
+
[path8.join(legacyStaticEngDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".staticeng", "runtime")],
|
|
13091
|
+
[path8.join(legacyStaticEngDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".staticeng", "generated")],
|
|
13032
13092
|
[legacyStaticEngDir(worktree), optimaDir(worktree), ".staticeng"],
|
|
13033
|
-
[
|
|
13034
|
-
[
|
|
13035
|
-
[
|
|
13036
|
-
[
|
|
13037
|
-
[
|
|
13038
|
-
[
|
|
13039
|
-
[
|
|
13040
|
-
[
|
|
13041
|
-
[
|
|
13042
|
-
[
|
|
13043
|
-
[
|
|
13044
|
-
[
|
|
13093
|
+
[path8.join(legacyNomadworkDir(worktree), "nomadworks.yaml"), repoConfigPath(worktree), path8.join(".nomadwork", "nomadworks.yaml")],
|
|
13094
|
+
[path8.join(legacyNomadworksDir(worktree), "nomadworks.yaml"), repoConfigPath(worktree), path8.join(".nomadworks", "nomadworks.yaml")],
|
|
13095
|
+
[path8.join(legacyNomadworkDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".nomadwork", "staticeng.yaml")],
|
|
13096
|
+
[path8.join(legacyNomadworksDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".nomadworks", "staticeng.yaml")],
|
|
13097
|
+
[path8.join(legacyNomadworkDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".nomadwork", "orbita.yaml")],
|
|
13098
|
+
[path8.join(legacyNomadworksDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".nomadworks", "orbita.yaml")],
|
|
13099
|
+
[path8.join(legacyNomadworkDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".nomadwork", "runtime")],
|
|
13100
|
+
[path8.join(legacyNomadworksDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".nomadworks", "runtime")],
|
|
13101
|
+
[path8.join(legacyNomadworkDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".nomadwork", "generated")],
|
|
13102
|
+
[path8.join(legacyNomadworksDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".nomadworks", "generated")],
|
|
13103
|
+
[path8.join(legacyNomadworkDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".nomadwork", "config")],
|
|
13104
|
+
[path8.join(legacyNomadworksDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".nomadworks", "config")],
|
|
13045
13105
|
[legacyNomadworkDir(worktree), optimaDir(worktree), ".nomadwork"],
|
|
13046
13106
|
[legacyNomadworksDir(worktree), optimaDir(worktree), ".nomadworks"],
|
|
13047
|
-
[
|
|
13048
|
-
[
|
|
13049
|
-
[
|
|
13050
|
-
[
|
|
13051
|
-
[
|
|
13107
|
+
[path8.join(worktree, "tasks"), optimaTasksDir(worktree), "tasks"],
|
|
13108
|
+
[path8.join(worktree, "evidences"), optimaEvidencesDir(worktree), "evidences"],
|
|
13109
|
+
[path8.join(worktree, "docs", "scrs"), optimaScrsDir(worktree), path8.join("docs", "scrs")],
|
|
13110
|
+
[path8.join(worktree, "codemap.yml"), optimaCodemapPath(worktree), "codemap.yml"],
|
|
13111
|
+
[path8.join(worktree, "codemap.yaml"), optimaCodemapPath(worktree), "codemap.yaml"]
|
|
13052
13112
|
];
|
|
13053
13113
|
if (!isOptimaPluginPackageWorktree(worktree)) {
|
|
13054
|
-
migrations.push([
|
|
13114
|
+
migrations.push([path8.join(worktree, "policies"), repoPoliciesDir(worktree), "policies"]);
|
|
13055
13115
|
}
|
|
13056
13116
|
for (const [source, destination, relativeSource] of migrations) {
|
|
13057
|
-
if (
|
|
13117
|
+
if (fs8.existsSync(source)) mergePathIntoDestination(source, destination, relativeSource, gitState);
|
|
13058
13118
|
}
|
|
13059
13119
|
for (const [source, destination, relativeSource] of [
|
|
13060
|
-
[
|
|
13061
|
-
[
|
|
13062
|
-
[
|
|
13063
|
-
[
|
|
13120
|
+
[path8.join(optimaDir(worktree), "optima.yaml"), repoConfigPath(worktree), path8.join(".optima", "optima.yaml")],
|
|
13121
|
+
[path8.join(optimaDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".optima", "config")],
|
|
13122
|
+
[path8.join(optimaDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".optima", "runtime")],
|
|
13123
|
+
[path8.join(optimaDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".optima", "generated")]
|
|
13064
13124
|
]) {
|
|
13065
|
-
if (
|
|
13125
|
+
if (fs8.existsSync(source)) mergePathIntoDestination(source, destination, relativeSource, gitState);
|
|
13066
13126
|
}
|
|
13067
13127
|
}
|
|
13068
13128
|
function listMarkdownFiles(dirPath) {
|
|
13069
|
-
if (!
|
|
13129
|
+
if (!fs8.existsSync(dirPath)) return [];
|
|
13070
13130
|
try {
|
|
13071
|
-
return
|
|
13131
|
+
return fs8.readdirSync(dirPath).filter((file) => file.endsWith(".md") && file.toLowerCase() !== "readme.md");
|
|
13072
13132
|
} catch (e) {
|
|
13073
13133
|
console.error(`[Optima] Failed to read markdown files from ${dirPath}:`, e);
|
|
13074
13134
|
return [];
|
|
@@ -13109,10 +13169,10 @@ function toModelString(provider, model) {
|
|
|
13109
13169
|
}
|
|
13110
13170
|
function readTaskMetadata(taskPath, worktree) {
|
|
13111
13171
|
if (!taskPath) return {};
|
|
13112
|
-
const absoluteTaskPath =
|
|
13113
|
-
if (!
|
|
13172
|
+
const absoluteTaskPath = path8.isAbsolute(taskPath) ? taskPath : path8.join(worktree, taskPath);
|
|
13173
|
+
if (!fs8.existsSync(absoluteTaskPath)) return {};
|
|
13114
13174
|
try {
|
|
13115
|
-
const raw =
|
|
13175
|
+
const raw = fs8.readFileSync(absoluteTaskPath, "utf8");
|
|
13116
13176
|
const { data } = parseFrontmatter(raw);
|
|
13117
13177
|
return {
|
|
13118
13178
|
complexity: typeof data.complexity === "string" ? data.complexity.trim().toLowerCase() : void 0,
|
|
@@ -13137,11 +13197,11 @@ function slugifyTitle(input) {
|
|
|
13137
13197
|
}
|
|
13138
13198
|
function loadDiscussionRegistry(worktree) {
|
|
13139
13199
|
const registryPath = runtimeDiscussionRegistryPath(worktree);
|
|
13140
|
-
if (!
|
|
13200
|
+
if (!fs8.existsSync(registryPath)) {
|
|
13141
13201
|
return { version: 1, active: {} };
|
|
13142
13202
|
}
|
|
13143
13203
|
try {
|
|
13144
|
-
const parsed = JSON.parse(
|
|
13204
|
+
const parsed = JSON.parse(fs8.readFileSync(registryPath, "utf8"));
|
|
13145
13205
|
const registry = {
|
|
13146
13206
|
version: 1,
|
|
13147
13207
|
active: Object.fromEntries(Object.entries(parsed.active || {}).map(([key, value]) => [key, normalizeLegacyDiscussionEntry(value)]))
|
|
@@ -13154,34 +13214,34 @@ function loadDiscussionRegistry(worktree) {
|
|
|
13154
13214
|
}
|
|
13155
13215
|
function saveDiscussionRegistry(worktree, registry) {
|
|
13156
13216
|
const registryPath = runtimeDiscussionRegistryPath(worktree);
|
|
13157
|
-
const runtimeDir =
|
|
13158
|
-
if (!
|
|
13159
|
-
|
|
13217
|
+
const runtimeDir = path8.dirname(registryPath);
|
|
13218
|
+
if (!fs8.existsSync(runtimeDir)) fs8.mkdirSync(runtimeDir, { recursive: true });
|
|
13219
|
+
fs8.writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf8");
|
|
13160
13220
|
}
|
|
13161
13221
|
function runtimeDiscussionsDir(worktree) {
|
|
13162
|
-
return
|
|
13222
|
+
return path8.join(optimaLocalConfigDir(worktree), "runtime", "discussions");
|
|
13163
13223
|
}
|
|
13164
13224
|
function archivedRuntimeDiscussionsDir(worktree) {
|
|
13165
|
-
return
|
|
13225
|
+
return path8.join(runtimeDiscussionsDir(worktree), "archive");
|
|
13166
13226
|
}
|
|
13167
13227
|
function finalDiscussionsDir(worktree) {
|
|
13168
|
-
return
|
|
13228
|
+
return path8.join(optimaTasksDir(worktree), "discussions");
|
|
13169
13229
|
}
|
|
13170
13230
|
function nextDiscussionIdentity(worktree, title) {
|
|
13171
13231
|
const discussionsDir = finalDiscussionsDir(worktree);
|
|
13172
13232
|
const runtimeDir = runtimeDiscussionsDir(worktree);
|
|
13173
|
-
if (!
|
|
13174
|
-
if (!
|
|
13233
|
+
if (!fs8.existsSync(discussionsDir)) fs8.mkdirSync(discussionsDir, { recursive: true });
|
|
13234
|
+
if (!fs8.existsSync(runtimeDir)) fs8.mkdirSync(runtimeDir, { recursive: true });
|
|
13175
13235
|
let sequence = 1;
|
|
13176
13236
|
while (true) {
|
|
13177
13237
|
const id = `DISCUSSION-${String(sequence).padStart(3, "0")}`;
|
|
13178
13238
|
const filename = `${id}-${slugifyTitle(title)}.md`;
|
|
13179
|
-
const summaryRelativePath =
|
|
13180
|
-
const summaryAbsolutePath =
|
|
13239
|
+
const summaryRelativePath = path8.join(".optima", "tasks", "discussions", filename);
|
|
13240
|
+
const summaryAbsolutePath = path8.join(worktree, summaryRelativePath);
|
|
13181
13241
|
const transcriptFilename = `${id}-transcript.md`;
|
|
13182
|
-
const transcriptRelativePath =
|
|
13183
|
-
const transcriptAbsolutePath =
|
|
13184
|
-
if (!
|
|
13242
|
+
const transcriptRelativePath = path8.join(".optima", ".config", "runtime", "discussions", transcriptFilename);
|
|
13243
|
+
const transcriptAbsolutePath = path8.join(worktree, transcriptRelativePath);
|
|
13244
|
+
if (!fs8.existsSync(summaryAbsolutePath) && !fs8.existsSync(transcriptAbsolutePath)) {
|
|
13185
13245
|
return {
|
|
13186
13246
|
id,
|
|
13187
13247
|
filename,
|
|
@@ -13197,23 +13257,23 @@ function nextDiscussionIdentity(worktree, title) {
|
|
|
13197
13257
|
}
|
|
13198
13258
|
function findDiscussionById(worktree, discussionID) {
|
|
13199
13259
|
const discussionsDir = finalDiscussionsDir(worktree);
|
|
13200
|
-
if (!
|
|
13201
|
-
const entries =
|
|
13260
|
+
if (!fs8.existsSync(discussionsDir)) return null;
|
|
13261
|
+
const entries = fs8.readdirSync(discussionsDir).filter((name) => name.startsWith(`${discussionID}-`) && name.endsWith(".md"));
|
|
13202
13262
|
if (entries.length === 0) return null;
|
|
13203
13263
|
const filename = entries.sort()[0];
|
|
13204
13264
|
const transcriptFilename = `${discussionID}-transcript.md`;
|
|
13205
13265
|
return {
|
|
13206
13266
|
id: discussionID,
|
|
13207
13267
|
filename,
|
|
13208
|
-
summaryRelativePath:
|
|
13209
|
-
summaryAbsolutePath:
|
|
13268
|
+
summaryRelativePath: path8.join(".optima", "tasks", "discussions", filename),
|
|
13269
|
+
summaryAbsolutePath: path8.join(discussionsDir, filename),
|
|
13210
13270
|
transcriptFilename,
|
|
13211
|
-
transcriptRelativePath:
|
|
13212
|
-
transcriptAbsolutePath:
|
|
13271
|
+
transcriptRelativePath: path8.join(".optima", ".config", "runtime", "discussions", transcriptFilename),
|
|
13272
|
+
transcriptAbsolutePath: path8.join(runtimeDiscussionsDir(worktree), transcriptFilename)
|
|
13213
13273
|
};
|
|
13214
13274
|
}
|
|
13215
13275
|
function parseDiscussionFile(filePath) {
|
|
13216
|
-
const raw =
|
|
13276
|
+
const raw = fs8.readFileSync(filePath, "utf8");
|
|
13217
13277
|
const { data, body } = parseFrontmatter(raw);
|
|
13218
13278
|
return { data, body: body.trimStart() };
|
|
13219
13279
|
}
|
|
@@ -13224,10 +13284,10 @@ ${import_yaml3.default.stringify(frontmatter).trim()}
|
|
|
13224
13284
|
|
|
13225
13285
|
${body.trimEnd()}
|
|
13226
13286
|
`;
|
|
13227
|
-
|
|
13287
|
+
fs8.writeFileSync(filePath, serialized, "utf8");
|
|
13228
13288
|
}
|
|
13229
13289
|
function setDiscussionStatus(filePath, status) {
|
|
13230
|
-
if (!
|
|
13290
|
+
if (!fs8.existsSync(filePath)) return;
|
|
13231
13291
|
const { data, body } = parseDiscussionFile(filePath);
|
|
13232
13292
|
writeDiscussionFile(filePath, { ...data, status }, body);
|
|
13233
13293
|
}
|
|
@@ -13292,18 +13352,18 @@ async function appendMessageIfNeeded(client, worktree, registry, sessionID, mess
|
|
|
13292
13352
|
});
|
|
13293
13353
|
const text = extractTextParts(response.data.parts || []);
|
|
13294
13354
|
if (!text) return;
|
|
13295
|
-
appendDiscussionMessage(
|
|
13355
|
+
appendDiscussionMessage(path8.join(worktree, discussion.transcriptPath), speaker, text, messageID);
|
|
13296
13356
|
discussion.appendedMessageIDs ??= [];
|
|
13297
13357
|
discussion.appendedMessageIDs.push(messageID);
|
|
13298
13358
|
saveDiscussionRegistry(worktree, registry);
|
|
13299
13359
|
}
|
|
13300
13360
|
async function summarizeDiscussionWithBA(client, worktree, discussion) {
|
|
13301
|
-
const transcriptPath =
|
|
13302
|
-
const summaryPath =
|
|
13303
|
-
const summaryDir =
|
|
13304
|
-
if (!
|
|
13305
|
-
const hasExistingSummary =
|
|
13306
|
-
const priorMtimeMs = hasExistingSummary ?
|
|
13361
|
+
const transcriptPath = path8.join(worktree, discussion.transcriptPath);
|
|
13362
|
+
const summaryPath = path8.join(worktree, discussion.summaryPath);
|
|
13363
|
+
const summaryDir = path8.dirname(summaryPath);
|
|
13364
|
+
if (!fs8.existsSync(summaryDir)) fs8.mkdirSync(summaryDir, { recursive: true });
|
|
13365
|
+
const hasExistingSummary = fs8.existsSync(summaryPath);
|
|
13366
|
+
const priorMtimeMs = hasExistingSummary ? fs8.statSync(summaryPath).mtimeMs : null;
|
|
13307
13367
|
const summarizerSession = await client.session.create({
|
|
13308
13368
|
body: { title: `Discussion Summary: ${discussion.id}` }
|
|
13309
13369
|
});
|
|
@@ -13403,30 +13463,30 @@ async function summarizeDiscussionWithBA(client, worktree, discussion) {
|
|
|
13403
13463
|
return { confirmation, summaryPath, transcriptPath, hasExistingSummary, priorMtimeMs };
|
|
13404
13464
|
}
|
|
13405
13465
|
function archiveDiscussionTranscript(worktree, transcriptRelativePath) {
|
|
13406
|
-
const sourcePath =
|
|
13407
|
-
if (!
|
|
13466
|
+
const sourcePath = path8.join(worktree, transcriptRelativePath);
|
|
13467
|
+
if (!fs8.existsSync(sourcePath)) return null;
|
|
13408
13468
|
const archiveDir = archivedRuntimeDiscussionsDir(worktree);
|
|
13409
|
-
if (!
|
|
13410
|
-
const targetPath =
|
|
13411
|
-
|
|
13469
|
+
if (!fs8.existsSync(archiveDir)) fs8.mkdirSync(archiveDir, { recursive: true });
|
|
13470
|
+
const targetPath = path8.join(archiveDir, path8.basename(sourcePath));
|
|
13471
|
+
fs8.renameSync(sourcePath, targetPath);
|
|
13412
13472
|
return targetPath;
|
|
13413
13473
|
}
|
|
13414
13474
|
async function finalizeClosingDiscussion(client, worktree, registry, sessionID, discussion) {
|
|
13415
13475
|
const { confirmation, summaryPath, hasExistingSummary, priorMtimeMs } = await summarizeDiscussionWithBA(client, worktree, discussion);
|
|
13416
|
-
if (!
|
|
13476
|
+
if (!fs8.existsSync(summaryPath)) {
|
|
13417
13477
|
throw new Error(`Discussion summary was not written to ${discussion.summaryPath}`);
|
|
13418
13478
|
}
|
|
13419
13479
|
if (hasExistingSummary) {
|
|
13420
|
-
const currentMtimeMs =
|
|
13480
|
+
const currentMtimeMs = fs8.statSync(summaryPath).mtimeMs;
|
|
13421
13481
|
if (currentMtimeMs <= priorMtimeMs) {
|
|
13422
13482
|
throw new Error(`Discussion summary file was not updated at ${discussion.summaryPath}`);
|
|
13423
13483
|
}
|
|
13424
13484
|
}
|
|
13425
|
-
const summaryContent =
|
|
13485
|
+
const summaryContent = fs8.readFileSync(summaryPath, "utf8").trim();
|
|
13426
13486
|
if (!summaryContent) {
|
|
13427
13487
|
throw new Error(`Discussion summary file is empty at ${discussion.summaryPath}`);
|
|
13428
13488
|
}
|
|
13429
|
-
const transcriptPath =
|
|
13489
|
+
const transcriptPath = path8.join(worktree, discussion.transcriptPath);
|
|
13430
13490
|
setDiscussionStatus(transcriptPath, "closed");
|
|
13431
13491
|
const archivedTranscriptPath = archiveDiscussionTranscript(worktree, discussion.transcriptPath);
|
|
13432
13492
|
delete registry.active[sessionID];
|
|
@@ -13434,7 +13494,7 @@ async function finalizeClosingDiscussion(client, worktree, registry, sessionID,
|
|
|
13434
13494
|
return {
|
|
13435
13495
|
confirmation,
|
|
13436
13496
|
summaryPath: discussion.summaryPath,
|
|
13437
|
-
archivedTranscriptPath: archivedTranscriptPath ?
|
|
13497
|
+
archivedTranscriptPath: archivedTranscriptPath ? path8.relative(worktree, archivedTranscriptPath) : path8.join(".optima", ".config", "runtime", "discussions", "archive", path8.basename(discussion.transcriptPath))
|
|
13438
13498
|
};
|
|
13439
13499
|
}
|
|
13440
13500
|
function normalizeTeamMode(value) {
|
|
@@ -13473,13 +13533,13 @@ function getOperatingTeamMode(repoCfg) {
|
|
|
13473
13533
|
}
|
|
13474
13534
|
function readResolvedFile(relativePath, worktree, options = {}) {
|
|
13475
13535
|
const filePath = resolveIncludeFile(`plugin:${relativePath}`, worktree, PKG_ROOT, options);
|
|
13476
|
-
if (!filePath || !
|
|
13477
|
-
return resolveIncludes(
|
|
13536
|
+
if (!filePath || !fs8.existsSync(filePath)) return "";
|
|
13537
|
+
return resolveIncludes(fs8.readFileSync(filePath, "utf8"), worktree, PKG_ROOT, options).trim();
|
|
13478
13538
|
}
|
|
13479
13539
|
function loadMarkdownFragment(filePath, worktree) {
|
|
13480
|
-
if (!
|
|
13540
|
+
if (!fs8.existsSync(filePath)) return "";
|
|
13481
13541
|
try {
|
|
13482
|
-
const raw =
|
|
13542
|
+
const raw = fs8.readFileSync(filePath, "utf8");
|
|
13483
13543
|
const { body } = parseFrontmatter(raw);
|
|
13484
13544
|
return resolveIncludes(body.trim(), worktree, PKG_ROOT);
|
|
13485
13545
|
} catch (e) {
|
|
@@ -13488,9 +13548,9 @@ function loadMarkdownFragment(filePath, worktree) {
|
|
|
13488
13548
|
}
|
|
13489
13549
|
}
|
|
13490
13550
|
function loadAgentDefinition(filePath, worktree, options = {}) {
|
|
13491
|
-
if (!
|
|
13551
|
+
if (!fs8.existsSync(filePath)) return null;
|
|
13492
13552
|
try {
|
|
13493
|
-
const rawContent =
|
|
13553
|
+
const rawContent = fs8.readFileSync(filePath, "utf8");
|
|
13494
13554
|
const { data, body } = parseFrontmatter(rawContent);
|
|
13495
13555
|
const prompt = resolveIncludes(body.trim(), worktree, PKG_ROOT, options);
|
|
13496
13556
|
return { data, prompt };
|
|
@@ -13501,13 +13561,13 @@ function loadAgentDefinition(filePath, worktree, options = {}) {
|
|
|
13501
13561
|
}
|
|
13502
13562
|
function syncGeneratedPolicies(worktree, repoCfg) {
|
|
13503
13563
|
if (repoCfg.policies?.extract_defaults !== "all") return;
|
|
13504
|
-
if (!
|
|
13564
|
+
if (!fs8.existsSync(BUNDLE_POLICIES_DIR)) return;
|
|
13505
13565
|
const generatedDir = generatedPoliciesDir(worktree);
|
|
13506
|
-
if (!
|
|
13507
|
-
const policyFiles =
|
|
13566
|
+
if (!fs8.existsSync(generatedDir)) fs8.mkdirSync(generatedDir, { recursive: true });
|
|
13567
|
+
const policyFiles = fs8.readdirSync(BUNDLE_POLICIES_DIR).filter((file) => file.endsWith(".md") && file !== "README.md");
|
|
13508
13568
|
for (const file of policyFiles) {
|
|
13509
|
-
const sourcePath =
|
|
13510
|
-
const source =
|
|
13569
|
+
const sourcePath = path8.join(BUNDLE_POLICIES_DIR, file);
|
|
13570
|
+
const source = fs8.readFileSync(sourcePath, "utf8").trimEnd();
|
|
13511
13571
|
const generated = [
|
|
13512
13572
|
"<!--",
|
|
13513
13573
|
"Generated from Optima plugin defaults.",
|
|
@@ -13518,19 +13578,19 @@ function syncGeneratedPolicies(worktree, repoCfg) {
|
|
|
13518
13578
|
source,
|
|
13519
13579
|
""
|
|
13520
13580
|
].join("\n");
|
|
13521
|
-
|
|
13581
|
+
fs8.writeFileSync(path8.join(generatedDir, file), generated, "utf8");
|
|
13522
13582
|
}
|
|
13523
13583
|
}
|
|
13524
13584
|
function ensureReadmeFile(dirPath, content) {
|
|
13525
|
-
if (!
|
|
13526
|
-
const readmePath =
|
|
13527
|
-
if (!
|
|
13528
|
-
|
|
13585
|
+
if (!fs8.existsSync(dirPath)) fs8.mkdirSync(dirPath, { recursive: true });
|
|
13586
|
+
const readmePath = path8.join(dirPath, "README.md");
|
|
13587
|
+
if (!fs8.existsSync(readmePath)) {
|
|
13588
|
+
fs8.writeFileSync(readmePath, content, "utf8");
|
|
13529
13589
|
}
|
|
13530
13590
|
}
|
|
13531
13591
|
function ensureFileIfMissing(filePath, content) {
|
|
13532
|
-
if (!
|
|
13533
|
-
if (!
|
|
13592
|
+
if (!fs8.existsSync(path8.dirname(filePath))) fs8.mkdirSync(path8.dirname(filePath), { recursive: true });
|
|
13593
|
+
if (!fs8.existsSync(filePath)) fs8.writeFileSync(filePath, content, "utf8");
|
|
13534
13594
|
}
|
|
13535
13595
|
function currentTasksRegistryContent() {
|
|
13536
13596
|
return "# Current Tasks (Backlog)\n\n## \u{1F4AC} Active Discussions\n- (None)\n\n## \u{1F680} Active\n- (None)\n\n## \u{1F4CB} Todo\n- (None)\n\n## \u{1F6D1} Blocked\n- (None)\n";
|
|
@@ -13616,16 +13676,16 @@ Never place implementation evidence under root \`evidences/\`.
|
|
|
13616
13676
|
}
|
|
13617
13677
|
function ensureOptimaTaskTemplates(worktree) {
|
|
13618
13678
|
const tasksDir = optimaTasksDir(worktree);
|
|
13619
|
-
ensureFileIfMissing(
|
|
13620
|
-
ensureFileIfMissing(
|
|
13679
|
+
ensureFileIfMissing(path8.join(tasksDir, "task-template.md"), taskTemplateContent());
|
|
13680
|
+
ensureFileIfMissing(path8.join(tasksDir, "subtask-template.md"), subtaskTemplateContent());
|
|
13621
13681
|
}
|
|
13622
13682
|
function scaffoldOptimaConfig(worktree, teamMode = "full") {
|
|
13623
13683
|
const configPath = repoConfigPath(worktree);
|
|
13624
|
-
if (
|
|
13625
|
-
const templatePath =
|
|
13626
|
-
if (!
|
|
13627
|
-
const agentIds =
|
|
13628
|
-
let optimaConfig =
|
|
13684
|
+
if (fs8.existsSync(configPath)) return false;
|
|
13685
|
+
const templatePath = path8.join(TEMPLATES_DIR, "optima.yaml.template");
|
|
13686
|
+
if (!fs8.existsSync(templatePath)) return false;
|
|
13687
|
+
const agentIds = fs8.existsSync(BUNDLE_AGENTS_DIR) ? fs8.readdirSync(BUNDLE_AGENTS_DIR).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", "")) : [];
|
|
13688
|
+
let optimaConfig = fs8.readFileSync(templatePath, "utf8");
|
|
13629
13689
|
optimaConfig = optimaConfig.replace("{{teamMode}}", normalizeTeamMode(teamMode));
|
|
13630
13690
|
let agentsSection = "";
|
|
13631
13691
|
for (const id of agentIds) {
|
|
@@ -13636,26 +13696,26 @@ function scaffoldOptimaConfig(worktree, teamMode = "full") {
|
|
|
13636
13696
|
}
|
|
13637
13697
|
optimaConfig = optimaConfig.replace(/^agents:/m, "agents:\n" + agentsSection);
|
|
13638
13698
|
ensureFileIfMissing(configPath, optimaConfig);
|
|
13639
|
-
return
|
|
13699
|
+
return fs8.existsSync(configPath);
|
|
13640
13700
|
}
|
|
13641
13701
|
function scaffoldOptimaRootCodemap(worktree) {
|
|
13642
13702
|
const rootCodemapPath = optimaCodemapPath(worktree);
|
|
13643
|
-
if (
|
|
13644
|
-
const templatePath =
|
|
13645
|
-
if (!
|
|
13646
|
-
const codemapConfig =
|
|
13703
|
+
if (fs8.existsSync(rootCodemapPath)) return false;
|
|
13704
|
+
const templatePath = path8.join(TEMPLATES_DIR, "codemap.yml.template");
|
|
13705
|
+
if (!fs8.existsSync(templatePath)) return false;
|
|
13706
|
+
const codemapConfig = fs8.readFileSync(templatePath, "utf8").replace("{{projectName}}", path8.basename(worktree));
|
|
13647
13707
|
ensureFileIfMissing(rootCodemapPath, codemapConfig);
|
|
13648
|
-
return
|
|
13708
|
+
return fs8.existsSync(rootCodemapPath);
|
|
13649
13709
|
}
|
|
13650
13710
|
function ensureOptimaRegistries(worktree) {
|
|
13651
13711
|
const tasksDir = optimaTasksDir(worktree);
|
|
13652
13712
|
const scrsDir = optimaScrsDir(worktree);
|
|
13653
|
-
if (!
|
|
13654
|
-
if (!
|
|
13655
|
-
ensureFileIfMissing(
|
|
13656
|
-
ensureFileIfMissing(
|
|
13657
|
-
ensureFileIfMissing(
|
|
13658
|
-
ensureFileIfMissing(
|
|
13713
|
+
if (!fs8.existsSync(tasksDir)) fs8.mkdirSync(tasksDir, { recursive: true });
|
|
13714
|
+
if (!fs8.existsSync(scrsDir)) fs8.mkdirSync(scrsDir, { recursive: true });
|
|
13715
|
+
ensureFileIfMissing(path8.join(tasksDir, "current.md"), currentTasksRegistryContent());
|
|
13716
|
+
ensureFileIfMissing(path8.join(tasksDir, "done.md"), doneTasksRegistryContent());
|
|
13717
|
+
ensureFileIfMissing(path8.join(scrsDir, "current.md"), currentScrRegistryContent());
|
|
13718
|
+
ensureFileIfMissing(path8.join(scrsDir, "done.md"), doneScrRegistryContent());
|
|
13659
13719
|
}
|
|
13660
13720
|
function scaffoldOptimaReadmes(worktree) {
|
|
13661
13721
|
ensureReadmeFile(repoPoliciesDir(worktree), REPO_LOCAL_POLICIES_README);
|
|
@@ -13817,12 +13877,12 @@ function shouldRegisterWorkflowProductManager(options = {}, worktree = process.c
|
|
|
13817
13877
|
}
|
|
13818
13878
|
function isClickUpDerivedWorktreeSibling(candidate, basePath) {
|
|
13819
13879
|
if (typeof candidate !== "string" || typeof basePath !== "string" || !candidate.trim() || !basePath.trim()) return false;
|
|
13820
|
-
const resolvedCandidate =
|
|
13821
|
-
const resolvedBase =
|
|
13822
|
-
const baseParent =
|
|
13823
|
-
if (
|
|
13824
|
-
const baseName =
|
|
13825
|
-
const candidateName =
|
|
13880
|
+
const resolvedCandidate = path8.resolve(candidate);
|
|
13881
|
+
const resolvedBase = path8.resolve(basePath);
|
|
13882
|
+
const baseParent = path8.dirname(resolvedBase);
|
|
13883
|
+
if (path8.dirname(resolvedCandidate) !== baseParent) return false;
|
|
13884
|
+
const baseName = path8.basename(resolvedBase);
|
|
13885
|
+
const candidateName = path8.basename(resolvedCandidate);
|
|
13826
13886
|
if (!candidateName.startsWith(`${baseName}-`)) return false;
|
|
13827
13887
|
const branchSlug = candidateName.slice(baseName.length + 1);
|
|
13828
13888
|
const parts = branchSlug.split("-").filter(Boolean);
|
|
@@ -13830,11 +13890,11 @@ function isClickUpDerivedWorktreeSibling(candidate, basePath) {
|
|
|
13830
13890
|
if (!(/* @__PURE__ */ new Set(["tarea", "bug", "doc", "poc", "idea"])).has(parts[0])) return false;
|
|
13831
13891
|
if (!parts.slice(1).every((part) => /^[a-z0-9][a-z0-9-]*$/.test(part))) return false;
|
|
13832
13892
|
try {
|
|
13833
|
-
const candidateStat =
|
|
13893
|
+
const candidateStat = fs8.lstatSync(resolvedCandidate);
|
|
13834
13894
|
if (!candidateStat.isDirectory() || candidateStat.isSymbolicLink()) return false;
|
|
13835
|
-
const realCandidate =
|
|
13836
|
-
const realBaseParent =
|
|
13837
|
-
return
|
|
13895
|
+
const realCandidate = fs8.realpathSync.native(resolvedCandidate);
|
|
13896
|
+
const realBaseParent = fs8.realpathSync.native(baseParent);
|
|
13897
|
+
return path8.dirname(realCandidate) === realBaseParent && path8.basename(realCandidate) === candidateName;
|
|
13838
13898
|
} catch {
|
|
13839
13899
|
return false;
|
|
13840
13900
|
}
|
|
@@ -13842,15 +13902,15 @@ function isClickUpDerivedWorktreeSibling(candidate, basePath) {
|
|
|
13842
13902
|
function resolveSessionToolDirectory({ requestedDirectory, context, pluginWorktree, clickUpWebhookValidation } = {}) {
|
|
13843
13903
|
const safe = safeWorktreeOrFailure(context, pluginWorktree);
|
|
13844
13904
|
if (!safe.ok) return { ok: false, error: safe.message };
|
|
13845
|
-
const requested = String(requestedDirectory || "").trim() ?
|
|
13905
|
+
const requested = String(requestedDirectory || "").trim() ? path8.resolve(safe.worktree, String(requestedDirectory).trim()) : safe.worktree;
|
|
13846
13906
|
if (!isSafeWritableDirectory(requested)) return { ok: false, error: `Directory is not a safe writable directory: ${requested}` };
|
|
13847
13907
|
if (isSameOrNestedPath(requested, safe.worktree)) return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "context_worktree" };
|
|
13848
13908
|
const clickUpBasePath = clickUpWebhookValidation?.complete === true && clickUpWebhookValidation?.ok !== false ? clickUpWebhookValidation.config?.basePath : "";
|
|
13849
13909
|
if (clickUpBasePath && isSameOrNestedPath(requested, clickUpBasePath)) {
|
|
13850
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath:
|
|
13910
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath: path8.resolve(clickUpBasePath) };
|
|
13851
13911
|
}
|
|
13852
13912
|
if (clickUpBasePath && isClickUpDerivedWorktreeSibling(requested, clickUpBasePath)) {
|
|
13853
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath:
|
|
13913
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath: path8.resolve(clickUpBasePath) };
|
|
13854
13914
|
}
|
|
13855
13915
|
return {
|
|
13856
13916
|
ok: false,
|
|
@@ -13884,8 +13944,8 @@ function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, optio
|
|
|
13884
13944
|
if (!enabled) continue;
|
|
13885
13945
|
}
|
|
13886
13946
|
const promptOptions = { preferCompactPromptDocs: repoCfg.features?.compact_prompt_docs !== false };
|
|
13887
|
-
const bundledDefinition = loadAgentDefinition(
|
|
13888
|
-
const repoDefinition = loadAgentDefinition(
|
|
13947
|
+
const bundledDefinition = loadAgentDefinition(path8.join(BUNDLE_AGENTS_DIR, file), worktree, promptOptions);
|
|
13948
|
+
const repoDefinition = loadAgentDefinition(path8.join(repoAgentDefinitions, file), worktree, promptOptions) || loadAgentDefinition(path8.join(legacyAgentsDir, file), worktree, promptOptions);
|
|
13889
13949
|
const activeDefinition = repoDefinition || bundledDefinition;
|
|
13890
13950
|
if (!activeDefinition) continue;
|
|
13891
13951
|
const { data } = activeDefinition;
|
|
@@ -13894,7 +13954,7 @@ function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, optio
|
|
|
13894
13954
|
if (modePromptFragment) finalPrompt = `${finalPrompt}
|
|
13895
13955
|
|
|
13896
13956
|
${modePromptFragment}`;
|
|
13897
|
-
const additionFragment = loadMarkdownFragment(
|
|
13957
|
+
const additionFragment = loadMarkdownFragment(path8.join(repoAgentAdditions, file), worktree);
|
|
13898
13958
|
if (additionFragment) {
|
|
13899
13959
|
finalPrompt = `${finalPrompt}
|
|
13900
13960
|
|
|
@@ -13947,14 +14007,14 @@ Use this Optima-provided fallback when the current task worktree lacks docs/core
|
|
|
13947
14007
|
}
|
|
13948
14008
|
ourAgents[id] = agentConfig;
|
|
13949
14009
|
if (repoCfg.features?.debug_dumps !== false) {
|
|
13950
|
-
const debugPath =
|
|
14010
|
+
const debugPath = path8.join(debugDir, `${id}.md`);
|
|
13951
14011
|
const { prompt, ...dumpConfig } = agentConfig;
|
|
13952
14012
|
const debugHeader = `---
|
|
13953
14013
|
${import_yaml3.default.stringify(dumpConfig).trim()}
|
|
13954
14014
|
---`;
|
|
13955
14015
|
try {
|
|
13956
|
-
if (!
|
|
13957
|
-
|
|
14016
|
+
if (!fs8.existsSync(debugDir)) fs8.mkdirSync(debugDir, { recursive: true });
|
|
14017
|
+
fs8.writeFileSync(debugPath, `${debugHeader}
|
|
13958
14018
|
|
|
13959
14019
|
${prompt}`, "utf8");
|
|
13960
14020
|
} catch (e) {
|
|
@@ -13978,9 +14038,9 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
13978
14038
|
const configPath = resolveConfigPath(worktree);
|
|
13979
14039
|
const discussionRegistry = loadDiscussionRegistry(worktree);
|
|
13980
14040
|
let repoCfg = { agents: {}, defaults: {}, features: {} };
|
|
13981
|
-
if (
|
|
14041
|
+
if (fs8.existsSync(configPath)) {
|
|
13982
14042
|
try {
|
|
13983
|
-
repoCfg = import_yaml3.default.parse(
|
|
14043
|
+
repoCfg = import_yaml3.default.parse(fs8.readFileSync(configPath, "utf8")) || repoCfg;
|
|
13984
14044
|
} catch (e) {
|
|
13985
14045
|
console.error(`[Optima] Failed to parse config at ${configPath}:`, e);
|
|
13986
14046
|
}
|
|
@@ -14108,10 +14168,10 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
14108
14168
|
}
|
|
14109
14169
|
migrateLegacyOptimaLayout(toolWorktree);
|
|
14110
14170
|
const cfgDir = optimaConfigDir(toolWorktree);
|
|
14111
|
-
if (!
|
|
14112
|
-
const optimaTmplPath =
|
|
14113
|
-
const codemapTmplPath =
|
|
14114
|
-
if (!
|
|
14171
|
+
if (!fs8.existsSync(cfgDir)) fs8.mkdirSync(cfgDir, { recursive: true });
|
|
14172
|
+
const optimaTmplPath = path8.join(TEMPLATES_DIR, "optima.yaml.template");
|
|
14173
|
+
const codemapTmplPath = path8.join(TEMPLATES_DIR, "codemap.yml.template");
|
|
14174
|
+
if (!fs8.existsSync(optimaTmplPath) || !fs8.existsSync(codemapTmplPath)) {
|
|
14115
14175
|
return "Error: Initialization templates not found in plugin.";
|
|
14116
14176
|
}
|
|
14117
14177
|
scaffoldOptimaConfig(toolWorktree, requestedTeamMode);
|
|
@@ -14232,14 +14292,14 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14232
14292
|
async execute(args, context) {
|
|
14233
14293
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14234
14294
|
if (!safe.ok) return safe.message;
|
|
14235
|
-
const summaryPath =
|
|
14236
|
-
if (!
|
|
14237
|
-
const taskPath = args.task_path ?
|
|
14295
|
+
const summaryPath = path8.resolve(safe.worktree, args.summary_path || "");
|
|
14296
|
+
if (!fs8.existsSync(summaryPath)) return `FAIL: summary_path not found: ${summaryPath}`;
|
|
14297
|
+
const taskPath = args.task_path ? path8.resolve(safe.worktree, args.task_path) : "";
|
|
14238
14298
|
const payload = buildClickUpSummaryPayload({
|
|
14239
|
-
summaryMarkdown:
|
|
14240
|
-
summaryPath:
|
|
14241
|
-
taskMarkdown: taskPath &&
|
|
14242
|
-
taskPath: taskPath ?
|
|
14299
|
+
summaryMarkdown: fs8.readFileSync(summaryPath, "utf8"),
|
|
14300
|
+
summaryPath: path8.relative(safe.worktree, summaryPath),
|
|
14301
|
+
taskMarkdown: taskPath && fs8.existsSync(taskPath) ? fs8.readFileSync(taskPath, "utf8") : "",
|
|
14302
|
+
taskPath: taskPath ? path8.relative(safe.worktree, taskPath) : "",
|
|
14243
14303
|
branch: args.branch,
|
|
14244
14304
|
worktree: args.worktree,
|
|
14245
14305
|
pr: args.pr
|
|
@@ -14318,12 +14378,12 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14318
14378
|
async execute(args, context) {
|
|
14319
14379
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14320
14380
|
if (!safe.ok) return safe.message;
|
|
14321
|
-
const markdownPath =
|
|
14322
|
-
if (!
|
|
14381
|
+
const markdownPath = path8.resolve(safe.worktree, args.markdown_path || "");
|
|
14382
|
+
if (!fs8.existsSync(markdownPath)) return `FAIL: markdown_path not found: ${markdownPath}`;
|
|
14323
14383
|
const payload = buildClickUpCreateSubtasksPayload({
|
|
14324
14384
|
parentTaskId: args.parent_task_id,
|
|
14325
|
-
markdown:
|
|
14326
|
-
sourcePath:
|
|
14385
|
+
markdown: fs8.readFileSync(markdownPath, "utf8"),
|
|
14386
|
+
sourcePath: path8.relative(safe.worktree, markdownPath),
|
|
14327
14387
|
parentBranch: args.parent_branch,
|
|
14328
14388
|
parentTaskType: args.parent_task_type || "Tarea",
|
|
14329
14389
|
apply: String(args.apply || "").toLowerCase() === "true"
|
|
@@ -14693,7 +14753,7 @@ Backfilled messages: ${backfilled}`;
|
|
|
14693
14753
|
if (!existing) {
|
|
14694
14754
|
return "FAIL: No active discussion exists for this session.";
|
|
14695
14755
|
}
|
|
14696
|
-
const discussionPath =
|
|
14756
|
+
const discussionPath = path8.join(toolWorktree, existing.transcriptPath);
|
|
14697
14757
|
setDiscussionStatus(discussionPath, "summarizing");
|
|
14698
14758
|
existing.status = "summarizing";
|
|
14699
14759
|
saveDiscussionRegistry(toolWorktree, toolRegistry);
|
|
@@ -14745,7 +14805,7 @@ Reason: ${err.message}`;
|
|
|
14745
14805
|
try {
|
|
14746
14806
|
const sessionResult = await client.session.create({
|
|
14747
14807
|
query: { directory: workflowDirectory },
|
|
14748
|
-
body: { title: `Workflow Run: ${
|
|
14808
|
+
body: { title: `Workflow Run: ${path8.basename(workflowTaskPath)}` }
|
|
14749
14809
|
});
|
|
14750
14810
|
const sessionId = sessionResult.data.id;
|
|
14751
14811
|
activeWorkflows.set(sessionId, { pmaSessionId, taskPath: workflowTaskPath, track: workflowTrack, directory: workflowDirectory });
|