@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/sanitize_cli.js
CHANGED
|
@@ -109,17 +109,17 @@ var require_visit = __commonJS({
|
|
|
109
109
|
visit.BREAK = BREAK;
|
|
110
110
|
visit.SKIP = SKIP;
|
|
111
111
|
visit.REMOVE = REMOVE;
|
|
112
|
-
function visit_(key, node, visitor,
|
|
113
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
112
|
+
function visit_(key, node, visitor, path22) {
|
|
113
|
+
const ctrl = callVisitor(key, node, visitor, path22);
|
|
114
114
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
115
|
-
replaceNode(key,
|
|
116
|
-
return visit_(key, ctrl, visitor,
|
|
115
|
+
replaceNode(key, path22, ctrl);
|
|
116
|
+
return visit_(key, ctrl, visitor, path22);
|
|
117
117
|
}
|
|
118
118
|
if (typeof ctrl !== "symbol") {
|
|
119
119
|
if (identity.isCollection(node)) {
|
|
120
|
-
|
|
120
|
+
path22 = Object.freeze(path22.concat(node));
|
|
121
121
|
for (let i = 0; i < node.items.length; ++i) {
|
|
122
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
122
|
+
const ci = visit_(i, node.items[i], visitor, path22);
|
|
123
123
|
if (typeof ci === "number")
|
|
124
124
|
i = ci - 1;
|
|
125
125
|
else if (ci === BREAK)
|
|
@@ -130,13 +130,13 @@ var require_visit = __commonJS({
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
} else if (identity.isPair(node)) {
|
|
133
|
-
|
|
134
|
-
const ck = visit_("key", node.key, visitor,
|
|
133
|
+
path22 = Object.freeze(path22.concat(node));
|
|
134
|
+
const ck = visit_("key", node.key, visitor, path22);
|
|
135
135
|
if (ck === BREAK)
|
|
136
136
|
return BREAK;
|
|
137
137
|
else if (ck === REMOVE)
|
|
138
138
|
node.key = null;
|
|
139
|
-
const cv = visit_("value", node.value, visitor,
|
|
139
|
+
const cv = visit_("value", node.value, visitor, path22);
|
|
140
140
|
if (cv === BREAK)
|
|
141
141
|
return BREAK;
|
|
142
142
|
else if (cv === REMOVE)
|
|
@@ -157,17 +157,17 @@ var require_visit = __commonJS({
|
|
|
157
157
|
visitAsync.BREAK = BREAK;
|
|
158
158
|
visitAsync.SKIP = SKIP;
|
|
159
159
|
visitAsync.REMOVE = REMOVE;
|
|
160
|
-
async function visitAsync_(key, node, visitor,
|
|
161
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
160
|
+
async function visitAsync_(key, node, visitor, path22) {
|
|
161
|
+
const ctrl = await callVisitor(key, node, visitor, path22);
|
|
162
162
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
163
|
-
replaceNode(key,
|
|
164
|
-
return visitAsync_(key, ctrl, visitor,
|
|
163
|
+
replaceNode(key, path22, ctrl);
|
|
164
|
+
return visitAsync_(key, ctrl, visitor, path22);
|
|
165
165
|
}
|
|
166
166
|
if (typeof ctrl !== "symbol") {
|
|
167
167
|
if (identity.isCollection(node)) {
|
|
168
|
-
|
|
168
|
+
path22 = Object.freeze(path22.concat(node));
|
|
169
169
|
for (let i = 0; i < node.items.length; ++i) {
|
|
170
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
170
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path22);
|
|
171
171
|
if (typeof ci === "number")
|
|
172
172
|
i = ci - 1;
|
|
173
173
|
else if (ci === BREAK)
|
|
@@ -178,13 +178,13 @@ var require_visit = __commonJS({
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
} else if (identity.isPair(node)) {
|
|
181
|
-
|
|
182
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
181
|
+
path22 = Object.freeze(path22.concat(node));
|
|
182
|
+
const ck = await visitAsync_("key", node.key, visitor, path22);
|
|
183
183
|
if (ck === BREAK)
|
|
184
184
|
return BREAK;
|
|
185
185
|
else if (ck === REMOVE)
|
|
186
186
|
node.key = null;
|
|
187
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
187
|
+
const cv = await visitAsync_("value", node.value, visitor, path22);
|
|
188
188
|
if (cv === BREAK)
|
|
189
189
|
return BREAK;
|
|
190
190
|
else if (cv === REMOVE)
|
|
@@ -211,23 +211,23 @@ var require_visit = __commonJS({
|
|
|
211
211
|
}
|
|
212
212
|
return visitor;
|
|
213
213
|
}
|
|
214
|
-
function callVisitor(key, node, visitor,
|
|
214
|
+
function callVisitor(key, node, visitor, path22) {
|
|
215
215
|
if (typeof visitor === "function")
|
|
216
|
-
return visitor(key, node,
|
|
216
|
+
return visitor(key, node, path22);
|
|
217
217
|
if (identity.isMap(node))
|
|
218
|
-
return visitor.Map?.(key, node,
|
|
218
|
+
return visitor.Map?.(key, node, path22);
|
|
219
219
|
if (identity.isSeq(node))
|
|
220
|
-
return visitor.Seq?.(key, node,
|
|
220
|
+
return visitor.Seq?.(key, node, path22);
|
|
221
221
|
if (identity.isPair(node))
|
|
222
|
-
return visitor.Pair?.(key, node,
|
|
222
|
+
return visitor.Pair?.(key, node, path22);
|
|
223
223
|
if (identity.isScalar(node))
|
|
224
|
-
return visitor.Scalar?.(key, node,
|
|
224
|
+
return visitor.Scalar?.(key, node, path22);
|
|
225
225
|
if (identity.isAlias(node))
|
|
226
|
-
return visitor.Alias?.(key, node,
|
|
226
|
+
return visitor.Alias?.(key, node, path22);
|
|
227
227
|
return void 0;
|
|
228
228
|
}
|
|
229
|
-
function replaceNode(key,
|
|
230
|
-
const parent =
|
|
229
|
+
function replaceNode(key, path22, node) {
|
|
230
|
+
const parent = path22[path22.length - 1];
|
|
231
231
|
if (identity.isCollection(parent)) {
|
|
232
232
|
parent.items[key] = node;
|
|
233
233
|
} else if (identity.isPair(parent)) {
|
|
@@ -835,10 +835,10 @@ var require_Collection = __commonJS({
|
|
|
835
835
|
var createNode = require_createNode();
|
|
836
836
|
var identity = require_identity();
|
|
837
837
|
var Node = require_Node();
|
|
838
|
-
function collectionFromPath(schema,
|
|
838
|
+
function collectionFromPath(schema, path22, value) {
|
|
839
839
|
let v = value;
|
|
840
|
-
for (let i =
|
|
841
|
-
const k =
|
|
840
|
+
for (let i = path22.length - 1; i >= 0; --i) {
|
|
841
|
+
const k = path22[i];
|
|
842
842
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
843
843
|
const a = [];
|
|
844
844
|
a[k] = v;
|
|
@@ -857,7 +857,7 @@ var require_Collection = __commonJS({
|
|
|
857
857
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
858
858
|
});
|
|
859
859
|
}
|
|
860
|
-
var isEmptyPath = (
|
|
860
|
+
var isEmptyPath = (path22) => path22 == null || typeof path22 === "object" && !!path22[Symbol.iterator]().next().done;
|
|
861
861
|
var Collection = class extends Node.NodeBase {
|
|
862
862
|
constructor(type, schema) {
|
|
863
863
|
super(type);
|
|
@@ -887,11 +887,11 @@ var require_Collection = __commonJS({
|
|
|
887
887
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
888
888
|
* that already exists in the map.
|
|
889
889
|
*/
|
|
890
|
-
addIn(
|
|
891
|
-
if (isEmptyPath(
|
|
890
|
+
addIn(path22, value) {
|
|
891
|
+
if (isEmptyPath(path22))
|
|
892
892
|
this.add(value);
|
|
893
893
|
else {
|
|
894
|
-
const [key, ...rest] =
|
|
894
|
+
const [key, ...rest] = path22;
|
|
895
895
|
const node = this.get(key, true);
|
|
896
896
|
if (identity.isCollection(node))
|
|
897
897
|
node.addIn(rest, value);
|
|
@@ -905,8 +905,8 @@ var require_Collection = __commonJS({
|
|
|
905
905
|
* Removes a value from the collection.
|
|
906
906
|
* @returns `true` if the item was found and removed.
|
|
907
907
|
*/
|
|
908
|
-
deleteIn(
|
|
909
|
-
const [key, ...rest] =
|
|
908
|
+
deleteIn(path22) {
|
|
909
|
+
const [key, ...rest] = path22;
|
|
910
910
|
if (rest.length === 0)
|
|
911
911
|
return this.delete(key);
|
|
912
912
|
const node = this.get(key, true);
|
|
@@ -920,8 +920,8 @@ var require_Collection = __commonJS({
|
|
|
920
920
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
921
921
|
* `true` (collections are always returned intact).
|
|
922
922
|
*/
|
|
923
|
-
getIn(
|
|
924
|
-
const [key, ...rest] =
|
|
923
|
+
getIn(path22, keepScalar) {
|
|
924
|
+
const [key, ...rest] = path22;
|
|
925
925
|
const node = this.get(key, true);
|
|
926
926
|
if (rest.length === 0)
|
|
927
927
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -939,8 +939,8 @@ var require_Collection = __commonJS({
|
|
|
939
939
|
/**
|
|
940
940
|
* Checks if the collection includes a value with the key `key`.
|
|
941
941
|
*/
|
|
942
|
-
hasIn(
|
|
943
|
-
const [key, ...rest] =
|
|
942
|
+
hasIn(path22) {
|
|
943
|
+
const [key, ...rest] = path22;
|
|
944
944
|
if (rest.length === 0)
|
|
945
945
|
return this.has(key);
|
|
946
946
|
const node = this.get(key, true);
|
|
@@ -950,8 +950,8 @@ var require_Collection = __commonJS({
|
|
|
950
950
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
951
951
|
* boolean to add/remove the item from the set.
|
|
952
952
|
*/
|
|
953
|
-
setIn(
|
|
954
|
-
const [key, ...rest] =
|
|
953
|
+
setIn(path22, value) {
|
|
954
|
+
const [key, ...rest] = path22;
|
|
955
955
|
if (rest.length === 0) {
|
|
956
956
|
this.set(key, value);
|
|
957
957
|
} else {
|
|
@@ -3455,9 +3455,9 @@ var require_Document = __commonJS({
|
|
|
3455
3455
|
this.contents.add(value);
|
|
3456
3456
|
}
|
|
3457
3457
|
/** Adds a value to the document. */
|
|
3458
|
-
addIn(
|
|
3458
|
+
addIn(path22, value) {
|
|
3459
3459
|
if (assertCollection(this.contents))
|
|
3460
|
-
this.contents.addIn(
|
|
3460
|
+
this.contents.addIn(path22, value);
|
|
3461
3461
|
}
|
|
3462
3462
|
/**
|
|
3463
3463
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -3532,14 +3532,14 @@ var require_Document = __commonJS({
|
|
|
3532
3532
|
* Removes a value from the document.
|
|
3533
3533
|
* @returns `true` if the item was found and removed.
|
|
3534
3534
|
*/
|
|
3535
|
-
deleteIn(
|
|
3536
|
-
if (Collection.isEmptyPath(
|
|
3535
|
+
deleteIn(path22) {
|
|
3536
|
+
if (Collection.isEmptyPath(path22)) {
|
|
3537
3537
|
if (this.contents == null)
|
|
3538
3538
|
return false;
|
|
3539
3539
|
this.contents = null;
|
|
3540
3540
|
return true;
|
|
3541
3541
|
}
|
|
3542
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3542
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path22) : false;
|
|
3543
3543
|
}
|
|
3544
3544
|
/**
|
|
3545
3545
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -3554,10 +3554,10 @@ var require_Document = __commonJS({
|
|
|
3554
3554
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
3555
3555
|
* `true` (collections are always returned intact).
|
|
3556
3556
|
*/
|
|
3557
|
-
getIn(
|
|
3558
|
-
if (Collection.isEmptyPath(
|
|
3557
|
+
getIn(path22, keepScalar) {
|
|
3558
|
+
if (Collection.isEmptyPath(path22))
|
|
3559
3559
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3560
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3560
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path22, keepScalar) : void 0;
|
|
3561
3561
|
}
|
|
3562
3562
|
/**
|
|
3563
3563
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -3568,10 +3568,10 @@ var require_Document = __commonJS({
|
|
|
3568
3568
|
/**
|
|
3569
3569
|
* Checks if the document includes a value at `path`.
|
|
3570
3570
|
*/
|
|
3571
|
-
hasIn(
|
|
3572
|
-
if (Collection.isEmptyPath(
|
|
3571
|
+
hasIn(path22) {
|
|
3572
|
+
if (Collection.isEmptyPath(path22))
|
|
3573
3573
|
return this.contents !== void 0;
|
|
3574
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3574
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path22) : false;
|
|
3575
3575
|
}
|
|
3576
3576
|
/**
|
|
3577
3577
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -3588,13 +3588,13 @@ var require_Document = __commonJS({
|
|
|
3588
3588
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
3589
3589
|
* boolean to add/remove the item from the set.
|
|
3590
3590
|
*/
|
|
3591
|
-
setIn(
|
|
3592
|
-
if (Collection.isEmptyPath(
|
|
3591
|
+
setIn(path22, value) {
|
|
3592
|
+
if (Collection.isEmptyPath(path22)) {
|
|
3593
3593
|
this.contents = value;
|
|
3594
3594
|
} else if (this.contents == null) {
|
|
3595
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3595
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path22), value);
|
|
3596
3596
|
} else if (assertCollection(this.contents)) {
|
|
3597
|
-
this.contents.setIn(
|
|
3597
|
+
this.contents.setIn(path22, value);
|
|
3598
3598
|
}
|
|
3599
3599
|
}
|
|
3600
3600
|
/**
|
|
@@ -5546,9 +5546,9 @@ var require_cst_visit = __commonJS({
|
|
|
5546
5546
|
visit.BREAK = BREAK;
|
|
5547
5547
|
visit.SKIP = SKIP;
|
|
5548
5548
|
visit.REMOVE = REMOVE;
|
|
5549
|
-
visit.itemAtPath = (cst,
|
|
5549
|
+
visit.itemAtPath = (cst, path22) => {
|
|
5550
5550
|
let item = cst;
|
|
5551
|
-
for (const [field, index] of
|
|
5551
|
+
for (const [field, index] of path22) {
|
|
5552
5552
|
const tok = item?.[field];
|
|
5553
5553
|
if (tok && "items" in tok) {
|
|
5554
5554
|
item = tok.items[index];
|
|
@@ -5557,23 +5557,23 @@ var require_cst_visit = __commonJS({
|
|
|
5557
5557
|
}
|
|
5558
5558
|
return item;
|
|
5559
5559
|
};
|
|
5560
|
-
visit.parentCollection = (cst,
|
|
5561
|
-
const parent = visit.itemAtPath(cst,
|
|
5562
|
-
const field =
|
|
5560
|
+
visit.parentCollection = (cst, path22) => {
|
|
5561
|
+
const parent = visit.itemAtPath(cst, path22.slice(0, -1));
|
|
5562
|
+
const field = path22[path22.length - 1][0];
|
|
5563
5563
|
const coll = parent?.[field];
|
|
5564
5564
|
if (coll && "items" in coll)
|
|
5565
5565
|
return coll;
|
|
5566
5566
|
throw new Error("Parent collection not found");
|
|
5567
5567
|
};
|
|
5568
|
-
function _visit(
|
|
5569
|
-
let ctrl = visitor(item,
|
|
5568
|
+
function _visit(path22, item, visitor) {
|
|
5569
|
+
let ctrl = visitor(item, path22);
|
|
5570
5570
|
if (typeof ctrl === "symbol")
|
|
5571
5571
|
return ctrl;
|
|
5572
5572
|
for (const field of ["key", "value"]) {
|
|
5573
5573
|
const token = item[field];
|
|
5574
5574
|
if (token && "items" in token) {
|
|
5575
5575
|
for (let i = 0; i < token.items.length; ++i) {
|
|
5576
|
-
const ci = _visit(Object.freeze(
|
|
5576
|
+
const ci = _visit(Object.freeze(path22.concat([[field, i]])), token.items[i], visitor);
|
|
5577
5577
|
if (typeof ci === "number")
|
|
5578
5578
|
i = ci - 1;
|
|
5579
5579
|
else if (ci === BREAK)
|
|
@@ -5584,10 +5584,10 @@ var require_cst_visit = __commonJS({
|
|
|
5584
5584
|
}
|
|
5585
5585
|
}
|
|
5586
5586
|
if (typeof ctrl === "function" && field === "key")
|
|
5587
|
-
ctrl = ctrl(item,
|
|
5587
|
+
ctrl = ctrl(item, path22);
|
|
5588
5588
|
}
|
|
5589
5589
|
}
|
|
5590
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5590
|
+
return typeof ctrl === "function" ? ctrl(item, path22) : ctrl;
|
|
5591
5591
|
}
|
|
5592
5592
|
exports.visit = visit;
|
|
5593
5593
|
}
|
|
@@ -6872,14 +6872,14 @@ var require_parser = __commonJS({
|
|
|
6872
6872
|
case "scalar":
|
|
6873
6873
|
case "single-quoted-scalar":
|
|
6874
6874
|
case "double-quoted-scalar": {
|
|
6875
|
-
const
|
|
6875
|
+
const fs21 = this.flowScalar(this.type);
|
|
6876
6876
|
if (atNextItem || it.value) {
|
|
6877
|
-
map.items.push({ start, key:
|
|
6877
|
+
map.items.push({ start, key: fs21, sep: [] });
|
|
6878
6878
|
this.onKeyLine = true;
|
|
6879
6879
|
} else if (it.sep) {
|
|
6880
|
-
this.stack.push(
|
|
6880
|
+
this.stack.push(fs21);
|
|
6881
6881
|
} else {
|
|
6882
|
-
Object.assign(it, { key:
|
|
6882
|
+
Object.assign(it, { key: fs21, sep: [] });
|
|
6883
6883
|
this.onKeyLine = true;
|
|
6884
6884
|
}
|
|
6885
6885
|
return;
|
|
@@ -7007,13 +7007,13 @@ var require_parser = __commonJS({
|
|
|
7007
7007
|
case "scalar":
|
|
7008
7008
|
case "single-quoted-scalar":
|
|
7009
7009
|
case "double-quoted-scalar": {
|
|
7010
|
-
const
|
|
7010
|
+
const fs21 = this.flowScalar(this.type);
|
|
7011
7011
|
if (!it || it.value)
|
|
7012
|
-
fc.items.push({ start: [], key:
|
|
7012
|
+
fc.items.push({ start: [], key: fs21, sep: [] });
|
|
7013
7013
|
else if (it.sep)
|
|
7014
|
-
this.stack.push(
|
|
7014
|
+
this.stack.push(fs21);
|
|
7015
7015
|
else
|
|
7016
|
-
Object.assign(it, { key:
|
|
7016
|
+
Object.assign(it, { key: fs21, sep: [] });
|
|
7017
7017
|
return;
|
|
7018
7018
|
}
|
|
7019
7019
|
case "flow-map-end":
|
|
@@ -7551,17 +7551,17 @@ var require_ignore = __commonJS({
|
|
|
7551
7551
|
var throwError = (message, Ctor) => {
|
|
7552
7552
|
throw new Ctor(message);
|
|
7553
7553
|
};
|
|
7554
|
-
var checkPath = (
|
|
7555
|
-
if (!isString(
|
|
7554
|
+
var checkPath = (path22, originalPath, doThrow) => {
|
|
7555
|
+
if (!isString(path22)) {
|
|
7556
7556
|
return doThrow(
|
|
7557
7557
|
`path must be a string, but got \`${originalPath}\``,
|
|
7558
7558
|
TypeError
|
|
7559
7559
|
);
|
|
7560
7560
|
}
|
|
7561
|
-
if (!
|
|
7561
|
+
if (!path22) {
|
|
7562
7562
|
return doThrow(`path must not be empty`, TypeError);
|
|
7563
7563
|
}
|
|
7564
|
-
if (checkPath.isNotRelative(
|
|
7564
|
+
if (checkPath.isNotRelative(path22)) {
|
|
7565
7565
|
const r = "`path.relative()`d";
|
|
7566
7566
|
return doThrow(
|
|
7567
7567
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -7570,7 +7570,7 @@ var require_ignore = __commonJS({
|
|
|
7570
7570
|
}
|
|
7571
7571
|
return true;
|
|
7572
7572
|
};
|
|
7573
|
-
var isNotRelative = (
|
|
7573
|
+
var isNotRelative = (path22) => REGEX_TEST_INVALID_PATH.test(path22);
|
|
7574
7574
|
checkPath.isNotRelative = isNotRelative;
|
|
7575
7575
|
checkPath.convert = (p) => p;
|
|
7576
7576
|
var Ignore = class {
|
|
@@ -7629,7 +7629,7 @@ var require_ignore = __commonJS({
|
|
|
7629
7629
|
// setting `checkUnignored` to `false` could reduce additional
|
|
7630
7630
|
// path matching.
|
|
7631
7631
|
// @returns {TestResult} true if a file is ignored
|
|
7632
|
-
_testOne(
|
|
7632
|
+
_testOne(path22, checkUnignored) {
|
|
7633
7633
|
let ignored = false;
|
|
7634
7634
|
let unignored = false;
|
|
7635
7635
|
this._rules.forEach((rule) => {
|
|
@@ -7637,7 +7637,7 @@ var require_ignore = __commonJS({
|
|
|
7637
7637
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
7638
7638
|
return;
|
|
7639
7639
|
}
|
|
7640
|
-
const matched = rule.regex.test(
|
|
7640
|
+
const matched = rule.regex.test(path22);
|
|
7641
7641
|
if (matched) {
|
|
7642
7642
|
ignored = !negative;
|
|
7643
7643
|
unignored = negative;
|
|
@@ -7650,24 +7650,24 @@ var require_ignore = __commonJS({
|
|
|
7650
7650
|
}
|
|
7651
7651
|
// @returns {TestResult}
|
|
7652
7652
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
7653
|
-
const
|
|
7653
|
+
const path22 = originalPath && checkPath.convert(originalPath);
|
|
7654
7654
|
checkPath(
|
|
7655
|
-
|
|
7655
|
+
path22,
|
|
7656
7656
|
originalPath,
|
|
7657
7657
|
this._allowRelativePaths ? RETURN_FALSE : throwError
|
|
7658
7658
|
);
|
|
7659
|
-
return this._t(
|
|
7659
|
+
return this._t(path22, cache, checkUnignored, slices);
|
|
7660
7660
|
}
|
|
7661
|
-
_t(
|
|
7662
|
-
if (
|
|
7663
|
-
return cache[
|
|
7661
|
+
_t(path22, cache, checkUnignored, slices) {
|
|
7662
|
+
if (path22 in cache) {
|
|
7663
|
+
return cache[path22];
|
|
7664
7664
|
}
|
|
7665
7665
|
if (!slices) {
|
|
7666
|
-
slices =
|
|
7666
|
+
slices = path22.split(SLASH);
|
|
7667
7667
|
}
|
|
7668
7668
|
slices.pop();
|
|
7669
7669
|
if (!slices.length) {
|
|
7670
|
-
return cache[
|
|
7670
|
+
return cache[path22] = this._testOne(path22, checkUnignored);
|
|
7671
7671
|
}
|
|
7672
7672
|
const parent = this._t(
|
|
7673
7673
|
slices.join(SLASH) + SLASH,
|
|
@@ -7675,24 +7675,24 @@ var require_ignore = __commonJS({
|
|
|
7675
7675
|
checkUnignored,
|
|
7676
7676
|
slices
|
|
7677
7677
|
);
|
|
7678
|
-
return cache[
|
|
7678
|
+
return cache[path22] = parent.ignored ? parent : this._testOne(path22, checkUnignored);
|
|
7679
7679
|
}
|
|
7680
|
-
ignores(
|
|
7681
|
-
return this._test(
|
|
7680
|
+
ignores(path22) {
|
|
7681
|
+
return this._test(path22, this._ignoreCache, false).ignored;
|
|
7682
7682
|
}
|
|
7683
7683
|
createFilter() {
|
|
7684
|
-
return (
|
|
7684
|
+
return (path22) => !this.ignores(path22);
|
|
7685
7685
|
}
|
|
7686
7686
|
filter(paths) {
|
|
7687
7687
|
return makeArray(paths).filter(this.createFilter());
|
|
7688
7688
|
}
|
|
7689
7689
|
// @returns {TestResult}
|
|
7690
|
-
test(
|
|
7691
|
-
return this._test(
|
|
7690
|
+
test(path22) {
|
|
7691
|
+
return this._test(path22, this._testCache, true);
|
|
7692
7692
|
}
|
|
7693
7693
|
};
|
|
7694
7694
|
var factory = (options) => new Ignore(options);
|
|
7695
|
-
var isPathValid = (
|
|
7695
|
+
var isPathValid = (path22) => checkPath(path22 && checkPath.convert(path22), path22, RETURN_FALSE);
|
|
7696
7696
|
factory.isPathValid = isPathValid;
|
|
7697
7697
|
factory.default = factory;
|
|
7698
7698
|
module.exports = factory;
|
|
@@ -7703,22 +7703,22 @@ var require_ignore = __commonJS({
|
|
|
7703
7703
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
7704
7704
|
checkPath.convert = makePosix;
|
|
7705
7705
|
const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
7706
|
-
checkPath.isNotRelative = (
|
|
7706
|
+
checkPath.isNotRelative = (path22) => REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path22) || isNotRelative(path22);
|
|
7707
7707
|
}
|
|
7708
7708
|
}
|
|
7709
7709
|
});
|
|
7710
7710
|
|
|
7711
7711
|
// src/sanitize_cli.js
|
|
7712
|
-
import
|
|
7713
|
-
import
|
|
7714
|
-
import
|
|
7712
|
+
import fs20 from "node:fs";
|
|
7713
|
+
import os7 from "node:os";
|
|
7714
|
+
import path21 from "node:path";
|
|
7715
7715
|
import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
|
|
7716
7716
|
|
|
7717
7717
|
// src/plugin.js
|
|
7718
|
-
import
|
|
7718
|
+
import fs19 from "node:fs";
|
|
7719
7719
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
7720
7720
|
var import_ignore3 = __toESM(require_ignore(), 1);
|
|
7721
|
-
import
|
|
7721
|
+
import path20 from "node:path";
|
|
7722
7722
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
7723
7723
|
|
|
7724
7724
|
// src/agents.js
|
|
@@ -9962,8 +9962,8 @@ function clickUpWebhookLogPath(worktree) {
|
|
|
9962
9962
|
return path7.join(optimaRuntimeDir(worktree), "clickup-webhook.log.jsonl");
|
|
9963
9963
|
}
|
|
9964
9964
|
function clickUpWebhookAuditLogDir() {
|
|
9965
|
-
const
|
|
9966
|
-
return path7.join(
|
|
9965
|
+
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");
|
|
9966
|
+
return path7.join(dataHome2, "opencode-optima");
|
|
9967
9967
|
}
|
|
9968
9968
|
function clickUpWebhookAuditLogPath(at = /* @__PURE__ */ new Date(), logDir = clickUpWebhookAuditLogDir()) {
|
|
9969
9969
|
return path7.join(logDir, `clickup-webhook-${at.toISOString().slice(0, 10)}.jsonl`);
|
|
@@ -11753,13 +11753,19 @@ var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
|
|
|
11753
11753
|
var CLICKUP_HUMAN_HANDOFF_TEXT_PATTERNS = [
|
|
11754
11754
|
/\bhuman validation requested\b/i,
|
|
11755
11755
|
/\bhuman validation\/approval\b/i,
|
|
11756
|
+
/\bvalidation handoff\b/i,
|
|
11757
|
+
/\bplease validate\b/i,
|
|
11758
|
+
/\bfinal validation\b/i,
|
|
11756
11759
|
/\bplease review pr\b/i,
|
|
11757
11760
|
/\bapprove or request changes\b/i,
|
|
11761
|
+
/\bremaining QA\b/i,
|
|
11758
11762
|
/\bexternal blocker\b/i,
|
|
11759
11763
|
/\bowner routing\b/i,
|
|
11760
11764
|
/\bpo action needed\b/i,
|
|
11761
11765
|
/\brouted to PO\b/i,
|
|
11762
11766
|
/\bauthenticated Chrome session\b/i,
|
|
11767
|
+
/\bauthenticated extension session\b/i,
|
|
11768
|
+
/\bbrowser-level\b/i,
|
|
11763
11769
|
/\bbrowser-real\b/i,
|
|
11764
11770
|
/\bAgent Jake\b/i,
|
|
11765
11771
|
/\bCDP\s+9222\b/i,
|
|
@@ -13500,11 +13506,392 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
13500
13506
|
};
|
|
13501
13507
|
}
|
|
13502
13508
|
|
|
13509
|
+
// src/qa/chrome.js
|
|
13510
|
+
import fs16 from "node:fs";
|
|
13511
|
+
import os6 from "node:os";
|
|
13512
|
+
import path17 from "node:path";
|
|
13513
|
+
|
|
13514
|
+
// src/qa/queue.js
|
|
13515
|
+
import fs15 from "node:fs";
|
|
13516
|
+
import os5 from "node:os";
|
|
13517
|
+
import path16 from "node:path";
|
|
13518
|
+
var DEFAULT_QA_LEASE_MS = 5 * 60 * 1e3;
|
|
13519
|
+
var DEFAULT_QA_PROVIDER = "chatgpt";
|
|
13520
|
+
function dataHome() {
|
|
13521
|
+
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");
|
|
13522
|
+
}
|
|
13523
|
+
function qaRuntimeDir() {
|
|
13524
|
+
return path16.join(dataHome(), "opencode-optima", "qa");
|
|
13525
|
+
}
|
|
13526
|
+
function normalizeQaProvider(provider = DEFAULT_QA_PROVIDER) {
|
|
13527
|
+
const normalized = String(provider || DEFAULT_QA_PROVIDER).trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
13528
|
+
return normalized || DEFAULT_QA_PROVIDER;
|
|
13529
|
+
}
|
|
13530
|
+
function qaQueueStatePath(provider = DEFAULT_QA_PROVIDER) {
|
|
13531
|
+
return path16.join(qaRuntimeDir(), `${normalizeQaProvider(provider)}-queue.json`);
|
|
13532
|
+
}
|
|
13533
|
+
function nowIso(now = /* @__PURE__ */ new Date()) {
|
|
13534
|
+
return now instanceof Date ? now.toISOString() : new Date(now).toISOString();
|
|
13535
|
+
}
|
|
13536
|
+
function normalizeClickUpTaskId(value = "") {
|
|
13537
|
+
return String(value || "").trim();
|
|
13538
|
+
}
|
|
13539
|
+
function emptyQaQueueState(provider = DEFAULT_QA_PROVIDER) {
|
|
13540
|
+
return {
|
|
13541
|
+
provider: normalizeQaProvider(provider),
|
|
13542
|
+
active: null,
|
|
13543
|
+
queue: [],
|
|
13544
|
+
history: []
|
|
13545
|
+
};
|
|
13546
|
+
}
|
|
13547
|
+
function readQaQueueState(provider = DEFAULT_QA_PROVIDER, statePath = qaQueueStatePath(provider)) {
|
|
13548
|
+
try {
|
|
13549
|
+
const raw = fs15.readFileSync(statePath, "utf8");
|
|
13550
|
+
const parsed = JSON.parse(raw);
|
|
13551
|
+
if (!isPlainObject(parsed)) return emptyQaQueueState(provider);
|
|
13552
|
+
return {
|
|
13553
|
+
...emptyQaQueueState(provider),
|
|
13554
|
+
...parsed,
|
|
13555
|
+
provider: normalizeQaProvider(parsed.provider || provider),
|
|
13556
|
+
queue: Array.isArray(parsed.queue) ? parsed.queue.filter(isPlainObject) : [],
|
|
13557
|
+
history: Array.isArray(parsed.history) ? parsed.history.filter(isPlainObject).slice(-50) : []
|
|
13558
|
+
};
|
|
13559
|
+
} catch {
|
|
13560
|
+
return emptyQaQueueState(provider);
|
|
13561
|
+
}
|
|
13562
|
+
}
|
|
13563
|
+
function writeQaQueueState(state, statePath = qaQueueStatePath(state?.provider)) {
|
|
13564
|
+
const normalized = {
|
|
13565
|
+
...emptyQaQueueState(state?.provider),
|
|
13566
|
+
...state,
|
|
13567
|
+
provider: normalizeQaProvider(state?.provider),
|
|
13568
|
+
queue: Array.isArray(state?.queue) ? state.queue.filter(isPlainObject) : [],
|
|
13569
|
+
history: Array.isArray(state?.history) ? state.history.filter(isPlainObject).slice(-50) : []
|
|
13570
|
+
};
|
|
13571
|
+
fs15.mkdirSync(path16.dirname(statePath), { recursive: true });
|
|
13572
|
+
fs15.writeFileSync(statePath, `${JSON.stringify(normalized, null, 2)}
|
|
13573
|
+
`, { mode: 384 });
|
|
13574
|
+
return normalized;
|
|
13575
|
+
}
|
|
13576
|
+
function activeExpired(active, nowMs, leaseMs) {
|
|
13577
|
+
if (!active?.clickupTaskId) return false;
|
|
13578
|
+
const last = Date.parse(active.lastActivityAt || active.grantedAt || "");
|
|
13579
|
+
return Number.isFinite(last) && nowMs - last > leaseMs;
|
|
13580
|
+
}
|
|
13581
|
+
function queuedIndex(state, clickupTaskId) {
|
|
13582
|
+
return state.queue.findIndex((entry) => entry.clickupTaskId === clickupTaskId);
|
|
13583
|
+
}
|
|
13584
|
+
function queueEntry({ clickupTaskId, sessionId = "", worktree = "", reason = "", now = /* @__PURE__ */ new Date() } = {}) {
|
|
13585
|
+
return {
|
|
13586
|
+
clickupTaskId,
|
|
13587
|
+
sessionId: String(sessionId || "").trim(),
|
|
13588
|
+
worktree: String(worktree || "").trim(),
|
|
13589
|
+
reason: String(reason || "").trim(),
|
|
13590
|
+
queuedAt: nowIso(now),
|
|
13591
|
+
lastRequestAt: nowIso(now)
|
|
13592
|
+
};
|
|
13593
|
+
}
|
|
13594
|
+
function activeEntry({ provider, clickupTaskId, sessionId = "", worktree = "", leaseMs = DEFAULT_QA_LEASE_MS, now = /* @__PURE__ */ new Date() } = {}) {
|
|
13595
|
+
return {
|
|
13596
|
+
provider: normalizeQaProvider(provider),
|
|
13597
|
+
clickupTaskId,
|
|
13598
|
+
sessionId: String(sessionId || "").trim(),
|
|
13599
|
+
worktree: String(worktree || "").trim(),
|
|
13600
|
+
grantedAt: nowIso(now),
|
|
13601
|
+
lastActivityAt: nowIso(now),
|
|
13602
|
+
leaseMs
|
|
13603
|
+
};
|
|
13604
|
+
}
|
|
13605
|
+
function releaseExpiredQaSlot(state, { now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13606
|
+
const normalized = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13607
|
+
if (!activeExpired(normalized.active, now.getTime(), leaseMs)) return { state: normalized, expired: null, promoted: null };
|
|
13608
|
+
const expired = normalized.active;
|
|
13609
|
+
normalized.history = [...normalized.history || [], { action: "expired", clickupTaskId: expired.clickupTaskId, at: nowIso(now) }].slice(-50);
|
|
13610
|
+
normalized.active = null;
|
|
13611
|
+
const promoted = normalized.queue.shift() || null;
|
|
13612
|
+
if (promoted) {
|
|
13613
|
+
normalized.active = activeEntry({ ...promoted, provider: normalized.provider, leaseMs, now });
|
|
13614
|
+
normalized.history = [...normalized.history || [], { action: "promoted", clickupTaskId: promoted.clickupTaskId, at: nowIso(now), reason: "expired_previous_slot" }].slice(-50);
|
|
13615
|
+
}
|
|
13616
|
+
return { state: normalized, expired, promoted };
|
|
13617
|
+
}
|
|
13618
|
+
function requestQaSlot(state, { clickupTaskId, sessionId = "", worktree = "", reason = "", now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13619
|
+
const taskId = normalizeClickUpTaskId(clickupTaskId);
|
|
13620
|
+
if (!taskId) return { state, ok: false, action: "invalid", reason: "clickup_task_id_required" };
|
|
13621
|
+
let next = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13622
|
+
const expiredResult = releaseExpiredQaSlot(next, { now, leaseMs });
|
|
13623
|
+
next = expiredResult.state;
|
|
13624
|
+
if (!next.active) {
|
|
13625
|
+
next.active = activeEntry({ provider: next.provider, clickupTaskId: taskId, sessionId, worktree, leaseMs, now });
|
|
13626
|
+
next.queue = next.queue.filter((entry) => entry.clickupTaskId !== taskId);
|
|
13627
|
+
next.history = [...next.history || [], { action: "granted", clickupTaskId: taskId, at: nowIso(now) }].slice(-50);
|
|
13628
|
+
return { state: next, ok: true, action: "granted", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13629
|
+
}
|
|
13630
|
+
if (next.active.clickupTaskId === taskId) {
|
|
13631
|
+
next.active = { ...next.active, sessionId: sessionId || next.active.sessionId || "", worktree: worktree || next.active.worktree || "", lastActivityAt: nowIso(now), leaseMs };
|
|
13632
|
+
next.history = [...next.history || [], { action: "renewed", clickupTaskId: taskId, at: nowIso(now) }].slice(-50);
|
|
13633
|
+
return { state: next, ok: true, action: "granted_existing", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13634
|
+
}
|
|
13635
|
+
const index = queuedIndex(next, taskId);
|
|
13636
|
+
if (index >= 0) {
|
|
13637
|
+
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) };
|
|
13638
|
+
} else {
|
|
13639
|
+
next.queue.push(queueEntry({ clickupTaskId: taskId, sessionId, worktree, reason, now }));
|
|
13640
|
+
}
|
|
13641
|
+
return {
|
|
13642
|
+
state: next,
|
|
13643
|
+
ok: false,
|
|
13644
|
+
action: "queued",
|
|
13645
|
+
active: next.active,
|
|
13646
|
+
position: queuedIndex(next, taskId) + 1,
|
|
13647
|
+
expired: expiredResult.expired,
|
|
13648
|
+
promoted: expiredResult.promoted
|
|
13649
|
+
};
|
|
13650
|
+
}
|
|
13651
|
+
function touchQaSlot(state, { clickupTaskId, now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13652
|
+
const taskId = normalizeClickUpTaskId(clickupTaskId);
|
|
13653
|
+
let next = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13654
|
+
const expiredResult = releaseExpiredQaSlot(next, { now, leaseMs });
|
|
13655
|
+
next = expiredResult.state;
|
|
13656
|
+
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 };
|
|
13657
|
+
next.active = { ...next.active, lastActivityAt: nowIso(now), leaseMs };
|
|
13658
|
+
return { state: next, ok: true, action: "touched", active: next.active, expired: expiredResult.expired, promoted: expiredResult.promoted };
|
|
13659
|
+
}
|
|
13660
|
+
function finishQaSlot(state, { clickupTaskId, now = /* @__PURE__ */ new Date(), leaseMs = DEFAULT_QA_LEASE_MS } = {}) {
|
|
13661
|
+
const taskId = normalizeClickUpTaskId(clickupTaskId);
|
|
13662
|
+
let next = { ...emptyQaQueueState(state?.provider), ...state, queue: Array.isArray(state?.queue) ? [...state.queue] : [] };
|
|
13663
|
+
const expiredResult = releaseExpiredQaSlot(next, { now, leaseMs });
|
|
13664
|
+
next = expiredResult.state;
|
|
13665
|
+
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 };
|
|
13666
|
+
const released = next.active;
|
|
13667
|
+
next.active = null;
|
|
13668
|
+
next.history = [...next.history || [], { action: "finished", clickupTaskId: taskId, at: nowIso(now) }].slice(-50);
|
|
13669
|
+
const promoted = next.queue.shift() || null;
|
|
13670
|
+
if (promoted) {
|
|
13671
|
+
next.active = activeEntry({ ...promoted, provider: next.provider, leaseMs, now });
|
|
13672
|
+
next.history = [...next.history || [], { action: "promoted", clickupTaskId: promoted.clickupTaskId, at: nowIso(now), reason: "previous_finished" }].slice(-50);
|
|
13673
|
+
}
|
|
13674
|
+
return { state: next, ok: true, action: "finished", released, promoted };
|
|
13675
|
+
}
|
|
13676
|
+
|
|
13677
|
+
// src/qa/chrome.js
|
|
13678
|
+
var DEFAULT_QA_CDP_URL = "http://127.0.0.1:9222";
|
|
13679
|
+
function providerDir(provider = DEFAULT_QA_PROVIDER) {
|
|
13680
|
+
return path17.join(qaRuntimeDir(), normalizeQaProvider(provider));
|
|
13681
|
+
}
|
|
13682
|
+
function defaultQaExtensionStageDir(provider = DEFAULT_QA_PROVIDER) {
|
|
13683
|
+
return path17.join(providerDir(provider), "extension-under-test");
|
|
13684
|
+
}
|
|
13685
|
+
function defaultQaArtifactsDir(provider = DEFAULT_QA_PROVIDER, clickupTaskId = "unknown") {
|
|
13686
|
+
return path17.join(providerDir(provider), "artifacts", String(clickupTaskId || "unknown"));
|
|
13687
|
+
}
|
|
13688
|
+
function normalizeCdpUrl(value = "") {
|
|
13689
|
+
return String(value || process.env.OPTIMA_QA_CHROME_CDP_URL || DEFAULT_QA_CDP_URL).trim() || DEFAULT_QA_CDP_URL;
|
|
13690
|
+
}
|
|
13691
|
+
async function loadPlaywrightChromium() {
|
|
13692
|
+
try {
|
|
13693
|
+
const mod = await import("playwright-core");
|
|
13694
|
+
return mod.chromium;
|
|
13695
|
+
} catch (error) {
|
|
13696
|
+
throw new Error(`playwright-core is required for Optima QA browser tools: ${error.message}`);
|
|
13697
|
+
}
|
|
13698
|
+
}
|
|
13699
|
+
function qaWindowName(clickupTaskId) {
|
|
13700
|
+
return `optima-qa:${String(clickupTaskId || "").trim()}`;
|
|
13701
|
+
}
|
|
13702
|
+
async function safePageWindowName(page) {
|
|
13703
|
+
try {
|
|
13704
|
+
return await page.evaluate(() => window.name || "");
|
|
13705
|
+
} catch {
|
|
13706
|
+
return "";
|
|
13707
|
+
}
|
|
13708
|
+
}
|
|
13709
|
+
async function findQaPage(context, clickupTaskId) {
|
|
13710
|
+
const expected = qaWindowName(clickupTaskId);
|
|
13711
|
+
const pages = context.pages();
|
|
13712
|
+
for (let index = 1; index < pages.length; index += 1) {
|
|
13713
|
+
const page = pages[index];
|
|
13714
|
+
if (await safePageWindowName(page) === expected) return page;
|
|
13715
|
+
}
|
|
13716
|
+
return null;
|
|
13717
|
+
}
|
|
13718
|
+
async function ensurePersistentTab(context, url = "https://chatgpt.com/") {
|
|
13719
|
+
const pages = context.pages();
|
|
13720
|
+
if (pages[0]) return pages[0];
|
|
13721
|
+
const page = await context.newPage();
|
|
13722
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
13723
|
+
});
|
|
13724
|
+
return page;
|
|
13725
|
+
}
|
|
13726
|
+
async function ensureQaPage(context, { clickupTaskId, startUrl = "https://chatgpt.com/" } = {}) {
|
|
13727
|
+
await ensurePersistentTab(context, startUrl);
|
|
13728
|
+
const existing = await findQaPage(context, clickupTaskId);
|
|
13729
|
+
if (existing) return existing;
|
|
13730
|
+
const page = await context.newPage();
|
|
13731
|
+
await page.goto(startUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
|
|
13732
|
+
});
|
|
13733
|
+
await page.evaluate((name) => {
|
|
13734
|
+
window.name = name;
|
|
13735
|
+
}, qaWindowName(clickupTaskId)).catch(() => {
|
|
13736
|
+
});
|
|
13737
|
+
return page;
|
|
13738
|
+
}
|
|
13739
|
+
function copyExtensionSource(sourcePath, stageDir) {
|
|
13740
|
+
const source = String(sourcePath || "").trim();
|
|
13741
|
+
if (!source) return { ok: true, skipped: true, reason: "extension_path_not_provided", stageDir };
|
|
13742
|
+
const resolved = path17.resolve(source.replace(/^~(?=$|\/)/, os6.homedir()));
|
|
13743
|
+
if (!fs16.existsSync(resolved)) return { ok: false, error: `extension_path_not_found: ${resolved}`, stageDir };
|
|
13744
|
+
fs16.rmSync(stageDir, { recursive: true, force: true });
|
|
13745
|
+
fs16.mkdirSync(path17.dirname(stageDir), { recursive: true });
|
|
13746
|
+
fs16.cpSync(resolved, stageDir, {
|
|
13747
|
+
recursive: true,
|
|
13748
|
+
filter: (src) => !/[/\\](node_modules|\.git|dist|build|test-results)([/\\]|$)/.test(src)
|
|
13749
|
+
});
|
|
13750
|
+
return { ok: true, source: resolved, stageDir };
|
|
13751
|
+
}
|
|
13752
|
+
function extensionIdFromWorkerUrl(url = "") {
|
|
13753
|
+
const match = String(url || "").match(/^chrome-extension:\/\/([^/]+)/);
|
|
13754
|
+
return match?.[1] || "";
|
|
13755
|
+
}
|
|
13756
|
+
async function findExtensionWorker(context, extensionId = "") {
|
|
13757
|
+
const workers = context.serviceWorkers ? context.serviceWorkers() : [];
|
|
13758
|
+
const normalized = String(extensionId || "").trim();
|
|
13759
|
+
if (normalized) return workers.find((worker) => worker.url().startsWith(`chrome-extension://${normalized}/`)) || null;
|
|
13760
|
+
return workers.find((worker) => String(worker.url()).startsWith("chrome-extension://")) || null;
|
|
13761
|
+
}
|
|
13762
|
+
async function resetExtensionState(context, { extensionId = "" } = {}) {
|
|
13763
|
+
const worker = await findExtensionWorker(context, extensionId);
|
|
13764
|
+
if (!worker) return { ok: false, error: "extension_service_worker_not_found" };
|
|
13765
|
+
const id = extensionIdFromWorkerUrl(worker.url());
|
|
13766
|
+
const result = await worker.evaluate(async () => {
|
|
13767
|
+
const deletedCaches = [];
|
|
13768
|
+
const deletedDbs = [];
|
|
13769
|
+
if (globalThis.chrome?.storage?.local) await chrome.storage.local.clear();
|
|
13770
|
+
if (globalThis.chrome?.storage?.session?.clear) await chrome.storage.session.clear();
|
|
13771
|
+
if (globalThis.chrome?.storage?.sync) await chrome.storage.sync.clear();
|
|
13772
|
+
if (globalThis.caches?.keys) {
|
|
13773
|
+
for (const name of await caches.keys()) {
|
|
13774
|
+
await caches.delete(name);
|
|
13775
|
+
deletedCaches.push(name);
|
|
13776
|
+
}
|
|
13777
|
+
}
|
|
13778
|
+
if (globalThis.indexedDB?.databases) {
|
|
13779
|
+
for (const db of await indexedDB.databases()) {
|
|
13780
|
+
if (db.name) {
|
|
13781
|
+
indexedDB.deleteDatabase(db.name);
|
|
13782
|
+
deletedDbs.push(db.name);
|
|
13783
|
+
}
|
|
13784
|
+
}
|
|
13785
|
+
}
|
|
13786
|
+
return { ok: true, deletedCaches, deletedDbs };
|
|
13787
|
+
});
|
|
13788
|
+
return { extensionId: id, ...result };
|
|
13789
|
+
}
|
|
13790
|
+
async function reloadExtension(context, { extensionId = "" } = {}) {
|
|
13791
|
+
const worker = await findExtensionWorker(context, extensionId);
|
|
13792
|
+
if (!worker) return { ok: false, error: "extension_service_worker_not_found" };
|
|
13793
|
+
const id = extensionIdFromWorkerUrl(worker.url());
|
|
13794
|
+
await worker.evaluate(() => chrome.runtime.reload()).catch(() => {
|
|
13795
|
+
});
|
|
13796
|
+
return { ok: true, extensionId: id };
|
|
13797
|
+
}
|
|
13798
|
+
async function summarizeBrowser(context, { clickupTaskId = "" } = {}) {
|
|
13799
|
+
const pages = context.pages();
|
|
13800
|
+
const pageSummaries = [];
|
|
13801
|
+
for (let index = 0; index < pages.length; index += 1) {
|
|
13802
|
+
const page = pages[index];
|
|
13803
|
+
pageSummaries.push({
|
|
13804
|
+
index,
|
|
13805
|
+
role: index === 0 ? "persistent_session_tab" : await safePageWindowName(page) === qaWindowName(clickupTaskId) ? "qa_task_tab" : "other",
|
|
13806
|
+
url: page.url(),
|
|
13807
|
+
title: await page.title().catch(() => "")
|
|
13808
|
+
});
|
|
13809
|
+
}
|
|
13810
|
+
const workers = context.serviceWorkers ? context.serviceWorkers().map((worker) => worker.url()) : [];
|
|
13811
|
+
return { pages: pageSummaries, serviceWorkers: workers };
|
|
13812
|
+
}
|
|
13813
|
+
function parseQaCommand(commandJson = "") {
|
|
13814
|
+
if (!String(commandJson || "").trim()) return { action: "status" };
|
|
13815
|
+
try {
|
|
13816
|
+
const parsed = JSON.parse(commandJson);
|
|
13817
|
+
return parsed && typeof parsed === "object" ? parsed : { action: "invalid", error: "command_json_must_be_object" };
|
|
13818
|
+
} catch (error) {
|
|
13819
|
+
return { action: "script", script: String(commandJson || "") };
|
|
13820
|
+
}
|
|
13821
|
+
}
|
|
13822
|
+
async function runQaCommandOnPage(page, context, browser, command, { artifactsDir }) {
|
|
13823
|
+
const action = String(command.action || "status").trim().toLowerCase();
|
|
13824
|
+
if (action === "status") return { ok: true, page: { url: page.url(), title: await page.title().catch(() => "") } };
|
|
13825
|
+
if (action === "goto") {
|
|
13826
|
+
await page.goto(String(command.url || "https://chatgpt.com/"), { waitUntil: command.wait_until || "domcontentloaded", timeout: Number(command.timeout_ms || 3e4) });
|
|
13827
|
+
return { ok: true, url: page.url(), title: await page.title().catch(() => "") };
|
|
13828
|
+
}
|
|
13829
|
+
if (action === "evaluate") {
|
|
13830
|
+
const result = await page.evaluate(String(command.expression || "() => null"));
|
|
13831
|
+
return { ok: true, result };
|
|
13832
|
+
}
|
|
13833
|
+
if (action === "screenshot") {
|
|
13834
|
+
const name = String(command.name || `screenshot-${Date.now()}.png`).replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
13835
|
+
const outPath = path17.join(artifactsDir, name);
|
|
13836
|
+
fs16.mkdirSync(path17.dirname(outPath), { recursive: true });
|
|
13837
|
+
await page.screenshot({ path: outPath, fullPage: command.full_page !== false });
|
|
13838
|
+
return { ok: true, path: outPath };
|
|
13839
|
+
}
|
|
13840
|
+
if (action === "set_input_files") {
|
|
13841
|
+
const selector = String(command.selector || "input[type=file]");
|
|
13842
|
+
await page.setInputFiles(selector, command.files);
|
|
13843
|
+
return { ok: true, selector };
|
|
13844
|
+
}
|
|
13845
|
+
if (action === "script") {
|
|
13846
|
+
const script = String(command.script || "");
|
|
13847
|
+
if (!script.trim()) return { ok: false, error: "script_required" };
|
|
13848
|
+
const fn = new Function("page", "context", "browser", "artifactsDir", `return (async () => {
|
|
13849
|
+
${script}
|
|
13850
|
+
})()`);
|
|
13851
|
+
const result = await fn(page, context, browser, artifactsDir);
|
|
13852
|
+
return { ok: true, result };
|
|
13853
|
+
}
|
|
13854
|
+
return { ok: false, error: `unsupported_qa_command_action: ${action}` };
|
|
13855
|
+
}
|
|
13856
|
+
async function withQaBrowser({ provider = DEFAULT_QA_PROVIDER, cdpUrl = "", clickupTaskId = "", startUrl = "https://chatgpt.com/" } = {}, callback) {
|
|
13857
|
+
const chromium = await loadPlaywrightChromium();
|
|
13858
|
+
const browser = await chromium.connectOverCDP(normalizeCdpUrl(cdpUrl));
|
|
13859
|
+
try {
|
|
13860
|
+
const context = browser.contexts()[0] || await browser.newContext();
|
|
13861
|
+
const page = clickupTaskId ? await ensureQaPage(context, { clickupTaskId, startUrl }) : null;
|
|
13862
|
+
return await callback({ browser, context, page });
|
|
13863
|
+
} finally {
|
|
13864
|
+
await browser.close().catch(() => {
|
|
13865
|
+
});
|
|
13866
|
+
}
|
|
13867
|
+
}
|
|
13868
|
+
async function qaBrowserStatus(options = {}) {
|
|
13869
|
+
return withQaBrowser(options, async ({ context }) => ({ ok: true, ...await summarizeBrowser(context, { clickupTaskId: options.clickupTaskId }) })).catch((error) => ({ ok: false, error: error.message, cdpUrl: normalizeCdpUrl(options.cdpUrl) }));
|
|
13870
|
+
}
|
|
13871
|
+
async function qaPrepareExtension({ provider = DEFAULT_QA_PROVIDER, cdpUrl = "", extensionPath = "", extensionId = "", reset = true, reload = true } = {}) {
|
|
13872
|
+
const stageDir = defaultQaExtensionStageDir(provider);
|
|
13873
|
+
const sync = copyExtensionSource(extensionPath, stageDir);
|
|
13874
|
+
const browserResult = await withQaBrowser({ provider, cdpUrl }, async ({ context }) => {
|
|
13875
|
+
const resetResult = reset ? await resetExtensionState(context, { extensionId }).catch((error) => ({ ok: false, error: error.message })) : { ok: true, skipped: true };
|
|
13876
|
+
const reloadResult = reload ? await reloadExtension(context, { extensionId }).catch((error) => ({ ok: false, error: error.message })) : { ok: true, skipped: true };
|
|
13877
|
+
return { reset: resetResult, reload: reloadResult, browser: await summarizeBrowser(context) };
|
|
13878
|
+
}).catch((error) => ({ ok: false, error: error.message }));
|
|
13879
|
+
return { sync, ...browserResult };
|
|
13880
|
+
}
|
|
13881
|
+
async function qaRunChromeCommand({ provider = DEFAULT_QA_PROVIDER, cdpUrl = "", clickupTaskId = "", commandJson = "", startUrl = "https://chatgpt.com/" } = {}) {
|
|
13882
|
+
const command = parseQaCommand(commandJson);
|
|
13883
|
+
const artifactsDir = defaultQaArtifactsDir(provider, clickupTaskId);
|
|
13884
|
+
return withQaBrowser({ provider, cdpUrl, clickupTaskId, startUrl }, async ({ browser, context, page }) => {
|
|
13885
|
+
const result = await runQaCommandOnPage(page, context, browser, command, { artifactsDir });
|
|
13886
|
+
return { ok: result.ok !== false, command: command.action || "script", tab: { windowName: qaWindowName(clickupTaskId), url: page.url(), title: await page.title().catch(() => "") }, artifactsDir, result };
|
|
13887
|
+
});
|
|
13888
|
+
}
|
|
13889
|
+
|
|
13503
13890
|
// src/repair.js
|
|
13504
13891
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
13505
13892
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
13506
|
-
import
|
|
13507
|
-
import
|
|
13893
|
+
import fs17 from "node:fs";
|
|
13894
|
+
import path18 from "node:path";
|
|
13508
13895
|
var CODEMAP_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13509
13896
|
".js",
|
|
13510
13897
|
".ts",
|
|
@@ -13542,10 +13929,10 @@ var LEGACY_OPERATIONAL_ROOTS = /* @__PURE__ */ new Set([
|
|
|
13542
13929
|
]);
|
|
13543
13930
|
var OPTIMA_OPERATIONAL_FOLDERS = [".optima", "templates", "dist"];
|
|
13544
13931
|
function relPath(worktree, targetPath) {
|
|
13545
|
-
return
|
|
13932
|
+
return path18.relative(worktree, targetPath).split(path18.sep).join("/") || ".";
|
|
13546
13933
|
}
|
|
13547
13934
|
function normalizeCodemapRelPath(relPathValue) {
|
|
13548
|
-
return
|
|
13935
|
+
return path18.normalize(relPathValue).replace(/\\/g, "/").replace(/\/$/, "");
|
|
13549
13936
|
}
|
|
13550
13937
|
function firstPathSegment(relPathValue) {
|
|
13551
13938
|
return normalizeCodemapRelPath(relPathValue).split("/").filter(Boolean)[0] || "";
|
|
@@ -13564,8 +13951,8 @@ function isHiddenTree(relPathValue) {
|
|
|
13564
13951
|
function loadGitIgnoreMatcher(worktree) {
|
|
13565
13952
|
const ig = (0, import_ignore.default)();
|
|
13566
13953
|
ig.add(".git");
|
|
13567
|
-
const gitignorePath =
|
|
13568
|
-
if (
|
|
13954
|
+
const gitignorePath = path18.join(worktree, ".gitignore");
|
|
13955
|
+
if (fs17.existsSync(gitignorePath)) ig.add(fs17.readFileSync(gitignorePath, "utf8"));
|
|
13569
13956
|
return ig;
|
|
13570
13957
|
}
|
|
13571
13958
|
function codemapSectionEntries(value) {
|
|
@@ -13584,7 +13971,7 @@ function codemapIndexedPaths(map) {
|
|
|
13584
13971
|
}
|
|
13585
13972
|
function readCodemap(filePath, unresolved, worktree) {
|
|
13586
13973
|
try {
|
|
13587
|
-
const parsed = import_yaml4.default.parse(
|
|
13974
|
+
const parsed = import_yaml4.default.parse(fs17.readFileSync(filePath, "utf8"));
|
|
13588
13975
|
if (!parsed || typeof parsed !== "object") {
|
|
13589
13976
|
unresolved.push({ category: "codemap", path: relPath(worktree, filePath), message: "CodeMap is empty or invalid; manual repair required." });
|
|
13590
13977
|
return null;
|
|
@@ -13596,42 +13983,42 @@ function readCodemap(filePath, unresolved, worktree) {
|
|
|
13596
13983
|
}
|
|
13597
13984
|
}
|
|
13598
13985
|
function writeCodemap(filePath, map) {
|
|
13599
|
-
|
|
13986
|
+
fs17.writeFileSync(filePath, import_yaml4.default.stringify(map), "utf8");
|
|
13600
13987
|
}
|
|
13601
13988
|
function isIgnoredPath(ig, relativePath) {
|
|
13602
13989
|
const normalized = normalizeCodemapRelPath(relativePath);
|
|
13603
13990
|
return Boolean(normalized) && ig.ignores(normalized);
|
|
13604
13991
|
}
|
|
13605
13992
|
function hasImmediateSourceFile(dirPath, ig, worktree) {
|
|
13606
|
-
if (!
|
|
13607
|
-
for (const item of
|
|
13608
|
-
const childPath =
|
|
13609
|
-
const relative =
|
|
13993
|
+
if (!fs17.existsSync(dirPath)) return false;
|
|
13994
|
+
for (const item of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
13995
|
+
const childPath = path18.join(dirPath, item.name);
|
|
13996
|
+
const relative = path18.relative(worktree, childPath);
|
|
13610
13997
|
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative)) continue;
|
|
13611
|
-
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(
|
|
13998
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) return true;
|
|
13612
13999
|
}
|
|
13613
14000
|
return false;
|
|
13614
14001
|
}
|
|
13615
14002
|
function containsSource(dirPath, ig, worktree) {
|
|
13616
|
-
if (!
|
|
13617
|
-
const dirRelPath =
|
|
14003
|
+
if (!fs17.existsSync(dirPath)) return false;
|
|
14004
|
+
const dirRelPath = path18.relative(worktree, dirPath);
|
|
13618
14005
|
if (isOperationalRelPath(dirRelPath) || isHiddenTree(dirRelPath)) return false;
|
|
13619
|
-
for (const item of
|
|
13620
|
-
const childPath =
|
|
13621
|
-
const relative =
|
|
14006
|
+
for (const item of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
14007
|
+
const childPath = path18.join(dirPath, item.name);
|
|
14008
|
+
const relative = path18.relative(worktree, childPath);
|
|
13622
14009
|
if (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative)) continue;
|
|
13623
|
-
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(
|
|
14010
|
+
if (item.isFile() && CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) return true;
|
|
13624
14011
|
if (item.isDirectory() && containsSource(childPath, ig, worktree)) return true;
|
|
13625
14012
|
}
|
|
13626
14013
|
return false;
|
|
13627
14014
|
}
|
|
13628
14015
|
function createModuleCodemap(dirPath, worktree, deps) {
|
|
13629
|
-
const entries =
|
|
14016
|
+
const entries = fs17.readdirSync(dirPath, { withFileTypes: true });
|
|
13630
14017
|
const entrypoints = [];
|
|
13631
14018
|
const internals = [];
|
|
13632
|
-
const relToRoot =
|
|
14019
|
+
const relToRoot = path18.relative(dirPath, deps.optimaCodemapPath(worktree)).split(path18.sep).join("/");
|
|
13633
14020
|
for (const item of entries) {
|
|
13634
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
14021
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) continue;
|
|
13635
14022
|
const bucket = /^index\.(js|ts|tsx|jsx)$|^main\.(js|ts|tsx|jsx|py|go|rs|java)$/.test(item.name) ? entrypoints : internals;
|
|
13636
14023
|
bucket.push({ path: item.name });
|
|
13637
14024
|
}
|
|
@@ -13644,7 +14031,7 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13644
14031
|
const map = readCodemap(codemapPath, unresolved, worktree);
|
|
13645
14032
|
if (!map) return;
|
|
13646
14033
|
const isRootMap = codemapPath === deps.optimaCodemapPath(worktree);
|
|
13647
|
-
const mapDir =
|
|
14034
|
+
const mapDir = path18.dirname(codemapPath);
|
|
13648
14035
|
const pathBase = isRootMap ? worktree : mapDir;
|
|
13649
14036
|
let changed = false;
|
|
13650
14037
|
for (const section of ["modules", "entrypoints", "sources_of_truth", "links", "internals"]) {
|
|
@@ -13655,13 +14042,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13655
14042
|
}
|
|
13656
14043
|
const nextEntries = [];
|
|
13657
14044
|
for (const entry of originalEntries) {
|
|
13658
|
-
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") ||
|
|
14045
|
+
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://") || path18.isAbsolute(entry.path)) {
|
|
13659
14046
|
nextEntries.push(entry);
|
|
13660
14047
|
continue;
|
|
13661
14048
|
}
|
|
13662
14049
|
const normalizedPath = normalizeCodemapRelPath(entry.path);
|
|
13663
|
-
const absPath =
|
|
13664
|
-
if (!
|
|
14050
|
+
const absPath = path18.join(pathBase, normalizedPath);
|
|
14051
|
+
if (!fs17.existsSync(absPath)) {
|
|
13665
14052
|
registerAction({ category: "codemap", action: apply ? "removed" : "would_remove", path: relPath(worktree, codemapPath), detail: `Remove broken ${section.slice(0, -1)} reference '${entry.path}'.` });
|
|
13666
14053
|
changed = true;
|
|
13667
14054
|
continue;
|
|
@@ -13670,8 +14057,8 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13670
14057
|
const maxParts = isRootMap ? 2 : 1;
|
|
13671
14058
|
if (parts.length > maxParts) {
|
|
13672
14059
|
const localPath = isRootMap ? parts.slice(0, 2).join("/") : parts[0];
|
|
13673
|
-
const localAbs =
|
|
13674
|
-
if (
|
|
14060
|
+
const localAbs = path18.join(pathBase, localPath);
|
|
14061
|
+
if (fs17.existsSync(localAbs)) {
|
|
13675
14062
|
nextEntries.push({ ...entry, path: localPath });
|
|
13676
14063
|
changed = true;
|
|
13677
14064
|
registerAction({ category: "codemap", action: apply ? "updated" : "would_update", path: relPath(worktree, codemapPath), detail: `Shorten '${entry.path}' to local path '${localPath}'.` });
|
|
@@ -13685,11 +14072,11 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13685
14072
|
}
|
|
13686
14073
|
if (Array.isArray(map[section])) map[section] = nextEntries;
|
|
13687
14074
|
}
|
|
13688
|
-
if (map.scope === "module" && !isOperationalRelPath(
|
|
14075
|
+
if (map.scope === "module" && !isOperationalRelPath(path18.relative(worktree, mapDir))) {
|
|
13689
14076
|
const indexed = codemapIndexedPaths(map);
|
|
13690
14077
|
const internals = Array.isArray(map.internals) ? map.internals : [];
|
|
13691
|
-
for (const item of
|
|
13692
|
-
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(
|
|
14078
|
+
for (const item of fs17.readdirSync(mapDir, { withFileTypes: true })) {
|
|
14079
|
+
if (!item.isFile() || item.name === "codemap.yml" || !CODEMAP_SOURCE_EXTENSIONS.has(path18.extname(item.name))) continue;
|
|
13693
14080
|
if (indexed.has(item.name)) continue;
|
|
13694
14081
|
internals.push({ path: item.name });
|
|
13695
14082
|
indexed.add(item.name);
|
|
@@ -13703,13 +14090,13 @@ function repairSingleCodemap(codemapPath, worktree, apply, registerAction, unres
|
|
|
13703
14090
|
function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
13704
14091
|
const ig = loadGitIgnoreMatcher(worktree);
|
|
13705
14092
|
const rootCodemapPath = deps.optimaCodemapPath(worktree);
|
|
13706
|
-
const rootCodemapMissing = !
|
|
14093
|
+
const rootCodemapMissing = !fs17.existsSync(rootCodemapPath);
|
|
13707
14094
|
if (rootCodemapMissing) {
|
|
13708
14095
|
actions.push({ category: "codemap", action: apply ? "created" : "would_create", path: ".optima/codemap.yml", detail: "Create missing root CodeMap from template." });
|
|
13709
14096
|
if (apply) deps.scaffoldOptimaRootCodemap(worktree);
|
|
13710
14097
|
}
|
|
13711
|
-
if (
|
|
13712
|
-
const rootMap =
|
|
14098
|
+
if (fs17.existsSync(rootCodemapPath)) repairSingleCodemap(rootCodemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
14099
|
+
const rootMap = fs17.existsSync(rootCodemapPath) ? readCodemap(rootCodemapPath, unresolved, worktree) : null;
|
|
13713
14100
|
let rootChanged = false;
|
|
13714
14101
|
const rootModulesRepairable = rootMap && (rootMap.modules === void 0 || Array.isArray(rootMap.modules));
|
|
13715
14102
|
if (rootMap) {
|
|
@@ -13721,10 +14108,10 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
13721
14108
|
}
|
|
13722
14109
|
}
|
|
13723
14110
|
function walk(dirPath) {
|
|
13724
|
-
const relative =
|
|
14111
|
+
const relative = path18.relative(worktree, dirPath);
|
|
13725
14112
|
if (relative && (isIgnoredPath(ig, relative) || isOperationalRelPath(relative) || isHiddenTree(relative))) return;
|
|
13726
|
-
const codemapPath =
|
|
13727
|
-
const hasCodemap =
|
|
14113
|
+
const codemapPath = path18.join(dirPath, "codemap.yml");
|
|
14114
|
+
const hasCodemap = fs17.existsSync(codemapPath);
|
|
13728
14115
|
const sourceDir = relative && containsSource(dirPath, ig, worktree);
|
|
13729
14116
|
if (sourceDir && !hasCodemap && dirPath !== worktree) {
|
|
13730
14117
|
if (hasImmediateSourceFile(dirPath, ig, worktree)) {
|
|
@@ -13743,19 +14130,19 @@ function repairCodemaps(worktree, apply, actions, unresolved, deps) {
|
|
|
13743
14130
|
actions.push({ category: "codemap", action: apply ? "updated" : "would_update", path: ".optima/codemap.yml", detail: `Register top-level source module '${normalizeCodemapRelPath(relative)}'.` });
|
|
13744
14131
|
}
|
|
13745
14132
|
}
|
|
13746
|
-
if (
|
|
13747
|
-
for (const item of
|
|
13748
|
-
if (item.isDirectory()) walk(
|
|
14133
|
+
if (fs17.existsSync(codemapPath) && codemapPath !== rootCodemapPath) repairSingleCodemap(codemapPath, worktree, apply, (action) => actions.push(action), unresolved, deps);
|
|
14134
|
+
for (const item of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
14135
|
+
if (item.isDirectory()) walk(path18.join(dirPath, item.name));
|
|
13749
14136
|
}
|
|
13750
14137
|
}
|
|
13751
|
-
if (
|
|
14138
|
+
if (fs17.existsSync(worktree)) walk(worktree);
|
|
13752
14139
|
if (rootMap && rootChanged && apply) writeCodemap(rootCodemapPath, rootMap);
|
|
13753
14140
|
}
|
|
13754
14141
|
function walkMarkdownFiles(dirPath) {
|
|
13755
|
-
if (!
|
|
14142
|
+
if (!fs17.existsSync(dirPath)) return [];
|
|
13756
14143
|
const files = [];
|
|
13757
|
-
for (const entry of
|
|
13758
|
-
const entryPath =
|
|
14144
|
+
for (const entry of fs17.readdirSync(dirPath, { withFileTypes: true })) {
|
|
14145
|
+
const entryPath = path18.join(dirPath, entry.name);
|
|
13759
14146
|
if (entry.isDirectory()) {
|
|
13760
14147
|
files.push(...walkMarkdownFiles(entryPath));
|
|
13761
14148
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -13794,7 +14181,7 @@ function rewriteOptimaMarkdownReferences(content, evidenceBasePath = null) {
|
|
|
13794
14181
|
}
|
|
13795
14182
|
function optimaEvidenceBaseForFile(worktree, filePath, deps) {
|
|
13796
14183
|
const evidenceRoot = deps.optimaEvidencesDir(worktree);
|
|
13797
|
-
const relative =
|
|
14184
|
+
const relative = path18.relative(evidenceRoot, filePath).split(path18.sep).join("/");
|
|
13798
14185
|
const [taskId] = relative.split("/");
|
|
13799
14186
|
if (!taskId || taskId === ".." || relative.startsWith("../")) return null;
|
|
13800
14187
|
return `.optima/evidences/${taskId}`;
|
|
@@ -13807,11 +14194,11 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
13807
14194
|
];
|
|
13808
14195
|
const changed = [];
|
|
13809
14196
|
for (const filePath of scanRoots.flatMap(walkMarkdownFiles)) {
|
|
13810
|
-
const raw =
|
|
13811
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
14197
|
+
const raw = fs17.readFileSync(filePath, "utf8");
|
|
14198
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path18.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
13812
14199
|
const rewritten = rewriteOptimaMarkdownReferences(raw, evidenceBasePath);
|
|
13813
14200
|
if (rewritten !== raw) {
|
|
13814
|
-
|
|
14201
|
+
fs17.writeFileSync(filePath, rewritten, "utf8");
|
|
13815
14202
|
changed.push(relPath(worktree, filePath));
|
|
13816
14203
|
}
|
|
13817
14204
|
}
|
|
@@ -13819,8 +14206,8 @@ function normalizeOptimaMarkdownReferences(worktree, deps) {
|
|
|
13819
14206
|
}
|
|
13820
14207
|
function missingOptimaGitignoreRules(worktree, deps) {
|
|
13821
14208
|
if (!deps.isGitRepository(worktree)) return [];
|
|
13822
|
-
const gitignorePath =
|
|
13823
|
-
const existing =
|
|
14209
|
+
const gitignorePath = path18.join(worktree, ".gitignore");
|
|
14210
|
+
const existing = fs17.existsSync(gitignorePath) ? fs17.readFileSync(gitignorePath, "utf8") : "";
|
|
13824
14211
|
const existingRules = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
13825
14212
|
return deps.optimaGitignoreRules.filter((rule) => !existingRules.has(rule));
|
|
13826
14213
|
}
|
|
@@ -13837,11 +14224,11 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
13837
14224
|
const apply = mode === "apply";
|
|
13838
14225
|
const actions = [];
|
|
13839
14226
|
const unresolved = [];
|
|
13840
|
-
const hadOptima =
|
|
14227
|
+
const hadOptima = fs17.existsSync(deps.optimaDir(worktree));
|
|
13841
14228
|
const missingGitignoreRules = missingOptimaGitignoreRules(worktree, deps);
|
|
13842
14229
|
const markdownBefore = apply ? [] : walkMarkdownFiles(deps.optimaDir(worktree)).filter((filePath) => {
|
|
13843
|
-
const raw =
|
|
13844
|
-
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) +
|
|
14230
|
+
const raw = fs17.readFileSync(filePath, "utf8");
|
|
14231
|
+
const evidenceBasePath = filePath.startsWith(deps.optimaEvidencesDir(worktree) + path18.sep) ? optimaEvidenceBaseForFile(worktree, filePath, deps) : null;
|
|
13845
14232
|
return rewriteOptimaMarkdownReferences(raw, evidenceBasePath) !== raw;
|
|
13846
14233
|
}).map((filePath) => relPath(worktree, filePath));
|
|
13847
14234
|
if (!hadOptima) pushRepairAction(actions, "operational", apply ? "created" : "would_create", ".optima/", "Create Optima operational root.");
|
|
@@ -13850,31 +14237,31 @@ function planOptimaRepair(worktree, args = {}, deps) {
|
|
|
13850
14237
|
[".staticeng/", deps.legacyStaticEngDir(worktree)],
|
|
13851
14238
|
[".nomadwork/", deps.legacyNomadworkDir(worktree)],
|
|
13852
14239
|
[".nomadworks/", deps.legacyNomadworksDir(worktree)],
|
|
13853
|
-
["tasks/",
|
|
13854
|
-
["evidences/",
|
|
13855
|
-
["docs/scrs/",
|
|
13856
|
-
["codemap.yml",
|
|
13857
|
-
["codemap.yaml",
|
|
14240
|
+
["tasks/", path18.join(worktree, "tasks")],
|
|
14241
|
+
["evidences/", path18.join(worktree, "evidences")],
|
|
14242
|
+
["docs/scrs/", path18.join(worktree, "docs", "scrs")],
|
|
14243
|
+
["codemap.yml", path18.join(worktree, "codemap.yml")],
|
|
14244
|
+
["codemap.yaml", path18.join(worktree, "codemap.yaml")]
|
|
13858
14245
|
];
|
|
13859
|
-
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/",
|
|
14246
|
+
if (!deps.isOptimaPluginPackageWorktree(worktree)) legacySources.push(["policies/", path18.join(worktree, "policies")]);
|
|
13860
14247
|
for (const [display, sourcePath] of legacySources) {
|
|
13861
|
-
if (
|
|
14248
|
+
if (fs17.existsSync(sourcePath)) pushRepairAction(actions, "legacy", apply ? "migrated" : "would_migrate", display, "Move or merge legacy/root Optima artifact into .optima using preservation safeguards.");
|
|
13862
14249
|
}
|
|
13863
14250
|
if (apply) deps.migrateLegacyOptimaLayout(worktree);
|
|
13864
|
-
const configMissing = !
|
|
14251
|
+
const configMissing = !fs17.existsSync(deps.repoConfigPath(worktree));
|
|
13865
14252
|
if (configMissing) pushRepairAction(actions, "config", apply ? "created" : "would_create", ".optima/.config/optima.yaml", "Create missing local Optima config from template.");
|
|
13866
14253
|
if (apply) deps.scaffoldOptimaConfig(worktree, args.team_mode || "full");
|
|
13867
14254
|
const requiredFiles = [
|
|
13868
|
-
[
|
|
13869
|
-
[
|
|
13870
|
-
[
|
|
13871
|
-
[
|
|
13872
|
-
[
|
|
13873
|
-
[
|
|
13874
|
-
[
|
|
14255
|
+
[path18.join(deps.optimaTasksDir(worktree), "current.md"), "registry", ".optima/tasks/current.md", deps.currentTasksRegistryContent()],
|
|
14256
|
+
[path18.join(deps.optimaTasksDir(worktree), "done.md"), "registry", ".optima/tasks/done.md", deps.doneTasksRegistryContent()],
|
|
14257
|
+
[path18.join(deps.optimaScrsDir(worktree), "current.md"), "registry", ".optima/docs/scrs/current.md", deps.currentScrRegistryContent()],
|
|
14258
|
+
[path18.join(deps.optimaScrsDir(worktree), "done.md"), "registry", ".optima/docs/scrs/done.md", deps.doneScrRegistryContent()],
|
|
14259
|
+
[path18.join(deps.optimaTasksDir(worktree), "task-template.md"), "template", ".optima/tasks/task-template.md", deps.taskTemplateContent()],
|
|
14260
|
+
[path18.join(deps.optimaTasksDir(worktree), "subtask-template.md"), "template", ".optima/tasks/subtask-template.md", deps.subtaskTemplateContent()],
|
|
14261
|
+
[path18.join(deps.repoPoliciesDir(worktree), "README.md"), "policy", ".optima/policies/README.md", deps.repoLocalPoliciesReadme]
|
|
13875
14262
|
];
|
|
13876
14263
|
for (const [filePath, category, displayPath, content] of requiredFiles) {
|
|
13877
|
-
if (!
|
|
14264
|
+
if (!fs17.existsSync(filePath)) {
|
|
13878
14265
|
pushRepairAction(actions, category, apply ? "created" : "would_create", displayPath, "Create missing Optima scaffold file without overwriting existing content.");
|
|
13879
14266
|
if (apply) deps.ensureFileIfMissing(filePath, content);
|
|
13880
14267
|
}
|
|
@@ -13928,18 +14315,18 @@ function formatRepairResult(plan, validationResult = null, formatValidationResul
|
|
|
13928
14315
|
// src/validate_logic.js
|
|
13929
14316
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
13930
14317
|
var import_ignore2 = __toESM(require_ignore(), 1);
|
|
13931
|
-
import
|
|
13932
|
-
import
|
|
14318
|
+
import fs18 from "node:fs";
|
|
14319
|
+
import path19 from "node:path";
|
|
13933
14320
|
async function optima_validate_logic(worktree) {
|
|
13934
|
-
const rootCodemapPath =
|
|
13935
|
-
if (!
|
|
14321
|
+
const rootCodemapPath = path19.join(worktree, ".optima", "codemap.yml");
|
|
14322
|
+
if (!fs18.existsSync(rootCodemapPath)) return { ok: false, errors: [".optima/codemap.yml not found."], warnings: [] };
|
|
13936
14323
|
const errors = [];
|
|
13937
14324
|
const warnings = [];
|
|
13938
14325
|
const ig = (0, import_ignore2.default)();
|
|
13939
14326
|
ig.add(".git");
|
|
13940
|
-
const gitignorePath =
|
|
13941
|
-
if (
|
|
13942
|
-
ig.add(
|
|
14327
|
+
const gitignorePath = path19.join(worktree, ".gitignore");
|
|
14328
|
+
if (fs18.existsSync(gitignorePath)) {
|
|
14329
|
+
ig.add(fs18.readFileSync(gitignorePath, "utf8"));
|
|
13943
14330
|
}
|
|
13944
14331
|
const sourceExtensions = [
|
|
13945
14332
|
".js",
|
|
@@ -13979,30 +14366,30 @@ async function optima_validate_logic(worktree) {
|
|
|
13979
14366
|
const operationalFolders = [".optima", "templates", "dist"];
|
|
13980
14367
|
const isHiddenTree2 = (relPath2) => {
|
|
13981
14368
|
if (!relPath2) return false;
|
|
13982
|
-
return relPath2.split(
|
|
14369
|
+
return relPath2.split(path19.sep).some((part) => part.startsWith("."));
|
|
13983
14370
|
};
|
|
13984
|
-
const firstPathSegment2 = (relPath2) => relPath2.split(
|
|
14371
|
+
const firstPathSegment2 = (relPath2) => relPath2.split(path19.sep).filter(Boolean)[0] || "";
|
|
13985
14372
|
const isOperationalRelPath2 = (relPath2) => {
|
|
13986
14373
|
if (!relPath2) return false;
|
|
13987
14374
|
const firstSegment = firstPathSegment2(relPath2);
|
|
13988
14375
|
if (legacyOperationalRoots.has(firstSegment)) return true;
|
|
13989
|
-
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f +
|
|
14376
|
+
return operationalFolders.some((f) => relPath2 === f || relPath2.startsWith(f + path19.sep));
|
|
13990
14377
|
};
|
|
13991
14378
|
const getSectionEntries = (value) => {
|
|
13992
14379
|
if (Array.isArray(value)) return value;
|
|
13993
14380
|
if (value && typeof value === "object") return Object.values(value);
|
|
13994
14381
|
return [];
|
|
13995
14382
|
};
|
|
13996
|
-
const normalizeRelativePath = (relPath2) =>
|
|
14383
|
+
const normalizeRelativePath = (relPath2) => path19.normalize(relPath2).replace(/\\/g, "/").replace(/\/$/, "");
|
|
13997
14384
|
const isSourceDir = (dirPath) => {
|
|
13998
|
-
const dirRelPath =
|
|
14385
|
+
const dirRelPath = path19.relative(worktree, dirPath);
|
|
13999
14386
|
if (isOperationalRelPath2(dirRelPath)) return false;
|
|
14000
|
-
const items =
|
|
14387
|
+
const items = fs18.readdirSync(dirPath, { withFileTypes: true });
|
|
14001
14388
|
for (const item of items) {
|
|
14002
|
-
const childPath =
|
|
14003
|
-
const relPath2 =
|
|
14389
|
+
const childPath = path19.join(dirPath, item.name);
|
|
14390
|
+
const relPath2 = path19.relative(worktree, childPath);
|
|
14004
14391
|
if (ig.ignores(relPath2) || isOperationalRelPath2(relPath2)) continue;
|
|
14005
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
14392
|
+
if (item.isFile() && sourceExtensions.includes(path19.extname(item.name))) return true;
|
|
14006
14393
|
if (item.isDirectory()) {
|
|
14007
14394
|
if (isSourceDir(childPath)) return true;
|
|
14008
14395
|
}
|
|
@@ -14010,7 +14397,7 @@ async function optima_validate_logic(worktree) {
|
|
|
14010
14397
|
return false;
|
|
14011
14398
|
};
|
|
14012
14399
|
function validateMap(filePath) {
|
|
14013
|
-
const content =
|
|
14400
|
+
const content = fs18.readFileSync(filePath, "utf8");
|
|
14014
14401
|
let map;
|
|
14015
14402
|
try {
|
|
14016
14403
|
map = import_yaml5.default.parse(content);
|
|
@@ -14022,32 +14409,32 @@ async function optima_validate_logic(worktree) {
|
|
|
14022
14409
|
errors.push(`${filePath}: Invalid YAML.`);
|
|
14023
14410
|
return;
|
|
14024
14411
|
}
|
|
14025
|
-
const dir =
|
|
14412
|
+
const dir = path19.dirname(filePath);
|
|
14026
14413
|
const pathBase = filePath === rootCodemapPath ? worktree : dir;
|
|
14027
14414
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
14028
14415
|
const sectionsToVerify = ["modules", "entrypoints", "sources_of_truth", "links", "internals"];
|
|
14029
14416
|
for (const section of sectionsToVerify) {
|
|
14030
14417
|
for (const item of getSectionEntries(map[section])) {
|
|
14031
14418
|
if (item?.path) {
|
|
14032
|
-
indexedPaths.add(
|
|
14419
|
+
indexedPaths.add(path19.normalize(item.path));
|
|
14033
14420
|
if (section === "links" && (item.path.startsWith("http://") || item.path.startsWith("https://"))) {
|
|
14034
14421
|
continue;
|
|
14035
14422
|
}
|
|
14036
|
-
const absPath =
|
|
14037
|
-
if (!
|
|
14423
|
+
const absPath = path19.isAbsolute(item.path) ? item.path : path19.join(pathBase, item.path);
|
|
14424
|
+
if (!fs18.existsSync(absPath)) {
|
|
14038
14425
|
errors.push(`${filePath}: ${section.slice(0, -1)} path does not exist: ${item.path}`);
|
|
14039
14426
|
}
|
|
14040
14427
|
}
|
|
14041
14428
|
}
|
|
14042
14429
|
}
|
|
14043
|
-
const relDir =
|
|
14430
|
+
const relDir = path19.relative(worktree, dir);
|
|
14044
14431
|
const isOperational = isOperationalRelPath2(relDir);
|
|
14045
14432
|
if (map.scope === "module" && !isOperational) {
|
|
14046
|
-
const items =
|
|
14433
|
+
const items = fs18.readdirSync(dir, { withFileTypes: true });
|
|
14047
14434
|
for (const item of items) {
|
|
14048
|
-
if (item.isFile() && sourceExtensions.includes(
|
|
14435
|
+
if (item.isFile() && sourceExtensions.includes(path19.extname(item.name))) {
|
|
14049
14436
|
if (item.name === "codemap.yml") continue;
|
|
14050
|
-
if (!indexedPaths.has(
|
|
14437
|
+
if (!indexedPaths.has(path19.normalize(item.name))) {
|
|
14051
14438
|
errors.push(`${filePath}: Unindexed source file found: '${item.name}'. Every source file must be categorized in a section (e.g., 'internals').`);
|
|
14052
14439
|
}
|
|
14053
14440
|
}
|
|
@@ -14057,7 +14444,7 @@ async function optima_validate_logic(worktree) {
|
|
|
14057
14444
|
for (const key of pathKeys) {
|
|
14058
14445
|
for (const entry of getSectionEntries(map[key])) {
|
|
14059
14446
|
if (!entry?.path || entry.path.startsWith("http://") || entry.path.startsWith("https://")) continue;
|
|
14060
|
-
if (
|
|
14447
|
+
if (path19.isAbsolute(entry.path)) continue;
|
|
14061
14448
|
const normalizedPath = normalizeRelativePath(entry.path);
|
|
14062
14449
|
if (!normalizedPath || normalizedPath === ".") continue;
|
|
14063
14450
|
const parts = normalizedPath.split("/").filter((p) => p && p !== ".");
|
|
@@ -14069,18 +14456,18 @@ async function optima_validate_logic(worktree) {
|
|
|
14069
14456
|
}
|
|
14070
14457
|
}
|
|
14071
14458
|
const walk = (dir) => {
|
|
14072
|
-
const relDir =
|
|
14459
|
+
const relDir = path19.relative(worktree, dir);
|
|
14073
14460
|
if (relDir && ig.ignores(relDir)) return;
|
|
14074
14461
|
if (isOperationalRelPath2(relDir)) return;
|
|
14075
14462
|
if (isHiddenTree2(relDir)) return;
|
|
14076
|
-
const hasCodemap =
|
|
14077
|
-
const items =
|
|
14078
|
-
if (!relDir.startsWith(
|
|
14463
|
+
const hasCodemap = fs18.existsSync(path19.join(dir, "codemap.yml"));
|
|
14464
|
+
const items = fs18.readdirSync(dir, { withFileTypes: true });
|
|
14465
|
+
if (!relDir.startsWith(path19.join(".optima", "tasks", "done"))) {
|
|
14079
14466
|
for (const item of items) {
|
|
14080
14467
|
if (item.isFile() && item.name.endsWith(".md")) {
|
|
14081
|
-
const content =
|
|
14468
|
+
const content = fs18.readFileSync(path19.join(dir, item.name), "utf8");
|
|
14082
14469
|
if (content.includes("[To be defined]") || content.includes("[Insert ")) {
|
|
14083
|
-
const relFilePath =
|
|
14470
|
+
const relFilePath = path19.join(relDir, item.name);
|
|
14084
14471
|
errors.push(`Documentation Placeholder found: '${relFilePath}' still contains [To be defined] or [Insert ...] placeholders.`);
|
|
14085
14472
|
}
|
|
14086
14473
|
}
|
|
@@ -14090,9 +14477,9 @@ async function optima_validate_logic(worktree) {
|
|
|
14090
14477
|
if (relDir !== "" && !hasCodemap && isSourceDir(dir) && !isOperational) {
|
|
14091
14478
|
errors.push(`Missing CodeMap: Directory '${relDir}' contains source but has no codemap.yml.`);
|
|
14092
14479
|
}
|
|
14093
|
-
if (hasCodemap) validateMap(
|
|
14480
|
+
if (hasCodemap) validateMap(path19.join(dir, "codemap.yml"));
|
|
14094
14481
|
for (const item of items) {
|
|
14095
|
-
if (item.isDirectory()) walk(
|
|
14482
|
+
if (item.isDirectory()) walk(path19.join(dir, item.name));
|
|
14096
14483
|
}
|
|
14097
14484
|
};
|
|
14098
14485
|
validateMap(rootCodemapPath);
|
|
@@ -14106,12 +14493,13 @@ async function optima_validate_logic(worktree) {
|
|
|
14106
14493
|
|
|
14107
14494
|
// src/plugin.js
|
|
14108
14495
|
var activeWorkflows = /* @__PURE__ */ new Map();
|
|
14496
|
+
var activeQaQueueSchedulers = /* @__PURE__ */ new Map();
|
|
14109
14497
|
function normalizeWorkflowTaskPath(taskPath) {
|
|
14110
14498
|
if (typeof taskPath !== "string") return { ok: false, message: "Error: task_path is required." };
|
|
14111
14499
|
const trimmed = taskPath.trim();
|
|
14112
14500
|
if (!trimmed) return { ok: false, message: "Error: task_path is required." };
|
|
14113
14501
|
const normalized = trimmed.replace(/\\/g, "/");
|
|
14114
|
-
if (
|
|
14502
|
+
if (path20.isAbsolute(trimmed)) return { ok: true, taskPath: trimmed };
|
|
14115
14503
|
if (normalized === "tasks" || normalized.startsWith("tasks/")) {
|
|
14116
14504
|
return {
|
|
14117
14505
|
ok: false,
|
|
@@ -14128,10 +14516,10 @@ function normalizeWorkflowTaskPath(taskPath) {
|
|
|
14128
14516
|
}
|
|
14129
14517
|
function readTaskMetadata(taskPath, worktree) {
|
|
14130
14518
|
if (!taskPath) return {};
|
|
14131
|
-
const absoluteTaskPath =
|
|
14132
|
-
if (!
|
|
14519
|
+
const absoluteTaskPath = path20.isAbsolute(taskPath) ? taskPath : path20.join(worktree, taskPath);
|
|
14520
|
+
if (!fs19.existsSync(absoluteTaskPath)) return {};
|
|
14133
14521
|
try {
|
|
14134
|
-
const raw =
|
|
14522
|
+
const raw = fs19.readFileSync(absoluteTaskPath, "utf8");
|
|
14135
14523
|
const { data } = parseFrontmatter(raw);
|
|
14136
14524
|
return {
|
|
14137
14525
|
complexity: typeof data.complexity === "string" ? data.complexity.trim().toLowerCase() : void 0,
|
|
@@ -14154,15 +14542,15 @@ function hasActiveImplementationWorkflow() {
|
|
|
14154
14542
|
function resolveSessionToolDirectory({ requestedDirectory, context, pluginWorktree, clickUpWebhookValidation } = {}) {
|
|
14155
14543
|
const safe = safeWorktreeOrFailure(context, pluginWorktree);
|
|
14156
14544
|
if (!safe.ok) return { ok: false, error: safe.message };
|
|
14157
|
-
const requested = String(requestedDirectory || "").trim() ?
|
|
14545
|
+
const requested = String(requestedDirectory || "").trim() ? path20.resolve(safe.worktree, String(requestedDirectory).trim()) : safe.worktree;
|
|
14158
14546
|
if (!isSafeWritableDirectory(requested)) return { ok: false, error: `Directory is not a safe writable directory: ${requested}` };
|
|
14159
14547
|
if (isSameOrNestedPath(requested, safe.worktree)) return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "context_worktree" };
|
|
14160
14548
|
const clickUpBasePath = clickUpWebhookValidation?.complete === true && clickUpWebhookValidation?.ok !== false ? clickUpWebhookValidation.config?.basePath : "";
|
|
14161
14549
|
if (clickUpBasePath && isSameOrNestedPath(requested, clickUpBasePath)) {
|
|
14162
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath:
|
|
14550
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_base_path", clickupBasePath: path20.resolve(clickUpBasePath) };
|
|
14163
14551
|
}
|
|
14164
14552
|
if (clickUpBasePath && isClickUpDerivedWorktreeSibling(requested, clickUpBasePath)) {
|
|
14165
|
-
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath:
|
|
14553
|
+
return { ok: true, directory: requested, safeWorktree: safe.worktree, scope: "clickup_derived_worktree", clickupBasePath: path20.resolve(clickUpBasePath) };
|
|
14166
14554
|
}
|
|
14167
14555
|
return {
|
|
14168
14556
|
ok: false,
|
|
@@ -14177,9 +14565,9 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
14177
14565
|
const configPath = resolveConfigPath(worktree);
|
|
14178
14566
|
const discussionRegistry = loadDiscussionRegistry(worktree);
|
|
14179
14567
|
let repoCfg = { agents: {}, defaults: {}, features: {} };
|
|
14180
|
-
if (
|
|
14568
|
+
if (fs19.existsSync(configPath)) {
|
|
14181
14569
|
try {
|
|
14182
|
-
repoCfg = import_yaml6.default.parse(
|
|
14570
|
+
repoCfg = import_yaml6.default.parse(fs19.readFileSync(configPath, "utf8")) || repoCfg;
|
|
14183
14571
|
} catch (e) {
|
|
14184
14572
|
console.error(`[Optima] Failed to parse config at ${configPath}:`, e);
|
|
14185
14573
|
}
|
|
@@ -14340,6 +14728,134 @@ Evidencia: ${evidencePath}` : ""
|
|
|
14340
14728
|
applied
|
|
14341
14729
|
};
|
|
14342
14730
|
};
|
|
14731
|
+
const qaLeaseMs = Number(repoCfg.qa?.lease_ms || repoCfg.qa?.leaseMs || DEFAULT_QA_LEASE_MS) || DEFAULT_QA_LEASE_MS;
|
|
14732
|
+
const qaProvider = String(repoCfg.qa?.provider || DEFAULT_QA_PROVIDER);
|
|
14733
|
+
const qaCdpUrl = String(repoCfg.qa?.cdp_url || repoCfg.qa?.cdpUrl || process.env.OPTIMA_QA_CHROME_CDP_URL || "").trim();
|
|
14734
|
+
const qaTaskMetadata = async (taskId) => {
|
|
14735
|
+
if (!taskId || typeof runtimeClickUpClient?.getTask !== "function") return { task: null, metadata: {} };
|
|
14736
|
+
const task = await runtimeClickUpClient.getTask(taskId);
|
|
14737
|
+
const metadata = normalizeAgentMetadataJson(clickUpTaskAgentMetadata(task, clickUpWebhookValidation.config?.routing?.metadataFieldId));
|
|
14738
|
+
return { task, metadata };
|
|
14739
|
+
};
|
|
14740
|
+
const qaSessionFromMetadata = (metadata = {}) => {
|
|
14741
|
+
return metadata.optima?.sessions?.product_manager || metadata.task?.product_manager || metadata.sessions?.product_manager || "";
|
|
14742
|
+
};
|
|
14743
|
+
const qaWorktreeFromMetadata = (metadata = {}) => {
|
|
14744
|
+
return metadata.task?.worktree || metadata.worktree || "";
|
|
14745
|
+
};
|
|
14746
|
+
const applyQaQueuedClickUpState = async (taskId) => {
|
|
14747
|
+
const applied = [];
|
|
14748
|
+
const errors = [];
|
|
14749
|
+
const pmId = String(clickUpWebhookValidation.config?.routing?.productManagerAssigneeId || "").trim();
|
|
14750
|
+
if (!taskId || !clickUpWebhookValidation.complete || !runtimeClickUpClient) return { applied, errors };
|
|
14751
|
+
if (runtimeClickUpClient.updateTaskStatus) {
|
|
14752
|
+
try {
|
|
14753
|
+
await runtimeClickUpClient.updateTaskStatus({ taskId, status: "waiting QA slot" });
|
|
14754
|
+
applied.push("status:waiting QA slot");
|
|
14755
|
+
} catch (error) {
|
|
14756
|
+
errors.push(`status:${error.message}`);
|
|
14757
|
+
}
|
|
14758
|
+
}
|
|
14759
|
+
if (pmId && runtimeClickUpClient.updateTaskAssignees) {
|
|
14760
|
+
try {
|
|
14761
|
+
await runtimeClickUpClient.updateTaskAssignees({ taskId, remove: [pmId] });
|
|
14762
|
+
applied.push("remove_product_manager");
|
|
14763
|
+
} catch (error) {
|
|
14764
|
+
errors.push(`assignees:${error.message}`);
|
|
14765
|
+
}
|
|
14766
|
+
}
|
|
14767
|
+
return { applied, errors };
|
|
14768
|
+
};
|
|
14769
|
+
const wakePromotedQaTask = async (entry, { provider = qaProvider, reason = "qa_slot_available" } = {}) => {
|
|
14770
|
+
const taskId = String(entry?.clickupTaskId || "").trim();
|
|
14771
|
+
const applied = [];
|
|
14772
|
+
const errors = [];
|
|
14773
|
+
if (!taskId || !clickUpWebhookValidation.complete) return { ok: false, reason: "missing_task_or_clickup_config" };
|
|
14774
|
+
const pmId = String(clickUpWebhookValidation.config?.routing?.productManagerAssigneeId || "").trim();
|
|
14775
|
+
const humanIds = Object.values(clickUpWebhookValidation.config?.routing?.humanRoleClickUpIds || {}).map((id) => String(id || "").trim()).filter(Boolean);
|
|
14776
|
+
try {
|
|
14777
|
+
if (runtimeClickUpClient.updateTaskStatus) {
|
|
14778
|
+
await runtimeClickUpClient.updateTaskStatus({ taskId, status: "in progress" });
|
|
14779
|
+
applied.push("status:in progress");
|
|
14780
|
+
}
|
|
14781
|
+
} catch (error) {
|
|
14782
|
+
errors.push(`status:${error.message}`);
|
|
14783
|
+
}
|
|
14784
|
+
try {
|
|
14785
|
+
if (pmId && runtimeClickUpClient.updateTaskAssignees) {
|
|
14786
|
+
await runtimeClickUpClient.updateTaskAssignees({ taskId, add: [pmId], remove: humanIds });
|
|
14787
|
+
applied.push("assign_product_manager");
|
|
14788
|
+
}
|
|
14789
|
+
} catch (error) {
|
|
14790
|
+
errors.push(`assignees:${error.message}`);
|
|
14791
|
+
}
|
|
14792
|
+
let metadata = {};
|
|
14793
|
+
try {
|
|
14794
|
+
metadata = (await qaTaskMetadata(taskId)).metadata;
|
|
14795
|
+
} catch (error) {
|
|
14796
|
+
errors.push(`metadata:${error.message}`);
|
|
14797
|
+
}
|
|
14798
|
+
const sessionId = entry.sessionId || qaSessionFromMetadata(metadata);
|
|
14799
|
+
const directory = entry.worktree || qaWorktreeFromMetadata(metadata);
|
|
14800
|
+
if (sessionId && input.client && typeof sendOpenCodeSessionEvent === "function") {
|
|
14801
|
+
try {
|
|
14802
|
+
const prompt = [
|
|
14803
|
+
`[Optima QA Slot Granted]`,
|
|
14804
|
+
`Provider: ${provider}`,
|
|
14805
|
+
`ClickUp task: ${taskId}`,
|
|
14806
|
+
`Reason: ${reason}`,
|
|
14807
|
+
"",
|
|
14808
|
+
"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.",
|
|
14809
|
+
"Puedes conservarla mientras hagas al menos una llamada QA cada 5 minutos.",
|
|
14810
|
+
"Cuando termines, llama obligatoriamente a `optima_qa_finish` para resetear la extensi\xF3n y liberar la cola.",
|
|
14811
|
+
"La pesta\xF1a 0 es intocable: usa \xFAnicamente la pesta\xF1a QA que Optima crea para esta tarea."
|
|
14812
|
+
].join("\n");
|
|
14813
|
+
await sendOpenCodeSessionEvent(input.client, {
|
|
14814
|
+
sessionId,
|
|
14815
|
+
agent: clickUpWebhookValidation.config.routing?.targetAgent || "workflow_product_manager",
|
|
14816
|
+
text: prompt,
|
|
14817
|
+
directory,
|
|
14818
|
+
opencodeBaseUrl: clickUpWebhookValidation.config.opencode?.baseUrl,
|
|
14819
|
+
fetchImpl: input.fetch,
|
|
14820
|
+
directDelivery: "steer"
|
|
14821
|
+
});
|
|
14822
|
+
applied.push("session_prompt");
|
|
14823
|
+
} catch (error) {
|
|
14824
|
+
errors.push(`session_prompt:${error.message}`);
|
|
14825
|
+
}
|
|
14826
|
+
} else {
|
|
14827
|
+
errors.push("session_prompt:missing_session_or_client");
|
|
14828
|
+
}
|
|
14829
|
+
return { ok: errors.length === 0, taskId, sessionId, directory, applied, errors };
|
|
14830
|
+
};
|
|
14831
|
+
const persistQaState = async (provider, state, transitions = {}) => {
|
|
14832
|
+
const written = writeQaQueueState(state, qaQueueStatePath(provider));
|
|
14833
|
+
const notifications = [];
|
|
14834
|
+
const promoted = transitions.promoted || null;
|
|
14835
|
+
if (promoted?.clickupTaskId) notifications.push(await wakePromotedQaTask(promoted, { provider, reason: transitions.reason || "qa_slot_available" }));
|
|
14836
|
+
return { state: written, notifications };
|
|
14837
|
+
};
|
|
14838
|
+
const scheduleQaQueueReaper = () => {
|
|
14839
|
+
if (input.startQaQueueScheduler === false) return;
|
|
14840
|
+
const provider = qaProvider || DEFAULT_QA_PROVIDER;
|
|
14841
|
+
const schedulerKey = `${provider}:${clickUpWebhookValidation.config?.basePath || worktree}`;
|
|
14842
|
+
if (activeQaQueueSchedulers.has(schedulerKey)) return;
|
|
14843
|
+
const interval = setInterval(async () => {
|
|
14844
|
+
try {
|
|
14845
|
+
const statePath = qaQueueStatePath(provider);
|
|
14846
|
+
const current = readQaQueueState(provider, statePath);
|
|
14847
|
+
const result = releaseExpiredQaSlot(current, { leaseMs: qaLeaseMs });
|
|
14848
|
+
if (result.expired || result.promoted) {
|
|
14849
|
+
await persistQaState(provider, result.state, { promoted: result.promoted, reason: result.expired ? "previous_slot_expired" : "qa_slot_available" });
|
|
14850
|
+
}
|
|
14851
|
+
} catch (error) {
|
|
14852
|
+
appendClickUpWebhookLocalLog(worktree, { type: "qa_queue_reaper_failed", provider, message: error.message });
|
|
14853
|
+
}
|
|
14854
|
+
}, Math.min(6e4, Math.max(1e4, Math.floor(qaLeaseMs / 5))));
|
|
14855
|
+
interval.unref?.();
|
|
14856
|
+
activeQaQueueSchedulers.set(schedulerKey, interval);
|
|
14857
|
+
};
|
|
14858
|
+
scheduleQaQueueReaper();
|
|
14343
14859
|
const tools = {
|
|
14344
14860
|
optima_init: tool({
|
|
14345
14861
|
description: "Initialize the Optima workflow and CodeMap in the current repository",
|
|
@@ -14356,10 +14872,10 @@ Evidencia: ${evidencePath}` : ""
|
|
|
14356
14872
|
}
|
|
14357
14873
|
migrateLegacyOptimaLayout(toolWorktree);
|
|
14358
14874
|
const cfgDir = optimaConfigDir(toolWorktree);
|
|
14359
|
-
if (!
|
|
14360
|
-
const optimaTmplPath =
|
|
14361
|
-
const codemapTmplPath =
|
|
14362
|
-
if (!
|
|
14875
|
+
if (!fs19.existsSync(cfgDir)) fs19.mkdirSync(cfgDir, { recursive: true });
|
|
14876
|
+
const optimaTmplPath = path20.join(TEMPLATES_DIR, "optima.yaml.template");
|
|
14877
|
+
const codemapTmplPath = path20.join(TEMPLATES_DIR, "codemap.yml.template");
|
|
14878
|
+
if (!fs19.existsSync(optimaTmplPath) || !fs19.existsSync(codemapTmplPath)) {
|
|
14363
14879
|
return "Error: Initialization templates not found in plugin.";
|
|
14364
14880
|
}
|
|
14365
14881
|
scaffoldOptimaConfig(toolWorktree, requestedTeamMode);
|
|
@@ -14480,14 +14996,14 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14480
14996
|
async execute(args, context) {
|
|
14481
14997
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14482
14998
|
if (!safe.ok) return safe.message;
|
|
14483
|
-
const summaryPath =
|
|
14484
|
-
if (!
|
|
14485
|
-
const taskPath = args.task_path ?
|
|
14999
|
+
const summaryPath = path20.resolve(safe.worktree, args.summary_path || "");
|
|
15000
|
+
if (!fs19.existsSync(summaryPath)) return `FAIL: summary_path not found: ${summaryPath}`;
|
|
15001
|
+
const taskPath = args.task_path ? path20.resolve(safe.worktree, args.task_path) : "";
|
|
14486
15002
|
const payload = buildClickUpSummaryPayload({
|
|
14487
|
-
summaryMarkdown:
|
|
14488
|
-
summaryPath:
|
|
14489
|
-
taskMarkdown: taskPath &&
|
|
14490
|
-
taskPath: taskPath ?
|
|
15003
|
+
summaryMarkdown: fs19.readFileSync(summaryPath, "utf8"),
|
|
15004
|
+
summaryPath: path20.relative(safe.worktree, summaryPath),
|
|
15005
|
+
taskMarkdown: taskPath && fs19.existsSync(taskPath) ? fs19.readFileSync(taskPath, "utf8") : "",
|
|
15006
|
+
taskPath: taskPath ? path20.relative(safe.worktree, taskPath) : "",
|
|
14491
15007
|
branch: args.branch,
|
|
14492
15008
|
worktree: args.worktree,
|
|
14493
15009
|
pr: args.pr
|
|
@@ -14580,6 +15096,116 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14580
15096
|
}
|
|
14581
15097
|
}
|
|
14582
15098
|
}),
|
|
15099
|
+
optima_qa_browser_status: tool({
|
|
15100
|
+
description: "Inspect the shared Optima QA browser and queue state for a provider such as chatgpt.",
|
|
15101
|
+
args: {
|
|
15102
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15103
|
+
clickup_task_id: tool.schema.string().optional().describe("Optional ClickUp task id to identify its QA tab."),
|
|
15104
|
+
cdp_url: tool.schema.string().optional().describe("Optional Chrome DevTools URL. Defaults to configured QA URL or http://127.0.0.1:9222.")
|
|
15105
|
+
},
|
|
15106
|
+
async execute(args = {}) {
|
|
15107
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15108
|
+
const state = readQaQueueState(provider);
|
|
15109
|
+
const browser = await qaBrowserStatus({ provider, cdpUrl: args.cdp_url || qaCdpUrl, clickupTaskId: args.clickup_task_id || "" });
|
|
15110
|
+
return JSON.stringify({ ok: browser.ok !== false, provider, leaseMs: qaLeaseMs, queue: state, browser }, null, 2);
|
|
15111
|
+
}
|
|
15112
|
+
}),
|
|
15113
|
+
optima_qa_request_slot: tool({
|
|
15114
|
+
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.",
|
|
15115
|
+
args: {
|
|
15116
|
+
clickup_task_id: tool.schema.string().describe("ClickUp task id requesting the QA slot"),
|
|
15117
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15118
|
+
session_id: tool.schema.string().optional().describe("Optional OpenCode session id to wake when the slot is granted later"),
|
|
15119
|
+
worktree: tool.schema.string().optional().describe("Optional task worktree/directory for session wakeups"),
|
|
15120
|
+
reason: tool.schema.string().optional().describe("Short reason/scenario for requesting the QA slot")
|
|
15121
|
+
},
|
|
15122
|
+
async execute(args = {}) {
|
|
15123
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15124
|
+
const pathToState = qaQueueStatePath(provider);
|
|
15125
|
+
const current = readQaQueueState(provider, pathToState);
|
|
15126
|
+
const result = requestQaSlot(current, {
|
|
15127
|
+
clickupTaskId: args.clickup_task_id,
|
|
15128
|
+
sessionId: args.session_id,
|
|
15129
|
+
worktree: args.worktree,
|
|
15130
|
+
reason: args.reason,
|
|
15131
|
+
leaseMs: qaLeaseMs
|
|
15132
|
+
});
|
|
15133
|
+
let queuedClickUp = null;
|
|
15134
|
+
if (result.action === "queued") queuedClickUp = await applyQaQueuedClickUpState(args.clickup_task_id);
|
|
15135
|
+
const persisted = await persistQaState(provider, result.state, { promoted: result.promoted, reason: result.expired ? "previous_slot_expired" : "qa_slot_available" });
|
|
15136
|
+
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.";
|
|
15137
|
+
return JSON.stringify({ ...result, state: persisted.state, notifications: persisted.notifications, queuedClickUp, guidance }, null, 2);
|
|
15138
|
+
}
|
|
15139
|
+
}),
|
|
15140
|
+
optima_qa_chrome_command: tool({
|
|
15141
|
+
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.",
|
|
15142
|
+
args: {
|
|
15143
|
+
clickup_task_id: tool.schema.string().describe("ClickUp task id that currently owns the QA slot"),
|
|
15144
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15145
|
+
command_json: tool.schema.string().describe("JSON command object, or raw async JS script. Actions: status, goto, evaluate, screenshot, set_input_files, script."),
|
|
15146
|
+
cdp_url: tool.schema.string().optional().describe("Optional Chrome DevTools URL"),
|
|
15147
|
+
start_url: tool.schema.string().optional().describe("Initial URL for a new QA tab. Defaults to https://chatgpt.com/")
|
|
15148
|
+
},
|
|
15149
|
+
async execute(args = {}) {
|
|
15150
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15151
|
+
const pathToState = qaQueueStatePath(provider);
|
|
15152
|
+
const current = readQaQueueState(provider, pathToState);
|
|
15153
|
+
const touched = touchQaSlot(current, { clickupTaskId: args.clickup_task_id, leaseMs: qaLeaseMs });
|
|
15154
|
+
const persisted = await persistQaState(provider, touched.state, { promoted: touched.promoted, reason: touched.expired ? "previous_slot_expired" : "qa_slot_available" });
|
|
15155
|
+
if (!touched.ok) {
|
|
15156
|
+
return JSON.stringify({
|
|
15157
|
+
ok: false,
|
|
15158
|
+
action: touched.action,
|
|
15159
|
+
active: persisted.state.active,
|
|
15160
|
+
queue: persisted.state.queue,
|
|
15161
|
+
notifications: persisted.notifications,
|
|
15162
|
+
guidance: "You do not own the QA slot. Call optima_qa_request_slot and stop if queued."
|
|
15163
|
+
}, null, 2);
|
|
15164
|
+
}
|
|
15165
|
+
try {
|
|
15166
|
+
const result = await qaRunChromeCommand({
|
|
15167
|
+
provider,
|
|
15168
|
+
cdpUrl: args.cdp_url || qaCdpUrl,
|
|
15169
|
+
clickupTaskId: args.clickup_task_id,
|
|
15170
|
+
commandJson: args.command_json,
|
|
15171
|
+
startUrl: args.start_url || "https://chatgpt.com/"
|
|
15172
|
+
});
|
|
15173
|
+
return JSON.stringify({ ok: true, lease: persisted.state.active, notifications: persisted.notifications, chrome: result }, null, 2);
|
|
15174
|
+
} catch (error) {
|
|
15175
|
+
return JSON.stringify({ ok: false, error: error.message, lease: persisted.state.active, notifications: persisted.notifications }, null, 2);
|
|
15176
|
+
}
|
|
15177
|
+
}
|
|
15178
|
+
}),
|
|
15179
|
+
optima_qa_finish: tool({
|
|
15180
|
+
description: "Finish the active QA browser slot, reset/reload the extension, release the slot, and wake the next queued ClickUp task if any.",
|
|
15181
|
+
args: {
|
|
15182
|
+
clickup_task_id: tool.schema.string().describe("ClickUp task id finishing QA"),
|
|
15183
|
+
provider: tool.schema.string().optional().describe("QA provider. Defaults to chatgpt."),
|
|
15184
|
+
cdp_url: tool.schema.string().optional().describe("Optional Chrome DevTools URL"),
|
|
15185
|
+
extension_path: tool.schema.string().optional().describe("Optional extension path to sync into the stable QA extension-under-test directory before reset/reload"),
|
|
15186
|
+
extension_id: tool.schema.string().optional().describe("Optional Chrome extension id when multiple extension service workers exist"),
|
|
15187
|
+
reset_extension: tool.schema.boolean().optional().describe("Defaults true. Clear extension storage/caches/IndexedDB."),
|
|
15188
|
+
reload_extension: tool.schema.boolean().optional().describe("Defaults true. Reload the extension service worker after reset.")
|
|
15189
|
+
},
|
|
15190
|
+
async execute(args = {}) {
|
|
15191
|
+
const provider = String(args.provider || qaProvider || DEFAULT_QA_PROVIDER);
|
|
15192
|
+
const reset = args.reset_extension !== false;
|
|
15193
|
+
const reload = args.reload_extension !== false;
|
|
15194
|
+
const extension = await qaPrepareExtension({
|
|
15195
|
+
provider,
|
|
15196
|
+
cdpUrl: args.cdp_url || qaCdpUrl,
|
|
15197
|
+
extensionPath: args.extension_path || "",
|
|
15198
|
+
extensionId: args.extension_id || "",
|
|
15199
|
+
reset,
|
|
15200
|
+
reload
|
|
15201
|
+
}).catch((error) => ({ ok: false, error: error.message }));
|
|
15202
|
+
const pathToState = qaQueueStatePath(provider);
|
|
15203
|
+
const current = readQaQueueState(provider, pathToState);
|
|
15204
|
+
const result = finishQaSlot(current, { clickupTaskId: args.clickup_task_id, leaseMs: qaLeaseMs });
|
|
15205
|
+
const persisted = await persistQaState(provider, result.state, { promoted: result.promoted, reason: "previous_finished" });
|
|
15206
|
+
return JSON.stringify({ ...result, state: persisted.state, notifications: persisted.notifications, extension }, null, 2);
|
|
15207
|
+
}
|
|
15208
|
+
}),
|
|
14583
15209
|
optima_clickup_create_subtasks: tool({
|
|
14584
15210
|
description: "Generate dry-run ClickUp subtask creation payloads from a strict Markdown ## Subtasks section",
|
|
14585
15211
|
args: {
|
|
@@ -14592,12 +15218,12 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14592
15218
|
async execute(args, context) {
|
|
14593
15219
|
const safe = safeWorktreeOrFailure(context, worktree);
|
|
14594
15220
|
if (!safe.ok) return safe.message;
|
|
14595
|
-
const markdownPath =
|
|
14596
|
-
if (!
|
|
15221
|
+
const markdownPath = path20.resolve(safe.worktree, args.markdown_path || "");
|
|
15222
|
+
if (!fs19.existsSync(markdownPath)) return `FAIL: markdown_path not found: ${markdownPath}`;
|
|
14597
15223
|
const payload = buildClickUpCreateSubtasksPayload({
|
|
14598
15224
|
parentTaskId: args.parent_task_id,
|
|
14599
|
-
markdown:
|
|
14600
|
-
sourcePath:
|
|
15225
|
+
markdown: fs19.readFileSync(markdownPath, "utf8"),
|
|
15226
|
+
sourcePath: path20.relative(safe.worktree, markdownPath),
|
|
14601
15227
|
parentBranch: args.parent_branch,
|
|
14602
15228
|
parentTaskType: args.parent_task_type || "Tarea",
|
|
14603
15229
|
apply: String(args.apply || "").toLowerCase() === "true"
|
|
@@ -14967,7 +15593,7 @@ Backfilled messages: ${backfilled}`;
|
|
|
14967
15593
|
if (!existing) {
|
|
14968
15594
|
return "FAIL: No active discussion exists for this session.";
|
|
14969
15595
|
}
|
|
14970
|
-
const discussionPath =
|
|
15596
|
+
const discussionPath = path20.join(toolWorktree, existing.transcriptPath);
|
|
14971
15597
|
setDiscussionStatus(discussionPath, "summarizing");
|
|
14972
15598
|
existing.status = "summarizing";
|
|
14973
15599
|
saveDiscussionRegistry(toolWorktree, toolRegistry);
|
|
@@ -15019,7 +15645,7 @@ Reason: ${err.message}`;
|
|
|
15019
15645
|
try {
|
|
15020
15646
|
const sessionResult = await client.session.create({
|
|
15021
15647
|
query: { directory: workflowDirectory },
|
|
15022
|
-
body: { title: `Workflow Run: ${
|
|
15648
|
+
body: { title: `Workflow Run: ${path20.basename(workflowTaskPath)}` }
|
|
15023
15649
|
});
|
|
15024
15650
|
const sessionId = sessionResult.data.id;
|
|
15025
15651
|
activeWorkflows.set(sessionId, { pmaSessionId, taskPath: workflowTaskPath, track: workflowTrack, directory: workflowDirectory });
|
|
@@ -15203,14 +15829,14 @@ function usage() {
|
|
|
15203
15829
|
].join("\n");
|
|
15204
15830
|
}
|
|
15205
15831
|
function expandHome(inputPath) {
|
|
15206
|
-
if (!inputPath || inputPath === "~") return
|
|
15207
|
-
if (inputPath.startsWith("~/")) return
|
|
15832
|
+
if (!inputPath || inputPath === "~") return os7.homedir();
|
|
15833
|
+
if (inputPath.startsWith("~/")) return path21.join(os7.homedir(), inputPath.slice(2));
|
|
15208
15834
|
return inputPath;
|
|
15209
15835
|
}
|
|
15210
15836
|
function resolveOpenCodeDbPath(inputPath = null) {
|
|
15211
15837
|
const expanded = expandHome(inputPath || process.env.OPTIMA_OPENCODE_DB_PATH || DEFAULT_DB_PATH);
|
|
15212
15838
|
try {
|
|
15213
|
-
if (
|
|
15839
|
+
if (fs20.existsSync(expanded) && fs20.statSync(expanded).isDirectory()) return path21.join(expanded, "opencode.db");
|
|
15214
15840
|
} catch {
|
|
15215
15841
|
return expanded;
|
|
15216
15842
|
}
|
|
@@ -15274,12 +15900,12 @@ function extractOpenedPathValue(value) {
|
|
|
15274
15900
|
}
|
|
15275
15901
|
function normalizePathCandidate(candidate) {
|
|
15276
15902
|
const expanded = expandHome(candidate);
|
|
15277
|
-
if (!
|
|
15278
|
-
return
|
|
15903
|
+
if (!path21.isAbsolute(expanded)) return null;
|
|
15904
|
+
return path21.resolve(expanded);
|
|
15279
15905
|
}
|
|
15280
15906
|
function discoverOpenCodePaths(dbPath) {
|
|
15281
15907
|
const resolvedDb = resolveOpenCodeDbPath(dbPath);
|
|
15282
|
-
if (!
|
|
15908
|
+
if (!fs20.existsSync(resolvedDb)) throw new Error(`OpenCode database not found: ${resolvedDb}`);
|
|
15283
15909
|
const tables = sqliteJson(resolvedDb, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name");
|
|
15284
15910
|
const discovered = /* @__PURE__ */ new Set();
|
|
15285
15911
|
for (const table of tables) {
|
|
@@ -15304,27 +15930,27 @@ function discoverOpenCodePaths(dbPath) {
|
|
|
15304
15930
|
function collectMarkdownOverridesFrom(baseDir) {
|
|
15305
15931
|
const files = [];
|
|
15306
15932
|
for (const dirName of OVERRIDE_DIRS) {
|
|
15307
|
-
const dirPath =
|
|
15308
|
-
if (!
|
|
15309
|
-
for (const entry of
|
|
15933
|
+
const dirPath = path21.join(baseDir, dirName);
|
|
15934
|
+
if (!fs20.existsSync(dirPath)) continue;
|
|
15935
|
+
for (const entry of fs20.readdirSync(dirPath, { withFileTypes: true })) {
|
|
15310
15936
|
if (!entry.isFile()) continue;
|
|
15311
15937
|
if (!entry.name.endsWith(".md")) continue;
|
|
15312
15938
|
if (entry.name.toLowerCase() === "readme.md") continue;
|
|
15313
|
-
files.push(
|
|
15939
|
+
files.push(path21.join(dirPath, entry.name));
|
|
15314
15940
|
}
|
|
15315
15941
|
}
|
|
15316
15942
|
return files;
|
|
15317
15943
|
}
|
|
15318
15944
|
function collectOverrideFiles(worktree) {
|
|
15319
|
-
return collectMarkdownOverridesFrom(
|
|
15945
|
+
return collectMarkdownOverridesFrom(path21.join(worktree, ".optima")).sort();
|
|
15320
15946
|
}
|
|
15321
15947
|
function collectPlannedOverrideFiles(worktree) {
|
|
15322
15948
|
return [
|
|
15323
15949
|
...collectOverrideFiles(worktree),
|
|
15324
|
-
...collectMarkdownOverridesFrom(
|
|
15325
|
-
...collectMarkdownOverridesFrom(
|
|
15326
|
-
...collectMarkdownOverridesFrom(
|
|
15327
|
-
...collectMarkdownOverridesFrom(
|
|
15950
|
+
...collectMarkdownOverridesFrom(path21.join(worktree, ".staticeng")),
|
|
15951
|
+
...collectMarkdownOverridesFrom(path21.join(worktree, ".orbita")),
|
|
15952
|
+
...collectMarkdownOverridesFrom(path21.join(worktree, ".nomadwork")),
|
|
15953
|
+
...collectMarkdownOverridesFrom(path21.join(worktree, ".nomadworks"))
|
|
15328
15954
|
].sort();
|
|
15329
15955
|
}
|
|
15330
15956
|
var CRC_TABLE = (() => {
|
|
@@ -15357,14 +15983,14 @@ function writeUInt16LE(value) {
|
|
|
15357
15983
|
return buffer;
|
|
15358
15984
|
}
|
|
15359
15985
|
function createZipBackup(files, backupPath) {
|
|
15360
|
-
|
|
15986
|
+
fs20.mkdirSync(path21.dirname(backupPath), { recursive: true });
|
|
15361
15987
|
const localParts = [];
|
|
15362
15988
|
const centralParts = [];
|
|
15363
15989
|
let offset = 0;
|
|
15364
15990
|
const now = dosTimeDate();
|
|
15365
15991
|
for (const filePath of files) {
|
|
15366
|
-
const data =
|
|
15367
|
-
const name =
|
|
15992
|
+
const data = fs20.readFileSync(filePath);
|
|
15993
|
+
const name = path21.resolve(filePath).replace(/^\//, "").split(path21.sep).join("/");
|
|
15368
15994
|
const nameBuffer = Buffer.from(name, "utf8");
|
|
15369
15995
|
const crc = crc32(data);
|
|
15370
15996
|
const local = Buffer.concat([
|
|
@@ -15416,7 +16042,7 @@ function createZipBackup(files, backupPath) {
|
|
|
15416
16042
|
writeUInt32LE(offset),
|
|
15417
16043
|
writeUInt16LE(0)
|
|
15418
16044
|
]);
|
|
15419
|
-
|
|
16045
|
+
fs20.writeFileSync(backupPath, Buffer.concat([...localParts, ...centralParts, end]));
|
|
15420
16046
|
}
|
|
15421
16047
|
function timestamp() {
|
|
15422
16048
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
@@ -15426,17 +16052,17 @@ function validationStatus(result) {
|
|
|
15426
16052
|
return result.ok ? "passed" : "failed";
|
|
15427
16053
|
}
|
|
15428
16054
|
function hasLegacyMarker(worktree) {
|
|
15429
|
-
return
|
|
16055
|
+
return fs20.existsSync(path21.join(worktree, ".staticeng")) || fs20.existsSync(path21.join(worktree, ".orbita")) || fs20.existsSync(path21.join(worktree, ".nomadwork")) || fs20.existsSync(path21.join(worktree, ".nomadworks"));
|
|
15430
16056
|
}
|
|
15431
16057
|
function rootOptimaArtifacts(worktree) {
|
|
15432
16058
|
const artifacts = [
|
|
15433
|
-
["tasks/",
|
|
15434
|
-
["evidences/",
|
|
15435
|
-
["docs/scrs/",
|
|
15436
|
-
["codemap.yml",
|
|
15437
|
-
["codemap.yaml",
|
|
16059
|
+
["tasks/", path21.join(worktree, "tasks")],
|
|
16060
|
+
["evidences/", path21.join(worktree, "evidences")],
|
|
16061
|
+
["docs/scrs/", path21.join(worktree, "docs", "scrs")],
|
|
16062
|
+
["codemap.yml", path21.join(worktree, "codemap.yml")],
|
|
16063
|
+
["codemap.yaml", path21.join(worktree, "codemap.yaml")]
|
|
15438
16064
|
];
|
|
15439
|
-
return artifacts.map(([display, artifactPath]) => ({ display, path: artifactPath })).filter((item) =>
|
|
16065
|
+
return artifacts.map(([display, artifactPath]) => ({ display, path: artifactPath })).filter((item) => fs20.existsSync(item.path));
|
|
15440
16066
|
}
|
|
15441
16067
|
function hasSanitizableOverrides(worktree) {
|
|
15442
16068
|
return collectPlannedOverrideFiles(worktree).length > 0;
|
|
@@ -15462,7 +16088,7 @@ function planOptimaMigration(worktree, dryRun) {
|
|
|
15462
16088
|
...overrides.map((file) => ({
|
|
15463
16089
|
category: "override",
|
|
15464
16090
|
action: dryRun ? "would_remove_after_backup" : "removed_after_backup",
|
|
15465
|
-
path:
|
|
16091
|
+
path: path21.relative(worktree, file).split(path21.sep).join("/"),
|
|
15466
16092
|
detail: "Remove implicit agent/policy override after host-level backup."
|
|
15467
16093
|
}))
|
|
15468
16094
|
],
|
|
@@ -15483,12 +16109,12 @@ async function sanitizeHost(options = {}) {
|
|
|
15483
16109
|
const repoStates = [];
|
|
15484
16110
|
for (const candidate of paths) {
|
|
15485
16111
|
try {
|
|
15486
|
-
if (!
|
|
16112
|
+
if (!fs20.existsSync(candidate)) {
|
|
15487
16113
|
report.totals.skipped += 1;
|
|
15488
16114
|
report.repos.push({ path: candidate, status: "skipped", reason: "missing" });
|
|
15489
16115
|
continue;
|
|
15490
16116
|
}
|
|
15491
|
-
if (!
|
|
16117
|
+
if (!fs20.statSync(candidate).isDirectory()) {
|
|
15492
16118
|
report.totals.skipped += 1;
|
|
15493
16119
|
report.repos.push({ path: candidate, status: "skipped", reason: "not_directory" });
|
|
15494
16120
|
continue;
|
|
@@ -15510,7 +16136,7 @@ async function sanitizeHost(options = {}) {
|
|
|
15510
16136
|
const overrides = repoStates.flatMap((state) => state.overrideFiles.map((file) => ({ repo: state.repo, file })));
|
|
15511
16137
|
report.totals.overrideFiles = overrides.length;
|
|
15512
16138
|
if (!dryRun && overrides.length > 0) {
|
|
15513
|
-
const backupPath =
|
|
16139
|
+
const backupPath = path21.join(os7.homedir(), BACKUP_DIRNAME, `${timestamp()}.zip`);
|
|
15514
16140
|
try {
|
|
15515
16141
|
createZipBackup(overrides.map((item) => item.file), backupPath);
|
|
15516
16142
|
report.backupPath = backupPath;
|
|
@@ -15526,7 +16152,7 @@ async function sanitizeHost(options = {}) {
|
|
|
15526
16152
|
for (const state of repoStates) {
|
|
15527
16153
|
try {
|
|
15528
16154
|
if (!dryRun) {
|
|
15529
|
-
for (const file of state.overrideFiles)
|
|
16155
|
+
for (const file of state.overrideFiles) fs20.rmSync(file, { force: true });
|
|
15530
16156
|
}
|
|
15531
16157
|
const validation = !dryRun && hasLegacyMarker(state.repo) ? await optima_validate_logic(state.repo) : null;
|
|
15532
16158
|
const status = (!validation || validation.ok) && state.plan.unresolved.length === 0 ? "repaired" : "failed";
|
|
@@ -15539,7 +16165,7 @@ async function sanitizeHost(options = {}) {
|
|
|
15539
16165
|
repairActions: state.plan.actions.length,
|
|
15540
16166
|
repairSkipped: state.plan.skippedRepair === true,
|
|
15541
16167
|
overrideFiles: state.overrideFiles.length,
|
|
15542
|
-
overridePaths: state.overrideFiles.map((file) =>
|
|
16168
|
+
overridePaths: state.overrideFiles.map((file) => path21.relative(state.repo, file).split(path21.sep).join("/")),
|
|
15543
16169
|
validation: validation ? validationStatus(validation) : "skipped",
|
|
15544
16170
|
unresolved: state.plan.unresolved
|
|
15545
16171
|
});
|