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