opencastle 0.6.0 → 0.8.0

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 (223) hide show
  1. package/README.md +8 -7
  2. package/dist/cli/adapters/claude-code.d.ts.map +1 -1
  3. package/dist/cli/adapters/claude-code.js +30 -3
  4. package/dist/cli/adapters/claude-code.js.map +1 -1
  5. package/dist/cli/adapters/cursor.d.ts.map +1 -1
  6. package/dist/cli/adapters/cursor.js +27 -3
  7. package/dist/cli/adapters/cursor.js.map +1 -1
  8. package/dist/cli/adapters/opencode.d.ts +20 -0
  9. package/dist/cli/adapters/opencode.d.ts.map +1 -0
  10. package/dist/cli/adapters/opencode.js +265 -0
  11. package/dist/cli/adapters/opencode.js.map +1 -0
  12. package/dist/cli/adapters/vscode.d.ts.map +1 -1
  13. package/dist/cli/adapters/vscode.js +37 -6
  14. package/dist/cli/adapters/vscode.js.map +1 -1
  15. package/dist/cli/copy.d.ts +12 -0
  16. package/dist/cli/copy.d.ts.map +1 -1
  17. package/dist/cli/copy.js +27 -0
  18. package/dist/cli/copy.js.map +1 -1
  19. package/dist/cli/detect.d.ts +1 -1
  20. package/dist/cli/detect.js +21 -15
  21. package/dist/cli/detect.js.map +1 -1
  22. package/dist/cli/init.d.ts.map +1 -1
  23. package/dist/cli/init.js +143 -94
  24. package/dist/cli/init.js.map +1 -1
  25. package/dist/cli/manifest.d.ts +1 -1
  26. package/dist/cli/manifest.d.ts.map +1 -1
  27. package/dist/cli/manifest.js +2 -1
  28. package/dist/cli/manifest.js.map +1 -1
  29. package/dist/cli/mcp.d.ts +6 -6
  30. package/dist/cli/mcp.d.ts.map +1 -1
  31. package/dist/cli/mcp.js +104 -33
  32. package/dist/cli/mcp.js.map +1 -1
  33. package/dist/cli/prompt.d.ts +19 -0
  34. package/dist/cli/prompt.d.ts.map +1 -1
  35. package/dist/cli/prompt.js +143 -0
  36. package/dist/cli/prompt.js.map +1 -1
  37. package/dist/cli/stack-config.d.ts +23 -0
  38. package/dist/cli/stack-config.d.ts.map +1 -1
  39. package/dist/cli/stack-config.js +128 -124
  40. package/dist/cli/stack-config.js.map +1 -1
  41. package/dist/cli/types.d.ts +26 -9
  42. package/dist/cli/types.d.ts.map +1 -1
  43. package/dist/cli/types.js +26 -1
  44. package/dist/cli/types.js.map +1 -1
  45. package/dist/cli/update.d.ts.map +1 -1
  46. package/dist/cli/update.js +60 -19
  47. package/dist/cli/update.js.map +1 -1
  48. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
  49. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
  50. package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
  51. package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
  52. package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
  53. package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
  54. package/dist/orchestrator/plugins/contentful/config.js +48 -0
  55. package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
  56. package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
  57. package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
  58. package/dist/orchestrator/plugins/convex/config.js +32 -0
  59. package/dist/orchestrator/plugins/convex/config.js.map +1 -0
  60. package/dist/orchestrator/plugins/index.d.ts +28 -0
  61. package/dist/orchestrator/plugins/index.d.ts.map +1 -0
  62. package/dist/orchestrator/plugins/index.js +63 -0
  63. package/dist/orchestrator/plugins/index.js.map +1 -0
  64. package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
  65. package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
  66. package/dist/orchestrator/plugins/jira/config.js +29 -0
  67. package/dist/orchestrator/plugins/jira/config.js.map +1 -0
  68. package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
  69. package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
  70. package/dist/orchestrator/plugins/linear/config.js +33 -0
  71. package/dist/orchestrator/plugins/linear/config.js.map +1 -0
  72. package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
  73. package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
  74. package/dist/orchestrator/plugins/nx/config.js +28 -0
  75. package/dist/orchestrator/plugins/nx/config.js.map +1 -0
  76. package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
  77. package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
  78. package/dist/orchestrator/plugins/sanity/config.js +43 -0
  79. package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
  80. package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
  81. package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
  82. package/dist/orchestrator/plugins/slack/config.js +34 -0
  83. package/dist/orchestrator/plugins/slack/config.js.map +1 -0
  84. package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
  85. package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
  86. package/dist/orchestrator/plugins/strapi/config.js +40 -0
  87. package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
  88. package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
  89. package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
  90. package/dist/orchestrator/plugins/supabase/config.js +33 -0
  91. package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
  92. package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
  93. package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
  94. package/dist/orchestrator/plugins/teams/config.js +43 -0
  95. package/dist/orchestrator/plugins/teams/config.js.map +1 -0
  96. package/dist/orchestrator/plugins/types.d.ts +61 -0
  97. package/dist/orchestrator/plugins/types.d.ts.map +1 -0
  98. package/dist/orchestrator/plugins/types.js +2 -0
  99. package/dist/orchestrator/plugins/types.js.map +1 -0
  100. package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
  101. package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
  102. package/dist/orchestrator/plugins/vercel/config.js +32 -0
  103. package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
  104. package/package.json +1 -1
  105. package/src/cli/adapters/claude-code.ts +36 -4
  106. package/src/cli/adapters/cursor.ts +42 -4
  107. package/src/cli/adapters/opencode.ts +320 -0
  108. package/src/cli/adapters/vscode.ts +40 -8
  109. package/src/cli/copy.ts +32 -0
  110. package/src/cli/detect.ts +17 -17
  111. package/src/cli/init.ts +157 -99
  112. package/src/cli/manifest.ts +2 -1
  113. package/src/cli/mcp.ts +129 -50
  114. package/src/cli/prompt.ts +176 -0
  115. package/src/cli/stack-config.ts +174 -145
  116. package/src/cli/types.ts +39 -8
  117. package/src/cli/update.ts +71 -20
  118. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  119. package/src/orchestrator/agent-workflows/README.md +1 -1
  120. package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
  121. package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
  122. package/src/orchestrator/agent-workflows/database-migration.md +11 -11
  123. package/src/orchestrator/agent-workflows/feature-implementation.md +19 -12
  124. package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
  125. package/src/orchestrator/agent-workflows/refactoring.md +10 -10
  126. package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
  127. package/src/orchestrator/agent-workflows/security-audit.md +12 -12
  128. package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
  129. package/src/orchestrator/agents/api-designer.agent.md +1 -1
  130. package/src/orchestrator/agents/architect.agent.md +2 -2
  131. package/src/orchestrator/agents/content-engineer.agent.md +3 -3
  132. package/src/orchestrator/agents/copywriter.agent.md +2 -2
  133. package/src/orchestrator/agents/data-expert.agent.md +6 -6
  134. package/src/orchestrator/agents/database-engineer.agent.md +3 -3
  135. package/src/orchestrator/agents/developer.agent.md +4 -4
  136. package/src/orchestrator/agents/devops-expert.agent.md +5 -5
  137. package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
  138. package/src/orchestrator/agents/performance-expert.agent.md +2 -2
  139. package/src/orchestrator/agents/release-manager.agent.md +4 -4
  140. package/src/orchestrator/agents/researcher.agent.md +3 -3
  141. package/src/orchestrator/agents/reviewer.agent.md +1 -1
  142. package/src/orchestrator/agents/security-expert.agent.md +4 -4
  143. package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
  144. package/src/orchestrator/agents/team-lead.agent.md +56 -38
  145. package/src/orchestrator/agents/testing-expert.agent.md +5 -5
  146. package/src/orchestrator/agents/ui-ux-expert.agent.md +6 -6
  147. package/src/orchestrator/copilot-instructions.md +1 -1
  148. package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
  149. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
  150. package/src/orchestrator/customizations/DISPUTES.md +5 -5
  151. package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
  152. package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
  153. package/src/orchestrator/customizations/README.md +5 -2
  154. package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
  155. package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
  156. package/src/orchestrator/customizations/logs/README.md +1 -1
  157. package/src/orchestrator/customizations/project/decisions.md +31 -0
  158. package/src/orchestrator/customizations/project/docs-structure.md +16 -5
  159. package/src/orchestrator/customizations/project/roadmap.md +24 -0
  160. package/src/orchestrator/customizations/project/tracker-config.md +1 -1
  161. package/src/orchestrator/customizations/stack/cms-config.md +1 -1
  162. package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
  163. package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
  164. package/src/orchestrator/instructions/general.instructions.md +102 -40
  165. package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
  166. package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
  167. package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
  168. package/src/orchestrator/plugins/contentful/config.ts +49 -0
  169. package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
  170. package/src/orchestrator/plugins/convex/config.ts +33 -0
  171. package/src/orchestrator/plugins/index.ts +85 -0
  172. package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
  173. package/src/orchestrator/plugins/jira/config.ts +30 -0
  174. package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
  175. package/src/orchestrator/plugins/linear/config.ts +34 -0
  176. package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
  177. package/src/orchestrator/plugins/nx/config.ts +29 -0
  178. package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
  179. package/src/orchestrator/plugins/sanity/config.ts +44 -0
  180. package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
  181. package/src/orchestrator/plugins/slack/config.ts +35 -0
  182. package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
  183. package/src/orchestrator/plugins/strapi/config.ts +41 -0
  184. package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
  185. package/src/orchestrator/plugins/supabase/config.ts +34 -0
  186. package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
  187. package/src/orchestrator/plugins/teams/config.ts +44 -0
  188. package/src/orchestrator/plugins/types.ts +79 -0
  189. package/src/orchestrator/plugins/vercel/config.ts +33 -0
  190. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +8 -8
  191. package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
  192. package/src/orchestrator/prompts/bug-fix.prompt.md +27 -22
  193. package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
  194. package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
  195. package/src/orchestrator/prompts/implement-feature.prompt.md +34 -29
  196. package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
  197. package/src/orchestrator/prompts/quick-refinement.prompt.md +23 -19
  198. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +19 -5
  199. package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
  200. package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
  201. package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
  202. package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
  203. package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
  204. package/src/orchestrator/skills/context-map/SKILL.md +4 -4
  205. package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
  206. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
  207. package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
  208. package/src/orchestrator/skills/fast-review/SKILL.md +18 -7
  209. package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
  210. package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
  211. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
  212. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
  213. package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
  214. package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
  215. package/src/orchestrator/skills/react-development/SKILL.md +3 -3
  216. package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
  217. package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
  218. package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
  219. package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
  220. package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
  221. package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
  222. package/src/orchestrator/skills/validation-gates/SKILL.md +157 -27
  223. package/src/orchestrator/mcp.json +0 -69
package/src/cli/prompt.ts CHANGED
@@ -14,6 +14,19 @@ function moveUp(n: number): string {
14
14
  return n > 0 ? `${CSI}${n}A` : '';
15
15
  }
16
16
 
17
+ // ── Color helpers ─────────────────────────────────────────────────
18
+
19
+ /** ANSI color helpers for CLI output. */
20
+ export const c = {
21
+ cyan: (s: string) => `\x1B[36m${s}\x1B[0m`,
22
+ green: (s: string) => `\x1B[32m${s}\x1B[0m`,
23
+ yellow: (s: string) => `\x1B[33m${s}\x1B[0m`,
24
+ red: (s: string) => `\x1B[31m${s}\x1B[0m`,
25
+ bold: (s: string) => `\x1B[1m${s}\x1B[0m`,
26
+ dim: (s: string) => `\x1B[2m${s}\x1B[0m`,
27
+ magenta: (s: string) => `\x1B[35m${s}\x1B[0m`,
28
+ };
29
+
17
30
  // ── Line-buffered readline ────────────────────────────────────────
18
31
  // readline.question() drops lines that arrived between calls because
19
32
  // it only listens for the NEXT 'line' event. When piped input
@@ -236,3 +249,166 @@ export async function confirm(
236
249
  if (!answer.trim()) return defaultYes;
237
250
  return answer.trim().toLowerCase().startsWith('y');
238
251
  }
252
+
253
+ // ── Multiselect ───────────────────────────────────────────────────
254
+
255
+ /**
256
+ * Interactive multi-choice selection prompt.
257
+ *
258
+ * TTY mode: arrow-key navigation (↑/↓), Space to toggle, Enter to confirm.
259
+ * Piped mode: falls back to comma-separated number input.
260
+ *
261
+ * Returns an array of selected values (possibly empty).
262
+ */
263
+ export async function multiselect(
264
+ message: string,
265
+ options: SelectOption[]
266
+ ): Promise<string[]> {
267
+ if (stdin.isTTY) {
268
+ return multiselectInteractive(message, options);
269
+ }
270
+ return multiselectNumbered(message, options);
271
+ }
272
+
273
+ // ── Arrow-key multiselect (TTY) ───────────────────────────────────
274
+
275
+ function renderMultiselectOptions(
276
+ options: SelectOption[],
277
+ cursor: number,
278
+ selected: Set<number>,
279
+ initial: boolean
280
+ ): void {
281
+ if (!initial) {
282
+ stdout.write(moveUp(options.length));
283
+ }
284
+
285
+ for (let i = 0; i < options.length; i++) {
286
+ const active = i === cursor;
287
+ const checked = selected.has(i);
288
+ const checkbox = checked ? `\x1B[32m✔\x1B[0m` : ' ';
289
+ const marker = active ? '❯' : ' ';
290
+ const hint = options[i].hint ? ` ${c.dim('—')} ${c.dim(options[i].hint!)}` : '';
291
+ const label = active
292
+ ? `\x1B[36m${options[i].label}\x1B[0m${hint}`
293
+ : `${options[i].label}${hint}`;
294
+ stdout.write(`${ERASE_LINE}\r ${marker} [${checkbox}] ${label}\n`);
295
+ }
296
+ }
297
+
298
+ function multiselectInteractive(
299
+ message: string,
300
+ options: SelectOption[]
301
+ ): Promise<string[]> {
302
+ return new Promise<string[]>((resolve) => {
303
+ let cursor = 0;
304
+ const selected = new Set<number>();
305
+ // Pre-select options marked as selected
306
+ for (let i = 0; i < options.length; i++) {
307
+ if (options[i].selected) selected.add(i);
308
+ }
309
+
310
+ if (_rl) _rl.pause();
311
+
312
+ stdout.write(`\n ${message} ${c.dim('(↑/↓ navigate, Space toggle, Enter confirm)')}\n\n`);
313
+ stdout.write(HIDE_CURSOR);
314
+ renderMultiselectOptions(options, cursor, selected, true);
315
+
316
+ stdin.setRawMode(true);
317
+ stdin.resume();
318
+
319
+ const onData = (data: Buffer): void => {
320
+ const key = data.toString();
321
+
322
+ // Arrow up or k
323
+ if (key === `${ESC}[A` || key === 'k') {
324
+ cursor = (cursor - 1 + options.length) % options.length;
325
+ renderMultiselectOptions(options, cursor, selected, false);
326
+ return;
327
+ }
328
+
329
+ // Arrow down or j
330
+ if (key === `${ESC}[B` || key === 'j') {
331
+ cursor = (cursor + 1) % options.length;
332
+ renderMultiselectOptions(options, cursor, selected, false);
333
+ return;
334
+ }
335
+
336
+ // Space — toggle selection
337
+ if (key === ' ') {
338
+ if (selected.has(cursor)) {
339
+ selected.delete(cursor);
340
+ } else {
341
+ selected.add(cursor);
342
+ }
343
+ renderMultiselectOptions(options, cursor, selected, false);
344
+ return;
345
+ }
346
+
347
+ // Enter — confirm
348
+ if (key === '\r' || key === '\n') {
349
+ cleanup();
350
+ // Final render
351
+ stdout.write(moveUp(options.length));
352
+ for (let i = 0; i < options.length; i++) {
353
+ const checked = selected.has(i);
354
+ const hint = options[i].hint ? ` ${c.dim('—')} ${c.dim(options[i].hint!)}` : '';
355
+ const checkbox = checked ? `\x1B[32m✔\x1B[0m` : ' ';
356
+ const label = checked
357
+ ? `\x1B[36m${options[i].label}\x1B[0m${hint}`
358
+ : `\x1B[2m${options[i].label}${hint}\x1B[0m`;
359
+ stdout.write(`${ERASE_LINE}\r [${checkbox}] ${label}\n`);
360
+ }
361
+ stdout.write('\n');
362
+ resolve(Array.from(selected).sort().map(i => options[i].value));
363
+ return;
364
+ }
365
+
366
+ // Ctrl+C
367
+ if (key === '\x03') {
368
+ cleanup();
369
+ stdout.write('\n');
370
+ process.exit(130);
371
+ }
372
+ };
373
+
374
+ function cleanup(): void {
375
+ stdin.removeListener('data', onData);
376
+ stdin.setRawMode(false);
377
+ stdout.write(SHOW_CURSOR);
378
+ if (_rl) _rl.resume();
379
+ }
380
+
381
+ stdin.on('data', onData);
382
+ });
383
+ }
384
+
385
+ // ── Number-based multiselect (piped / non-TTY) ────────────────────
386
+
387
+ async function multiselectNumbered(
388
+ message: string,
389
+ options: SelectOption[]
390
+ ): Promise<string[]> {
391
+ console.log(`\n ${message}\n`);
392
+ options.forEach((opt, i) => {
393
+ const hint = opt.hint ? ` — ${opt.hint}` : '';
394
+ console.log(` ${i + 1}) ${opt.label}${hint}`);
395
+ });
396
+
397
+ const preselected = options
398
+ .map((opt, i) => (opt.selected ? i + 1 : null))
399
+ .filter((n): n is number => n !== null);
400
+ const defaultHint = preselected.length > 0 ? preselected.join(',') : 'none';
401
+ const answer = await nextLine(`\n Select [comma-separated, e.g. 1,3] or Enter for ${defaultHint}: `);
402
+ if (!answer.trim()) {
403
+ return preselected.map(n => options[n - 1].value);
404
+ }
405
+
406
+ const nums = answer.split(',').map(s => parseInt(s.trim(), 10));
407
+ const result: string[] = [];
408
+ for (const num of nums) {
409
+ if (num >= 1 && num <= options.length) {
410
+ result.push(options[num - 1].value);
411
+ }
412
+ }
413
+ return result;
414
+ }
@@ -1,111 +1,39 @@
1
- import type { CmsChoice, DbChoice, PmChoice, NotifChoice, StackConfig, CopyDirOptions, RepoInfo } from './types.js';
2
-
3
- // ── Skill / Technology labels ─────────────────────────────────
4
-
5
- /** Display name for each CMS choice */
6
- const CMS_LABELS: Record<Exclude<CmsChoice, 'none'>, { tech: string; skill: string }> = {
7
- sanity: { tech: 'Sanity', skill: 'sanity-cms' },
8
- contentful: { tech: 'Contentful', skill: 'contentful-cms' },
9
- strapi: { tech: 'Strapi', skill: 'strapi-cms' },
10
- };
11
-
12
- /** Display name for each DB choice */
13
- const DB_LABELS: Record<Exclude<DbChoice, 'none'>, { tech: string; skill: string }> = {
14
- supabase: { tech: 'Supabase', skill: 'supabase-database' },
15
- convex: { tech: 'Convex', skill: 'convex-database' },
16
- };
17
-
18
- /** Display name for each PM choice */
19
- const PM_LABELS: Record<Exclude<PmChoice, 'none'>, { tech: string; skill: string }> = {
20
- linear: { tech: 'Linear', skill: 'task-management' },
21
- jira: { tech: 'Jira', skill: 'jira-management' },
22
- };
23
-
24
- /** Display name for each notifications choice */
25
- const NOTIF_LABELS: Record<Exclude<NotifChoice, 'none'>, { tech: string; skill: string }> = {
26
- slack: { tech: 'Slack', skill: 'slack-notifications' },
27
- teams: { tech: 'Teams', skill: 'teams-notifications' },
28
- };
29
-
30
- // ── Exclusion / inclusion maps ────────────────────────────────
31
-
32
- /** Skills to EXCLUDE based on CMS choice */
33
- const CMS_SKILL_MAP: Record<CmsChoice, string[]> = {
34
- sanity: ['contentful-cms', 'strapi-cms'],
35
- contentful: ['sanity-cms', 'strapi-cms'],
36
- strapi: ['sanity-cms', 'contentful-cms'],
37
- none: ['sanity-cms', 'contentful-cms', 'strapi-cms'],
38
- };
39
-
40
- /** Skills to EXCLUDE based on DB choice */
41
- const DB_SKILL_MAP: Record<DbChoice, string[]> = {
42
- supabase: ['convex-database'],
43
- convex: ['supabase-database'],
44
- none: ['supabase-database', 'convex-database'],
45
- };
46
-
47
- /** Skills to EXCLUDE based on PM choice */
48
- const PM_SKILL_MAP: Record<PmChoice, string[]> = {
49
- linear: ['jira-management'],
50
- jira: ['task-management'],
51
- none: ['task-management', 'jira-management'],
52
- };
53
-
54
- /** Agents to EXCLUDE based on CMS choice */
55
- const CMS_AGENT_EXCLUSIONS: Record<CmsChoice, string[]> = {
56
- sanity: [],
57
- contentful: [],
58
- strapi: [],
59
- none: ['content-engineer.agent.md'],
60
- };
61
-
62
- /** Agents to EXCLUDE based on DB choice */
63
- const DB_AGENT_EXCLUSIONS: Record<DbChoice, string[]> = {
64
- supabase: [],
65
- convex: [],
66
- none: ['database-engineer.agent.md'],
67
- };
68
-
69
- /** MCP server keys to INCLUDE based on CMS choice */
70
- const CMS_MCP_MAP: Record<CmsChoice, string[]> = {
71
- sanity: ['Sanity'],
72
- contentful: ['Contentful'],
73
- strapi: ['Strapi'],
74
- none: [],
75
- };
76
-
77
- /** MCP server keys to INCLUDE based on DB choice */
78
- const DB_MCP_MAP: Record<DbChoice, string[]> = {
79
- supabase: ['Supabase'],
80
- convex: ['Convex'],
81
- none: [],
82
- };
83
-
84
- /** MCP server keys to INCLUDE based on PM choice */
85
- const PM_MCP_MAP: Record<PmChoice, string[]> = {
86
- linear: ['Linear'],
87
- jira: ['Jira'],
88
- none: [],
89
- };
1
+ import type { TechTool, TeamTool, StackConfig, CopyDirOptions, RepoInfo } from './types.js';
2
+ import {
3
+ PLUGINS,
4
+ TECH_PLUGINS,
5
+ TEAM_PLUGINS,
6
+ CMS_PLUGINS,
7
+ DB_PLUGINS,
8
+ ALL_PLUGIN_SKILL_NAMES,
9
+ getSelectedSkillNames,
10
+ } from '../orchestrator/plugins/index.js';
11
+ import type { PluginConfig } from '../orchestrator/plugins/types.js';
12
+
13
+ // ── Tool registries (derived from plugins) ────────────────────
14
+
15
+ interface ToolInfo {
16
+ tech: string;
17
+ skill: string | null;
18
+ mcpServer: string | null;
19
+ }
90
20
 
91
- /** Skills to EXCLUDE based on notifications choice */
92
- const NOTIF_SKILL_MAP: Record<NotifChoice, string[]> = {
93
- slack: ['teams-notifications'],
94
- teams: ['slack-notifications'],
95
- none: ['slack-notifications', 'teams-notifications'],
96
- };
21
+ /** All tech-tool metadata derived from plugin configs. */
22
+ const TECH_TOOL_INFO: Record<TechTool, ToolInfo> = Object.fromEntries(
23
+ TECH_PLUGINS.map((p) => [p.id, { tech: p.name, skill: p.skillName, mcpServer: p.mcpServerKey }])
24
+ ) as Record<TechTool, ToolInfo>;
97
25
 
98
- /** MCP server keys to INCLUDE based on notifications choice */
99
- const NOTIF_MCP_MAP: Record<NotifChoice, string[]> = {
100
- slack: ['Slack'],
101
- teams: ['Teams'],
102
- none: [],
103
- };
26
+ /** All team-tool metadata derived from plugin configs. */
27
+ const TEAM_TOOL_INFO: Record<TeamTool, ToolInfo> = Object.fromEntries(
28
+ TEAM_PLUGINS.map((p) => [p.id, { tech: p.name, skill: p.skillName, mcpServer: p.mcpServerKey }])
29
+ ) as Record<TeamTool, ToolInfo>;
104
30
 
105
- /** Always-included MCP servers */
106
- const CORE_MCP_SERVERS = ['chrome-devtools'];
31
+ /** CMS-related tech tools. */
32
+ const CMS_TOOLS: readonly TechTool[] = CMS_PLUGINS.map((p) => p.id) as TechTool[];
33
+ /** Database-related tech tools. */
34
+ const DB_TOOLS: readonly TechTool[] = DB_PLUGINS.map((p) => p.id) as TechTool[];
107
35
 
108
- /** MCP servers included only when detected in the repo */
36
+ /** MCP servers auto-included when detected in the repo. */
109
37
  const DETECTED_MCP_MAP: Record<string, string> = {
110
38
  vercel: 'Vercel',
111
39
  };
@@ -123,41 +51,64 @@ export interface McpEnvRequirement {
123
51
 
124
52
  /**
125
53
  * Registry of MCP servers that require API keys via environment variables.
126
- * Only servers with `env` fields in mcp.json need to be listed here.
127
- * HTTP-based servers (Sanity, Slack, Vercel, etc.) handle auth via OAuth.
54
+ * Derived from plugin configs only plugins with envVars are included.
128
55
  */
129
- const MCP_ENV_REQUIREMENTS: McpEnvRequirement[] = [
130
- {
131
- server: 'Linear',
132
- envVar: 'LINEAR_API_KEY',
133
- hint: 'Create at linear.app → Settings → API → Personal API keys',
134
- },
135
- ];
56
+ const MCP_ENV_REQUIREMENTS: McpEnvRequirement[] = Object.values(PLUGINS)
57
+ .filter((p) => p.envVars.length > 0 && p.mcpServerKey)
58
+ .flatMap((p) =>
59
+ p.envVars.map((ev) => ({
60
+ server: p.mcpServerKey!,
61
+ envVar: ev.name,
62
+ hint: ev.hint,
63
+ }))
64
+ );
65
+
66
+ // ── Exported helpers ──────────────────────────────────────────
136
67
 
68
+ /**
69
+ * Skills to EXCLUDE — all tool-specific skills that are NOT selected.
70
+ */
137
71
  export function getExcludedSkills(stack: StackConfig): Set<string> {
138
- return new Set([
139
- ...CMS_SKILL_MAP[stack.cms],
140
- ...DB_SKILL_MAP[stack.db],
141
- ...PM_SKILL_MAP[stack.pm ?? 'none'],
142
- ...NOTIF_SKILL_MAP[stack.notifications ?? 'none'],
143
- ]);
72
+ const selectedIds = [...stack.techTools, ...stack.teamTools] as string[];
73
+ const includedSkills = new Set(getSelectedSkillNames(selectedIds));
74
+ return new Set(ALL_PLUGIN_SKILL_NAMES.filter((s) => !includedSkills.has(s)));
144
75
  }
145
76
 
77
+ /**
78
+ * Plugin IDs to INCLUDE — the user's selected tools.
79
+ */
80
+ export function getIncludedPluginIds(stack: StackConfig): Set<string> {
81
+ return new Set([...stack.techTools, ...stack.teamTools]);
82
+ }
83
+
84
+ /**
85
+ * Agents to EXCLUDE — content-engineer if no CMS, database-engineer if no DB.
86
+ */
146
87
  export function getExcludedAgents(stack: StackConfig): Set<string> {
147
- return new Set([
148
- ...CMS_AGENT_EXCLUSIONS[stack.cms],
149
- ...DB_AGENT_EXCLUSIONS[stack.db],
150
- ]);
88
+ const excluded = new Set<string>();
89
+ const hasCms = stack.techTools.some((t) => (CMS_TOOLS as readonly string[]).includes(t));
90
+ const hasDb = stack.techTools.some((t) => (DB_TOOLS as readonly string[]).includes(t));
91
+
92
+ if (!hasCms) excluded.add('content-engineer.agent.md');
93
+ if (!hasDb) excluded.add('database-engineer.agent.md');
94
+
95
+ return excluded;
151
96
  }
152
97
 
98
+ /**
99
+ * MCP servers to INCLUDE — core + selected tools + auto-detected from repo.
100
+ */
153
101
  export function getIncludedMcpServers(stack: StackConfig, repoInfo?: RepoInfo): Set<string> {
154
- const servers = new Set([
155
- ...CORE_MCP_SERVERS,
156
- ...CMS_MCP_MAP[stack.cms],
157
- ...DB_MCP_MAP[stack.db],
158
- ...PM_MCP_MAP[stack.pm ?? 'none'],
159
- ...NOTIF_MCP_MAP[stack.notifications ?? 'none'],
160
- ]);
102
+ const servers = new Set<string>();
103
+
104
+ for (const tool of stack.techTools) {
105
+ const server = TECH_TOOL_INFO[tool]?.mcpServer;
106
+ if (server) servers.add(server);
107
+ }
108
+ for (const tool of stack.teamTools) {
109
+ const server = TEAM_TOOL_INFO[tool]?.mcpServer;
110
+ if (server) servers.add(server);
111
+ }
161
112
 
162
113
  // Add servers for detected deployment targets
163
114
  for (const dep of repoInfo?.deployment ?? []) {
@@ -165,6 +116,11 @@ export function getIncludedMcpServers(stack: StackConfig, repoInfo?: RepoInfo):
165
116
  if (server) servers.add(server);
166
117
  }
167
118
 
119
+ // Auto-detect NX from monorepo info
120
+ if (repoInfo?.monorepo === 'nx' && !stack.techTools.includes('nx')) {
121
+ servers.add('Nx');
122
+ }
123
+
168
124
  return servers;
169
125
  }
170
126
 
@@ -189,7 +145,6 @@ export function getCustomizationsTransform(
189
145
  stack: StackConfig
190
146
  ): NonNullable<CopyDirOptions['transform']> {
191
147
  return (content: string, srcPath: string) => {
192
- // Pre-fill skill matrix with CMS and DB bindings
193
148
  if (srcPath.endsWith('skill-matrix.md')) {
194
149
  return transformSkillMatrix(content, stack);
195
150
  }
@@ -204,23 +159,97 @@ export function getCustomizationsTransform(
204
159
  function transformSkillMatrix(content: string, stack: StackConfig): string {
205
160
  let result = content;
206
161
 
207
- // Fill the database row
208
- if (stack.db !== 'none') {
209
- const { tech, skill } = DB_LABELS[stack.db];
210
- result = result.replace(
211
- /(\| `database`\s*\|)\s*\|(\s*\|)/,
212
- `$1 ${tech} | \`${skill}\` $2`
213
- );
162
+ // Find first selected DB tool
163
+ const db = stack.techTools.find((t) => (DB_TOOLS as readonly string[]).includes(t));
164
+ if (db) {
165
+ const info = TECH_TOOL_INFO[db as TechTool];
166
+ if (info?.skill) {
167
+ result = result.replace(
168
+ /(\| `database`\s*\|)\s*\|(\s*\|)/,
169
+ `$1 ${info.tech} | \`${info.skill}\` $2`
170
+ );
171
+ }
214
172
  }
215
173
 
216
- // Fill the CMS row
217
- if (stack.cms !== 'none') {
218
- const { tech, skill } = CMS_LABELS[stack.cms];
219
- result = result.replace(
220
- /(\| `cms`\s*\|)\s*\|(\s*\|)/,
221
- `$1 ${tech} | \`${skill}\` $2`
222
- );
174
+ // Find first selected CMS tool
175
+ const cms = stack.techTools.find((t) => (CMS_TOOLS as readonly string[]).includes(t));
176
+ if (cms) {
177
+ const info = TECH_TOOL_INFO[cms as TechTool];
178
+ if (info?.skill) {
179
+ result = result.replace(
180
+ /(\| `cms`\s*\|)\s*\|(\s*\|)/,
181
+ `$1 ${info.tech} | \`${info.skill}\` $2`
182
+ );
183
+ }
223
184
  }
224
185
 
225
186
  return result;
226
187
  }
188
+
189
+ // ── Agent tool injection ──────────────────────────────────────
190
+
191
+ /**
192
+ * Compute tool injections per agent based on the user's selected stack.
193
+ * Returns a Map where key = agent name (e.g. 'content-engineer'), value = tools to inject.
194
+ */
195
+ export function getAgentToolInjections(stack: StackConfig): Map<string, string[]> {
196
+ const injections = new Map<string, string[]>();
197
+ const selectedIds = [...stack.techTools, ...stack.teamTools] as string[];
198
+
199
+ for (const id of selectedIds) {
200
+ const plugin = PLUGINS[id];
201
+ if (!plugin?.agentToolMap) continue;
202
+
203
+ for (const [agentName, tools] of Object.entries(plugin.agentToolMap)) {
204
+ const existing = injections.get(agentName) ?? [];
205
+ existing.push(...tools);
206
+ injections.set(agentName, existing);
207
+ }
208
+ }
209
+
210
+ return injections;
211
+ }
212
+
213
+ /**
214
+ * Returns a transform callback that injects plugin-specific tools
215
+ * into agent file frontmatter based on the user's stack selection.
216
+ */
217
+ export function getAgentTransform(
218
+ stack: StackConfig
219
+ ): NonNullable<CopyDirOptions['transform']> {
220
+ const injections = getAgentToolInjections(stack);
221
+
222
+ return (content: string, srcPath: string) => {
223
+ // Extract agent name from filename (e.g., 'content-engineer' from 'content-engineer.agent.md')
224
+ const match = srcPath.match(/([^/\\]+)\.agent\.md$/);
225
+ if (!match) return content;
226
+
227
+ const agentName = match[1];
228
+ const toolsToInject = injections.get(agentName);
229
+ if (!toolsToInject || toolsToInject.length === 0) return content;
230
+
231
+ // Parse the frontmatter to find the tools array
232
+ const fmMatch = content.match(/^(---\n)([\s\S]*?)\n(---\n)([\s\S]*)$/);
233
+ if (!fmMatch) return content;
234
+
235
+ const frontmatter = fmMatch[2];
236
+ const body = fmMatch[4];
237
+
238
+ // Find and modify the tools line
239
+ const toolsMatch = frontmatter.match(/^(tools:\s*\[)(.*?)(\]\s*)$/m);
240
+ if (!toolsMatch) return content;
241
+
242
+ const existingTools = toolsMatch[2];
243
+ const injectedToolsList = toolsToInject.map((t) => `'${t}'`).join(', ');
244
+ const newTools = existingTools
245
+ ? `${existingTools}, ${injectedToolsList}`
246
+ : injectedToolsList;
247
+
248
+ const newFrontmatter = frontmatter.replace(
249
+ toolsMatch[0],
250
+ `${toolsMatch[1]}${newTools}${toolsMatch[3]}`
251
+ );
252
+
253
+ return `---\n${newFrontmatter}\n---\n${body}`;
254
+ };
255
+ }
package/src/cli/types.ts CHANGED
@@ -2,16 +2,36 @@ import type { ChildProcess } from 'node:child_process';
2
2
 
3
3
  // ── Stack selection types ──────────────────────────────────────
4
4
 
5
- export type CmsChoice = 'sanity' | 'contentful' | 'strapi' | 'none';
6
- export type DbChoice = 'supabase' | 'convex' | 'none';
7
- export type PmChoice = 'linear' | 'jira' | 'none';
8
- export type NotifChoice = 'slack' | 'teams' | 'none';
5
+ export type IdeChoice = 'vscode' | 'cursor' | 'claude-code' | 'opencode';
6
+ export type TechTool = 'sanity' | 'contentful' | 'strapi' | 'supabase' | 'convex' | 'vercel' | 'nx' | 'chrome-devtools';
7
+ export type TeamTool = 'linear' | 'jira' | 'slack' | 'teams';
9
8
 
10
9
  export interface StackConfig {
11
- cms: CmsChoice;
12
- db: DbChoice;
13
- pm: PmChoice;
14
- notifications: NotifChoice;
10
+ ides: IdeChoice[];
11
+ techTools: TechTool[];
12
+ teamTools: TeamTool[];
13
+ }
14
+
15
+ /** Check if a stack config uses the legacy v1 format (individual choices with 'none'). */
16
+ export function isLegacyStack(stack: unknown): stack is { cms: string; db: string; pm?: string; notifications?: string } {
17
+ return typeof stack === 'object' && stack !== null && 'cms' in stack;
18
+ }
19
+
20
+ /** Migrate a legacy v1 stack config to the v2 array format. */
21
+ export function migrateStackConfig(
22
+ legacy: { cms: string; db: string; pm?: string; notifications?: string },
23
+ ide?: string
24
+ ): StackConfig {
25
+ const techTools: TechTool[] = [];
26
+ const teamTools: TeamTool[] = [];
27
+
28
+ if (legacy.cms && legacy.cms !== 'none') techTools.push(legacy.cms as TechTool);
29
+ if (legacy.db && legacy.db !== 'none') techTools.push(legacy.db as TechTool);
30
+ if (legacy.pm && legacy.pm !== 'none') teamTools.push(legacy.pm as TeamTool);
31
+ if (legacy.notifications && legacy.notifications !== 'none') teamTools.push(legacy.notifications as TeamTool);
32
+
33
+ const ides: IdeChoice[] = ide ? [ide as IdeChoice] : [];
34
+ return { ides, techTools, teamTools };
15
35
  }
16
36
 
17
37
  /** Context passed from bin/cli.mjs to every command handler. */
@@ -60,6 +80,7 @@ export interface RepoInfo {
60
80
  export interface Manifest {
61
81
  version: string;
62
82
  ide: string;
83
+ ides?: string[];
63
84
  installedAt: string;
64
85
  updatedAt: string;
65
86
  managedPaths?: ManagedPaths;
@@ -85,6 +106,8 @@ export interface SelectOption {
85
106
  label: string;
86
107
  hint?: string;
87
108
  value: string;
109
+ /** Whether this option starts selected (preselected). */
110
+ selected?: boolean;
88
111
  }
89
112
 
90
113
  /** Scaffold result from MCP config. */
@@ -93,6 +116,14 @@ export interface ScaffoldResult {
93
116
  action: 'created' | 'skipped';
94
117
  }
95
118
 
119
+ /** IDE display labels. */
120
+ export const IDE_LABELS: Record<IdeChoice, string> = {
121
+ vscode: 'VS Code',
122
+ cursor: 'Cursor',
123
+ 'claude-code': 'Claude Code',
124
+ opencode: 'OpenCode',
125
+ };
126
+
96
127
  // ── Run command types ──────────────────────────────────────────
97
128
 
98
129
  /** Validated task spec from YAML. */