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