cc-codeconductor 0.2.10 → 0.3.1

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.
Files changed (63) hide show
  1. package/README.md +5 -4
  2. package/dist/index.js +1636 -1078
  3. package/package.json +1 -1
  4. package/presets/agy/AGENTS.md +46 -2
  5. package/presets/claude/CLAUDE.md +62 -2
  6. package/presets/claude/skills/android/SKILL.md +119 -0
  7. package/presets/claude/skills/laravel-specialist/SKILL.md +264 -0
  8. package/presets/claude/skills/laravel-specialist/references/eloquent.md +351 -0
  9. package/presets/claude/skills/laravel-specialist/references/livewire.md +512 -0
  10. package/presets/claude/skills/laravel-specialist/references/queues.md +423 -0
  11. package/presets/claude/skills/laravel-specialist/references/routing.md +362 -0
  12. package/presets/claude/skills/laravel-specialist/references/testing.md +522 -0
  13. package/presets/claude/skills/php-pro/SKILL.md +208 -0
  14. package/presets/claude/skills/php-pro/references/async-patterns.md +412 -0
  15. package/presets/claude/skills/php-pro/references/laravel-patterns.md +377 -0
  16. package/presets/claude/skills/php-pro/references/modern-php-features.md +323 -0
  17. package/presets/claude/skills/php-pro/references/symfony-patterns.md +466 -0
  18. package/presets/claude/skills/php-pro/references/testing-quality.md +466 -0
  19. package/presets/codex/AGENTS.md +47 -2
  20. package/presets/codex/skills/android/SKILL.md +119 -0
  21. package/presets/codex/skills/laravel-specialist/SKILL.md +264 -0
  22. package/presets/codex/skills/laravel-specialist/references/eloquent.md +351 -0
  23. package/presets/codex/skills/laravel-specialist/references/livewire.md +512 -0
  24. package/presets/codex/skills/laravel-specialist/references/queues.md +423 -0
  25. package/presets/codex/skills/laravel-specialist/references/routing.md +362 -0
  26. package/presets/codex/skills/laravel-specialist/references/testing.md +522 -0
  27. package/presets/codex/skills/php-pro/SKILL.md +208 -0
  28. package/presets/codex/skills/php-pro/references/async-patterns.md +412 -0
  29. package/presets/codex/skills/php-pro/references/laravel-patterns.md +377 -0
  30. package/presets/codex/skills/php-pro/references/modern-php-features.md +323 -0
  31. package/presets/codex/skills/php-pro/references/symfony-patterns.md +466 -0
  32. package/presets/codex/skills/php-pro/references/testing-quality.md +466 -0
  33. package/presets/opencode/agents/implementer.md +2 -2
  34. package/presets/opencode/agents/orchestrator.md +132 -5
  35. package/presets/opencode/agents/reviewer.md +33 -0
  36. package/presets/opencode/prompts/v0.3.0/architect.md +221 -0
  37. package/presets/opencode/prompts/v0.3.0/docs.md +189 -0
  38. package/presets/opencode/prompts/v0.3.0/implementer.md +162 -0
  39. package/presets/opencode/prompts/v0.3.0/orchestrator.md +360 -0
  40. package/presets/opencode/prompts/v0.3.0/repo-explorer.md +110 -0
  41. package/presets/opencode/prompts/v0.3.0/reviewer.md +225 -0
  42. package/presets/opencode/prompts/v0.3.0/task-coach.md +155 -0
  43. package/presets/opencode/prompts/v0.3.0/tester.md +251 -0
  44. package/presets/opencode/skills/android/SKILL.md +119 -0
  45. package/presets/opencode/skills/laravel-specialist/SKILL.md +264 -0
  46. package/presets/opencode/skills/laravel-specialist/references/eloquent.md +351 -0
  47. package/presets/opencode/skills/laravel-specialist/references/livewire.md +512 -0
  48. package/presets/opencode/skills/laravel-specialist/references/queues.md +423 -0
  49. package/presets/opencode/skills/laravel-specialist/references/routing.md +362 -0
  50. package/presets/opencode/skills/laravel-specialist/references/testing.md +522 -0
  51. package/presets/opencode/skills/php-pro/SKILL.md +208 -0
  52. package/presets/opencode/skills/php-pro/references/async-patterns.md +412 -0
  53. package/presets/opencode/skills/php-pro/references/laravel-patterns.md +377 -0
  54. package/presets/opencode/skills/php-pro/references/modern-php-features.md +323 -0
  55. package/presets/opencode/skills/php-pro/references/symfony-patterns.md +466 -0
  56. package/presets/opencode/skills/php-pro/references/testing-quality.md +466 -0
  57. package/src/presets/manifests/agy.yml +3 -2
  58. package/src/presets/manifests/claude.yml +3 -2
  59. package/src/presets/manifests/codex.yml +3 -2
  60. package/src/presets/manifests/cursor.yml +3 -2
  61. package/src/presets/manifests/gemini.yml +3 -2
  62. package/src/presets/manifests/opencode.yml +3 -2
  63. package/src/presets/models/opencode.yml +6 -6
package/dist/index.js CHANGED
@@ -77,24 +77,24 @@ var require_identity = __commonJS((exports) => {
77
77
  var SCALAR = Symbol.for("yaml.scalar");
78
78
  var SEQ = Symbol.for("yaml.seq");
79
79
  var NODE_TYPE = Symbol.for("yaml.node.type");
80
- var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
81
- var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
82
- var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
83
- var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
84
- var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR;
85
- var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ;
86
- function isCollection(node) {
87
- if (node && typeof node === "object")
88
- switch (node[NODE_TYPE]) {
80
+ var isAlias = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === ALIAS;
81
+ var isDocument = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === DOC;
82
+ var isMap = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === MAP;
83
+ var isPair = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === PAIR;
84
+ var isScalar = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === SCALAR;
85
+ var isSeq = (node2) => !!node2 && typeof node2 === "object" && node2[NODE_TYPE] === SEQ;
86
+ function isCollection(node2) {
87
+ if (node2 && typeof node2 === "object")
88
+ switch (node2[NODE_TYPE]) {
89
89
  case MAP:
90
90
  case SEQ:
91
91
  return true;
92
92
  }
93
93
  return false;
94
94
  }
95
- function isNode(node) {
96
- if (node && typeof node === "object")
97
- switch (node[NODE_TYPE]) {
95
+ function isNode(node2) {
96
+ if (node2 && typeof node2 === "object")
97
+ switch (node2[NODE_TYPE]) {
98
98
  case ALIAS:
99
99
  case MAP:
100
100
  case SCALAR:
@@ -103,7 +103,7 @@ var require_identity = __commonJS((exports) => {
103
103
  }
104
104
  return false;
105
105
  }
106
- var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
106
+ var hasAnchor = (node2) => (isScalar(node2) || isCollection(node2)) && !!node2.anchor;
107
107
  exports.ALIAS = ALIAS;
108
108
  exports.DOC = DOC;
109
109
  exports.MAP = MAP;
@@ -128,98 +128,98 @@ var require_visit = __commonJS((exports) => {
128
128
  var BREAK = Symbol("break visit");
129
129
  var SKIP = Symbol("skip children");
130
130
  var REMOVE = Symbol("remove node");
131
- function visit(node, visitor) {
131
+ function visit(node2, visitor) {
132
132
  const visitor_ = initVisitor(visitor);
133
- if (identity.isDocument(node)) {
134
- const cd = visit_(null, node.contents, visitor_, Object.freeze([node]));
133
+ if (identity.isDocument(node2)) {
134
+ const cd = visit_(null, node2.contents, visitor_, Object.freeze([node2]));
135
135
  if (cd === REMOVE)
136
- node.contents = null;
136
+ node2.contents = null;
137
137
  } else
138
- visit_(null, node, visitor_, Object.freeze([]));
138
+ visit_(null, node2, visitor_, Object.freeze([]));
139
139
  }
140
140
  visit.BREAK = BREAK;
141
141
  visit.SKIP = SKIP;
142
142
  visit.REMOVE = REMOVE;
143
- function visit_(key, node, visitor, path) {
144
- const ctrl = callVisitor(key, node, visitor, path);
143
+ function visit_(key, node2, visitor, path) {
144
+ const ctrl = callVisitor(key, node2, visitor, path);
145
145
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
146
146
  replaceNode(key, path, ctrl);
147
147
  return visit_(key, ctrl, visitor, path);
148
148
  }
149
149
  if (typeof ctrl !== "symbol") {
150
- if (identity.isCollection(node)) {
151
- path = Object.freeze(path.concat(node));
152
- for (let i = 0;i < node.items.length; ++i) {
153
- const ci = visit_(i, node.items[i], visitor, path);
150
+ if (identity.isCollection(node2)) {
151
+ path = Object.freeze(path.concat(node2));
152
+ for (let i = 0;i < node2.items.length; ++i) {
153
+ const ci = visit_(i, node2.items[i], visitor, path);
154
154
  if (typeof ci === "number")
155
155
  i = ci - 1;
156
156
  else if (ci === BREAK)
157
157
  return BREAK;
158
158
  else if (ci === REMOVE) {
159
- node.items.splice(i, 1);
159
+ node2.items.splice(i, 1);
160
160
  i -= 1;
161
161
  }
162
162
  }
163
- } else if (identity.isPair(node)) {
164
- path = Object.freeze(path.concat(node));
165
- const ck = visit_("key", node.key, visitor, path);
163
+ } else if (identity.isPair(node2)) {
164
+ path = Object.freeze(path.concat(node2));
165
+ const ck = visit_("key", node2.key, visitor, path);
166
166
  if (ck === BREAK)
167
167
  return BREAK;
168
168
  else if (ck === REMOVE)
169
- node.key = null;
170
- const cv = visit_("value", node.value, visitor, path);
169
+ node2.key = null;
170
+ const cv = visit_("value", node2.value, visitor, path);
171
171
  if (cv === BREAK)
172
172
  return BREAK;
173
173
  else if (cv === REMOVE)
174
- node.value = null;
174
+ node2.value = null;
175
175
  }
176
176
  }
177
177
  return ctrl;
178
178
  }
179
- async function visitAsync(node, visitor) {
179
+ async function visitAsync(node2, visitor) {
180
180
  const visitor_ = initVisitor(visitor);
181
- if (identity.isDocument(node)) {
182
- const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node]));
181
+ if (identity.isDocument(node2)) {
182
+ const cd = await visitAsync_(null, node2.contents, visitor_, Object.freeze([node2]));
183
183
  if (cd === REMOVE)
184
- node.contents = null;
184
+ node2.contents = null;
185
185
  } else
186
- await visitAsync_(null, node, visitor_, Object.freeze([]));
186
+ await visitAsync_(null, node2, visitor_, Object.freeze([]));
187
187
  }
188
188
  visitAsync.BREAK = BREAK;
189
189
  visitAsync.SKIP = SKIP;
190
190
  visitAsync.REMOVE = REMOVE;
191
- async function visitAsync_(key, node, visitor, path) {
192
- const ctrl = await callVisitor(key, node, visitor, path);
191
+ async function visitAsync_(key, node2, visitor, path) {
192
+ const ctrl = await callVisitor(key, node2, visitor, path);
193
193
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
194
194
  replaceNode(key, path, ctrl);
195
195
  return visitAsync_(key, ctrl, visitor, path);
196
196
  }
197
197
  if (typeof ctrl !== "symbol") {
198
- if (identity.isCollection(node)) {
199
- path = Object.freeze(path.concat(node));
200
- for (let i = 0;i < node.items.length; ++i) {
201
- const ci = await visitAsync_(i, node.items[i], visitor, path);
198
+ if (identity.isCollection(node2)) {
199
+ path = Object.freeze(path.concat(node2));
200
+ for (let i = 0;i < node2.items.length; ++i) {
201
+ const ci = await visitAsync_(i, node2.items[i], visitor, path);
202
202
  if (typeof ci === "number")
203
203
  i = ci - 1;
204
204
  else if (ci === BREAK)
205
205
  return BREAK;
206
206
  else if (ci === REMOVE) {
207
- node.items.splice(i, 1);
207
+ node2.items.splice(i, 1);
208
208
  i -= 1;
209
209
  }
210
210
  }
211
- } else if (identity.isPair(node)) {
212
- path = Object.freeze(path.concat(node));
213
- const ck = await visitAsync_("key", node.key, visitor, path);
211
+ } else if (identity.isPair(node2)) {
212
+ path = Object.freeze(path.concat(node2));
213
+ const ck = await visitAsync_("key", node2.key, visitor, path);
214
214
  if (ck === BREAK)
215
215
  return BREAK;
216
216
  else if (ck === REMOVE)
217
- node.key = null;
218
- const cv = await visitAsync_("value", node.value, visitor, path);
217
+ node2.key = null;
218
+ const cv = await visitAsync_("value", node2.value, visitor, path);
219
219
  if (cv === BREAK)
220
220
  return BREAK;
221
221
  else if (cv === REMOVE)
222
- node.value = null;
222
+ node2.value = null;
223
223
  }
224
224
  }
225
225
  return ctrl;
@@ -242,32 +242,32 @@ var require_visit = __commonJS((exports) => {
242
242
  }
243
243
  return visitor;
244
244
  }
245
- function callVisitor(key, node, visitor, path) {
245
+ function callVisitor(key, node2, visitor, path) {
246
246
  if (typeof visitor === "function")
247
- return visitor(key, node, path);
248
- if (identity.isMap(node))
249
- return visitor.Map?.(key, node, path);
250
- if (identity.isSeq(node))
251
- return visitor.Seq?.(key, node, path);
252
- if (identity.isPair(node))
253
- return visitor.Pair?.(key, node, path);
254
- if (identity.isScalar(node))
255
- return visitor.Scalar?.(key, node, path);
256
- if (identity.isAlias(node))
257
- return visitor.Alias?.(key, node, path);
247
+ return visitor(key, node2, path);
248
+ if (identity.isMap(node2))
249
+ return visitor.Map?.(key, node2, path);
250
+ if (identity.isSeq(node2))
251
+ return visitor.Seq?.(key, node2, path);
252
+ if (identity.isPair(node2))
253
+ return visitor.Pair?.(key, node2, path);
254
+ if (identity.isScalar(node2))
255
+ return visitor.Scalar?.(key, node2, path);
256
+ if (identity.isAlias(node2))
257
+ return visitor.Alias?.(key, node2, path);
258
258
  return;
259
259
  }
260
- function replaceNode(key, path, node) {
260
+ function replaceNode(key, path, node2) {
261
261
  const parent = path[path.length - 1];
262
262
  if (identity.isCollection(parent)) {
263
- parent.items[key] = node;
263
+ parent.items[key] = node2;
264
264
  } else if (identity.isPair(parent)) {
265
265
  if (key === "key")
266
- parent.key = node;
266
+ parent.key = node2;
267
267
  else
268
- parent.value = node;
268
+ parent.value = node2;
269
269
  } else if (identity.isDocument(parent)) {
270
- parent.contents = node;
270
+ parent.contents = node2;
271
271
  } else {
272
272
  const pt = identity.isAlias(parent) ? "alias" : "scalar";
273
273
  throw new Error(`Cannot replace node with ${pt} parent`);
@@ -407,9 +407,9 @@ var require_directives = __commonJS((exports) => {
407
407
  let tagNames;
408
408
  if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) {
409
409
  const tags = {};
410
- visit.visit(doc.contents, (_key, node) => {
411
- if (identity.isNode(node) && node.tag)
412
- tags[node.tag] = true;
410
+ visit.visit(doc.contents, (_key, node2) => {
411
+ if (identity.isNode(node2) && node2.tag)
412
+ tags[node2.tag] = true;
413
413
  });
414
414
  tagNames = Object.keys(tags);
415
415
  } else
@@ -444,9 +444,9 @@ var require_anchors = __commonJS((exports) => {
444
444
  function anchorNames(root) {
445
445
  const anchors = new Set;
446
446
  visit.visit(root, {
447
- Value(_key, node) {
448
- if (node.anchor)
449
- anchors.add(node.anchor);
447
+ Value(_key, node2) {
448
+ if (node2.anchor)
449
+ anchors.add(node2.anchor);
450
450
  }
451
451
  });
452
452
  return anchors;
@@ -629,20 +629,20 @@ var require_Alias = __commonJS((exports) => {
629
629
  } else {
630
630
  nodes = [];
631
631
  visit.visit(doc, {
632
- Node: (_key, node) => {
633
- if (identity.isAlias(node) || identity.hasAnchor(node))
634
- nodes.push(node);
632
+ Node: (_key, node2) => {
633
+ if (identity.isAlias(node2) || identity.hasAnchor(node2))
634
+ nodes.push(node2);
635
635
  }
636
636
  });
637
637
  if (ctx)
638
638
  ctx.aliasResolveCache = nodes;
639
639
  }
640
640
  let found = undefined;
641
- for (const node of nodes) {
642
- if (node === this)
641
+ for (const node2 of nodes) {
642
+ if (node2 === this)
643
643
  break;
644
- if (node.anchor === this.source)
645
- found = node;
644
+ if (node2.anchor === this.source)
645
+ found = node2;
646
646
  }
647
647
  return found;
648
648
  }
@@ -689,22 +689,22 @@ var require_Alias = __commonJS((exports) => {
689
689
  return src;
690
690
  }
691
691
  }
692
- function getAliasCount(doc, node, anchors2) {
693
- if (identity.isAlias(node)) {
694
- const source = node.resolve(doc);
692
+ function getAliasCount(doc, node2, anchors2) {
693
+ if (identity.isAlias(node2)) {
694
+ const source = node2.resolve(doc);
695
695
  const anchor = anchors2 && source && anchors2.get(source);
696
696
  return anchor ? anchor.count * anchor.aliasCount : 0;
697
- } else if (identity.isCollection(node)) {
697
+ } else if (identity.isCollection(node2)) {
698
698
  let count = 0;
699
- for (const item of node.items) {
699
+ for (const item of node2.items) {
700
700
  const c = getAliasCount(doc, item, anchors2);
701
701
  if (c > count)
702
702
  count = c;
703
703
  }
704
704
  return count;
705
- } else if (identity.isPair(node)) {
706
- const kc = getAliasCount(doc, node.key, anchors2);
707
- const vc = getAliasCount(doc, node.value, anchors2);
705
+ } else if (identity.isPair(node2)) {
706
+ const kc = getAliasCount(doc, node2.key, anchors2);
707
+ const vc = getAliasCount(doc, node2.value, anchors2);
708
708
  return Math.max(kc, vc);
709
709
  }
710
710
  return 1;
@@ -789,10 +789,10 @@ var require_createNode = __commonJS((exports) => {
789
789
  value = value.toJSON();
790
790
  }
791
791
  if (!value || typeof value !== "object") {
792
- const node2 = new Scalar.Scalar(value);
792
+ const node3 = new Scalar.Scalar(value);
793
793
  if (ref)
794
- ref.node = node2;
795
- return node2;
794
+ ref.node = node3;
795
+ return node3;
796
796
  }
797
797
  tagObj = value instanceof Map ? schema[identity.MAP] : (Symbol.iterator in Object(value)) ? schema[identity.SEQ] : schema[identity.MAP];
798
798
  }
@@ -800,14 +800,14 @@ var require_createNode = __commonJS((exports) => {
800
800
  onTagObj(tagObj);
801
801
  delete ctx.onTagObj;
802
802
  }
803
- const node = tagObj?.createNode ? tagObj.createNode(ctx.schema, value, ctx) : typeof tagObj?.nodeClass?.from === "function" ? tagObj.nodeClass.from(ctx.schema, value, ctx) : new Scalar.Scalar(value);
803
+ const node2 = tagObj?.createNode ? tagObj.createNode(ctx.schema, value, ctx) : typeof tagObj?.nodeClass?.from === "function" ? tagObj.nodeClass.from(ctx.schema, value, ctx) : new Scalar.Scalar(value);
804
804
  if (tagName)
805
- node.tag = tagName;
805
+ node2.tag = tagName;
806
806
  else if (!tagObj.default)
807
- node.tag = tagObj.tag;
807
+ node2.tag = tagObj.tag;
808
808
  if (ref)
809
- ref.node = node;
810
- return node;
809
+ ref.node = node2;
810
+ return node2;
811
811
  }
812
812
  exports.createNode = createNode;
813
813
  });
@@ -865,10 +865,10 @@ var require_Collection = __commonJS((exports) => {
865
865
  this.add(value);
866
866
  else {
867
867
  const [key, ...rest] = path;
868
- const node = this.get(key, true);
869
- if (identity.isCollection(node))
870
- node.addIn(rest, value);
871
- else if (node === undefined && this.schema)
868
+ const node2 = this.get(key, true);
869
+ if (identity.isCollection(node2))
870
+ node2.addIn(rest, value);
871
+ else if (node2 === undefined && this.schema)
872
872
  this.set(key, collectionFromPath(this.schema, rest, value));
873
873
  else
874
874
  throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
@@ -878,25 +878,25 @@ var require_Collection = __commonJS((exports) => {
878
878
  const [key, ...rest] = path;
879
879
  if (rest.length === 0)
880
880
  return this.delete(key);
881
- const node = this.get(key, true);
882
- if (identity.isCollection(node))
883
- return node.deleteIn(rest);
881
+ const node2 = this.get(key, true);
882
+ if (identity.isCollection(node2))
883
+ return node2.deleteIn(rest);
884
884
  else
885
885
  throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
886
886
  }
887
887
  getIn(path, keepScalar) {
888
888
  const [key, ...rest] = path;
889
- const node = this.get(key, true);
889
+ const node2 = this.get(key, true);
890
890
  if (rest.length === 0)
891
- return !keepScalar && identity.isScalar(node) ? node.value : node;
891
+ return !keepScalar && identity.isScalar(node2) ? node2.value : node2;
892
892
  else
893
- return identity.isCollection(node) ? node.getIn(rest, keepScalar) : undefined;
893
+ return identity.isCollection(node2) ? node2.getIn(rest, keepScalar) : undefined;
894
894
  }
895
895
  hasAllNullValues(allowScalar) {
896
- return this.items.every((node) => {
897
- if (!identity.isPair(node))
896
+ return this.items.every((node2) => {
897
+ if (!identity.isPair(node2))
898
898
  return false;
899
- const n = node.value;
899
+ const n = node2.value;
900
900
  return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
901
901
  });
902
902
  }
@@ -904,18 +904,18 @@ var require_Collection = __commonJS((exports) => {
904
904
  const [key, ...rest] = path;
905
905
  if (rest.length === 0)
906
906
  return this.has(key);
907
- const node = this.get(key, true);
908
- return identity.isCollection(node) ? node.hasIn(rest) : false;
907
+ const node2 = this.get(key, true);
908
+ return identity.isCollection(node2) ? node2.hasIn(rest) : false;
909
909
  }
910
910
  setIn(path, value) {
911
911
  const [key, ...rest] = path;
912
912
  if (rest.length === 0) {
913
913
  this.set(key, value);
914
914
  } else {
915
- const node = this.get(key, true);
916
- if (identity.isCollection(node))
917
- node.setIn(rest, value);
918
- else if (node === undefined && this.schema)
915
+ const node2 = this.get(key, true);
916
+ if (identity.isCollection(node2))
917
+ node2.setIn(rest, value);
918
+ else if (node2 === undefined && this.schema)
919
919
  this.set(key, collectionFromPath(this.schema, rest, value));
920
920
  else
921
921
  throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
@@ -1454,16 +1454,16 @@ var require_stringify = __commonJS((exports) => {
1454
1454
  }
1455
1455
  return tagObj;
1456
1456
  }
1457
- function stringifyProps(node, tagObj, { anchors: anchors$1, doc }) {
1457
+ function stringifyProps(node2, tagObj, { anchors: anchors$1, doc }) {
1458
1458
  if (!doc.directives)
1459
1459
  return "";
1460
1460
  const props = [];
1461
- const anchor = (identity.isScalar(node) || identity.isCollection(node)) && node.anchor;
1461
+ const anchor = (identity.isScalar(node2) || identity.isCollection(node2)) && node2.anchor;
1462
1462
  if (anchor && anchors.anchorIsValid(anchor)) {
1463
1463
  anchors$1.add(anchor);
1464
1464
  props.push(`&${anchor}`);
1465
1465
  }
1466
- const tag = node.tag ?? (tagObj.default ? null : tagObj.tag);
1466
+ const tag = node2.tag ?? (tagObj.default ? null : tagObj.tag);
1467
1467
  if (tag)
1468
1468
  props.push(doc.directives.tagString(tag));
1469
1469
  return props.join(" ");
@@ -1485,15 +1485,15 @@ var require_stringify = __commonJS((exports) => {
1485
1485
  }
1486
1486
  }
1487
1487
  let tagObj = undefined;
1488
- const node = identity.isNode(item) ? item : ctx.doc.createNode(item, { onTagObj: (o) => tagObj = o });
1489
- tagObj ?? (tagObj = getTagObject(ctx.doc.schema.tags, node));
1490
- const props = stringifyProps(node, tagObj, ctx);
1488
+ const node2 = identity.isNode(item) ? item : ctx.doc.createNode(item, { onTagObj: (o) => tagObj = o });
1489
+ tagObj ?? (tagObj = getTagObject(ctx.doc.schema.tags, node2));
1490
+ const props = stringifyProps(node2, tagObj, ctx);
1491
1491
  if (props.length > 0)
1492
1492
  ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1;
1493
- const str = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : identity.isScalar(node) ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep);
1493
+ const str = typeof tagObj.stringify === "function" ? tagObj.stringify(node2, ctx, onComment, onChompKeep) : identity.isScalar(node2) ? stringifyString.stringifyString(node2, ctx, onComment, onChompKeep) : node2.toString(ctx, onComment, onChompKeep);
1494
1494
  if (!props)
1495
1495
  return str;
1496
- return identity.isScalar(node) || str[0] === "{" || str[0] === "[" ? `${props} ${str}` : `${props}
1496
+ return identity.isScalar(node2) || str[0] === "{" || str[0] === "[" ? `${props} ${str}` : `${props}
1497
1497
  ${ctx.indent}${str}`;
1498
1498
  }
1499
1499
  exports.createStringifyContext = createStringifyContext;
@@ -1754,8 +1754,8 @@ var require_addPairToJSMap = __commonJS((exports) => {
1754
1754
  if (identity.isNode(key) && ctx?.doc) {
1755
1755
  const strCtx = stringify.createStringifyContext(ctx.doc, {});
1756
1756
  strCtx.anchors = new Set;
1757
- for (const node of ctx.anchors.keys())
1758
- strCtx.anchors.add(node.anchor);
1757
+ for (const node2 of ctx.anchors.keys())
1758
+ strCtx.anchors.add(node2.anchor);
1759
1759
  strCtx.inFlow = true;
1760
1760
  strCtx.inStringifyKey = true;
1761
1761
  const strKey = key.toString(strCtx);
@@ -2051,8 +2051,8 @@ var require_YAMLMap = __commonJS((exports) => {
2051
2051
  }
2052
2052
  get(key, keepScalar) {
2053
2053
  const it = findPair(this.items, key);
2054
- const node = it?.value;
2055
- return (!keepScalar && identity.isScalar(node) ? node.value : node) ?? undefined;
2054
+ const node2 = it?.value;
2055
+ return (!keepScalar && identity.isScalar(node2) ? node2.value : node2) ?? undefined;
2056
2056
  }
2057
2057
  has(key) {
2058
2058
  return !!findPair(this.items, key);
@@ -2316,9 +2316,9 @@ var require_float = __commonJS((exports) => {
2316
2316
  format: "EXP",
2317
2317
  test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
2318
2318
  resolve: (str) => parseFloat(str),
2319
- stringify(node) {
2320
- const num = Number(node.value);
2321
- return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node);
2319
+ stringify(node2) {
2320
+ const num = Number(node2.value);
2321
+ return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node2);
2322
2322
  }
2323
2323
  };
2324
2324
  var float = {
@@ -2327,11 +2327,11 @@ var require_float = __commonJS((exports) => {
2327
2327
  tag: "tag:yaml.org,2002:float",
2328
2328
  test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/,
2329
2329
  resolve(str) {
2330
- const node = new Scalar.Scalar(parseFloat(str));
2330
+ const node2 = new Scalar.Scalar(parseFloat(str));
2331
2331
  const dot = str.indexOf(".");
2332
2332
  if (dot !== -1 && str[str.length - 1] === "0")
2333
- node.minFractionDigits = str.length - dot - 1;
2334
- return node;
2333
+ node2.minFractionDigits = str.length - dot - 1;
2334
+ return node2;
2335
2335
  },
2336
2336
  stringify: stringifyNumber.stringifyNumber
2337
2337
  };
@@ -2345,11 +2345,11 @@ var require_int = __commonJS((exports) => {
2345
2345
  var stringifyNumber = require_stringifyNumber();
2346
2346
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
2347
2347
  var intResolve = (str, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix);
2348
- function intStringify(node, radix, prefix) {
2349
- const { value } = node;
2348
+ function intStringify(node2, radix, prefix) {
2349
+ const { value } = node2;
2350
2350
  if (intIdentify(value) && value >= 0)
2351
2351
  return prefix + value.toString(radix);
2352
- return stringifyNumber.stringifyNumber(node);
2352
+ return stringifyNumber.stringifyNumber(node2);
2353
2353
  }
2354
2354
  var intOct = {
2355
2355
  identify: (value) => intIdentify(value) && value >= 0,
@@ -2358,7 +2358,7 @@ var require_int = __commonJS((exports) => {
2358
2358
  format: "OCT",
2359
2359
  test: /^0o[0-7]+$/,
2360
2360
  resolve: (str, _onError, opt) => intResolve(str, 2, 8, opt),
2361
- stringify: (node) => intStringify(node, 8, "0o")
2361
+ stringify: (node2) => intStringify(node2, 8, "0o")
2362
2362
  };
2363
2363
  var int = {
2364
2364
  identify: intIdentify,
@@ -2375,7 +2375,7 @@ var require_int = __commonJS((exports) => {
2375
2375
  format: "HEX",
2376
2376
  test: /^0x[0-9a-fA-F]+$/,
2377
2377
  resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt),
2378
- stringify: (node) => intStringify(node, 16, "0x")
2378
+ stringify: (node2) => intStringify(node2, 16, "0x")
2379
2379
  };
2380
2380
  exports.int = int;
2381
2381
  exports.intHex = intHex;
@@ -2721,9 +2721,9 @@ var require_float2 = __commonJS((exports) => {
2721
2721
  format: "EXP",
2722
2722
  test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/,
2723
2723
  resolve: (str) => parseFloat(str.replace(/_/g, "")),
2724
- stringify(node) {
2725
- const num = Number(node.value);
2726
- return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node);
2724
+ stringify(node2) {
2725
+ const num = Number(node2.value);
2726
+ return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node2);
2727
2727
  }
2728
2728
  };
2729
2729
  var float = {
@@ -2732,14 +2732,14 @@ var require_float2 = __commonJS((exports) => {
2732
2732
  tag: "tag:yaml.org,2002:float",
2733
2733
  test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/,
2734
2734
  resolve(str) {
2735
- const node = new Scalar.Scalar(parseFloat(str.replace(/_/g, "")));
2735
+ const node2 = new Scalar.Scalar(parseFloat(str.replace(/_/g, "")));
2736
2736
  const dot = str.indexOf(".");
2737
2737
  if (dot !== -1) {
2738
2738
  const f = str.substring(dot + 1).replace(/_/g, "");
2739
2739
  if (f[f.length - 1] === "0")
2740
- node.minFractionDigits = f.length;
2740
+ node2.minFractionDigits = f.length;
2741
2741
  }
2742
- return node;
2742
+ return node2;
2743
2743
  },
2744
2744
  stringify: stringifyNumber.stringifyNumber
2745
2745
  };
@@ -2775,13 +2775,13 @@ var require_int2 = __commonJS((exports) => {
2775
2775
  const n = parseInt(str, radix);
2776
2776
  return sign === "-" ? -1 * n : n;
2777
2777
  }
2778
- function intStringify(node, radix, prefix) {
2779
- const { value } = node;
2778
+ function intStringify(node2, radix, prefix) {
2779
+ const { value } = node2;
2780
2780
  if (intIdentify(value)) {
2781
2781
  const str = value.toString(radix);
2782
2782
  return value < 0 ? "-" + prefix + str.substr(1) : prefix + str;
2783
2783
  }
2784
- return stringifyNumber.stringifyNumber(node);
2784
+ return stringifyNumber.stringifyNumber(node2);
2785
2785
  }
2786
2786
  var intBin = {
2787
2787
  identify: intIdentify,
@@ -2790,7 +2790,7 @@ var require_int2 = __commonJS((exports) => {
2790
2790
  format: "BIN",
2791
2791
  test: /^[-+]?0b[0-1_]+$/,
2792
2792
  resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt),
2793
- stringify: (node) => intStringify(node, 2, "0b")
2793
+ stringify: (node2) => intStringify(node2, 2, "0b")
2794
2794
  };
2795
2795
  var intOct = {
2796
2796
  identify: intIdentify,
@@ -2799,7 +2799,7 @@ var require_int2 = __commonJS((exports) => {
2799
2799
  format: "OCT",
2800
2800
  test: /^[-+]?0[0-7_]+$/,
2801
2801
  resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt),
2802
- stringify: (node) => intStringify(node, 8, "0")
2802
+ stringify: (node2) => intStringify(node2, 8, "0")
2803
2803
  };
2804
2804
  var int = {
2805
2805
  identify: intIdentify,
@@ -2816,7 +2816,7 @@ var require_int2 = __commonJS((exports) => {
2816
2816
  format: "HEX",
2817
2817
  test: /^[-+]?0x[0-9a-fA-F_]+$/,
2818
2818
  resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt),
2819
- stringify: (node) => intStringify(node, 16, "0x")
2819
+ stringify: (node2) => intStringify(node2, 16, "0x")
2820
2820
  };
2821
2821
  exports.int = int;
2822
2822
  exports.intBin = intBin;
@@ -2917,13 +2917,13 @@ var require_timestamp = __commonJS((exports) => {
2917
2917
  const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num(60) + num(p), num(0));
2918
2918
  return sign === "-" ? num(-1) * res : res;
2919
2919
  }
2920
- function stringifySexagesimal(node) {
2921
- let { value } = node;
2920
+ function stringifySexagesimal(node2) {
2921
+ let { value } = node2;
2922
2922
  let num = (n) => n;
2923
2923
  if (typeof value === "bigint")
2924
2924
  num = (n) => BigInt(n);
2925
2925
  else if (isNaN(value) || !isFinite(value))
2926
- return stringifyNumber.stringifyNumber(node);
2926
+ return stringifyNumber.stringifyNumber(node2);
2927
2927
  let sign = "";
2928
2928
  if (value < 0) {
2929
2929
  sign = "-";
@@ -3305,12 +3305,12 @@ var require_Document = __commonJS((exports) => {
3305
3305
  if (assertCollection(this.contents))
3306
3306
  this.contents.addIn(path, value);
3307
3307
  }
3308
- createAlias(node, name) {
3309
- if (!node.anchor) {
3308
+ createAlias(node2, name) {
3309
+ if (!node2.anchor) {
3310
3310
  const prev = anchors.anchorNames(this);
3311
- node.anchor = !name || prev.has(name) ? anchors.findNewAnchor(name || "a", prev) : name;
3311
+ node2.anchor = !name || prev.has(name) ? anchors.findNewAnchor(name || "a", prev) : name;
3312
3312
  }
3313
- return new Alias.Alias(node.anchor);
3313
+ return new Alias.Alias(node2.anchor);
3314
3314
  }
3315
3315
  createNode(value, replacer, options) {
3316
3316
  let _replacer = undefined;
@@ -3338,11 +3338,11 @@ var require_Document = __commonJS((exports) => {
3338
3338
  schema: this.schema,
3339
3339
  sourceObjects
3340
3340
  };
3341
- const node = createNode.createNode(value, tag, ctx);
3342
- if (flow && identity.isCollection(node))
3343
- node.flow = true;
3341
+ const node2 = createNode.createNode(value, tag, ctx);
3342
+ if (flow && identity.isCollection(node2))
3343
+ node2.flow = true;
3344
3344
  setAnchors();
3345
- return node;
3345
+ return node2;
3346
3346
  }
3347
3347
  createPair(key, value, options = {}) {
3348
3348
  const k = this.createNode(key, null, options);
@@ -3872,11 +3872,11 @@ var require_resolve_block_seq = __commonJS((exports) => {
3872
3872
  continue;
3873
3873
  }
3874
3874
  }
3875
- const node = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError);
3875
+ const node2 = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError);
3876
3876
  if (ctx.schema.compat)
3877
3877
  utilFlowIndentCheck.flowIndentCheck(bs.indent, value, onError);
3878
- offset = node.range[2];
3879
- seq.items.push(node);
3878
+ offset = node2.range[2];
3879
+ seq.items.push(node2);
3880
3880
  }
3881
3881
  seq.range = [bs.offset, offset, commentEnd ?? offset];
3882
3882
  return seq;
@@ -4167,12 +4167,12 @@ var require_compose_collection = __commonJS((exports) => {
4167
4167
  }
4168
4168
  const coll = resolveCollection(CN, ctx, token, onError, tagName, tag);
4169
4169
  const res = tag.resolve?.(coll, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg), ctx.options) ?? coll;
4170
- const node = identity.isNode(res) ? res : new Scalar.Scalar(res);
4171
- node.range = coll.range;
4172
- node.tag = tagName;
4170
+ const node2 = identity.isNode(res) ? res : new Scalar.Scalar(res);
4171
+ node2.range = coll.range;
4172
+ node2.tag = tagName;
4173
4173
  if (tag?.format)
4174
- node.format = tag.format;
4175
- return node;
4174
+ node2.format = tag.format;
4175
+ return node2;
4176
4176
  }
4177
4177
  exports.composeCollection = composeCollection;
4178
4178
  });
@@ -4704,11 +4704,11 @@ var require_compose_node = __commonJS((exports) => {
4704
4704
  function composeNode(ctx, token, props, onError) {
4705
4705
  const atKey = ctx.atKey;
4706
4706
  const { spaceBefore, comment, anchor, tag } = props;
4707
- let node;
4707
+ let node2;
4708
4708
  let isSrcToken = true;
4709
4709
  switch (token.type) {
4710
4710
  case "alias":
4711
- node = composeAlias(ctx, token, onError);
4711
+ node2 = composeAlias(ctx, token, onError);
4712
4712
  if (anchor || tag)
4713
4713
  onError(token, "ALIAS_PROPS", "An alias node must not specify any properties");
4714
4714
  break;
@@ -4716,17 +4716,17 @@ var require_compose_node = __commonJS((exports) => {
4716
4716
  case "single-quoted-scalar":
4717
4717
  case "double-quoted-scalar":
4718
4718
  case "block-scalar":
4719
- node = composeScalar.composeScalar(ctx, token, tag, onError);
4719
+ node2 = composeScalar.composeScalar(ctx, token, tag, onError);
4720
4720
  if (anchor)
4721
- node.anchor = anchor.source.substring(1);
4721
+ node2.anchor = anchor.source.substring(1);
4722
4722
  break;
4723
4723
  case "block-map":
4724
4724
  case "block-seq":
4725
4725
  case "flow-collection":
4726
4726
  try {
4727
- node = composeCollection.composeCollection(CN, ctx, token, props, onError);
4727
+ node2 = composeCollection.composeCollection(CN, ctx, token, props, onError);
4728
4728
  if (anchor)
4729
- node.anchor = anchor.source.substring(1);
4729
+ node2.anchor = anchor.source.substring(1);
4730
4730
  } catch (error) {
4731
4731
  const message = error instanceof Error ? error.message : String(error);
4732
4732
  onError(token, "RESOURCE_EXHAUSTION", message);
@@ -4738,24 +4738,24 @@ var require_compose_node = __commonJS((exports) => {
4738
4738
  isSrcToken = false;
4739
4739
  }
4740
4740
  }
4741
- node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
4742
- if (anchor && node.anchor === "")
4741
+ node2 ?? (node2 = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
4742
+ if (anchor && node2.anchor === "")
4743
4743
  onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
4744
- if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
4744
+ if (atKey && ctx.options.stringKeys && (!identity.isScalar(node2) || typeof node2.value !== "string" || node2.tag && node2.tag !== "tag:yaml.org,2002:str")) {
4745
4745
  const msg = "With stringKeys, all keys must be strings";
4746
4746
  onError(tag ?? token, "NON_STRING_KEY", msg);
4747
4747
  }
4748
4748
  if (spaceBefore)
4749
- node.spaceBefore = true;
4749
+ node2.spaceBefore = true;
4750
4750
  if (comment) {
4751
4751
  if (token.type === "scalar" && token.source === "")
4752
- node.comment = comment;
4752
+ node2.comment = comment;
4753
4753
  else
4754
- node.commentBefore = comment;
4754
+ node2.commentBefore = comment;
4755
4755
  }
4756
4756
  if (ctx.options.keepSourceTokens && isSrcToken)
4757
- node.srcToken = token;
4758
- return node;
4757
+ node2.srcToken = token;
4758
+ return node2;
4759
4759
  }
4760
4760
  function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) {
4761
4761
  const token = {
@@ -4764,19 +4764,19 @@ var require_compose_node = __commonJS((exports) => {
4764
4764
  indent: -1,
4765
4765
  source: ""
4766
4766
  };
4767
- const node = composeScalar.composeScalar(ctx, token, tag, onError);
4767
+ const node2 = composeScalar.composeScalar(ctx, token, tag, onError);
4768
4768
  if (anchor) {
4769
- node.anchor = anchor.source.substring(1);
4770
- if (node.anchor === "")
4769
+ node2.anchor = anchor.source.substring(1);
4770
+ if (node2.anchor === "")
4771
4771
  onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
4772
4772
  }
4773
4773
  if (spaceBefore)
4774
- node.spaceBefore = true;
4774
+ node2.spaceBefore = true;
4775
4775
  if (comment) {
4776
- node.comment = comment;
4777
- node.range[2] = end;
4776
+ node2.comment = comment;
4777
+ node2.range[2] = end;
4778
4778
  }
4779
- return node;
4779
+ return node2;
4780
4780
  }
4781
4781
  function composeAlias({ options }, { offset, source, end }, onError) {
4782
4782
  const alias = new Alias.Alias(source.substring(1));
@@ -7044,7 +7044,7 @@ function getExitCode(error) {
7044
7044
  // package.json
7045
7045
  var package_default = {
7046
7046
  name: "cc-codeconductor",
7047
- version: "0.2.10",
7047
+ version: "0.3.1",
7048
7048
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
7049
7049
  keywords: [
7050
7050
  "ai",
@@ -7098,6 +7098,230 @@ var package_default = {
7098
7098
  }
7099
7099
  };
7100
7100
 
7101
+ // src/core/detection/detectors/node.ts
7102
+ async function detectNode(rootDir) {
7103
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7104
+ const signals = [];
7105
+ if (await fileExists2(rootDir, "package.json")) {
7106
+ signals.push("package.json");
7107
+ }
7108
+ if (await fileExists2(rootDir, "node_modules")) {
7109
+ signals.push("node_modules/");
7110
+ }
7111
+ return signals;
7112
+ }
7113
+ async function detectBun(rootDir) {
7114
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7115
+ const signals = [];
7116
+ if (await fileExists2(rootDir, "bun.lockb")) {
7117
+ signals.push("bun.lockb");
7118
+ }
7119
+ if (await fileExists2(rootDir, "bun.lock")) {
7120
+ signals.push("bun.lock");
7121
+ }
7122
+ return signals;
7123
+ }
7124
+ // src/core/detection/detectors/backend.ts
7125
+ async function detectSpring(rootDir) {
7126
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7127
+ const signals = [];
7128
+ if (await fileExists2(rootDir, "build.gradle") || await fileExists2(rootDir, "build.gradle.kts")) {
7129
+ signals.push("build.gradle");
7130
+ }
7131
+ if (await fileExists2(rootDir, "pom.xml")) {
7132
+ signals.push("pom.xml");
7133
+ }
7134
+ return signals;
7135
+ }
7136
+ async function detectDjango(rootDir) {
7137
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7138
+ const signals = [];
7139
+ if (await fileExists2(rootDir, "manage.py")) {
7140
+ signals.push("manage.py");
7141
+ }
7142
+ if (await fileExists2(rootDir, "requirements.txt")) {
7143
+ signals.push("requirements.txt");
7144
+ }
7145
+ return signals;
7146
+ }
7147
+ async function detectFastApi(rootDir) {
7148
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7149
+ const { join } = await import("node:path");
7150
+ const { readFile } = await import("node:fs/promises");
7151
+ const signals = [];
7152
+ if (await fileExists2(rootDir, "requirements.txt")) {
7153
+ try {
7154
+ const content = await readFile(join(rootDir, "requirements.txt"), "utf-8");
7155
+ if (content.includes("fastapi")) {
7156
+ signals.push("fastapi-requirements");
7157
+ }
7158
+ } catch {}
7159
+ }
7160
+ if (await fileExists2(rootDir, "pyproject.toml")) {
7161
+ try {
7162
+ const content = await readFile(join(rootDir, "pyproject.toml"), "utf-8");
7163
+ if (content.includes("fastapi")) {
7164
+ signals.push("fastapi-pyproject");
7165
+ }
7166
+ } catch {}
7167
+ }
7168
+ for (const filename of ["main.py", "app.py", "app/main.py", "app/app.py"]) {
7169
+ if (await fileExists2(rootDir, filename)) {
7170
+ try {
7171
+ const content = await readFile(join(rootDir, filename), "utf-8");
7172
+ if (content.includes("FastAPI") && content.includes("fastapi")) {
7173
+ signals.push(`${filename}-fastapi`);
7174
+ }
7175
+ } catch {}
7176
+ }
7177
+ }
7178
+ return signals;
7179
+ }
7180
+ async function detectPhp(rootDir) {
7181
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7182
+ const signals = [];
7183
+ if (await fileExists2(rootDir, "composer.json")) {
7184
+ signals.push("composer.json");
7185
+ }
7186
+ if (await fileExists2(rootDir, "artisan")) {
7187
+ signals.push("artisan");
7188
+ }
7189
+ try {
7190
+ const { readdir } = await import("node:fs/promises");
7191
+ const entries = await readdir(rootDir, { withFileTypes: true });
7192
+ const hasPhpFiles = entries.some((entry) => entry.isFile() && entry.name.endsWith(".php"));
7193
+ if (hasPhpFiles) {
7194
+ signals.push("*.php");
7195
+ }
7196
+ } catch {}
7197
+ return signals;
7198
+ }
7199
+ async function detectGenericBackend(rootDir) {
7200
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7201
+ const { readdir } = await import("node:fs/promises");
7202
+ const signals = [];
7203
+ if (await fileExists2(rootDir, "go.mod")) {
7204
+ signals.push("go.mod");
7205
+ }
7206
+ if (await fileExists2(rootDir, "Cargo.toml")) {
7207
+ signals.push("Cargo.toml");
7208
+ }
7209
+ if (await fileExists2(rootDir, "Gemfile")) {
7210
+ signals.push("Gemfile");
7211
+ }
7212
+ try {
7213
+ const entries = await readdir(rootDir, { withFileTypes: true });
7214
+ const hasCsProj = entries.some((entry) => entry.isFile() && entry.name.endsWith(".csproj"));
7215
+ if (hasCsProj) {
7216
+ signals.push("*.csproj");
7217
+ }
7218
+ } catch {}
7219
+ return signals;
7220
+ }
7221
+ // src/core/detection/detectors/frontend.ts
7222
+ async function detectAstro(rootDir) {
7223
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7224
+ const signals = [];
7225
+ if (await fileExists2(rootDir, "astro.config.mjs") || await fileExists2(rootDir, "astro.config.ts")) {
7226
+ signals.push("astro.config");
7227
+ }
7228
+ return signals;
7229
+ }
7230
+ async function detectNext(rootDir) {
7231
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7232
+ const { join } = await import("node:path");
7233
+ const { readFile } = await import("node:fs/promises");
7234
+ const signals = [];
7235
+ if (await fileExists2(rootDir, "next.config.js") || await fileExists2(rootDir, "next.config.mjs") || await fileExists2(rootDir, "next.config.ts")) {
7236
+ signals.push("next.config");
7237
+ }
7238
+ if (await fileExists2(rootDir, "package.json")) {
7239
+ try {
7240
+ const content = await readFile(join(rootDir, "package.json"), "utf-8");
7241
+ const pkg = JSON.parse(content);
7242
+ if (pkg.dependencies?.next || pkg.devDependencies?.next) {
7243
+ signals.push("package.json-next");
7244
+ }
7245
+ } catch {}
7246
+ }
7247
+ return signals;
7248
+ }
7249
+ async function detectGenericFrontend(rootDir) {
7250
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7251
+ const { join } = await import("node:path");
7252
+ const { readFile } = await import("node:fs/promises");
7253
+ const signals = [];
7254
+ if (await fileExists2(rootDir, "index.html")) {
7255
+ signals.push("index.html");
7256
+ }
7257
+ if (await fileExists2(rootDir, "vite.config.js") || await fileExists2(rootDir, "vite.config.ts")) {
7258
+ signals.push("vite.config");
7259
+ }
7260
+ if (await fileExists2(rootDir, "webpack.config.js")) {
7261
+ signals.push("webpack.config.js");
7262
+ }
7263
+ if (await fileExists2(rootDir, "package.json")) {
7264
+ try {
7265
+ const content = await readFile(join(rootDir, "package.json"), "utf-8");
7266
+ const pkg = JSON.parse(content);
7267
+ const frontendDeps = ["react", "vue", "svelte", "solid-js", "angular", "@angular/core"];
7268
+ const hasFrontendDep = frontendDeps.some((dep) => pkg.dependencies?.[dep] || pkg.devDependencies?.[dep]);
7269
+ if (hasFrontendDep) {
7270
+ signals.push("package.json-frontend");
7271
+ }
7272
+ } catch {}
7273
+ }
7274
+ return signals;
7275
+ }
7276
+ // src/core/detection/detectors/monorepo.ts
7277
+ async function detectMonorepo(rootDir) {
7278
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7279
+ const { join } = await import("node:path");
7280
+ const { readFile } = await import("node:fs/promises");
7281
+ const signals = [];
7282
+ if (await fileExists2(rootDir, "pnpm-workspace.yaml")) {
7283
+ signals.push("pnpm-workspace.yaml");
7284
+ }
7285
+ if (await fileExists2(rootDir, "lerna.json")) {
7286
+ signals.push("lerna.json");
7287
+ }
7288
+ if (await fileExists2(rootDir, "go.work")) {
7289
+ signals.push("go.work");
7290
+ }
7291
+ if (await fileExists2(rootDir, "package.json")) {
7292
+ try {
7293
+ const content = await readFile(join(rootDir, "package.json"), "utf-8");
7294
+ const pkg = JSON.parse(content);
7295
+ if (pkg.workspaces) {
7296
+ signals.push("package.json-workspaces");
7297
+ }
7298
+ } catch {}
7299
+ }
7300
+ if (await fileExists2(rootDir, "Cargo.toml")) {
7301
+ try {
7302
+ const content = await readFile(join(rootDir, "Cargo.toml"), "utf-8");
7303
+ if (content.includes("[workspace]")) {
7304
+ signals.push("Cargo.toml-workspace");
7305
+ }
7306
+ } catch {}
7307
+ }
7308
+ return signals;
7309
+ }
7310
+ // src/core/detection/detectors/android.ts
7311
+ async function detectAndroid(rootDir) {
7312
+ const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7313
+ const signals = [];
7314
+ if (await fileExists2(rootDir, "AndroidManifest.xml") || await fileExists2(rootDir, "app/src/main/AndroidManifest.xml")) {
7315
+ signals.push("AndroidManifest.xml");
7316
+ }
7317
+ if (await fileExists2(rootDir, "settings.gradle") || await fileExists2(rootDir, "settings.gradle.kts")) {
7318
+ signals.push("settings.gradle");
7319
+ }
7320
+ if (await fileExists2(rootDir, "gradlew")) {
7321
+ signals.push("gradlew");
7322
+ }
7323
+ return signals;
7324
+ }
7101
7325
  // src/core/detection/project-detector.ts
7102
7326
  async function detectProject(rootDir) {
7103
7327
  const signals = [];
@@ -7138,12 +7362,68 @@ async function detectProject(rootDir) {
7138
7362
  languages.push("php");
7139
7363
  runtimes.push("php");
7140
7364
  packageManagers.push("composer");
7365
+ if (phpSignals.includes("artisan")) {
7366
+ frameworks.push("laravel");
7367
+ }
7141
7368
  }
7142
7369
  const astroSignals = await detectAstro(rootDir);
7143
7370
  if (astroSignals.length > 0) {
7144
7371
  signals.push(...astroSignals);
7145
7372
  frameworks.push("astro");
7146
7373
  }
7374
+ const nextSignals = await detectNext(rootDir);
7375
+ if (nextSignals.length > 0) {
7376
+ signals.push(...nextSignals);
7377
+ languages.push("javascript", "typescript");
7378
+ runtimes.push("node");
7379
+ frameworks.push("nextjs");
7380
+ }
7381
+ const fastapiSignals = await detectFastApi(rootDir);
7382
+ if (fastapiSignals.length > 0) {
7383
+ signals.push(...fastapiSignals);
7384
+ languages.push("python");
7385
+ runtimes.push("python");
7386
+ frameworks.push("fastapi");
7387
+ }
7388
+ const monorepoSignals = await detectMonorepo(rootDir);
7389
+ if (monorepoSignals.length > 0) {
7390
+ signals.push(...monorepoSignals);
7391
+ }
7392
+ const androidSignals = await detectAndroid(rootDir);
7393
+ if (androidSignals.includes("AndroidManifest.xml")) {
7394
+ signals.push(...androidSignals);
7395
+ languages.push("kotlin", "java");
7396
+ runtimes.push("android");
7397
+ frameworks.push("android");
7398
+ }
7399
+ const genericBackendSignals = await detectGenericBackend(rootDir);
7400
+ if (genericBackendSignals.length > 0 && !frameworks.includes("django") && !frameworks.includes("fastapi") && !frameworks.includes("spring")) {
7401
+ signals.push(...genericBackendSignals);
7402
+ frameworks.push("backend");
7403
+ if (genericBackendSignals.includes("go.mod")) {
7404
+ languages.push("go");
7405
+ runtimes.push("go");
7406
+ }
7407
+ if (genericBackendSignals.includes("Cargo.toml")) {
7408
+ languages.push("rust");
7409
+ runtimes.push("rust");
7410
+ }
7411
+ if (genericBackendSignals.includes("Gemfile")) {
7412
+ languages.push("ruby");
7413
+ runtimes.push("ruby");
7414
+ }
7415
+ if (genericBackendSignals.includes("*.csproj")) {
7416
+ languages.push("csharp");
7417
+ runtimes.push("dotnet");
7418
+ }
7419
+ }
7420
+ const genericFrontendSignals = await detectGenericFrontend(rootDir);
7421
+ if (genericFrontendSignals.length > 0 && !frameworks.includes("astro") && !frameworks.includes("nextjs")) {
7422
+ signals.push(...genericFrontendSignals);
7423
+ frameworks.push("frontend");
7424
+ languages.push("javascript", "typescript");
7425
+ runtimes.push("node");
7426
+ }
7147
7427
  const uniqueSignals = [...new Set(signals)];
7148
7428
  return {
7149
7429
  rootDir,
@@ -7173,74 +7453,6 @@ function calculateConfidence(profile) {
7173
7453
  }
7174
7454
  return "low";
7175
7455
  }
7176
- async function detectNode(rootDir) {
7177
- const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7178
- const signals = [];
7179
- if (await fileExists2(rootDir, "package.json")) {
7180
- signals.push("package.json");
7181
- }
7182
- if (await fileExists2(rootDir, "node_modules")) {
7183
- signals.push("node_modules/");
7184
- }
7185
- return signals;
7186
- }
7187
- async function detectBun(rootDir) {
7188
- const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7189
- const signals = [];
7190
- if (await fileExists2(rootDir, "bun.lockb")) {
7191
- signals.push("bun.lockb");
7192
- }
7193
- if (await fileExists2(rootDir, "bun.lock")) {
7194
- signals.push("bun.lock");
7195
- }
7196
- return signals;
7197
- }
7198
- async function detectSpring(rootDir) {
7199
- const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7200
- const signals = [];
7201
- if (await fileExists2(rootDir, "build.gradle") || await fileExists2(rootDir, "build.gradle.kts")) {
7202
- signals.push("build.gradle");
7203
- }
7204
- if (await fileExists2(rootDir, "pom.xml")) {
7205
- signals.push("pom.xml");
7206
- }
7207
- return signals;
7208
- }
7209
- async function detectDjango(rootDir) {
7210
- const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7211
- const signals = [];
7212
- if (await fileExists2(rootDir, "manage.py")) {
7213
- signals.push("manage.py");
7214
- }
7215
- if (await fileExists2(rootDir, "requirements.txt")) {
7216
- signals.push("requirements.txt");
7217
- }
7218
- return signals;
7219
- }
7220
- async function detectAstro(rootDir) {
7221
- const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7222
- const signals = [];
7223
- if (await fileExists2(rootDir, "astro.config.mjs") || await fileExists2(rootDir, "astro.config.ts")) {
7224
- signals.push("astro.config");
7225
- }
7226
- return signals;
7227
- }
7228
- async function detectPhp(rootDir) {
7229
- const { fileExists: fileExists2 } = await Promise.resolve().then(() => (init_safety(), exports_safety));
7230
- const signals = [];
7231
- if (await fileExists2(rootDir, "composer.json")) {
7232
- signals.push("composer.json");
7233
- }
7234
- try {
7235
- const { readdir } = await import("node:fs/promises");
7236
- const entries = await readdir(rootDir, { withFileTypes: true });
7237
- const hasPhpFiles = entries.some((entry) => entry.isFile() && entry.name.endsWith(".php"));
7238
- if (hasPhpFiles) {
7239
- signals.push("*.php");
7240
- }
7241
- } catch {}
7242
- return signals;
7243
- }
7244
7456
 
7245
7457
  // src/commands/detect.command.ts
7246
7458
  async function detectCommand(options) {
@@ -7299,7 +7511,8 @@ function getRecommendedPresets(profile) {
7299
7511
  }
7300
7512
 
7301
7513
  // src/commands/doctor.command.ts
7302
- import { join } from "node:path";
7514
+ import { join as join4 } from "node:path";
7515
+ import { homedir as homedir3 } from "node:os";
7303
7516
 
7304
7517
  // src/core/config/config-loader.ts
7305
7518
  import { readFile } from "node:fs/promises";
@@ -11509,39 +11722,658 @@ async function loadTargetSecurityCompatibility() {
11509
11722
  });
11510
11723
  }
11511
11724
 
11512
- // src/commands/doctor.command.ts
11513
- async function doctorCommand(options) {
11514
- const { projectRoot, output } = options;
11515
- const checks = [];
11516
- try {
11517
- const hasConfig = await configExists(projectRoot);
11518
- if (hasConfig) {
11519
- checks.push({
11520
- name: "config-exists",
11521
- status: "pass",
11522
- message: ".codeconductor/config.yml exists"
11523
- });
11524
- } else {
11525
- checks.push({
11526
- name: "config-exists",
11527
- status: "fail",
11528
- message: ".codeconductor/config.yml not found. Run `codeconductor init` first."
11529
- });
11530
- return {
11531
- code: 4,
11532
- data: {
11533
- success: false,
11534
- command: "doctor",
11535
- checks
11536
- }
11537
- };
11538
- }
11539
- const configResult = await loadConfig(projectRoot);
11540
- if (configResult.success) {
11541
- checks.push({
11542
- name: "config-valid",
11543
- status: "pass",
11544
- message: "Config is valid"
11725
+ // src/core/presets/update-checker.ts
11726
+ import { stat as stat2, readFile as readFile5 } from "node:fs/promises";
11727
+ import { resolve as resolve4, join as join3 } from "node:path";
11728
+ import { homedir as homedir2 } from "node:os";
11729
+
11730
+ // src/core/presets/manifest-loader.ts
11731
+ import { readFile as readFile3 } from "node:fs/promises";
11732
+ import { join } from "node:path";
11733
+ var MANIFESTS_DIR = join(SRC_PRESETS_DIR, "manifests");
11734
+ var MODELS_DIR = join(SRC_PRESETS_DIR, "models");
11735
+ var PRESETS_DIR = ROOT_PRESETS_DIR;
11736
+ async function loadManifest(target) {
11737
+ const manifestPath = join(MANIFESTS_DIR, `${target}.yml`);
11738
+ const content = await readFile3(manifestPath, "utf-8");
11739
+ const data = $parse(content);
11740
+ return InstallManifestSchema.parse(data);
11741
+ }
11742
+ async function loadModelConfig(target) {
11743
+ const modelPath = join(MODELS_DIR, `${target}.yml`);
11744
+ const content = await readFile3(modelPath, "utf-8");
11745
+ const data = $parse(content);
11746
+ return ModelConfigSchema.parse(data);
11747
+ }
11748
+
11749
+ // src/core/presets/file-copier.ts
11750
+ import { mkdir, readdir, readFile as readFile4, stat, writeFile } from "node:fs/promises";
11751
+ import { homedir } from "node:os";
11752
+ import { dirname as dirname2, join as join2, relative, resolve as resolve3 } from "node:path";
11753
+
11754
+ // src/core/i18n/language-instructions.ts
11755
+ var SUPPORTED_LOCALES = ["en", "es"];
11756
+ var LANGUAGE_INSTRUCTIONS = {
11757
+ en: 'Prose/docs/code comments: be terse and direct. Prefer concrete nouns over abstract ones. Omit filler phrases ("note that", "please", "as mentioned"). One idea per sentence.',
11758
+ es: "Spanish prose/docs/reports/Markdown: preserve natural Spanish orthography, including accents, `ñ`, `¿`, `¡`, and normal Unicode. The ASCII-only editing preference does not apply to these artifacts."
11759
+ };
11760
+ var COMMIT_STYLE = {
11761
+ en: `---
11762
+ trigger: always_on
11763
+ description: Rules for generating commit messages using Conventional Commits format
11764
+ ---
11765
+
11766
+ ## Git Commit Messages
11767
+
11768
+ ### Format
11769
+
11770
+ \`\`\`
11771
+ <type>(<scope>): <short description>
11772
+
11773
+ - <detail 1>
11774
+ - <detail 2>
11775
+ \`\`\`
11776
+
11777
+ ### Valid Types
11778
+
11779
+ \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
11780
+ \`revert\`
11781
+
11782
+ ### Rules
11783
+
11784
+ - Language: **neutral English** always
11785
+ - Header: maximum 69 characters, no trailing period
11786
+ - Body: concise bullet points, one idea per line
11787
+ - Footer: only for breaking changes or issues
11788
+ - No gerunds ("adding", "fixing")
11789
+ - Use infinitive or imperative ("add", "fix", "update")`,
11790
+ es: `---
11791
+ trigger: always_on
11792
+ description: Reglas para generar mensajes de commit con formato Conventional Commits
11793
+ ---
11794
+
11795
+ ## Git Commit Messages
11796
+
11797
+ ### Formato
11798
+
11799
+ \`\`\`
11800
+ <tipo>(<scope>): <descripcion corta>
11801
+
11802
+ - <detalle 1>
11803
+ - <detalle 2>
11804
+ \`\`\`
11805
+
11806
+ ### Tipos validos
11807
+
11808
+ \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
11809
+ \`revert\`
11810
+
11811
+ ### Reglas
11812
+
11813
+ - Idioma: **inglés neutro** siempre
11814
+ - Encabezado: maximo 69 caracteres, sin punto final
11815
+ - Cuerpo: viñetas concisas, una idea por linea
11816
+ - Footer: solo para breaking changes o issues
11817
+ - Sin gerundios ("agregando", "corrigiendo")
11818
+ - Usar infinitivo o imperativo ("agregar", "corregir", "actualizar")`
11819
+ };
11820
+ var COMMIT_WORKFLOW = {
11821
+ en: `---
11822
+ name: commit
11823
+ description: Generates a commit in English following Conventional Commits based on staged changes
11824
+ ---
11825
+
11826
+ // turbo-all
11827
+
11828
+ ## Steps
11829
+
11830
+ 1. View staged changes: \`git diff --cached --stat\`
11831
+ 2. If nothing is staged, suggest \`git add .\` or specific files
11832
+ 3. Get full diff: \`git diff --cached\`
11833
+ 4. Check recent style: \`git log -n 3 --oneline\`
11834
+ 5. Generate message following \`.agents/rules/commit-style.md\`
11835
+ 6. Show proposal and confirm with the user
11836
+ 7. Execute: \`git commit -m "<message>"\`
11837
+ 8. Confirm success: \`git status\``,
11838
+ es: `---
11839
+ name: commit
11840
+ description: Genera un commit en inglés siguiendo Conventional Commits basado en los cambios staged
11841
+ ---
11842
+
11843
+ // turbo-all
11844
+
11845
+ ## Pasos
11846
+
11847
+ 1. Ver cambios staged: \`git diff --cached --stat\`
11848
+ 2. Si no hay nada staged, sugiere \`git add .\` o archivos específicos
11849
+ 3. Obtener diff completo: \`git diff --cached\`
11850
+ 4. Ver estilo reciente: \`git log -n 3 --oneline\`
11851
+ 5. Generar mensaje siguiendo \`.agents/rules/commit-style.md\`
11852
+ 6. Mostrar propuesta y confirmar con el usuario
11853
+ 7. Ejecutar: \`git commit -m "<mensaje>"\`
11854
+ 8. Confirmar éxito: \`git status\``
11855
+ };
11856
+ function getLanguageInstruction(locale) {
11857
+ const key = SUPPORTED_LOCALES.includes(locale) ? locale : "en";
11858
+ return `- ${LANGUAGE_INSTRUCTIONS[key]}`;
11859
+ }
11860
+ var LOCALE_PLACEHOLDER = "{{LANGUAGE_INSTRUCTIONS}}";
11861
+
11862
+ // src/core/filesystem/safe-merger.ts
11863
+ var MANAGED_BEGIN_MARKER = "<!-- CODECONDUCTOR:BEGIN managed -->";
11864
+ var MANAGED_END_MARKER = "<!-- CODECONDUCTOR:END managed -->";
11865
+ function mergeManagedBlock(existing, incoming) {
11866
+ validateMarkers(incoming, "incoming content");
11867
+ if (!existing) {
11868
+ return { content: incoming, action: "written" };
11869
+ }
11870
+ validateMarkers(existing, "existing content");
11871
+ const existingBlock = getManagedBlockRange(existing);
11872
+ const incomingBlock = getManagedBlockRange(incoming);
11873
+ return {
11874
+ content: existing.slice(0, existingBlock.start) + incoming.slice(incomingBlock.start, incomingBlock.end) + existing.slice(existingBlock.end),
11875
+ action: "merged"
11876
+ };
11877
+ }
11878
+ function validateMarkers(content, label) {
11879
+ const beginCount = countOccurrences(content, MANAGED_BEGIN_MARKER);
11880
+ const endCount = countOccurrences(content, MANAGED_END_MARKER);
11881
+ if (beginCount !== 1 || endCount !== 1) {
11882
+ throw new Error(`${label} must contain exactly one managed begin marker and one managed end marker`);
11883
+ }
11884
+ if (content.indexOf(MANAGED_BEGIN_MARKER) > content.indexOf(MANAGED_END_MARKER)) {
11885
+ throw new Error(`${label} has managed markers in the wrong order`);
11886
+ }
11887
+ }
11888
+ function getManagedBlockRange(content) {
11889
+ const start = content.indexOf(MANAGED_BEGIN_MARKER);
11890
+ const end = content.indexOf(MANAGED_END_MARKER) + MANAGED_END_MARKER.length;
11891
+ return { start, end };
11892
+ }
11893
+ function countOccurrences(content, needle) {
11894
+ return content.split(needle).length - 1;
11895
+ }
11896
+
11897
+ // src/core/presets/file-copier.ts
11898
+ async function listFilesRecursive(dir, base = dir) {
11899
+ const entries = await readdir(dir, { withFileTypes: true });
11900
+ const files = [];
11901
+ for (const entry of entries) {
11902
+ const full = join2(dir, entry.name);
11903
+ if (entry.isDirectory()) {
11904
+ files.push(...await listFilesRecursive(full, base));
11905
+ } else {
11906
+ files.push(relative(base, full));
11907
+ }
11908
+ }
11909
+ return files;
11910
+ }
11911
+ async function resolveEntryFiles(entry, presetsDir, baseDir) {
11912
+ const srcAbsolute = resolve3(presetsDir, entry.src);
11913
+ try {
11914
+ const s = await stat(srcAbsolute);
11915
+ if (s.isDirectory()) {
11916
+ const files = await listFilesRecursive(srcAbsolute);
11917
+ return files.map((f) => ({
11918
+ src: join2(srcAbsolute, f),
11919
+ dest: resolve3(baseDir, entry.dest, f)
11920
+ }));
11921
+ }
11922
+ return [{ src: srcAbsolute, dest: resolve3(baseDir, entry.dest) }];
11923
+ } catch {
11924
+ return [];
11925
+ }
11926
+ }
11927
+ function mergeDeep(target, source) {
11928
+ const result = { ...target };
11929
+ for (const key of Object.keys(source)) {
11930
+ const srcVal = source[key];
11931
+ const tgtVal = target[key];
11932
+ if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
11933
+ result[key] = [...new Set([...tgtVal, ...srcVal])];
11934
+ } else if (srcVal && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
11935
+ result[key] = mergeDeep(tgtVal, srcVal);
11936
+ } else {
11937
+ result[key] = srcVal;
11938
+ }
11939
+ }
11940
+ return result;
11941
+ }
11942
+ function extractAgentRole(filePath) {
11943
+ const parts = filePath.replace(/\\/g, "/").split("/");
11944
+ const basename = parts[parts.length - 1];
11945
+ const name = basename.replace(/\.md$/, "");
11946
+ const knownRoles = [
11947
+ "architect",
11948
+ "implementer",
11949
+ "tester",
11950
+ "orchestrator",
11951
+ "reviewer",
11952
+ "docs",
11953
+ "task-coach",
11954
+ "repo-explorer"
11955
+ ];
11956
+ return knownRoles.includes(name) ? name : null;
11957
+ }
11958
+ function renderTemplate(content, modelConfig, filePath, locale = "en") {
11959
+ const cleanLocale = locale === "es" || locale === "en" ? locale : "en";
11960
+ const templatedContent = content.replace(/\{\{COMMIT_STYLE\}\}/g, COMMIT_STYLE[cleanLocale]).replace(/\{\{COMMIT_WORKFLOW\}\}/g, COMMIT_WORKFLOW[cleanLocale]);
11961
+ const agentRole = extractAgentRole(filePath);
11962
+ if (agentRole && modelConfig.agents[agentRole]) {
11963
+ const agentModels = modelConfig.agents[agentRole];
11964
+ const targetModel = agentModels[modelConfig.target];
11965
+ let result2 = templatedContent.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
11966
+ if (modelConfig.tools || modelConfig.permissions) {
11967
+ result2 = substituteToolNames(result2, modelConfig);
11968
+ }
11969
+ return result2;
11970
+ }
11971
+ const knownRoles = [
11972
+ "orchestrator",
11973
+ "task-coach",
11974
+ "architect",
11975
+ "implementer",
11976
+ "tester",
11977
+ "reviewer",
11978
+ "docs",
11979
+ "repo-explorer"
11980
+ ];
11981
+ let result = templatedContent;
11982
+ for (const role of knownRoles) {
11983
+ if (!modelConfig.agents[role])
11984
+ continue;
11985
+ const agentModels = modelConfig.agents[role];
11986
+ const sectionRegex = new RegExp(`(### ${role}[\\s\\S]*?)(?=### |## |$)`, "gi");
11987
+ const sectionMatch = result.match(sectionRegex);
11988
+ if (sectionMatch) {
11989
+ for (const section of sectionMatch) {
11990
+ const renderedSection = section.replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "");
11991
+ result = result.replace(section, renderedSection);
11992
+ }
11993
+ }
11994
+ }
11995
+ result = result.replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
11996
+ if (modelConfig.tools || modelConfig.permissions) {
11997
+ result = substituteToolNames(result, modelConfig);
11998
+ }
11999
+ return result;
12000
+ }
12001
+ function substituteToolNames(content, modelConfig) {
12002
+ if (!modelConfig.tools && !modelConfig.permissions)
12003
+ return content;
12004
+ const target = modelConfig.target;
12005
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12006
+ if (!fmMatch)
12007
+ return content;
12008
+ const frontmatter = fmMatch[1];
12009
+ if (target === "opencode" && modelConfig.permissions) {
12010
+ const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\r?\n?/m, "");
12011
+ return content.replace(fmMatch[0], `---
12012
+ ${updatedFrontmatter2}
12013
+ ---`);
12014
+ }
12015
+ if (!modelConfig.tools)
12016
+ return content;
12017
+ const updatedFrontmatter = frontmatter.replace(/^tools:\s*(.+)$/m, (_match, toolsLine) => {
12018
+ const baseNames = toolsLine.split(",").map((t) => t.trim());
12019
+ const mappedNames = baseNames.map((baseName) => {
12020
+ const toolMapping = modelConfig.tools?.[baseName];
12021
+ if (toolMapping && toolMapping[target]) {
12022
+ return toolMapping[target];
12023
+ }
12024
+ return baseName;
12025
+ });
12026
+ return `tools: ${mappedNames.join(", ")}`;
12027
+ });
12028
+ return content.replace(fmMatch[0], `---
12029
+ ${updatedFrontmatter}
12030
+ ---`);
12031
+ }
12032
+ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTemplate, modelConfig, locale) {
12033
+ if (strategy === "skip") {
12034
+ return { src: srcPath, dest: destPath, action: "skipped", dryRun };
12035
+ }
12036
+ let content;
12037
+ try {
12038
+ content = await readFile4(srcPath, "utf-8");
12039
+ } catch (e) {
12040
+ return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
12041
+ }
12042
+ const incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath, locale) : content;
12043
+ let finalContent = incomingContent;
12044
+ let action = "written";
12045
+ if (strategy === "append") {
12046
+ let existing = "";
12047
+ try {
12048
+ existing = await readFile4(destPath, "utf-8");
12049
+ } catch {}
12050
+ if (existing) {
12051
+ finalContent = existing + `
12052
+
12053
+ ---
12054
+
12055
+ ` + incomingContent;
12056
+ }
12057
+ action = "appended";
12058
+ } else if (strategy === "merge-json") {
12059
+ let existing = {};
12060
+ try {
12061
+ existing = JSON.parse(await readFile4(destPath, "utf-8"));
12062
+ } catch {}
12063
+ try {
12064
+ const incoming = JSON.parse(incomingContent);
12065
+ finalContent = JSON.stringify(mergeDeep(existing, incoming), null, 2);
12066
+ } catch (e) {
12067
+ return { src: srcPath, dest: destPath, action: "error", error: `JSON merge failed: ${e}` };
12068
+ }
12069
+ action = "merged";
12070
+ } else if (strategy === "merge-managed") {
12071
+ let existing = null;
12072
+ try {
12073
+ existing = await readFile4(destPath, "utf-8");
12074
+ } catch {}
12075
+ try {
12076
+ const merged = mergeManagedBlock(existing, incomingContent);
12077
+ finalContent = merged.content;
12078
+ action = merged.action;
12079
+ } catch (e) {
12080
+ return { src: srcPath, dest: destPath, action: "error", error: `Managed merge failed: ${e}` };
12081
+ }
12082
+ }
12083
+ if (dryRun) {
12084
+ return { src: srcPath, dest: destPath, action, dryRun: true };
12085
+ }
12086
+ try {
12087
+ await mkdir(dirname2(destPath), { recursive: true });
12088
+ await writeFile(destPath, finalContent, "utf-8");
12089
+ return { src: srcPath, dest: destPath, action };
12090
+ } catch (e) {
12091
+ return { src: srcPath, dest: destPath, action: "error", error: String(e) };
12092
+ }
12093
+ }
12094
+ async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun, force, modelConfig = null, locale = "en") {
12095
+ const results = [];
12096
+ for (const entry of manifest.entries) {
12097
+ const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
12098
+ let resolvedBaseDir = baseDir;
12099
+ let resolvedEntry = entry;
12100
+ if (manifest.target === "agy" && isGlobal) {
12101
+ resolvedBaseDir = join2(homedir(), ".gemini", "config");
12102
+ resolvedEntry = {
12103
+ ...entry,
12104
+ dest: entry.dest.replace(/^\.agents\/?/, "")
12105
+ };
12106
+ }
12107
+ const files = await resolveEntryFiles(resolvedEntry, presetsDir, resolvedBaseDir);
12108
+ const isTemplate = entry.template === true;
12109
+ for (const { src, dest } of files) {
12110
+ results.push(await applySingleFile(src, dest, strategy, force, dryRun, isTemplate, modelConfig, locale));
12111
+ }
12112
+ }
12113
+ return results;
12114
+ }
12115
+
12116
+ // src/core/presets/update-checker.ts
12117
+ function getTargetInstallationPath(target, basePath, isGlobal) {
12118
+ if (target === "agy") {
12119
+ return isGlobal ? resolve4(basePath, ".gemini", "config") : resolve4(basePath, ".agents");
12120
+ }
12121
+ return resolve4(basePath, `.${target}`);
12122
+ }
12123
+ async function isTargetInstalled(target, basePath, isGlobal) {
12124
+ const path = getTargetInstallationPath(target, basePath, isGlobal);
12125
+ try {
12126
+ const s = await stat2(path);
12127
+ return s.isDirectory();
12128
+ } catch {
12129
+ return false;
12130
+ }
12131
+ }
12132
+ async function validateAgentFileSizes(basePath, isGlobal) {
12133
+ const filesToCheck = [];
12134
+ filesToCheck.push(resolve4(basePath, ".claude", "CLAUDE.md"));
12135
+ filesToCheck.push(resolve4(basePath, ".codex", "AGENTS.md"));
12136
+ if (isGlobal) {
12137
+ filesToCheck.push(resolve4(basePath, ".gemini", "config", "AGENTS.md"));
12138
+ } else {
12139
+ filesToCheck.push(resolve4(basePath, ".agents", "AGENTS.md"));
12140
+ }
12141
+ const largeFiles = [];
12142
+ for (const filePath of filesToCheck) {
12143
+ try {
12144
+ const s = await stat2(filePath);
12145
+ if (s.isFile() && s.size > 40 * 1024) {
12146
+ largeFiles.push({ path: filePath, size: s.size });
12147
+ }
12148
+ } catch {}
12149
+ }
12150
+ return largeFiles;
12151
+ }
12152
+ async function fileContentDiffers(pathA, pathB) {
12153
+ try {
12154
+ const contentA = await readFile5(pathA, "utf-8");
12155
+ const contentB = await readFile5(pathB, "utf-8");
12156
+ return contentA.trim() !== contentB.trim();
12157
+ } catch {
12158
+ try {
12159
+ await stat2(pathB);
12160
+ return true;
12161
+ } catch {
12162
+ return false;
12163
+ }
12164
+ }
12165
+ }
12166
+ async function loadSkillsLock(basePath) {
12167
+ const paths = [
12168
+ resolve4(basePath, ".codeconductor", "skills-lock.json"),
12169
+ resolve4(basePath, ".agents", "skills-lock.json")
12170
+ ];
12171
+ for (const p of paths) {
12172
+ try {
12173
+ const content = await readFile5(p, "utf-8");
12174
+ return JSON.parse(content);
12175
+ } catch {}
12176
+ }
12177
+ return null;
12178
+ }
12179
+ async function getLatestSkillVersion(skillId) {
12180
+ const targets = [
12181
+ "opencode",
12182
+ "agy",
12183
+ "claude",
12184
+ "codex",
12185
+ "gemini",
12186
+ "cursor"
12187
+ ];
12188
+ for (const target of targets) {
12189
+ const skillPath = resolve4(ROOT_PRESETS_DIR, target, "skills", skillId, "SKILL.md");
12190
+ try {
12191
+ const content = await readFile5(skillPath, "utf-8");
12192
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12193
+ if (fmMatch) {
12194
+ const parsed = $parse(fmMatch[1]);
12195
+ if (parsed && parsed.id === skillId && parsed.version) {
12196
+ return String(parsed.version);
12197
+ }
12198
+ }
12199
+ } catch {}
12200
+ }
12201
+ return null;
12202
+ }
12203
+ async function checkUpdates(basePath, isGlobal) {
12204
+ const localCouncil = resolve4(basePath, ".codeconductor", "presets", "council.yml");
12205
+ const bundledCouncil = resolve4(SRC_PRESETS_DIR, "council", "council.yml");
12206
+ const councilHasUpdate = await fileContentDiffers(localCouncil, bundledCouncil);
12207
+ const localPolicy = resolve4(basePath, ".codeconductor", "presets", "policy.yml");
12208
+ const policyHasUpdate = await fileContentDiffers(localPolicy, POLICY_PATH);
12209
+ const targetsToCheck = [
12210
+ "opencode",
12211
+ "claude",
12212
+ "codex",
12213
+ "gemini",
12214
+ "cursor",
12215
+ "agy"
12216
+ ];
12217
+ const targetResults = [];
12218
+ let locale = "en";
12219
+ try {
12220
+ const configResult = await loadConfig(basePath);
12221
+ if (configResult.success) {
12222
+ locale = configResult.data.defaults.locale ?? "en";
12223
+ }
12224
+ } catch {}
12225
+ for (const target of targetsToCheck) {
12226
+ const isInstalled = await isTargetInstalled(target, basePath, isGlobal);
12227
+ if (!isInstalled) {
12228
+ continue;
12229
+ }
12230
+ let manifest;
12231
+ let modelConfig;
12232
+ try {
12233
+ manifest = await loadManifest(target);
12234
+ modelConfig = await loadModelConfig(target);
12235
+ } catch {
12236
+ continue;
12237
+ }
12238
+ const changedFiles = [];
12239
+ for (const entry of manifest.entries) {
12240
+ const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
12241
+ if (strategy === "skip")
12242
+ continue;
12243
+ let resolvedEntry = entry;
12244
+ let targetBaseDir = basePath;
12245
+ if (target === "agy" && isGlobal) {
12246
+ targetBaseDir = join3(homedir2(), ".gemini", "config");
12247
+ resolvedEntry = {
12248
+ ...entry,
12249
+ dest: entry.dest.replace(/^\.agents\/?/, "")
12250
+ };
12251
+ }
12252
+ const files = await resolveEntryFiles(resolvedEntry, PRESETS_DIR, targetBaseDir);
12253
+ const isTemplate = entry.template === true;
12254
+ for (const { src, dest } of files) {
12255
+ let expectedContent;
12256
+ try {
12257
+ const srcContent = await readFile5(src, "utf-8");
12258
+ expectedContent = isTemplate && modelConfig ? renderTemplate(srcContent, modelConfig, src, locale) : srcContent;
12259
+ } catch {
12260
+ continue;
12261
+ }
12262
+ let destContent;
12263
+ try {
12264
+ destContent = await readFile5(dest, "utf-8");
12265
+ } catch {
12266
+ changedFiles.push(dest);
12267
+ continue;
12268
+ }
12269
+ let fileHasUpdate = false;
12270
+ if (strategy === "overwrite") {
12271
+ if (destContent.trim() !== expectedContent.trim()) {
12272
+ fileHasUpdate = true;
12273
+ }
12274
+ } else if (strategy === "append") {
12275
+ if (!destContent.includes(expectedContent)) {
12276
+ fileHasUpdate = true;
12277
+ }
12278
+ } else if (strategy === "merge-json") {
12279
+ try {
12280
+ const destJson = JSON.parse(destContent);
12281
+ const expectedJson = JSON.parse(expectedContent);
12282
+ const merged = mergeDeep(destJson, expectedJson);
12283
+ if (JSON.stringify(merged) !== JSON.stringify(destJson)) {
12284
+ fileHasUpdate = true;
12285
+ }
12286
+ } catch {
12287
+ fileHasUpdate = true;
12288
+ }
12289
+ } else if (strategy === "merge-managed") {
12290
+ try {
12291
+ const merged = mergeManagedBlock(destContent, expectedContent);
12292
+ if (merged.content.trim() !== destContent.trim()) {
12293
+ fileHasUpdate = true;
12294
+ }
12295
+ } catch {
12296
+ fileHasUpdate = true;
12297
+ }
12298
+ }
12299
+ if (fileHasUpdate) {
12300
+ changedFiles.push(dest);
12301
+ }
12302
+ }
12303
+ }
12304
+ targetResults.push({
12305
+ target,
12306
+ hasUpdate: changedFiles.length > 0,
12307
+ files: changedFiles
12308
+ });
12309
+ }
12310
+ const skillResults = [];
12311
+ const skillsLock = await loadSkillsLock(basePath);
12312
+ if (skillsLock) {
12313
+ for (const [skillId, currentVersion] of Object.entries(skillsLock)) {
12314
+ const latestVersion = await getLatestSkillVersion(skillId);
12315
+ if (latestVersion && latestVersion !== currentVersion) {
12316
+ skillResults.push({
12317
+ id: skillId,
12318
+ currentVersion,
12319
+ latestVersion,
12320
+ hasUpdate: true
12321
+ });
12322
+ } else {
12323
+ skillResults.push({
12324
+ id: skillId,
12325
+ currentVersion,
12326
+ latestVersion: latestVersion || currentVersion,
12327
+ hasUpdate: false
12328
+ });
12329
+ }
12330
+ }
12331
+ }
12332
+ const hasPresetUpdates = councilHasUpdate || policyHasUpdate;
12333
+ const hasTargetUpdates = targetResults.some((t) => t.hasUpdate);
12334
+ const hasSkillUpdates = skillResults.some((s) => s.hasUpdate);
12335
+ return {
12336
+ hasUpdates: hasPresetUpdates || hasTargetUpdates || hasSkillUpdates,
12337
+ council: councilHasUpdate,
12338
+ policy: policyHasUpdate,
12339
+ targets: targetResults,
12340
+ skills: skillResults
12341
+ };
12342
+ }
12343
+
12344
+ // src/commands/doctor.command.ts
12345
+ async function doctorCommand(options) {
12346
+ const { projectRoot, output } = options;
12347
+ const checks = [];
12348
+ try {
12349
+ const hasConfig = await configExists(projectRoot);
12350
+ if (hasConfig) {
12351
+ checks.push({
12352
+ name: "config-exists",
12353
+ status: "pass",
12354
+ message: ".codeconductor/config.yml exists"
12355
+ });
12356
+ } else {
12357
+ checks.push({
12358
+ name: "config-exists",
12359
+ status: "fail",
12360
+ message: ".codeconductor/config.yml not found. Run `codeconductor init` first."
12361
+ });
12362
+ return {
12363
+ code: 4,
12364
+ data: {
12365
+ success: false,
12366
+ command: "doctor",
12367
+ checks
12368
+ }
12369
+ };
12370
+ }
12371
+ const configResult = await loadConfig(projectRoot);
12372
+ if (configResult.success) {
12373
+ checks.push({
12374
+ name: "config-valid",
12375
+ status: "pass",
12376
+ message: "Config is valid"
11545
12377
  });
11546
12378
  } else {
11547
12379
  checks.push({
@@ -11562,7 +12394,7 @@ async function doctorCommand(options) {
11562
12394
  for (const dir of runnerDirs) {
11563
12395
  try {
11564
12396
  const { access: access2 } = await import("node:fs/promises");
11565
- await access2(join(projectRoot, dir));
12397
+ await access2(join4(projectRoot, dir));
11566
12398
  checks.push({
11567
12399
  name: `dir-${dir}`,
11568
12400
  status: "pass",
@@ -11584,6 +12416,66 @@ async function doctorCommand(options) {
11584
12416
  message: `Council preset enabled (v${config.presets.council.version})`
11585
12417
  });
11586
12418
  }
12419
+ const localUpdates = await checkUpdates(projectRoot, false);
12420
+ const globalUpdates = await checkUpdates(homedir3(), true);
12421
+ const updateDetails = [];
12422
+ if (localUpdates.hasUpdates) {
12423
+ if (localUpdates.council)
12424
+ updateDetails.push("local council preset");
12425
+ if (localUpdates.policy)
12426
+ updateDetails.push("local policy");
12427
+ const updatedLocalTargets = localUpdates.targets.filter((t) => t.hasUpdate).map((t) => t.target);
12428
+ if (updatedLocalTargets.length > 0) {
12429
+ updateDetails.push(`local targets (${updatedLocalTargets.join(", ")})`);
12430
+ }
12431
+ const updatedLocalSkills = localUpdates.skills.filter((s) => s.hasUpdate).map((s) => s.id);
12432
+ if (updatedLocalSkills.length > 0) {
12433
+ updateDetails.push(`local skills (${updatedLocalSkills.join(", ")})`);
12434
+ }
12435
+ }
12436
+ if (globalUpdates.hasUpdates) {
12437
+ if (globalUpdates.council)
12438
+ updateDetails.push("global council preset");
12439
+ if (globalUpdates.policy)
12440
+ updateDetails.push("global policy");
12441
+ const updatedGlobalTargets = globalUpdates.targets.filter((t) => t.hasUpdate).map((t) => t.target);
12442
+ if (updatedGlobalTargets.length > 0) {
12443
+ updateDetails.push(`global targets (${updatedGlobalTargets.join(", ")})`);
12444
+ }
12445
+ const updatedGlobalSkills = globalUpdates.skills.filter((s) => s.hasUpdate).map((s) => s.id);
12446
+ if (updatedGlobalSkills.length > 0) {
12447
+ updateDetails.push(`global skills (${updatedGlobalSkills.join(", ")})`);
12448
+ }
12449
+ }
12450
+ if (updateDetails.length > 0) {
12451
+ checks.push({
12452
+ name: "updates-available",
12453
+ status: "warn",
12454
+ message: `Updates available for: ${updateDetails.join(", ")}`
12455
+ });
12456
+ } else {
12457
+ checks.push({
12458
+ name: "updates-available",
12459
+ status: "pass",
12460
+ message: "All presets, targets, and skills are up to date"
12461
+ });
12462
+ }
12463
+ const largeFilesLocal = await validateAgentFileSizes(projectRoot, false);
12464
+ const largeFilesGlobal = await validateAgentFileSizes(homedir3(), true);
12465
+ const allLargeFiles = [...largeFilesLocal, ...largeFilesGlobal];
12466
+ if (allLargeFiles.length > 0) {
12467
+ checks.push({
12468
+ name: "agent-file-sizes",
12469
+ status: "warn",
12470
+ message: `The following files exceed 40KB: ${allLargeFiles.map((f) => f.path).join(", ")}`
12471
+ });
12472
+ } else {
12473
+ checks.push({
12474
+ name: "agent-file-sizes",
12475
+ status: "pass",
12476
+ message: "All agent files (AGENTS.md/CLAUDE.md) are under 40KB"
12477
+ });
12478
+ }
11587
12479
  const securityCompatibility = await loadTargetSecurityCompatibility();
11588
12480
  for (const compatibility of securityCompatibility) {
11589
12481
  checks.push({
@@ -11625,13 +12517,13 @@ async function doctorCommand(options) {
11625
12517
  }
11626
12518
 
11627
12519
  // src/commands/init.command.ts
11628
- import { access as access3, mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
11629
- import { homedir } from "node:os";
11630
- import { basename, resolve as resolve4 } from "node:path";
12520
+ import { access as access3, mkdir as mkdir3, readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
12521
+ import { homedir as homedir4 } from "node:os";
12522
+ import { basename, resolve as resolve6 } from "node:path";
11631
12523
 
11632
12524
  // src/core/config/config-writer.ts
11633
- import { access as access2, mkdir, writeFile } from "node:fs/promises";
11634
- import { resolve as resolve3 } from "node:path";
12525
+ import { access as access2, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
12526
+ import { resolve as resolve5 } from "node:path";
11635
12527
 
11636
12528
  // src/core/config/codeconductor-config.ts
11637
12529
  var DEFAULT_CONFIG = {
@@ -11661,9 +12553,9 @@ var CONFIG_DIR = ".codeconductor";
11661
12553
  var CONFIG_FILE2 = "config.yml";
11662
12554
  async function writeConfig(projectRoot, config = {}, force = false) {
11663
12555
  try {
11664
- const configDir = resolve3(projectRoot, CONFIG_DIR);
11665
- await mkdir(configDir, { recursive: true });
11666
- const configPath = resolve3(configDir, CONFIG_FILE2);
12556
+ const configDir = resolve5(projectRoot, CONFIG_DIR);
12557
+ await mkdir2(configDir, { recursive: true });
12558
+ const configPath = resolve5(configDir, CONFIG_FILE2);
11667
12559
  if (!force) {
11668
12560
  try {
11669
12561
  await access2(configPath);
@@ -11691,7 +12583,7 @@ async function writeConfig(projectRoot, config = {}, force = false) {
11691
12583
  }
11692
12584
  };
11693
12585
  const yamlContent = $stringify(mergedConfig);
11694
- await writeFile(configPath, yamlContent, "utf-8");
12586
+ await writeFile2(configPath, yamlContent, "utf-8");
11695
12587
  return ok(undefined);
11696
12588
  } catch (error) {
11697
12589
  return err(new ValidationError("Failed to write config", error));
@@ -11699,10 +12591,17 @@ async function writeConfig(projectRoot, config = {}, force = false) {
11699
12591
  }
11700
12592
 
11701
12593
  // src/core/presets/preset-resolver.ts
11702
- var CURRENT_PRESET_VERSION = "v0.2.0";
12594
+ var CURRENT_PRESET_VERSION = "v0.3.0";
11703
12595
  function resolvePreset(target, profile) {
11704
12596
  const stack = resolveStack(profile);
11705
- const architecture = profile.signals.length > 0 ? "single-project" : "unknown";
12597
+ const isMonorepo = profile.signals.some((s) => [
12598
+ "pnpm-workspace.yaml",
12599
+ "lerna.json",
12600
+ "go.work",
12601
+ "package.json-workspaces",
12602
+ "Cargo.toml-workspace"
12603
+ ].includes(s));
12604
+ const architecture = isMonorepo ? "monorepo" : profile.signals.length > 0 ? "single-project" : "unknown";
11706
12605
  const warnings = [];
11707
12606
  if (profile.confidence === "low") {
11708
12607
  warnings.push("Detection confidence is low; review the selected preset before applying it.");
@@ -11732,6 +12631,20 @@ function resolveStack(profile) {
11732
12631
  return "django";
11733
12632
  if (profile.frameworks.includes("astro"))
11734
12633
  return "astro";
12634
+ if (profile.frameworks.includes("nextjs"))
12635
+ return "nextjs";
12636
+ if (profile.frameworks.includes("fastapi"))
12637
+ return "fastapi";
12638
+ if (profile.frameworks.includes("backend"))
12639
+ return "backend";
12640
+ if (profile.frameworks.includes("frontend"))
12641
+ return "frontend";
12642
+ if (profile.frameworks.includes("android"))
12643
+ return "android";
12644
+ if (profile.frameworks.includes("laravel"))
12645
+ return "laravel";
12646
+ if (profile.runtimes.includes("php"))
12647
+ return "php";
11735
12648
  if (profile.runtimes.includes("bun"))
11736
12649
  return "bun";
11737
12650
  if (profile.runtimes.includes("node"))
@@ -11741,24 +12654,24 @@ function resolveStack(profile) {
11741
12654
  function resolveAssets(target) {
11742
12655
  switch (target) {
11743
12656
  case "opencode":
11744
- return ["opencode.jsonc", "agents", "commands", "prompts/v0.2.0", "skills"];
12657
+ return ["opencode.jsonc", "agents", "commands", "prompts/v0.3.0", "skills"];
11745
12658
  case "claude":
11746
- return ["CLAUDE.md", "settings.json", "commands", "skills", "agents", "prompts/v0.2.0"];
12659
+ return ["CLAUDE.md", "settings.json", "commands", "skills", "agents", "prompts/v0.3.0"];
11747
12660
  case "codex":
11748
- return ["AGENTS.md", "skills", "prompts/v0.2.0"];
12661
+ return ["AGENTS.md", "skills", "prompts/v0.3.0"];
11749
12662
  case "gemini":
11750
- return ["agents", "prompts/v0.2.0"];
12663
+ return ["agents", "prompts/v0.3.0"];
11751
12664
  case "cursor":
11752
- return ["agents", "prompts/v0.2.0"];
12665
+ return ["agents", "prompts/v0.3.0"];
11753
12666
  case "agy":
11754
- return ["AGENTS.md", "rules", "workflows", "skills", "prompts/v0.2.0"];
12667
+ return ["AGENTS.md", "rules", "workflows", "skills", "prompts/v0.3.0"];
11755
12668
  }
11756
12669
  }
11757
12670
 
11758
12671
  // src/commands/init.command.ts
11759
12672
  async function initCommand(options) {
11760
12673
  const { projectRoot, dryRun, force, global: isGlobal, output, locale } = options;
11761
- const baseDir = isGlobal ? homedir() : projectRoot;
12674
+ const baseDir = isGlobal ? homedir4() : projectRoot;
11762
12675
  try {
11763
12676
  let profile = null;
11764
12677
  if (!isGlobal) {
@@ -11865,7 +12778,7 @@ async function initCommand(options) {
11865
12778
  }
11866
12779
  async function resolvePresetsToCopy() {
11867
12780
  const sources = [];
11868
- const bundledCouncil = resolve4(SRC_PRESETS_DIR, "council", "council.yml");
12781
+ const bundledCouncil = resolve6(SRC_PRESETS_DIR, "council", "council.yml");
11869
12782
  if (await fileExists2(bundledCouncil)) {
11870
12783
  sources.push({ name: "council.yml", sourcePath: bundledCouncil });
11871
12784
  }
@@ -11876,17 +12789,17 @@ async function resolvePresetsToCopy() {
11876
12789
  return sources;
11877
12790
  }
11878
12791
  async function copyPresets(baseDir, presets, force) {
11879
- const presetsDir = resolve4(baseDir, ".codeconductor", "presets");
11880
- await mkdir2(presetsDir, { recursive: true });
12792
+ const presetsDir = resolve6(baseDir, ".codeconductor", "presets");
12793
+ await mkdir3(presetsDir, { recursive: true });
11881
12794
  const copied = [];
11882
12795
  for (const preset of presets) {
11883
- const destPath = resolve4(presetsDir, preset.name);
12796
+ const destPath = resolve6(presetsDir, preset.name);
11884
12797
  if (!force && await fileExists2(destPath)) {
11885
12798
  continue;
11886
12799
  }
11887
12800
  try {
11888
- const content = await readFile3(preset.sourcePath, "utf-8");
11889
- await writeFile2(destPath, content, "utf-8");
12801
+ const content = await readFile6(preset.sourcePath, "utf-8");
12802
+ await writeFile3(destPath, content, "utf-8");
11890
12803
  copied.push(`.codeconductor/presets/${preset.name}`);
11891
12804
  } catch (err2) {
11892
12805
  const code = err2.code;
@@ -11908,8 +12821,8 @@ async function fileExists2(path) {
11908
12821
  }
11909
12822
 
11910
12823
  // src/commands/install.command.ts
11911
- import { homedir as homedir3 } from "node:os";
11912
- import { resolve as resolve7 } from "node:path";
12824
+ import { homedir as homedir5 } from "node:os";
12825
+ import { resolve as resolve8 } from "node:path";
11913
12826
 
11914
12827
  // src/domain/council/council-agent.ts
11915
12828
  function getResponsibilities(agentId) {
@@ -11967,281 +12880,63 @@ function generateAgentContent(agent) {
11967
12880
  ## Role
11968
12881
  ${agent.role}
11969
12882
 
11970
- ## Context
11971
- ${agent.context === "repo-readonly" ? "Can read repository but cannot modify files" : "Only receives prompts, no direct repository access"}
11972
-
11973
- ## Model Hint
11974
- ${agent.modelHint}
11975
-
11976
- ## Focus Areas
11977
- ${agent.focus.map((f) => `- ${f}`).join(`
11978
- `)}
11979
-
11980
- ## Responsibilities
11981
- ${getResponsibilities(agent.id)}
11982
- `;
11983
- }
11984
-
11985
- // src/adapters/claude/claude-council-generator.ts
11986
- function generateClaudeFiles(spec) {
11987
- const files = [];
11988
- files.push({
11989
- path: ".claude/skills/council/SKILL.md",
11990
- content: generateCouncilSkill(spec),
11991
- overwrite: false
11992
- });
11993
- for (const agent of spec.agents) {
11994
- files.push({
11995
- path: `.claude/agents/council-${agent.id}.md`,
11996
- content: generateAgentContent(agent),
11997
- overwrite: false
11998
- });
11999
- }
12000
- return files;
12001
- }
12002
- function generateCouncilSkill(spec) {
12003
- return `# Council Skill
12004
-
12005
- ## Description
12006
- ${spec.description}
12007
-
12008
- ## Version
12009
- ${spec.version}
12010
-
12011
- ## Agents
12012
- ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
12013
- `)}
12014
-
12015
- ## Usage
12016
- Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
12017
-
12018
- ## Context
12019
- ${spec.outputContract}
12020
- `;
12021
- }
12022
-
12023
- // src/adapters/claude/claude-installer.ts
12024
- class ClaudeInstaller {
12025
- name = "claude";
12026
- target = "claude";
12027
- spec = null;
12028
- setSpec(spec) {
12029
- this.spec = spec;
12030
- }
12031
- async generate() {
12032
- if (!this.spec) {
12033
- throw new Error("Council spec not set");
12034
- }
12035
- return generateClaudeFiles(this.spec);
12036
- }
12037
- async isAvailable() {
12038
- return true;
12039
- }
12040
- }
12041
- function createClaudeInstaller(spec) {
12042
- const installer = new ClaudeInstaller;
12043
- installer.setSpec(spec);
12044
- return installer;
12045
- }
12046
-
12047
- // src/adapters/codex/codex-council-generator.ts
12048
- function generateCodexFiles(spec) {
12049
- const files = [];
12050
- files.push({
12051
- path: ".codex/config.toml",
12052
- content: generateCodexConfig(spec),
12053
- overwrite: false
12054
- });
12055
- for (const agent of spec.agents) {
12056
- files.push({
12057
- path: `.codex/agents/council_${agent.id}.toml`,
12058
- content: generateCodexAgent(agent),
12059
- overwrite: true
12060
- });
12061
- }
12062
- files.push({
12063
- path: ".codex/skills/council/SKILL.md",
12064
- content: generateCodexSkill(spec),
12065
- overwrite: true
12066
- });
12067
- return files;
12068
- }
12069
- function generateCodexConfig(spec) {
12070
- const agentTables = spec.agents.map((agent) => {
12071
- const focusAreas = agent.focus.join(", ");
12072
- return `
12073
- [agents.${agent.id}]
12074
- description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
12075
- nickname_candidates = ["${agent.role}", "Council ${agent.role}"]`;
12076
- }).join(`
12077
- `);
12078
- return `# Codex Council Configuration
12079
-
12080
- [project]
12081
- name = "council"
12082
- version = "${spec.version}"
12083
- ${agentTables}
12084
- `;
12085
- }
12086
- function generateCodexAgent(agent) {
12087
- const focusAreas = agent.focus.join(", ");
12088
- return `name = "${agent.role}"
12089
- description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
12090
- nickname_candidates = ["${agent.role}", "Council ${agent.role}"]
12091
- developer_instructions = "You are the ${agent.role} council agent. Your focus areas are: ${focusAreas}. Context: ${agent.context}. Apply ${agent.modelHint} reasoning to your analysis."
12092
- `;
12093
- }
12094
- function generateCodexSkill(spec) {
12095
- return `---
12096
- name: council
12097
- description: ${spec.description}
12098
- version: ${spec.version}
12099
- ---
12100
-
12101
- ## Agents
12102
- ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
12103
- `)}
12104
-
12105
- ## Usage
12106
- Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
12107
- `;
12108
- }
12109
-
12110
- // src/adapters/codex/codex-installer.ts
12111
- class CodexInstaller {
12112
- name = "codex";
12113
- target = "codex";
12114
- spec = null;
12115
- setSpec(spec) {
12116
- this.spec = spec;
12117
- }
12118
- async generate() {
12119
- if (!this.spec) {
12120
- throw new Error("Council spec not set");
12121
- }
12122
- return generateCodexFiles(this.spec);
12123
- }
12124
- async isAvailable() {
12125
- return true;
12126
- }
12127
- }
12128
- function createCodexInstaller(spec) {
12129
- const installer = new CodexInstaller;
12130
- installer.setSpec(spec);
12131
- return installer;
12132
- }
12133
-
12134
- // src/adapters/opencode/opencode-council-generator.ts
12135
- function generateOpenCodeFiles(spec) {
12136
- const files = [];
12137
- files.push({
12138
- path: ".opencode/commands/cc-council.md",
12139
- content: generateCouncilCommand(spec),
12140
- overwrite: false
12141
- });
12142
- files.push({
12143
- path: ".opencode/agents/council-lead.md",
12144
- content: generateCouncilLead(spec),
12145
- overwrite: false
12146
- });
12147
- for (const agent of spec.agents) {
12148
- files.push({
12149
- path: `.opencode/agents/council-${agent.id}.md`,
12150
- content: generateOpenCodeAgentContent(agent),
12151
- overwrite: false
12152
- });
12153
- }
12154
- return files;
12155
- }
12156
- function generateCouncilCommand(spec) {
12157
- return `---
12158
- description: ${yamlString(spec.description)}
12159
- agent: council-lead
12160
- subtask: true
12161
- ---
12162
-
12163
- Run the CodeConductor council for multi-perspective analysis.
12164
-
12165
- ## Version
12166
- ${spec.version}
12167
-
12168
- ## Agents
12169
- ${spec.agents.map((a) => `- ${a.role} (${a.id})`).join(`
12170
- `)}
12171
-
12172
- ## Instructions
12173
- Coordinate with the council agents and synthesize their perspectives into the configured output contract.
12174
- `;
12175
- }
12176
- function generateCouncilLead(spec) {
12177
- return `---
12178
- description: ${yamlString(`${spec.description} Council lead. Coordinates council members and synthesizes recommendations.`)}
12179
- mode: subagent
12180
- permission:
12181
- read: allow
12182
- edit: deny
12183
- bash: deny
12184
- glob: allow
12185
- grep: allow
12186
- webfetch: deny
12187
- websearch: deny
12188
- ---
12189
-
12190
- # Council Lead Agent
12191
-
12192
- ## Role
12193
- Coordinates the council and synthesizes perspectives
12883
+ ## Context
12884
+ ${agent.context === "repo-readonly" ? "Can read repository but cannot modify files" : "Only receives prompts, no direct repository access"}
12194
12885
 
12195
- ## Version
12196
- ${spec.version}
12886
+ ## Model Hint
12887
+ ${agent.modelHint}
12197
12888
 
12198
- ## Council Members
12199
- ${spec.agents.map((a) => `- ${a.role}: ${a.focus.join(", ")}`).join(`
12889
+ ## Focus Areas
12890
+ ${agent.focus.map((f) => `- ${f}`).join(`
12200
12891
  `)}
12201
12892
 
12202
12893
  ## Responsibilities
12203
- - Coordinate agent responses
12204
- - Synthesize different perspectives
12205
- - Identify consensus and disagreements
12206
- - Provide final recommendation
12894
+ ${getResponsibilities(agent.id)}
12207
12895
  `;
12208
12896
  }
12209
- function generateOpenCodeAgentContent(agent) {
12210
- return `---
12211
- description: ${yamlString(`${agent.role} council agent. Focus: ${agent.focus.join(", ")}. Context: ${agent.context}. Model hint: ${agent.modelHint}.`)}
12212
- mode: subagent
12213
- permission:
12214
- ${generatePermissionBlock(agent.context)}
12215
- ---
12216
12897
 
12217
- ${generateAgentContent(agent)}`;
12218
- }
12219
- function generatePermissionBlock(context) {
12220
- if (context === "repo-readonly") {
12221
- return ` read: allow
12222
- edit: deny
12223
- bash: deny
12224
- glob: allow
12225
- grep: allow
12226
- webfetch: deny
12227
- websearch: deny`;
12898
+ // src/adapters/claude/claude-council-generator.ts
12899
+ function generateClaudeFiles(spec) {
12900
+ const files = [];
12901
+ files.push({
12902
+ path: ".claude/skills/council/SKILL.md",
12903
+ content: generateCouncilSkill(spec),
12904
+ overwrite: false
12905
+ });
12906
+ for (const agent of spec.agents) {
12907
+ files.push({
12908
+ path: `.claude/agents/council-${agent.id}.md`,
12909
+ content: generateAgentContent(agent),
12910
+ overwrite: false
12911
+ });
12228
12912
  }
12229
- return ` read: deny
12230
- edit: deny
12231
- bash: deny
12232
- glob: deny
12233
- grep: deny
12234
- webfetch: deny
12235
- websearch: deny`;
12913
+ return files;
12236
12914
  }
12237
- function yamlString(value) {
12238
- return JSON.stringify(value);
12915
+ function generateCouncilSkill(spec) {
12916
+ return `# Council Skill
12917
+
12918
+ ## Description
12919
+ ${spec.description}
12920
+
12921
+ ## Version
12922
+ ${spec.version}
12923
+
12924
+ ## Agents
12925
+ ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
12926
+ `)}
12927
+
12928
+ ## Usage
12929
+ Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
12930
+
12931
+ ## Context
12932
+ ${spec.outputContract}
12933
+ `;
12239
12934
  }
12240
12935
 
12241
- // src/adapters/opencode/opencode-installer.ts
12242
- class OpenCodeInstaller {
12243
- name = "opencode";
12244
- target = "opencode";
12936
+ // src/adapters/claude/claude-installer.ts
12937
+ class ClaudeInstaller {
12938
+ name = "claude";
12939
+ target = "claude";
12245
12940
  spec = null;
12246
12941
  setSpec(spec) {
12247
12942
  this.spec = spec;
@@ -12250,467 +12945,299 @@ class OpenCodeInstaller {
12250
12945
  if (!this.spec) {
12251
12946
  throw new Error("Council spec not set");
12252
12947
  }
12253
- return generateOpenCodeFiles(this.spec);
12948
+ return generateClaudeFiles(this.spec);
12254
12949
  }
12255
12950
  async isAvailable() {
12256
12951
  return true;
12257
12952
  }
12258
12953
  }
12259
- function createOpenCodeInstaller(spec) {
12260
- const installer = new OpenCodeInstaller;
12954
+ function createClaudeInstaller(spec) {
12955
+ const installer = new ClaudeInstaller;
12261
12956
  installer.setSpec(spec);
12262
12957
  return installer;
12263
12958
  }
12264
12959
 
12265
- // src/core/filesystem/file-writer.ts
12266
- import { access as access4, mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
12267
- import { dirname as dirname2 } from "node:path";
12268
- init_safety();
12269
- async function writeGeneratedFiles(files, options) {
12270
- const results = [];
12271
- for (const file of files) {
12272
- if (!validateWritePath(file.path)) {
12273
- results.push({
12274
- path: file.path,
12275
- success: false,
12276
- error: "Protected path"
12277
- });
12278
- continue;
12279
- }
12280
- if (options.dryRun) {
12281
- results.push({
12282
- path: file.path,
12283
- success: true
12284
- });
12285
- continue;
12286
- }
12287
- if (!options.force) {
12288
- try {
12289
- await access4(file.path);
12290
- results.push({
12291
- path: file.path,
12292
- success: false,
12293
- error: "File exists, use --force to overwrite"
12294
- });
12295
- continue;
12296
- } catch {}
12297
- }
12298
- try {
12299
- const dir = dirname2(file.path);
12300
- await mkdir3(dir, { recursive: true });
12301
- await writeFile3(file.path, file.content, "utf-8");
12302
- results.push({
12303
- path: file.path,
12304
- success: true
12305
- });
12306
- } catch (error) {
12307
- results.push({
12308
- path: file.path,
12309
- success: false,
12310
- error: String(error)
12311
- });
12312
- }
12960
+ // src/adapters/codex/codex-council-generator.ts
12961
+ function generateCodexFiles(spec) {
12962
+ const files = [];
12963
+ files.push({
12964
+ path: ".codex/config.toml",
12965
+ content: generateCodexConfig(spec),
12966
+ overwrite: false
12967
+ });
12968
+ for (const agent of spec.agents) {
12969
+ files.push({
12970
+ path: `.codex/agents/council_${agent.id}.toml`,
12971
+ content: generateCodexAgent(agent),
12972
+ overwrite: true
12973
+ });
12313
12974
  }
12314
- return results;
12975
+ files.push({
12976
+ path: ".codex/skills/council/SKILL.md",
12977
+ content: generateCodexSkill(spec),
12978
+ overwrite: true
12979
+ });
12980
+ return files;
12315
12981
  }
12982
+ function generateCodexConfig(spec) {
12983
+ const agentTables = spec.agents.map((agent) => {
12984
+ const focusAreas = agent.focus.join(", ");
12985
+ return `
12986
+ [agents.${agent.id}]
12987
+ description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
12988
+ nickname_candidates = ["${agent.role}", "Council ${agent.role}"]`;
12989
+ }).join(`
12990
+ `);
12991
+ return `# Codex Council Configuration
12316
12992
 
12317
- // src/core/presets/file-copier.ts
12318
- import { mkdir as mkdir4, readdir, readFile as readFile4, stat, writeFile as writeFile4 } from "node:fs/promises";
12319
- import { homedir as homedir2 } from "node:os";
12320
- import { dirname as dirname3, join as join2, relative, resolve as resolve5 } from "node:path";
12321
-
12322
- // src/core/i18n/language-instructions.ts
12323
- var SUPPORTED_LOCALES = ["en", "es"];
12324
- var LANGUAGE_INSTRUCTIONS = {
12325
- en: 'Prose/docs/code comments: be terse and direct. Prefer concrete nouns over abstract ones. Omit filler phrases ("note that", "please", "as mentioned"). One idea per sentence.',
12326
- es: "Spanish prose/docs/reports/Markdown: preserve natural Spanish orthography, including accents, `ñ`, `¿`, `¡`, and normal Unicode. The ASCII-only editing preference does not apply to these artifacts."
12327
- };
12328
- var COMMIT_STYLE = {
12329
- en: `---
12330
- trigger: always_on
12331
- description: Rules for generating commit messages using Conventional Commits format
12332
- ---
12333
-
12334
- ## Git Commit Messages
12335
-
12336
- ### Format
12337
-
12338
- \`\`\`
12339
- <type>(<scope>): <short description>
12340
-
12341
- - <detail 1>
12342
- - <detail 2>
12343
- \`\`\`
12344
-
12345
- ### Valid Types
12346
-
12347
- \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
12348
- \`revert\`
12349
-
12350
- ### Rules
12351
-
12352
- - Language: **neutral English** always
12353
- - Header: maximum 69 characters, no trailing period
12354
- - Body: concise bullet points, one idea per line
12355
- - Footer: only for breaking changes or issues
12356
- - No gerunds ("adding", "fixing")
12357
- - Use infinitive or imperative ("add", "fix", "update")`,
12358
- es: `---
12359
- trigger: always_on
12360
- description: Reglas para generar mensajes de commit con formato Conventional Commits
12361
- ---
12362
-
12363
- ## Git Commit Messages
12364
-
12365
- ### Formato
12366
-
12367
- \`\`\`
12368
- <tipo>(<scope>): <descripcion corta>
12369
-
12370
- - <detalle 1>
12371
- - <detalle 2>
12372
- \`\`\`
12373
-
12374
- ### Tipos validos
12375
-
12376
- \`feat\` \`fix\` \`docs\` \`style\` \`refactor\` \`test\` \`chore\` \`perf\` \`ci\` \`build\`
12377
- \`revert\`
12378
-
12379
- ### Reglas
12380
-
12381
- - Idioma: **inglés neutro** siempre
12382
- - Encabezado: maximo 69 caracteres, sin punto final
12383
- - Cuerpo: viñetas concisas, una idea por linea
12384
- - Footer: solo para breaking changes o issues
12385
- - Sin gerundios ("agregando", "corrigiendo")
12386
- - Usar infinitivo o imperativo ("agregar", "corregir", "actualizar")`
12387
- };
12388
- var COMMIT_WORKFLOW = {
12389
- en: `---
12390
- name: commit
12391
- description: Generates a commit in English following Conventional Commits based on staged changes
12392
- ---
12393
-
12394
- // turbo-all
12395
-
12396
- ## Steps
12397
-
12398
- 1. View staged changes: \`git diff --cached --stat\`
12399
- 2. If nothing is staged, suggest \`git add .\` or specific files
12400
- 3. Get full diff: \`git diff --cached\`
12401
- 4. Check recent style: \`git log -n 3 --oneline\`
12402
- 5. Generate message following \`.agents/rules/commit-style.md\`
12403
- 6. Show proposal and confirm with the user
12404
- 7. Execute: \`git commit -m "<message>"\`
12405
- 8. Confirm success: \`git status\``,
12406
- es: `---
12407
- name: commit
12408
- description: Genera un commit en inglés siguiendo Conventional Commits basado en los cambios staged
12409
- ---
12410
-
12411
- // turbo-all
12993
+ [project]
12994
+ name = "council"
12995
+ version = "${spec.version}"
12996
+ ${agentTables}
12997
+ `;
12998
+ }
12999
+ function generateCodexAgent(agent) {
13000
+ const focusAreas = agent.focus.join(", ");
13001
+ return `name = "${agent.role}"
13002
+ description = "${agent.role} council agent. Focus: ${focusAreas}. Context: ${agent.context}. Model hint: ${agent.modelHint}."
13003
+ nickname_candidates = ["${agent.role}", "Council ${agent.role}"]
13004
+ developer_instructions = "You are the ${agent.role} council agent. Your focus areas are: ${focusAreas}. Context: ${agent.context}. Apply ${agent.modelHint} reasoning to your analysis."
13005
+ `;
13006
+ }
13007
+ function generateCodexSkill(spec) {
13008
+ return `---
13009
+ name: council
13010
+ description: ${spec.description}
13011
+ version: ${spec.version}
13012
+ ---
12412
13013
 
12413
- ## Pasos
13014
+ ## Agents
13015
+ ${spec.agents.map((a) => `- **${a.role}** (${a.id}): ${a.focus.join(", ")}`).join(`
13016
+ `)}
12414
13017
 
12415
- 1. Ver cambios staged: \`git diff --cached --stat\`
12416
- 2. Si no hay nada staged, sugiere \`git add .\` o archivos específicos
12417
- 3. Obtener diff completo: \`git diff --cached\`
12418
- 4. Ver estilo reciente: \`git log -n 3 --oneline\`
12419
- 5. Generar mensaje siguiendo \`.agents/rules/commit-style.md\`
12420
- 6. Mostrar propuesta y confirmar con el usuario
12421
- 7. Ejecutar: \`git commit -m "<mensaje>"\`
12422
- 8. Confirmar éxito: \`git status\``
12423
- };
12424
- function getLanguageInstruction(locale) {
12425
- const key = SUPPORTED_LOCALES.includes(locale) ? locale : "en";
12426
- return `- ${LANGUAGE_INSTRUCTIONS[key]}`;
13018
+ ## Usage
13019
+ Use the council agents to get multi-perspective analysis on code changes, architecture decisions, and security reviews.
13020
+ `;
12427
13021
  }
12428
- var LOCALE_PLACEHOLDER = "{{LANGUAGE_INSTRUCTIONS}}";
12429
13022
 
12430
- // src/core/filesystem/safe-merger.ts
12431
- var MANAGED_BEGIN_MARKER = "<!-- CODECONDUCTOR:BEGIN managed -->";
12432
- var MANAGED_END_MARKER = "<!-- CODECONDUCTOR:END managed -->";
12433
- function mergeManagedBlock(existing, incoming) {
12434
- validateMarkers(incoming, "incoming content");
12435
- if (!existing) {
12436
- return { content: incoming, action: "written" };
13023
+ // src/adapters/codex/codex-installer.ts
13024
+ class CodexInstaller {
13025
+ name = "codex";
13026
+ target = "codex";
13027
+ spec = null;
13028
+ setSpec(spec) {
13029
+ this.spec = spec;
12437
13030
  }
12438
- validateMarkers(existing, "existing content");
12439
- const existingBlock = getManagedBlockRange(existing);
12440
- const incomingBlock = getManagedBlockRange(incoming);
12441
- return {
12442
- content: existing.slice(0, existingBlock.start) + incoming.slice(incomingBlock.start, incomingBlock.end) + existing.slice(existingBlock.end),
12443
- action: "merged"
12444
- };
12445
- }
12446
- function validateMarkers(content, label) {
12447
- const beginCount = countOccurrences(content, MANAGED_BEGIN_MARKER);
12448
- const endCount = countOccurrences(content, MANAGED_END_MARKER);
12449
- if (beginCount !== 1 || endCount !== 1) {
12450
- throw new Error(`${label} must contain exactly one managed begin marker and one managed end marker`);
13031
+ async generate() {
13032
+ if (!this.spec) {
13033
+ throw new Error("Council spec not set");
13034
+ }
13035
+ return generateCodexFiles(this.spec);
12451
13036
  }
12452
- if (content.indexOf(MANAGED_BEGIN_MARKER) > content.indexOf(MANAGED_END_MARKER)) {
12453
- throw new Error(`${label} has managed markers in the wrong order`);
13037
+ async isAvailable() {
13038
+ return true;
12454
13039
  }
12455
13040
  }
12456
- function getManagedBlockRange(content) {
12457
- const start = content.indexOf(MANAGED_BEGIN_MARKER);
12458
- const end = content.indexOf(MANAGED_END_MARKER) + MANAGED_END_MARKER.length;
12459
- return { start, end };
12460
- }
12461
- function countOccurrences(content, needle) {
12462
- return content.split(needle).length - 1;
13041
+ function createCodexInstaller(spec) {
13042
+ const installer = new CodexInstaller;
13043
+ installer.setSpec(spec);
13044
+ return installer;
12463
13045
  }
12464
13046
 
12465
- // src/core/presets/file-copier.ts
12466
- async function listFilesRecursive(dir, base = dir) {
12467
- const entries = await readdir(dir, { withFileTypes: true });
13047
+ // src/adapters/opencode/opencode-council-generator.ts
13048
+ function generateOpenCodeFiles(spec) {
12468
13049
  const files = [];
12469
- for (const entry of entries) {
12470
- const full = join2(dir, entry.name);
12471
- if (entry.isDirectory()) {
12472
- files.push(...await listFilesRecursive(full, base));
12473
- } else {
12474
- files.push(relative(base, full));
12475
- }
13050
+ files.push({
13051
+ path: ".opencode/commands/cc-council.md",
13052
+ content: generateCouncilCommand(spec),
13053
+ overwrite: false
13054
+ });
13055
+ files.push({
13056
+ path: ".opencode/agents/council-lead.md",
13057
+ content: generateCouncilLead(spec),
13058
+ overwrite: false
13059
+ });
13060
+ for (const agent of spec.agents) {
13061
+ files.push({
13062
+ path: `.opencode/agents/council-${agent.id}.md`,
13063
+ content: generateOpenCodeAgentContent(agent),
13064
+ overwrite: false
13065
+ });
12476
13066
  }
12477
13067
  return files;
12478
13068
  }
12479
- async function resolveEntryFiles(entry, presetsDir, baseDir) {
12480
- const srcAbsolute = resolve5(presetsDir, entry.src);
12481
- try {
12482
- const s = await stat(srcAbsolute);
12483
- if (s.isDirectory()) {
12484
- const files = await listFilesRecursive(srcAbsolute);
12485
- return files.map((f) => ({
12486
- src: join2(srcAbsolute, f),
12487
- dest: resolve5(baseDir, entry.dest, f)
12488
- }));
12489
- }
12490
- return [{ src: srcAbsolute, dest: resolve5(baseDir, entry.dest) }];
12491
- } catch {
12492
- return [];
12493
- }
12494
- }
12495
- function mergeDeep(target, source) {
12496
- const result = { ...target };
12497
- for (const key of Object.keys(source)) {
12498
- const srcVal = source[key];
12499
- const tgtVal = target[key];
12500
- if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
12501
- result[key] = [...new Set([...tgtVal, ...srcVal])];
12502
- } else if (srcVal && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
12503
- result[key] = mergeDeep(tgtVal, srcVal);
12504
- } else {
12505
- result[key] = srcVal;
12506
- }
12507
- }
12508
- return result;
13069
+ function generateCouncilCommand(spec) {
13070
+ return `---
13071
+ description: ${yamlString(spec.description)}
13072
+ agent: council-lead
13073
+ subtask: true
13074
+ ---
13075
+
13076
+ Run the CodeConductor council for multi-perspective analysis.
13077
+
13078
+ ## Version
13079
+ ${spec.version}
13080
+
13081
+ ## Agents
13082
+ ${spec.agents.map((a) => `- ${a.role} (${a.id})`).join(`
13083
+ `)}
13084
+
13085
+ ## Instructions
13086
+ Coordinate with the council agents and synthesize their perspectives into the configured output contract.
13087
+ `;
12509
13088
  }
12510
- function extractAgentRole(filePath) {
12511
- const parts = filePath.replace(/\\/g, "/").split("/");
12512
- const basename2 = parts[parts.length - 1];
12513
- const name = basename2.replace(/\.md$/, "");
12514
- const knownRoles = [
12515
- "architect",
12516
- "implementer",
12517
- "tester",
12518
- "orchestrator",
12519
- "reviewer",
12520
- "docs",
12521
- "task-coach",
12522
- "repo-explorer"
12523
- ];
12524
- return knownRoles.includes(name) ? name : null;
13089
+ function generateCouncilLead(spec) {
13090
+ return `---
13091
+ description: ${yamlString(`${spec.description} Council lead. Coordinates council members and synthesizes recommendations.`)}
13092
+ mode: subagent
13093
+ permission:
13094
+ read: allow
13095
+ edit: deny
13096
+ bash: deny
13097
+ glob: allow
13098
+ grep: allow
13099
+ webfetch: deny
13100
+ websearch: deny
13101
+ ---
13102
+
13103
+ # Council Lead Agent
13104
+
13105
+ ## Role
13106
+ Coordinates the council and synthesizes perspectives
13107
+
13108
+ ## Version
13109
+ ${spec.version}
13110
+
13111
+ ## Council Members
13112
+ ${spec.agents.map((a) => `- ${a.role}: ${a.focus.join(", ")}`).join(`
13113
+ `)}
13114
+
13115
+ ## Responsibilities
13116
+ - Coordinate agent responses
13117
+ - Synthesize different perspectives
13118
+ - Identify consensus and disagreements
13119
+ - Provide final recommendation
13120
+ `;
12525
13121
  }
12526
- function renderTemplate(content, modelConfig, filePath, locale = "en") {
12527
- const cleanLocale = locale === "es" || locale === "en" ? locale : "en";
12528
- const templatedContent = content.replace(/\{\{COMMIT_STYLE\}\}/g, COMMIT_STYLE[cleanLocale]).replace(/\{\{COMMIT_WORKFLOW\}\}/g, COMMIT_WORKFLOW[cleanLocale]);
12529
- const agentRole = extractAgentRole(filePath);
12530
- if (agentRole && modelConfig.agents[agentRole]) {
12531
- const agentModels = modelConfig.agents[agentRole];
12532
- const targetModel = agentModels[modelConfig.target];
12533
- let result2 = templatedContent.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
12534
- if (modelConfig.tools || modelConfig.permissions) {
12535
- result2 = substituteToolNames(result2, modelConfig);
12536
- }
12537
- return result2;
12538
- }
12539
- const knownRoles = [
12540
- "orchestrator",
12541
- "task-coach",
12542
- "architect",
12543
- "implementer",
12544
- "tester",
12545
- "reviewer",
12546
- "docs",
12547
- "repo-explorer"
12548
- ];
12549
- let result = templatedContent;
12550
- for (const role of knownRoles) {
12551
- if (!modelConfig.agents[role])
12552
- continue;
12553
- const agentModels = modelConfig.agents[role];
12554
- const sectionRegex = new RegExp(`(### ${role}[\\s\\S]*?)(?=### |## |$)`, "gi");
12555
- const sectionMatch = result.match(sectionRegex);
12556
- if (sectionMatch) {
12557
- for (const section of sectionMatch) {
12558
- const renderedSection = section.replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "");
12559
- result = result.replace(section, renderedSection);
12560
- }
12561
- }
12562
- }
12563
- result = result.replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
12564
- if (modelConfig.tools || modelConfig.permissions) {
12565
- result = substituteToolNames(result, modelConfig);
12566
- }
12567
- return result;
13122
+ function generateOpenCodeAgentContent(agent) {
13123
+ return `---
13124
+ description: ${yamlString(`${agent.role} council agent. Focus: ${agent.focus.join(", ")}. Context: ${agent.context}. Model hint: ${agent.modelHint}.`)}
13125
+ mode: subagent
13126
+ permission:
13127
+ ${generatePermissionBlock(agent.context)}
13128
+ ---
13129
+
13130
+ ${generateAgentContent(agent)}`;
12568
13131
  }
12569
- function substituteToolNames(content, modelConfig) {
12570
- if (!modelConfig.tools && !modelConfig.permissions)
12571
- return content;
12572
- const target = modelConfig.target;
12573
- const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12574
- if (!fmMatch)
12575
- return content;
12576
- const frontmatter = fmMatch[1];
12577
- if (target === "opencode" && modelConfig.permissions) {
12578
- const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\r?\n?/m, "");
12579
- return content.replace(fmMatch[0], `---
12580
- ${updatedFrontmatter2}
12581
- ---`);
13132
+ function generatePermissionBlock(context) {
13133
+ if (context === "repo-readonly") {
13134
+ return ` read: allow
13135
+ edit: deny
13136
+ bash: deny
13137
+ glob: allow
13138
+ grep: allow
13139
+ webfetch: deny
13140
+ websearch: deny`;
12582
13141
  }
12583
- if (!modelConfig.tools)
12584
- return content;
12585
- const updatedFrontmatter = frontmatter.replace(/^tools:\s*(.+)$/m, (_match, toolsLine) => {
12586
- const baseNames = toolsLine.split(",").map((t) => t.trim());
12587
- const mappedNames = baseNames.map((baseName) => {
12588
- const toolMapping = modelConfig.tools?.[baseName];
12589
- if (toolMapping && toolMapping[target]) {
12590
- return toolMapping[target];
12591
- }
12592
- return baseName;
12593
- });
12594
- return `tools: ${mappedNames.join(", ")}`;
12595
- });
12596
- return content.replace(fmMatch[0], `---
12597
- ${updatedFrontmatter}
12598
- ---`);
13142
+ return ` read: deny
13143
+ edit: deny
13144
+ bash: deny
13145
+ glob: deny
13146
+ grep: deny
13147
+ webfetch: deny
13148
+ websearch: deny`;
13149
+ }
13150
+ function yamlString(value) {
13151
+ return JSON.stringify(value);
12599
13152
  }
12600
- async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTemplate, modelConfig, locale) {
12601
- if (strategy === "skip") {
12602
- return { src: srcPath, dest: destPath, action: "skipped", dryRun };
12603
- }
12604
- let content;
12605
- try {
12606
- content = await readFile4(srcPath, "utf-8");
12607
- } catch (e) {
12608
- return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
12609
- }
12610
- const incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath, locale) : content;
12611
- let finalContent = incomingContent;
12612
- let action = "written";
12613
- if (strategy === "append") {
12614
- let existing = "";
12615
- try {
12616
- existing = await readFile4(destPath, "utf-8");
12617
- } catch {}
12618
- if (existing) {
12619
- finalContent = existing + `
12620
-
12621
- ---
12622
13153
 
12623
- ` + incomingContent;
12624
- }
12625
- action = "appended";
12626
- } else if (strategy === "merge-json") {
12627
- let existing = {};
12628
- try {
12629
- existing = JSON.parse(await readFile4(destPath, "utf-8"));
12630
- } catch {}
12631
- try {
12632
- const incoming = JSON.parse(incomingContent);
12633
- finalContent = JSON.stringify(mergeDeep(existing, incoming), null, 2);
12634
- } catch (e) {
12635
- return { src: srcPath, dest: destPath, action: "error", error: `JSON merge failed: ${e}` };
12636
- }
12637
- action = "merged";
12638
- } else if (strategy === "merge-managed") {
12639
- let existing = null;
12640
- try {
12641
- existing = await readFile4(destPath, "utf-8");
12642
- } catch {}
12643
- try {
12644
- const merged = mergeManagedBlock(existing, incomingContent);
12645
- finalContent = merged.content;
12646
- action = merged.action;
12647
- } catch (e) {
12648
- return { src: srcPath, dest: destPath, action: "error", error: `Managed merge failed: ${e}` };
12649
- }
13154
+ // src/adapters/opencode/opencode-installer.ts
13155
+ class OpenCodeInstaller {
13156
+ name = "opencode";
13157
+ target = "opencode";
13158
+ spec = null;
13159
+ setSpec(spec) {
13160
+ this.spec = spec;
12650
13161
  }
12651
- if (dryRun) {
12652
- return { src: srcPath, dest: destPath, action, dryRun: true };
13162
+ async generate() {
13163
+ if (!this.spec) {
13164
+ throw new Error("Council spec not set");
13165
+ }
13166
+ return generateOpenCodeFiles(this.spec);
12653
13167
  }
12654
- try {
12655
- await mkdir4(dirname3(destPath), { recursive: true });
12656
- await writeFile4(destPath, finalContent, "utf-8");
12657
- return { src: srcPath, dest: destPath, action };
12658
- } catch (e) {
12659
- return { src: srcPath, dest: destPath, action: "error", error: String(e) };
13168
+ async isAvailable() {
13169
+ return true;
12660
13170
  }
12661
13171
  }
12662
- async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun, force, modelConfig = null, locale = "en") {
13172
+ function createOpenCodeInstaller(spec) {
13173
+ const installer = new OpenCodeInstaller;
13174
+ installer.setSpec(spec);
13175
+ return installer;
13176
+ }
13177
+
13178
+ // src/core/filesystem/file-writer.ts
13179
+ import { access as access4, mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
13180
+ import { dirname as dirname3 } from "node:path";
13181
+ init_safety();
13182
+ async function writeGeneratedFiles(files, options) {
12663
13183
  const results = [];
12664
- for (const entry of manifest.entries) {
12665
- const strategy = isGlobal && entry.globalStrategy ? entry.globalStrategy : entry.strategy;
12666
- let resolvedBaseDir = baseDir;
12667
- let resolvedEntry = entry;
12668
- if (manifest.target === "agy" && isGlobal) {
12669
- resolvedBaseDir = join2(homedir2(), ".gemini", "config");
12670
- resolvedEntry = {
12671
- ...entry,
12672
- dest: entry.dest.replace(/^\.agents\/?/, "")
12673
- };
13184
+ for (const file of files) {
13185
+ if (!validateWritePath(file.path)) {
13186
+ results.push({
13187
+ path: file.path,
13188
+ success: false,
13189
+ error: "Protected path"
13190
+ });
13191
+ continue;
12674
13192
  }
12675
- const files = await resolveEntryFiles(resolvedEntry, presetsDir, resolvedBaseDir);
12676
- const isTemplate = entry.template === true;
12677
- for (const { src, dest } of files) {
12678
- results.push(await applySingleFile(src, dest, strategy, force, dryRun, isTemplate, modelConfig, locale));
13193
+ if (options.dryRun) {
13194
+ results.push({
13195
+ path: file.path,
13196
+ success: true
13197
+ });
13198
+ continue;
13199
+ }
13200
+ if (!options.force) {
13201
+ try {
13202
+ await access4(file.path);
13203
+ results.push({
13204
+ path: file.path,
13205
+ success: false,
13206
+ error: "File exists, use --force to overwrite"
13207
+ });
13208
+ continue;
13209
+ } catch {}
13210
+ }
13211
+ try {
13212
+ const dir = dirname3(file.path);
13213
+ await mkdir4(dir, { recursive: true });
13214
+ await writeFile4(file.path, file.content, "utf-8");
13215
+ results.push({
13216
+ path: file.path,
13217
+ success: true
13218
+ });
13219
+ } catch (error) {
13220
+ results.push({
13221
+ path: file.path,
13222
+ success: false,
13223
+ error: String(error)
13224
+ });
12679
13225
  }
12680
13226
  }
12681
13227
  return results;
12682
13228
  }
12683
13229
 
12684
- // src/core/presets/manifest-loader.ts
12685
- import { readFile as readFile5 } from "node:fs/promises";
12686
- import { join as join3 } from "node:path";
12687
- var MANIFESTS_DIR = join3(SRC_PRESETS_DIR, "manifests");
12688
- var MODELS_DIR = join3(SRC_PRESETS_DIR, "models");
12689
- var PRESETS_DIR = ROOT_PRESETS_DIR;
12690
- async function loadManifest(target) {
12691
- const manifestPath = join3(MANIFESTS_DIR, `${target}.yml`);
12692
- const content = await readFile5(manifestPath, "utf-8");
12693
- const data = $parse(content);
12694
- return InstallManifestSchema.parse(data);
12695
- }
12696
- async function loadModelConfig(target) {
12697
- const modelPath = join3(MODELS_DIR, `${target}.yml`);
12698
- const content = await readFile5(modelPath, "utf-8");
12699
- const data = $parse(content);
12700
- return ModelConfigSchema.parse(data);
12701
- }
12702
-
12703
13230
  // src/core/presets/preset-loader.ts
12704
- import { readFile as readFile6 } from "node:fs/promises";
12705
- import { resolve as resolve6 } from "node:path";
13231
+ import { readFile as readFile7 } from "node:fs/promises";
13232
+ import { resolve as resolve7 } from "node:path";
12706
13233
  async function loadPreset(name, projectRoot = process.cwd()) {
12707
13234
  const candidates = [
12708
- resolve6(projectRoot, ".codeconductor", "presets", `${name}.yml`),
12709
- resolve6(SRC_PRESETS_DIR, name, `${name}.yml`)
13235
+ resolve7(projectRoot, ".codeconductor", "presets", `${name}.yml`),
13236
+ resolve7(SRC_PRESETS_DIR, name, `${name}.yml`)
12710
13237
  ];
12711
13238
  for (const presetPath of candidates) {
12712
13239
  try {
12713
- const content = await readFile6(presetPath, "utf-8");
13240
+ const content = await readFile7(presetPath, "utf-8");
12714
13241
  const data = $parse(content);
12715
13242
  if (!data)
12716
13243
  continue;
@@ -12754,7 +13281,7 @@ function getIndividualTargets(target) {
12754
13281
  // src/commands/install.command.ts
12755
13282
  async function installCommand(options) {
12756
13283
  const { target, dryRun, force, global: isGlobal, output, projectRoot } = options;
12757
- const baseDir = isGlobal ? homedir3() : projectRoot;
13284
+ const baseDir = isGlobal ? homedir5() : projectRoot;
12758
13285
  try {
12759
13286
  const runnerTarget = parseRunnerTarget(target);
12760
13287
  const targets = getIndividualTargets(runnerTarget);
@@ -12790,7 +13317,7 @@ async function installCommand(options) {
12790
13317
  const generatedFiles = await installer.generate();
12791
13318
  const resolvedFiles = generatedFiles.map((f) => ({
12792
13319
  ...f,
12793
- path: resolve7(baseDir, f.path)
13320
+ path: resolve8(baseDir, f.path)
12794
13321
  }));
12795
13322
  const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
12796
13323
  for (const result of results) {
@@ -12849,7 +13376,7 @@ async function installCommand(options) {
12849
13376
  }
12850
13377
  async function installPresetCommand(options) {
12851
13378
  const { target, dryRun, force, global: isGlobal, projectRoot } = options;
12852
- const baseDir = isGlobal ? homedir3() : projectRoot;
13379
+ const baseDir = isGlobal ? homedir5() : projectRoot;
12853
13380
  try {
12854
13381
  const runnerTarget = parseRunnerTarget(target);
12855
13382
  const targets = getIndividualTargets(runnerTarget);
@@ -12899,8 +13426,8 @@ async function installPresetCommand(options) {
12899
13426
  }
12900
13427
 
12901
13428
  // src/commands/install-lsp.command.ts
12902
- import { homedir as homedir5 } from "node:os";
12903
- import { resolve as resolve8 } from "node:path";
13429
+ import { homedir as homedir7 } from "node:os";
13430
+ import { resolve as resolve9 } from "node:path";
12904
13431
 
12905
13432
  // src/core/lsp/lsp-config-utils.ts
12906
13433
  function getLanguageServerConfig(lspIds) {
@@ -13144,15 +13671,15 @@ function createOpenCodeLspGenerator() {
13144
13671
  // src/core/lsp/lsp-installer.ts
13145
13672
  import { execFile } from "node:child_process";
13146
13673
  import { access as access5, mkdir as mkdir5 } from "node:fs/promises";
13147
- import { homedir as homedir4 } from "node:os";
13148
- import { join as join4 } from "node:path";
13674
+ import { homedir as homedir6 } from "node:os";
13675
+ import { join as join5 } from "node:path";
13149
13676
  import { promisify } from "node:util";
13150
13677
  var execFileAsync = promisify(execFile);
13151
13678
 
13152
13679
  class LspInstaller {
13153
13680
  lspBinDir;
13154
13681
  constructor() {
13155
- this.lspBinDir = join4(homedir4(), ".codeconductor", "lsp", "bin");
13682
+ this.lspBinDir = join5(homedir6(), ".codeconductor", "lsp", "bin");
13156
13683
  }
13157
13684
  async checkInstalled(def) {
13158
13685
  try {
@@ -13279,7 +13806,7 @@ class LspInstaller {
13279
13806
  throw new Error(`No binary available for platform: ${platformKey}`);
13280
13807
  }
13281
13808
  await mkdir5(this.lspBinDir, { recursive: true });
13282
- const destPath = join4(this.lspBinDir, def.binaryName);
13809
+ const destPath = join5(this.lspBinDir, def.binaryName);
13283
13810
  try {
13284
13811
  await access5(destPath);
13285
13812
  return;
@@ -13386,7 +13913,7 @@ function resolveLsps(languages) {
13386
13913
  // src/commands/install-lsp.command.ts
13387
13914
  async function installLspCommand(options) {
13388
13915
  const { target, lang, dryRun, force, global: isGlobal, output, projectRoot } = options;
13389
- const baseDir = isGlobal ? homedir5() : projectRoot;
13916
+ const baseDir = isGlobal ? homedir7() : projectRoot;
13390
13917
  try {
13391
13918
  const runnerTarget = parseRunnerTarget(target);
13392
13919
  const targets = getIndividualTargets(runnerTarget);
@@ -13432,7 +13959,7 @@ async function installLspCommand(options) {
13432
13959
  const generatedFiles = generator.generate(installReport.results);
13433
13960
  const resolvedFiles = generatedFiles.map((f) => ({
13434
13961
  ...f,
13435
- path: resolve8(baseDir, f.path)
13962
+ path: resolve9(baseDir, f.path)
13436
13963
  }));
13437
13964
  const results = await writeGeneratedFiles(resolvedFiles, writeOptions);
13438
13965
  for (const result of results) {
@@ -13526,7 +14053,7 @@ function getLspConfigGenerator(target) {
13526
14053
 
13527
14054
  // src/commands/seo-audit.command.ts
13528
14055
  import { writeFile as writeFile5, mkdir as mkdir6 } from "node:fs/promises";
13529
- import { dirname as dirname4, resolve as resolve9 } from "node:path";
14056
+ import { dirname as dirname4, resolve as resolve10 } from "node:path";
13530
14057
 
13531
14058
  // src/infrastructure/http/safe-fetch.ts
13532
14059
  import { lookup } from "node:dns/promises";
@@ -13613,7 +14140,7 @@ async function safeFetch(urlString, options = {}) {
13613
14140
  }
13614
14141
  }
13615
14142
  async function delay(ms) {
13616
- return new Promise((resolve9) => setTimeout(resolve9, ms));
14143
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
13617
14144
  }
13618
14145
 
13619
14146
  // src/infrastructure/parsers/sitemap-parser.ts
@@ -14817,12 +15344,12 @@ async function seoAuditCommand(options) {
14817
15344
  formattedOutput = formatCli(report);
14818
15345
  }
14819
15346
  if (output) {
14820
- const outputPath = resolve9(options.projectRoot, output);
15347
+ const outputPath = resolve10(options.projectRoot, output);
14821
15348
  await mkdir6(dirname4(outputPath), { recursive: true });
14822
15349
  await writeFile5(outputPath, formattedOutput, "utf-8");
14823
15350
  } else if (format === "markdown") {
14824
15351
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
14825
- const defaultPath = resolve9(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
15352
+ const defaultPath = resolve10(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
14826
15353
  await mkdir6(dirname4(defaultPath), { recursive: true });
14827
15354
  await writeFile5(defaultPath, formattedOutput, "utf-8");
14828
15355
  process.stderr.write(`Report saved to: ${defaultPath}
@@ -14836,7 +15363,7 @@ async function seoAuditCommand(options) {
14836
15363
  command: "seo audit",
14837
15364
  report,
14838
15365
  output: formattedOutput,
14839
- outputFile: output ? resolve9(options.projectRoot, output) : format === "markdown" ? resolve9(options.projectRoot, "seo-reports") : undefined
15366
+ outputFile: output ? resolve10(options.projectRoot, output) : format === "markdown" ? resolve10(options.projectRoot, "seo-reports") : undefined
14840
15367
  }
14841
15368
  };
14842
15369
  } catch (error) {
@@ -14853,7 +15380,7 @@ async function seoAuditCommand(options) {
14853
15380
 
14854
15381
  // src/commands/seo-llms.command.ts
14855
15382
  import { writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
14856
- import { dirname as dirname5, resolve as resolve10 } from "node:path";
15383
+ import { dirname as dirname5, resolve as resolve11 } from "node:path";
14857
15384
 
14858
15385
  // src/domain/seo/llms-generator.ts
14859
15386
  function extractTitle2(html) {
@@ -14996,7 +15523,7 @@ async function seoLlmsCommand(options) {
14996
15523
  }
14997
15524
  }) : await generateLlmsTxtFromUrl(url);
14998
15525
  process.stderr.write("\r" + " ".repeat(80) + "\r");
14999
- const outputPath = output ? resolve10(options.projectRoot, output) : resolve10(options.projectRoot, "llms.txt");
15526
+ const outputPath = output ? resolve11(options.projectRoot, output) : resolve11(options.projectRoot, "llms.txt");
15000
15527
  await mkdir7(dirname5(outputPath), { recursive: true });
15001
15528
  await writeFile6(outputPath, result.content, "utf-8");
15002
15529
  process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
@@ -15024,10 +15551,14 @@ async function seoLlmsCommand(options) {
15024
15551
  }
15025
15552
 
15026
15553
  // src/commands/update.command.ts
15554
+ import { homedir as homedir8 } from "node:os";
15555
+ import { resolve as resolve12, dirname as dirname6 } from "node:path";
15556
+ import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile7, stat as stat3 } from "node:fs/promises";
15027
15557
  async function updateCommand(options) {
15028
- const { dryRun, force, output, projectRoot } = options;
15558
+ const { dryRun, force, global: isGlobal, output, projectRoot } = options;
15559
+ const basePath = isGlobal ? homedir8() : projectRoot;
15029
15560
  try {
15030
- const configResult = await loadConfig(projectRoot);
15561
+ const configResult = await loadConfig(basePath);
15031
15562
  if (!configResult.success) {
15032
15563
  return {
15033
15564
  code: 1,
@@ -15039,42 +15570,40 @@ async function updateCommand(options) {
15039
15570
  };
15040
15571
  }
15041
15572
  const config = configResult.data;
15042
- if (!config.presets.council.enabled) {
15043
- return {
15044
- code: 4,
15045
- data: {
15046
- success: false,
15047
- command: "update",
15048
- errors: ["Council preset is not enabled"]
15049
- }
15050
- };
15051
- }
15052
- const presetResult = await loadCouncilPreset(projectRoot);
15053
- if (!presetResult.success) {
15054
- return {
15055
- code: 1,
15056
- data: {
15057
- success: false,
15058
- command: "update",
15059
- errors: ["Failed to load preset"]
15060
- }
15061
- };
15573
+ const updateResults = await checkUpdates(basePath, isGlobal);
15574
+ const largeFiles = await validateAgentFileSizes(basePath, isGlobal);
15575
+ if (largeFiles.length > 0) {
15576
+ largeFiles.forEach((file) => {
15577
+ console.warn(`[WARNING] File exceeds 40KB: ${file.path} (${(file.size / 1024).toFixed(1)} KB)`);
15578
+ });
15062
15579
  }
15063
- const spec = presetResult.data;
15064
- const currentVersion = config.presets.council.version;
15065
- const newVersion = spec.version;
15066
- if (currentVersion === newVersion) {
15580
+ if (!updateResults.hasUpdates) {
15067
15581
  return {
15068
15582
  code: 0,
15069
15583
  data: {
15070
15584
  success: true,
15071
15585
  command: "update",
15072
15586
  message: "Already up to date",
15073
- currentVersion,
15074
- newVersion
15587
+ wouldUpdate: [],
15588
+ updated: []
15075
15589
  }
15076
15590
  };
15077
15591
  }
15592
+ const wouldUpdate = [];
15593
+ if (updateResults.council)
15594
+ wouldUpdate.push("council preset");
15595
+ if (updateResults.policy)
15596
+ wouldUpdate.push("policy file");
15597
+ for (const t of updateResults.targets) {
15598
+ if (t.hasUpdate) {
15599
+ wouldUpdate.push(`${t.target} target files`);
15600
+ }
15601
+ }
15602
+ for (const s of updateResults.skills) {
15603
+ if (s.hasUpdate) {
15604
+ wouldUpdate.push(`skill ${s.id} to v${s.latestVersion}`);
15605
+ }
15606
+ }
15078
15607
  if (dryRun) {
15079
15608
  return {
15080
15609
  code: 0,
@@ -15082,48 +15611,77 @@ async function updateCommand(options) {
15082
15611
  success: true,
15083
15612
  command: "update",
15084
15613
  message: "Dry run - would update",
15085
- currentVersion,
15086
- newVersion,
15087
- wouldUpdate: ["council preset files"]
15614
+ wouldUpdate,
15615
+ updated: []
15088
15616
  }
15089
15617
  };
15090
15618
  }
15091
- const writeOptions = { dryRun: false, force };
15092
- const target = config.defaults.target;
15093
- let installer;
15094
- switch (target) {
15095
- case "opencode":
15096
- installer = createOpenCodeInstaller(spec);
15097
- break;
15098
- case "claude":
15099
- installer = createClaudeInstaller(spec);
15100
- break;
15101
- case "codex":
15102
- installer = createCodexInstaller(spec);
15103
- break;
15104
- default:
15105
- return {
15106
- code: 1,
15107
- data: {
15108
- success: false,
15109
- command: "update",
15110
- errors: [`Unknown target: ${target}`]
15619
+ const updated = [];
15620
+ if (updateResults.council) {
15621
+ const localCouncil = resolve12(basePath, ".codeconductor", "presets", "council.yml");
15622
+ const bundledCouncil = resolve12(SRC_PRESETS_DIR, "council", "council.yml");
15623
+ try {
15624
+ const content = await readFile8(bundledCouncil, "utf-8");
15625
+ await mkdir8(dirname6(localCouncil), { recursive: true });
15626
+ await writeFile7(localCouncil, content, "utf-8");
15627
+ updated.push(localCouncil);
15628
+ } catch (e) {
15629
+ throw new Error(`Failed to update council.yml: ${e}`);
15630
+ }
15631
+ }
15632
+ if (updateResults.policy) {
15633
+ const localPolicy = resolve12(basePath, ".codeconductor", "presets", "policy.yml");
15634
+ try {
15635
+ const content = await readFile8(POLICY_PATH, "utf-8");
15636
+ await mkdir8(dirname6(localPolicy), { recursive: true });
15637
+ await writeFile7(localPolicy, content, "utf-8");
15638
+ updated.push(localPolicy);
15639
+ } catch (e) {
15640
+ throw new Error(`Failed to update policy.yml: ${e}`);
15641
+ }
15642
+ }
15643
+ const locale = config?.defaults?.locale ?? "en";
15644
+ for (const t of updateResults.targets) {
15645
+ if (t.hasUpdate) {
15646
+ const targetName = t.target;
15647
+ const manifest = await loadManifest(targetName);
15648
+ const modelConfig = await loadModelConfig(targetName);
15649
+ const results = await copyFromManifest(manifest, PRESETS_DIR, basePath, isGlobal, false, force, modelConfig, locale);
15650
+ for (const r of results) {
15651
+ if (r.action !== "skipped" && r.action !== "error") {
15652
+ updated.push(r.dest);
15653
+ } else if (r.action === "error") {
15654
+ throw new Error(`Failed to copy target file: ${r.error}`);
15111
15655
  }
15112
- };
15656
+ }
15657
+ }
15113
15658
  }
15114
- const files = await installer.generate();
15115
- const results = await writeGeneratedFiles(files, writeOptions);
15116
- const updated = results.filter((r) => r.success).map((r) => r.path);
15117
- const errors3 = results.filter((r) => !r.success).map((r) => `${r.path}: ${r.error}`);
15118
- if (errors3.length > 0) {
15119
- return {
15120
- code: 2,
15121
- data: {
15122
- success: false,
15123
- command: "update",
15124
- errors: errors3
15659
+ if (updateResults.skills.some((s) => s.hasUpdate)) {
15660
+ const newSkillsLock = {};
15661
+ const currentLock = await loadSkillsLock(basePath) || {};
15662
+ for (const [id, ver] of Object.entries(currentLock)) {
15663
+ newSkillsLock[id] = ver;
15664
+ }
15665
+ for (const s of updateResults.skills) {
15666
+ newSkillsLock[s.id] = s.latestVersion;
15667
+ }
15668
+ let lockDest = resolve12(basePath, ".codeconductor", "skills-lock.json");
15669
+ try {
15670
+ const statAgents = await stat3(resolve12(basePath, ".agents"));
15671
+ if (statAgents.isDirectory()) {
15672
+ const statCodeConductor = await stat3(resolve12(basePath, ".codeconductor")).catch(() => null);
15673
+ if (!statCodeConductor) {
15674
+ lockDest = resolve12(basePath, ".agents", "skills-lock.json");
15675
+ }
15125
15676
  }
15126
- };
15677
+ } catch {}
15678
+ try {
15679
+ await mkdir8(dirname6(lockDest), { recursive: true });
15680
+ await writeFile7(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
15681
+ updated.push(lockDest);
15682
+ } catch (e) {
15683
+ throw new Error(`Failed to write skills-lock.json: ${e}`);
15684
+ }
15127
15685
  }
15128
15686
  return {
15129
15687
  code: 0,
@@ -15131,8 +15689,7 @@ async function updateCommand(options) {
15131
15689
  success: true,
15132
15690
  command: "update",
15133
15691
  message: "Updated successfully",
15134
- currentVersion,
15135
- newVersion,
15692
+ wouldUpdate: [],
15136
15693
  updated
15137
15694
  }
15138
15695
  };
@@ -15337,6 +15894,7 @@ async function routeCommand(args, projectRoot) {
15337
15894
  projectRoot,
15338
15895
  dryRun: flags.dryRun,
15339
15896
  force: flags.force,
15897
+ global: options.global === true || options.global === "true",
15340
15898
  output: flags.output
15341
15899
  });
15342
15900
  case "seo": {