@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/sanitize_cli.js
CHANGED
|
@@ -109,17 +109,17 @@ var require_visit = __commonJS({
|
|
|
109
109
|
visit.BREAK = BREAK;
|
|
110
110
|
visit.SKIP = SKIP;
|
|
111
111
|
visit.REMOVE = REMOVE;
|
|
112
|
-
function visit_(key, node, visitor,
|
|
113
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
112
|
+
function visit_(key, node, visitor, path10) {
|
|
113
|
+
const ctrl = callVisitor(key, node, visitor, path10);
|
|
114
114
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
115
|
-
replaceNode(key,
|
|
116
|
-
return visit_(key, ctrl, visitor,
|
|
115
|
+
replaceNode(key, path10, ctrl);
|
|
116
|
+
return visit_(key, ctrl, visitor, path10);
|
|
117
117
|
}
|
|
118
118
|
if (typeof ctrl !== "symbol") {
|
|
119
119
|
if (identity.isCollection(node)) {
|
|
120
|
-
|
|
120
|
+
path10 = Object.freeze(path10.concat(node));
|
|
121
121
|
for (let i = 0; i < node.items.length; ++i) {
|
|
122
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
122
|
+
const ci = visit_(i, node.items[i], visitor, path10);
|
|
123
123
|
if (typeof ci === "number")
|
|
124
124
|
i = ci - 1;
|
|
125
125
|
else if (ci === BREAK)
|
|
@@ -130,13 +130,13 @@ var require_visit = __commonJS({
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
} else if (identity.isPair(node)) {
|
|
133
|
-
|
|
134
|
-
const ck = visit_("key", node.key, visitor,
|
|
133
|
+
path10 = Object.freeze(path10.concat(node));
|
|
134
|
+
const ck = visit_("key", node.key, visitor, path10);
|
|
135
135
|
if (ck === BREAK)
|
|
136
136
|
return BREAK;
|
|
137
137
|
else if (ck === REMOVE)
|
|
138
138
|
node.key = null;
|
|
139
|
-
const cv = visit_("value", node.value, visitor,
|
|
139
|
+
const cv = visit_("value", node.value, visitor, path10);
|
|
140
140
|
if (cv === BREAK)
|
|
141
141
|
return BREAK;
|
|
142
142
|
else if (cv === REMOVE)
|
|
@@ -157,17 +157,17 @@ var require_visit = __commonJS({
|
|
|
157
157
|
visitAsync.BREAK = BREAK;
|
|
158
158
|
visitAsync.SKIP = SKIP;
|
|
159
159
|
visitAsync.REMOVE = REMOVE;
|
|
160
|
-
async function visitAsync_(key, node, visitor,
|
|
161
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
160
|
+
async function visitAsync_(key, node, visitor, path10) {
|
|
161
|
+
const ctrl = await callVisitor(key, node, visitor, path10);
|
|
162
162
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
163
|
-
replaceNode(key,
|
|
164
|
-
return visitAsync_(key, ctrl, visitor,
|
|
163
|
+
replaceNode(key, path10, ctrl);
|
|
164
|
+
return visitAsync_(key, ctrl, visitor, path10);
|
|
165
165
|
}
|
|
166
166
|
if (typeof ctrl !== "symbol") {
|
|
167
167
|
if (identity.isCollection(node)) {
|
|
168
|
-
|
|
168
|
+
path10 = Object.freeze(path10.concat(node));
|
|
169
169
|
for (let i = 0; i < node.items.length; ++i) {
|
|
170
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
170
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path10);
|
|
171
171
|
if (typeof ci === "number")
|
|
172
172
|
i = ci - 1;
|
|
173
173
|
else if (ci === BREAK)
|
|
@@ -178,13 +178,13 @@ var require_visit = __commonJS({
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
} else if (identity.isPair(node)) {
|
|
181
|
-
|
|
182
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
181
|
+
path10 = Object.freeze(path10.concat(node));
|
|
182
|
+
const ck = await visitAsync_("key", node.key, visitor, path10);
|
|
183
183
|
if (ck === BREAK)
|
|
184
184
|
return BREAK;
|
|
185
185
|
else if (ck === REMOVE)
|
|
186
186
|
node.key = null;
|
|
187
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
187
|
+
const cv = await visitAsync_("value", node.value, visitor, path10);
|
|
188
188
|
if (cv === BREAK)
|
|
189
189
|
return BREAK;
|
|
190
190
|
else if (cv === REMOVE)
|
|
@@ -211,23 +211,23 @@ var require_visit = __commonJS({
|
|
|
211
211
|
}
|
|
212
212
|
return visitor;
|
|
213
213
|
}
|
|
214
|
-
function callVisitor(key, node, visitor,
|
|
214
|
+
function callVisitor(key, node, visitor, path10) {
|
|
215
215
|
if (typeof visitor === "function")
|
|
216
|
-
return visitor(key, node,
|
|
216
|
+
return visitor(key, node, path10);
|
|
217
217
|
if (identity.isMap(node))
|
|
218
|
-
return visitor.Map?.(key, node,
|
|
218
|
+
return visitor.Map?.(key, node, path10);
|
|
219
219
|
if (identity.isSeq(node))
|
|
220
|
-
return visitor.Seq?.(key, node,
|
|
220
|
+
return visitor.Seq?.(key, node, path10);
|
|
221
221
|
if (identity.isPair(node))
|
|
222
|
-
return visitor.Pair?.(key, node,
|
|
222
|
+
return visitor.Pair?.(key, node, path10);
|
|
223
223
|
if (identity.isScalar(node))
|
|
224
|
-
return visitor.Scalar?.(key, node,
|
|
224
|
+
return visitor.Scalar?.(key, node, path10);
|
|
225
225
|
if (identity.isAlias(node))
|
|
226
|
-
return visitor.Alias?.(key, node,
|
|
226
|
+
return visitor.Alias?.(key, node, path10);
|
|
227
227
|
return void 0;
|
|
228
228
|
}
|
|
229
|
-
function replaceNode(key,
|
|
230
|
-
const parent =
|
|
229
|
+
function replaceNode(key, path10, node) {
|
|
230
|
+
const parent = path10[path10.length - 1];
|
|
231
231
|
if (identity.isCollection(parent)) {
|
|
232
232
|
parent.items[key] = node;
|
|
233
233
|
} else if (identity.isPair(parent)) {
|
|
@@ -835,10 +835,10 @@ var require_Collection = __commonJS({
|
|
|
835
835
|
var createNode = require_createNode();
|
|
836
836
|
var identity = require_identity();
|
|
837
837
|
var Node = require_Node();
|
|
838
|
-
function collectionFromPath(schema,
|
|
838
|
+
function collectionFromPath(schema, path10, value) {
|
|
839
839
|
let v = value;
|
|
840
|
-
for (let i =
|
|
841
|
-
const k =
|
|
840
|
+
for (let i = path10.length - 1; i >= 0; --i) {
|
|
841
|
+
const k = path10[i];
|
|
842
842
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
843
843
|
const a = [];
|
|
844
844
|
a[k] = v;
|
|
@@ -857,7 +857,7 @@ var require_Collection = __commonJS({
|
|
|
857
857
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
858
858
|
});
|
|
859
859
|
}
|
|
860
|
-
var isEmptyPath = (
|
|
860
|
+
var isEmptyPath = (path10) => path10 == null || typeof path10 === "object" && !!path10[Symbol.iterator]().next().done;
|
|
861
861
|
var Collection = class extends Node.NodeBase {
|
|
862
862
|
constructor(type, schema) {
|
|
863
863
|
super(type);
|
|
@@ -887,11 +887,11 @@ var require_Collection = __commonJS({
|
|
|
887
887
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
888
888
|
* that already exists in the map.
|
|
889
889
|
*/
|
|
890
|
-
addIn(
|
|
891
|
-
if (isEmptyPath(
|
|
890
|
+
addIn(path10, value) {
|
|
891
|
+
if (isEmptyPath(path10))
|
|
892
892
|
this.add(value);
|
|
893
893
|
else {
|
|
894
|
-
const [key, ...rest] =
|
|
894
|
+
const [key, ...rest] = path10;
|
|
895
895
|
const node = this.get(key, true);
|
|
896
896
|
if (identity.isCollection(node))
|
|
897
897
|
node.addIn(rest, value);
|
|
@@ -905,8 +905,8 @@ var require_Collection = __commonJS({
|
|
|
905
905
|
* Removes a value from the collection.
|
|
906
906
|
* @returns `true` if the item was found and removed.
|
|
907
907
|
*/
|
|
908
|
-
deleteIn(
|
|
909
|
-
const [key, ...rest] =
|
|
908
|
+
deleteIn(path10) {
|
|
909
|
+
const [key, ...rest] = path10;
|
|
910
910
|
if (rest.length === 0)
|
|
911
911
|
return this.delete(key);
|
|
912
912
|
const node = this.get(key, true);
|
|
@@ -920,8 +920,8 @@ var require_Collection = __commonJS({
|
|
|
920
920
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
921
921
|
* `true` (collections are always returned intact).
|
|
922
922
|
*/
|
|
923
|
-
getIn(
|
|
924
|
-
const [key, ...rest] =
|
|
923
|
+
getIn(path10, keepScalar) {
|
|
924
|
+
const [key, ...rest] = path10;
|
|
925
925
|
const node = this.get(key, true);
|
|
926
926
|
if (rest.length === 0)
|
|
927
927
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -939,8 +939,8 @@ var require_Collection = __commonJS({
|
|
|
939
939
|
/**
|
|
940
940
|
* Checks if the collection includes a value with the key `key`.
|
|
941
941
|
*/
|
|
942
|
-
hasIn(
|
|
943
|
-
const [key, ...rest] =
|
|
942
|
+
hasIn(path10) {
|
|
943
|
+
const [key, ...rest] = path10;
|
|
944
944
|
if (rest.length === 0)
|
|
945
945
|
return this.has(key);
|
|
946
946
|
const node = this.get(key, true);
|
|
@@ -950,8 +950,8 @@ var require_Collection = __commonJS({
|
|
|
950
950
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
951
951
|
* boolean to add/remove the item from the set.
|
|
952
952
|
*/
|
|
953
|
-
setIn(
|
|
954
|
-
const [key, ...rest] =
|
|
953
|
+
setIn(path10, value) {
|
|
954
|
+
const [key, ...rest] = path10;
|
|
955
955
|
if (rest.length === 0) {
|
|
956
956
|
this.set(key, value);
|
|
957
957
|
} else {
|
|
@@ -3455,9 +3455,9 @@ var require_Document = __commonJS({
|
|
|
3455
3455
|
this.contents.add(value);
|
|
3456
3456
|
}
|
|
3457
3457
|
/** Adds a value to the document. */
|
|
3458
|
-
addIn(
|
|
3458
|
+
addIn(path10, value) {
|
|
3459
3459
|
if (assertCollection(this.contents))
|
|
3460
|
-
this.contents.addIn(
|
|
3460
|
+
this.contents.addIn(path10, value);
|
|
3461
3461
|
}
|
|
3462
3462
|
/**
|
|
3463
3463
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -3532,14 +3532,14 @@ var require_Document = __commonJS({
|
|
|
3532
3532
|
* Removes a value from the document.
|
|
3533
3533
|
* @returns `true` if the item was found and removed.
|
|
3534
3534
|
*/
|
|
3535
|
-
deleteIn(
|
|
3536
|
-
if (Collection.isEmptyPath(
|
|
3535
|
+
deleteIn(path10) {
|
|
3536
|
+
if (Collection.isEmptyPath(path10)) {
|
|
3537
3537
|
if (this.contents == null)
|
|
3538
3538
|
return false;
|
|
3539
3539
|
this.contents = null;
|
|
3540
3540
|
return true;
|
|
3541
3541
|
}
|
|
3542
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3542
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path10) : false;
|
|
3543
3543
|
}
|
|
3544
3544
|
/**
|
|
3545
3545
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -3554,10 +3554,10 @@ var require_Document = __commonJS({
|
|
|
3554
3554
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
3555
3555
|
* `true` (collections are always returned intact).
|
|
3556
3556
|
*/
|
|
3557
|
-
getIn(
|
|
3558
|
-
if (Collection.isEmptyPath(
|
|
3557
|
+
getIn(path10, keepScalar) {
|
|
3558
|
+
if (Collection.isEmptyPath(path10))
|
|
3559
3559
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3560
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3560
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path10, keepScalar) : void 0;
|
|
3561
3561
|
}
|
|
3562
3562
|
/**
|
|
3563
3563
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -3568,10 +3568,10 @@ var require_Document = __commonJS({
|
|
|
3568
3568
|
/**
|
|
3569
3569
|
* Checks if the document includes a value at `path`.
|
|
3570
3570
|
*/
|
|
3571
|
-
hasIn(
|
|
3572
|
-
if (Collection.isEmptyPath(
|
|
3571
|
+
hasIn(path10) {
|
|
3572
|
+
if (Collection.isEmptyPath(path10))
|
|
3573
3573
|
return this.contents !== void 0;
|
|
3574
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3574
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path10) : false;
|
|
3575
3575
|
}
|
|
3576
3576
|
/**
|
|
3577
3577
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -3588,13 +3588,13 @@ var require_Document = __commonJS({
|
|
|
3588
3588
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
3589
3589
|
* boolean to add/remove the item from the set.
|
|
3590
3590
|
*/
|
|
3591
|
-
setIn(
|
|
3592
|
-
if (Collection.isEmptyPath(
|
|
3591
|
+
setIn(path10, value) {
|
|
3592
|
+
if (Collection.isEmptyPath(path10)) {
|
|
3593
3593
|
this.contents = value;
|
|
3594
3594
|
} else if (this.contents == null) {
|
|
3595
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3595
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path10), value);
|
|
3596
3596
|
} else if (assertCollection(this.contents)) {
|
|
3597
|
-
this.contents.setIn(
|
|
3597
|
+
this.contents.setIn(path10, value);
|
|
3598
3598
|
}
|
|
3599
3599
|
}
|
|
3600
3600
|
/**
|
|
@@ -5546,9 +5546,9 @@ var require_cst_visit = __commonJS({
|
|
|
5546
5546
|
visit.BREAK = BREAK;
|
|
5547
5547
|
visit.SKIP = SKIP;
|
|
5548
5548
|
visit.REMOVE = REMOVE;
|
|
5549
|
-
visit.itemAtPath = (cst,
|
|
5549
|
+
visit.itemAtPath = (cst, path10) => {
|
|
5550
5550
|
let item = cst;
|
|
5551
|
-
for (const [field, index] of
|
|
5551
|
+
for (const [field, index] of path10) {
|
|
5552
5552
|
const tok = item?.[field];
|
|
5553
5553
|
if (tok && "items" in tok) {
|
|
5554
5554
|
item = tok.items[index];
|
|
@@ -5557,23 +5557,23 @@ var require_cst_visit = __commonJS({
|
|
|
5557
5557
|
}
|
|
5558
5558
|
return item;
|
|
5559
5559
|
};
|
|
5560
|
-
visit.parentCollection = (cst,
|
|
5561
|
-
const parent = visit.itemAtPath(cst,
|
|
5562
|
-
const field =
|
|
5560
|
+
visit.parentCollection = (cst, path10) => {
|
|
5561
|
+
const parent = visit.itemAtPath(cst, path10.slice(0, -1));
|
|
5562
|
+
const field = path10[path10.length - 1][0];
|
|
5563
5563
|
const coll = parent?.[field];
|
|
5564
5564
|
if (coll && "items" in coll)
|
|
5565
5565
|
return coll;
|
|
5566
5566
|
throw new Error("Parent collection not found");
|
|
5567
5567
|
};
|
|
5568
|
-
function _visit(
|
|
5569
|
-
let ctrl = visitor(item,
|
|
5568
|
+
function _visit(path10, item, visitor) {
|
|
5569
|
+
let ctrl = visitor(item, path10);
|
|
5570
5570
|
if (typeof ctrl === "symbol")
|
|
5571
5571
|
return ctrl;
|
|
5572
5572
|
for (const field of ["key", "value"]) {
|
|
5573
5573
|
const token = item[field];
|
|
5574
5574
|
if (token && "items" in token) {
|
|
5575
5575
|
for (let i = 0; i < token.items.length; ++i) {
|
|
5576
|
-
const ci = _visit(Object.freeze(
|
|
5576
|
+
const ci = _visit(Object.freeze(path10.concat([[field, i]])), token.items[i], visitor);
|
|
5577
5577
|
if (typeof ci === "number")
|
|
5578
5578
|
i = ci - 1;
|
|
5579
5579
|
else if (ci === BREAK)
|
|
@@ -5584,10 +5584,10 @@ var require_cst_visit = __commonJS({
|
|
|
5584
5584
|
}
|
|
5585
5585
|
}
|
|
5586
5586
|
if (typeof ctrl === "function" && field === "key")
|
|
5587
|
-
ctrl = ctrl(item,
|
|
5587
|
+
ctrl = ctrl(item, path10);
|
|
5588
5588
|
}
|
|
5589
5589
|
}
|
|
5590
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5590
|
+
return typeof ctrl === "function" ? ctrl(item, path10) : ctrl;
|
|
5591
5591
|
}
|
|
5592
5592
|
exports.visit = visit;
|
|
5593
5593
|
}
|
|
@@ -6872,14 +6872,14 @@ var require_parser = __commonJS({
|
|
|
6872
6872
|
case "scalar":
|
|
6873
6873
|
case "single-quoted-scalar":
|
|
6874
6874
|
case "double-quoted-scalar": {
|
|
6875
|
-
const
|
|
6875
|
+
const fs10 = this.flowScalar(this.type);
|
|
6876
6876
|
if (atNextItem || it.value) {
|
|
6877
|
-
map.items.push({ start, key:
|
|
6877
|
+
map.items.push({ start, key: fs10, sep: [] });
|
|
6878
6878
|
this.onKeyLine = true;
|
|
6879
6879
|
} else if (it.sep) {
|
|
6880
|
-
this.stack.push(
|
|
6880
|
+
this.stack.push(fs10);
|
|
6881
6881
|
} else {
|
|
6882
|
-
Object.assign(it, { key:
|
|
6882
|
+
Object.assign(it, { key: fs10, sep: [] });
|
|
6883
6883
|
this.onKeyLine = true;
|
|
6884
6884
|
}
|
|
6885
6885
|
return;
|
|
@@ -7007,13 +7007,13 @@ var require_parser = __commonJS({
|
|
|
7007
7007
|
case "scalar":
|
|
7008
7008
|
case "single-quoted-scalar":
|
|
7009
7009
|
case "double-quoted-scalar": {
|
|
7010
|
-
const
|
|
7010
|
+
const fs10 = this.flowScalar(this.type);
|
|
7011
7011
|
if (!it || it.value)
|
|
7012
|
-
fc.items.push({ start: [], key:
|
|
7012
|
+
fc.items.push({ start: [], key: fs10, sep: [] });
|
|
7013
7013
|
else if (it.sep)
|
|
7014
|
-
this.stack.push(
|
|
7014
|
+
this.stack.push(fs10);
|
|
7015
7015
|
else
|
|
7016
|
-
Object.assign(it, { key:
|
|
7016
|
+
Object.assign(it, { key: fs10, sep: [] });
|
|
7017
7017
|
return;
|
|
7018
7018
|
}
|
|
7019
7019
|
case "flow-map-end":
|
|
@@ -7551,17 +7551,17 @@ var require_ignore = __commonJS({
|
|
|
7551
7551
|
var throwError = (message, Ctor) => {
|
|
7552
7552
|
throw new Ctor(message);
|
|
7553
7553
|
};
|
|
7554
|
-
var checkPath = (
|
|
7555
|
-
if (!isString(
|
|
7554
|
+
var checkPath = (path10, originalPath, doThrow) => {
|
|
7555
|
+
if (!isString(path10)) {
|
|
7556
7556
|
return doThrow(
|
|
7557
7557
|
`path must be a string, but got \`${originalPath}\``,
|
|
7558
7558
|
TypeError
|
|
7559
7559
|
);
|
|
7560
7560
|
}
|
|
7561
|
-
if (!
|
|
7561
|
+
if (!path10) {
|
|
7562
7562
|
return doThrow(`path must not be empty`, TypeError);
|
|
7563
7563
|
}
|
|
7564
|
-
if (checkPath.isNotRelative(
|
|
7564
|
+
if (checkPath.isNotRelative(path10)) {
|
|
7565
7565
|
const r = "`path.relative()`d";
|
|
7566
7566
|
return doThrow(
|
|
7567
7567
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -7570,7 +7570,7 @@ var require_ignore = __commonJS({
|
|
|
7570
7570
|
}
|
|
7571
7571
|
return true;
|
|
7572
7572
|
};
|
|
7573
|
-
var isNotRelative = (
|
|
7573
|
+
var isNotRelative = (path10) => REGEX_TEST_INVALID_PATH.test(path10);
|
|
7574
7574
|
checkPath.isNotRelative = isNotRelative;
|
|
7575
7575
|
checkPath.convert = (p) => p;
|
|
7576
7576
|
var Ignore = class {
|
|
@@ -7629,7 +7629,7 @@ var require_ignore = __commonJS({
|
|
|
7629
7629
|
// setting `checkUnignored` to `false` could reduce additional
|
|
7630
7630
|
// path matching.
|
|
7631
7631
|
// @returns {TestResult} true if a file is ignored
|
|
7632
|
-
_testOne(
|
|
7632
|
+
_testOne(path10, checkUnignored) {
|
|
7633
7633
|
let ignored = false;
|
|
7634
7634
|
let unignored = false;
|
|
7635
7635
|
this._rules.forEach((rule) => {
|
|
@@ -7637,7 +7637,7 @@ var require_ignore = __commonJS({
|
|
|
7637
7637
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
7638
7638
|
return;
|
|
7639
7639
|
}
|
|
7640
|
-
const matched = rule.regex.test(
|
|
7640
|
+
const matched = rule.regex.test(path10);
|
|
7641
7641
|
if (matched) {
|
|
7642
7642
|
ignored = !negative;
|
|
7643
7643
|
unignored = negative;
|
|
@@ -7650,24 +7650,24 @@ var require_ignore = __commonJS({
|
|
|
7650
7650
|
}
|
|
7651
7651
|
// @returns {TestResult}
|
|
7652
7652
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
7653
|
-
const
|
|
7653
|
+
const path10 = originalPath && checkPath.convert(originalPath);
|
|
7654
7654
|
checkPath(
|
|
7655
|
-
|
|
7655
|
+
path10,
|
|
7656
7656
|
originalPath,
|
|
7657
7657
|
this._allowRelativePaths ? RETURN_FALSE : throwError
|
|
7658
7658
|
);
|
|
7659
|
-
return this._t(
|
|
7659
|
+
return this._t(path10, cache, checkUnignored, slices);
|
|
7660
7660
|
}
|
|
7661
|
-
_t(
|
|
7662
|
-
if (
|
|
7663
|
-
return cache[
|
|
7661
|
+
_t(path10, cache, checkUnignored, slices) {
|
|
7662
|
+
if (path10 in cache) {
|
|
7663
|
+
return cache[path10];
|
|
7664
7664
|
}
|
|
7665
7665
|
if (!slices) {
|
|
7666
|
-
slices =
|
|
7666
|
+
slices = path10.split(SLASH);
|
|
7667
7667
|
}
|
|
7668
7668
|
slices.pop();
|
|
7669
7669
|
if (!slices.length) {
|
|
7670
|
-
return cache[
|
|
7670
|
+
return cache[path10] = this._testOne(path10, checkUnignored);
|
|
7671
7671
|
}
|
|
7672
7672
|
const parent = this._t(
|
|
7673
7673
|
slices.join(SLASH) + SLASH,
|
|
@@ -7675,24 +7675,24 @@ var require_ignore = __commonJS({
|
|
|
7675
7675
|
checkUnignored,
|
|
7676
7676
|
slices
|
|
7677
7677
|
);
|
|
7678
|
-
return cache[
|
|
7678
|
+
return cache[path10] = parent.ignored ? parent : this._testOne(path10, checkUnignored);
|
|
7679
7679
|
}
|
|
7680
|
-
ignores(
|
|
7681
|
-
return this._test(
|
|
7680
|
+
ignores(path10) {
|
|
7681
|
+
return this._test(path10, this._ignoreCache, false).ignored;
|
|
7682
7682
|
}
|
|
7683
7683
|
createFilter() {
|
|
7684
|
-
return (
|
|
7684
|
+
return (path10) => !this.ignores(path10);
|
|
7685
7685
|
}
|
|
7686
7686
|
filter(paths) {
|
|
7687
7687
|
return makeArray(paths).filter(this.createFilter());
|
|
7688
7688
|
}
|
|
7689
7689
|
// @returns {TestResult}
|
|
7690
|
-
test(
|
|
7691
|
-
return this._test(
|
|
7690
|
+
test(path10) {
|
|
7691
|
+
return this._test(path10, this._testCache, true);
|
|
7692
7692
|
}
|
|
7693
7693
|
};
|
|
7694
7694
|
var factory = (options) => new Ignore(options);
|
|
7695
|
-
var isPathValid = (
|
|
7695
|
+
var isPathValid = (path10) => checkPath(path10 && checkPath.convert(path10), path10, RETURN_FALSE);
|
|
7696
7696
|
factory.isPathValid = isPathValid;
|
|
7697
7697
|
factory.default = factory;
|
|
7698
7698
|
module.exports = factory;
|
|
@@ -7703,27 +7703,59 @@ var require_ignore = __commonJS({
|
|
|
7703
7703
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
7704
7704
|
checkPath.convert = makePosix;
|
|
7705
7705
|
const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
7706
|
-
checkPath.isNotRelative = (
|
|
7706
|
+
checkPath.isNotRelative = (path10) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path10) || isNotRelative(path10);
|
|
7707
7707
|
}
|
|
7708
7708
|
}
|
|
7709
7709
|
});
|
|
7710
7710
|
|
|
7711
7711
|
// src/sanitize_cli.js
|
|
7712
|
-
import
|
|
7713
|
-
import
|
|
7714
|
-
import
|
|
7712
|
+
import fs9 from "node:fs";
|
|
7713
|
+
import os3 from "node:os";
|
|
7714
|
+
import path9 from "node:path";
|
|
7715
7715
|
import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
|
|
7716
7716
|
|
|
7717
7717
|
// src/index.js
|
|
7718
7718
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
7719
7719
|
var import_ignore3 = __toESM(require_ignore(), 1);
|
|
7720
|
-
import
|
|
7721
|
-
import
|
|
7720
|
+
import crypto2 from "node:crypto";
|
|
7721
|
+
import fs8 from "node:fs";
|
|
7722
7722
|
import http from "node:http";
|
|
7723
|
-
import
|
|
7724
|
-
import
|
|
7723
|
+
import os2 from "node:os";
|
|
7724
|
+
import path8 from "node:path";
|
|
7725
7725
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
7726
7726
|
|
|
7727
|
+
// src/clickup_comments.js
|
|
7728
|
+
var COMMENT_TEXT_KEYS = /* @__PURE__ */ new Set(["comment", "body", "description"]);
|
|
7729
|
+
function normalizeClickUpMarkdown(value = "") {
|
|
7730
|
+
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();
|
|
7731
|
+
}
|
|
7732
|
+
function formatClickUpStatusComment({ title = "", summary = "", sections = [], context = [] } = {}) {
|
|
7733
|
+
const lines = [];
|
|
7734
|
+
const heading = normalizeClickUpMarkdown(title);
|
|
7735
|
+
if (heading) lines.push(`## ${heading}`, "");
|
|
7736
|
+
const intro = normalizeClickUpMarkdown(summary);
|
|
7737
|
+
if (intro) lines.push(intro, "");
|
|
7738
|
+
for (const section of sections) {
|
|
7739
|
+
const sectionTitle = normalizeClickUpMarkdown(section?.title);
|
|
7740
|
+
const sectionBody = normalizeClickUpMarkdown(section?.body);
|
|
7741
|
+
if (!sectionTitle || !sectionBody) continue;
|
|
7742
|
+
lines.push(`### ${sectionTitle}`, sectionBody, "");
|
|
7743
|
+
}
|
|
7744
|
+
const contextLines = context.map(normalizeClickUpMarkdown).filter(Boolean);
|
|
7745
|
+
if (contextLines.length > 0) lines.push("### Context", contextLines.map((item) => item.startsWith("- ") ? item : `- ${item}`).join("\n"));
|
|
7746
|
+
return normalizeClickUpMarkdown(lines.join("\n"));
|
|
7747
|
+
}
|
|
7748
|
+
function normalizeClickUpPayloadComments(value) {
|
|
7749
|
+
if (Array.isArray(value)) return value.map(normalizeClickUpPayloadComments);
|
|
7750
|
+
if (!value || typeof value !== "object") return value;
|
|
7751
|
+
return Object.fromEntries(
|
|
7752
|
+
Object.entries(value).map(([key, entry]) => [
|
|
7753
|
+
key,
|
|
7754
|
+
COMMENT_TEXT_KEYS.has(key) && typeof entry === "string" ? normalizeClickUpMarkdown(entry) : normalizeClickUpPayloadComments(entry)
|
|
7755
|
+
])
|
|
7756
|
+
);
|
|
7757
|
+
}
|
|
7758
|
+
|
|
7727
7759
|
// src/git_utils.js
|
|
7728
7760
|
import fs from "node:fs";
|
|
7729
7761
|
import path from "node:path";
|
|
@@ -7802,224 +7834,691 @@ function stageGitAwareMerge(sourcePath, destinationPath, gitState) {
|
|
|
7802
7834
|
}
|
|
7803
7835
|
}
|
|
7804
7836
|
|
|
7805
|
-
// src/
|
|
7837
|
+
// src/github.js
|
|
7838
|
+
import crypto from "node:crypto";
|
|
7839
|
+
import fs3 from "node:fs";
|
|
7840
|
+
import path3 from "node:path";
|
|
7841
|
+
|
|
7842
|
+
// src/secrets.js
|
|
7806
7843
|
import fs2 from "node:fs";
|
|
7844
|
+
import os from "node:os";
|
|
7807
7845
|
import path2 from "node:path";
|
|
7808
|
-
function
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
|
|
7814
|
-
const resolvedCandidate = path2.resolve(candidate);
|
|
7815
|
-
const resolvedRoot = path2.resolve(root);
|
|
7816
|
-
const relative = path2.relative(resolvedRoot, resolvedCandidate);
|
|
7817
|
-
return relative === "" || !!relative && !relative.startsWith("..") && !path2.isAbsolute(relative);
|
|
7818
|
-
}
|
|
7819
|
-
function resolveWithin(baseDir, relativePath) {
|
|
7820
|
-
if (!relativePath) return null;
|
|
7821
|
-
if (path2.isAbsolute(relativePath)) return null;
|
|
7822
|
-
const resolved = path2.resolve(baseDir, relativePath);
|
|
7823
|
-
return isSameOrNestedPath(resolved, baseDir) ? resolved : null;
|
|
7824
|
-
}
|
|
7825
|
-
function withCompactPreference(paths, options = {}) {
|
|
7826
|
-
if (!options.preferCompactPromptDocs) return paths;
|
|
7827
|
-
const compactPaths = paths.map((filePath) => filePath ? compactPromptPath(filePath) : null).filter(Boolean);
|
|
7828
|
-
return [...compactPaths, ...paths];
|
|
7846
|
+
function expandHomePath(value = "") {
|
|
7847
|
+
const input = String(value || "").trim();
|
|
7848
|
+
if (input === "~") return os.homedir();
|
|
7849
|
+
if (input.startsWith("~/")) return path2.join(os.homedir(), input.slice(2));
|
|
7850
|
+
return input;
|
|
7829
7851
|
}
|
|
7830
|
-
function
|
|
7831
|
-
const
|
|
7832
|
-
const
|
|
7833
|
-
|
|
7834
|
-
const
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7841
|
-
}
|
|
7842
|
-
return null;
|
|
7843
|
-
}
|
|
7844
|
-
if (scope === "repo") {
|
|
7845
|
-
for (const filePath of withCompactPreference([resolveWithin(optimaRoot, target)], options)) {
|
|
7846
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7847
|
-
}
|
|
7848
|
-
return null;
|
|
7849
|
-
}
|
|
7850
|
-
if (scope === "policy") {
|
|
7851
|
-
const candidates2 = withCompactPreference([
|
|
7852
|
-
resolveWithin(repoPolicyRoot, target),
|
|
7853
|
-
resolveWithin(bundlePolicyRoot, target)
|
|
7854
|
-
], options);
|
|
7855
|
-
for (const filePath of candidates2) {
|
|
7856
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7852
|
+
function resolveSecretReference(value = "") {
|
|
7853
|
+
const raw = String(value || "").trim();
|
|
7854
|
+
const envMatch = raw.match(/^\{env:([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
7855
|
+
if (envMatch) return process.env[envMatch[1]] || "";
|
|
7856
|
+
const fileMatch = raw.match(/^\{file:(.+)\}$/);
|
|
7857
|
+
if (fileMatch) {
|
|
7858
|
+
try {
|
|
7859
|
+
return fs2.readFileSync(expandHomePath(fileMatch[1]), "utf8").trim();
|
|
7860
|
+
} catch {
|
|
7861
|
+
return "";
|
|
7857
7862
|
}
|
|
7858
|
-
return null;
|
|
7859
|
-
}
|
|
7860
|
-
const candidates = withCompactPreference([
|
|
7861
|
-
resolveWithin(repoRoot, target),
|
|
7862
|
-
resolveWithin(bundleRoot, target)
|
|
7863
|
-
], options);
|
|
7864
|
-
for (const filePath of candidates) {
|
|
7865
|
-
if (filePath && fs2.existsSync(filePath)) return filePath;
|
|
7866
7863
|
}
|
|
7867
|
-
return
|
|
7864
|
+
return raw;
|
|
7868
7865
|
}
|
|
7869
|
-
function resolveIncludes(text, repoRoot, bundleRoot, options = {}) {
|
|
7870
|
-
const includeDepth = Number(options.includeDepth || 0);
|
|
7871
|
-
const maxIncludeDepth = Number(options.maxIncludeDepth || 20);
|
|
7872
|
-
if (includeDepth > maxIncludeDepth) {
|
|
7873
|
-
return "\n\n# ERROR: Include recursion limit exceeded.\n\n";
|
|
7874
|
-
}
|
|
7875
|
-
const includeRegex = /<include:(.*?)>/g;
|
|
7876
|
-
return String(text || "").replace(includeRegex, (match, includeRef) => {
|
|
7877
|
-
const filePath = resolveIncludeFile(includeRef, repoRoot, bundleRoot, options);
|
|
7878
|
-
if (!filePath) {
|
|
7879
|
-
console.warn(`[Optima] Include file not found: ${includeRef}`);
|
|
7880
|
-
return `
|
|
7881
7866
|
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
}
|
|
7886
|
-
const content = fs2.readFileSync(filePath, "utf8");
|
|
7887
|
-
return resolveIncludes(content, repoRoot, bundleRoot, { ...options, includeDepth: includeDepth + 1, maxIncludeDepth });
|
|
7888
|
-
});
|
|
7867
|
+
// src/github.js
|
|
7868
|
+
function base64UrlJson(value) {
|
|
7869
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
7889
7870
|
}
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
".jsx",
|
|
7901
|
-
".dart",
|
|
7902
|
-
".py",
|
|
7903
|
-
".go",
|
|
7904
|
-
".rs",
|
|
7905
|
-
".java",
|
|
7906
|
-
".c",
|
|
7907
|
-
".cpp",
|
|
7908
|
-
".cs",
|
|
7909
|
-
".php",
|
|
7910
|
-
".rb",
|
|
7911
|
-
".swift",
|
|
7912
|
-
".kt",
|
|
7913
|
-
".m",
|
|
7914
|
-
".sh",
|
|
7915
|
-
".sql",
|
|
7916
|
-
".yaml",
|
|
7917
|
-
".yml",
|
|
7918
|
-
".json",
|
|
7919
|
-
".md"
|
|
7920
|
-
]);
|
|
7921
|
-
var LEGACY_OPERATIONAL_ROOTS = /* @__PURE__ */ new Set([
|
|
7922
|
-
"tasks",
|
|
7923
|
-
"evidences",
|
|
7924
|
-
".orbita",
|
|
7925
|
-
".staticeng",
|
|
7926
|
-
".nomadworks",
|
|
7927
|
-
".nomadwork",
|
|
7928
|
-
"nomadworks",
|
|
7929
|
-
".codenomad"
|
|
7930
|
-
]);
|
|
7931
|
-
var OPTIMA_OPERATIONAL_FOLDERS = [".optima", "templates", "dist"];
|
|
7932
|
-
function relPath(worktree, targetPath) {
|
|
7933
|
-
return path3.relative(worktree, targetPath).split(path3.sep).join("/") || ".";
|
|
7871
|
+
function resolveGitHubAppPrivateKey(app = {}) {
|
|
7872
|
+
const direct = resolveSecretReference(app.privateKey || "");
|
|
7873
|
+
if (direct) return direct.replace(/\\n/g, "\n");
|
|
7874
|
+
const file = expandHomePath(app.privateKeyFile || "");
|
|
7875
|
+
if (!file) return "";
|
|
7876
|
+
try {
|
|
7877
|
+
return fs3.readFileSync(file, "utf8").trim().replace(/\\n/g, "\n");
|
|
7878
|
+
} catch {
|
|
7879
|
+
return "";
|
|
7880
|
+
}
|
|
7934
7881
|
}
|
|
7935
|
-
function
|
|
7936
|
-
|
|
7882
|
+
function createGitHubAppJwt({ appId, privateKey, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
7883
|
+
const key = String(privateKey || "").trim();
|
|
7884
|
+
const issuer = String(appId || "").trim();
|
|
7885
|
+
if (!issuer || !key) throw new Error("GitHub App app_id and private key are required");
|
|
7886
|
+
const nowSeconds = Math.floor(now().getTime() / 1e3);
|
|
7887
|
+
const header = base64UrlJson({ alg: "RS256", typ: "JWT" });
|
|
7888
|
+
const payload = base64UrlJson({ iat: nowSeconds - 60, exp: nowSeconds + 540, iss: issuer });
|
|
7889
|
+
const unsigned = `${header}.${payload}`;
|
|
7890
|
+
const signature = crypto.createSign("RSA-SHA256").update(unsigned).end().sign(key, "base64url");
|
|
7891
|
+
return `${unsigned}.${signature}`;
|
|
7937
7892
|
}
|
|
7938
|
-
function
|
|
7939
|
-
return
|
|
7893
|
+
function encodeGitHubPathSegment(value) {
|
|
7894
|
+
return encodeURIComponent(String(value || ""));
|
|
7940
7895
|
}
|
|
7941
|
-
function
|
|
7942
|
-
|
|
7943
|
-
const normalized = normalizeCodemapRelPath(relPathValue);
|
|
7944
|
-
const firstSegment = firstPathSegment(normalized);
|
|
7945
|
-
if (LEGACY_OPERATIONAL_ROOTS.has(firstSegment)) return true;
|
|
7946
|
-
return OPTIMA_OPERATIONAL_FOLDERS.some((folder) => normalized === folder || normalized.startsWith(`${folder}/`));
|
|
7896
|
+
function encodeGitHubBranchRef(branch = "") {
|
|
7897
|
+
return String(branch || "").split("/").map(encodeGitHubPathSegment).join("/");
|
|
7947
7898
|
}
|
|
7948
|
-
function
|
|
7949
|
-
|
|
7950
|
-
return normalizeCodemapRelPath(relPathValue).split("/").some((part) => part.startsWith("."));
|
|
7899
|
+
function parseNullSeparatedGitOutput(output = "") {
|
|
7900
|
+
return String(output || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
7951
7901
|
}
|
|
7952
|
-
function
|
|
7953
|
-
const
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
if (fs3.existsSync(gitignorePath)) ig.add(fs3.readFileSync(gitignorePath, "utf8"));
|
|
7957
|
-
return ig;
|
|
7902
|
+
function listWorktreeChangedPaths(worktree, runGitFn = runGit) {
|
|
7903
|
+
const tracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["diff", "--name-only", "-z", "HEAD", "--"]));
|
|
7904
|
+
const untracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["ls-files", "--others", "--exclude-standard", "-z", "--"]));
|
|
7905
|
+
return [.../* @__PURE__ */ new Set([...tracked, ...untracked])].filter((item) => item && !item.startsWith("../") && !path3.isAbsolute(item));
|
|
7958
7906
|
}
|
|
7959
|
-
function
|
|
7960
|
-
|
|
7961
|
-
if (value && typeof value === "object") return Object.values(value);
|
|
7962
|
-
return [];
|
|
7907
|
+
function currentGitBranch(worktree, runGitFn = runGit) {
|
|
7908
|
+
return String(runGitFn(worktree, ["rev-parse", "--abbrev-ref", "HEAD"]) || "").trim();
|
|
7963
7909
|
}
|
|
7964
|
-
function
|
|
7965
|
-
const
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7910
|
+
function treeEntryForWorktreePath(worktree, gitPath) {
|
|
7911
|
+
const absolutePath = path3.join(worktree, ...String(gitPath || "").split("/"));
|
|
7912
|
+
if (!fs3.existsSync(absolutePath)) return { path: gitPath, mode: "100644", type: "blob", sha: null, deleted: true };
|
|
7913
|
+
const stat = fs3.lstatSync(absolutePath);
|
|
7914
|
+
if (stat.isDirectory()) return null;
|
|
7915
|
+
if (stat.isSymbolicLink()) {
|
|
7916
|
+
return {
|
|
7917
|
+
path: gitPath,
|
|
7918
|
+
mode: "120000",
|
|
7919
|
+
type: "blob",
|
|
7920
|
+
content: fs3.readlinkSync(absolutePath),
|
|
7921
|
+
encoding: "utf-8"
|
|
7922
|
+
};
|
|
7970
7923
|
}
|
|
7971
|
-
return
|
|
7924
|
+
return {
|
|
7925
|
+
path: gitPath,
|
|
7926
|
+
mode: stat.mode & 73 ? "100755" : "100644",
|
|
7927
|
+
type: "blob",
|
|
7928
|
+
content: fs3.readFileSync(absolutePath).toString("base64"),
|
|
7929
|
+
encoding: "base64"
|
|
7930
|
+
};
|
|
7972
7931
|
}
|
|
7973
|
-
function
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
if (
|
|
7977
|
-
|
|
7978
|
-
return null;
|
|
7979
|
-
}
|
|
7980
|
-
return parsed;
|
|
7981
|
-
} catch {
|
|
7982
|
-
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap has invalid YAML; manual repair required." });
|
|
7983
|
-
return null;
|
|
7932
|
+
function appendGitHubQuery(pathname, params = {}) {
|
|
7933
|
+
const query = new URLSearchParams();
|
|
7934
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
7935
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
7936
|
+
query.set(key, String(value));
|
|
7984
7937
|
}
|
|
7938
|
+
const suffix = query.toString();
|
|
7939
|
+
return suffix ? `${pathname}?${suffix}` : pathname;
|
|
7985
7940
|
}
|
|
7986
|
-
function
|
|
7987
|
-
|
|
7941
|
+
function timestampMs(value = "") {
|
|
7942
|
+
const parsed = Date.parse(String(value || ""));
|
|
7943
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
7988
7944
|
}
|
|
7989
|
-
function
|
|
7990
|
-
|
|
7991
|
-
|
|
7945
|
+
function sortNewestFirst(items = []) {
|
|
7946
|
+
return [...items].sort((a, b) => {
|
|
7947
|
+
const bTime = Math.max(timestampMs(b?.updated_at), timestampMs(b?.created_at));
|
|
7948
|
+
const aTime = Math.max(timestampMs(a?.updated_at), timestampMs(a?.created_at));
|
|
7949
|
+
return bTime - aTime;
|
|
7950
|
+
});
|
|
7992
7951
|
}
|
|
7993
|
-
function
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
return
|
|
7952
|
+
function matchesVercelStatusContext(context = "", expected = "") {
|
|
7953
|
+
const actual = String(context || "").trim();
|
|
7954
|
+
const wanted = String(expected || "").trim();
|
|
7955
|
+
if (wanted && actual === wanted) return true;
|
|
7956
|
+
const lower = actual.toLowerCase();
|
|
7957
|
+
if (!lower.includes("vercel")) return false;
|
|
7958
|
+
if (!wanted) return lower.includes("preproduction") || lower.includes("defend-preproduction");
|
|
7959
|
+
const wantedLower = wanted.toLowerCase();
|
|
7960
|
+
return wantedLower.split(/\s+|–|-/).filter((part) => part && part !== "vercel").every((part) => lower.includes(part));
|
|
8002
7961
|
}
|
|
8003
|
-
function
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
7962
|
+
function selectFunctionalDeploymentUrl(status = {}, deployment = {}) {
|
|
7963
|
+
for (const candidate of [
|
|
7964
|
+
status?.environment_url,
|
|
7965
|
+
deployment?.environment_url,
|
|
7966
|
+
status?.target_url,
|
|
7967
|
+
deployment?.target_url
|
|
7968
|
+
]) {
|
|
7969
|
+
const value = String(candidate || "").trim();
|
|
7970
|
+
if (!value) continue;
|
|
7971
|
+
try {
|
|
7972
|
+
const url = new URL(value);
|
|
7973
|
+
if (url.protocol === "http:" || url.protocol === "https:") return value;
|
|
7974
|
+
} catch {
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
return "";
|
|
7978
|
+
}
|
|
7979
|
+
async function checkFunctionalDeploymentUrl(url, fetchImpl = globalThis.fetch) {
|
|
7980
|
+
const target = String(url || "").trim();
|
|
7981
|
+
if (!target) return { ok: false, reason: "url_missing" };
|
|
7982
|
+
if (typeof fetchImpl !== "function") return { ok: false, reason: "fetch_unavailable", url: target };
|
|
7983
|
+
const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(8e3) : void 0;
|
|
7984
|
+
const probe = async (method) => {
|
|
7985
|
+
const response = await fetchImpl(target, { method, redirect: "follow", signal });
|
|
7986
|
+
return {
|
|
7987
|
+
ok: response.status >= 200 && response.status < 400,
|
|
7988
|
+
status: response.status,
|
|
7989
|
+
statusText: response.statusText || "",
|
|
7990
|
+
url: response.url || target,
|
|
7991
|
+
method
|
|
7992
|
+
};
|
|
7993
|
+
};
|
|
7994
|
+
try {
|
|
7995
|
+
const head = await probe("HEAD");
|
|
7996
|
+
if (head.ok) return head;
|
|
7997
|
+
const get = await probe("GET");
|
|
7998
|
+
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
7999
|
+
} catch (error) {
|
|
8000
|
+
try {
|
|
8001
|
+
const get = await probe("GET");
|
|
8002
|
+
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
8003
|
+
} catch (secondError) {
|
|
8004
|
+
return { ok: false, reason: "request_failed", url: target, error: secondError.message || error.message };
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
8009
|
+
const staticToken = resolveSecretReference(config?.apiToken);
|
|
8010
|
+
const appConfig = typeof config?.app === "object" && config.app !== null ? config.app : {};
|
|
8011
|
+
const appId = resolveSecretReference(appConfig.appId || appConfig.app_id || "");
|
|
8012
|
+
const installationId = resolveSecretReference(appConfig.installationId || appConfig.installation_id || "");
|
|
8013
|
+
const appEnabled = appConfig.enabled === true || Boolean(appId && installationId && (appConfig.privateKey || appConfig.privateKeyFile));
|
|
8014
|
+
const privateKey = resolveGitHubAppPrivateKey(appConfig);
|
|
8015
|
+
const owner = String(config?.owner || "").trim();
|
|
8016
|
+
const repo = String(config?.repo || "").trim();
|
|
8017
|
+
let installationToken = null;
|
|
8018
|
+
let installationTokenExpiresAt = 0;
|
|
8019
|
+
const requestJson = async (url, { method = "GET", headers = {}, body = void 0 } = {}) => {
|
|
8020
|
+
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
|
|
8021
|
+
const response = await fetchImpl(url, {
|
|
8022
|
+
method,
|
|
8023
|
+
headers: {
|
|
8024
|
+
Accept: "application/vnd.github+json",
|
|
8025
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
8026
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
8027
|
+
...headers
|
|
8028
|
+
},
|
|
8029
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
8030
|
+
});
|
|
8031
|
+
if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
|
|
8032
|
+
return response.status === 204 ? null : response.json();
|
|
8033
|
+
};
|
|
8034
|
+
const getAuthToken = async () => {
|
|
8035
|
+
if (!appEnabled) return staticToken || "";
|
|
8036
|
+
const nowMs = Date.now();
|
|
8037
|
+
if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
|
|
8038
|
+
const jwt = createGitHubAppJwt({ appId, privateKey });
|
|
8039
|
+
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(installationId)}/access_tokens`, {
|
|
8040
|
+
method: "POST",
|
|
8041
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
8042
|
+
});
|
|
8043
|
+
installationToken = String(installation?.token || "").trim();
|
|
8044
|
+
installationTokenExpiresAt = Date.parse(installation?.expires_at || "") || nowMs + 3e6;
|
|
8045
|
+
if (!installationToken) throw new Error("GitHub App installation token response did not include a token");
|
|
8046
|
+
return installationToken;
|
|
8047
|
+
};
|
|
8048
|
+
const request = async (pathname, { method = "GET", body = void 0 } = {}) => {
|
|
8049
|
+
if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
|
|
8050
|
+
const token = await getAuthToken();
|
|
8051
|
+
return requestJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
|
|
8052
|
+
method,
|
|
8053
|
+
body,
|
|
8054
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
8055
|
+
});
|
|
8056
|
+
};
|
|
8057
|
+
const getRef = async (branch) => request(`/git/ref/heads/${encodeGitHubBranchRef(branch)}`);
|
|
8058
|
+
const getCommitObject = async (sha) => request(`/git/commits/${encodeURIComponent(sha)}`);
|
|
8059
|
+
const createBlob = async ({ content, encoding }) => request("/git/blobs", { method: "POST", body: { content, encoding } });
|
|
8060
|
+
const createTree = async ({ baseTree, tree }) => request("/git/trees", { method: "POST", body: { base_tree: baseTree, tree } });
|
|
8061
|
+
const createCommit = async ({ message, treeSha, parents }) => request("/git/commits", { method: "POST", body: { message, tree: treeSha, parents } });
|
|
8062
|
+
const updateRef = async ({ branch, sha, force = false }) => request(`/git/refs/heads/${encodeGitHubBranchRef(branch)}`, { method: "PATCH", body: { sha, force } });
|
|
8063
|
+
return {
|
|
8064
|
+
async getPullRequest(number) {
|
|
8065
|
+
return request(`/pulls/${encodeURIComponent(number)}`);
|
|
8066
|
+
},
|
|
8067
|
+
async createPullRequest({ title, head, base, body = "", draft = false, maintainerCanModify = true }) {
|
|
8068
|
+
return request("/pulls", {
|
|
8069
|
+
method: "POST",
|
|
8070
|
+
body: {
|
|
8071
|
+
title: String(title || ""),
|
|
8072
|
+
head: String(head || ""),
|
|
8073
|
+
base: String(base || ""),
|
|
8074
|
+
body: String(body || ""),
|
|
8075
|
+
draft: draft === true,
|
|
8076
|
+
maintainer_can_modify: maintainerCanModify !== false
|
|
8077
|
+
}
|
|
8078
|
+
});
|
|
8079
|
+
},
|
|
8080
|
+
async createIssueComment({ issueNumber, body }) {
|
|
8081
|
+
return request(`/issues/${encodeURIComponent(issueNumber)}/comments`, { method: "POST", body: { body: String(body || "") } });
|
|
8082
|
+
},
|
|
8083
|
+
async replyToReviewComment({ commentId, body }) {
|
|
8084
|
+
return request(`/pulls/comments/${encodeURIComponent(commentId)}/replies`, { method: "POST", body: { body: String(body || "") } });
|
|
8085
|
+
},
|
|
8086
|
+
async createPullRequestReview({ pullNumber, body, event = "COMMENT" }) {
|
|
8087
|
+
return request(`/pulls/${encodeURIComponent(pullNumber)}/reviews`, { method: "POST", body: { body: String(body || ""), event: String(event || "COMMENT").toUpperCase() } });
|
|
8088
|
+
},
|
|
8089
|
+
async mergePullRequest({ pullNumber, commitTitle = "", commitMessage = "", mergeMethod = "squash" }) {
|
|
8090
|
+
const body = { merge_method: String(mergeMethod || "squash") };
|
|
8091
|
+
if (commitTitle) body.commit_title = String(commitTitle);
|
|
8092
|
+
if (commitMessage) body.commit_message = String(commitMessage);
|
|
8093
|
+
return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
|
|
8094
|
+
},
|
|
8095
|
+
async getCombinedStatus(ref) {
|
|
8096
|
+
return request(`/commits/${encodeURIComponent(String(ref || ""))}/status`);
|
|
8097
|
+
},
|
|
8098
|
+
async listDeployments({ sha = "", ref = "", environment = "", perPage = 30 } = {}) {
|
|
8099
|
+
return request(appendGitHubQuery("/deployments", { sha, ref, environment, per_page: perPage }));
|
|
8100
|
+
},
|
|
8101
|
+
async listDeploymentStatuses(deploymentId) {
|
|
8102
|
+
return request(`/deployments/${encodeURIComponent(deploymentId)}/statuses`);
|
|
8103
|
+
},
|
|
8104
|
+
async verifyVercelPullRequestDeployment({
|
|
8105
|
+
pullNumber,
|
|
8106
|
+
context = "Vercel \u2013 defend-preproduction",
|
|
8107
|
+
environment = "Preview \u2013 defend-preproduction",
|
|
8108
|
+
requireFunctionalUrl = true
|
|
8109
|
+
} = {}) {
|
|
8110
|
+
const pr = await this.getPullRequest(pullNumber);
|
|
8111
|
+
const headSha = String(pr?.head?.sha || "").trim();
|
|
8112
|
+
if (!headSha) return { ok: true, ready: false, reason: "pr_head_sha_missing", pull_request: { number: pullNumber } };
|
|
8113
|
+
const combined = await this.getCombinedStatus(headSha);
|
|
8114
|
+
const statuses = Array.isArray(combined?.statuses) ? combined.statuses : [];
|
|
8115
|
+
const selectedStatus = sortNewestFirst(statuses.filter((status) => matchesVercelStatusContext(status?.context, context)))[0] || null;
|
|
8116
|
+
if (!selectedStatus) {
|
|
8117
|
+
return {
|
|
8118
|
+
ok: true,
|
|
8119
|
+
ready: false,
|
|
8120
|
+
reason: "vercel_status_missing",
|
|
8121
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8122
|
+
required_context: context,
|
|
8123
|
+
combined_state: combined?.state || null
|
|
8124
|
+
};
|
|
8125
|
+
}
|
|
8126
|
+
if (String(selectedStatus.state || "").toLowerCase() !== "success") {
|
|
8127
|
+
return {
|
|
8128
|
+
ok: true,
|
|
8129
|
+
ready: false,
|
|
8130
|
+
reason: "vercel_status_not_success",
|
|
8131
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8132
|
+
required_context: context,
|
|
8133
|
+
status: selectedStatus,
|
|
8134
|
+
combined_state: combined?.state || null
|
|
8135
|
+
};
|
|
8136
|
+
}
|
|
8137
|
+
let deployments = await this.listDeployments({ sha: headSha, environment });
|
|
8138
|
+
if (!Array.isArray(deployments) || deployments.length === 0) deployments = await this.listDeployments({ sha: headSha });
|
|
8139
|
+
const newestDeployments = sortNewestFirst(Array.isArray(deployments) ? deployments : []);
|
|
8140
|
+
const selectedDeployment = newestDeployments.find((deployment) => !environment || String(deployment?.environment || "") === environment) || newestDeployments[0] || null;
|
|
8141
|
+
if (!selectedDeployment?.id) {
|
|
8142
|
+
return {
|
|
8143
|
+
ok: true,
|
|
8144
|
+
ready: false,
|
|
8145
|
+
reason: "vercel_deployment_missing",
|
|
8146
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8147
|
+
required_environment: environment,
|
|
8148
|
+
status: selectedStatus
|
|
8149
|
+
};
|
|
8150
|
+
}
|
|
8151
|
+
const deploymentStatuses = await this.listDeploymentStatuses(selectedDeployment.id);
|
|
8152
|
+
const selectedDeploymentStatus = sortNewestFirst(Array.isArray(deploymentStatuses) ? deploymentStatuses : [])[0] || null;
|
|
8153
|
+
if (!selectedDeploymentStatus || String(selectedDeploymentStatus.state || "").toLowerCase() !== "success") {
|
|
8154
|
+
return {
|
|
8155
|
+
ok: true,
|
|
8156
|
+
ready: false,
|
|
8157
|
+
reason: "vercel_deployment_not_success",
|
|
8158
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8159
|
+
required_environment: environment,
|
|
8160
|
+
status: selectedStatus,
|
|
8161
|
+
deployment: selectedDeployment,
|
|
8162
|
+
deployment_status: selectedDeploymentStatus
|
|
8163
|
+
};
|
|
8164
|
+
}
|
|
8165
|
+
const url = selectFunctionalDeploymentUrl(selectedDeploymentStatus, selectedDeployment);
|
|
8166
|
+
const urlCheck = requireFunctionalUrl ? await checkFunctionalDeploymentUrl(url, fetchImpl) : { ok: Boolean(url), reason: url ? void 0 : "url_missing", url };
|
|
8167
|
+
if (requireFunctionalUrl && !urlCheck.ok) {
|
|
8168
|
+
return {
|
|
8169
|
+
ok: true,
|
|
8170
|
+
ready: false,
|
|
8171
|
+
reason: "vercel_url_not_functional",
|
|
8172
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8173
|
+
required_environment: environment,
|
|
8174
|
+
status: selectedStatus,
|
|
8175
|
+
deployment: selectedDeployment,
|
|
8176
|
+
deployment_status: selectedDeploymentStatus,
|
|
8177
|
+
url,
|
|
8178
|
+
url_check: urlCheck
|
|
8179
|
+
};
|
|
8180
|
+
}
|
|
8181
|
+
return {
|
|
8182
|
+
ok: true,
|
|
8183
|
+
ready: true,
|
|
8184
|
+
reason: "vercel_pr_deployment_ready",
|
|
8185
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
8186
|
+
required_context: context,
|
|
8187
|
+
required_environment: environment,
|
|
8188
|
+
status: selectedStatus,
|
|
8189
|
+
deployment: selectedDeployment,
|
|
8190
|
+
deployment_status: selectedDeploymentStatus,
|
|
8191
|
+
url,
|
|
8192
|
+
url_check: urlCheck
|
|
8193
|
+
};
|
|
8194
|
+
},
|
|
8195
|
+
async commitWorktree({ worktree, branch = "", message = "", runGitFn = runGit, syncLocal = false } = {}) {
|
|
8196
|
+
const directory = path3.resolve(String(worktree || ""));
|
|
8197
|
+
if (!directory || !fs3.existsSync(directory)) throw new Error("worktree does not exist");
|
|
8198
|
+
const targetBranch = String(branch || currentGitBranch(directory, runGitFn)).trim();
|
|
8199
|
+
if (!targetBranch || targetBranch === "HEAD") throw new Error("target branch is required for GitHub API commit");
|
|
8200
|
+
const commitMessage = String(message || "").trim();
|
|
8201
|
+
if (!commitMessage) throw new Error("commit message is required");
|
|
8202
|
+
const changedPaths = listWorktreeChangedPaths(directory, runGitFn);
|
|
8203
|
+
if (changedPaths.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
8204
|
+
const ref = await getRef(targetBranch);
|
|
8205
|
+
const headSha = ref?.object?.sha;
|
|
8206
|
+
if (!headSha) throw new Error(`GitHub ref for ${targetBranch} did not include a head sha`);
|
|
8207
|
+
const headCommit = await getCommitObject(headSha);
|
|
8208
|
+
const baseTree = headCommit?.tree?.sha;
|
|
8209
|
+
if (!baseTree) throw new Error(`GitHub commit ${headSha} did not include a tree sha`);
|
|
8210
|
+
const tree = [];
|
|
8211
|
+
for (const gitPath of changedPaths) {
|
|
8212
|
+
const entry = treeEntryForWorktreePath(directory, gitPath);
|
|
8213
|
+
if (!entry) continue;
|
|
8214
|
+
if (entry.deleted) {
|
|
8215
|
+
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: null });
|
|
8216
|
+
continue;
|
|
8217
|
+
}
|
|
8218
|
+
const blob = await createBlob({ content: entry.content, encoding: entry.encoding });
|
|
8219
|
+
if (!blob?.sha) throw new Error(`GitHub blob creation failed for ${gitPath}`);
|
|
8220
|
+
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: blob.sha });
|
|
8221
|
+
}
|
|
8222
|
+
if (tree.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
8223
|
+
const nextTree = await createTree({ baseTree, tree });
|
|
8224
|
+
const nextCommit = await createCommit({ message: commitMessage, treeSha: nextTree.sha, parents: [headSha] });
|
|
8225
|
+
if (!nextCommit?.sha) throw new Error("GitHub commit creation did not return a sha");
|
|
8226
|
+
const updatedRef = await updateRef({ branch: targetBranch, sha: nextCommit.sha, force: false });
|
|
8227
|
+
if (syncLocal) {
|
|
8228
|
+
runGitFn(directory, ["fetch", "origin", targetBranch]);
|
|
8229
|
+
runGitFn(directory, ["reset", "--hard", "FETCH_HEAD"]);
|
|
8230
|
+
}
|
|
8231
|
+
return {
|
|
8232
|
+
ok: true,
|
|
8233
|
+
action: "committed",
|
|
8234
|
+
branch: targetBranch,
|
|
8235
|
+
before: headSha,
|
|
8236
|
+
after: nextCommit.sha,
|
|
8237
|
+
changedPaths,
|
|
8238
|
+
treeEntries: tree.length,
|
|
8239
|
+
verification: nextCommit.verification || null,
|
|
8240
|
+
ref: updatedRef
|
|
8241
|
+
};
|
|
8242
|
+
},
|
|
8243
|
+
async authMode() {
|
|
8244
|
+
if (appEnabled) return { mode: "github_app", appId, installationId };
|
|
8245
|
+
if (staticToken) return { mode: "token" };
|
|
8246
|
+
return { mode: "anonymous" };
|
|
8247
|
+
}
|
|
8248
|
+
};
|
|
8249
|
+
}
|
|
8250
|
+
|
|
8251
|
+
// src/include_resolver.js
|
|
8252
|
+
import fs4 from "node:fs";
|
|
8253
|
+
import path4 from "node:path";
|
|
8254
|
+
function compactPromptPath(filePath) {
|
|
8255
|
+
if (!filePath.endsWith(".md") || filePath.endsWith(".prompt.md")) return null;
|
|
8256
|
+
return filePath.replace(/\.md$/, ".prompt.md");
|
|
8257
|
+
}
|
|
8258
|
+
function isSameOrNestedPath(candidate, root) {
|
|
8259
|
+
if (typeof candidate !== "string" || typeof root !== "string" || !candidate.trim() || !root.trim()) return false;
|
|
8260
|
+
const resolvedCandidate = path4.resolve(candidate);
|
|
8261
|
+
const resolvedRoot = path4.resolve(root);
|
|
8262
|
+
const relative = path4.relative(resolvedRoot, resolvedCandidate);
|
|
8263
|
+
return relative === "" || !!relative && !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
8264
|
+
}
|
|
8265
|
+
function resolveWithin(baseDir, relativePath) {
|
|
8266
|
+
if (!relativePath) return null;
|
|
8267
|
+
if (path4.isAbsolute(relativePath)) return null;
|
|
8268
|
+
const resolved = path4.resolve(baseDir, relativePath);
|
|
8269
|
+
return isSameOrNestedPath(resolved, baseDir) ? resolved : null;
|
|
8270
|
+
}
|
|
8271
|
+
function withCompactPreference(paths, options = {}) {
|
|
8272
|
+
if (!options.preferCompactPromptDocs) return paths;
|
|
8273
|
+
const compactPaths = paths.map((filePath) => filePath ? compactPromptPath(filePath) : null).filter(Boolean);
|
|
8274
|
+
return [...compactPaths, ...paths];
|
|
8275
|
+
}
|
|
8276
|
+
function resolveIncludeFile(includeRef, repoRoot, bundleRoot, options = {}) {
|
|
8277
|
+
const trimmed = String(includeRef || "").trim();
|
|
8278
|
+
const scopedMatch = trimmed.match(/^([a-z]+):(.*)$/i);
|
|
8279
|
+
const scope = scopedMatch?.[1]?.toLowerCase();
|
|
8280
|
+
const target = scopedMatch ? scopedMatch[2].trim() : trimmed;
|
|
8281
|
+
const optimaRoot = options.optimaRoot || path4.join(repoRoot, ".optima");
|
|
8282
|
+
const repoPolicyRoot = options.repoPolicyRoot || path4.join(optimaRoot, "policies");
|
|
8283
|
+
const bundlePolicyRoot = options.bundlePolicyRoot || path4.join(bundleRoot, "assets", "policies");
|
|
8284
|
+
if (scope === "plugin") {
|
|
8285
|
+
for (const filePath of withCompactPreference([resolveWithin(bundleRoot, target)], options)) {
|
|
8286
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8287
|
+
}
|
|
8288
|
+
return null;
|
|
8289
|
+
}
|
|
8290
|
+
if (scope === "repo") {
|
|
8291
|
+
for (const filePath of withCompactPreference([resolveWithin(optimaRoot, target)], options)) {
|
|
8292
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8293
|
+
}
|
|
8294
|
+
return null;
|
|
8295
|
+
}
|
|
8296
|
+
if (scope === "policy") {
|
|
8297
|
+
const candidates2 = withCompactPreference([
|
|
8298
|
+
resolveWithin(repoPolicyRoot, target),
|
|
8299
|
+
resolveWithin(bundlePolicyRoot, target)
|
|
8300
|
+
], options);
|
|
8301
|
+
for (const filePath of candidates2) {
|
|
8302
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8303
|
+
}
|
|
8304
|
+
return null;
|
|
8305
|
+
}
|
|
8306
|
+
const candidates = withCompactPreference([
|
|
8307
|
+
resolveWithin(repoRoot, target),
|
|
8308
|
+
resolveWithin(bundleRoot, target)
|
|
8309
|
+
], options);
|
|
8310
|
+
for (const filePath of candidates) {
|
|
8311
|
+
if (filePath && fs4.existsSync(filePath)) return filePath;
|
|
8312
|
+
}
|
|
8313
|
+
return null;
|
|
8314
|
+
}
|
|
8315
|
+
function resolveIncludes(text, repoRoot, bundleRoot, options = {}) {
|
|
8316
|
+
const includeDepth = Number(options.includeDepth || 0);
|
|
8317
|
+
const maxIncludeDepth = Number(options.maxIncludeDepth || 20);
|
|
8318
|
+
if (includeDepth > maxIncludeDepth) {
|
|
8319
|
+
return "\n\n# ERROR: Include recursion limit exceeded.\n\n";
|
|
8320
|
+
}
|
|
8321
|
+
const includeRegex = /<include:(.*?)>/g;
|
|
8322
|
+
return String(text || "").replace(includeRegex, (match, includeRef) => {
|
|
8323
|
+
const filePath = resolveIncludeFile(includeRef, repoRoot, bundleRoot, options);
|
|
8324
|
+
if (!filePath) {
|
|
8325
|
+
console.warn(`[Optima] Include file not found: ${includeRef}`);
|
|
8326
|
+
return `
|
|
8327
|
+
|
|
8328
|
+
# ERROR: Include file not found: ${includeRef}
|
|
8329
|
+
|
|
8330
|
+
`;
|
|
8331
|
+
}
|
|
8332
|
+
const content = fs4.readFileSync(filePath, "utf8");
|
|
8333
|
+
return resolveIncludes(content, repoRoot, bundleRoot, { ...options, includeDepth: includeDepth + 1, maxIncludeDepth });
|
|
8334
|
+
});
|
|
8335
|
+
}
|
|
8336
|
+
|
|
8337
|
+
// src/markdown_artifacts.js
|
|
8338
|
+
import fs5 from "node:fs";
|
|
8339
|
+
var CLICKUP_REQUIRED_SUMMARY_SECTIONS = [
|
|
8340
|
+
"Summary",
|
|
8341
|
+
"Work Performed",
|
|
8342
|
+
"AC Coverage",
|
|
8343
|
+
"Verification Results",
|
|
8344
|
+
"Documentation Impact",
|
|
8345
|
+
"Open Risks",
|
|
8346
|
+
"Recommended Next Step"
|
|
8347
|
+
];
|
|
8348
|
+
var RAW_LOG_SECTION_NAMES = /* @__PURE__ */ new Set(["Raw Logs", "Logs", "Full Logs", "Command Output", "Transcript"]);
|
|
8349
|
+
function parseMarkdownSections(markdown = "") {
|
|
8350
|
+
const sections = {};
|
|
8351
|
+
let current = null;
|
|
8352
|
+
let buffer = [];
|
|
8353
|
+
const flush = () => {
|
|
8354
|
+
if (!current) return;
|
|
8355
|
+
sections[current] = buffer.join("\n").trim();
|
|
8356
|
+
};
|
|
8357
|
+
for (const line of String(markdown).split(/\r?\n/)) {
|
|
8358
|
+
const heading = /^(#{2,3})\s+(.+?)\s*$/.exec(line);
|
|
8359
|
+
if (heading) {
|
|
8360
|
+
flush();
|
|
8361
|
+
current = heading[2].trim();
|
|
8362
|
+
buffer = [];
|
|
8363
|
+
continue;
|
|
8364
|
+
}
|
|
8365
|
+
if (current) buffer.push(line);
|
|
8366
|
+
}
|
|
8367
|
+
flush();
|
|
8368
|
+
return sections;
|
|
8369
|
+
}
|
|
8370
|
+
function parseMarkdownArtifact(markdown = "", { requiredSections = [] } = {}) {
|
|
8371
|
+
const sections = parseMarkdownSections(markdown);
|
|
8372
|
+
const missing = requiredSections.filter((section) => !sections[section]);
|
|
8373
|
+
return {
|
|
8374
|
+
ok: missing.length === 0,
|
|
8375
|
+
sections,
|
|
8376
|
+
missing,
|
|
8377
|
+
message: missing.length ? `Missing required section(s): ${missing.join(", ")}` : "ok"
|
|
8378
|
+
};
|
|
8379
|
+
}
|
|
8380
|
+
function readMarkdownArtifact(filePath, options = {}) {
|
|
8381
|
+
const markdown = fs5.readFileSync(filePath, "utf8");
|
|
8382
|
+
return parseMarkdownArtifact(markdown, options);
|
|
8383
|
+
}
|
|
8384
|
+
function stripRawLogSections(sections = {}) {
|
|
8385
|
+
return Object.fromEntries(
|
|
8386
|
+
Object.entries(sections).filter(([name]) => !RAW_LOG_SECTION_NAMES.has(name))
|
|
8387
|
+
);
|
|
8388
|
+
}
|
|
8389
|
+
|
|
8390
|
+
// src/repair.js
|
|
8391
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
8392
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
8393
|
+
import fs6 from "node:fs";
|
|
8394
|
+
import path5 from "node:path";
|
|
8395
|
+
var CODEMAP_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8396
|
+
".js",
|
|
8397
|
+
".ts",
|
|
8398
|
+
".tsx",
|
|
8399
|
+
".jsx",
|
|
8400
|
+
".dart",
|
|
8401
|
+
".py",
|
|
8402
|
+
".go",
|
|
8403
|
+
".rs",
|
|
8404
|
+
".java",
|
|
8405
|
+
".c",
|
|
8406
|
+
".cpp",
|
|
8407
|
+
".cs",
|
|
8408
|
+
".php",
|
|
8409
|
+
".rb",
|
|
8410
|
+
".swift",
|
|
8411
|
+
".kt",
|
|
8412
|
+
".m",
|
|
8413
|
+
".sh",
|
|
8414
|
+
".sql",
|
|
8415
|
+
".yaml",
|
|
8416
|
+
".yml",
|
|
8417
|
+
".json",
|
|
8418
|
+
".md"
|
|
8419
|
+
]);
|
|
8420
|
+
var LEGACY_OPERATIONAL_ROOTS = /* @__PURE__ */ new Set([
|
|
8421
|
+
"tasks",
|
|
8422
|
+
"evidences",
|
|
8423
|
+
".orbita",
|
|
8424
|
+
".staticeng",
|
|
8425
|
+
".nomadworks",
|
|
8426
|
+
".nomadwork",
|
|
8427
|
+
"nomadworks",
|
|
8428
|
+
".codenomad"
|
|
8429
|
+
]);
|
|
8430
|
+
var OPTIMA_OPERATIONAL_FOLDERS = [".optima", "templates", "dist"];
|
|
8431
|
+
function relPath(worktree, targetPath) {
|
|
8432
|
+
return path5.relative(worktree, targetPath).split(path5.sep).join("/") || ".";
|
|
8433
|
+
}
|
|
8434
|
+
function normalizeCodemapRelPath(relPathValue) {
|
|
8435
|
+
return path5.normalize(relPathValue).replace(/\\/g, "/").replace(/\/$/, "");
|
|
8436
|
+
}
|
|
8437
|
+
function firstPathSegment(relPathValue) {
|
|
8438
|
+
return normalizeCodemapRelPath(relPathValue).split("/").filter(Boolean)[0] || "";
|
|
8439
|
+
}
|
|
8440
|
+
function isOperationalRelPath(relPathValue) {
|
|
8441
|
+
if (!relPathValue) return false;
|
|
8442
|
+
const normalized = normalizeCodemapRelPath(relPathValue);
|
|
8443
|
+
const firstSegment = firstPathSegment(normalized);
|
|
8444
|
+
if (LEGACY_OPERATIONAL_ROOTS.has(firstSegment)) return true;
|
|
8445
|
+
return OPTIMA_OPERATIONAL_FOLDERS.some((folder) => normalized === folder || normalized.startsWith(`${folder}/`));
|
|
8446
|
+
}
|
|
8447
|
+
function isHiddenTree(relPathValue) {
|
|
8448
|
+
if (!relPathValue) return false;
|
|
8449
|
+
return normalizeCodemapRelPath(relPathValue).split("/").some((part) => part.startsWith("."));
|
|
8450
|
+
}
|
|
8451
|
+
function loadGitIgnoreMatcher(worktree) {
|
|
8452
|
+
const ig = (0, import_ignore.default)();
|
|
8453
|
+
ig.add(".git");
|
|
8454
|
+
const gitignorePath = path5.join(worktree, ".gitignore");
|
|
8455
|
+
if (fs6.existsSync(gitignorePath)) ig.add(fs6.readFileSync(gitignorePath, "utf8"));
|
|
8456
|
+
return ig;
|
|
8457
|
+
}
|
|
8458
|
+
function codemapSectionEntries(value) {
|
|
8459
|
+
if (Array.isArray(value)) return value;
|
|
8460
|
+
if (value && typeof value === "object") return Object.values(value);
|
|
8461
|
+
return [];
|
|
8462
|
+
}
|
|
8463
|
+
function codemapIndexedPaths(map) {
|
|
8464
|
+
const indexed = /* @__PURE__ */ new Set();
|
|
8465
|
+
for (const section of ["modules", "entrypoints", "sources_of_truth", "links", "internals"]) {
|
|
8466
|
+
for (const item of codemapSectionEntries(map?.[section])) {
|
|
8467
|
+
if (item?.path) indexed.add(normalizeCodemapRelPath(item.path));
|
|
8468
|
+
}
|
|
8469
|
+
}
|
|
8470
|
+
return indexed;
|
|
8471
|
+
}
|
|
8472
|
+
function readCodemap(filePath, unresolved, worktree) {
|
|
8473
|
+
try {
|
|
8474
|
+
const parsed = import_yaml.default.parse(fs6.readFileSync(filePath, "utf8"));
|
|
8475
|
+
if (!parsed || typeof parsed !== "object") {
|
|
8476
|
+
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap is empty or invalid; manual repair required." });
|
|
8477
|
+
return null;
|
|
8478
|
+
}
|
|
8479
|
+
return parsed;
|
|
8480
|
+
} catch {
|
|
8481
|
+
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap has invalid YAML; manual repair required." });
|
|
8482
|
+
return null;
|
|
8483
|
+
}
|
|
8484
|
+
}
|
|
8485
|
+
function writeCodemap(filePath, map) {
|
|
8486
|
+
fs6.writeFileSync(filePath, import_yaml.default.stringify(map), "utf8");
|
|
8487
|
+
}
|
|
8488
|
+
function isIgnoredPath(ig, relativePath) {
|
|
8489
|
+
const normalized = normalizeCodemapRelPath(relativePath);
|
|
8490
|
+
return Boolean(normalized) && ig.ignores(normalized);
|
|
8491
|
+
}
|
|
8492
|
+
function hasImmediateSourceFile(dirPath, ig, worktree) {
|
|
8493
|
+
if (!fs6.existsSync(dirPath)) return false;
|
|
8494
|
+
for (const item of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8495
|
+
const childPath = path5.join(dirPath, item.name);
|
|
8496
|
+
const relative = path5.relative(worktree, childPath);
|
|
8497
|
+
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative)) continue;
|
|
8498
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) return true;
|
|
8499
|
+
}
|
|
8500
|
+
return false;
|
|
8501
|
+
}
|
|
8502
|
+
function containsSource(dirPath, ig, worktree) {
|
|
8503
|
+
if (!fs6.existsSync(dirPath)) return false;
|
|
8504
|
+
const dirRelPath = path5.relative(worktree, dirPath);
|
|
8505
|
+
if (isOperationalRelPath(dirRelPath) || isHiddenTree(dirRelPath)) return false;
|
|
8506
|
+
for (const item of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8507
|
+
const childPath = path5.join(dirPath, item.name);
|
|
8508
|
+
const relative = path5.relative(worktree, childPath);
|
|
8010
8509
|
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative)) continue;
|
|
8011
|
-
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(
|
|
8510
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) return true;
|
|
8012
8511
|
if (item.isDirectory() && containsSource(childPath, ig, worktree)) return true;
|
|
8013
8512
|
}
|
|
8014
8513
|
return false;
|
|
8015
8514
|
}
|
|
8016
8515
|
function createModuleCodemap(dirPath, worktree, deps) {
|
|
8017
|
-
const entries =
|
|
8516
|
+
const entries = fs6.readdirSync(dirPath, { withFileTypes: true });
|
|
8018
8517
|
const entrypoints = [];
|
|
8019
8518
|
const internals = [];
|
|
8020
|
-
const relToRoot =
|
|
8519
|
+
const relToRoot = path5.relative(dirPath, deps.optimaCodemapPath(worktree)).split(path5.sep).join("/");
|
|
8021
8520
|
for (const item of entries) {
|
|
8022
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
8521
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) continue;
|
|
8023
8522
|
const bucket = /^index\.(js|ts|tsx|jsx)$|^main\.(js|ts|tsx|jsx|py|go|rs|java)$/.test(item.name) ? entrypoints : internals;
|
|
8024
8523
|
bucket.push({ path: item.name });
|
|
8025
8524
|
}
|
|
@@ -8032,7 +8531,7 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8032
8531
|
const map = readCodemap(codemapPath, unresolved, worktree);
|
|
8033
8532
|
if (!map) return;
|
|
8034
8533
|
const isRootMap = codemapPath === deps.optimaCodemapPath(worktree);
|
|
8035
|
-
const mapDir =
|
|
8534
|
+
const mapDir = path5.dirname(codemapPath);
|
|
8036
8535
|
const pathBase = isRootMap ? worktree : mapDir;
|
|
8037
8536
|
let changed = false;
|
|
8038
8537
|
for (const section of ["modules", "entrypoints", "sources_of_truth", "links", "internals"]) {
|
|
@@ -8043,13 +8542,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8043
8542
|
}
|
|
8044
8543
|
const nextEntries = [];
|
|
8045
8544
|
for (const entry of originalEntries) {
|
|
8046
|
-
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") ||
|
|
8545
|
+
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") || path5.isAbsolute(entry.path)) {
|
|
8047
8546
|
nextEntries.push(entry);
|
|
8048
8547
|
continue;
|
|
8049
8548
|
}
|
|
8050
8549
|
const normalizedPath = normalizeCodemapRelPath(entry.path);
|
|
8051
|
-
const absPath =
|
|
8052
|
-
if (!
|
|
8550
|
+
const absPath = path5.join(pathBase, normalizedPath);
|
|
8551
|
+
if (!fs6.existsSync(absPath)) {
|
|
8053
8552
|
registerAction({ category: "codemap", action: apply ? "removed" : "would_remove", path: relPath(worktree, codemapPath), detail: `Remove broken ${section.slice(0, -1)} reference '${entry.path}'.` });
|
|
8054
8553
|
changed = true;
|
|
8055
8554
|
continue;
|
|
@@ -8058,8 +8557,8 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8058
8557
|
const maxParts = isRootMap ? 2 : 1;
|
|
8059
8558
|
if (parts.length > maxParts) {
|
|
8060
8559
|
const localPath = isRootMap ? parts.slice(0, 2).join("/") : parts[0];
|
|
8061
|
-
const localAbs =
|
|
8062
|
-
if (
|
|
8560
|
+
const localAbs = path5.join(pathBase, localPath);
|
|
8561
|
+
if (fs6.existsSync(localAbs)) {
|
|
8063
8562
|
nextEntries.push({ ...entry, path: localPath });
|
|
8064
8563
|
changed = true;
|
|
8065
8564
|
registerAction({ category: "codemap", action: apply ? "updated" : "would_update", path: relPath(worktree, codemapPath), detail: `Shorten '${entry.path}' to local path '${localPath}'.` });
|
|
@@ -8073,11 +8572,11 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8073
8572
|
}
|
|
8074
8573
|
if (Array.isArray(map[section])) map[section] = nextEntries;
|
|
8075
8574
|
}
|
|
8076
|
-
if (map.scope === "module" && !isOperationalRelPath(
|
|
8575
|
+
if (map.scope === "module" && !isOperationalRelPath(path5.relative(worktree, mapDir))) {
|
|
8077
8576
|
const indexed = codemapIndexedPaths(map);
|
|
8078
8577
|
const internals = Array.isArray(map.internals) ? map.internals : [];
|
|
8079
|
-
for (const item of
|
|
8080
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
8578
|
+
for (const item of fs6.readdirSync(mapDir, { withFileTypes: true })) {
|
|
8579
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path5.extname(item.name))) continue;
|
|
8081
8580
|
if (indexed.has(item.name)) continue;
|
|
8082
8581
|
internals.push({ path: item.name });
|
|
8083
8582
|
indexed.add(item.name);
|
|
@@ -8091,13 +8590,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
8091
8590
|
function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
8092
8591
|
const ig = loadGitIgnoreMatcher(worktree);
|
|
8093
8592
|
const rootCodemapPath = deps.optimaCodemapPath(worktree);
|
|
8094
|
-
const rootCodemapMissing = !
|
|
8593
|
+
const rootCodemapMissing = !fs6.existsSync(rootCodemapPath);
|
|
8095
8594
|
if (rootCodemapMissing) {
|
|
8096
8595
|
actions.push({ category: "codemap", action: apply ? "created" : "would_create", path: ".optima/codemap.yml", detail: "Create missing root CodeMap from template." });
|
|
8097
8596
|
if (apply) deps.scaffoldOptimaRootCodemap(worktree);
|
|
8098
8597
|
}
|
|
8099
|
-
if (
|
|
8100
|
-
const rootMap =
|
|
8598
|
+
if (fs6.existsSync(rootCodemapPath)) repairSingleCodemap(rootCodemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
8599
|
+
const rootMap = fs6.existsSync(rootCodemapPath) ? readCodemap(rootCodemapPath, unresolved, worktree) : null;
|
|
8101
8600
|
let rootChanged = false;
|
|
8102
8601
|
const rootModulesRepairable = rootMap && (rootMap.modules === void 0 || Array.isArray(rootMap.modules));
|
|
8103
8602
|
if (rootMap) {
|
|
@@ -8109,10 +8608,10 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
8109
8608
|
}
|
|
8110
8609
|
}
|
|
8111
8610
|
function walk(dirPath) {
|
|
8112
|
-
const relative =
|
|
8611
|
+
const relative = path5.relative(worktree, dirPath);
|
|
8113
8612
|
if (relative && (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative))) return;
|
|
8114
|
-
const codemapPath =
|
|
8115
|
-
const hasCodemap =
|
|
8613
|
+
const codemapPath = path5.join(dirPath, "codemap.yml");
|
|
8614
|
+
const hasCodemap = fs6.existsSync(codemapPath);
|
|
8116
8615
|
const sourceDir = relative && containsSource(dirPath, ig, worktree);
|
|
8117
8616
|
if (sourceDir && !hasCodemap && dirPath !== worktree) {
|
|
8118
8617
|
if (hasImmediateSourceFile(dirPath, ig, worktree)) {
|
|
@@ -8131,19 +8630,19 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
8131
8630
|
actions.push({ category: "codemap", action: apply ? "updated" : "would_update", path: ".optima/codemap.yml", detail: `Register top-level source module '${normalizeCodemapRelPath(relative)}'.` });
|
|
8132
8631
|
}
|
|
8133
8632
|
}
|
|
8134
|
-
if (
|
|
8135
|
-
for (const item of
|
|
8136
|
-
if (item.isDirectory()) walk(
|
|
8633
|
+
if (fs6.existsSync(codemapPath) && codemapPath !== rootCodemapPath) repairSingleCodemap(codemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
8634
|
+
for (const item of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8635
|
+
if (item.isDirectory()) walk(path5.join(dirPath, item.name));
|
|
8137
8636
|
}
|
|
8138
8637
|
}
|
|
8139
|
-
if (
|
|
8638
|
+
if (fs6.existsSync(worktree)) walk(worktree);
|
|
8140
8639
|
if (rootMap && rootChanged && apply) writeCodemap(rootCodemapPath, rootMap);
|
|
8141
8640
|
}
|
|
8142
8641
|
function walkMarkdownFiles(dirPath) {
|
|
8143
|
-
if (!
|
|
8642
|
+
if (!fs6.existsSync(dirPath)) return [];
|
|
8144
8643
|
const files = [];
|
|
8145
|
-
for (const entry of
|
|
8146
|
-
const entryPath =
|
|
8644
|
+
for (const entry of fs6.readdirSync(dirPath, { withFileTypes: true })) {
|
|
8645
|
+
const entryPath = path5.join(dirPath, entry.name);
|
|
8147
8646
|
if (entry.isDirectory()) {
|
|
8148
8647
|
files.push(...walkMarkdownFiles(entryPath));
|
|
8149
8648
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -8182,7 +8681,7 @@ function rewriteOptimaMarkdownReferences(content, evidenceBasePath = null) {
|
|
|
8182
8681
|
}
|
|
8183
8682
|
function optimaEvidenceBaseForFile(worktree, filePath, deps) {
|
|
8184
8683
|
const evidenceRoot = deps.optimaEvidencesDir(worktree);
|
|
8185
|
-
const relative =
|
|
8684
|
+
const relative = path5.relative(evidenceRoot, filePath).split(path5.sep).join("/");
|
|
8186
8685
|
const [taskId] = relative.split("/");
|
|
8187
8686
|
if (!taskId || taskId === ".." || relative.startsWith("../")) return null;
|
|
8188
8687
|
return `.optima/evidences/${taskId}`;
|
|
@@ -8195,11 +8694,11 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
8195
8694
|
];
|
|
8196
8695
|
const changed = [];
|
|
8197
8696
|
for (const filePath of scanRoots.flatMap(walkMarkdownFiles)) {
|
|
8198
|
-
const raw =
|
|
8199
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
8697
|
+
const raw = fs6.readFileSync(filePath, "utf8");
|
|
8698
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path5.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
8200
8699
|
const rewritten = rewriteOptimaMarkdownReferences(raw, evidenceBasePath);
|
|
8201
8700
|
if (rewritten !== raw) {
|
|
8202
|
-
|
|
8701
|
+
fs6.writeFileSync(filePath, rewritten, "utf8");
|
|
8203
8702
|
changed.push(relPath(worktree, filePath));
|
|
8204
8703
|
}
|
|
8205
8704
|
}
|
|
@@ -8207,8 +8706,8 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
8207
8706
|
}
|
|
8208
8707
|
function missingOptimaGitignoreRules(worktree, deps) {
|
|
8209
8708
|
if (!deps.isGitRepository(worktree)) return [];
|
|
8210
|
-
const gitignorePath =
|
|
8211
|
-
const existing =
|
|
8709
|
+
const gitignorePath = path5.join(worktree, ".gitignore");
|
|
8710
|
+
const existing = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
|
|
8212
8711
|
const existingRules = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
8213
8712
|
return deps.optimaGitignoreRules.filter((rule) => !existingRules.has(rule));
|
|
8214
8713
|
}
|
|
@@ -8225,11 +8724,11 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
8225
8724
|
const apply = mode === "apply";
|
|
8226
8725
|
const actions = [];
|
|
8227
8726
|
const unresolved = [];
|
|
8228
|
-
const hadOptima =
|
|
8727
|
+
const hadOptima = fs6.existsSync(deps.optimaDir(worktree));
|
|
8229
8728
|
const missingGitignoreRules = missingOptimaGitignoreRules(worktree, deps);
|
|
8230
8729
|
const markdownBefore = apply ? [] : walkMarkdownFiles(deps.optimaDir(worktree)).filter((filePath) => {
|
|
8231
|
-
const raw =
|
|
8232
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
8730
|
+
const raw = fs6.readFileSync(filePath, "utf8");
|
|
8731
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path5.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
8233
8732
|
return rewriteOptimaMarkdownReferences(raw, evidenceBasePath) !== raw;
|
|
8234
8733
|
}).map((filePath) => relPath(worktree, filePath));
|
|
8235
8734
|
if (!hadOptima) pushRepairAction(actions, "operational", apply ? "created" : "would_create", ".optima/", "Create Optima operational root.");
|
|
@@ -8238,31 +8737,31 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
8238
8737
|
[".staticeng/", deps.legacyStaticEngDir(worktree)],
|
|
8239
8738
|
[".nomadwork/", deps.legacyNomadworkDir(worktree)],
|
|
8240
8739
|
[".nomadworks/", deps.legacyNomadworksDir(worktree)],
|
|
8241
|
-
["tasks/",
|
|
8242
|
-
["evidences/",
|
|
8243
|
-
["docs/scrs/",
|
|
8244
|
-
["codemap.yml",
|
|
8245
|
-
["codemap.yaml",
|
|
8740
|
+
["tasks/", path5.join(worktree, "tasks")],
|
|
8741
|
+
["evidences/", path5.join(worktree, "evidences")],
|
|
8742
|
+
["docs/scrs/", path5.join(worktree, "docs", "scrs")],
|
|
8743
|
+
["codemap.yml", path5.join(worktree, "codemap.yml")],
|
|
8744
|
+
["codemap.yaml", path5.join(worktree, "codemap.yaml")]
|
|
8246
8745
|
];
|
|
8247
|
-
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/",
|
|
8746
|
+
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/", path5.join(worktree, "policies")]);
|
|
8248
8747
|
for (const [display, sourcePath] of legacySources) {
|
|
8249
|
-
if (
|
|
8748
|
+
if (fs6.existsSync(sourcePath)) pushRepairAction(actions, "legacy", apply ? "migrated" : "would_migrate", display, "Move or merge legacy/root Optima artifact into .optima using preservation safeguards.");
|
|
8250
8749
|
}
|
|
8251
8750
|
if (apply) deps.migrateLegacyOptimaLayout(worktree);
|
|
8252
|
-
const configMissing = !
|
|
8751
|
+
const configMissing = !fs6.existsSync(deps.repoConfigPath(worktree));
|
|
8253
8752
|
if (configMissing) pushRepairAction(actions, "config", apply ? "created" : "would_create", ".optima/.config/optima.yaml", "Create missing local Optima config from template.");
|
|
8254
8753
|
if (apply) deps.scaffoldOptimaConfig(worktree, args.team_mode || "full");
|
|
8255
8754
|
const requiredFiles = [
|
|
8256
|
-
[
|
|
8257
|
-
[
|
|
8258
|
-
[
|
|
8259
|
-
[
|
|
8260
|
-
[
|
|
8261
|
-
[
|
|
8262
|
-
[
|
|
8755
|
+
[path5.join(deps.optimaTasksDir(worktree), "current.md"), "registry", ".optima/tasks/current.md", deps.currentTasksRegistryContent()],
|
|
8756
|
+
[path5.join(deps.optimaTasksDir(worktree), "done.md"), "registry", ".optima/tasks/done.md", deps.doneTasksRegistryContent()],
|
|
8757
|
+
[path5.join(deps.optimaScrsDir(worktree), "current.md"), "registry", ".optima/docs/scrs/current.md", deps.currentScrRegistryContent()],
|
|
8758
|
+
[path5.join(deps.optimaScrsDir(worktree), "done.md"), "registry", ".optima/docs/scrs/done.md", deps.doneScrRegistryContent()],
|
|
8759
|
+
[path5.join(deps.optimaTasksDir(worktree), "task-template.md"), "template", ".optima/tasks/task-template.md", deps.taskTemplateContent()],
|
|
8760
|
+
[path5.join(deps.optimaTasksDir(worktree), "subtask-template.md"), "template", ".optima/tasks/subtask-template.md", deps.subtaskTemplateContent()],
|
|
8761
|
+
[path5.join(deps.repoPoliciesDir(worktree), "README.md"), "policy", ".optima/policies/README.md", deps.repoLocalPoliciesReadme]
|
|
8263
8762
|
];
|
|
8264
8763
|
for (const [filePath, category, displayPath, content] of requiredFiles) {
|
|
8265
|
-
if (!
|
|
8764
|
+
if (!fs6.existsSync(filePath)) {
|
|
8266
8765
|
pushRepairAction(actions, category, apply ? "created" : "would_create", displayPath, "Create missing Optima scaffold file without overwriting existing content.");
|
|
8267
8766
|
if (apply) deps.ensureFileIfMissing(filePath, content);
|
|
8268
8767
|
}
|
|
@@ -8316,18 +8815,18 @@ function formatRepairResult(plan, validationResult = null, formatValidationResul
|
|
|
8316
8815
|
// src/validate_logic.js
|
|
8317
8816
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
8318
8817
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
8319
|
-
import
|
|
8320
|
-
import
|
|
8818
|
+
import fs7 from "node:fs";
|
|
8819
|
+
import path6 from "node:path";
|
|
8321
8820
|
async function optima_validate_logic(worktree) {
|
|
8322
|
-
const rootCodemapPath =
|
|
8323
|
-
if (!
|
|
8821
|
+
const rootCodemapPath = path6.join(worktree, ".optima", "codemap.yml");
|
|
8822
|
+
if (!fs7.existsSync(rootCodemapPath)) return { ok: false, errors: [".optima/codemap.yml not found."], warnings: [] };
|
|
8324
8823
|
const errors = [];
|
|
8325
8824
|
const warnings = [];
|
|
8326
8825
|
const ig = (0, import_ignore2.default)();
|
|
8327
8826
|
ig.add(".git");
|
|
8328
|
-
const gitignorePath =
|
|
8329
|
-
if (
|
|
8330
|
-
ig.add(
|
|
8827
|
+
const gitignorePath = path6.join(worktree, ".gitignore");
|
|
8828
|
+
if (fs7.existsSync(gitignorePath)) {
|
|
8829
|
+
ig.add(fs7.readFileSync(gitignorePath, "utf8"));
|
|
8331
8830
|
}
|
|
8332
8831
|
const sourceExtensions = [
|
|
8333
8832
|
".js",
|
|
@@ -8367,30 +8866,30 @@ async function optima_validate_logic(worktree) {
|
|
|
8367
8866
|
const operationalFolders = [".optima", "templates", "dist"];
|
|
8368
8867
|
const isHiddenTree2 = (relPath2) => {
|
|
8369
8868
|
if (!relPath2) return false;
|
|
8370
|
-
return relPath2.split(
|
|
8869
|
+
return relPath2.split(path6.sep).some((part) => part.startsWith("."));
|
|
8371
8870
|
};
|
|
8372
|
-
const firstPathSegment2 = (relPath2) => relPath2.split(
|
|
8871
|
+
const firstPathSegment2 = (relPath2) => relPath2.split(path6.sep).filter(Boolean)[0] || "";
|
|
8373
8872
|
const isOperationalRelPath2 = (relPath2) => {
|
|
8374
8873
|
if (!relPath2) return false;
|
|
8375
8874
|
const firstSegment = firstPathSegment2(relPath2);
|
|
8376
8875
|
if (legacyOperationalRoots.has(firstSegment)) return true;
|
|
8377
|
-
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f +
|
|
8876
|
+
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f + path6.sep));
|
|
8378
8877
|
};
|
|
8379
8878
|
const getSectionEntries = (value) => {
|
|
8380
8879
|
if (Array.isArray(value)) return value;
|
|
8381
8880
|
if (value && typeof value === "object") return Object.values(value);
|
|
8382
8881
|
return [];
|
|
8383
8882
|
};
|
|
8384
|
-
const normalizeRelativePath = (relPath2) =>
|
|
8883
|
+
const normalizeRelativePath = (relPath2) => path6.normalize(relPath2).replace(/\\/g, "/").replace(/\/$/, "");
|
|
8385
8884
|
const isSourceDir = (dirPath) => {
|
|
8386
|
-
const dirRelPath =
|
|
8885
|
+
const dirRelPath = path6.relative(worktree, dirPath);
|
|
8387
8886
|
if (isOperationalRelPath2(dirRelPath)) return false;
|
|
8388
|
-
const items =
|
|
8887
|
+
const items = fs7.readdirSync(dirPath, { withFileTypes: true });
|
|
8389
8888
|
for (const item of items) {
|
|
8390
|
-
const childPath =
|
|
8391
|
-
const relPath2 =
|
|
8889
|
+
const childPath = path6.join(dirPath, item.name);
|
|
8890
|
+
const relPath2 = path6.relative(worktree, childPath);
|
|
8392
8891
|
if (ig.ignores(relPath2) || isOperationalRelPath2(relPath2)) continue;
|
|
8393
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
8892
|
+
if (item.isFile() && sourceExtensions.includes(path6.extname(item.name))) return true;
|
|
8394
8893
|
if (item.isDirectory()) {
|
|
8395
8894
|
if (isSourceDir(childPath)) return true;
|
|
8396
8895
|
}
|
|
@@ -8398,7 +8897,7 @@ async function optima_validate_logic(worktree) {
|
|
|
8398
8897
|
return false;
|
|
8399
8898
|
};
|
|
8400
8899
|
function validateMap(filePath) {
|
|
8401
|
-
const content =
|
|
8900
|
+
const content = fs7.readFileSync(filePath, "utf8");
|
|
8402
8901
|
let map;
|
|
8403
8902
|
try {
|
|
8404
8903
|
map = import_yaml2.default.parse(content);
|
|
@@ -8410,32 +8909,32 @@ async function optima_validate_logic(worktree) {
|
|
|
8410
8909
|
errors.push(`${filePath}: Invalid YAML.`);
|
|
8411
8910
|
return;
|
|
8412
8911
|
}
|
|
8413
|
-
const dir =
|
|
8912
|
+
const dir = path6.dirname(filePath);
|
|
8414
8913
|
const pathBase = filePath === rootCodemapPath ? worktree : dir;
|
|
8415
8914
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
8416
8915
|
const sectionsToVerify = ["modules", "entrypoints", "sources_of_truth", "links", "internals"];
|
|
8417
8916
|
for (const section of sectionsToVerify) {
|
|
8418
8917
|
for (const item of getSectionEntries(map[section])) {
|
|
8419
8918
|
if (item?.path) {
|
|
8420
|
-
indexedPaths.add(
|
|
8919
|
+
indexedPaths.add(path6.normalize(item.path));
|
|
8421
8920
|
if (section === "links" && (item.path.startsWith("http://") || item.path.startsWith("https://"))) {
|
|
8422
8921
|
continue;
|
|
8423
8922
|
}
|
|
8424
|
-
const absPath =
|
|
8425
|
-
if (!
|
|
8923
|
+
const absPath = path6.isAbsolute(item.path) ? item.path : path6.join(pathBase, item.path);
|
|
8924
|
+
if (!fs7.existsSync(absPath)) {
|
|
8426
8925
|
errors.push(`${filePath}: ${section.slice(0, -1)} path does not exist: ${item.path}`);
|
|
8427
8926
|
}
|
|
8428
8927
|
}
|
|
8429
8928
|
}
|
|
8430
8929
|
}
|
|
8431
|
-
const relDir =
|
|
8930
|
+
const relDir = path6.relative(worktree, dir);
|
|
8432
8931
|
const isOperational = isOperationalRelPath2(relDir);
|
|
8433
8932
|
if (map.scope === "module" && !isOperational) {
|
|
8434
|
-
const items =
|
|
8933
|
+
const items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
8435
8934
|
for (const item of items) {
|
|
8436
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
8935
|
+
if (item.isFile() && sourceExtensions.includes(path6.extname(item.name))) {
|
|
8437
8936
|
if (item.name === "codemap.yml") continue;
|
|
8438
|
-
if (!indexedPaths.has(
|
|
8937
|
+
if (!indexedPaths.has(path6.normalize(item.name))) {
|
|
8439
8938
|
errors.push(`${filePath}: Unindexed source file found: '${item.name}'. Every source file must be categorized in a section (e.g., 'internals').`);
|
|
8440
8939
|
}
|
|
8441
8940
|
}
|
|
@@ -8445,7 +8944,7 @@ async function optima_validate_logic(worktree) {
|
|
|
8445
8944
|
for (const key of pathKeys) {
|
|
8446
8945
|
for (const entry of getSectionEntries(map[key])) {
|
|
8447
8946
|
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://")) continue;
|
|
8448
|
-
if (
|
|
8947
|
+
if (path6.isAbsolute(entry.path)) continue;
|
|
8449
8948
|
const normalizedPath = normalizeRelativePath(entry.path);
|
|
8450
8949
|
if (!normalizedPath || normalizedPath === ".") continue;
|
|
8451
8950
|
const parts = normalizedPath.split("/").filter((p) => p && p !== ".");
|
|
@@ -8457,18 +8956,18 @@ async function optima_validate_logic(worktree) {
|
|
|
8457
8956
|
}
|
|
8458
8957
|
}
|
|
8459
8958
|
const walk = (dir) => {
|
|
8460
|
-
const relDir =
|
|
8959
|
+
const relDir = path6.relative(worktree, dir);
|
|
8461
8960
|
if (relDir && ig.ignores(relDir)) return;
|
|
8462
8961
|
if (isOperationalRelPath2(relDir)) return;
|
|
8463
8962
|
if (isHiddenTree2(relDir)) return;
|
|
8464
|
-
const hasCodemap =
|
|
8465
|
-
const items =
|
|
8466
|
-
if (!relDir.startsWith(
|
|
8963
|
+
const hasCodemap = fs7.existsSync(path6.join(dir, "codemap.yml"));
|
|
8964
|
+
const items = fs7.readdirSync(dir, { withFileTypes: true });
|
|
8965
|
+
if (!relDir.startsWith(path6.join(".optima", "tasks", "done"))) {
|
|
8467
8966
|
for (const item of items) {
|
|
8468
8967
|
if (item.isFile() && item.name.endsWith(".md")) {
|
|
8469
|
-
const content =
|
|
8968
|
+
const content = fs7.readFileSync(path6.join(dir, item.name), "utf8");
|
|
8470
8969
|
if (content.includes("[To be defined]") || content.includes("[Insert ")) {
|
|
8471
|
-
const relFilePath =
|
|
8970
|
+
const relFilePath = path6.join(relDir, item.name);
|
|
8472
8971
|
errors.push(`Documentation Placeholder found: '${relFilePath}' still contains [To be defined] or [Insert ...] placeholders.`);
|
|
8473
8972
|
}
|
|
8474
8973
|
}
|
|
@@ -8478,9 +8977,9 @@ async function optima_validate_logic(worktree) {
|
|
|
8478
8977
|
if (relDir !== "" && !hasCodemap && isSourceDir(dir) && !isOperational) {
|
|
8479
8978
|
errors.push(`Missing CodeMap: Directory '${relDir}' contains source but has no codemap.yml.`);
|
|
8480
8979
|
}
|
|
8481
|
-
if (hasCodemap) validateMap(
|
|
8980
|
+
if (hasCodemap) validateMap(path6.join(dir, "codemap.yml"));
|
|
8482
8981
|
for (const item of items) {
|
|
8483
|
-
if (item.isDirectory()) walk(
|
|
8982
|
+
if (item.isDirectory()) walk(path6.join(dir, item.name));
|
|
8484
8983
|
}
|
|
8485
8984
|
};
|
|
8486
8985
|
validateMap(rootCodemapPath);
|
|
@@ -8493,14 +8992,14 @@ async function optima_validate_logic(worktree) {
|
|
|
8493
8992
|
}
|
|
8494
8993
|
|
|
8495
8994
|
// src/constants.js
|
|
8496
|
-
import
|
|
8995
|
+
import path7 from "node:path";
|
|
8497
8996
|
import { fileURLToPath } from "node:url";
|
|
8498
|
-
var PKG_ROOT =
|
|
8499
|
-
var BUNDLE_ASSETS_DIR =
|
|
8500
|
-
var BUNDLE_AGENTS_DIR =
|
|
8501
|
-
var BUNDLE_POLICIES_DIR =
|
|
8502
|
-
var TEMPLATES_DIR =
|
|
8503
|
-
var HUMANS_REGISTRY_PATH =
|
|
8997
|
+
var PKG_ROOT = path7.resolve(path7.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8998
|
+
var BUNDLE_ASSETS_DIR = path7.join(PKG_ROOT, "assets");
|
|
8999
|
+
var BUNDLE_AGENTS_DIR = path7.join(BUNDLE_ASSETS_DIR, "agents");
|
|
9000
|
+
var BUNDLE_POLICIES_DIR = path7.join(BUNDLE_ASSETS_DIR, "policies");
|
|
9001
|
+
var TEMPLATES_DIR = path7.join(PKG_ROOT, "templates");
|
|
9002
|
+
var HUMANS_REGISTRY_PATH = path7.join(PKG_ROOT, "docs", "core", "humans.md");
|
|
8504
9003
|
var MANDATORY_AGENTS = /* @__PURE__ */ new Set(["product_manager", "business_analyst", "tech_lead"]);
|
|
8505
9004
|
var MINI_MODE_AGENTS = /* @__PURE__ */ new Set(["product_manager", "business_analyst", "tech_lead"]);
|
|
8506
9005
|
var CLICKUP_IGNORED_TASK_TYPES = ["Idea", "Backlog", "Hito", "Nota de reuni\xF3n", "Respuesta del formulario"];
|
|
@@ -8638,18 +9137,18 @@ var GITHUB_WEBHOOK_EVENTS = ["pull_request", "pull_request_review", "pull_reques
|
|
|
8638
9137
|
var OPTIMA_GITHUB_COMMITTER_NAME = "Optima Product Manager";
|
|
8639
9138
|
var OPTIMA_GITHUB_COMMITTER_EMAIL = "optima-product-manager[bot]@users.noreply.github.com";
|
|
8640
9139
|
function isRootDirectory(candidate) {
|
|
8641
|
-
const resolved =
|
|
8642
|
-
return resolved ===
|
|
9140
|
+
const resolved = path8.resolve(candidate);
|
|
9141
|
+
return resolved === path8.parse(resolved).root;
|
|
8643
9142
|
}
|
|
8644
9143
|
function isSafeWritableDirectory(candidate) {
|
|
8645
9144
|
if (typeof candidate !== "string" || !candidate.trim()) return false;
|
|
8646
|
-
if (!
|
|
8647
|
-
const resolved =
|
|
9145
|
+
if (!path8.isAbsolute(candidate)) return false;
|
|
9146
|
+
const resolved = path8.resolve(candidate);
|
|
8648
9147
|
if (isRootDirectory(resolved)) return false;
|
|
8649
9148
|
try {
|
|
8650
|
-
const stat =
|
|
9149
|
+
const stat = fs8.statSync(resolved);
|
|
8651
9150
|
if (!stat.isDirectory()) return false;
|
|
8652
|
-
|
|
9151
|
+
fs8.accessSync(resolved, fs8.constants.W_OK);
|
|
8653
9152
|
return true;
|
|
8654
9153
|
} catch {
|
|
8655
9154
|
return false;
|
|
@@ -8661,11 +9160,11 @@ function resolveSafeWorktree(context = {}, pluginWorktree = null) {
|
|
|
8661
9160
|
context?.directory,
|
|
8662
9161
|
pluginWorktree,
|
|
8663
9162
|
process.cwd(),
|
|
8664
|
-
|
|
9163
|
+
os2.homedir()
|
|
8665
9164
|
];
|
|
8666
9165
|
for (const candidate of candidates) {
|
|
8667
9166
|
if (typeof candidate !== "string" || !candidate.trim()) continue;
|
|
8668
|
-
const resolved =
|
|
9167
|
+
const resolved = path8.resolve(candidate);
|
|
8669
9168
|
if (isSafeWritableDirectory(resolved)) return resolved;
|
|
8670
9169
|
}
|
|
8671
9170
|
throw new Error(SAFE_WORKTREE_FAILURE);
|
|
@@ -8680,13 +9179,13 @@ function safeWorktreeOrFailure(context, pluginWorktree) {
|
|
|
8680
9179
|
function explicitSafeInputWorktree(input = {}) {
|
|
8681
9180
|
for (const candidate of [input?.worktree, input?.directory]) {
|
|
8682
9181
|
if (typeof candidate !== "string" || !candidate.trim()) continue;
|
|
8683
|
-
const resolved =
|
|
9182
|
+
const resolved = path8.resolve(candidate);
|
|
8684
9183
|
if (isSafeWritableDirectory(resolved)) return resolved;
|
|
8685
9184
|
}
|
|
8686
9185
|
return null;
|
|
8687
9186
|
}
|
|
8688
9187
|
function isGitRepository(worktree) {
|
|
8689
|
-
return
|
|
9188
|
+
return fs8.existsSync(path8.join(worktree, ".git"));
|
|
8690
9189
|
}
|
|
8691
9190
|
function normalizeLooseToken(value) {
|
|
8692
9191
|
return String(value ?? "").trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -8720,8 +9219,8 @@ function parseHumansRegistry(markdown = "") {
|
|
|
8720
9219
|
return roles;
|
|
8721
9220
|
}
|
|
8722
9221
|
function loadHumansRegistry(registryPath = HUMANS_REGISTRY_PATH) {
|
|
8723
|
-
if (!
|
|
8724
|
-
return parseHumansRegistry(
|
|
9222
|
+
if (!fs8.existsSync(registryPath)) return {};
|
|
9223
|
+
return parseHumansRegistry(fs8.readFileSync(registryPath, "utf8"));
|
|
8725
9224
|
}
|
|
8726
9225
|
function resolveHumanRoles(roles = CLICKUP_FINAL_APPROVER_ROLES, registry = loadHumansRegistry()) {
|
|
8727
9226
|
return [...new Set(roles.map((role) => registry[String(role ?? "").trim()] || "").filter(Boolean))];
|
|
@@ -8897,74 +9396,24 @@ function validateMainWorkspaceBranchSafety({ currentBranch, requiredBranch = "de
|
|
|
8897
9396
|
const normalized = String(currentBranch ?? "").trim();
|
|
8898
9397
|
if (!normalized) {
|
|
8899
9398
|
return { ok: false, branch: normalized, message: "Unable to determine the current branch; expected main workspace on dev." };
|
|
8900
|
-
}
|
|
8901
|
-
if (normalized === forbiddenBranch) {
|
|
8902
|
-
return { ok: false, branch: normalized, message: "Unsafe workspace branch: main is never allowed for Optima delivery work." };
|
|
8903
|
-
}
|
|
8904
|
-
if (normalized !== requiredBranch) {
|
|
8905
|
-
return { ok: false, branch: normalized, message: `Unsafe workspace branch: expected ${requiredBranch}, got ${normalized}.` };
|
|
8906
|
-
}
|
|
8907
|
-
return { ok: true, branch: normalized, message: `Workspace branch is safe on ${requiredBranch}.` };
|
|
8908
|
-
}
|
|
8909
|
-
var CLICKUP_REQUIRED_SUMMARY_SECTIONS = [
|
|
8910
|
-
"Summary",
|
|
8911
|
-
"Work Performed",
|
|
8912
|
-
"AC Coverage",
|
|
8913
|
-
"Verification Results",
|
|
8914
|
-
"Documentation Impact",
|
|
8915
|
-
"Open Risks",
|
|
8916
|
-
"Recommended Next Step"
|
|
8917
|
-
];
|
|
8918
|
-
var CLICKUP_RAW_LOG_SECTION_NAMES = /* @__PURE__ */ new Set(["Raw Logs", "Logs", "Full Logs", "Command Output", "Transcript"]);
|
|
8919
|
-
var CLICKUP_TRANSITIONS = /* @__PURE__ */ new Map([
|
|
8920
|
-
["plan->in progress", { status: "in progress", comment: "Plan complete; moving to implementation without generic CTO/PO assignment." }],
|
|
8921
|
-
["in progress->validation", { status: "validation", comment: "Implementation complete; ready for validation." }],
|
|
8922
|
-
["validation->merge", { status: "merge", assignFinalApprovers: true, parentOnlyFinalApproval: true, comment: "Parent validation passed with a functional preview URL; ready for CTO/PO approval flow." }],
|
|
8923
|
-
["validation->in progress", { status: "in progress", comment: "Validation failed; returning to implementation." }],
|
|
8924
|
-
["merge->completed", { status: "completed", comment: "Merge complete; closing delivery task." }],
|
|
8925
|
-
["completed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }],
|
|
8926
|
-
["closed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }]
|
|
8927
|
-
]);
|
|
8928
|
-
function parseMarkdownSections(markdown = "") {
|
|
8929
|
-
const sections = {};
|
|
8930
|
-
let current = null;
|
|
8931
|
-
let buffer = [];
|
|
8932
|
-
const flush = () => {
|
|
8933
|
-
if (!current) return;
|
|
8934
|
-
sections[current] = buffer.join("\n").trim();
|
|
8935
|
-
};
|
|
8936
|
-
for (const line of String(markdown).split(/\r?\n/)) {
|
|
8937
|
-
const heading = /^(#{2,3})\s+(.+?)\s*$/.exec(line);
|
|
8938
|
-
if (heading) {
|
|
8939
|
-
flush();
|
|
8940
|
-
current = heading[2].trim();
|
|
8941
|
-
buffer = [];
|
|
8942
|
-
continue;
|
|
8943
|
-
}
|
|
8944
|
-
if (current) buffer.push(line);
|
|
8945
|
-
}
|
|
8946
|
-
flush();
|
|
8947
|
-
return sections;
|
|
8948
|
-
}
|
|
8949
|
-
function parseMarkdownArtifact(markdown = "", { requiredSections = [] } = {}) {
|
|
8950
|
-
const sections = parseMarkdownSections(markdown);
|
|
8951
|
-
const missing = requiredSections.filter((section) => !sections[section]);
|
|
8952
|
-
return {
|
|
8953
|
-
ok: missing.length === 0,
|
|
8954
|
-
sections,
|
|
8955
|
-
missing,
|
|
8956
|
-
message: missing.length ? `Missing required section(s): ${missing.join(", ")}` : "ok"
|
|
8957
|
-
};
|
|
8958
|
-
}
|
|
8959
|
-
function readMarkdownArtifact(filePath, options = {}) {
|
|
8960
|
-
const markdown = fs5.readFileSync(filePath, "utf8");
|
|
8961
|
-
return parseMarkdownArtifact(markdown, options);
|
|
8962
|
-
}
|
|
8963
|
-
function stripRawLogSections(sections = {}) {
|
|
8964
|
-
return Object.fromEntries(
|
|
8965
|
-
Object.entries(sections).filter(([name]) => !CLICKUP_RAW_LOG_SECTION_NAMES.has(name))
|
|
8966
|
-
);
|
|
9399
|
+
}
|
|
9400
|
+
if (normalized === forbiddenBranch) {
|
|
9401
|
+
return { ok: false, branch: normalized, message: "Unsafe workspace branch: main is never allowed for Optima delivery work." };
|
|
9402
|
+
}
|
|
9403
|
+
if (normalized !== requiredBranch) {
|
|
9404
|
+
return { ok: false, branch: normalized, message: `Unsafe workspace branch: expected ${requiredBranch}, got ${normalized}.` };
|
|
9405
|
+
}
|
|
9406
|
+
return { ok: true, branch: normalized, message: `Workspace branch is safe on ${requiredBranch}.` };
|
|
8967
9407
|
}
|
|
9408
|
+
var CLICKUP_TRANSITIONS = /* @__PURE__ */ new Map([
|
|
9409
|
+
["plan->in progress", { status: "in progress", comment: "Plan complete; moving to implementation without generic CTO/PO assignment." }],
|
|
9410
|
+
["in progress->validation", { status: "validation", comment: "Implementation complete; ready for validation." }],
|
|
9411
|
+
["validation->merge", { status: "merge", assignFinalApprovers: true, parentOnlyFinalApproval: true, comment: "Parent validation passed with a functional preview URL; ready for CTO/PO approval flow." }],
|
|
9412
|
+
["validation->in progress", { status: "in progress", comment: "Validation failed; returning to implementation." }],
|
|
9413
|
+
["merge->completed", { status: "completed", comment: "Merge complete; closing delivery task." }],
|
|
9414
|
+
["completed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }],
|
|
9415
|
+
["closed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }]
|
|
9416
|
+
]);
|
|
8968
9417
|
var CLICKUP_SUBTASK_REQUIRED_FIELDS = ["Type", "Owner Role", "Story Points", "Slice", "Acceptance Criteria"];
|
|
8969
9418
|
var CLICKUP_SUBTASK_OPTIONAL_FIELDS = ["Depends On", "Branch", "Description", "Definition", "Documentation"];
|
|
8970
9419
|
var CLICKUP_SUBTASK_KNOWN_FIELDS = /* @__PURE__ */ new Set([...CLICKUP_SUBTASK_REQUIRED_FIELDS, ...CLICKUP_SUBTASK_OPTIONAL_FIELDS]);
|
|
@@ -9168,7 +9617,7 @@ function buildClickUpApplyPayloadResult({ payload, apply = false } = {}) {
|
|
|
9168
9617
|
noop: true,
|
|
9169
9618
|
applied: false,
|
|
9170
9619
|
message: "Dry-run only. Payload validated; no ClickUp calls were made.",
|
|
9171
|
-
payload: parsed
|
|
9620
|
+
payload: normalizeClickUpPayloadComments(parsed)
|
|
9172
9621
|
};
|
|
9173
9622
|
}
|
|
9174
9623
|
return {
|
|
@@ -9178,7 +9627,7 @@ function buildClickUpApplyPayloadResult({ payload, apply = false } = {}) {
|
|
|
9178
9627
|
applyRequested: true,
|
|
9179
9628
|
applied: false,
|
|
9180
9629
|
message: "Live ClickUp execution is not implemented/configured in this safe boundary; no ClickUp calls were made.",
|
|
9181
|
-
payload: parsed
|
|
9630
|
+
payload: normalizeClickUpPayloadComments(parsed)
|
|
9182
9631
|
};
|
|
9183
9632
|
}
|
|
9184
9633
|
function sectionValue(sections, names) {
|
|
@@ -9188,7 +9637,7 @@ function sectionValue(sections, names) {
|
|
|
9188
9637
|
return "";
|
|
9189
9638
|
}
|
|
9190
9639
|
function compactMarkdownValue(value = "") {
|
|
9191
|
-
return
|
|
9640
|
+
return normalizeClickUpMarkdown(value);
|
|
9192
9641
|
}
|
|
9193
9642
|
function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", taskMarkdown = "", taskPath = "", branch = "", worktree = "", pr = "" } = {}) {
|
|
9194
9643
|
const parsedSummary = parseMarkdownArtifact(summaryMarkdown, { requiredSections: CLICKUP_REQUIRED_SUMMARY_SECTIONS });
|
|
@@ -9213,13 +9662,12 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
9213
9662
|
deliveryPath ? `- Delivery evidence: ${compactMarkdownValue(deliveryPath)}` : null,
|
|
9214
9663
|
prValue ? `- PR: ${compactMarkdownValue(prValue)}` : null
|
|
9215
9664
|
].filter(Boolean);
|
|
9216
|
-
const comment =
|
|
9217
|
-
"
|
|
9218
|
-
"",
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
].filter((part) => part !== null).join("\n").trim();
|
|
9665
|
+
const comment = formatClickUpStatusComment({
|
|
9666
|
+
title: "Optima Delivery Summary",
|
|
9667
|
+
summary: "ClickUp sync payload generated from Optima Markdown artifacts.",
|
|
9668
|
+
sections: commentParts.map(([title, value]) => ({ title, body: compactMarkdownValue(value) || "Not specified." })),
|
|
9669
|
+
context: contextParts
|
|
9670
|
+
});
|
|
9223
9671
|
return {
|
|
9224
9672
|
ok: true,
|
|
9225
9673
|
mode: "payload",
|
|
@@ -9245,7 +9693,7 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
9245
9693
|
function deriveClickUpWorktree({ baseWorktree = "", taskId, taskType, parentTaskId, subtaskId } = {}) {
|
|
9246
9694
|
const branch = deriveClickUpBranchName({ taskType, parentTaskId, subtaskId, taskId });
|
|
9247
9695
|
const root = baseWorktree || process.cwd();
|
|
9248
|
-
return
|
|
9696
|
+
return path8.join(path8.dirname(root), `${path8.basename(root)}-${branch.replace(/\//g, "-")}`);
|
|
9249
9697
|
}
|
|
9250
9698
|
function clickUpCustomFieldValue(task = {}, names = []) {
|
|
9251
9699
|
const fields = Array.isArray(task.custom_fields) ? task.custom_fields : [];
|
|
@@ -9269,12 +9717,12 @@ function safeExistingClickUpWorktree({ metadata = {}, branch = "" } = {}) {
|
|
|
9269
9717
|
const taskMetadata = metadataTaskRouting(metadata);
|
|
9270
9718
|
const existingWorktree = typeof taskMetadata.worktree === "string" ? taskMetadata.worktree.trim() : "";
|
|
9271
9719
|
const existingBranch = typeof taskMetadata.branch === "string" ? taskMetadata.branch.trim() : "";
|
|
9272
|
-
if (!existingWorktree || !
|
|
9720
|
+
if (!existingWorktree || !path8.isAbsolute(existingWorktree) || !fs8.existsSync(existingWorktree)) return null;
|
|
9273
9721
|
try {
|
|
9274
|
-
const stat =
|
|
9722
|
+
const stat = fs8.statSync(existingWorktree);
|
|
9275
9723
|
if (!stat.isDirectory()) return null;
|
|
9276
9724
|
if (branch && existingBranch && existingBranch !== branch) return null;
|
|
9277
|
-
return { branch: existingBranch || branch, worktree:
|
|
9725
|
+
return { branch: existingBranch || branch, worktree: path8.resolve(existingWorktree), reused: true };
|
|
9278
9726
|
} catch {
|
|
9279
9727
|
return null;
|
|
9280
9728
|
}
|
|
@@ -9308,7 +9756,7 @@ function normalizeOptimaGitIdentity(identity = {}) {
|
|
|
9308
9756
|
};
|
|
9309
9757
|
}
|
|
9310
9758
|
function configureOptimaWorktreeGitIdentity({ worktreePath, identity = {}, runGitFn = runGit } = {}) {
|
|
9311
|
-
if (!worktreePath || !
|
|
9759
|
+
if (!worktreePath || !fs8.existsSync(worktreePath)) return { configured: false, reason: "worktree_missing" };
|
|
9312
9760
|
const normalized = normalizeOptimaGitIdentity(identity);
|
|
9313
9761
|
if (!normalized.name || !normalized.email) return { configured: false, reason: "identity_missing" };
|
|
9314
9762
|
try {
|
|
@@ -9394,17 +9842,17 @@ function openChamberEntryBranch(entry) {
|
|
|
9394
9842
|
return String(entry?.branch || entry?.branchName || entry?.branch_name || entry?.worktree?.branch || "").replace(/^refs\/heads\//, "");
|
|
9395
9843
|
}
|
|
9396
9844
|
function openChamberListIncludesDirectory(list, directory) {
|
|
9397
|
-
const resolved =
|
|
9845
|
+
const resolved = path8.resolve(directory);
|
|
9398
9846
|
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9399
9847
|
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9400
|
-
return entryDirectory &&
|
|
9848
|
+
return entryDirectory && path8.resolve(entryDirectory) === resolved;
|
|
9401
9849
|
});
|
|
9402
9850
|
}
|
|
9403
9851
|
function openChamberListIncludesBranch(list, directory, branch) {
|
|
9404
|
-
const resolved =
|
|
9852
|
+
const resolved = path8.resolve(directory);
|
|
9405
9853
|
return normalizeOpenChamberCollection(list).some((entry) => {
|
|
9406
9854
|
const entryDirectory = openChamberEntryDirectory(entry);
|
|
9407
|
-
if (!entryDirectory ||
|
|
9855
|
+
if (!entryDirectory || path8.resolve(entryDirectory) !== resolved) return false;
|
|
9408
9856
|
const entryBranch = openChamberEntryBranch(entry);
|
|
9409
9857
|
return !entryBranch || entryBranch === branch;
|
|
9410
9858
|
});
|
|
@@ -9412,8 +9860,8 @@ function openChamberListIncludesBranch(list, directory, branch) {
|
|
|
9412
9860
|
async function findOpenChamberProject({ opencodeBaseUrl, baseWorktree, fetchImpl = globalThis.fetch } = {}) {
|
|
9413
9861
|
const projects = await requestOpenCodeJson({ baseUrl: opencodeBaseUrl, endpoint: "/project", directory: baseWorktree, fetchImpl });
|
|
9414
9862
|
if (!Array.isArray(projects)) return null;
|
|
9415
|
-
const resolvedBase =
|
|
9416
|
-
return projects.find((project) =>
|
|
9863
|
+
const resolvedBase = path8.resolve(baseWorktree);
|
|
9864
|
+
return projects.find((project) => path8.resolve(String(project?.worktree || project?.path || "")) === resolvedBase) || null;
|
|
9417
9865
|
}
|
|
9418
9866
|
async function refreshOpenChamberProjectCopy({ opencodeBaseUrl, projectId, fetchImpl = globalThis.fetch } = {}) {
|
|
9419
9867
|
if (!projectId) return { refreshed: false, reason: "project_id_unavailable" };
|
|
@@ -9468,18 +9916,18 @@ async function createOpenChamberClickUpWorktree({ openchamberBaseUrl, baseUrl, b
|
|
|
9468
9916
|
}
|
|
9469
9917
|
const createdDirectory = openChamberEntryDirectory(created);
|
|
9470
9918
|
const createdBranch = openChamberEntryBranch(created);
|
|
9471
|
-
if (!createdDirectory || !
|
|
9919
|
+
if (!createdDirectory || !path8.isAbsolute(createdDirectory)) {
|
|
9472
9920
|
throw new Error(`OpenChamber did not return an absolute worktree path for ${branch}.`);
|
|
9473
9921
|
}
|
|
9474
9922
|
if (createdBranch !== branch) {
|
|
9475
9923
|
throw new Error(`OpenChamber created unexpected branch ${createdBranch || "<unknown>"}; expected ${branch}.`);
|
|
9476
9924
|
}
|
|
9477
9925
|
const verified = await verifyOpenChamberGitWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, worktreePath: createdDirectory, branch, fetchImpl });
|
|
9478
|
-
return { created, worktree:
|
|
9926
|
+
return { created, worktree: path8.resolve(createdDirectory), branch: createdBranch, verified };
|
|
9479
9927
|
}
|
|
9480
9928
|
async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencodeBaseUrl, baseUrl, baseWorktree, branch, worktreePath, fetchImpl = globalThis.fetch, source = "reuse" } = {}) {
|
|
9481
9929
|
const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
|
|
9482
|
-
return { branch, worktree:
|
|
9930
|
+
return { branch, worktree: path8.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
|
|
9483
9931
|
}
|
|
9484
9932
|
async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, openchamberBaseUrl = "", opencodeBaseUrl = "", baseUrl = "", fetchImpl = globalThis.fetch, log = null, gitIdentity = {} } = {}) {
|
|
9485
9933
|
const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
|
|
@@ -9497,7 +9945,7 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
|
|
|
9497
9945
|
return { ...registered, parentBranch: parentBranch || void 0, prTarget, gitIdentity: identity2 };
|
|
9498
9946
|
}
|
|
9499
9947
|
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
9500
|
-
if (
|
|
9948
|
+
if (fs8.existsSync(worktreePath)) {
|
|
9501
9949
|
const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
|
|
9502
9950
|
const identity2 = configureOptimaWorktreeGitIdentity({ worktreePath: registered.worktree, identity: gitIdentity, runGitFn });
|
|
9503
9951
|
log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "existing_directory", visibility: registered.openChamber.visibility });
|
|
@@ -9506,7 +9954,7 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
|
|
|
9506
9954
|
let parentBootstrap = null;
|
|
9507
9955
|
if (isSubtask) {
|
|
9508
9956
|
const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
|
|
9509
|
-
if (
|
|
9957
|
+
if (fs8.existsSync(parentWorktree)) {
|
|
9510
9958
|
const registeredParent = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
|
|
9511
9959
|
const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: registeredParent.worktree, identity: gitIdentity, runGitFn });
|
|
9512
9960
|
parentBootstrap = { branch: parentBranch, worktree: registeredParent.worktree, reused: true, provider: "openchamber", visibility: registeredParent.openChamber.visibility };
|
|
@@ -9549,33 +9997,33 @@ function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tare
|
|
|
9549
9997
|
const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
|
|
9550
9998
|
if (existing) return { ...existing, branch, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: existing.worktree, identity: gitIdentity, runGitFn }) };
|
|
9551
9999
|
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
9552
|
-
if (
|
|
9553
|
-
const resolvedWorktree2 =
|
|
10000
|
+
if (fs8.existsSync(worktreePath)) {
|
|
10001
|
+
const resolvedWorktree2 = path8.resolve(worktreePath);
|
|
9554
10002
|
return { branch, worktree: resolvedWorktree2, reused: true, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: resolvedWorktree2, identity: gitIdentity, runGitFn }) };
|
|
9555
10003
|
}
|
|
9556
10004
|
if (allowNonGitFallback) {
|
|
9557
|
-
|
|
9558
|
-
return { branch, worktree:
|
|
10005
|
+
fs8.mkdirSync(worktreePath, { recursive: true });
|
|
10006
|
+
return { branch, worktree: path8.resolve(worktreePath), reused: false, fallback: "non_git", parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", startPoint: parentBranch || "dev" };
|
|
9559
10007
|
}
|
|
9560
10008
|
let parentBootstrap = null;
|
|
9561
10009
|
if (isSubtask) {
|
|
9562
10010
|
const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
|
|
9563
|
-
if (!
|
|
10011
|
+
if (!fs8.existsSync(parentWorktree)) {
|
|
9564
10012
|
const parentStartPoint = resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9565
10013
|
const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
|
|
9566
10014
|
addClickUpWorktreeForBranch({ baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, runGitFn });
|
|
9567
|
-
parentBootstrap = { branch: parentBranch, worktree:
|
|
10015
|
+
parentBootstrap = { branch: parentBranch, worktree: path8.resolve(parentWorktree), startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists };
|
|
9568
10016
|
const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: parentBootstrap.worktree, identity: gitIdentity, runGitFn });
|
|
9569
10017
|
if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
|
|
9570
10018
|
} else {
|
|
9571
|
-
parentBootstrap = { branch: parentBranch, worktree:
|
|
10019
|
+
parentBootstrap = { branch: parentBranch, worktree: path8.resolve(parentWorktree), reused: true };
|
|
9572
10020
|
const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: parentBootstrap.worktree, identity: gitIdentity, runGitFn });
|
|
9573
10021
|
if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
|
|
9574
10022
|
}
|
|
9575
10023
|
}
|
|
9576
10024
|
const startPoint = isSubtask ? parentBranch : resolveClickUpDevStartPoint(baseWorktree, runGitFn);
|
|
9577
10025
|
addClickUpWorktreeForBranch({ baseWorktree, branch, worktreePath, startPoint, runGitFn });
|
|
9578
|
-
const resolvedWorktree =
|
|
10026
|
+
const resolvedWorktree = path8.resolve(worktreePath);
|
|
9579
10027
|
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 }) };
|
|
9580
10028
|
}
|
|
9581
10029
|
function normalizeClickUpDefinitionDocParent(parent = {}) {
|
|
@@ -9658,7 +10106,17 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
9658
10106
|
wouldCreate: true
|
|
9659
10107
|
},
|
|
9660
10108
|
clickup: {
|
|
9661
|
-
comment:
|
|
10109
|
+
comment: formatClickUpStatusComment({
|
|
10110
|
+
title: "Task Workspace Ready",
|
|
10111
|
+
summary: "Optima prepared the task workspace and validation target.",
|
|
10112
|
+
context: [
|
|
10113
|
+
`Branch: ${branch}`,
|
|
10114
|
+
`Start from: ${startFrom}`,
|
|
10115
|
+
`Worktree: ${worktree}`,
|
|
10116
|
+
`Validation PR target: ${requiredPullRequest.targetBranch}`,
|
|
10117
|
+
`Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}`
|
|
10118
|
+
]
|
|
10119
|
+
}),
|
|
9662
10120
|
description,
|
|
9663
10121
|
fields: {
|
|
9664
10122
|
Definition: definitionContent,
|
|
@@ -9744,7 +10202,18 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
|
|
|
9744
10202
|
remove: removalTargets,
|
|
9745
10203
|
objective: assignsFinalApprovers ? "zero_product_manager_assigned_tasks" : "preserve_existing_owner_policy"
|
|
9746
10204
|
},
|
|
9747
|
-
comment:
|
|
10205
|
+
comment: formatClickUpStatusComment({
|
|
10206
|
+
title: "Workflow Status Update",
|
|
10207
|
+
summary: rule.comment,
|
|
10208
|
+
context: [
|
|
10209
|
+
`From: ${from}`,
|
|
10210
|
+
`To: ${rule.status}`,
|
|
10211
|
+
requiredPullRequest?.prUrl ? `PR: ${requiredPullRequest.prUrl}` : null,
|
|
10212
|
+
requiredPullRequest?.prNumber ? `PR number: ${requiredPullRequest.prNumber}` : null,
|
|
10213
|
+
requiredPullRequest?.sourceBranch ? `Source branch: ${requiredPullRequest.sourceBranch}` : null,
|
|
10214
|
+
requiredPullRequest?.targetBranch ? `Target branch: ${requiredPullRequest.targetBranch}` : null
|
|
10215
|
+
].filter(Boolean)
|
|
10216
|
+
}),
|
|
9748
10217
|
description,
|
|
9749
10218
|
fields,
|
|
9750
10219
|
definition_doc: definitionContent ? {
|
|
@@ -9757,8 +10226,8 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
|
|
|
9757
10226
|
}
|
|
9758
10227
|
function ensureOptimaGitignoreRules(worktree) {
|
|
9759
10228
|
if (!isGitRepository(worktree)) return { touched: false, added: [] };
|
|
9760
|
-
const gitignorePath =
|
|
9761
|
-
const existing =
|
|
10229
|
+
const gitignorePath = path8.join(worktree, ".gitignore");
|
|
10230
|
+
const existing = fs8.existsSync(gitignorePath) ? fs8.readFileSync(gitignorePath, "utf8") : "";
|
|
9762
10231
|
const existingRules = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
9763
10232
|
const missingRules = OPTIMA_GITIGNORE_RULES.filter((rule) => !existingRules.has(rule));
|
|
9764
10233
|
if (missingRules.length === 0) return { touched: false, added: [] };
|
|
@@ -9768,45 +10237,45 @@ function ensureOptimaGitignoreRules(worktree) {
|
|
|
9768
10237
|
"# Optima local/private state",
|
|
9769
10238
|
...missingRules
|
|
9770
10239
|
].join("\n");
|
|
9771
|
-
|
|
10240
|
+
fs8.writeFileSync(gitignorePath, `${existing}${prefix}${separator}${block}
|
|
9772
10241
|
`, "utf8");
|
|
9773
10242
|
return { touched: true, added: missingRules };
|
|
9774
10243
|
}
|
|
9775
10244
|
function optimaDir(worktree) {
|
|
9776
|
-
return
|
|
10245
|
+
return path8.join(worktree, OPTIMA_DIRNAME);
|
|
9777
10246
|
}
|
|
9778
10247
|
function optimaLocalConfigDir(worktree) {
|
|
9779
|
-
return
|
|
10248
|
+
return path8.join(optimaDir(worktree), ".config");
|
|
9780
10249
|
}
|
|
9781
10250
|
function optimaConfigDir(worktree) {
|
|
9782
10251
|
return optimaLocalConfigDir(worktree);
|
|
9783
10252
|
}
|
|
9784
10253
|
function optimaCodemapPath(worktree) {
|
|
9785
|
-
return
|
|
10254
|
+
return path8.join(optimaDir(worktree), "codemap.yml");
|
|
9786
10255
|
}
|
|
9787
10256
|
function optimaTasksDir(worktree) {
|
|
9788
|
-
return
|
|
10257
|
+
return path8.join(optimaDir(worktree), "tasks");
|
|
9789
10258
|
}
|
|
9790
10259
|
function optimaEvidencesDir(worktree) {
|
|
9791
|
-
return
|
|
10260
|
+
return path8.join(optimaDir(worktree), "evidences");
|
|
9792
10261
|
}
|
|
9793
10262
|
function optimaScrsDir(worktree) {
|
|
9794
|
-
return
|
|
10263
|
+
return path8.join(optimaDir(worktree), "docs", "scrs");
|
|
9795
10264
|
}
|
|
9796
10265
|
function legacyNomadworkDir(worktree) {
|
|
9797
|
-
return
|
|
10266
|
+
return path8.join(worktree, LEGACY_NOMADWORK_DIRNAME);
|
|
9798
10267
|
}
|
|
9799
10268
|
function legacyNomadworksDir(worktree) {
|
|
9800
|
-
return
|
|
10269
|
+
return path8.join(worktree, LEGACY_NOMADWORKS_DIRNAME);
|
|
9801
10270
|
}
|
|
9802
10271
|
function legacyOrbitaDir(worktree) {
|
|
9803
|
-
return
|
|
10272
|
+
return path8.join(worktree, LEGACY_ORBITA_DIRNAME);
|
|
9804
10273
|
}
|
|
9805
10274
|
function legacyStaticEngDir(worktree) {
|
|
9806
|
-
return
|
|
10275
|
+
return path8.join(worktree, LEGACY_STATICENG_DIRNAME);
|
|
9807
10276
|
}
|
|
9808
10277
|
function repoConfigPath(worktree) {
|
|
9809
|
-
return
|
|
10278
|
+
return path8.join(optimaConfigDir(worktree), "optima.yaml");
|
|
9810
10279
|
}
|
|
9811
10280
|
function normalizeLegacyDiscussionEntry(entry) {
|
|
9812
10281
|
if (!entry || typeof entry !== "object") return entry;
|
|
@@ -9819,25 +10288,25 @@ function normalizeLegacyDiscussionEntry(entry) {
|
|
|
9819
10288
|
return next;
|
|
9820
10289
|
}
|
|
9821
10290
|
function repoPoliciesDir(worktree) {
|
|
9822
|
-
return
|
|
10291
|
+
return path8.join(optimaDir(worktree), "policies");
|
|
9823
10292
|
}
|
|
9824
10293
|
function generatedPoliciesDir(worktree) {
|
|
9825
|
-
return
|
|
10294
|
+
return path8.join(optimaLocalConfigDir(worktree), "generated", "policies");
|
|
9826
10295
|
}
|
|
9827
10296
|
function generatedAgentsDir(worktree) {
|
|
9828
|
-
return
|
|
10297
|
+
return path8.join(optimaLocalConfigDir(worktree), "generated", "agents");
|
|
9829
10298
|
}
|
|
9830
10299
|
function repoAgentsDir(worktree) {
|
|
9831
|
-
return
|
|
10300
|
+
return path8.join(optimaDir(worktree), "agents");
|
|
9832
10301
|
}
|
|
9833
10302
|
function repoAgentAdditionsDir(worktree) {
|
|
9834
|
-
return
|
|
10303
|
+
return path8.join(optimaDir(worktree), "agent-additions");
|
|
9835
10304
|
}
|
|
9836
10305
|
function legacyRepoAgentsDir(worktree) {
|
|
9837
|
-
return
|
|
10306
|
+
return path8.join(legacyNomadworksDir(worktree), "agents");
|
|
9838
10307
|
}
|
|
9839
10308
|
function runtimeDiscussionRegistryPath(worktree) {
|
|
9840
|
-
return
|
|
10309
|
+
return path8.join(optimaLocalConfigDir(worktree), "runtime", "discussions.json");
|
|
9841
10310
|
}
|
|
9842
10311
|
function resolveConfigPath(worktree) {
|
|
9843
10312
|
return repoConfigPath(worktree);
|
|
@@ -9880,32 +10349,32 @@ function mergeClickUpAgentMetadata(existing, update = {}) {
|
|
|
9880
10349
|
return JSON.stringify(sortJsonValue(merged), null, 2);
|
|
9881
10350
|
}
|
|
9882
10351
|
function optimaRuntimeDir(worktree) {
|
|
9883
|
-
return
|
|
10352
|
+
return path8.join(optimaLocalConfigDir(worktree), "runtime");
|
|
9884
10353
|
}
|
|
9885
10354
|
function clickUpWebhookStatePath(worktree) {
|
|
9886
|
-
return
|
|
10355
|
+
return path8.join(optimaRuntimeDir(worktree), "clickup-webhook.json");
|
|
9887
10356
|
}
|
|
9888
10357
|
function clickUpCommentLedgerPath(worktree) {
|
|
9889
|
-
return
|
|
10358
|
+
return path8.join(optimaRuntimeDir(worktree), "clickup-comment-ledger.jsonl");
|
|
9890
10359
|
}
|
|
9891
10360
|
function clickUpWebhookLogPath(worktree) {
|
|
9892
|
-
return
|
|
10361
|
+
return path8.join(optimaRuntimeDir(worktree), "clickup-webhook.log.jsonl");
|
|
9893
10362
|
}
|
|
9894
10363
|
function normalizeClickUpWebhookLogLevel(value) {
|
|
9895
10364
|
const level = String(value || "info").trim().toLowerCase();
|
|
9896
10365
|
return CLICKUP_WEBHOOK_LOG_LEVELS.has(level) ? level : "info";
|
|
9897
10366
|
}
|
|
9898
10367
|
function clickUpWebhookAuditLogDir() {
|
|
9899
|
-
const dataHome = process.env.XDG_DATA_HOME &&
|
|
9900
|
-
return
|
|
10368
|
+
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");
|
|
10369
|
+
return path8.join(dataHome, "opencode-optima");
|
|
9901
10370
|
}
|
|
9902
10371
|
function clickUpWebhookAuditLogPath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
|
|
9903
|
-
return
|
|
10372
|
+
return path8.join(logDir, `clickup-webhook-${at.toISOString().slice(0, 10)}.jsonl`);
|
|
9904
10373
|
}
|
|
9905
10374
|
function clickUpWebhookRequestFilePath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
|
|
9906
10375
|
const stamp = at.toISOString().replace(/:/g, "-").replace(/\./g, "-");
|
|
9907
|
-
const shortId =
|
|
9908
|
-
return
|
|
10376
|
+
const shortId = crypto2.randomBytes(4).toString("hex");
|
|
10377
|
+
return path8.join(logDir, "requests", `clickup-webhook-${stamp}-${shortId}.json`);
|
|
9909
10378
|
}
|
|
9910
10379
|
function findOptimaPluginTupleOptions(pluginEntries = []) {
|
|
9911
10380
|
if (!Array.isArray(pluginEntries)) return null;
|
|
@@ -10065,7 +10534,7 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
10065
10534
|
};
|
|
10066
10535
|
const errors = [];
|
|
10067
10536
|
if (!config.enabled) errors.push("clickup.enabled must be true");
|
|
10068
|
-
if (!config.basePath || !
|
|
10537
|
+
if (!config.basePath || !path8.isAbsolute(config.basePath)) errors.push("clickup.base_path must be an absolute path");
|
|
10069
10538
|
if (!config.teamId) errors.push("clickup.team_id is required");
|
|
10070
10539
|
if (!config.apiToken) errors.push("clickup.api_token is required");
|
|
10071
10540
|
if (!config.webhook.publicUrl) errors.push("clickup.webhook.public_url is required");
|
|
@@ -10128,503 +10597,94 @@ function sanitizeClickUpWebhookState(state = {}, config = null) {
|
|
|
10128
10597
|
}
|
|
10129
10598
|
function readClickUpWebhookState(worktree, config = null) {
|
|
10130
10599
|
const statePath = clickUpWebhookStatePath(worktree);
|
|
10131
|
-
if (!
|
|
10600
|
+
if (!fs8.existsSync(statePath)) return sanitizeClickUpWebhookState({}, config);
|
|
10132
10601
|
try {
|
|
10133
|
-
return sanitizeClickUpWebhookState(JSON.parse(
|
|
10602
|
+
return sanitizeClickUpWebhookState(JSON.parse(fs8.readFileSync(statePath, "utf8")), config);
|
|
10134
10603
|
} catch {
|
|
10135
10604
|
return sanitizeClickUpWebhookState({}, config);
|
|
10136
10605
|
}
|
|
10137
10606
|
}
|
|
10138
10607
|
function writeClickUpWebhookState(worktree, state, config = null) {
|
|
10139
10608
|
const statePath = clickUpWebhookStatePath(worktree);
|
|
10140
|
-
|
|
10609
|
+
fs8.mkdirSync(path8.dirname(statePath), { recursive: true, mode: 448 });
|
|
10141
10610
|
const next = sanitizeClickUpWebhookState(state, config);
|
|
10142
|
-
|
|
10611
|
+
fs8.writeFileSync(statePath, `${JSON.stringify(next, null, 2)}
|
|
10143
10612
|
`, { encoding: "utf8", mode: 384 });
|
|
10144
10613
|
try {
|
|
10145
|
-
|
|
10614
|
+
fs8.chmodSync(statePath, 384);
|
|
10146
10615
|
} catch {
|
|
10147
10616
|
}
|
|
10148
10617
|
return next;
|
|
10149
10618
|
}
|
|
10150
10619
|
function isClickUpWebhookStateActive(state, config) {
|
|
10151
|
-
const expectedEvents = new Set(config?.webhook?.events || []);
|
|
10152
|
-
const actualEvents = new Set(state?.events || []);
|
|
10153
|
-
const hasEvents = [...expectedEvents].every((event) => actualEvents.has(event));
|
|
10154
|
-
return Boolean(
|
|
10155
|
-
state?.active && state?.webhookId && state?.secret && state?.publicUrl === config?.webhook?.publicUrl && hasEvents
|
|
10156
|
-
);
|
|
10157
|
-
}
|
|
10158
|
-
function
|
|
10159
|
-
const
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
}
|
|
10164
|
-
function resolveSecretReference(value = "") {
|
|
10165
|
-
const raw = String(value || "").trim();
|
|
10166
|
-
const envMatch = raw.match(/^\{env:([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
10167
|
-
if (envMatch) return process.env[envMatch[1]] || "";
|
|
10168
|
-
const fileMatch = raw.match(/^\{file:(.+)\}$/);
|
|
10169
|
-
if (fileMatch) {
|
|
10170
|
-
try {
|
|
10171
|
-
return fs5.readFileSync(expandHomePath(fileMatch[1]), "utf8").trim();
|
|
10172
|
-
} catch {
|
|
10173
|
-
return "";
|
|
10174
|
-
}
|
|
10175
|
-
}
|
|
10176
|
-
return raw;
|
|
10177
|
-
}
|
|
10178
|
-
function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
|
|
10179
|
-
const token = resolveSecretReference(config?.apiToken);
|
|
10180
|
-
const request = async (url, options = {}) => {
|
|
10181
|
-
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a ClickUp client for live webhook setup");
|
|
10182
|
-
if (!token) throw new Error("ClickUp API token is not available");
|
|
10183
|
-
const response = await fetchImpl(url, {
|
|
10184
|
-
...options,
|
|
10185
|
-
headers: {
|
|
10186
|
-
Authorization: token,
|
|
10187
|
-
"Content-Type": "application/json",
|
|
10188
|
-
...options.headers || {}
|
|
10189
|
-
}
|
|
10190
|
-
});
|
|
10191
|
-
if (!response.ok) throw new Error(`ClickUp API request failed: ${response.status}`);
|
|
10192
|
-
return response.json();
|
|
10193
|
-
};
|
|
10194
|
-
return {
|
|
10195
|
-
async createWebhook({ endpoint, events, location }) {
|
|
10196
|
-
const body = { endpoint, events, ...Object.fromEntries(Object.entries(location || {}).filter(([, value]) => value)) };
|
|
10197
|
-
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`, {
|
|
10198
|
-
method: "POST",
|
|
10199
|
-
body: JSON.stringify(body)
|
|
10200
|
-
});
|
|
10201
|
-
},
|
|
10202
|
-
async listWebhooks() {
|
|
10203
|
-
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`);
|
|
10204
|
-
},
|
|
10205
|
-
async deleteWebhook(webhookId) {
|
|
10206
|
-
return request(`https://api.clickup.com/api/v2/webhook/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
|
|
10207
|
-
},
|
|
10208
|
-
async getAuthorizedUser() {
|
|
10209
|
-
return request("https://api.clickup.com/api/v2/user");
|
|
10210
|
-
},
|
|
10211
|
-
async getTask(taskId) {
|
|
10212
|
-
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}`);
|
|
10213
|
-
},
|
|
10214
|
-
async updateTaskMetadata({ taskId, fieldId, value }) {
|
|
10215
|
-
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/field/${encodeURIComponent(fieldId)}`, {
|
|
10216
|
-
method: "POST",
|
|
10217
|
-
body: JSON.stringify({ value })
|
|
10218
|
-
});
|
|
10219
|
-
},
|
|
10220
|
-
async postTaskComment() {
|
|
10221
|
-
throw new Error("Optima runtime does not post ClickUp task comments; use local logs for process failures.");
|
|
10222
|
-
},
|
|
10223
|
-
async addTaskTag() {
|
|
10224
|
-
throw new Error("Optima runtime does not add ClickUp task tags; use local logs for process failures.");
|
|
10225
|
-
},
|
|
10226
|
-
async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10227
|
-
const params = new URLSearchParams();
|
|
10228
|
-
if (assigneeId) params.append("assignees[]", assigneeId);
|
|
10229
|
-
for (const status of statuses) if (status) params.append("statuses[]", status);
|
|
10230
|
-
if (limit) params.append("limit", String(limit));
|
|
10231
|
-
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/task?${params.toString()}`);
|
|
10232
|
-
},
|
|
10233
|
-
async getTaskComments({ taskId, start, limit = CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT } = {}) {
|
|
10234
|
-
const params = new URLSearchParams();
|
|
10235
|
-
if (start) params.set("start", String(start));
|
|
10236
|
-
if (limit) params.set("limit", String(limit));
|
|
10237
|
-
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
10238
|
-
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/comment${suffix}`);
|
|
10239
|
-
}
|
|
10240
|
-
};
|
|
10241
|
-
}
|
|
10242
|
-
function base64UrlJson(value) {
|
|
10243
|
-
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
10244
|
-
}
|
|
10245
|
-
function resolveGitHubAppPrivateKey(app = {}) {
|
|
10246
|
-
const direct = resolveSecretReference(app.privateKey || "");
|
|
10247
|
-
if (direct) return direct.replace(/\\n/g, "\n");
|
|
10248
|
-
const file = expandHomePath(app.privateKeyFile || "");
|
|
10249
|
-
if (!file) return "";
|
|
10250
|
-
try {
|
|
10251
|
-
return fs5.readFileSync(file, "utf8").trim().replace(/\\n/g, "\n");
|
|
10252
|
-
} catch {
|
|
10253
|
-
return "";
|
|
10254
|
-
}
|
|
10255
|
-
}
|
|
10256
|
-
function createGitHubAppJwt({ appId, privateKey, now = () => /* @__PURE__ */ new Date() } = {}) {
|
|
10257
|
-
const key = String(privateKey || "").trim();
|
|
10258
|
-
const issuer = String(appId || "").trim();
|
|
10259
|
-
if (!issuer || !key) throw new Error("GitHub App app_id and private key are required");
|
|
10260
|
-
const nowSeconds = Math.floor(now().getTime() / 1e3);
|
|
10261
|
-
const header = base64UrlJson({ alg: "RS256", typ: "JWT" });
|
|
10262
|
-
const payload = base64UrlJson({ iat: nowSeconds - 60, exp: nowSeconds + 540, iss: issuer });
|
|
10263
|
-
const unsigned = `${header}.${payload}`;
|
|
10264
|
-
const signature = crypto.createSign("RSA-SHA256").update(unsigned).end().sign(key, "base64url");
|
|
10265
|
-
return `${unsigned}.${signature}`;
|
|
10266
|
-
}
|
|
10267
|
-
function encodeGitHubPathSegment(value) {
|
|
10268
|
-
return encodeURIComponent(String(value || ""));
|
|
10269
|
-
}
|
|
10270
|
-
function encodeGitHubBranchRef(branch = "") {
|
|
10271
|
-
return String(branch || "").split("/").map(encodeGitHubPathSegment).join("/");
|
|
10272
|
-
}
|
|
10273
|
-
function parseNullSeparatedGitOutput(output = "") {
|
|
10274
|
-
return String(output || "").split("\0").map((item) => item.trim()).filter(Boolean);
|
|
10275
|
-
}
|
|
10276
|
-
function listWorktreeChangedPaths(worktree, runGitFn = runGit) {
|
|
10277
|
-
const tracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["diff", "--name-only", "-z", "HEAD", "--"]));
|
|
10278
|
-
const untracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["ls-files", "--others", "--exclude-standard", "-z", "--"]));
|
|
10279
|
-
return [.../* @__PURE__ */ new Set([...tracked, ...untracked])].filter((item) => item && !item.startsWith("../") && !path6.isAbsolute(item));
|
|
10280
|
-
}
|
|
10281
|
-
function currentGitBranch(worktree, runGitFn = runGit) {
|
|
10282
|
-
return String(runGitFn(worktree, ["rev-parse", "--abbrev-ref", "HEAD"]) || "").trim();
|
|
10283
|
-
}
|
|
10284
|
-
function treeEntryForWorktreePath(worktree, gitPath) {
|
|
10285
|
-
const absolutePath = path6.join(worktree, ...String(gitPath || "").split("/"));
|
|
10286
|
-
if (!fs5.existsSync(absolutePath)) return { path: gitPath, mode: "100644", type: "blob", sha: null, deleted: true };
|
|
10287
|
-
const stat = fs5.lstatSync(absolutePath);
|
|
10288
|
-
if (stat.isDirectory()) return null;
|
|
10289
|
-
if (stat.isSymbolicLink()) {
|
|
10290
|
-
return {
|
|
10291
|
-
path: gitPath,
|
|
10292
|
-
mode: "120000",
|
|
10293
|
-
type: "blob",
|
|
10294
|
-
content: fs5.readlinkSync(absolutePath),
|
|
10295
|
-
encoding: "utf-8"
|
|
10296
|
-
};
|
|
10297
|
-
}
|
|
10298
|
-
return {
|
|
10299
|
-
path: gitPath,
|
|
10300
|
-
mode: stat.mode & 73 ? "100755" : "100644",
|
|
10301
|
-
type: "blob",
|
|
10302
|
-
content: fs5.readFileSync(absolutePath).toString("base64"),
|
|
10303
|
-
encoding: "base64"
|
|
10304
|
-
};
|
|
10305
|
-
}
|
|
10306
|
-
function appendGitHubQuery(pathname, params = {}) {
|
|
10307
|
-
const query = new URLSearchParams();
|
|
10308
|
-
for (const [key, value] of Object.entries(params || {})) {
|
|
10309
|
-
if (value === void 0 || value === null || value === "") continue;
|
|
10310
|
-
query.set(key, String(value));
|
|
10311
|
-
}
|
|
10312
|
-
const suffix = query.toString();
|
|
10313
|
-
return suffix ? `${pathname}?${suffix}` : pathname;
|
|
10314
|
-
}
|
|
10315
|
-
function timestampMs(value = "") {
|
|
10316
|
-
const parsed = Date.parse(String(value || ""));
|
|
10317
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
10318
|
-
}
|
|
10319
|
-
function sortNewestFirst(items = []) {
|
|
10320
|
-
return [...items].sort((a, b) => {
|
|
10321
|
-
const bTime = Math.max(timestampMs(b?.updated_at), timestampMs(b?.created_at));
|
|
10322
|
-
const aTime = Math.max(timestampMs(a?.updated_at), timestampMs(a?.created_at));
|
|
10323
|
-
return bTime - aTime;
|
|
10324
|
-
});
|
|
10325
|
-
}
|
|
10326
|
-
function matchesVercelStatusContext(context = "", expected = "") {
|
|
10327
|
-
const actual = String(context || "").trim();
|
|
10328
|
-
const wanted = String(expected || "").trim();
|
|
10329
|
-
if (wanted && actual === wanted) return true;
|
|
10330
|
-
const lower = actual.toLowerCase();
|
|
10331
|
-
if (!lower.includes("vercel")) return false;
|
|
10332
|
-
if (!wanted) return lower.includes("preproduction") || lower.includes("defend-preproduction");
|
|
10333
|
-
const wantedLower = wanted.toLowerCase();
|
|
10334
|
-
return wantedLower.split(/\s+|–|-/).filter((part) => part && part !== "vercel").every((part) => lower.includes(part));
|
|
10335
|
-
}
|
|
10336
|
-
function selectFunctionalDeploymentUrl(status = {}, deployment = {}) {
|
|
10337
|
-
for (const candidate of [
|
|
10338
|
-
status?.environment_url,
|
|
10339
|
-
deployment?.environment_url,
|
|
10340
|
-
status?.target_url,
|
|
10341
|
-
deployment?.target_url
|
|
10342
|
-
]) {
|
|
10343
|
-
const value = String(candidate || "").trim();
|
|
10344
|
-
if (!value) continue;
|
|
10345
|
-
try {
|
|
10346
|
-
const url = new URL(value);
|
|
10347
|
-
if (url.protocol === "http:" || url.protocol === "https:") return value;
|
|
10348
|
-
} catch {
|
|
10349
|
-
}
|
|
10350
|
-
}
|
|
10351
|
-
return "";
|
|
10352
|
-
}
|
|
10353
|
-
async function checkFunctionalDeploymentUrl(url, fetchImpl = globalThis.fetch) {
|
|
10354
|
-
const target = String(url || "").trim();
|
|
10355
|
-
if (!target) return { ok: false, reason: "url_missing" };
|
|
10356
|
-
if (typeof fetchImpl !== "function") return { ok: false, reason: "fetch_unavailable", url: target };
|
|
10357
|
-
const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(8e3) : void 0;
|
|
10358
|
-
const probe = async (method) => {
|
|
10359
|
-
const response = await fetchImpl(target, { method, redirect: "follow", signal });
|
|
10360
|
-
return {
|
|
10361
|
-
ok: response.status >= 200 && response.status < 400,
|
|
10362
|
-
status: response.status,
|
|
10363
|
-
statusText: response.statusText || "",
|
|
10364
|
-
url: response.url || target,
|
|
10365
|
-
method
|
|
10366
|
-
};
|
|
10367
|
-
};
|
|
10368
|
-
try {
|
|
10369
|
-
const head = await probe("HEAD");
|
|
10370
|
-
if (head.ok) return head;
|
|
10371
|
-
const get = await probe("GET");
|
|
10372
|
-
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
10373
|
-
} catch (error) {
|
|
10374
|
-
try {
|
|
10375
|
-
const get = await probe("GET");
|
|
10376
|
-
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
10377
|
-
} catch (secondError) {
|
|
10378
|
-
return { ok: false, reason: "request_failed", url: target, error: secondError.message || error.message };
|
|
10379
|
-
}
|
|
10380
|
-
}
|
|
10381
|
-
}
|
|
10382
|
-
function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
10383
|
-
const staticToken = resolveSecretReference(config?.apiToken);
|
|
10384
|
-
const appConfig = isPlainObject(config?.app) ? config.app : {};
|
|
10385
|
-
const appId = resolveSecretReference(appConfig.appId || appConfig.app_id || "");
|
|
10386
|
-
const installationId = resolveSecretReference(appConfig.installationId || appConfig.installation_id || "");
|
|
10387
|
-
const appEnabled = appConfig.enabled === true || Boolean(appId && installationId && (appConfig.privateKey || appConfig.privateKeyFile));
|
|
10388
|
-
const privateKey = resolveGitHubAppPrivateKey(appConfig);
|
|
10389
|
-
const owner = String(config?.owner || "").trim();
|
|
10390
|
-
const repo = String(config?.repo || "").trim();
|
|
10391
|
-
let installationToken = null;
|
|
10392
|
-
let installationTokenExpiresAt = 0;
|
|
10393
|
-
const requestJson = async (url, { method = "GET", headers = {}, body = void 0 } = {}) => {
|
|
10394
|
-
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
|
|
10620
|
+
const expectedEvents = new Set(config?.webhook?.events || []);
|
|
10621
|
+
const actualEvents = new Set(state?.events || []);
|
|
10622
|
+
const hasEvents = [...expectedEvents].every((event) => actualEvents.has(event));
|
|
10623
|
+
return Boolean(
|
|
10624
|
+
state?.active && state?.webhookId && state?.secret && state?.publicUrl === config?.webhook?.publicUrl && hasEvents
|
|
10625
|
+
);
|
|
10626
|
+
}
|
|
10627
|
+
function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
|
|
10628
|
+
const token = resolveSecretReference(config?.apiToken);
|
|
10629
|
+
const request = async (url, options = {}) => {
|
|
10630
|
+
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a ClickUp client for live webhook setup");
|
|
10631
|
+
if (!token) throw new Error("ClickUp API token is not available");
|
|
10395
10632
|
const response = await fetchImpl(url, {
|
|
10396
|
-
|
|
10633
|
+
...options,
|
|
10397
10634
|
headers: {
|
|
10398
|
-
|
|
10399
|
-
"
|
|
10400
|
-
...
|
|
10401
|
-
|
|
10402
|
-
},
|
|
10403
|
-
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
10404
|
-
});
|
|
10405
|
-
if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
|
|
10406
|
-
return response.status === 204 ? null : response.json();
|
|
10407
|
-
};
|
|
10408
|
-
const getAuthToken = async () => {
|
|
10409
|
-
if (!appEnabled) return staticToken || "";
|
|
10410
|
-
const nowMs = Date.now();
|
|
10411
|
-
if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
|
|
10412
|
-
const jwt = createGitHubAppJwt({ appId, privateKey });
|
|
10413
|
-
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(installationId)}/access_tokens`, {
|
|
10414
|
-
method: "POST",
|
|
10415
|
-
headers: { Authorization: `Bearer ${jwt}` }
|
|
10416
|
-
});
|
|
10417
|
-
installationToken = String(installation?.token || "").trim();
|
|
10418
|
-
installationTokenExpiresAt = Date.parse(installation?.expires_at || "") || nowMs + 3e6;
|
|
10419
|
-
if (!installationToken) throw new Error("GitHub App installation token response did not include a token");
|
|
10420
|
-
return installationToken;
|
|
10421
|
-
};
|
|
10422
|
-
const request = async (pathname, { method = "GET", body = void 0 } = {}) => {
|
|
10423
|
-
if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
|
|
10424
|
-
const token = await getAuthToken();
|
|
10425
|
-
return requestJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
|
|
10426
|
-
method,
|
|
10427
|
-
body,
|
|
10428
|
-
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
10635
|
+
Authorization: token,
|
|
10636
|
+
"Content-Type": "application/json",
|
|
10637
|
+
...options.headers || {}
|
|
10638
|
+
}
|
|
10429
10639
|
});
|
|
10640
|
+
if (!response.ok) throw new Error(`ClickUp API request failed: ${response.status}`);
|
|
10641
|
+
return response.json();
|
|
10430
10642
|
};
|
|
10431
|
-
const getRef = async (branch) => request(`/git/ref/heads/${encodeGitHubBranchRef(branch)}`);
|
|
10432
|
-
const getCommitObject = async (sha) => request(`/git/commits/${encodeURIComponent(sha)}`);
|
|
10433
|
-
const createBlob = async ({ content, encoding }) => request("/git/blobs", { method: "POST", body: { content, encoding } });
|
|
10434
|
-
const createTree = async ({ baseTree, tree }) => request("/git/trees", { method: "POST", body: { base_tree: baseTree, tree } });
|
|
10435
|
-
const createCommit = async ({ message, treeSha, parents }) => request("/git/commits", { method: "POST", body: { message, tree: treeSha, parents } });
|
|
10436
|
-
const updateRef = async ({ branch, sha, force = false }) => request(`/git/refs/heads/${encodeGitHubBranchRef(branch)}`, { method: "PATCH", body: { sha, force } });
|
|
10437
10643
|
return {
|
|
10438
|
-
async
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
async createPullRequest({ title, head, base, body = "", draft = false, maintainerCanModify = true }) {
|
|
10442
|
-
return request("/pulls", {
|
|
10644
|
+
async createWebhook({ endpoint, events, location }) {
|
|
10645
|
+
const body = { endpoint, events, ...Object.fromEntries(Object.entries(location || {}).filter(([, value]) => value)) };
|
|
10646
|
+
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`, {
|
|
10443
10647
|
method: "POST",
|
|
10444
|
-
body:
|
|
10445
|
-
title: String(title || ""),
|
|
10446
|
-
head: String(head || ""),
|
|
10447
|
-
base: String(base || ""),
|
|
10448
|
-
body: String(body || ""),
|
|
10449
|
-
draft: draft === true,
|
|
10450
|
-
maintainer_can_modify: maintainerCanModify !== false
|
|
10451
|
-
}
|
|
10648
|
+
body: JSON.stringify(body)
|
|
10452
10649
|
});
|
|
10453
10650
|
},
|
|
10454
|
-
async
|
|
10455
|
-
return request(
|
|
10456
|
-
},
|
|
10457
|
-
async replyToReviewComment({ commentId, body }) {
|
|
10458
|
-
return request(`/pulls/comments/${encodeURIComponent(commentId)}/replies`, { method: "POST", body: { body: String(body || "") } });
|
|
10651
|
+
async listWebhooks() {
|
|
10652
|
+
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/webhook`);
|
|
10459
10653
|
},
|
|
10460
|
-
async
|
|
10461
|
-
return request(
|
|
10654
|
+
async deleteWebhook(webhookId) {
|
|
10655
|
+
return request(`https://api.clickup.com/api/v2/webhook/${encodeURIComponent(webhookId)}`, { method: "DELETE" });
|
|
10462
10656
|
},
|
|
10463
|
-
async
|
|
10464
|
-
|
|
10465
|
-
merge_method: String(mergeMethod || "squash")
|
|
10466
|
-
};
|
|
10467
|
-
if (commitTitle) body.commit_title = String(commitTitle);
|
|
10468
|
-
if (commitMessage) body.commit_message = String(commitMessage);
|
|
10469
|
-
return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
|
|
10657
|
+
async getAuthorizedUser() {
|
|
10658
|
+
return request("https://api.clickup.com/api/v2/user");
|
|
10470
10659
|
},
|
|
10471
|
-
async
|
|
10472
|
-
return request(
|
|
10660
|
+
async getTask(taskId) {
|
|
10661
|
+
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}`);
|
|
10473
10662
|
},
|
|
10474
|
-
async
|
|
10475
|
-
return request(
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
per_page: perPage
|
|
10480
|
-
}));
|
|
10663
|
+
async updateTaskMetadata({ taskId, fieldId, value }) {
|
|
10664
|
+
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/field/${encodeURIComponent(fieldId)}`, {
|
|
10665
|
+
method: "POST",
|
|
10666
|
+
body: JSON.stringify({ value })
|
|
10667
|
+
});
|
|
10481
10668
|
},
|
|
10482
|
-
async
|
|
10483
|
-
|
|
10669
|
+
async postTaskComment() {
|
|
10670
|
+
throw new Error("Optima runtime does not post ClickUp task comments; use local logs for process failures.");
|
|
10484
10671
|
},
|
|
10485
|
-
async
|
|
10486
|
-
|
|
10487
|
-
context = "Vercel \u2013 defend-preproduction",
|
|
10488
|
-
environment = "Preview \u2013 defend-preproduction",
|
|
10489
|
-
requireFunctionalUrl = true
|
|
10490
|
-
} = {}) {
|
|
10491
|
-
const pr = await this.getPullRequest(pullNumber);
|
|
10492
|
-
const headSha = String(pr?.head?.sha || "").trim();
|
|
10493
|
-
if (!headSha) return { ok: true, ready: false, reason: "pr_head_sha_missing", pull_request: { number: pullNumber } };
|
|
10494
|
-
const combined = await this.getCombinedStatus(headSha);
|
|
10495
|
-
const statuses = Array.isArray(combined?.statuses) ? combined.statuses : [];
|
|
10496
|
-
const matchingStatuses = statuses.filter((status) => matchesVercelStatusContext(status?.context, context));
|
|
10497
|
-
const selectedStatus = sortNewestFirst(matchingStatuses)[0] || null;
|
|
10498
|
-
if (!selectedStatus) {
|
|
10499
|
-
return {
|
|
10500
|
-
ok: true,
|
|
10501
|
-
ready: false,
|
|
10502
|
-
reason: "vercel_status_missing",
|
|
10503
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10504
|
-
required_context: context,
|
|
10505
|
-
combined_state: combined?.state || null
|
|
10506
|
-
};
|
|
10507
|
-
}
|
|
10508
|
-
if (String(selectedStatus.state || "").toLowerCase() !== "success") {
|
|
10509
|
-
return {
|
|
10510
|
-
ok: true,
|
|
10511
|
-
ready: false,
|
|
10512
|
-
reason: "vercel_status_not_success",
|
|
10513
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10514
|
-
required_context: context,
|
|
10515
|
-
status: selectedStatus,
|
|
10516
|
-
combined_state: combined?.state || null
|
|
10517
|
-
};
|
|
10518
|
-
}
|
|
10519
|
-
let deployments = await this.listDeployments({ sha: headSha, environment });
|
|
10520
|
-
if (!Array.isArray(deployments) || deployments.length === 0) deployments = await this.listDeployments({ sha: headSha });
|
|
10521
|
-
const selectedDeployment = sortNewestFirst(Array.isArray(deployments) ? deployments : []).find((deployment) => !environment || String(deployment?.environment || "") === environment) || sortNewestFirst(Array.isArray(deployments) ? deployments : [])[0] || null;
|
|
10522
|
-
if (!selectedDeployment?.id) {
|
|
10523
|
-
return {
|
|
10524
|
-
ok: true,
|
|
10525
|
-
ready: false,
|
|
10526
|
-
reason: "vercel_deployment_missing",
|
|
10527
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10528
|
-
required_environment: environment,
|
|
10529
|
-
status: selectedStatus
|
|
10530
|
-
};
|
|
10531
|
-
}
|
|
10532
|
-
const deploymentStatuses = await this.listDeploymentStatuses(selectedDeployment.id);
|
|
10533
|
-
const selectedDeploymentStatus = sortNewestFirst(Array.isArray(deploymentStatuses) ? deploymentStatuses : [])[0] || null;
|
|
10534
|
-
if (!selectedDeploymentStatus || String(selectedDeploymentStatus.state || "").toLowerCase() !== "success") {
|
|
10535
|
-
return {
|
|
10536
|
-
ok: true,
|
|
10537
|
-
ready: false,
|
|
10538
|
-
reason: "vercel_deployment_not_success",
|
|
10539
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10540
|
-
required_environment: environment,
|
|
10541
|
-
status: selectedStatus,
|
|
10542
|
-
deployment: selectedDeployment,
|
|
10543
|
-
deployment_status: selectedDeploymentStatus
|
|
10544
|
-
};
|
|
10545
|
-
}
|
|
10546
|
-
const url = selectFunctionalDeploymentUrl(selectedDeploymentStatus, selectedDeployment);
|
|
10547
|
-
const urlCheck = requireFunctionalUrl ? await checkFunctionalDeploymentUrl(url, fetchImpl) : { ok: Boolean(url), reason: url ? void 0 : "url_missing", url };
|
|
10548
|
-
if (requireFunctionalUrl && !urlCheck.ok) {
|
|
10549
|
-
return {
|
|
10550
|
-
ok: true,
|
|
10551
|
-
ready: false,
|
|
10552
|
-
reason: "vercel_url_not_functional",
|
|
10553
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10554
|
-
required_environment: environment,
|
|
10555
|
-
status: selectedStatus,
|
|
10556
|
-
deployment: selectedDeployment,
|
|
10557
|
-
deployment_status: selectedDeploymentStatus,
|
|
10558
|
-
url,
|
|
10559
|
-
url_check: urlCheck
|
|
10560
|
-
};
|
|
10561
|
-
}
|
|
10562
|
-
return {
|
|
10563
|
-
ok: true,
|
|
10564
|
-
ready: true,
|
|
10565
|
-
reason: "vercel_pr_deployment_ready",
|
|
10566
|
-
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10567
|
-
required_context: context,
|
|
10568
|
-
required_environment: environment,
|
|
10569
|
-
status: selectedStatus,
|
|
10570
|
-
deployment: selectedDeployment,
|
|
10571
|
-
deployment_status: selectedDeploymentStatus,
|
|
10572
|
-
url,
|
|
10573
|
-
url_check: urlCheck
|
|
10574
|
-
};
|
|
10672
|
+
async addTaskTag() {
|
|
10673
|
+
throw new Error("Optima runtime does not add ClickUp task tags; use local logs for process failures.");
|
|
10575
10674
|
},
|
|
10576
|
-
async
|
|
10577
|
-
const
|
|
10578
|
-
if (
|
|
10579
|
-
const
|
|
10580
|
-
if (
|
|
10581
|
-
|
|
10582
|
-
if (!commitMessage) throw new Error("commit message is required");
|
|
10583
|
-
const changedPaths = listWorktreeChangedPaths(directory, runGitFn);
|
|
10584
|
-
if (changedPaths.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
10585
|
-
const ref = await getRef(targetBranch);
|
|
10586
|
-
const headSha = ref?.object?.sha;
|
|
10587
|
-
if (!headSha) throw new Error(`GitHub ref for ${targetBranch} did not include a head sha`);
|
|
10588
|
-
const headCommit = await getCommitObject(headSha);
|
|
10589
|
-
const baseTree = headCommit?.tree?.sha;
|
|
10590
|
-
if (!baseTree) throw new Error(`GitHub commit ${headSha} did not include a tree sha`);
|
|
10591
|
-
const tree = [];
|
|
10592
|
-
for (const gitPath of changedPaths) {
|
|
10593
|
-
const entry = treeEntryForWorktreePath(directory, gitPath);
|
|
10594
|
-
if (!entry) continue;
|
|
10595
|
-
if (entry.deleted) {
|
|
10596
|
-
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: null });
|
|
10597
|
-
continue;
|
|
10598
|
-
}
|
|
10599
|
-
const blob = await createBlob({ content: entry.content, encoding: entry.encoding });
|
|
10600
|
-
if (!blob?.sha) throw new Error(`GitHub blob creation failed for ${gitPath}`);
|
|
10601
|
-
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: blob.sha });
|
|
10602
|
-
}
|
|
10603
|
-
if (tree.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
10604
|
-
const nextTree = await createTree({ baseTree, tree });
|
|
10605
|
-
const nextCommit = await createCommit({ message: commitMessage, treeSha: nextTree.sha, parents: [headSha] });
|
|
10606
|
-
if (!nextCommit?.sha) throw new Error("GitHub commit creation did not return a sha");
|
|
10607
|
-
const updatedRef = await updateRef({ branch: targetBranch, sha: nextCommit.sha, force: false });
|
|
10608
|
-
if (syncLocal) {
|
|
10609
|
-
runGitFn(directory, ["fetch", "origin", targetBranch]);
|
|
10610
|
-
runGitFn(directory, ["reset", "--hard", "FETCH_HEAD"]);
|
|
10611
|
-
}
|
|
10612
|
-
return {
|
|
10613
|
-
ok: true,
|
|
10614
|
-
action: "committed",
|
|
10615
|
-
branch: targetBranch,
|
|
10616
|
-
before: headSha,
|
|
10617
|
-
after: nextCommit.sha,
|
|
10618
|
-
changedPaths,
|
|
10619
|
-
treeEntries: tree.length,
|
|
10620
|
-
verification: nextCommit.verification || null,
|
|
10621
|
-
ref: updatedRef
|
|
10622
|
-
};
|
|
10675
|
+
async listAssignedTasks({ assigneeId, statuses = [], limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10676
|
+
const params = new URLSearchParams();
|
|
10677
|
+
if (assigneeId) params.append("assignees[]", assigneeId);
|
|
10678
|
+
for (const status of statuses) if (status) params.append("statuses[]", status);
|
|
10679
|
+
if (limit) params.append("limit", String(limit));
|
|
10680
|
+
return request(`https://api.clickup.com/api/v2/team/${encodeURIComponent(config.teamId)}/task?${params.toString()}`);
|
|
10623
10681
|
},
|
|
10624
|
-
async
|
|
10625
|
-
|
|
10626
|
-
if (
|
|
10627
|
-
|
|
10682
|
+
async getTaskComments({ taskId, start, limit = CLICKUP_WEBHOOK_STARTUP_COMMENT_LIMIT } = {}) {
|
|
10683
|
+
const params = new URLSearchParams();
|
|
10684
|
+
if (start) params.set("start", String(start));
|
|
10685
|
+
if (limit) params.set("limit", String(limit));
|
|
10686
|
+
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
10687
|
+
return request(`https://api.clickup.com/api/v2/task/${encodeURIComponent(taskId)}/comment${suffix}`);
|
|
10628
10688
|
}
|
|
10629
10689
|
};
|
|
10630
10690
|
}
|
|
@@ -10853,10 +10913,10 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
|
|
|
10853
10913
|
function verifyClickUpSignature(rawBody, signatureHeader, secret) {
|
|
10854
10914
|
const signature = String(signatureHeader || "").replace(/^sha256=/i, "").trim();
|
|
10855
10915
|
if (!signature || !secret) return false;
|
|
10856
|
-
const expected =
|
|
10916
|
+
const expected = crypto2.createHmac("sha256", secret).update(Buffer.isBuffer(rawBody) ? rawBody : String(rawBody || ""), "utf8").digest("hex");
|
|
10857
10917
|
const given = Buffer.from(signature, "hex");
|
|
10858
10918
|
const wanted = Buffer.from(expected, "hex");
|
|
10859
|
-
return given.length === wanted.length &&
|
|
10919
|
+
return given.length === wanted.length && crypto2.timingSafeEqual(given, wanted);
|
|
10860
10920
|
}
|
|
10861
10921
|
function verifyGitHubSignature(rawBody, signatureHeader, secret) {
|
|
10862
10922
|
return verifyClickUpSignature(rawBody, signatureHeader, secret);
|
|
@@ -10910,9 +10970,9 @@ function rememberClickUpWebhookEvent(state = {}, eventKey, limit = 200) {
|
|
|
10910
10970
|
return { duplicate: false, state: { ...state, recentEventKeys: [...recent, eventKey].slice(-limit) } };
|
|
10911
10971
|
}
|
|
10912
10972
|
function readClickUpCommentLedgerEntries(ledgerPath) {
|
|
10913
|
-
if (!ledgerPath || !
|
|
10973
|
+
if (!ledgerPath || !fs8.existsSync(ledgerPath)) return [];
|
|
10914
10974
|
try {
|
|
10915
|
-
const raw =
|
|
10975
|
+
const raw = fs8.readFileSync(ledgerPath, "utf8");
|
|
10916
10976
|
const entries = [];
|
|
10917
10977
|
for (const [index, line] of raw.split(/\r?\n/).entries()) {
|
|
10918
10978
|
if (!line.trim()) continue;
|
|
@@ -10936,11 +10996,11 @@ function readClickUpCommentLedger(ledgerPath) {
|
|
|
10936
10996
|
}
|
|
10937
10997
|
function appendClickUpCommentLedgerEntry(ledgerPath, entry = {}) {
|
|
10938
10998
|
if (!ledgerPath || !entry.key) return;
|
|
10939
|
-
|
|
10940
|
-
|
|
10999
|
+
fs8.mkdirSync(path8.dirname(ledgerPath), { recursive: true, mode: 448 });
|
|
11000
|
+
fs8.appendFileSync(ledgerPath, `${JSON.stringify(entry)}
|
|
10941
11001
|
`, { encoding: "utf8", mode: 384 });
|
|
10942
11002
|
try {
|
|
10943
|
-
|
|
11003
|
+
fs8.chmodSync(ledgerPath, 384);
|
|
10944
11004
|
} catch {
|
|
10945
11005
|
}
|
|
10946
11006
|
}
|
|
@@ -10959,7 +11019,7 @@ function stableClickUpCommentVersionMarker(value) {
|
|
|
10959
11019
|
const direct = value.version ?? value.date_updated ?? value.updated_at ?? value.revision ?? value.modified_at;
|
|
10960
11020
|
const directMarker = stableClickUpCommentVersionMarker(direct);
|
|
10961
11021
|
if (directMarker) return directMarker;
|
|
10962
|
-
return `json:${
|
|
11022
|
+
return `json:${crypto2.createHash("sha256").update(JSON.stringify(value, Object.keys(value).sort())).digest("hex").slice(0, 16)}`;
|
|
10963
11023
|
}
|
|
10964
11024
|
function clickUpCommentLedgerKey({ taskId, eventType, payload }) {
|
|
10965
11025
|
const history = Array.isArray(payload?.history_items) ? payload.history_items[0] : payload?.history_item;
|
|
@@ -10969,7 +11029,7 @@ function clickUpCommentLedgerKey({ taskId, eventType, payload }) {
|
|
|
10969
11029
|
const explicitVersion = stableClickUpCommentVersionMarker(
|
|
10970
11030
|
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 || ""
|
|
10971
11031
|
);
|
|
10972
|
-
const contentVersion =
|
|
11032
|
+
const contentVersion = crypto2.createHash("sha256").update(JSON.stringify({ text: clickUpCommentText(comment), parts: Array.isArray(comment.comment) ? comment.comment : null })).digest("hex").slice(0, 16);
|
|
10973
11033
|
return [String(taskId || "").trim(), "comment", commentId, explicitVersion || `sha256-${contentVersion}`].filter(Boolean).join(":");
|
|
10974
11034
|
}
|
|
10975
11035
|
function isClickUpCommentVersionProcessed({ ledgerPath, key, ledger = null, worktree = process.cwd() } = {}) {
|
|
@@ -11499,7 +11559,7 @@ function clampOpenCodeSessionText(text = "", maxLength = 12e3) {
|
|
|
11499
11559
|
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
11500
11560
|
}
|
|
11501
11561
|
function hashOpenCodeSessionText(text = "") {
|
|
11502
|
-
return
|
|
11562
|
+
return crypto2.createHash("sha256").update(String(text ?? "")).digest("hex");
|
|
11503
11563
|
}
|
|
11504
11564
|
async function readOpenCodeSessionMessages(client, { sessionId, directory, limit = 20 } = {}) {
|
|
11505
11565
|
if (typeof client?.session?.messages !== "function") return null;
|
|
@@ -11737,7 +11797,7 @@ async function readOpenCodeSessionControl(client, { sessionId, directory, limit
|
|
|
11737
11797
|
};
|
|
11738
11798
|
}
|
|
11739
11799
|
async function probeOpenCodeSessionControl(client, { directory, agent, omitAgentOnPrompt = false, text = "" } = {}) {
|
|
11740
|
-
const requestedMarker = text || `optima-session-probe-${
|
|
11800
|
+
const requestedMarker = text || `optima-session-probe-${crypto2.randomUUID()}`;
|
|
11741
11801
|
const marker = clampOpenCodeSessionText(requestedMarker);
|
|
11742
11802
|
const create = await createOpenCodeSessionControl(client, {
|
|
11743
11803
|
directory,
|
|
@@ -11965,9 +12025,9 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
|
|
|
11965
12025
|
}
|
|
11966
12026
|
function appendClickUpWebhookLocalLog(worktree, entry) {
|
|
11967
12027
|
const logPath = clickUpWebhookLogPath(worktree);
|
|
11968
|
-
|
|
12028
|
+
fs8.mkdirSync(path8.dirname(logPath), { recursive: true });
|
|
11969
12029
|
const safeEntry = { ...entry, at: entry.at || (/* @__PURE__ */ new Date()).toISOString() };
|
|
11970
|
-
|
|
12030
|
+
fs8.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
|
|
11971
12031
|
`, "utf8");
|
|
11972
12032
|
}
|
|
11973
12033
|
function clickUpWebhookLifecycleLog(worktree, entry) {
|
|
@@ -11996,7 +12056,7 @@ function closeClickUpWebhookServer(server) {
|
|
|
11996
12056
|
});
|
|
11997
12057
|
}
|
|
11998
12058
|
function managedClickUpWebhookKey({ worktree, state, config } = {}) {
|
|
11999
|
-
return [
|
|
12059
|
+
return [path8.resolve(worktree || process.cwd()), state?.webhookId || "", config?.webhook?.publicUrl || ""].join("|");
|
|
12000
12060
|
}
|
|
12001
12061
|
async function deleteClickUpWebhookBestEffort({ webhookId, clickupClient, worktree, reason = "cleanup" } = {}) {
|
|
12002
12062
|
const id = String(webhookId || "").trim();
|
|
@@ -12096,7 +12156,7 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
12096
12156
|
const failed = Boolean(error || !handled?.ok || (handled?.status || 500) >= 400);
|
|
12097
12157
|
if (level === "error" && !failed) return;
|
|
12098
12158
|
const logDir = clickUpWebhookAuditLogDir();
|
|
12099
|
-
|
|
12159
|
+
fs8.mkdirSync(logDir, { recursive: true });
|
|
12100
12160
|
const secretValues = [
|
|
12101
12161
|
resolveSecretReference(config?.apiToken),
|
|
12102
12162
|
resolveSecretReference(config?.github?.apiToken),
|
|
@@ -12107,8 +12167,8 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
12107
12167
|
let requestFile;
|
|
12108
12168
|
if (level === "verbose") {
|
|
12109
12169
|
const absoluteRequestFile = clickUpWebhookRequestFilePath(at, logDir);
|
|
12110
|
-
|
|
12111
|
-
requestFile =
|
|
12170
|
+
fs8.mkdirSync(path8.dirname(absoluteRequestFile), { recursive: true });
|
|
12171
|
+
requestFile = path8.relative(logDir, absoluteRequestFile).split(path8.sep).join("/");
|
|
12112
12172
|
const parsedBody = payload || (() => {
|
|
12113
12173
|
try {
|
|
12114
12174
|
return JSON.parse(Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || ""));
|
|
@@ -12124,11 +12184,11 @@ function writeClickUpWebhookAuditLog({ method, url, headers = {}, rawBody = "",
|
|
|
12124
12184
|
headers,
|
|
12125
12185
|
body: parsedBody === void 0 ? Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || "") : parsedBody
|
|
12126
12186
|
}, secretValues);
|
|
12127
|
-
|
|
12187
|
+
fs8.writeFileSync(absoluteRequestFile, `${JSON.stringify(requestArtifact, null, 2)}
|
|
12128
12188
|
`, "utf8");
|
|
12129
12189
|
}
|
|
12130
12190
|
const summary = redactClickUpWebhookAuditValue(buildClickUpWebhookAuditSummary({ method, url, config, handled, error, payload, requestFile, at }), secretValues);
|
|
12131
|
-
|
|
12191
|
+
fs8.appendFileSync(clickUpWebhookAuditLogPath(at, logDir), `${JSON.stringify(summary)}
|
|
12132
12192
|
`, "utf8");
|
|
12133
12193
|
} catch {
|
|
12134
12194
|
}
|
|
@@ -12146,7 +12206,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
|
|
|
12146
12206
|
if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
|
|
12147
12207
|
}
|
|
12148
12208
|
}
|
|
12149
|
-
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 =
|
|
12209
|
+
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) } = {}) {
|
|
12150
12210
|
const eventType = clickUpEventType(payload);
|
|
12151
12211
|
const eventKey = clickUpWebhookEventKey(payload);
|
|
12152
12212
|
const isStartupAssignmentReconciliation = payload?.startup_reconciliation === true && eventType === "taskAssigneeUpdated";
|
|
@@ -12915,17 +12975,17 @@ function startClickUpWebhookListener({ config, state, worktree, clickupClient, g
|
|
|
12915
12975
|
return result;
|
|
12916
12976
|
}
|
|
12917
12977
|
function legacyVariantPath(destinationPath) {
|
|
12918
|
-
const parsed =
|
|
12978
|
+
const parsed = path8.parse(destinationPath);
|
|
12919
12979
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
|
|
12920
|
-
if (parsed.ext) return
|
|
12921
|
-
return
|
|
12980
|
+
if (parsed.ext) return path8.join(parsed.dir, `${parsed.name}.legacy-${stamp}${parsed.ext}`);
|
|
12981
|
+
return path8.join(parsed.dir, `${parsed.base}.legacy-${stamp}`);
|
|
12922
12982
|
}
|
|
12923
12983
|
function normalizeWorkflowTaskPath(taskPath) {
|
|
12924
12984
|
if (typeof taskPath !== "string") return { ok: false, message: "Error: task_path is required." };
|
|
12925
12985
|
const trimmed = taskPath.trim();
|
|
12926
12986
|
if (!trimmed) return { ok: false, message: "Error: task_path is required." };
|
|
12927
12987
|
const normalized = trimmed.replace(/\\/g, "/");
|
|
12928
|
-
if (
|
|
12988
|
+
if (path8.isAbsolute(trimmed)) return { ok: true, taskPath: trimmed };
|
|
12929
12989
|
if (normalized === "tasks" || normalized.startsWith("tasks/")) {
|
|
12930
12990
|
return {
|
|
12931
12991
|
ok: false,
|
|
@@ -12943,78 +13003,78 @@ function normalizeWorkflowTaskPath(taskPath) {
|
|
|
12943
13003
|
function mergeFileIntoDestination(sourcePath, destinationPath, relativeSource, gitState = null) {
|
|
12944
13004
|
const sourceWasTracked = isGitTracked(gitState, sourcePath);
|
|
12945
13005
|
if (gitAwareMoveIfTracked(sourcePath, destinationPath, gitState)) return;
|
|
12946
|
-
if (!
|
|
12947
|
-
|
|
12948
|
-
|
|
13006
|
+
if (!fs8.existsSync(destinationPath)) {
|
|
13007
|
+
fs8.mkdirSync(path8.dirname(destinationPath), { recursive: true });
|
|
13008
|
+
fs8.renameSync(sourcePath, destinationPath);
|
|
12949
13009
|
if (sourceWasTracked) stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12950
13010
|
return;
|
|
12951
13011
|
}
|
|
12952
|
-
const ext =
|
|
13012
|
+
const ext = path8.extname(destinationPath).toLowerCase();
|
|
12953
13013
|
if ([".yaml", ".yml", ".json"].includes(ext)) {
|
|
12954
|
-
const sourceRaw =
|
|
12955
|
-
const destRaw =
|
|
13014
|
+
const sourceRaw = fs8.readFileSync(sourcePath, "utf8");
|
|
13015
|
+
const destRaw = fs8.readFileSync(destinationPath, "utf8");
|
|
12956
13016
|
const parser = ext === ".json" ? JSON : import_yaml3.default;
|
|
12957
13017
|
const sourceValue = parser.parse(sourceRaw) || {};
|
|
12958
13018
|
const destValue = parser.parse(destRaw) || {};
|
|
12959
13019
|
const merged = mergeStructuredValues(sourceValue, destValue);
|
|
12960
13020
|
const serialized = ext === ".json" ? `${JSON.stringify(merged, null, 2)}
|
|
12961
13021
|
` : import_yaml3.default.stringify(merged);
|
|
12962
|
-
|
|
12963
|
-
|
|
13022
|
+
fs8.writeFileSync(destinationPath, serialized, "utf8");
|
|
13023
|
+
fs8.unlinkSync(sourcePath);
|
|
12964
13024
|
stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12965
13025
|
return;
|
|
12966
13026
|
}
|
|
12967
13027
|
if (ext === ".md") {
|
|
12968
|
-
const sourceRaw =
|
|
12969
|
-
const destRaw =
|
|
13028
|
+
const sourceRaw = fs8.readFileSync(sourcePath, "utf8").trimEnd();
|
|
13029
|
+
const destRaw = fs8.readFileSync(destinationPath, "utf8").trimEnd();
|
|
12970
13030
|
if (sourceRaw && !destRaw.includes(sourceRaw)) {
|
|
12971
13031
|
const marker = `## Legacy Content From ${relativeSource}`;
|
|
12972
|
-
|
|
13032
|
+
fs8.writeFileSync(destinationPath, `${destRaw}
|
|
12973
13033
|
|
|
12974
13034
|
${marker}
|
|
12975
13035
|
|
|
12976
13036
|
${sourceRaw}
|
|
12977
13037
|
`, "utf8");
|
|
12978
13038
|
}
|
|
12979
|
-
|
|
13039
|
+
fs8.unlinkSync(sourcePath);
|
|
12980
13040
|
stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12981
13041
|
return;
|
|
12982
13042
|
}
|
|
12983
13043
|
const preservedPath = legacyVariantPath(destinationPath);
|
|
12984
|
-
|
|
13044
|
+
fs8.renameSync(sourcePath, preservedPath);
|
|
12985
13045
|
stageGitAwareMerge(sourcePath, preservedPath, gitState);
|
|
12986
13046
|
}
|
|
12987
|
-
function mergePathIntoDestination(sourcePath, destinationPath, relativeSource =
|
|
12988
|
-
if (!
|
|
12989
|
-
const stat =
|
|
13047
|
+
function mergePathIntoDestination(sourcePath, destinationPath, relativeSource = path8.basename(sourcePath), gitState = null) {
|
|
13048
|
+
if (!fs8.existsSync(sourcePath)) return;
|
|
13049
|
+
const stat = fs8.statSync(sourcePath);
|
|
12990
13050
|
if (stat.isDirectory()) {
|
|
12991
13051
|
const sourceWasTracked = isGitTracked(gitState, sourcePath) || hasGitTrackedChildren(gitState, sourcePath);
|
|
12992
13052
|
if (gitAwareMoveIfTracked(sourcePath, destinationPath, gitState)) return;
|
|
12993
|
-
if (!
|
|
12994
|
-
|
|
12995
|
-
|
|
13053
|
+
if (!fs8.existsSync(destinationPath)) {
|
|
13054
|
+
fs8.mkdirSync(path8.dirname(destinationPath), { recursive: true });
|
|
13055
|
+
fs8.renameSync(sourcePath, destinationPath);
|
|
12996
13056
|
if (sourceWasTracked) stageGitAwareMerge(sourcePath, destinationPath, gitState);
|
|
12997
13057
|
return;
|
|
12998
13058
|
}
|
|
12999
|
-
|
|
13000
|
-
for (const entry of
|
|
13059
|
+
fs8.mkdirSync(destinationPath, { recursive: true });
|
|
13060
|
+
for (const entry of fs8.readdirSync(sourcePath)) {
|
|
13001
13061
|
mergePathIntoDestination(
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13062
|
+
path8.join(sourcePath, entry),
|
|
13063
|
+
path8.join(destinationPath, entry),
|
|
13064
|
+
path8.join(relativeSource, entry),
|
|
13005
13065
|
gitState
|
|
13006
13066
|
);
|
|
13007
13067
|
}
|
|
13008
|
-
|
|
13068
|
+
fs8.rmSync(sourcePath, { recursive: true, force: true });
|
|
13009
13069
|
return;
|
|
13010
13070
|
}
|
|
13011
13071
|
mergeFileIntoDestination(sourcePath, destinationPath, relativeSource, gitState);
|
|
13012
13072
|
}
|
|
13013
13073
|
function isOptimaPluginPackageWorktree(worktree) {
|
|
13014
|
-
const packageJsonPath =
|
|
13015
|
-
if (!
|
|
13074
|
+
const packageJsonPath = path8.join(worktree, "package.json");
|
|
13075
|
+
if (!fs8.existsSync(packageJsonPath)) return false;
|
|
13016
13076
|
try {
|
|
13017
|
-
const pkg = JSON.parse(
|
|
13077
|
+
const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
|
|
13018
13078
|
return pkg?.name === "@defend-tech/opencode-optima";
|
|
13019
13079
|
} catch {
|
|
13020
13080
|
return false;
|
|
@@ -13023,59 +13083,59 @@ function isOptimaPluginPackageWorktree(worktree) {
|
|
|
13023
13083
|
function migrateLegacyOptimaLayout(worktree) {
|
|
13024
13084
|
const gitState = gitMigrationState(worktree);
|
|
13025
13085
|
const migrations = [
|
|
13026
|
-
[
|
|
13027
|
-
[
|
|
13028
|
-
[
|
|
13029
|
-
[
|
|
13030
|
-
[
|
|
13031
|
-
[
|
|
13086
|
+
[path8.join(legacyOrbitaDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".orbita", "orbita.yaml")],
|
|
13087
|
+
[path8.join(legacyOrbitaDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".orbita", "staticeng.yaml")],
|
|
13088
|
+
[path8.join(legacyOrbitaDir(worktree), ".config"), optimaLocalConfigDir(worktree), path8.join(".orbita", ".config")],
|
|
13089
|
+
[path8.join(legacyOrbitaDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".orbita", "config")],
|
|
13090
|
+
[path8.join(legacyOrbitaDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".orbita", "runtime")],
|
|
13091
|
+
[path8.join(legacyOrbitaDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".orbita", "generated")],
|
|
13032
13092
|
[legacyOrbitaDir(worktree), optimaDir(worktree), ".orbita"],
|
|
13033
|
-
[
|
|
13034
|
-
[
|
|
13035
|
-
[
|
|
13036
|
-
[
|
|
13037
|
-
[
|
|
13038
|
-
[
|
|
13093
|
+
[path8.join(legacyStaticEngDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".staticeng", "staticeng.yaml")],
|
|
13094
|
+
[path8.join(legacyStaticEngDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".staticeng", "orbita.yaml")],
|
|
13095
|
+
[path8.join(legacyStaticEngDir(worktree), ".config"), optimaLocalConfigDir(worktree), path8.join(".staticeng", ".config")],
|
|
13096
|
+
[path8.join(legacyStaticEngDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".staticeng", "config")],
|
|
13097
|
+
[path8.join(legacyStaticEngDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".staticeng", "runtime")],
|
|
13098
|
+
[path8.join(legacyStaticEngDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".staticeng", "generated")],
|
|
13039
13099
|
[legacyStaticEngDir(worktree), optimaDir(worktree), ".staticeng"],
|
|
13040
|
-
[
|
|
13041
|
-
[
|
|
13042
|
-
[
|
|
13043
|
-
[
|
|
13044
|
-
[
|
|
13045
|
-
[
|
|
13046
|
-
[
|
|
13047
|
-
[
|
|
13048
|
-
[
|
|
13049
|
-
[
|
|
13050
|
-
[
|
|
13051
|
-
[
|
|
13100
|
+
[path8.join(legacyNomadworkDir(worktree), "nomadworks.yaml"), repoConfigPath(worktree), path8.join(".nomadwork", "nomadworks.yaml")],
|
|
13101
|
+
[path8.join(legacyNomadworksDir(worktree), "nomadworks.yaml"), repoConfigPath(worktree), path8.join(".nomadworks", "nomadworks.yaml")],
|
|
13102
|
+
[path8.join(legacyNomadworkDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".nomadwork", "staticeng.yaml")],
|
|
13103
|
+
[path8.join(legacyNomadworksDir(worktree), "staticeng.yaml"), repoConfigPath(worktree), path8.join(".nomadworks", "staticeng.yaml")],
|
|
13104
|
+
[path8.join(legacyNomadworkDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".nomadwork", "orbita.yaml")],
|
|
13105
|
+
[path8.join(legacyNomadworksDir(worktree), "orbita.yaml"), repoConfigPath(worktree), path8.join(".nomadworks", "orbita.yaml")],
|
|
13106
|
+
[path8.join(legacyNomadworkDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".nomadwork", "runtime")],
|
|
13107
|
+
[path8.join(legacyNomadworksDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".nomadworks", "runtime")],
|
|
13108
|
+
[path8.join(legacyNomadworkDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".nomadwork", "generated")],
|
|
13109
|
+
[path8.join(legacyNomadworksDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".nomadworks", "generated")],
|
|
13110
|
+
[path8.join(legacyNomadworkDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".nomadwork", "config")],
|
|
13111
|
+
[path8.join(legacyNomadworksDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".nomadworks", "config")],
|
|
13052
13112
|
[legacyNomadworkDir(worktree), optimaDir(worktree), ".nomadwork"],
|
|
13053
13113
|
[legacyNomadworksDir(worktree), optimaDir(worktree), ".nomadworks"],
|
|
13054
|
-
[
|
|
13055
|
-
[
|
|
13056
|
-
[
|
|
13057
|
-
[
|
|
13058
|
-
[
|
|
13114
|
+
[path8.join(worktree, "tasks"), optimaTasksDir(worktree), "tasks"],
|
|
13115
|
+
[path8.join(worktree, "evidences"), optimaEvidencesDir(worktree), "evidences"],
|
|
13116
|
+
[path8.join(worktree, "docs", "scrs"), optimaScrsDir(worktree), path8.join("docs", "scrs")],
|
|
13117
|
+
[path8.join(worktree, "codemap.yml"), optimaCodemapPath(worktree), "codemap.yml"],
|
|
13118
|
+
[path8.join(worktree, "codemap.yaml"), optimaCodemapPath(worktree), "codemap.yaml"]
|
|
13059
13119
|
];
|
|
13060
13120
|
if (!isOptimaPluginPackageWorktree(worktree)) {
|
|
13061
|
-
migrations.push([
|
|
13121
|
+
migrations.push([path8.join(worktree, "policies"), repoPoliciesDir(worktree), "policies"]);
|
|
13062
13122
|
}
|
|
13063
13123
|
for (const [source, destination, relativeSource] of migrations) {
|
|
13064
|
-
if (
|
|
13124
|
+
if (fs8.existsSync(source)) mergePathIntoDestination(source, destination, relativeSource, gitState);
|
|
13065
13125
|
}
|
|
13066
13126
|
for (const [source, destination, relativeSource] of [
|
|
13067
|
-
[
|
|
13068
|
-
[
|
|
13069
|
-
[
|
|
13070
|
-
[
|
|
13127
|
+
[path8.join(optimaDir(worktree), "optima.yaml"), repoConfigPath(worktree), path8.join(".optima", "optima.yaml")],
|
|
13128
|
+
[path8.join(optimaDir(worktree), "config"), optimaLocalConfigDir(worktree), path8.join(".optima", "config")],
|
|
13129
|
+
[path8.join(optimaDir(worktree), "runtime"), path8.join(optimaLocalConfigDir(worktree), "runtime"), path8.join(".optima", "runtime")],
|
|
13130
|
+
[path8.join(optimaDir(worktree), "generated"), path8.join(optimaLocalConfigDir(worktree), "generated"), path8.join(".optima", "generated")]
|
|
13071
13131
|
]) {
|
|
13072
|
-
if (
|
|
13132
|
+
if (fs8.existsSync(source)) mergePathIntoDestination(source, destination, relativeSource, gitState);
|
|
13073
13133
|
}
|
|
13074
13134
|
}
|
|
13075
13135
|
function listMarkdownFiles(dirPath) {
|
|
13076
|
-
if (!
|
|
13136
|
+
if (!fs8.existsSync(dirPath)) return [];
|
|
13077
13137
|
try {
|
|
13078
|
-
return
|
|
13138
|
+
return fs8.readdirSync(dirPath).filter((file) => file.endsWith(".md") && file.toLowerCase() !== "readme.md");
|
|
13079
13139
|
} catch (e) {
|
|
13080
13140
|
console.error(`[Optima] Failed to read markdown files from ${dirPath}:`, e);
|
|
13081
13141
|
return [];
|
|
@@ -13116,10 +13176,10 @@ function toModelString(provider, model) {
|
|
|
13116
13176
|
}
|
|
13117
13177
|
function readTaskMetadata(taskPath, worktree) {
|
|
13118
13178
|
if (!taskPath) return {};
|
|
13119
|
-
const absoluteTaskPath =
|
|
13120
|
-
if (!
|
|
13179
|
+
const absoluteTaskPath = path8.isAbsolute(taskPath) ? taskPath : path8.join(worktree, taskPath);
|
|
13180
|
+
if (!fs8.existsSync(absoluteTaskPath)) return {};
|
|
13121
13181
|
try {
|
|
13122
|
-
const raw =
|
|
13182
|
+
const raw = fs8.readFileSync(absoluteTaskPath, "utf8");
|
|
13123
13183
|
const { data } = parseFrontmatter(raw);
|
|
13124
13184
|
return {
|
|
13125
13185
|
complexity: typeof data.complexity === "string" ? data.complexity.trim().toLowerCase() : void 0,
|
|
@@ -13144,11 +13204,11 @@ function slugifyTitle(input) {
|
|
|
13144
13204
|
}
|
|
13145
13205
|
function loadDiscussionRegistry(worktree) {
|
|
13146
13206
|
const registryPath = runtimeDiscussionRegistryPath(worktree);
|
|
13147
|
-
if (!
|
|
13207
|
+
if (!fs8.existsSync(registryPath)) {
|
|
13148
13208
|
return { version: 1, active: {} };
|
|
13149
13209
|
}
|
|
13150
13210
|
try {
|
|
13151
|
-
const parsed = JSON.parse(
|
|
13211
|
+
const parsed = JSON.parse(fs8.readFileSync(registryPath, "utf8"));
|
|
13152
13212
|
const registry = {
|
|
13153
13213
|
version: 1,
|
|
13154
13214
|
active: Object.fromEntries(Object.entries(parsed.active || {}).map(([key, value]) => [key, normalizeLegacyDiscussionEntry(value)]))
|
|
@@ -13161,34 +13221,34 @@ function loadDiscussionRegistry(worktree) {
|
|
|
13161
13221
|
}
|
|
13162
13222
|
function saveDiscussionRegistry(worktree, registry) {
|
|
13163
13223
|
const registryPath = runtimeDiscussionRegistryPath(worktree);
|
|
13164
|
-
const runtimeDir =
|
|
13165
|
-
if (!
|
|
13166
|
-
|
|
13224
|
+
const runtimeDir = path8.dirname(registryPath);
|
|
13225
|
+
if (!fs8.existsSync(runtimeDir)) fs8.mkdirSync(runtimeDir, { recursive: true });
|
|
13226
|
+
fs8.writeFileSync(registryPath, JSON.stringify(registry, null, 2), "utf8");
|
|
13167
13227
|
}
|
|
13168
13228
|
function runtimeDiscussionsDir(worktree) {
|
|
13169
|
-
return
|
|
13229
|
+
return path8.join(optimaLocalConfigDir(worktree), "runtime", "discussions");
|
|
13170
13230
|
}
|
|
13171
13231
|
function archivedRuntimeDiscussionsDir(worktree) {
|
|
13172
|
-
return
|
|
13232
|
+
return path8.join(runtimeDiscussionsDir(worktree), "archive");
|
|
13173
13233
|
}
|
|
13174
13234
|
function finalDiscussionsDir(worktree) {
|
|
13175
|
-
return
|
|
13235
|
+
return path8.join(optimaTasksDir(worktree), "discussions");
|
|
13176
13236
|
}
|
|
13177
13237
|
function nextDiscussionIdentity(worktree, title) {
|
|
13178
13238
|
const discussionsDir = finalDiscussionsDir(worktree);
|
|
13179
13239
|
const runtimeDir = runtimeDiscussionsDir(worktree);
|
|
13180
|
-
if (!
|
|
13181
|
-
if (!
|
|
13240
|
+
if (!fs8.existsSync(discussionsDir)) fs8.mkdirSync(discussionsDir, { recursive: true });
|
|
13241
|
+
if (!fs8.existsSync(runtimeDir)) fs8.mkdirSync(runtimeDir, { recursive: true });
|
|
13182
13242
|
let sequence = 1;
|
|
13183
13243
|
while (true) {
|
|
13184
13244
|
const id = `DISCUSSION-${String(sequence).padStart(3, "0")}`;
|
|
13185
13245
|
const filename = `${id}-${slugifyTitle(title)}.md`;
|
|
13186
|
-
const summaryRelativePath =
|
|
13187
|
-
const summaryAbsolutePath =
|
|
13246
|
+
const summaryRelativePath = path8.join(".optima", "tasks", "discussions", filename);
|
|
13247
|
+
const summaryAbsolutePath = path8.join(worktree, summaryRelativePath);
|
|
13188
13248
|
const transcriptFilename = `${id}-transcript.md`;
|
|
13189
|
-
const transcriptRelativePath =
|
|
13190
|
-
const transcriptAbsolutePath =
|
|
13191
|
-
if (!
|
|
13249
|
+
const transcriptRelativePath = path8.join(".optima", ".config", "runtime", "discussions", transcriptFilename);
|
|
13250
|
+
const transcriptAbsolutePath = path8.join(worktree, transcriptRelativePath);
|
|
13251
|
+
if (!fs8.existsSync(summaryAbsolutePath) && !fs8.existsSync(transcriptAbsolutePath)) {
|
|
13192
13252
|
return {
|
|
13193
13253
|
id,
|
|
13194
13254
|
filename,
|
|
@@ -13204,23 +13264,23 @@ function nextDiscussionIdentity(worktree, title) {
|
|
|
13204
13264
|
}
|
|
13205
13265
|
function findDiscussionById(worktree, discussionID) {
|
|
13206
13266
|
const discussionsDir = finalDiscussionsDir(worktree);
|
|
13207
|
-
if (!
|
|
13208
|
-
const entries =
|
|
13267
|
+
if (!fs8.existsSync(discussionsDir)) return null;
|
|
13268
|
+
const entries = fs8.readdirSync(discussionsDir).filter((name) => name.startsWith(`${discussionID}-`) && name.endsWith(".md"));
|
|
13209
13269
|
if (entries.length === 0) return null;
|
|
13210
13270
|
const filename = entries.sort()[0];
|
|
13211
13271
|
const transcriptFilename = `${discussionID}-transcript.md`;
|
|
13212
13272
|
return {
|
|
13213
13273
|
id: discussionID,
|
|
13214
13274
|
filename,
|
|
13215
|
-
summaryRelativePath:
|
|
13216
|
-
summaryAbsolutePath:
|
|
13275
|
+
summaryRelativePath: path8.join(".optima", "tasks", "discussions", filename),
|
|
13276
|
+
summaryAbsolutePath: path8.join(discussionsDir, filename),
|
|
13217
13277
|
transcriptFilename,
|
|
13218
|
-
transcriptRelativePath:
|
|
13219
|
-
transcriptAbsolutePath:
|
|
13278
|
+
transcriptRelativePath: path8.join(".optima", ".config", "runtime", "discussions", transcriptFilename),
|
|
13279
|
+
transcriptAbsolutePath: path8.join(runtimeDiscussionsDir(worktree), transcriptFilename)
|
|
13220
13280
|
};
|
|
13221
13281
|
}
|
|
13222
13282
|
function parseDiscussionFile(filePath) {
|
|
13223
|
-
const raw =
|
|
13283
|
+
const raw = fs8.readFileSync(filePath, "utf8");
|
|
13224
13284
|
const { data, body } = parseFrontmatter(raw);
|
|
13225
13285
|
return { data, body: body.trimStart() };
|
|
13226
13286
|
}
|
|
@@ -13231,10 +13291,10 @@ ${import_yaml3.default.stringify(frontmatter).trim()}
|
|
|
13231
13291
|
|
|
13232
13292
|
${body.trimEnd()}
|
|
13233
13293
|
`;
|
|
13234
|
-
|
|
13294
|
+
fs8.writeFileSync(filePath, serialized, "utf8");
|
|
13235
13295
|
}
|
|
13236
13296
|
function setDiscussionStatus(filePath, status) {
|
|
13237
|
-
if (!
|
|
13297
|
+
if (!fs8.existsSync(filePath)) return;
|
|
13238
13298
|
const { data, body } = parseDiscussionFile(filePath);
|
|
13239
13299
|
writeDiscussionFile(filePath, { ...data, status }, body);
|
|
13240
13300
|
}
|
|
@@ -13299,18 +13359,18 @@ async function appendMessageIfNeeded(client, worktree, registry, sessionID, mess
|
|
|
13299
13359
|
});
|
|
13300
13360
|
const text = extractTextParts(response.data.parts || []);
|
|
13301
13361
|
if (!text) return;
|
|
13302
|
-
appendDiscussionMessage(
|
|
13362
|
+
appendDiscussionMessage(path8.join(worktree, discussion.transcriptPath), speaker, text, messageID);
|
|
13303
13363
|
discussion.appendedMessageIDs ??= [];
|
|
13304
13364
|
discussion.appendedMessageIDs.push(messageID);
|
|
13305
13365
|
saveDiscussionRegistry(worktree, registry);
|
|
13306
13366
|
}
|
|
13307
13367
|
async function summarizeDiscussionWithBA(client, worktree, discussion) {
|
|
13308
|
-
const transcriptPath =
|
|
13309
|
-
const summaryPath =
|
|
13310
|
-
const summaryDir =
|
|
13311
|
-
if (!
|
|
13312
|
-
const hasExistingSummary =
|
|
13313
|
-
const priorMtimeMs = hasExistingSummary ?
|
|
13368
|
+
const transcriptPath = path8.join(worktree, discussion.transcriptPath);
|
|
13369
|
+
const summaryPath = path8.join(worktree, discussion.summaryPath);
|
|
13370
|
+
const summaryDir = path8.dirname(summaryPath);
|
|
13371
|
+
if (!fs8.existsSync(summaryDir)) fs8.mkdirSync(summaryDir, { recursive: true });
|
|
13372
|
+
const hasExistingSummary = fs8.existsSync(summaryPath);
|
|
13373
|
+
const priorMtimeMs = hasExistingSummary ? fs8.statSync(summaryPath).mtimeMs : null;
|
|
13314
13374
|
const summarizerSession = await client.session.create({
|
|
13315
13375
|
body: { title: `Discussion Summary: ${discussion.id}` }
|
|
13316
13376
|
});
|
|
@@ -13410,30 +13470,30 @@ async function summarizeDiscussionWithBA(client, worktree, discussion) {
|
|
|
13410
13470
|
return { confirmation, summaryPath, transcriptPath, hasExistingSummary, priorMtimeMs };
|
|
13411
13471
|
}
|
|
13412
13472
|
function archiveDiscussionTranscript(worktree, transcriptRelativePath) {
|
|
13413
|
-
const sourcePath =
|
|
13414
|
-
if (!
|
|
13473
|
+
const sourcePath = path8.join(worktree, transcriptRelativePath);
|
|
13474
|
+
if (!fs8.existsSync(sourcePath)) return null;
|
|
13415
13475
|
const archiveDir = archivedRuntimeDiscussionsDir(worktree);
|
|
13416
|
-
if (!
|
|
13417
|
-
const targetPath =
|
|
13418
|
-
|
|
13476
|
+
if (!fs8.existsSync(archiveDir)) fs8.mkdirSync(archiveDir, { recursive: true });
|
|
13477
|
+
const targetPath = path8.join(archiveDir, path8.basename(sourcePath));
|
|
13478
|
+
fs8.renameSync(sourcePath, targetPath);
|
|
13419
13479
|
return targetPath;
|
|
13420
13480
|
}
|
|
13421
13481
|
async function finalizeClosingDiscussion(client, worktree, registry, sessionID, discussion) {
|
|
13422
13482
|
const { confirmation, summaryPath, hasExistingSummary, priorMtimeMs } = await summarizeDiscussionWithBA(client, worktree, discussion);
|
|
13423
|
-
if (!
|
|
13483
|
+
if (!fs8.existsSync(summaryPath)) {
|
|
13424
13484
|
throw new Error(`Discussion summary was not written to ${discussion.summaryPath}`);
|
|
13425
13485
|
}
|
|
13426
13486
|
if (hasExistingSummary) {
|
|
13427
|
-
const currentMtimeMs =
|
|
13487
|
+
const currentMtimeMs = fs8.statSync(summaryPath).mtimeMs;
|
|
13428
13488
|
if (currentMtimeMs <= priorMtimeMs) {
|
|
13429
13489
|
throw new Error(`Discussion summary file was not updated at ${discussion.summaryPath}`);
|
|
13430
13490
|
}
|
|
13431
13491
|
}
|
|
13432
|
-
const summaryContent =
|
|
13492
|
+
const summaryContent = fs8.readFileSync(summaryPath, "utf8").trim();
|
|
13433
13493
|
if (!summaryContent) {
|
|
13434
13494
|
throw new Error(`Discussion summary file is empty at ${discussion.summaryPath}`);
|
|
13435
13495
|
}
|
|
13436
|
-
const transcriptPath =
|
|
13496
|
+
const transcriptPath = path8.join(worktree, discussion.transcriptPath);
|
|
13437
13497
|
setDiscussionStatus(transcriptPath, "closed");
|
|
13438
13498
|
const archivedTranscriptPath = archiveDiscussionTranscript(worktree, discussion.transcriptPath);
|
|
13439
13499
|
delete registry.active[sessionID];
|
|
@@ -13441,7 +13501,7 @@ async function finalizeClosingDiscussion(client, worktree, registry, sessionID,
|
|
|
13441
13501
|
return {
|
|
13442
13502
|
confirmation,
|
|
13443
13503
|
summaryPath: discussion.summaryPath,
|
|
13444
|
-
archivedTranscriptPath: archivedTranscriptPath ?
|
|
13504
|
+
archivedTranscriptPath: archivedTranscriptPath ? path8.relative(worktree, archivedTranscriptPath) : path8.join(".optima", ".config", "runtime", "discussions", "archive", path8.basename(discussion.transcriptPath))
|
|
13445
13505
|
};
|
|
13446
13506
|
}
|
|
13447
13507
|
function normalizeTeamMode(value) {
|
|
@@ -13480,13 +13540,13 @@ function getOperatingTeamMode(repoCfg) {
|
|
|
13480
13540
|
}
|
|
13481
13541
|
function readResolvedFile(relativePath, worktree, options = {}) {
|
|
13482
13542
|
const filePath = resolveIncludeFile(`plugin:${relativePath}`, worktree, PKG_ROOT, options);
|
|
13483
|
-
if (!filePath || !
|
|
13484
|
-
return resolveIncludes(
|
|
13543
|
+
if (!filePath || !fs8.existsSync(filePath)) return "";
|
|
13544
|
+
return resolveIncludes(fs8.readFileSync(filePath, "utf8"), worktree, PKG_ROOT, options).trim();
|
|
13485
13545
|
}
|
|
13486
13546
|
function loadMarkdownFragment(filePath, worktree) {
|
|
13487
|
-
if (!
|
|
13547
|
+
if (!fs8.existsSync(filePath)) return "";
|
|
13488
13548
|
try {
|
|
13489
|
-
const raw =
|
|
13549
|
+
const raw = fs8.readFileSync(filePath, "utf8");
|
|
13490
13550
|
const { body } = parseFrontmatter(raw);
|
|
13491
13551
|
return resolveIncludes(body.trim(), worktree, PKG_ROOT);
|
|
13492
13552
|
} catch (e) {
|
|
@@ -13495,9 +13555,9 @@ function loadMarkdownFragment(filePath, worktree) {
|
|
|
13495
13555
|
}
|
|
13496
13556
|
}
|
|
13497
13557
|
function loadAgentDefinition(filePath, worktree, options = {}) {
|
|
13498
|
-
if (!
|
|
13558
|
+
if (!fs8.existsSync(filePath)) return null;
|
|
13499
13559
|
try {
|
|
13500
|
-
const rawContent =
|
|
13560
|
+
const rawContent = fs8.readFileSync(filePath, "utf8");
|
|
13501
13561
|
const { data, body } = parseFrontmatter(rawContent);
|
|
13502
13562
|
const prompt = resolveIncludes(body.trim(), worktree, PKG_ROOT, options);
|
|
13503
13563
|
return { data, prompt };
|
|
@@ -13508,13 +13568,13 @@ function loadAgentDefinition(filePath, worktree, options = {}) {
|
|
|
13508
13568
|
}
|
|
13509
13569
|
function syncGeneratedPolicies(worktree, repoCfg) {
|
|
13510
13570
|
if (repoCfg.policies?.extract_defaults !== "all") return;
|
|
13511
|
-
if (!
|
|
13571
|
+
if (!fs8.existsSync(BUNDLE_POLICIES_DIR)) return;
|
|
13512
13572
|
const generatedDir = generatedPoliciesDir(worktree);
|
|
13513
|
-
if (!
|
|
13514
|
-
const policyFiles =
|
|
13573
|
+
if (!fs8.existsSync(generatedDir)) fs8.mkdirSync(generatedDir, { recursive: true });
|
|
13574
|
+
const policyFiles = fs8.readdirSync(BUNDLE_POLICIES_DIR).filter((file) => file.endsWith(".md") && file !== "README.md");
|
|
13515
13575
|
for (const file of policyFiles) {
|
|
13516
|
-
const sourcePath =
|
|
13517
|
-
const source =
|
|
13576
|
+
const sourcePath = path8.join(BUNDLE_POLICIES_DIR, file);
|
|
13577
|
+
const source = fs8.readFileSync(sourcePath, "utf8").trimEnd();
|
|
13518
13578
|
const generated = [
|
|
13519
13579
|
"<!--",
|
|
13520
13580
|
"Generated from Optima plugin defaults.",
|
|
@@ -13525,19 +13585,19 @@ function syncGeneratedPolicies(worktree, repoCfg) {
|
|
|
13525
13585
|
source,
|
|
13526
13586
|
""
|
|
13527
13587
|
].join("\n");
|
|
13528
|
-
|
|
13588
|
+
fs8.writeFileSync(path8.join(generatedDir, file), generated, "utf8");
|
|
13529
13589
|
}
|
|
13530
13590
|
}
|
|
13531
13591
|
function ensureReadmeFile(dirPath, content) {
|
|
13532
|
-
if (!
|
|
13533
|
-
const readmePath =
|
|
13534
|
-
if (!
|
|
13535
|
-
|
|
13592
|
+
if (!fs8.existsSync(dirPath)) fs8.mkdirSync(dirPath, { recursive: true });
|
|
13593
|
+
const readmePath = path8.join(dirPath, "README.md");
|
|
13594
|
+
if (!fs8.existsSync(readmePath)) {
|
|
13595
|
+
fs8.writeFileSync(readmePath, content, "utf8");
|
|
13536
13596
|
}
|
|
13537
13597
|
}
|
|
13538
13598
|
function ensureFileIfMissing(filePath, content) {
|
|
13539
|
-
if (!
|
|
13540
|
-
if (!
|
|
13599
|
+
if (!fs8.existsSync(path8.dirname(filePath))) fs8.mkdirSync(path8.dirname(filePath), { recursive: true });
|
|
13600
|
+
if (!fs8.existsSync(filePath)) fs8.writeFileSync(filePath, content, "utf8");
|
|
13541
13601
|
}
|
|
13542
13602
|
function currentTasksRegistryContent() {
|
|
13543
13603
|
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";
|
|
@@ -13623,16 +13683,16 @@ Never place implementation evidence under root \`evidences/\`.
|
|
|
13623
13683
|
}
|
|
13624
13684
|
function ensureOptimaTaskTemplates(worktree) {
|
|
13625
13685
|
const tasksDir = optimaTasksDir(worktree);
|
|
13626
|
-
ensureFileIfMissing(
|
|
13627
|
-
ensureFileIfMissing(
|
|
13686
|
+
ensureFileIfMissing(path8.join(tasksDir, "task-template.md"), taskTemplateContent());
|
|
13687
|
+
ensureFileIfMissing(path8.join(tasksDir, "subtask-template.md"), subtaskTemplateContent());
|
|
13628
13688
|
}
|
|
13629
13689
|
function scaffoldOptimaConfig(worktree, teamMode = "full") {
|
|
13630
13690
|
const configPath = repoConfigPath(worktree);
|
|
13631
|
-
if (
|
|
13632
|
-
const templatePath =
|
|
13633
|
-
if (!
|
|
13634
|
-
const agentIds =
|
|
13635
|
-
let optimaConfig =
|
|
13691
|
+
if (fs8.existsSync(configPath)) return false;
|
|
13692
|
+
const templatePath = path8.join(TEMPLATES_DIR, "optima.yaml.template");
|
|
13693
|
+
if (!fs8.existsSync(templatePath)) return false;
|
|
13694
|
+
const agentIds = fs8.existsSync(BUNDLE_AGENTS_DIR) ? fs8.readdirSync(BUNDLE_AGENTS_DIR).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", "")) : [];
|
|
13695
|
+
let optimaConfig = fs8.readFileSync(templatePath, "utf8");
|
|
13636
13696
|
optimaConfig = optimaConfig.replace("{{teamMode}}", normalizeTeamMode(teamMode));
|
|
13637
13697
|
let agentsSection = "";
|
|
13638
13698
|
for (const id of agentIds) {
|
|
@@ -13643,26 +13703,26 @@ function scaffoldOptimaConfig(worktree, teamMode = "full") {
|
|
|
13643
13703
|
}
|
|
13644
13704
|
optimaConfig = optimaConfig.replace(/^agents:/m, "agents:\n" + agentsSection);
|
|
13645
13705
|
ensureFileIfMissing(configPath, optimaConfig);
|
|
13646
|
-
return
|
|
13706
|
+
return fs8.existsSync(configPath);
|
|
13647
13707
|
}
|
|
13648
13708
|
function scaffoldOptimaRootCodemap(worktree) {
|
|
13649
13709
|
const rootCodemapPath = optimaCodemapPath(worktree);
|
|
13650
|
-
if (
|
|
13651
|
-
const templatePath =
|
|
13652
|
-
if (!
|
|
13653
|
-
const codemapConfig =
|
|
13710
|
+
if (fs8.existsSync(rootCodemapPath)) return false;
|
|
13711
|
+
const templatePath = path8.join(TEMPLATES_DIR, "codemap.yml.template");
|
|
13712
|
+
if (!fs8.existsSync(templatePath)) return false;
|
|
13713
|
+
const codemapConfig = fs8.readFileSync(templatePath, "utf8").replace("{{projectName}}", path8.basename(worktree));
|
|
13654
13714
|
ensureFileIfMissing(rootCodemapPath, codemapConfig);
|
|
13655
|
-
return
|
|
13715
|
+
return fs8.existsSync(rootCodemapPath);
|
|
13656
13716
|
}
|
|
13657
13717
|
function ensureOptimaRegistries(worktree) {
|
|
13658
13718
|
const tasksDir = optimaTasksDir(worktree);
|
|
13659
13719
|
const scrsDir = optimaScrsDir(worktree);
|
|
13660
|
-
if (!
|
|
13661
|
-
if (!
|
|
13662
|
-
ensureFileIfMissing(
|
|
13663
|
-
ensureFileIfMissing(
|
|
13664
|
-
ensureFileIfMissing(
|
|
13665
|
-
ensureFileIfMissing(
|
|
13720
|
+
if (!fs8.existsSync(tasksDir)) fs8.mkdirSync(tasksDir, { recursive: true });
|
|
13721
|
+
if (!fs8.existsSync(scrsDir)) fs8.mkdirSync(scrsDir, { recursive: true });
|
|
13722
|
+
ensureFileIfMissing(path8.join(tasksDir, "current.md"), currentTasksRegistryContent());
|
|
13723
|
+
ensureFileIfMissing(path8.join(tasksDir, "done.md"), doneTasksRegistryContent());
|
|
13724
|
+
ensureFileIfMissing(path8.join(scrsDir, "current.md"), currentScrRegistryContent());
|
|
13725
|
+
ensureFileIfMissing(path8.join(scrsDir, "done.md"), doneScrRegistryContent());
|
|
13666
13726
|
}
|
|
13667
13727
|
function scaffoldOptimaReadmes(worktree) {
|
|
13668
13728
|
ensureReadmeFile(repoPoliciesDir(worktree), REPO_LOCAL_POLICIES_README);
|
|
@@ -13824,12 +13884,12 @@ function shouldRegisterWorkflowProductManager(options = {}, worktree = process.c
|
|
|
13824
13884
|
}
|
|
13825
13885
|
function isClickUpDerivedWorktreeSibling(candidate, basePath) {
|
|
13826
13886
|
if (typeof candidate !== "string" || typeof basePath !== "string" || !candidate.trim() || !basePath.trim()) return false;
|
|
13827
|
-
const resolvedCandidate =
|
|
13828
|
-
const resolvedBase =
|
|
13829
|
-
const baseParent =
|
|
13830
|
-
if (
|
|
13831
|
-
const baseName =
|
|
13832
|
-
const candidateName =
|
|
13887
|
+
const resolvedCandidate = path8.resolve(candidate);
|
|
13888
|
+
const resolvedBase = path8.resolve(basePath);
|
|
13889
|
+
const baseParent = path8.dirname(resolvedBase);
|
|
13890
|
+
if (path8.dirname(resolvedCandidate) !== baseParent) return false;
|
|
13891
|
+
const baseName = path8.basename(resolvedBase);
|
|
13892
|
+
const candidateName = path8.basename(resolvedCandidate);
|
|
13833
13893
|
if (!candidateName.startsWith(`${baseName}-`)) return false;
|
|
13834
13894
|
const branchSlug = candidateName.slice(baseName.length + 1);
|
|
13835
13895
|
const parts = branchSlug.split("-").filter(Boolean);
|
|
@@ -13837,11 +13897,11 @@ function isClickUpDerivedWorktreeSibling(candidate, basePath) {
|
|
|
13837
13897
|
if (!(/* @__PURE__ */ new Set(["tarea", "bug", "doc", "poc", "idea"])).has(parts[0])) return false;
|
|
13838
13898
|
if (!parts.slice(1).every((part) => /^[a-z0-9][a-z0-9-]*$/.test(part))) return false;
|
|
13839
13899
|
try {
|
|
13840
|
-
const candidateStat =
|
|
13900
|
+
const candidateStat = fs8.lstatSync(resolvedCandidate);
|
|
13841
13901
|
if (!candidateStat.isDirectory() || candidateStat.isSymbolicLink()) return false;
|
|
13842
|
-
const realCandidate =
|
|
13843
|
-
const realBaseParent =
|
|
13844
|
-
return
|
|
13902
|
+
const realCandidate = fs8.realpathSync.native(resolvedCandidate);
|
|
13903
|
+
const realBaseParent = fs8.realpathSync.native(baseParent);
|
|
13904
|
+
return path8.dirname(realCandidate) === realBaseParent && path8.basename(realCandidate) === candidateName;
|
|
13845
13905
|
} catch {
|
|
13846
13906
|
return false;
|
|
13847
13907
|
}
|
|
@@ -13849,15 +13909,15 @@ function isClickUpDerivedWorktreeSibling(candidate, basePath) {
|
|
|
13849
13909
|
function resolveSessionToolDirectory({ requestedDirectory, context, pluginWorktree, clickUpWebhookValidation } = {}) {
|
|
13850
13910
|
const safe = safeWorktreeOrFailure(context, pluginWorktree);
|
|
13851
13911
|
if (!safe.ok) return { ok: false, error: safe.message };
|
|
13852
|
-
const requested = String(requestedDirectory || "").trim() ?
|
|
13912
|
+
const requested = String(requestedDirectory || "").trim() ? path8.resolve(safe.worktree, String(requestedDirectory).trim()) : safe.worktree;
|
|
13853
13913
|
if (!isSafeWritableDirectory(requested)) return { ok: false, error: `Directory is not a safe writable directory: ${requested}` };
|
|
13854
13914
|
if (isSameOrNestedPath(requested, safe.worktree)) return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "context_worktree" };
|
|
13855
13915
|
const clickUpBasePath = clickUpWebhookValidation?.complete === true && clickUpWebhookValidation?.ok !== false ? clickUpWebhookValidation.config?.basePath : "";
|
|
13856
13916
|
if (clickUpBasePath && isSameOrNestedPath(requested, clickUpBasePath)) {
|
|
13857
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath:
|
|
13917
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath: path8.resolve(clickUpBasePath) };
|
|
13858
13918
|
}
|
|
13859
13919
|
if (clickUpBasePath && isClickUpDerivedWorktreeSibling(requested, clickUpBasePath)) {
|
|
13860
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath:
|
|
13920
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath: path8.resolve(clickUpBasePath) };
|
|
13861
13921
|
}
|
|
13862
13922
|
return {
|
|
13863
13923
|
ok: false,
|
|
@@ -13891,8 +13951,8 @@ function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, optio
|
|
|
13891
13951
|
if (!enabled) continue;
|
|
13892
13952
|
}
|
|
13893
13953
|
const promptOptions = { preferCompactPromptDocs: repoCfg.features?.compact_prompt_docs !== false };
|
|
13894
|
-
const bundledDefinition = loadAgentDefinition(
|
|
13895
|
-
const repoDefinition = loadAgentDefinition(
|
|
13954
|
+
const bundledDefinition = loadAgentDefinition(path8.join(BUNDLE_AGENTS_DIR, file), worktree, promptOptions);
|
|
13955
|
+
const repoDefinition = loadAgentDefinition(path8.join(repoAgentDefinitions, file), worktree, promptOptions) || loadAgentDefinition(path8.join(legacyAgentsDir, file), worktree, promptOptions);
|
|
13896
13956
|
const activeDefinition = repoDefinition || bundledDefinition;
|
|
13897
13957
|
if (!activeDefinition) continue;
|
|
13898
13958
|
const { data } = activeDefinition;
|
|
@@ -13901,7 +13961,7 @@ function buildOptimaAgents(repoCfg, operatingTeamMode, worktree, debugDir, optio
|
|
|
13901
13961
|
if (modePromptFragment) finalPrompt = `${finalPrompt}
|
|
13902
13962
|
|
|
13903
13963
|
${modePromptFragment}`;
|
|
13904
|
-
const additionFragment = loadMarkdownFragment(
|
|
13964
|
+
const additionFragment = loadMarkdownFragment(path8.join(repoAgentAdditions, file), worktree);
|
|
13905
13965
|
if (additionFragment) {
|
|
13906
13966
|
finalPrompt = `${finalPrompt}
|
|
13907
13967
|
|
|
@@ -13954,14 +14014,14 @@ Use this Optima-provided fallback when the current task worktree lacks docs/core
|
|
|
13954
14014
|
}
|
|
13955
14015
|
ourAgents[id] = agentConfig;
|
|
13956
14016
|
if (repoCfg.features?.debug_dumps !== false) {
|
|
13957
|
-
const debugPath =
|
|
14017
|
+
const debugPath = path8.join(debugDir, `${id}.md`);
|
|
13958
14018
|
const { prompt, ...dumpConfig } = agentConfig;
|
|
13959
14019
|
const debugHeader = `---
|
|
13960
14020
|
${import_yaml3.default.stringify(dumpConfig).trim()}
|
|
13961
14021
|
---`;
|
|
13962
14022
|
try {
|
|
13963
|
-
if (!
|
|
13964
|
-
|
|
14023
|
+
if (!fs8.existsSync(debugDir)) fs8.mkdirSync(debugDir, { recursive: true });
|
|
14024
|
+
fs8.writeFileSync(debugPath, `${debugHeader}
|
|
13965
14025
|
|
|
13966
14026
|
${prompt}`, "utf8");
|
|
13967
14027
|
} catch (e) {
|
|
@@ -13985,9 +14045,9 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
13985
14045
|
const configPath = resolveConfigPath(worktree);
|
|
13986
14046
|
const discussionRegistry = loadDiscussionRegistry(worktree);
|
|
13987
14047
|
let repoCfg = { agents: {}, defaults: {}, features: {} };
|
|
13988
|
-
if (
|
|
14048
|
+
if (fs8.existsSync(configPath)) {
|
|
13989
14049
|
try {
|
|
13990
|
-
repoCfg = import_yaml3.default.parse(
|
|
14050
|
+
repoCfg = import_yaml3.default.parse(fs8.readFileSync(configPath, "utf8")) || repoCfg;
|
|
13991
14051
|
} catch (e) {
|
|
13992
14052
|
console.error(`[Optima] Failed to parse config at ${configPath}:`, e);
|
|
13993
14053
|
}
|
|
@@ -14115,10 +14175,10 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
14115
14175
|
}
|
|
14116
14176
|
migrateLegacyOptimaLayout(toolWorktree);
|
|
14117
14177
|
const cfgDir = optimaConfigDir(toolWorktree);
|
|
14118
|
-
if (!
|
|
14119
|
-
const optimaTmplPath =
|
|
14120
|
-
const codemapTmplPath =
|
|
14121
|
-
if (!
|
|
14178
|
+
if (!fs8.existsSync(cfgDir)) fs8.mkdirSync(cfgDir, { recursive: true });
|
|
14179
|
+
const optimaTmplPath = path8.join(TEMPLATES_DIR, "optima.yaml.template");
|
|
14180
|
+
const codemapTmplPath = path8.join(TEMPLATES_DIR, "codemap.yml.template");
|
|
14181
|
+
if (!fs8.existsSync(optimaTmplPath) || !fs8.existsSync(codemapTmplPath)) {
|
|
14122
14182
|
return "Error: Initialization templates not found in plugin.";
|
|
14123
14183
|
}
|
|
14124
14184
|
scaffoldOptimaConfig(toolWorktree, requestedTeamMode);
|
|
@@ -14239,14 +14299,14 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14239
14299
|
async execute(args, context) {
|
|
14240
14300
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14241
14301
|
if (!safe.ok) return safe.message;
|
|
14242
|
-
const summaryPath =
|
|
14243
|
-
if (!
|
|
14244
|
-
const taskPath = args.task_path ?
|
|
14302
|
+
const summaryPath = path8.resolve(safe.worktree, args.summary_path || "");
|
|
14303
|
+
if (!fs8.existsSync(summaryPath)) return `FAIL: summary_path not found: ${summaryPath}`;
|
|
14304
|
+
const taskPath = args.task_path ? path8.resolve(safe.worktree, args.task_path) : "";
|
|
14245
14305
|
const payload = buildClickUpSummaryPayload({
|
|
14246
|
-
summaryMarkdown:
|
|
14247
|
-
summaryPath:
|
|
14248
|
-
taskMarkdown: taskPath &&
|
|
14249
|
-
taskPath: taskPath ?
|
|
14306
|
+
summaryMarkdown: fs8.readFileSync(summaryPath, "utf8"),
|
|
14307
|
+
summaryPath: path8.relative(safe.worktree, summaryPath),
|
|
14308
|
+
taskMarkdown: taskPath && fs8.existsSync(taskPath) ? fs8.readFileSync(taskPath, "utf8") : "",
|
|
14309
|
+
taskPath: taskPath ? path8.relative(safe.worktree, taskPath) : "",
|
|
14250
14310
|
branch: args.branch,
|
|
14251
14311
|
worktree: args.worktree,
|
|
14252
14312
|
pr: args.pr
|
|
@@ -14325,12 +14385,12 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14325
14385
|
async execute(args, context) {
|
|
14326
14386
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14327
14387
|
if (!safe.ok) return safe.message;
|
|
14328
|
-
const markdownPath =
|
|
14329
|
-
if (!
|
|
14388
|
+
const markdownPath = path8.resolve(safe.worktree, args.markdown_path || "");
|
|
14389
|
+
if (!fs8.existsSync(markdownPath)) return `FAIL: markdown_path not found: ${markdownPath}`;
|
|
14330
14390
|
const payload = buildClickUpCreateSubtasksPayload({
|
|
14331
14391
|
parentTaskId: args.parent_task_id,
|
|
14332
|
-
markdown:
|
|
14333
|
-
sourcePath:
|
|
14392
|
+
markdown: fs8.readFileSync(markdownPath, "utf8"),
|
|
14393
|
+
sourcePath: path8.relative(safe.worktree, markdownPath),
|
|
14334
14394
|
parentBranch: args.parent_branch,
|
|
14335
14395
|
parentTaskType: args.parent_task_type || "Tarea",
|
|
14336
14396
|
apply: String(args.apply || "").toLowerCase() === "true"
|
|
@@ -14700,7 +14760,7 @@ Backfilled messages: ${backfilled}`;
|
|
|
14700
14760
|
if (!existing) {
|
|
14701
14761
|
return "FAIL: No active discussion exists for this session.";
|
|
14702
14762
|
}
|
|
14703
|
-
const discussionPath =
|
|
14763
|
+
const discussionPath = path8.join(toolWorktree, existing.transcriptPath);
|
|
14704
14764
|
setDiscussionStatus(discussionPath, "summarizing");
|
|
14705
14765
|
existing.status = "summarizing";
|
|
14706
14766
|
saveDiscussionRegistry(toolWorktree, toolRegistry);
|
|
@@ -14752,7 +14812,7 @@ Reason: ${err.message}`;
|
|
|
14752
14812
|
try {
|
|
14753
14813
|
const sessionResult = await client.session.create({
|
|
14754
14814
|
query: { directory: workflowDirectory },
|
|
14755
|
-
body: { title: `Workflow Run: ${
|
|
14815
|
+
body: { title: `Workflow Run: ${path8.basename(workflowTaskPath)}` }
|
|
14756
14816
|
});
|
|
14757
14817
|
const sessionId = sessionResult.data.id;
|
|
14758
14818
|
activeWorkflows.set(sessionId, { pmaSessionId, taskPath: workflowTaskPath, track: workflowTrack, directory: workflowDirectory });
|
|
@@ -14936,14 +14996,14 @@ function usage() {
|
|
|
14936
14996
|
].join("\n");
|
|
14937
14997
|
}
|
|
14938
14998
|
function expandHome(inputPath) {
|
|
14939
|
-
if (!inputPath || inputPath === "~") return
|
|
14940
|
-
if (inputPath.startsWith("~/")) return
|
|
14999
|
+
if (!inputPath || inputPath === "~") return os3.homedir();
|
|
15000
|
+
if (inputPath.startsWith("~/")) return path9.join(os3.homedir(), inputPath.slice(2));
|
|
14941
15001
|
return inputPath;
|
|
14942
15002
|
}
|
|
14943
15003
|
function resolveOpenCodeDbPath(inputPath = null) {
|
|
14944
15004
|
const expanded = expandHome(inputPath || process.env.OPTIMA_OPENCODE_DB_PATH || DEFAULT_DB_PATH);
|
|
14945
15005
|
try {
|
|
14946
|
-
if (
|
|
15006
|
+
if (fs9.existsSync(expanded) && fs9.statSync(expanded).isDirectory()) return path9.join(expanded, "opencode.db");
|
|
14947
15007
|
} catch {
|
|
14948
15008
|
return expanded;
|
|
14949
15009
|
}
|
|
@@ -15007,12 +15067,12 @@ function extractOpenedPathValue(value) {
|
|
|
15007
15067
|
}
|
|
15008
15068
|
function normalizePathCandidate(candidate) {
|
|
15009
15069
|
const expanded = expandHome(candidate);
|
|
15010
|
-
if (!
|
|
15011
|
-
return
|
|
15070
|
+
if (!path9.isAbsolute(expanded)) return null;
|
|
15071
|
+
return path9.resolve(expanded);
|
|
15012
15072
|
}
|
|
15013
15073
|
function discoverOpenCodePaths(dbPath) {
|
|
15014
15074
|
const resolvedDb = resolveOpenCodeDbPath(dbPath);
|
|
15015
|
-
if (!
|
|
15075
|
+
if (!fs9.existsSync(resolvedDb)) throw new Error(`OpenCode database not found: ${resolvedDb}`);
|
|
15016
15076
|
const tables = sqliteJson(resolvedDb, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name");
|
|
15017
15077
|
const discovered = /* @__PURE__ */ new Set();
|
|
15018
15078
|
for (const table of tables) {
|
|
@@ -15037,27 +15097,27 @@ function discoverOpenCodePaths(dbPath) {
|
|
|
15037
15097
|
function collectMarkdownOverridesFrom(baseDir) {
|
|
15038
15098
|
const files = [];
|
|
15039
15099
|
for (const dirName of OVERRIDE_DIRS) {
|
|
15040
|
-
const dirPath =
|
|
15041
|
-
if (!
|
|
15042
|
-
for (const entry of
|
|
15100
|
+
const dirPath = path9.join(baseDir, dirName);
|
|
15101
|
+
if (!fs9.existsSync(dirPath)) continue;
|
|
15102
|
+
for (const entry of fs9.readdirSync(dirPath, { withFileTypes: true })) {
|
|
15043
15103
|
if (!entry.isFile()) continue;
|
|
15044
15104
|
if (!entry.name.endsWith(".md")) continue;
|
|
15045
15105
|
if (entry.name.toLowerCase() === "readme.md") continue;
|
|
15046
|
-
files.push(
|
|
15106
|
+
files.push(path9.join(dirPath, entry.name));
|
|
15047
15107
|
}
|
|
15048
15108
|
}
|
|
15049
15109
|
return files;
|
|
15050
15110
|
}
|
|
15051
15111
|
function collectOverrideFiles(worktree) {
|
|
15052
|
-
return collectMarkdownOverridesFrom(
|
|
15112
|
+
return collectMarkdownOverridesFrom(path9.join(worktree, ".optima")).sort();
|
|
15053
15113
|
}
|
|
15054
15114
|
function collectPlannedOverrideFiles(worktree) {
|
|
15055
15115
|
return [
|
|
15056
15116
|
...collectOverrideFiles(worktree),
|
|
15057
|
-
...collectMarkdownOverridesFrom(
|
|
15058
|
-
...collectMarkdownOverridesFrom(
|
|
15059
|
-
...collectMarkdownOverridesFrom(
|
|
15060
|
-
...collectMarkdownOverridesFrom(
|
|
15117
|
+
...collectMarkdownOverridesFrom(path9.join(worktree, ".staticeng")),
|
|
15118
|
+
...collectMarkdownOverridesFrom(path9.join(worktree, ".orbita")),
|
|
15119
|
+
...collectMarkdownOverridesFrom(path9.join(worktree, ".nomadwork")),
|
|
15120
|
+
...collectMarkdownOverridesFrom(path9.join(worktree, ".nomadworks"))
|
|
15061
15121
|
].sort();
|
|
15062
15122
|
}
|
|
15063
15123
|
var CRC_TABLE = (() => {
|
|
@@ -15090,14 +15150,14 @@ function writeUInt16LE(value) {
|
|
|
15090
15150
|
return buffer;
|
|
15091
15151
|
}
|
|
15092
15152
|
function createZipBackup(files, backupPath) {
|
|
15093
|
-
|
|
15153
|
+
fs9.mkdirSync(path9.dirname(backupPath), { recursive: true });
|
|
15094
15154
|
const localParts = [];
|
|
15095
15155
|
const centralParts = [];
|
|
15096
15156
|
let offset = 0;
|
|
15097
15157
|
const now = dosTimeDate();
|
|
15098
15158
|
for (const filePath of files) {
|
|
15099
|
-
const data =
|
|
15100
|
-
const name =
|
|
15159
|
+
const data = fs9.readFileSync(filePath);
|
|
15160
|
+
const name = path9.resolve(filePath).replace(/^\//, "").split(path9.sep).join("/");
|
|
15101
15161
|
const nameBuffer = Buffer.from(name, "utf8");
|
|
15102
15162
|
const crc = crc32(data);
|
|
15103
15163
|
const local = Buffer.concat([
|
|
@@ -15149,7 +15209,7 @@ function createZipBackup(files, backupPath) {
|
|
|
15149
15209
|
writeUInt32LE(offset),
|
|
15150
15210
|
writeUInt16LE(0)
|
|
15151
15211
|
]);
|
|
15152
|
-
|
|
15212
|
+
fs9.writeFileSync(backupPath, Buffer.concat([...localParts, ...centralParts, end]));
|
|
15153
15213
|
}
|
|
15154
15214
|
function timestamp() {
|
|
15155
15215
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
@@ -15159,17 +15219,17 @@ function validationStatus(result) {
|
|
|
15159
15219
|
return result.ok ? "passed" : "failed";
|
|
15160
15220
|
}
|
|
15161
15221
|
function hasLegacyMarker(worktree) {
|
|
15162
|
-
return
|
|
15222
|
+
return fs9.existsSync(path9.join(worktree, ".staticeng")) || fs9.existsSync(path9.join(worktree, ".orbita")) || fs9.existsSync(path9.join(worktree, ".nomadwork")) || fs9.existsSync(path9.join(worktree, ".nomadworks"));
|
|
15163
15223
|
}
|
|
15164
15224
|
function rootOptimaArtifacts(worktree) {
|
|
15165
15225
|
const artifacts = [
|
|
15166
|
-
["tasks/",
|
|
15167
|
-
["evidences/",
|
|
15168
|
-
["docs/scrs/",
|
|
15169
|
-
["codemap.yml",
|
|
15170
|
-
["codemap.yaml",
|
|
15226
|
+
["tasks/", path9.join(worktree, "tasks")],
|
|
15227
|
+
["evidences/", path9.join(worktree, "evidences")],
|
|
15228
|
+
["docs/scrs/", path9.join(worktree, "docs", "scrs")],
|
|
15229
|
+
["codemap.yml", path9.join(worktree, "codemap.yml")],
|
|
15230
|
+
["codemap.yaml", path9.join(worktree, "codemap.yaml")]
|
|
15171
15231
|
];
|
|
15172
|
-
return artifacts.map(([display, artifactPath]) => ({ display, path: artifactPath })).filter((item) =>
|
|
15232
|
+
return artifacts.map(([display, artifactPath]) => ({ display, path: artifactPath })).filter((item) => fs9.existsSync(item.path));
|
|
15173
15233
|
}
|
|
15174
15234
|
function hasSanitizableOverrides(worktree) {
|
|
15175
15235
|
return collectPlannedOverrideFiles(worktree).length > 0;
|
|
@@ -15195,7 +15255,7 @@ function planOptimaMigration(worktree, dryRun) {
|
|
|
15195
15255
|
...overrides.map((file) => ({
|
|
15196
15256
|
category: "override",
|
|
15197
15257
|
action: dryRun ? "would_remove_after_backup" : "removed_after_backup",
|
|
15198
|
-
path:
|
|
15258
|
+
path: path9.relative(worktree, file).split(path9.sep).join("/"),
|
|
15199
15259
|
detail: "Remove implicit agent/policy override after host-level backup."
|
|
15200
15260
|
}))
|
|
15201
15261
|
],
|
|
@@ -15216,12 +15276,12 @@ async function sanitizeHost(options = {}) {
|
|
|
15216
15276
|
const repoStates = [];
|
|
15217
15277
|
for (const candidate of paths) {
|
|
15218
15278
|
try {
|
|
15219
|
-
if (!
|
|
15279
|
+
if (!fs9.existsSync(candidate)) {
|
|
15220
15280
|
report.totals.skipped += 1;
|
|
15221
15281
|
report.repos.push({ path: candidate, status: "skipped", reason: "missing" });
|
|
15222
15282
|
continue;
|
|
15223
15283
|
}
|
|
15224
|
-
if (!
|
|
15284
|
+
if (!fs9.statSync(candidate).isDirectory()) {
|
|
15225
15285
|
report.totals.skipped += 1;
|
|
15226
15286
|
report.repos.push({ path: candidate, status: "skipped", reason: "not_directory" });
|
|
15227
15287
|
continue;
|
|
@@ -15243,7 +15303,7 @@ async function sanitizeHost(options = {}) {
|
|
|
15243
15303
|
const overrides = repoStates.flatMap((state) => state.overrideFiles.map((file) => ({ repo: state.repo, file })));
|
|
15244
15304
|
report.totals.overrideFiles = overrides.length;
|
|
15245
15305
|
if (!dryRun && overrides.length > 0) {
|
|
15246
|
-
const backupPath =
|
|
15306
|
+
const backupPath = path9.join(os3.homedir(), BACKUP_DIRNAME, `${timestamp()}.zip`);
|
|
15247
15307
|
try {
|
|
15248
15308
|
createZipBackup(overrides.map((item) => item.file), backupPath);
|
|
15249
15309
|
report.backupPath = backupPath;
|
|
@@ -15259,7 +15319,7 @@ async function sanitizeHost(options = {}) {
|
|
|
15259
15319
|
for (const state of repoStates) {
|
|
15260
15320
|
try {
|
|
15261
15321
|
if (!dryRun) {
|
|
15262
|
-
for (const file of state.overrideFiles)
|
|
15322
|
+
for (const file of state.overrideFiles) fs9.rmSync(file, { force: true });
|
|
15263
15323
|
}
|
|
15264
15324
|
const validation = !dryRun && hasLegacyMarker(state.repo) ? await optima_validate_logic(state.repo) : null;
|
|
15265
15325
|
const status = (!validation || validation.ok) && state.plan.unresolved.length === 0 ? "repaired" : "failed";
|
|
@@ -15272,7 +15332,7 @@ async function sanitizeHost(options = {}) {
|
|
|
15272
15332
|
repairActions: state.plan.actions.length,
|
|
15273
15333
|
repairSkipped: state.plan.skippedRepair === true,
|
|
15274
15334
|
overrideFiles: state.overrideFiles.length,
|
|
15275
|
-
overridePaths: state.overrideFiles.map((file) =>
|
|
15335
|
+
overridePaths: state.overrideFiles.map((file) => path9.relative(state.repo, file).split(path9.sep).join("/")),
|
|
15276
15336
|
validation: validation ? validationStatus(validation) : "skipped",
|
|
15277
15337
|
unresolved: state.plan.unresolved
|
|
15278
15338
|
});
|