@defend-tech/opencode-optima 0.1.82 → 0.1.84
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/README.md +4 -0
- package/assets/agents/developer.md +4 -0
- package/assets/agents/qa_engineer.md +4 -0
- package/assets/agents/tech_lead.md +4 -0
- package/assets/agents/workflow_product_manager.md +7 -2
- package/assets/agents/workflow_runner.md +4 -0
- package/dist/index.js +857 -231
- package/dist/sanitize_cli.js +892 -266
- package/docs/core/task_model.md +2 -0
- package/docs/core/task_model.prompt.md +1 -1
- package/docs/guides/TOOLS.md +50 -0
- package/docs/setup/CONFIGURATION.md +11 -0
- package/package.json +3 -2
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, path21) {
|
|
112
|
+
const ctrl = callVisitor(key, node, visitor, path21);
|
|
113
113
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
114
|
-
replaceNode(key,
|
|
115
|
-
return visit_(key, ctrl, visitor,
|
|
114
|
+
replaceNode(key, path21, ctrl);
|
|
115
|
+
return visit_(key, ctrl, visitor, path21);
|
|
116
116
|
}
|
|
117
117
|
if (typeof ctrl !== "symbol") {
|
|
118
118
|
if (identity.isCollection(node)) {
|
|
119
|
-
|
|
119
|
+
path21 = Object.freeze(path21.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, path21);
|
|
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
|
+
path21 = Object.freeze(path21.concat(node));
|
|
133
|
+
const ck = visit_("key", node.key, visitor, path21);
|
|
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, path21);
|
|
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, path21) {
|
|
160
|
+
const ctrl = await callVisitor(key, node, visitor, path21);
|
|
161
161
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
162
|
-
replaceNode(key,
|
|
163
|
-
return visitAsync_(key, ctrl, visitor,
|
|
162
|
+
replaceNode(key, path21, ctrl);
|
|
163
|
+
return visitAsync_(key, ctrl, visitor, path21);
|
|
164
164
|
}
|
|
165
165
|
if (typeof ctrl !== "symbol") {
|
|
166
166
|
if (identity.isCollection(node)) {
|
|
167
|
-
|
|
167
|
+
path21 = Object.freeze(path21.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, path21);
|
|
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
|
+
path21 = Object.freeze(path21.concat(node));
|
|
181
|
+
const ck = await visitAsync_("key", node.key, visitor, path21);
|
|
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, path21);
|
|
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, path21) {
|
|
214
214
|
if (typeof visitor === "function")
|
|
215
|
-
return visitor(key, node,
|
|
215
|
+
return visitor(key, node, path21);
|
|
216
216
|
if (identity.isMap(node))
|
|
217
|
-
return visitor.Map?.(key, node,
|
|
217
|
+
return visitor.Map?.(key, node, path21);
|
|
218
218
|
if (identity.isSeq(node))
|
|
219
|
-
return visitor.Seq?.(key, node,
|
|
219
|
+
return visitor.Seq?.(key, node, path21);
|
|
220
220
|
if (identity.isPair(node))
|
|
221
|
-
return visitor.Pair?.(key, node,
|
|
221
|
+
return visitor.Pair?.(key, node, path21);
|
|
222
222
|
if (identity.isScalar(node))
|
|
223
|
-
return visitor.Scalar?.(key, node,
|
|
223
|
+
return visitor.Scalar?.(key, node, path21);
|
|
224
224
|
if (identity.isAlias(node))
|
|
225
|
-
return visitor.Alias?.(key, node,
|
|
225
|
+
return visitor.Alias?.(key, node, path21);
|
|
226
226
|
return void 0;
|
|
227
227
|
}
|
|
228
|
-
function replaceNode(key,
|
|
229
|
-
const parent =
|
|
228
|
+
function replaceNode(key, path21, node) {
|
|
229
|
+
const parent = path21[path21.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, path21, value) {
|
|
838
838
|
let v = value;
|
|
839
|
-
for (let i =
|
|
840
|
-
const k =
|
|
839
|
+
for (let i = path21.length - 1; i >= 0; --i) {
|
|
840
|
+
const k = path21[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 = (path21) => path21 == null || typeof path21 === "object" && !!path21[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(path21, value) {
|
|
890
|
+
if (isEmptyPath(path21))
|
|
891
891
|
this.add(value);
|
|
892
892
|
else {
|
|
893
|
-
const [key, ...rest] =
|
|
893
|
+
const [key, ...rest] = path21;
|
|
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(path21) {
|
|
908
|
+
const [key, ...rest] = path21;
|
|
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(path21, keepScalar) {
|
|
923
|
+
const [key, ...rest] = path21;
|
|
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(path21) {
|
|
942
|
+
const [key, ...rest] = path21;
|
|
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(path21, value) {
|
|
953
|
+
const [key, ...rest] = path21;
|
|
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(path21, value) {
|
|
3458
3458
|
if (assertCollection(this.contents))
|
|
3459
|
-
this.contents.addIn(
|
|
3459
|
+
this.contents.addIn(path21, 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(path21) {
|
|
3535
|
+
if (Collection.isEmptyPath(path21)) {
|
|
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(path21) : 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(path21, keepScalar) {
|
|
3557
|
+
if (Collection.isEmptyPath(path21))
|
|
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(path21, 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(path21) {
|
|
3571
|
+
if (Collection.isEmptyPath(path21))
|
|
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(path21) : 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(path21, value) {
|
|
3591
|
+
if (Collection.isEmptyPath(path21)) {
|
|
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(path21), value);
|
|
3595
3595
|
} else if (assertCollection(this.contents)) {
|
|
3596
|
-
this.contents.setIn(
|
|
3596
|
+
this.contents.setIn(path21, 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, path21) => {
|
|
5549
5549
|
let item = cst;
|
|
5550
|
-
for (const [field, index] of
|
|
5550
|
+
for (const [field, index] of path21) {
|
|
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, path21) => {
|
|
5560
|
+
const parent = visit.itemAtPath(cst, path21.slice(0, -1));
|
|
5561
|
+
const field = path21[path21.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(path21, item, visitor) {
|
|
5568
|
+
let ctrl = visitor(item, path21);
|
|
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(path21.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, path21);
|
|
5587
5587
|
}
|
|
5588
5588
|
}
|
|
5589
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5589
|
+
return typeof ctrl === "function" ? ctrl(item, path21) : 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 fs20 = this.flowScalar(this.type);
|
|
6875
6875
|
if (atNextItem || it.value) {
|
|
6876
|
-
map.items.push({ start, key:
|
|
6876
|
+
map.items.push({ start, key: fs20, sep: [] });
|
|
6877
6877
|
this.onKeyLine = true;
|
|
6878
6878
|
} else if (it.sep) {
|
|
6879
|
-
this.stack.push(
|
|
6879
|
+
this.stack.push(fs20);
|
|
6880
6880
|
} else {
|
|
6881
|
-
Object.assign(it, { key:
|
|
6881
|
+
Object.assign(it, { key: fs20, 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 fs20 = this.flowScalar(this.type);
|
|
7010
7010
|
if (!it || it.value)
|
|
7011
|
-
fc.items.push({ start: [], key:
|
|
7011
|
+
fc.items.push({ start: [], key: fs20, sep: [] });
|
|
7012
7012
|
else if (it.sep)
|
|
7013
|
-
this.stack.push(
|
|
7013
|
+
this.stack.push(fs20);
|
|
7014
7014
|
else
|
|
7015
|
-
Object.assign(it, { key:
|
|
7015
|
+
Object.assign(it, { key: fs20, 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 = (path21, originalPath, doThrow) => {
|
|
7554
|
+
if (!isString(path21)) {
|
|
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 (!path21) {
|
|
7561
7561
|
return doThrow(`path must not be empty`, TypeError);
|
|
7562
7562
|
}
|
|
7563
|
-
if (checkPath.isNotRelative(
|
|
7563
|
+
if (checkPath.isNotRelative(path21)) {
|
|
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 = (path21) => REGEX_TEST_INVALID_PATH.test(path21);
|
|
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(path21, 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(path21);
|
|
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 path21 = originalPath && checkPath.convert(originalPath);
|
|
7653
7653
|
checkPath(
|
|
7654
|
-
|
|
7654
|
+
path21,
|
|
7655
7655
|
originalPath,
|
|
7656
7656
|
this._allowRelativePaths ? RETURN_FALSE : throwError
|
|
7657
7657
|
);
|
|
7658
|
-
return this._t(
|
|
7658
|
+
return this._t(path21, cache, checkUnignored, slices);
|
|
7659
7659
|
}
|
|
7660
|
-
_t(
|
|
7661
|
-
if (
|
|
7662
|
-
return cache[
|
|
7660
|
+
_t(path21, cache, checkUnignored, slices) {
|
|
7661
|
+
if (path21 in cache) {
|
|
7662
|
+
return cache[path21];
|
|
7663
7663
|
}
|
|
7664
7664
|
if (!slices) {
|
|
7665
|
-
slices =
|
|
7665
|
+
slices = path21.split(SLASH);
|
|
7666
7666
|
}
|
|
7667
7667
|
slices.pop();
|
|
7668
7668
|
if (!slices.length) {
|
|
7669
|
-
return cache[
|
|
7669
|
+
return cache[path21] = this._testOne(path21, 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[path21] = parent.ignored ? parent : this._testOne(path21, checkUnignored);
|
|
7678
7678
|
}
|
|
7679
|
-
ignores(
|
|
7680
|
-
return this._test(
|
|
7679
|
+
ignores(path21) {
|
|
7680
|
+
return this._test(path21, this._ignoreCache, false).ignored;
|
|
7681
7681
|
}
|
|
7682
7682
|
createFilter() {
|
|
7683
|
-
return (
|
|
7683
|
+
return (path21) => !this.ignores(path21);
|
|
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(path21) {
|
|
7690
|
+
return this._test(path21, this._testCache, true);
|
|
7691
7691
|
}
|
|
7692
7692
|
};
|
|
7693
7693
|
var factory = (options) => new Ignore(options);
|
|
7694
|
-
var isPathValid = (
|
|
7694
|
+
var isPathValid = (path21) => checkPath(path21 && checkPath.convert(path21), path21, RETURN_FALSE);
|
|
7695
7695
|
factory.isPathValid = isPathValid;
|
|
7696
7696
|
factory.default = factory;
|
|
7697
7697
|
module.exports = factory;
|
|
@@ -7702,16 +7702,16 @@ 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 = (path21) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path21) || isNotRelative(path21);
|
|
7706
7706
|
}
|
|
7707
7707
|
}
|
|
7708
7708
|
});
|
|
7709
7709
|
|
|
7710
7710
|
// src/plugin.js
|
|
7711
|
-
import
|
|
7711
|
+
import fs19 from "node:fs";
|
|
7712
7712
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
7713
7713
|
var import_ignore3 = __toESM(require_ignore(), 1);
|
|
7714
|
-
import
|
|
7714
|
+
import path20 from "node:path";
|
|
7715
7715
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
7716
7716
|
|
|
7717
7717
|
// src/agents.js
|
|
@@ -9955,8 +9955,8 @@ function clickUpWebhookLogPath(worktree) {
|
|
|
9955
9955
|
return path7.join(optimaRuntimeDir(worktree), "clickup-webhook.log.jsonl");
|
|
9956
9956
|
}
|
|
9957
9957
|
function clickUpWebhookAuditLogDir() {
|
|
9958
|
-
const
|
|
9959
|
-
return path7.join(
|
|
9958
|
+
const dataHome2 = process.env.XDG_DATA_HOME && path7.isAbsolute(process.env.XDG_DATA_HOME) ? process.env.XDG_DATA_HOME : path7.join(os2.homedir(), ".local", "share", "opencode");
|
|
9959
|
+
return path7.join(dataHome2, "opencode-optima");
|
|
9960
9960
|
}
|
|
9961
9961
|
function clickUpWebhookAuditLogPath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
|
|
9962
9962
|
return path7.join(logDir, `clickup-webhook-${at.toISOString().slice(0, 10)}.jsonl`);
|
|
@@ -11746,13 +11746,19 @@ var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
|
|
|
11746
11746
|
var CLICKUP_HUMAN_HANDOFF_TEXT_PATTERNS = [
|
|
11747
11747
|
/\bhuman validation requested\b/i,
|
|
11748
11748
|
/\bhuman validation\/approval\b/i,
|
|
11749
|
+
/\bvalidation handoff\b/i,
|
|
11750
|
+
/\bplease validate\b/i,
|
|
11751
|
+
/\bfinal validation\b/i,
|
|
11749
11752
|
/\bplease review pr\b/i,
|
|
11750
11753
|
/\bapprove or request changes\b/i,
|
|
11754
|
+
/\bremaining QA\b/i,
|
|
11751
11755
|
/\bexternal blocker\b/i,
|
|
11752
11756
|
/\bowner routing\b/i,
|
|
11753
11757
|
/\bpo action needed\b/i,
|
|
11754
11758
|
/\brouted to PO\b/i,
|
|
11755
11759
|
/\bauthenticated Chrome session\b/i,
|
|
11760
|
+
/\bauthenticated extension session\b/i,
|
|
11761
|
+
/\bbrowser-level\b/i,
|
|
11756
11762
|
/\bbrowser-real\b/i,
|
|
11757
11763
|
/\bAgent Jake\b/i,
|
|
11758
11764
|
/\bCDP\s+9222\b/i,
|
|
@@ -13493,11 +13499,392 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
13493
13499
|
};
|
|
13494
13500
|
}
|
|
13495
13501
|
|
|
13502
|
+
// src/qa/chrome.js
|
|
13503
|
+
import fs16 from "node:fs";
|
|
13504
|
+
import os6 from "node:os";
|
|
13505
|
+
import path17 from "node:path";
|
|
13506
|
+
|
|
13507
|
+
// src/qa/queue.js
|
|
13508
|
+
import fs15 from "node:fs";
|
|
13509
|
+
import os5 from "node:os";
|
|
13510
|
+
import path16 from "node:path";
|
|
13511
|
+
var DEFAULT_QA_LEASE_MS = 5 * 60 * 1e3;
|
|
13512
|
+
var DEFAULT_QA_PROVIDER = "chatgpt";
|
|
13513
|
+
function dataHome() {
|
|
13514
|
+
return process.env.XDG_DATA_HOME && path16.isAbsolute(process.env.XDG_DATA_HOME) ? process.env.XDG_DATA_HOME : path16.join(os5.homedir(), ".local", "share", "opencode");
|
|
13515
|
+
}
|
|
13516
|
+
function qaRuntimeDir() {
|
|
13517
|
+
return path16.join(dataHome(), "opencode-optima", "qa");
|
|
13518
|
+
}
|
|
13519
|
+
function normalizeQaProvider(provider = DEFAULT_QA_PROVIDER) {
|
|
13520
|
+
const normalized = String(provider || DEFAULT_QA_PROVIDER).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
13521
|
+
return normalized || DEFAULT_QA_PROVIDER;
|
|
13522
|
+
}
|
|
13523
|
+
function qaQueueStatePath(provider = DEFAULT_QA_PROVIDER) {
|
|
13524
|
+
return path16.join(qaRuntimeDir(), `${normalizeQaProvider(provider)}-queue.json`);
|
|
13525
|
+
}
|
|
13526
|
+
function nowIso(now = /* @__PURE__ */ new Date()) {
|
|
13527
|
+
return now instanceof Date ? now.toISOString() : new Date(now).toISOString();
|
|
13528
|
+
}
|
|
13529
|
+
function normalizeClickUpTaskId(value = "") {
|
|
13530
|
+
return String(value || "").trim();
|
|
13531
|
+
}
|
|
13532
|
+
function emptyQaQueueState(provider = DEFAULT_QA_PROVIDER) {
|
|
13533
|
+
return {
|
|
13534
|
+
provider: normalizeQaProvider(provider),
|
|
13535
|
+
active: null,
|
|
13536
|
+
queue: [],
|
|
13537
|
+
history: []
|
|
13538
|
+
};
|
|
13539
|
+
}
|
|
13540
|
+
function readQaQueueState(provider = DEFAULT_QA_PROVIDER, statePath = qaQueueStatePath(provider)) {
|
|
13541
|
+
try {
|
|
13542
|
+
const raw = fs15.readFileSync(statePath, "utf8");
|
|
13543
|
+
const parsed = JSON.parse(raw);
|
|
13544
|
+
if (!isPlainObject(parsed)) return emptyQaQueueState(provider);
|
|
13545
|
+
return {
|
|
13546
|
+
...emptyQaQueueState(provider),
|
|
13547
|
+
...parsed,
|
|
13548
|
+
provider: normalizeQaProvider(parsed.provider || provider),
|
|
13549
|
+
queue: Array.isArray(parsed.queue) ? parsed.queue.filter(isPlainObject) : [],
|
|
13550
|
+
history: Array.isArray(parsed.history) ? parsed.history.filter(isPlainObject).slice(-50) : []
|
|
13551
|
+
};
|
|
13552
|
+
} catch {
|
|
13553
|
+
return emptyQaQueueState(provider);
|
|
13554
|
+
}
|
|
13555
|
+
}
|
|
13556
|
+
function writeQaQueueState(state, statePath = qaQueueStatePath(state?.provider)) {
|
|
13557
|
+
const normalized = {
|
|
13558
|
+
...emptyQaQueueState(state?.provider),
|
|
13559
|
+
...state,
|
|
13560
|
+
provider: normalizeQaProvider(state?.provider),
|
|
13561
|
+
queue: Array.isArray(state?.queue) ? state.queue.filter(isPlainObject) : [],
|
|
13562
|
+
history: Array.isArray(state?.history) ? state.history.filter(isPlainObject).slice(-50) : []
|
|
13563
|
+
};
|
|
13564
|
+
fs15.mkdirSync(path16.dirname(statePath), { recursive: true });
|
|
13565
|
+
fs15.writeFileSync(statePath, `${JSON.stringify(normalized, null, 2)}
|
|
13566
|
+
`, { mode: 384 });
|
|
13567
|
+
return normalized;
|
|
13568
|
+
}
|
|
13569
|
+
function activeExpired(active, nowMs, leaseMs) {
|
|
13570
|
+
if (!active?.clickupTaskId) return false;
|
|
13571
|
+
const last = Date.parse(active.lastActivityAt || active.grantedAt || "");
|
|
13572
|
+
return Number.isFinite(last) && nowMs - last > leaseMs;
|
|
13573
|
+
}
|
|
13574
|
+
function queuedIndex(state, clickupTaskId) {
|
|
13575
|
+
return state.queue.findIndex((entry) => entry.clickupTaskId === clickupTaskId);
|
|
13576
|
+
}
|
|
13577
|
+
function queueEntry({ clickupTaskId, sessionId = "", worktree = "", reason = "", now = /* @__PURE__ */ new Date() } = {}) {
|
|
13578
|
+
return {
|
|
13579
|
+
clickupTaskId,
|
|
13580
|
+
sessionId: String(sessionId || "").trim(),
|
|
13581
|
+
worktree: String(worktree || "").trim(),
|
|
13582
|
+
reason: String(reason || "").trim(),
|
|
13583
|
+
queuedAt: nowIso(now),
|
|
13584
|
+
lastRequestAt: nowIso(now)
|
|
13585
|
+
};
|
|
13586
|
+
}
|
|
13587
|
+
function activeEntry({ provider, clickupTaskId, sessionId = "", worktree = "", leaseMs = DEFAULT_QA_LEASE_MS, now = /* @__PURE__ */ new Date() } = {}) {
|
|
13588
|
+
return {
|
|
13589
|
+
provider: normalizeQaProvider(provider),
|
|
13590
|
+
clickupTaskId,
|
|
13591
|
+
sessionId: String(sessionId || "").trim(),
|
|
13592
|
+
worktree: String(worktree || "").trim(),
|
|
13593
|
+
grantedAt: nowIso(now),
|
|
13594
|
+
lastActivityAt: nowIso(now),
|
|
13595
|
+
leaseMs
|
|
13596
|
+
};
|
|
13597
|
+
}
|
|
13598
|
+
function releaseExpiredQaSlot(state, { now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13599
|
+
const normalized = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13600
|
+
if (!activeExpired(normalized.active, now.getTime(), leaseMs)) return { state: normalized, expired: null, promoted: null };
|
|
13601
|
+
const expired = normalized.active;
|
|
13602
|
+
normalized.history = [...normalized.history || [], { action: "expired", clickupTaskId: expired.clickupTaskId, at: nowIso(now) }].slice(-50);
|
|
13603
|
+
normalized.active = null;
|
|
13604
|
+
const promoted = normalized.queue.shift() || null;
|
|
13605
|
+
if (promoted) {
|
|
13606
|
+
normalized.active = activeEntry({ ...promoted, provider: normalized.provider, leaseMs, now });
|
|
13607
|
+
normalized.history = [...normalized.history || [], { action: "promoted", clickupTaskId: promoted.clickupTaskId, at: nowIso(now), reason: "expired_previous_slot" }].slice(-50);
|
|
13608
|
+
}
|
|
13609
|
+
return { state: normalized, expired, promoted };
|
|
13610
|
+
}
|
|
13611
|
+
function requestQaSlot(state, { clickupTaskId, sessionId = "", worktree = "", reason = "", now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13612
|
+
const taskId = normalizeClickUpTaskId(clickupTaskId);
|
|
13613
|
+
if (!taskId) return { state, ok: false, action: "invalid", reason: "clickup_task_id_required" };
|
|
13614
|
+
let next = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13615
|
+
const expiredResult = releaseExpiredQaSlot(next, { now, leaseMs });
|
|
13616
|
+
next = expiredResult.state;
|
|
13617
|
+
if (!next.active) {
|
|
13618
|
+
next.active = activeEntry({ provider: next.provider, clickupTaskId: taskId, sessionId, worktree, leaseMs, now });
|
|
13619
|
+
next.queue = next.queue.filter((entry) => entry.clickupTaskId !== taskId);
|
|
13620
|
+
next.history = [...next.history || [], { action: "granted", clickupTaskId: taskId, at: nowIso(now) }].slice(-50);
|
|
13621
|
+
return { state: next, ok: true, action: "granted", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13622
|
+
}
|
|
13623
|
+
if (next.active.clickupTaskId === taskId) {
|
|
13624
|
+
next.active = { ...next.active, sessionId: sessionId || next.active.sessionId || "", worktree: worktree || next.active.worktree || "", lastActivityAt: nowIso(now), leaseMs };
|
|
13625
|
+
next.history = [...next.history || [], { action: "renewed", clickupTaskId: taskId, at: nowIso(now) }].slice(-50);
|
|
13626
|
+
return { state: next, ok: true, action: "granted_existing", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13627
|
+
}
|
|
13628
|
+
const index = queuedIndex(next, taskId);
|
|
13629
|
+
if (index >= 0) {
|
|
13630
|
+
next.queue[index] = { ...next.queue[index], sessionId: sessionId || next.queue[index].sessionId || "", worktree: worktree || next.queue[index].worktree || "", reason: reason || next.queue[index].reason || "", lastRequestAt: nowIso(now) };
|
|
13631
|
+
} else {
|
|
13632
|
+
next.queue.push(queueEntry({ clickupTaskId: taskId, sessionId, worktree, reason, now }));
|
|
13633
|
+
}
|
|
13634
|
+
return {
|
|
13635
|
+
state: next,
|
|
13636
|
+
ok: false,
|
|
13637
|
+
action: "queued",
|
|
13638
|
+
active: next.active,
|
|
13639
|
+
position: queuedIndex(next, taskId) + 1,
|
|
13640
|
+
expired: expiredResult.expired,
|
|
13641
|
+
promoted: expiredResult.promoted
|
|
13642
|
+
};
|
|
13643
|
+
}
|
|
13644
|
+
function touchQaSlot(state, { clickupTaskId, now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13645
|
+
const taskId = normalizeClickUpTaskId(clickupTaskId);
|
|
13646
|
+
let next = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13647
|
+
const expiredResult = releaseExpiredQaSlot(next, { now, leaseMs });
|
|
13648
|
+
next = expiredResult.state;
|
|
13649
|
+
if (!next.active || next.active.clickupTaskId !== taskId) return { state: next, ok: false, action: "not_active_holder", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13650
|
+
next.active = { ...next.active, lastActivityAt: nowIso(now), leaseMs };
|
|
13651
|
+
return { state: next, ok: true, action: "touched", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13652
|
+
}
|
|
13653
|
+
function finishQaSlot(state, { clickupTaskId, now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13654
|
+
const taskId = normalizeClickUpTaskId(clickupTaskId);
|
|
13655
|
+
let next = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13656
|
+
const expiredResult = releaseExpiredQaSlot(next, { now, leaseMs });
|
|
13657
|
+
next = expiredResult.state;
|
|
13658
|
+
if (!next.active || next.active.clickupTaskId !== taskId) return { state: next, ok: false, action: "not_active_holder", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13659
|
+
const released = next.active;
|
|
13660
|
+
next.active = null;
|
|
13661
|
+
next.history = [...next.history || [], { action: "finished", clickupTaskId: taskId, at: nowIso(now) }].slice(-50);
|
|
13662
|
+
const promoted = next.queue.shift() || null;
|
|
13663
|
+
if (promoted) {
|
|
13664
|
+
next.active = activeEntry({ ...promoted, provider: next.provider, leaseMs, now });
|
|
13665
|
+
next.history = [...next.history || [], { action: "promoted", clickupTaskId: promoted.clickupTaskId, at: nowIso(now), reason: "previous_finished" }].slice(-50);
|
|
13666
|
+
}
|
|
13667
|
+
return { state: next, ok: true, action: "finished", released, promoted };
|
|
13668
|
+
}
|
|
13669
|
+
|
|
13670
|
+
// src/qa/chrome.js
|
|
13671
|
+
var DEFAULT_QA_CDP_URL = "http://127.0.0.1:9222";
|
|
13672
|
+
function providerDir(provider = DEFAULT_QA_PROVIDER) {
|
|
13673
|
+
return path17.join(qaRuntimeDir(), normalizeQaProvider(provider));
|
|
13674
|
+
}
|
|
13675
|
+
function defaultQaExtensionStageDir(provider = DEFAULT_QA_PROVIDER) {
|
|
13676
|
+
return path17.join(providerDir(provider), "extension-under-test");
|
|
13677
|
+
}
|
|
13678
|
+
function defaultQaArtifactsDir(provider = DEFAULT_QA_PROVIDER, clickupTaskId = "unknown") {
|
|
13679
|
+
return path17.join(providerDir(provider), "artifacts", String(clickupTaskId || "unknown"));
|
|
13680
|
+
}
|
|
13681
|
+
function normalizeCdpUrl(value = "") {
|
|
13682
|
+
return String(value || process.env.OPTIMA_QA_CHROME_CDP_URL || DEFAULT_QA_CDP_URL).trim() || DEFAULT_QA_CDP_URL;
|
|
13683
|
+
}
|
|
13684
|
+
async function loadPlaywrightChromium() {
|
|
13685
|
+
try {
|
|
13686
|
+
const mod = await import("playwright-core");
|
|
13687
|
+
return mod.chromium;
|
|
13688
|
+
} catch (error) {
|
|
13689
|
+
throw new Error(`playwright-core is required for Optima QA browser tools: ${error.message}`);
|
|
13690
|
+
}
|
|
13691
|
+
}
|
|
13692
|
+
function qaWindowName(clickupTaskId) {
|
|
13693
|
+
return `optima-qa:${String(clickupTaskId || "").trim()}`;
|
|
13694
|
+
}
|
|
13695
|
+
async function safePageWindowName(page) {
|
|
13696
|
+
try {
|
|
13697
|
+
return await page.evaluate(() => window.name || "");
|
|
13698
|
+
} catch {
|
|
13699
|
+
return "";
|
|
13700
|
+
}
|
|
13701
|
+
}
|
|
13702
|
+
async function findQaPage(context, clickupTaskId) {
|
|
13703
|
+
const expected = qaWindowName(clickupTaskId);
|
|
13704
|
+
const pages = context.pages();
|
|
13705
|
+
for (let index = 1; index < pages.length; index += 1) {
|
|
13706
|
+
const page = pages[index];
|
|
13707
|
+
if (await safePageWindowName(page) === expected) return page;
|
|
13708
|
+
}
|
|
13709
|
+
return null;
|
|
13710
|
+
}
|
|
13711
|
+
async function ensurePersistentTab(context, url = "https://chatgpt.com/") {
|
|
13712
|
+
const pages = context.pages();
|
|
13713
|
+
if (pages[0]) return pages[0];
|
|
13714
|
+
const page = await context.newPage();
|
|
13715
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
13716
|
+
});
|
|
13717
|
+
return page;
|
|
13718
|
+
}
|
|
13719
|
+
async function ensureQaPage(context, { clickupTaskId, startUrl = "https://chatgpt.com/" } = {}) {
|
|
13720
|
+
await ensurePersistentTab(context, startUrl);
|
|
13721
|
+
const existing = await findQaPage(context, clickupTaskId);
|
|
13722
|
+
if (existing) return existing;
|
|
13723
|
+
const page = await context.newPage();
|
|
13724
|
+
await page.goto(startUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
13725
|
+
});
|
|
13726
|
+
await page.evaluate((name) => {
|
|
13727
|
+
window.name = name;
|
|
13728
|
+
}, qaWindowName(clickupTaskId)).catch(() => {
|
|
13729
|
+
});
|
|
13730
|
+
return page;
|
|
13731
|
+
}
|
|
13732
|
+
function copyExtensionSource(sourcePath, stageDir) {
|
|
13733
|
+
const source = String(sourcePath || "").trim();
|
|
13734
|
+
if (!source) return { ok: true, skipped: true, reason: "extension_path_not_provided", stageDir };
|
|
13735
|
+
const resolved = path17.resolve(source.replace(/^~(?=$|\/)/, os6.homedir()));
|
|
13736
|
+
if (!fs16.existsSync(resolved)) return { ok: false, error: `extension_path_not_found: ${resolved}`, stageDir };
|
|
13737
|
+
fs16.rmSync(stageDir, { recursive: true, force: true });
|
|
13738
|
+
fs16.mkdirSync(path17.dirname(stageDir), { recursive: true });
|
|
13739
|
+
fs16.cpSync(resolved, stageDir, {
|
|
13740
|
+
recursive: true,
|
|
13741
|
+
filter: (src) => !/[/\\](node_modules|\.git|dist|build|test-results)([/\\]|$)/.test(src)
|
|
13742
|
+
});
|
|
13743
|
+
return { ok: true, source: resolved, stageDir };
|
|
13744
|
+
}
|
|
13745
|
+
function extensionIdFromWorkerUrl(url = "") {
|
|
13746
|
+
const match = String(url || "").match(/^chrome-extension:\/\/([^/]+)/);
|
|
13747
|
+
return match?.[1] || "";
|
|
13748
|
+
}
|
|
13749
|
+
async function findExtensionWorker(context, extensionId = "") {
|
|
13750
|
+
const workers = context.serviceWorkers ? context.serviceWorkers() : [];
|
|
13751
|
+
const normalized = String(extensionId || "").trim();
|
|
13752
|
+
if (normalized) return workers.find((worker) => worker.url().startsWith(`chrome-extension://${normalized}/`)) || null;
|
|
13753
|
+
return workers.find((worker) => String(worker.url()).startsWith("chrome-extension://")) || null;
|
|
13754
|
+
}
|
|
13755
|
+
async function resetExtensionState(context, { extensionId = "" } = {}) {
|
|
13756
|
+
const worker = await findExtensionWorker(context, extensionId);
|
|
13757
|
+
if (!worker) return { ok: false, error: "extension_service_worker_not_found" };
|
|
13758
|
+
const id = extensionIdFromWorkerUrl(worker.url());
|
|
13759
|
+
const result = await worker.evaluate(async () => {
|
|
13760
|
+
const deletedCaches = [];
|
|
13761
|
+
const deletedDbs = [];
|
|
13762
|
+
if (globalThis.chrome?.storage?.local) await chrome.storage.local.clear();
|
|
13763
|
+
if (globalThis.chrome?.storage?.session?.clear) await chrome.storage.session.clear();
|
|
13764
|
+
if (globalThis.chrome?.storage?.sync) await chrome.storage.sync.clear();
|
|
13765
|
+
if (globalThis.caches?.keys) {
|
|
13766
|
+
for (const name of await caches.keys()) {
|
|
13767
|
+
await caches.delete(name);
|
|
13768
|
+
deletedCaches.push(name);
|
|
13769
|
+
}
|
|
13770
|
+
}
|
|
13771
|
+
if (globalThis.indexedDB?.databases) {
|
|
13772
|
+
for (const db of await indexedDB.databases()) {
|
|
13773
|
+
if (db.name) {
|
|
13774
|
+
indexedDB.deleteDatabase(db.name);
|
|
13775
|
+
deletedDbs.push(db.name);
|
|
13776
|
+
}
|
|
13777
|
+
}
|
|
13778
|
+
}
|
|
13779
|
+
return { ok: true, deletedCaches, deletedDbs };
|
|
13780
|
+
});
|
|
13781
|
+
return { extensionId: id, ...result };
|
|
13782
|
+
}
|
|
13783
|
+
async function reloadExtension(context, { extensionId = "" } = {}) {
|
|
13784
|
+
const worker = await findExtensionWorker(context, extensionId);
|
|
13785
|
+
if (!worker) return { ok: false, error: "extension_service_worker_not_found" };
|
|
13786
|
+
const id = extensionIdFromWorkerUrl(worker.url());
|
|
13787
|
+
await worker.evaluate(() => chrome.runtime.reload()).catch(() => {
|
|
13788
|
+
});
|
|
13789
|
+
return { ok: true, extensionId: id };
|
|
13790
|
+
}
|
|
13791
|
+
async function summarizeBrowser(context, { clickupTaskId = "" } = {}) {
|
|
13792
|
+
const pages = context.pages();
|
|
13793
|
+
const pageSummaries = [];
|
|
13794
|
+
for (let index = 0; index < pages.length; index += 1) {
|
|
13795
|
+
const page = pages[index];
|
|
13796
|
+
pageSummaries.push({
|
|
13797
|
+
index,
|
|
13798
|
+
role: index === 0 ? "persistent_session_tab" : await safePageWindowName(page) === qaWindowName(clickupTaskId) ? "qa_task_tab" : "other",
|
|
13799
|
+
url: page.url(),
|
|
13800
|
+
title: await page.title().catch(() => "")
|
|
13801
|
+
});
|
|
13802
|
+
}
|
|
13803
|
+
const workers = context.serviceWorkers ? context.serviceWorkers().map((worker) => worker.url()) : [];
|
|
13804
|
+
return { pages: pageSummaries, serviceWorkers: workers };
|
|
13805
|
+
}
|
|
13806
|
+
function parseQaCommand(commandJson = "") {
|
|
13807
|
+
if (!String(commandJson || "").trim()) return { action: "status" };
|
|
13808
|
+
try {
|
|
13809
|
+
const parsed = JSON.parse(commandJson);
|
|
13810
|
+
return parsed && typeof parsed === "object" ? parsed : { action: "invalid", error: "command_json_must_be_object" };
|
|
13811
|
+
} catch (error) {
|
|
13812
|
+
return { action: "script", script: String(commandJson || "") };
|
|
13813
|
+
}
|
|
13814
|
+
}
|
|
13815
|
+
async function runQaCommandOnPage(page, context, browser, command, { artifactsDir }) {
|
|
13816
|
+
const action = String(command.action || "status").trim().toLowerCase();
|
|
13817
|
+
if (action === "status") return { ok: true, page: { url: page.url(), title: await page.title().catch(() => "") } };
|
|
13818
|
+
if (action === "goto") {
|
|
13819
|
+
await page.goto(String(command.url || "https://chatgpt.com/"), { waitUntil: command.wait_until || "domcontentloaded", timeout: Number(command.timeout_ms || 3e4) });
|
|
13820
|
+
return { ok: true, url: page.url(), title: await page.title().catch(() => "") };
|
|
13821
|
+
}
|
|
13822
|
+
if (action === "evaluate") {
|
|
13823
|
+
const result = await page.evaluate(String(command.expression || "() => null"));
|
|
13824
|
+
return { ok: true, result };
|
|
13825
|
+
}
|
|
13826
|
+
if (action === "screenshot") {
|
|
13827
|
+
const name = String(command.name || `screenshot-${Date.now()}.png`).replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
13828
|
+
const outPath = path17.join(artifactsDir, name);
|
|
13829
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
13830
|
+
await page.screenshot({ path: outPath, fullPage: command.full_page !== false });
|
|
13831
|
+
return { ok: true, path: outPath };
|
|
13832
|
+
}
|
|
13833
|
+
if (action === "set_input_files") {
|
|
13834
|
+
const selector = String(command.selector || "input[type=file]");
|
|
13835
|
+
await page.setInputFiles(selector, command.files);
|
|
13836
|
+
return { ok: true, selector };
|
|
13837
|
+
}
|
|
13838
|
+
if (action === "script") {
|
|
13839
|
+
const script = String(command.script || "");
|
|
13840
|
+
if (!script.trim()) return { ok: false, error: "script_required" };
|
|
13841
|
+
const fn = new Function("page", "context", "browser", "artifactsDir", `return (async () => {
|
|
13842
|
+
${script}
|
|
13843
|
+
})()`);
|
|
13844
|
+
const result = await fn(page, context, browser, artifactsDir);
|
|
13845
|
+
return { ok: true, result };
|
|
13846
|
+
}
|
|
13847
|
+
return { ok: false, error: `unsupported_qa_command_action: ${action}` };
|
|
13848
|
+
}
|
|
13849
|
+
async function withQaBrowser({ provider = DEFAULT_QA_PROVIDER, cdpUrl = "", clickupTaskId = "", startUrl = "https://chatgpt.com/" } = {}, callback) {
|
|
13850
|
+
const chromium = await loadPlaywrightChromium();
|
|
13851
|
+
const browser = await chromium.connectOverCDP(normalizeCdpUrl(cdpUrl));
|
|
13852
|
+
try {
|
|
13853
|
+
const context = browser.contexts()[0] || await browser.newContext();
|
|
13854
|
+
const page = clickupTaskId ? await ensureQaPage(context, { clickupTaskId, startUrl }) : null;
|
|
13855
|
+
return await callback({ browser, context, page });
|
|
13856
|
+
} finally {
|
|
13857
|
+
await browser.close().catch(() => {
|
|
13858
|
+
});
|
|
13859
|
+
}
|
|
13860
|
+
}
|
|
13861
|
+
async function qaBrowserStatus(options = {}) {
|
|
13862
|
+
return withQaBrowser(options, async ({ context }) => ({ ok: true, ...await summarizeBrowser(context, { clickupTaskId: options.clickupTaskId }) })).catch((error) => ({ ok: false, error: error.message, cdpUrl: normalizeCdpUrl(options.cdpUrl) }));
|
|
13863
|
+
}
|
|
13864
|
+
async function qaPrepareExtension({ provider = DEFAULT_QA_PROVIDER, cdpUrl = "", extensionPath = "", extensionId = "", reset = true, reload = true } = {}) {
|
|
13865
|
+
const stageDir = defaultQaExtensionStageDir(provider);
|
|
13866
|
+
const sync = copyExtensionSource(extensionPath, stageDir);
|
|
13867
|
+
const browserResult = await withQaBrowser({ provider, cdpUrl }, async ({ context }) => {
|
|
13868
|
+
const resetResult = reset ? await resetExtensionState(context, { extensionId }).catch((error) => ({ ok: false, error: error.message })) : { ok: true, skipped: true };
|
|
13869
|
+
const reloadResult = reload ? await reloadExtension(context, { extensionId }).catch((error) => ({ ok: false, error: error.message })) : { ok: true, skipped: true };
|
|
13870
|
+
return { reset: resetResult, reload: reloadResult, browser: await summarizeBrowser(context) };
|
|
13871
|
+
}).catch((error) => ({ ok: false, error: error.message }));
|
|
13872
|
+
return { sync, ...browserResult };
|
|
13873
|
+
}
|
|
13874
|
+
async function qaRunChromeCommand({ provider = DEFAULT_QA_PROVIDER, cdpUrl = "", clickupTaskId = "", commandJson = "", startUrl = "https://chatgpt.com/" } = {}) {
|
|
13875
|
+
const command = parseQaCommand(commandJson);
|
|
13876
|
+
const artifactsDir = defaultQaArtifactsDir(provider, clickupTaskId);
|
|
13877
|
+
return withQaBrowser({ provider, cdpUrl, clickupTaskId, startUrl }, async ({ browser, context, page }) => {
|
|
13878
|
+
const result = await runQaCommandOnPage(page, context, browser, command, { artifactsDir });
|
|
13879
|
+
return { ok: result.ok !== false, command: command.action || "script", tab: { windowName: qaWindowName(clickupTaskId), url: page.url(), title: await page.title().catch(() => "") }, artifactsDir, result };
|
|
13880
|
+
});
|
|
13881
|
+
}
|
|
13882
|
+
|
|
13496
13883
|
// src/repair.js
|
|
13497
13884
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
13498
13885
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
13499
|
-
import
|
|
13500
|
-
import
|
|
13886
|
+
import fs17 from "node:fs";
|
|
13887
|
+
import path18 from "node:path";
|
|
13501
13888
|
var CODEMAP_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13502
13889
|
".js",
|
|
13503
13890
|
".ts",
|
|
@@ -13535,10 +13922,10 @@ var LEGACY_OPERATIONAL_ROOTS = /* @__PURE__ */ new Set([
|
|
|
13535
13922
|
]);
|
|
13536
13923
|
var OPTIMA_OPERATIONAL_FOLDERS = [".optima", "templates", "dist"];
|
|
13537
13924
|
function relPath(worktree, targetPath) {
|
|
13538
|
-
return
|
|
13925
|
+
return path18.relative(worktree, targetPath).split(path18.sep).join("/") || ".";
|
|
13539
13926
|
}
|
|
13540
13927
|
function normalizeCodemapRelPath(relPathValue) {
|
|
13541
|
-
return
|
|
13928
|
+
return path18.normalize(relPathValue).replace(/\\/g, "/").replace(/\/$/, "");
|
|
13542
13929
|
}
|
|
13543
13930
|
function firstPathSegment(relPathValue) {
|
|
13544
13931
|
return normalizeCodemapRelPath(relPathValue).split("/").filter(Boolean)[0] || "";
|
|
@@ -13557,8 +13944,8 @@ function isHiddenTree(relPathValue) {
|
|
|
13557
13944
|
function loadGitIgnoreMatcher(worktree) {
|
|
13558
13945
|
const ig = (0, import_ignore.default)();
|
|
13559
13946
|
ig.add(".git");
|
|
13560
|
-
const gitignorePath =
|
|
13561
|
-
if (
|
|
13947
|
+
const gitignorePath = path18.join(worktree, ".gitignore");
|
|
13948
|
+
if (fs17.existsSync(gitignorePath)) ig.add(fs17.readFileSync(gitignorePath, "utf8"));
|
|
13562
13949
|
return ig;
|
|
13563
13950
|
}
|
|
13564
13951
|
function codemapSectionEntries(value) {
|
|
@@ -13577,7 +13964,7 @@ function codemapIndexedPaths(map) {
|
|
|
13577
13964
|
}
|
|
13578
13965
|
function readCodemap(filePath, unresolved, worktree) {
|
|
13579
13966
|
try {
|
|
13580
|
-
const parsed = import_yaml4.default.parse(
|
|
13967
|
+
const parsed = import_yaml4.default.parse(fs17.readFileSync(filePath, "utf8"));
|
|
13581
13968
|
if (!parsed || typeof parsed !== "object") {
|
|
13582
13969
|
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap is empty or invalid; manual repair required." });
|
|
13583
13970
|
return null;
|
|
@@ -13589,42 +13976,42 @@ function readCodemap(filePath, unresolved, worktree) {
|
|
|
13589
13976
|
}
|
|
13590
13977
|
}
|
|
13591
13978
|
function writeCodemap(filePath, map) {
|
|
13592
|
-
|
|
13979
|
+
fs17.writeFileSync(filePath, import_yaml4.default.stringify(map), "utf8");
|
|
13593
13980
|
}
|
|
13594
13981
|
function isIgnoredPath(ig, relativePath) {
|
|
13595
13982
|
const normalized = normalizeCodemapRelPath(relativePath);
|
|
13596
13983
|
return Boolean(normalized) && ig.ignores(normalized);
|
|
13597
13984
|
}
|
|
13598
13985
|
function hasImmediateSourceFile(dirPath, ig, worktree) {
|
|
13599
|
-
if (!
|
|
13600
|
-
for (const item of
|
|
13601
|
-
const childPath =
|
|
13602
|
-
const relative =
|
|
13986
|
+
if (!fs17.existsSync(dirPath)) return false;
|
|
13987
|
+
for (const item of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
13988
|
+
const childPath = path18.join(dirPath, item.name);
|
|
13989
|
+
const relative = path18.relative(worktree, childPath);
|
|
13603
13990
|
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative)) continue;
|
|
13604
|
-
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(
|
|
13991
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) return true;
|
|
13605
13992
|
}
|
|
13606
13993
|
return false;
|
|
13607
13994
|
}
|
|
13608
13995
|
function containsSource(dirPath, ig, worktree) {
|
|
13609
|
-
if (!
|
|
13610
|
-
const dirRelPath =
|
|
13996
|
+
if (!fs17.existsSync(dirPath)) return false;
|
|
13997
|
+
const dirRelPath = path18.relative(worktree, dirPath);
|
|
13611
13998
|
if (isOperationalRelPath(dirRelPath) || isHiddenTree(dirRelPath)) return false;
|
|
13612
|
-
for (const item of
|
|
13613
|
-
const childPath =
|
|
13614
|
-
const relative =
|
|
13999
|
+
for (const item of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
14000
|
+
const childPath = path18.join(dirPath, item.name);
|
|
14001
|
+
const relative = path18.relative(worktree, childPath);
|
|
13615
14002
|
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative)) continue;
|
|
13616
|
-
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(
|
|
14003
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) return true;
|
|
13617
14004
|
if (item.isDirectory() && containsSource(childPath, ig, worktree)) return true;
|
|
13618
14005
|
}
|
|
13619
14006
|
return false;
|
|
13620
14007
|
}
|
|
13621
14008
|
function createModuleCodemap(dirPath, worktree, deps) {
|
|
13622
|
-
const entries =
|
|
14009
|
+
const entries = fs17.readdirSync(dirPath, { withFileTypes: true });
|
|
13623
14010
|
const entrypoints = [];
|
|
13624
14011
|
const internals = [];
|
|
13625
|
-
const relToRoot =
|
|
14012
|
+
const relToRoot = path18.relative(dirPath, deps.optimaCodemapPath(worktree)).split(path18.sep).join("/");
|
|
13626
14013
|
for (const item of entries) {
|
|
13627
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
14014
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) continue;
|
|
13628
14015
|
const bucket = /^index\.(js|ts|tsx|jsx)$|^main\.(js|ts|tsx|jsx|py|go|rs|java)$/.test(item.name) ? entrypoints : internals;
|
|
13629
14016
|
bucket.push({ path: item.name });
|
|
13630
14017
|
}
|
|
@@ -13637,7 +14024,7 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13637
14024
|
const map = readCodemap(codemapPath, unresolved, worktree);
|
|
13638
14025
|
if (!map) return;
|
|
13639
14026
|
const isRootMap = codemapPath === deps.optimaCodemapPath(worktree);
|
|
13640
|
-
const mapDir =
|
|
14027
|
+
const mapDir = path18.dirname(codemapPath);
|
|
13641
14028
|
const pathBase = isRootMap ? worktree : mapDir;
|
|
13642
14029
|
let changed = false;
|
|
13643
14030
|
for (const section of ["modules", "entrypoints", "sources_of_truth", "links", "internals"]) {
|
|
@@ -13648,13 +14035,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13648
14035
|
}
|
|
13649
14036
|
const nextEntries = [];
|
|
13650
14037
|
for (const entry of originalEntries) {
|
|
13651
|
-
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") ||
|
|
14038
|
+
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") || path18.isAbsolute(entry.path)) {
|
|
13652
14039
|
nextEntries.push(entry);
|
|
13653
14040
|
continue;
|
|
13654
14041
|
}
|
|
13655
14042
|
const normalizedPath = normalizeCodemapRelPath(entry.path);
|
|
13656
|
-
const absPath =
|
|
13657
|
-
if (!
|
|
14043
|
+
const absPath = path18.join(pathBase, normalizedPath);
|
|
14044
|
+
if (!fs17.existsSync(absPath)) {
|
|
13658
14045
|
registerAction({ category: "codemap", action: apply ? "removed" : "would_remove", path: relPath(worktree, codemapPath), detail: `Remove broken ${section.slice(0, -1)} reference '${entry.path}'.` });
|
|
13659
14046
|
changed = true;
|
|
13660
14047
|
continue;
|
|
@@ -13663,8 +14050,8 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13663
14050
|
const maxParts = isRootMap ? 2 : 1;
|
|
13664
14051
|
if (parts.length > maxParts) {
|
|
13665
14052
|
const localPath = isRootMap ? parts.slice(0, 2).join("/") : parts[0];
|
|
13666
|
-
const localAbs =
|
|
13667
|
-
if (
|
|
14053
|
+
const localAbs = path18.join(pathBase, localPath);
|
|
14054
|
+
if (fs17.existsSync(localAbs)) {
|
|
13668
14055
|
nextEntries.push({ ...entry, path: localPath });
|
|
13669
14056
|
changed = true;
|
|
13670
14057
|
registerAction({ category: "codemap", action: apply ? "updated" : "would_update", path: relPath(worktree, codemapPath), detail: `Shorten '${entry.path}' to local path '${localPath}'.` });
|
|
@@ -13678,11 +14065,11 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13678
14065
|
}
|
|
13679
14066
|
if (Array.isArray(map[section])) map[section] = nextEntries;
|
|
13680
14067
|
}
|
|
13681
|
-
if (map.scope === "module" && !isOperationalRelPath(
|
|
14068
|
+
if (map.scope === "module" && !isOperationalRelPath(path18.relative(worktree, mapDir))) {
|
|
13682
14069
|
const indexed = codemapIndexedPaths(map);
|
|
13683
14070
|
const internals = Array.isArray(map.internals) ? map.internals : [];
|
|
13684
|
-
for (const item of
|
|
13685
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
14071
|
+
for (const item of fs17.readdirSync(mapDir, { withFileTypes: true })) {
|
|
14072
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) continue;
|
|
13686
14073
|
if (indexed.has(item.name)) continue;
|
|
13687
14074
|
internals.push({ path: item.name });
|
|
13688
14075
|
indexed.add(item.name);
|
|
@@ -13696,13 +14083,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13696
14083
|
function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
13697
14084
|
const ig = loadGitIgnoreMatcher(worktree);
|
|
13698
14085
|
const rootCodemapPath = deps.optimaCodemapPath(worktree);
|
|
13699
|
-
const rootCodemapMissing = !
|
|
14086
|
+
const rootCodemapMissing = !fs17.existsSync(rootCodemapPath);
|
|
13700
14087
|
if (rootCodemapMissing) {
|
|
13701
14088
|
actions.push({ category: "codemap", action: apply ? "created" : "would_create", path: ".optima/codemap.yml", detail: "Create missing root CodeMap from template." });
|
|
13702
14089
|
if (apply) deps.scaffoldOptimaRootCodemap(worktree);
|
|
13703
14090
|
}
|
|
13704
|
-
if (
|
|
13705
|
-
const rootMap =
|
|
14091
|
+
if (fs17.existsSync(rootCodemapPath)) repairSingleCodemap(rootCodemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
14092
|
+
const rootMap = fs17.existsSync(rootCodemapPath) ? readCodemap(rootCodemapPath, unresolved, worktree) : null;
|
|
13706
14093
|
let rootChanged = false;
|
|
13707
14094
|
const rootModulesRepairable = rootMap && (rootMap.modules === void 0 || Array.isArray(rootMap.modules));
|
|
13708
14095
|
if (rootMap) {
|
|
@@ -13714,10 +14101,10 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
13714
14101
|
}
|
|
13715
14102
|
}
|
|
13716
14103
|
function walk(dirPath) {
|
|
13717
|
-
const relative =
|
|
14104
|
+
const relative = path18.relative(worktree, dirPath);
|
|
13718
14105
|
if (relative && (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative))) return;
|
|
13719
|
-
const codemapPath =
|
|
13720
|
-
const hasCodemap =
|
|
14106
|
+
const codemapPath = path18.join(dirPath, "codemap.yml");
|
|
14107
|
+
const hasCodemap = fs17.existsSync(codemapPath);
|
|
13721
14108
|
const sourceDir = relative && containsSource(dirPath, ig, worktree);
|
|
13722
14109
|
if (sourceDir && !hasCodemap && dirPath !== worktree) {
|
|
13723
14110
|
if (hasImmediateSourceFile(dirPath, ig, worktree)) {
|
|
@@ -13736,19 +14123,19 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
13736
14123
|
actions.push({ category: "codemap", action: apply ? "updated" : "would_update", path: ".optima/codemap.yml", detail: `Register top-level source module '${normalizeCodemapRelPath(relative)}'.` });
|
|
13737
14124
|
}
|
|
13738
14125
|
}
|
|
13739
|
-
if (
|
|
13740
|
-
for (const item of
|
|
13741
|
-
if (item.isDirectory()) walk(
|
|
14126
|
+
if (fs17.existsSync(codemapPath) && codemapPath !== rootCodemapPath) repairSingleCodemap(codemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
14127
|
+
for (const item of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
14128
|
+
if (item.isDirectory()) walk(path18.join(dirPath, item.name));
|
|
13742
14129
|
}
|
|
13743
14130
|
}
|
|
13744
|
-
if (
|
|
14131
|
+
if (fs17.existsSync(worktree)) walk(worktree);
|
|
13745
14132
|
if (rootMap && rootChanged && apply) writeCodemap(rootCodemapPath, rootMap);
|
|
13746
14133
|
}
|
|
13747
14134
|
function walkMarkdownFiles(dirPath) {
|
|
13748
|
-
if (!
|
|
14135
|
+
if (!fs17.existsSync(dirPath)) return [];
|
|
13749
14136
|
const files = [];
|
|
13750
|
-
for (const entry of
|
|
13751
|
-
const entryPath =
|
|
14137
|
+
for (const entry of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
14138
|
+
const entryPath = path18.join(dirPath, entry.name);
|
|
13752
14139
|
if (entry.isDirectory()) {
|
|
13753
14140
|
files.push(...walkMarkdownFiles(entryPath));
|
|
13754
14141
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -13787,7 +14174,7 @@ function rewriteOptimaMarkdownReferences(content, evidenceBasePath = null) {
|
|
|
13787
14174
|
}
|
|
13788
14175
|
function optimaEvidenceBaseForFile(worktree, filePath, deps) {
|
|
13789
14176
|
const evidenceRoot = deps.optimaEvidencesDir(worktree);
|
|
13790
|
-
const relative =
|
|
14177
|
+
const relative = path18.relative(evidenceRoot, filePath).split(path18.sep).join("/");
|
|
13791
14178
|
const [taskId] = relative.split("/");
|
|
13792
14179
|
if (!taskId || taskId === ".." || relative.startsWith("../")) return null;
|
|
13793
14180
|
return `.optima/evidences/${taskId}`;
|
|
@@ -13800,11 +14187,11 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
13800
14187
|
];
|
|
13801
14188
|
const changed = [];
|
|
13802
14189
|
for (const filePath of scanRoots.flatMap(walkMarkdownFiles)) {
|
|
13803
|
-
const raw =
|
|
13804
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
14190
|
+
const raw = fs17.readFileSync(filePath, "utf8");
|
|
14191
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path18.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
13805
14192
|
const rewritten = rewriteOptimaMarkdownReferences(raw, evidenceBasePath);
|
|
13806
14193
|
if (rewritten !== raw) {
|
|
13807
|
-
|
|
14194
|
+
fs17.writeFileSync(filePath, rewritten, "utf8");
|
|
13808
14195
|
changed.push(relPath(worktree, filePath));
|
|
13809
14196
|
}
|
|
13810
14197
|
}
|
|
@@ -13812,8 +14199,8 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
13812
14199
|
}
|
|
13813
14200
|
function missingOptimaGitignoreRules(worktree, deps) {
|
|
13814
14201
|
if (!deps.isGitRepository(worktree)) return [];
|
|
13815
|
-
const gitignorePath =
|
|
13816
|
-
const existing =
|
|
14202
|
+
const gitignorePath = path18.join(worktree, ".gitignore");
|
|
14203
|
+
const existing = fs17.existsSync(gitignorePath) ? fs17.readFileSync(gitignorePath, "utf8") : "";
|
|
13817
14204
|
const existingRules = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
13818
14205
|
return deps.optimaGitignoreRules.filter((rule) => !existingRules.has(rule));
|
|
13819
14206
|
}
|
|
@@ -13830,11 +14217,11 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
13830
14217
|
const apply = mode === "apply";
|
|
13831
14218
|
const actions = [];
|
|
13832
14219
|
const unresolved = [];
|
|
13833
|
-
const hadOptima =
|
|
14220
|
+
const hadOptima = fs17.existsSync(deps.optimaDir(worktree));
|
|
13834
14221
|
const missingGitignoreRules = missingOptimaGitignoreRules(worktree, deps);
|
|
13835
14222
|
const markdownBefore = apply ? [] : walkMarkdownFiles(deps.optimaDir(worktree)).filter((filePath) => {
|
|
13836
|
-
const raw =
|
|
13837
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
14223
|
+
const raw = fs17.readFileSync(filePath, "utf8");
|
|
14224
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path18.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
13838
14225
|
return rewriteOptimaMarkdownReferences(raw, evidenceBasePath) !== raw;
|
|
13839
14226
|
}).map((filePath) => relPath(worktree, filePath));
|
|
13840
14227
|
if (!hadOptima) pushRepairAction(actions, "operational", apply ? "created" : "would_create", ".optima/", "Create Optima operational root.");
|
|
@@ -13843,31 +14230,31 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
13843
14230
|
[".staticeng/", deps.legacyStaticEngDir(worktree)],
|
|
13844
14231
|
[".nomadwork/", deps.legacyNomadworkDir(worktree)],
|
|
13845
14232
|
[".nomadworks/", deps.legacyNomadworksDir(worktree)],
|
|
13846
|
-
["tasks/",
|
|
13847
|
-
["evidences/",
|
|
13848
|
-
["docs/scrs/",
|
|
13849
|
-
["codemap.yml",
|
|
13850
|
-
["codemap.yaml",
|
|
14233
|
+
["tasks/", path18.join(worktree, "tasks")],
|
|
14234
|
+
["evidences/", path18.join(worktree, "evidences")],
|
|
14235
|
+
["docs/scrs/", path18.join(worktree, "docs", "scrs")],
|
|
14236
|
+
["codemap.yml", path18.join(worktree, "codemap.yml")],
|
|
14237
|
+
["codemap.yaml", path18.join(worktree, "codemap.yaml")]
|
|
13851
14238
|
];
|
|
13852
|
-
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/",
|
|
14239
|
+
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/", path18.join(worktree, "policies")]);
|
|
13853
14240
|
for (const [display, sourcePath] of legacySources) {
|
|
13854
|
-
if (
|
|
14241
|
+
if (fs17.existsSync(sourcePath)) pushRepairAction(actions, "legacy", apply ? "migrated" : "would_migrate", display, "Move or merge legacy/root Optima artifact into .optima using preservation safeguards.");
|
|
13855
14242
|
}
|
|
13856
14243
|
if (apply) deps.migrateLegacyOptimaLayout(worktree);
|
|
13857
|
-
const configMissing = !
|
|
14244
|
+
const configMissing = !fs17.existsSync(deps.repoConfigPath(worktree));
|
|
13858
14245
|
if (configMissing) pushRepairAction(actions, "config", apply ? "created" : "would_create", ".optima/.config/optima.yaml", "Create missing local Optima config from template.");
|
|
13859
14246
|
if (apply) deps.scaffoldOptimaConfig(worktree, args.team_mode || "full");
|
|
13860
14247
|
const requiredFiles = [
|
|
13861
|
-
[
|
|
13862
|
-
[
|
|
13863
|
-
[
|
|
13864
|
-
[
|
|
13865
|
-
[
|
|
13866
|
-
[
|
|
13867
|
-
[
|
|
14248
|
+
[path18.join(deps.optimaTasksDir(worktree), "current.md"), "registry", ".optima/tasks/current.md", deps.currentTasksRegistryContent()],
|
|
14249
|
+
[path18.join(deps.optimaTasksDir(worktree), "done.md"), "registry", ".optima/tasks/done.md", deps.doneTasksRegistryContent()],
|
|
14250
|
+
[path18.join(deps.optimaScrsDir(worktree), "current.md"), "registry", ".optima/docs/scrs/current.md", deps.currentScrRegistryContent()],
|
|
14251
|
+
[path18.join(deps.optimaScrsDir(worktree), "done.md"), "registry", ".optima/docs/scrs/done.md", deps.doneScrRegistryContent()],
|
|
14252
|
+
[path18.join(deps.optimaTasksDir(worktree), "task-template.md"), "template", ".optima/tasks/task-template.md", deps.taskTemplateContent()],
|
|
14253
|
+
[path18.join(deps.optimaTasksDir(worktree), "subtask-template.md"), "template", ".optima/tasks/subtask-template.md", deps.subtaskTemplateContent()],
|
|
14254
|
+
[path18.join(deps.repoPoliciesDir(worktree), "README.md"), "policy", ".optima/policies/README.md", deps.repoLocalPoliciesReadme]
|
|
13868
14255
|
];
|
|
13869
14256
|
for (const [filePath, category, displayPath, content] of requiredFiles) {
|
|
13870
|
-
if (!
|
|
14257
|
+
if (!fs17.existsSync(filePath)) {
|
|
13871
14258
|
pushRepairAction(actions, category, apply ? "created" : "would_create", displayPath, "Create missing Optima scaffold file without overwriting existing content.");
|
|
13872
14259
|
if (apply) deps.ensureFileIfMissing(filePath, content);
|
|
13873
14260
|
}
|
|
@@ -13921,18 +14308,18 @@ function formatRepairResult(plan, validationResult = null, formatValidationResul
|
|
|
13921
14308
|
// src/validate_logic.js
|
|
13922
14309
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
13923
14310
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
13924
|
-
import
|
|
13925
|
-
import
|
|
14311
|
+
import fs18 from "node:fs";
|
|
14312
|
+
import path19 from "node:path";
|
|
13926
14313
|
async function optima_validate_logic(worktree) {
|
|
13927
|
-
const rootCodemapPath =
|
|
13928
|
-
if (!
|
|
14314
|
+
const rootCodemapPath = path19.join(worktree, ".optima", "codemap.yml");
|
|
14315
|
+
if (!fs18.existsSync(rootCodemapPath)) return { ok: false, errors: [".optima/codemap.yml not found."], warnings: [] };
|
|
13929
14316
|
const errors = [];
|
|
13930
14317
|
const warnings = [];
|
|
13931
14318
|
const ig = (0, import_ignore2.default)();
|
|
13932
14319
|
ig.add(".git");
|
|
13933
|
-
const gitignorePath =
|
|
13934
|
-
if (
|
|
13935
|
-
ig.add(
|
|
14320
|
+
const gitignorePath = path19.join(worktree, ".gitignore");
|
|
14321
|
+
if (fs18.existsSync(gitignorePath)) {
|
|
14322
|
+
ig.add(fs18.readFileSync(gitignorePath, "utf8"));
|
|
13936
14323
|
}
|
|
13937
14324
|
const sourceExtensions = [
|
|
13938
14325
|
".js",
|
|
@@ -13972,30 +14359,30 @@ async function optima_validate_logic(worktree) {
|
|
|
13972
14359
|
const operationalFolders = [".optima", "templates", "dist"];
|
|
13973
14360
|
const isHiddenTree2 = (relPath2) => {
|
|
13974
14361
|
if (!relPath2) return false;
|
|
13975
|
-
return relPath2.split(
|
|
14362
|
+
return relPath2.split(path19.sep).some((part) => part.startsWith("."));
|
|
13976
14363
|
};
|
|
13977
|
-
const firstPathSegment2 = (relPath2) => relPath2.split(
|
|
14364
|
+
const firstPathSegment2 = (relPath2) => relPath2.split(path19.sep).filter(Boolean)[0] || "";
|
|
13978
14365
|
const isOperationalRelPath2 = (relPath2) => {
|
|
13979
14366
|
if (!relPath2) return false;
|
|
13980
14367
|
const firstSegment = firstPathSegment2(relPath2);
|
|
13981
14368
|
if (legacyOperationalRoots.has(firstSegment)) return true;
|
|
13982
|
-
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f +
|
|
14369
|
+
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f + path19.sep));
|
|
13983
14370
|
};
|
|
13984
14371
|
const getSectionEntries = (value) => {
|
|
13985
14372
|
if (Array.isArray(value)) return value;
|
|
13986
14373
|
if (value && typeof value === "object") return Object.values(value);
|
|
13987
14374
|
return [];
|
|
13988
14375
|
};
|
|
13989
|
-
const normalizeRelativePath = (relPath2) =>
|
|
14376
|
+
const normalizeRelativePath = (relPath2) => path19.normalize(relPath2).replace(/\\/g, "/").replace(/\/$/, "");
|
|
13990
14377
|
const isSourceDir = (dirPath) => {
|
|
13991
|
-
const dirRelPath =
|
|
14378
|
+
const dirRelPath = path19.relative(worktree, dirPath);
|
|
13992
14379
|
if (isOperationalRelPath2(dirRelPath)) return false;
|
|
13993
|
-
const items =
|
|
14380
|
+
const items = fs18.readdirSync(dirPath, { withFileTypes: true });
|
|
13994
14381
|
for (const item of items) {
|
|
13995
|
-
const childPath =
|
|
13996
|
-
const relPath2 =
|
|
14382
|
+
const childPath = path19.join(dirPath, item.name);
|
|
14383
|
+
const relPath2 = path19.relative(worktree, childPath);
|
|
13997
14384
|
if (ig.ignores(relPath2) || isOperationalRelPath2(relPath2)) continue;
|
|
13998
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
14385
|
+
if (item.isFile() && sourceExtensions.includes(path19.extname(item.name))) return true;
|
|
13999
14386
|
if (item.isDirectory()) {
|
|
14000
14387
|
if (isSourceDir(childPath)) return true;
|
|
14001
14388
|
}
|
|
@@ -14003,7 +14390,7 @@ async function optima_validate_logic(worktree) {
|
|
|
14003
14390
|
return false;
|
|
14004
14391
|
};
|
|
14005
14392
|
function validateMap(filePath) {
|
|
14006
|
-
const content =
|
|
14393
|
+
const content = fs18.readFileSync(filePath, "utf8");
|
|
14007
14394
|
let map;
|
|
14008
14395
|
try {
|
|
14009
14396
|
map = import_yaml5.default.parse(content);
|
|
@@ -14015,32 +14402,32 @@ async function optima_validate_logic(worktree) {
|
|
|
14015
14402
|
errors.push(`${filePath}: Invalid YAML.`);
|
|
14016
14403
|
return;
|
|
14017
14404
|
}
|
|
14018
|
-
const dir =
|
|
14405
|
+
const dir = path19.dirname(filePath);
|
|
14019
14406
|
const pathBase = filePath === rootCodemapPath ? worktree : dir;
|
|
14020
14407
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
14021
14408
|
const sectionsToVerify = ["modules", "entrypoints", "sources_of_truth", "links", "internals"];
|
|
14022
14409
|
for (const section of sectionsToVerify) {
|
|
14023
14410
|
for (const item of getSectionEntries(map[section])) {
|
|
14024
14411
|
if (item?.path) {
|
|
14025
|
-
indexedPaths.add(
|
|
14412
|
+
indexedPaths.add(path19.normalize(item.path));
|
|
14026
14413
|
if (section === "links" && (item.path.startsWith("http://") || item.path.startsWith("https://"))) {
|
|
14027
14414
|
continue;
|
|
14028
14415
|
}
|
|
14029
|
-
const absPath =
|
|
14030
|
-
if (!
|
|
14416
|
+
const absPath = path19.isAbsolute(item.path) ? item.path : path19.join(pathBase, item.path);
|
|
14417
|
+
if (!fs18.existsSync(absPath)) {
|
|
14031
14418
|
errors.push(`${filePath}: ${section.slice(0, -1)} path does not exist: ${item.path}`);
|
|
14032
14419
|
}
|
|
14033
14420
|
}
|
|
14034
14421
|
}
|
|
14035
14422
|
}
|
|
14036
|
-
const relDir =
|
|
14423
|
+
const relDir = path19.relative(worktree, dir);
|
|
14037
14424
|
const isOperational = isOperationalRelPath2(relDir);
|
|
14038
14425
|
if (map.scope === "module" && !isOperational) {
|
|
14039
|
-
const items =
|
|
14426
|
+
const items = fs18.readdirSync(dir, { withFileTypes: true });
|
|
14040
14427
|
for (const item of items) {
|
|
14041
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
14428
|
+
if (item.isFile() && sourceExtensions.includes(path19.extname(item.name))) {
|
|
14042
14429
|
if (item.name === "codemap.yml") continue;
|
|
14043
|
-
if (!indexedPaths.has(
|
|
14430
|
+
if (!indexedPaths.has(path19.normalize(item.name))) {
|
|
14044
14431
|
errors.push(`${filePath}: Unindexed source file found: '${item.name}'. Every source file must be categorized in a section (e.g., 'internals').`);
|
|
14045
14432
|
}
|
|
14046
14433
|
}
|
|
@@ -14050,7 +14437,7 @@ async function optima_validate_logic(worktree) {
|
|
|
14050
14437
|
for (const key of pathKeys) {
|
|
14051
14438
|
for (const entry of getSectionEntries(map[key])) {
|
|
14052
14439
|
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://")) continue;
|
|
14053
|
-
if (
|
|
14440
|
+
if (path19.isAbsolute(entry.path)) continue;
|
|
14054
14441
|
const normalizedPath = normalizeRelativePath(entry.path);
|
|
14055
14442
|
if (!normalizedPath || normalizedPath === ".") continue;
|
|
14056
14443
|
const parts = normalizedPath.split("/").filter((p) => p && p !== ".");
|
|
@@ -14062,18 +14449,18 @@ async function optima_validate_logic(worktree) {
|
|
|
14062
14449
|
}
|
|
14063
14450
|
}
|
|
14064
14451
|
const walk = (dir) => {
|
|
14065
|
-
const relDir =
|
|
14452
|
+
const relDir = path19.relative(worktree, dir);
|
|
14066
14453
|
if (relDir && ig.ignores(relDir)) return;
|
|
14067
14454
|
if (isOperationalRelPath2(relDir)) return;
|
|
14068
14455
|
if (isHiddenTree2(relDir)) return;
|
|
14069
|
-
const hasCodemap =
|
|
14070
|
-
const items =
|
|
14071
|
-
if (!relDir.startsWith(
|
|
14456
|
+
const hasCodemap = fs18.existsSync(path19.join(dir, "codemap.yml"));
|
|
14457
|
+
const items = fs18.readdirSync(dir, { withFileTypes: true });
|
|
14458
|
+
if (!relDir.startsWith(path19.join(".optima", "tasks", "done"))) {
|
|
14072
14459
|
for (const item of items) {
|
|
14073
14460
|
if (item.isFile() && item.name.endsWith(".md")) {
|
|
14074
|
-
const content =
|
|
14461
|
+
const content = fs18.readFileSync(path19.join(dir, item.name), "utf8");
|
|
14075
14462
|
if (content.includes("[To be defined]") || content.includes("[Insert ")) {
|
|
14076
|
-
const relFilePath =
|
|
14463
|
+
const relFilePath = path19.join(relDir, item.name);
|
|
14077
14464
|
errors.push(`Documentation Placeholder found: '${relFilePath}' still contains [To be defined] or [Insert ...] placeholders.`);
|
|
14078
14465
|
}
|
|
14079
14466
|
}
|
|
@@ -14083,9 +14470,9 @@ async function optima_validate_logic(worktree) {
|
|
|
14083
14470
|
if (relDir !== "" && !hasCodemap && isSourceDir(dir) && !isOperational) {
|
|
14084
14471
|
errors.push(`Missing CodeMap: Directory '${relDir}' contains source but has no codemap.yml.`);
|
|
14085
14472
|
}
|
|
14086
|
-
if (hasCodemap) validateMap(
|
|
14473
|
+
if (hasCodemap) validateMap(path19.join(dir, "codemap.yml"));
|
|
14087
14474
|
for (const item of items) {
|
|
14088
|
-
if (item.isDirectory()) walk(
|
|
14475
|
+
if (item.isDirectory()) walk(path19.join(dir, item.name));
|
|
14089
14476
|
}
|
|
14090
14477
|
};
|
|
14091
14478
|
validateMap(rootCodemapPath);
|
|
@@ -14099,12 +14486,13 @@ async function optima_validate_logic(worktree) {
|
|
|
14099
14486
|
|
|
14100
14487
|
// src/plugin.js
|
|
14101
14488
|
var activeWorkflows = /* @__PURE__ */ new Map();
|
|
14489
|
+
var activeQaQueueSchedulers = /* @__PURE__ */ new Map();
|
|
14102
14490
|
function normalizeWorkflowTaskPath(taskPath) {
|
|
14103
14491
|
if (typeof taskPath !== "string") return { ok: false, message: "Error: task_path is required." };
|
|
14104
14492
|
const trimmed = taskPath.trim();
|
|
14105
14493
|
if (!trimmed) return { ok: false, message: "Error: task_path is required." };
|
|
14106
14494
|
const normalized = trimmed.replace(/\\/g, "/");
|
|
14107
|
-
if (
|
|
14495
|
+
if (path20.isAbsolute(trimmed)) return { ok: true, taskPath: trimmed };
|
|
14108
14496
|
if (normalized === "tasks" || normalized.startsWith("tasks/")) {
|
|
14109
14497
|
return {
|
|
14110
14498
|
ok: false,
|
|
@@ -14121,10 +14509,10 @@ function normalizeWorkflowTaskPath(taskPath) {
|
|
|
14121
14509
|
}
|
|
14122
14510
|
function readTaskMetadata(taskPath, worktree) {
|
|
14123
14511
|
if (!taskPath) return {};
|
|
14124
|
-
const absoluteTaskPath =
|
|
14125
|
-
if (!
|
|
14512
|
+
const absoluteTaskPath = path20.isAbsolute(taskPath) ? taskPath : path20.join(worktree, taskPath);
|
|
14513
|
+
if (!fs19.existsSync(absoluteTaskPath)) return {};
|
|
14126
14514
|
try {
|
|
14127
|
-
const raw =
|
|
14515
|
+
const raw = fs19.readFileSync(absoluteTaskPath, "utf8");
|
|
14128
14516
|
const { data } = parseFrontmatter(raw);
|
|
14129
14517
|
return {
|
|
14130
14518
|
complexity: typeof data.complexity === "string" ? data.complexity.trim().toLowerCase() : void 0,
|
|
@@ -14147,15 +14535,15 @@ function hasActiveImplementationWorkflow() {
|
|
|
14147
14535
|
function resolveSessionToolDirectory({ requestedDirectory, context, pluginWorktree, clickUpWebhookValidation } = {}) {
|
|
14148
14536
|
const safe = safeWorktreeOrFailure(context, pluginWorktree);
|
|
14149
14537
|
if (!safe.ok) return { ok: false, error: safe.message };
|
|
14150
|
-
const requested = String(requestedDirectory || "").trim() ?
|
|
14538
|
+
const requested = String(requestedDirectory || "").trim() ? path20.resolve(safe.worktree, String(requestedDirectory).trim()) : safe.worktree;
|
|
14151
14539
|
if (!isSafeWritableDirectory(requested)) return { ok: false, error: `Directory is not a safe writable directory: ${requested}` };
|
|
14152
14540
|
if (isSameOrNestedPath(requested, safe.worktree)) return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "context_worktree" };
|
|
14153
14541
|
const clickUpBasePath = clickUpWebhookValidation?.complete === true && clickUpWebhookValidation?.ok !== false ? clickUpWebhookValidation.config?.basePath : "";
|
|
14154
14542
|
if (clickUpBasePath && isSameOrNestedPath(requested, clickUpBasePath)) {
|
|
14155
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath:
|
|
14543
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath: path20.resolve(clickUpBasePath) };
|
|
14156
14544
|
}
|
|
14157
14545
|
if (clickUpBasePath && isClickUpDerivedWorktreeSibling(requested, clickUpBasePath)) {
|
|
14158
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath:
|
|
14546
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath: path20.resolve(clickUpBasePath) };
|
|
14159
14547
|
}
|
|
14160
14548
|
return {
|
|
14161
14549
|
ok: false,
|
|
@@ -14170,9 +14558,9 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
14170
14558
|
const configPath = resolveConfigPath(worktree);
|
|
14171
14559
|
const discussionRegistry = loadDiscussionRegistry(worktree);
|
|
14172
14560
|
let repoCfg = { agents: {}, defaults: {}, features: {} };
|
|
14173
|
-
if (
|
|
14561
|
+
if (fs19.existsSync(configPath)) {
|
|
14174
14562
|
try {
|
|
14175
|
-
repoCfg = import_yaml6.default.parse(
|
|
14563
|
+
repoCfg = import_yaml6.default.parse(fs19.readFileSync(configPath, "utf8")) || repoCfg;
|
|
14176
14564
|
} catch (e) {
|
|
14177
14565
|
console.error(`[Optima] Failed to parse config at ${configPath}:`, e);
|
|
14178
14566
|
}
|
|
@@ -14333,6 +14721,134 @@ Evidencia: ${evidencePath}` : ""
|
|
|
14333
14721
|
applied
|
|
14334
14722
|
};
|
|
14335
14723
|
};
|
|
14724
|
+
const qaLeaseMs = Number(repoCfg.qa?.lease_ms || repoCfg.qa?.leaseMs || DEFAULT_QA_LEASE_MS) || DEFAULT_QA_LEASE_MS;
|
|
14725
|
+
const qaProvider = String(repoCfg.qa?.provider || DEFAULT_QA_PROVIDER);
|
|
14726
|
+
const qaCdpUrl = String(repoCfg.qa?.cdp_url || repoCfg.qa?.cdpUrl || process.env.OPTIMA_QA_CHROME_CDP_URL || "").trim();
|
|
14727
|
+
const qaTaskMetadata = async (taskId) => {
|
|
14728
|
+
if (!taskId || typeof runtimeClickUpClient?.getTask !== "function") return { task: null, metadata: {} };
|
|
14729
|
+
const task = await runtimeClickUpClient.getTask(taskId);
|
|
14730
|
+
const metadata = normalizeAgentMetadataJson(clickUpTaskAgentMetadata(task, clickUpWebhookValidation.config?.routing?.metadataFieldId));
|
|
14731
|
+
return { task, metadata };
|
|
14732
|
+
};
|
|
14733
|
+
const qaSessionFromMetadata = (metadata = {}) => {
|
|
14734
|
+
return metadata.optima?.sessions?.product_manager || metadata.task?.product_manager || metadata.sessions?.product_manager || "";
|
|
14735
|
+
};
|
|
14736
|
+
const qaWorktreeFromMetadata = (metadata = {}) => {
|
|
14737
|
+
return metadata.task?.worktree || metadata.worktree || "";
|
|
14738
|
+
};
|
|
14739
|
+
const applyQaQueuedClickUpState = async (taskId) => {
|
|
14740
|
+
const applied = [];
|
|
14741
|
+
const errors = [];
|
|
14742
|
+
const pmId = String(clickUpWebhookValidation.config?.routing?.productManagerAssigneeId || "").trim();
|
|
14743
|
+
if (!taskId || !clickUpWebhookValidation.complete || !runtimeClickUpClient) return { applied, errors };
|
|
14744
|
+
if (runtimeClickUpClient.updateTaskStatus) {
|
|
14745
|
+
try {
|
|
14746
|
+
await runtimeClickUpClient.updateTaskStatus({ taskId, status: "waiting QA slot" });
|
|
14747
|
+
applied.push("status:waiting QA slot");
|
|
14748
|
+
} catch (error) {
|
|
14749
|
+
errors.push(`status:${error.message}`);
|
|
14750
|
+
}
|
|
14751
|
+
}
|
|
14752
|
+
if (pmId && runtimeClickUpClient.updateTaskAssignees) {
|
|
14753
|
+
try {
|
|
14754
|
+
await runtimeClickUpClient.updateTaskAssignees({ taskId, remove: [pmId] });
|
|
14755
|
+
applied.push("remove_product_manager");
|
|
14756
|
+
} catch (error) {
|
|
14757
|
+
errors.push(`assignees:${error.message}`);
|
|
14758
|
+
}
|
|
14759
|
+
}
|
|
14760
|
+
return { applied, errors };
|
|
14761
|
+
};
|
|
14762
|
+
const wakePromotedQaTask = async (entry, { provider = qaProvider, reason = "qa_slot_available" } = {}) => {
|
|
14763
|
+
const taskId = String(entry?.clickupTaskId || "").trim();
|
|
14764
|
+
const applied = [];
|
|
14765
|
+
const errors = [];
|
|
14766
|
+
if (!taskId || !clickUpWebhookValidation.complete) return { ok: false, reason: "missing_task_or_clickup_config" };
|
|
14767
|
+
const pmId = String(clickUpWebhookValidation.config?.routing?.productManagerAssigneeId || "").trim();
|
|
14768
|
+
const humanIds = Object.values(clickUpWebhookValidation.config?.routing?.humanRoleClickUpIds || {}).map((id) => String(id || "").trim()).filter(Boolean);
|
|
14769
|
+
try {
|
|
14770
|
+
if (runtimeClickUpClient.updateTaskStatus) {
|
|
14771
|
+
await runtimeClickUpClient.updateTaskStatus({ taskId, status: "in progress" });
|
|
14772
|
+
applied.push("status:in progress");
|
|
14773
|
+
}
|
|
14774
|
+
} catch (error) {
|
|
14775
|
+
errors.push(`status:${error.message}`);
|
|
14776
|
+
}
|
|
14777
|
+
try {
|
|
14778
|
+
if (pmId && runtimeClickUpClient.updateTaskAssignees) {
|
|
14779
|
+
await runtimeClickUpClient.updateTaskAssignees({ taskId, add: [pmId], remove: humanIds });
|
|
14780
|
+
applied.push("assign_product_manager");
|
|
14781
|
+
}
|
|
14782
|
+
} catch (error) {
|
|
14783
|
+
errors.push(`assignees:${error.message}`);
|
|
14784
|
+
}
|
|
14785
|
+
let metadata = {};
|
|
14786
|
+
try {
|
|
14787
|
+
metadata = (await qaTaskMetadata(taskId)).metadata;
|
|
14788
|
+
} catch (error) {
|
|
14789
|
+
errors.push(`metadata:${error.message}`);
|
|
14790
|
+
}
|
|
14791
|
+
const sessionId = entry.sessionId || qaSessionFromMetadata(metadata);
|
|
14792
|
+
const directory = entry.worktree || qaWorktreeFromMetadata(metadata);
|
|
14793
|
+
if (sessionId && input.client && typeof sendOpenCodeSessionEvent === "function") {
|
|
14794
|
+
try {
|
|
14795
|
+
const prompt = [
|
|
14796
|
+
`[Optima QA Slot Granted]`,
|
|
14797
|
+
`Provider: ${provider}`,
|
|
14798
|
+
`ClickUp task: ${taskId}`,
|
|
14799
|
+
`Reason: ${reason}`,
|
|
14800
|
+
"",
|
|
14801
|
+
"Ya tienes plaza exclusiva para usar el Chrome QA. Debes llamar a `optima_qa_chrome_command` en menos de 5 minutos para conservar la plaza.",
|
|
14802
|
+
"Puedes conservarla mientras hagas al menos una llamada QA cada 5 minutos.",
|
|
14803
|
+
"Cuando termines, llama obligatoriamente a `optima_qa_finish` para resetear la extensi\xF3n y liberar la cola.",
|
|
14804
|
+
"La pesta\xF1a 0 es intocable: usa \xFAnicamente la pesta\xF1a QA que Optima crea para esta tarea."
|
|
14805
|
+
].join("\n");
|
|
14806
|
+
await sendOpenCodeSessionEvent(input.client, {
|
|
14807
|
+
sessionId,
|
|
14808
|
+
agent: clickUpWebhookValidation.config.routing?.targetAgent || "workflow_product_manager",
|
|
14809
|
+
text: prompt,
|
|
14810
|
+
directory,
|
|
14811
|
+
opencodeBaseUrl: clickUpWebhookValidation.config.opencode?.baseUrl,
|
|
14812
|
+
fetchImpl: input.fetch,
|
|
14813
|
+
directDelivery: "steer"
|
|
14814
|
+
});
|
|
14815
|
+
applied.push("session_prompt");
|
|
14816
|
+
} catch (error) {
|
|
14817
|
+
errors.push(`session_prompt:${error.message}`);
|
|
14818
|
+
}
|
|
14819
|
+
} else {
|
|
14820
|
+
errors.push("session_prompt:missing_session_or_client");
|
|
14821
|
+
}
|
|
14822
|
+
return { ok: errors.length === 0, taskId, sessionId, directory, applied, errors };
|
|
14823
|
+
};
|
|
14824
|
+
const persistQaState = async (provider, state, transitions = {}) => {
|
|
14825
|
+
const written = writeQaQueueState(state, qaQueueStatePath(provider));
|
|
14826
|
+
const notifications = [];
|
|
14827
|
+
const promoted = transitions.promoted || null;
|
|
14828
|
+
if (promoted?.clickupTaskId) notifications.push(await wakePromotedQaTask(promoted, { provider, reason: transitions.reason || "qa_slot_available" }));
|
|
14829
|
+
return { state: written, notifications };
|
|
14830
|
+
};
|
|
14831
|
+
const scheduleQaQueueReaper = () => {
|
|
14832
|
+
if (input.startQaQueueScheduler === false) return;
|
|
14833
|
+
const provider = qaProvider || DEFAULT_QA_PROVIDER;
|
|
14834
|
+
const schedulerKey = `${provider}:${clickUpWebhookValidation.config?.basePath || worktree}`;
|
|
14835
|
+
if (activeQaQueueSchedulers.has(schedulerKey)) return;
|
|
14836
|
+
const interval = setInterval(async () => {
|
|
14837
|
+
try {
|
|
14838
|
+
const statePath = qaQueueStatePath(provider);
|
|
14839
|
+
const current = readQaQueueState(provider, statePath);
|
|
14840
|
+
const result = releaseExpiredQaSlot(current, { leaseMs: qaLeaseMs });
|
|
14841
|
+
if (result.expired || result.promoted) {
|
|
14842
|
+
await persistQaState(provider, result.state, { promoted: result.promoted, reason: result.expired ? "previous_slot_expired" : "qa_slot_available" });
|
|
14843
|
+
}
|
|
14844
|
+
} catch (error) {
|
|
14845
|
+
appendClickUpWebhookLocalLog(worktree, { type: "qa_queue_reaper_failed", provider, message: error.message });
|
|
14846
|
+
}
|
|
14847
|
+
}, Math.min(6e4, Math.max(1e4, Math.floor(qaLeaseMs / 5))));
|
|
14848
|
+
interval.unref?.();
|
|
14849
|
+
activeQaQueueSchedulers.set(schedulerKey, interval);
|
|
14850
|
+
};
|
|
14851
|
+
scheduleQaQueueReaper();
|
|
14336
14852
|
const tools = {
|
|
14337
14853
|
optima_init: tool({
|
|
14338
14854
|
description: "Initialize the Optima workflow and CodeMap in the current repository",
|
|
@@ -14349,10 +14865,10 @@ Evidencia: ${evidencePath}` : ""
|
|
|
14349
14865
|
}
|
|
14350
14866
|
migrateLegacyOptimaLayout(toolWorktree);
|
|
14351
14867
|
const cfgDir = optimaConfigDir(toolWorktree);
|
|
14352
|
-
if (!
|
|
14353
|
-
const optimaTmplPath =
|
|
14354
|
-
const codemapTmplPath =
|
|
14355
|
-
if (!
|
|
14868
|
+
if (!fs19.existsSync(cfgDir)) fs19.mkdirSync(cfgDir, { recursive: true });
|
|
14869
|
+
const optimaTmplPath = path20.join(TEMPLATES_DIR, "optima.yaml.template");
|
|
14870
|
+
const codemapTmplPath = path20.join(TEMPLATES_DIR, "codemap.yml.template");
|
|
14871
|
+
if (!fs19.existsSync(optimaTmplPath) || !fs19.existsSync(codemapTmplPath)) {
|
|
14356
14872
|
return "Error: Initialization templates not found in plugin.";
|
|
14357
14873
|
}
|
|
14358
14874
|
scaffoldOptimaConfig(toolWorktree, requestedTeamMode);
|
|
@@ -14473,14 +14989,14 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14473
14989
|
async execute(args, context) {
|
|
14474
14990
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14475
14991
|
if (!safe.ok) return safe.message;
|
|
14476
|
-
const summaryPath =
|
|
14477
|
-
if (!
|
|
14478
|
-
const taskPath = args.task_path ?
|
|
14992
|
+
const summaryPath = path20.resolve(safe.worktree, args.summary_path || "");
|
|
14993
|
+
if (!fs19.existsSync(summaryPath)) return `FAIL: summary_path not found: ${summaryPath}`;
|
|
14994
|
+
const taskPath = args.task_path ? path20.resolve(safe.worktree, args.task_path) : "";
|
|
14479
14995
|
const payload = buildClickUpSummaryPayload({
|
|
14480
|
-
summaryMarkdown:
|
|
14481
|
-
summaryPath:
|
|
14482
|
-
taskMarkdown: taskPath &&
|
|
14483
|
-
taskPath: taskPath ?
|
|
14996
|
+
summaryMarkdown: fs19.readFileSync(summaryPath, "utf8"),
|
|
14997
|
+
summaryPath: path20.relative(safe.worktree, summaryPath),
|
|
14998
|
+
taskMarkdown: taskPath && fs19.existsSync(taskPath) ? fs19.readFileSync(taskPath, "utf8") : "",
|
|
14999
|
+
taskPath: taskPath ? path20.relative(safe.worktree, taskPath) : "",
|
|
14484
15000
|
branch: args.branch,
|
|
14485
15001
|
worktree: args.worktree,
|
|
14486
15002
|
pr: args.pr
|
|
@@ -14573,6 +15089,116 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14573
15089
|
}
|
|
14574
15090
|
}
|
|
14575
15091
|
}),
|
|
15092
|
+
optima_qa_browser_status: tool({
|
|
15093
|
+
description: "Inspect the shared Optima QA browser and queue state for a provider such as chatgpt.",
|
|
15094
|
+
args: {
|
|
15095
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15096
|
+
clickup_task_id: tool.schema.string().optional().describe("Optional ClickUp task id to identify its QA tab."),
|
|
15097
|
+
cdp_url: tool.schema.string().optional().describe("Optional Chrome DevTools URL. Defaults to configured QA URL or http://127.0.0.1:9222.")
|
|
15098
|
+
},
|
|
15099
|
+
async execute(args = {}) {
|
|
15100
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15101
|
+
const state = readQaQueueState(provider);
|
|
15102
|
+
const browser = await qaBrowserStatus({ provider, cdpUrl: args.cdp_url || qaCdpUrl, clickupTaskId: args.clickup_task_id || "" });
|
|
15103
|
+
return JSON.stringify({ ok: browser.ok !== false, provider, leaseMs: qaLeaseMs, queue: state, browser }, null, 2);
|
|
15104
|
+
}
|
|
15105
|
+
}),
|
|
15106
|
+
optima_qa_request_slot: tool({
|
|
15107
|
+
description: "Request the exclusive QA browser slot for a ClickUp task. If busy, Optima queues the task, marks it waiting QA slot, and removes Product Manager assignment.",
|
|
15108
|
+
args: {
|
|
15109
|
+
clickup_task_id: tool.schema.string().describe("ClickUp task id requesting the QA slot"),
|
|
15110
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15111
|
+
session_id: tool.schema.string().optional().describe("Optional OpenCode session id to wake when the slot is granted later"),
|
|
15112
|
+
worktree: tool.schema.string().optional().describe("Optional task worktree/directory for session wakeups"),
|
|
15113
|
+
reason: tool.schema.string().optional().describe("Short reason/scenario for requesting the QA slot")
|
|
15114
|
+
},
|
|
15115
|
+
async execute(args = {}) {
|
|
15116
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15117
|
+
const pathToState = qaQueueStatePath(provider);
|
|
15118
|
+
const current = readQaQueueState(provider, pathToState);
|
|
15119
|
+
const result = requestQaSlot(current, {
|
|
15120
|
+
clickupTaskId: args.clickup_task_id,
|
|
15121
|
+
sessionId: args.session_id,
|
|
15122
|
+
worktree: args.worktree,
|
|
15123
|
+
reason: args.reason,
|
|
15124
|
+
leaseMs: qaLeaseMs
|
|
15125
|
+
});
|
|
15126
|
+
let queuedClickUp = null;
|
|
15127
|
+
if (result.action === "queued") queuedClickUp = await applyQaQueuedClickUpState(args.clickup_task_id);
|
|
15128
|
+
const persisted = await persistQaState(provider, result.state, { promoted: result.promoted, reason: result.expired ? "previous_slot_expired" : "qa_slot_available" });
|
|
15129
|
+
const guidance = result.ok ? "QA slot granted. Use optima_qa_chrome_command within 5 minutes and then at least once every 5 minutes. Use optima_qa_finish when done." : "QA slot busy. Task has been queued; stop work on QA, wait for Optima to reassign/wake the session when your slot is granted.";
|
|
15130
|
+
return JSON.stringify({ ...result, state: persisted.state, notifications: persisted.notifications, queuedClickUp, guidance }, null, 2);
|
|
15131
|
+
}
|
|
15132
|
+
}),
|
|
15133
|
+
optima_qa_chrome_command: tool({
|
|
15134
|
+
description: "Run one flexible Playwright/CDP command against the shared QA Chrome for the active ClickUp task. The persistent tab 0 is never used; Optima creates/reuses a task QA tab.",
|
|
15135
|
+
args: {
|
|
15136
|
+
clickup_task_id: tool.schema.string().describe("ClickUp task id that currently owns the QA slot"),
|
|
15137
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15138
|
+
command_json: tool.schema.string().describe("JSON command object, or raw async JS script. Actions: status, goto, evaluate, screenshot, set_input_files, script."),
|
|
15139
|
+
cdp_url: tool.schema.string().optional().describe("Optional Chrome DevTools URL"),
|
|
15140
|
+
start_url: tool.schema.string().optional().describe("Initial URL for a new QA tab. Defaults to https://chatgpt.com/")
|
|
15141
|
+
},
|
|
15142
|
+
async execute(args = {}) {
|
|
15143
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15144
|
+
const pathToState = qaQueueStatePath(provider);
|
|
15145
|
+
const current = readQaQueueState(provider, pathToState);
|
|
15146
|
+
const touched = touchQaSlot(current, { clickupTaskId: args.clickup_task_id, leaseMs: qaLeaseMs });
|
|
15147
|
+
const persisted = await persistQaState(provider, touched.state, { promoted: touched.promoted, reason: touched.expired ? "previous_slot_expired" : "qa_slot_available" });
|
|
15148
|
+
if (!touched.ok) {
|
|
15149
|
+
return JSON.stringify({
|
|
15150
|
+
ok: false,
|
|
15151
|
+
action: touched.action,
|
|
15152
|
+
active: persisted.state.active,
|
|
15153
|
+
queue: persisted.state.queue,
|
|
15154
|
+
notifications: persisted.notifications,
|
|
15155
|
+
guidance: "You do not own the QA slot. Call optima_qa_request_slot and stop if queued."
|
|
15156
|
+
}, null, 2);
|
|
15157
|
+
}
|
|
15158
|
+
try {
|
|
15159
|
+
const result = await qaRunChromeCommand({
|
|
15160
|
+
provider,
|
|
15161
|
+
cdpUrl: args.cdp_url || qaCdpUrl,
|
|
15162
|
+
clickupTaskId: args.clickup_task_id,
|
|
15163
|
+
commandJson: args.command_json,
|
|
15164
|
+
startUrl: args.start_url || "https://chatgpt.com/"
|
|
15165
|
+
});
|
|
15166
|
+
return JSON.stringify({ ok: true, lease: persisted.state.active, notifications: persisted.notifications, chrome: result }, null, 2);
|
|
15167
|
+
} catch (error) {
|
|
15168
|
+
return JSON.stringify({ ok: false, error: error.message, lease: persisted.state.active, notifications: persisted.notifications }, null, 2);
|
|
15169
|
+
}
|
|
15170
|
+
}
|
|
15171
|
+
}),
|
|
15172
|
+
optima_qa_finish: tool({
|
|
15173
|
+
description: "Finish the active QA browser slot, reset/reload the extension, release the slot, and wake the next queued ClickUp task if any.",
|
|
15174
|
+
args: {
|
|
15175
|
+
clickup_task_id: tool.schema.string().describe("ClickUp task id finishing QA"),
|
|
15176
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15177
|
+
cdp_url: tool.schema.string().optional().describe("Optional Chrome DevTools URL"),
|
|
15178
|
+
extension_path: tool.schema.string().optional().describe("Optional extension path to sync into the stable QA extension-under-test directory before reset/reload"),
|
|
15179
|
+
extension_id: tool.schema.string().optional().describe("Optional Chrome extension id when multiple extension service workers exist"),
|
|
15180
|
+
reset_extension: tool.schema.boolean().optional().describe("Defaults true. Clear extension storage/caches/IndexedDB."),
|
|
15181
|
+
reload_extension: tool.schema.boolean().optional().describe("Defaults true. Reload the extension service worker after reset.")
|
|
15182
|
+
},
|
|
15183
|
+
async execute(args = {}) {
|
|
15184
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15185
|
+
const reset = args.reset_extension !== false;
|
|
15186
|
+
const reload = args.reload_extension !== false;
|
|
15187
|
+
const extension = await qaPrepareExtension({
|
|
15188
|
+
provider,
|
|
15189
|
+
cdpUrl: args.cdp_url || qaCdpUrl,
|
|
15190
|
+
extensionPath: args.extension_path || "",
|
|
15191
|
+
extensionId: args.extension_id || "",
|
|
15192
|
+
reset,
|
|
15193
|
+
reload
|
|
15194
|
+
}).catch((error) => ({ ok: false, error: error.message }));
|
|
15195
|
+
const pathToState = qaQueueStatePath(provider);
|
|
15196
|
+
const current = readQaQueueState(provider, pathToState);
|
|
15197
|
+
const result = finishQaSlot(current, { clickupTaskId: args.clickup_task_id, leaseMs: qaLeaseMs });
|
|
15198
|
+
const persisted = await persistQaState(provider, result.state, { promoted: result.promoted, reason: "previous_finished" });
|
|
15199
|
+
return JSON.stringify({ ...result, state: persisted.state, notifications: persisted.notifications, extension }, null, 2);
|
|
15200
|
+
}
|
|
15201
|
+
}),
|
|
14576
15202
|
optima_clickup_create_subtasks: tool({
|
|
14577
15203
|
description: "Generate dry-run ClickUp subtask creation payloads from a strict Markdown ## Subtasks section",
|
|
14578
15204
|
args: {
|
|
@@ -14585,12 +15211,12 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14585
15211
|
async execute(args, context) {
|
|
14586
15212
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14587
15213
|
if (!safe.ok) return safe.message;
|
|
14588
|
-
const markdownPath =
|
|
14589
|
-
if (!
|
|
15214
|
+
const markdownPath = path20.resolve(safe.worktree, args.markdown_path || "");
|
|
15215
|
+
if (!fs19.existsSync(markdownPath)) return `FAIL: markdown_path not found: ${markdownPath}`;
|
|
14590
15216
|
const payload = buildClickUpCreateSubtasksPayload({
|
|
14591
15217
|
parentTaskId: args.parent_task_id,
|
|
14592
|
-
markdown:
|
|
14593
|
-
sourcePath:
|
|
15218
|
+
markdown: fs19.readFileSync(markdownPath, "utf8"),
|
|
15219
|
+
sourcePath: path20.relative(safe.worktree, markdownPath),
|
|
14594
15220
|
parentBranch: args.parent_branch,
|
|
14595
15221
|
parentTaskType: args.parent_task_type || "Tarea",
|
|
14596
15222
|
apply: String(args.apply || "").toLowerCase() === "true"
|
|
@@ -14960,7 +15586,7 @@ Backfilled messages: ${backfilled}`;
|
|
|
14960
15586
|
if (!existing) {
|
|
14961
15587
|
return "FAIL: No active discussion exists for this session.";
|
|
14962
15588
|
}
|
|
14963
|
-
const discussionPath =
|
|
15589
|
+
const discussionPath = path20.join(toolWorktree, existing.transcriptPath);
|
|
14964
15590
|
setDiscussionStatus(discussionPath, "summarizing");
|
|
14965
15591
|
existing.status = "summarizing";
|
|
14966
15592
|
saveDiscussionRegistry(toolWorktree, toolRegistry);
|
|
@@ -15012,7 +15638,7 @@ Reason: ${err.message}`;
|
|
|
15012
15638
|
try {
|
|
15013
15639
|
const sessionResult = await client.session.create({
|
|
15014
15640
|
query: { directory: workflowDirectory },
|
|
15015
|
-
body: { title: `Workflow Run: ${
|
|
15641
|
+
body: { title: `Workflow Run: ${path20.basename(workflowTaskPath)}` }
|
|
15016
15642
|
});
|
|
15017
15643
|
const sessionId = sessionResult.data.id;
|
|
15018
15644
|
activeWorkflows.set(sessionId, { pmaSessionId, taskPath: workflowTaskPath, track: workflowTrack, directory: workflowDirectory });
|