opencastle 0.5.1 → 0.7.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 (231) hide show
  1. package/README.md +5 -4
  2. package/dist/cli/adapters/claude-code.d.ts +2 -2
  3. package/dist/cli/adapters/claude-code.d.ts.map +1 -1
  4. package/dist/cli/adapters/claude-code.js +31 -4
  5. package/dist/cli/adapters/claude-code.js.map +1 -1
  6. package/dist/cli/adapters/cursor.d.ts +2 -2
  7. package/dist/cli/adapters/cursor.d.ts.map +1 -1
  8. package/dist/cli/adapters/cursor.js +28 -4
  9. package/dist/cli/adapters/cursor.js.map +1 -1
  10. package/dist/cli/adapters/opencode.d.ts +20 -0
  11. package/dist/cli/adapters/opencode.d.ts.map +1 -0
  12. package/dist/cli/adapters/opencode.js +265 -0
  13. package/dist/cli/adapters/opencode.js.map +1 -0
  14. package/dist/cli/adapters/vscode.d.ts +2 -2
  15. package/dist/cli/adapters/vscode.d.ts.map +1 -1
  16. package/dist/cli/adapters/vscode.js +38 -7
  17. package/dist/cli/adapters/vscode.js.map +1 -1
  18. package/dist/cli/copy.d.ts +12 -0
  19. package/dist/cli/copy.d.ts.map +1 -1
  20. package/dist/cli/copy.js +27 -0
  21. package/dist/cli/copy.js.map +1 -1
  22. package/dist/cli/detect.d.ts +18 -0
  23. package/dist/cli/detect.d.ts.map +1 -0
  24. package/dist/cli/detect.js +434 -0
  25. package/dist/cli/detect.js.map +1 -0
  26. package/dist/cli/gitignore.d.ts.map +1 -1
  27. package/dist/cli/gitignore.js +0 -2
  28. package/dist/cli/gitignore.js.map +1 -1
  29. package/dist/cli/init.d.ts.map +1 -1
  30. package/dist/cli/init.js +154 -91
  31. package/dist/cli/init.js.map +1 -1
  32. package/dist/cli/manifest.d.ts +1 -1
  33. package/dist/cli/manifest.d.ts.map +1 -1
  34. package/dist/cli/manifest.js +2 -1
  35. package/dist/cli/manifest.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -6
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +105 -34
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/prompt.d.ts +22 -0
  41. package/dist/cli/prompt.d.ts.map +1 -1
  42. package/dist/cli/prompt.js +239 -0
  43. package/dist/cli/prompt.js.map +1 -1
  44. package/dist/cli/stack-config.d.ts +26 -3
  45. package/dist/cli/stack-config.d.ts.map +1 -1
  46. package/dist/cli/stack-config.js +140 -125
  47. package/dist/cli/stack-config.js.map +1 -1
  48. package/dist/cli/types.d.ts +46 -10
  49. package/dist/cli/types.d.ts.map +1 -1
  50. package/dist/cli/types.js +26 -1
  51. package/dist/cli/types.js.map +1 -1
  52. package/dist/cli/update.d.ts.map +1 -1
  53. package/dist/cli/update.js +66 -19
  54. package/dist/cli/update.js.map +1 -1
  55. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
  56. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
  57. package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
  58. package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
  59. package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
  60. package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
  61. package/dist/orchestrator/plugins/contentful/config.js +48 -0
  62. package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
  63. package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
  64. package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
  65. package/dist/orchestrator/plugins/convex/config.js +32 -0
  66. package/dist/orchestrator/plugins/convex/config.js.map +1 -0
  67. package/dist/orchestrator/plugins/index.d.ts +28 -0
  68. package/dist/orchestrator/plugins/index.d.ts.map +1 -0
  69. package/dist/orchestrator/plugins/index.js +63 -0
  70. package/dist/orchestrator/plugins/index.js.map +1 -0
  71. package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
  72. package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
  73. package/dist/orchestrator/plugins/jira/config.js +29 -0
  74. package/dist/orchestrator/plugins/jira/config.js.map +1 -0
  75. package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
  76. package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
  77. package/dist/orchestrator/plugins/linear/config.js +33 -0
  78. package/dist/orchestrator/plugins/linear/config.js.map +1 -0
  79. package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
  80. package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
  81. package/dist/orchestrator/plugins/nx/config.js +28 -0
  82. package/dist/orchestrator/plugins/nx/config.js.map +1 -0
  83. package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
  84. package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
  85. package/dist/orchestrator/plugins/sanity/config.js +43 -0
  86. package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
  87. package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
  88. package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
  89. package/dist/orchestrator/plugins/slack/config.js +34 -0
  90. package/dist/orchestrator/plugins/slack/config.js.map +1 -0
  91. package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
  92. package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
  93. package/dist/orchestrator/plugins/strapi/config.js +40 -0
  94. package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
  95. package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
  96. package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
  97. package/dist/orchestrator/plugins/supabase/config.js +33 -0
  98. package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
  99. package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
  100. package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
  101. package/dist/orchestrator/plugins/teams/config.js +43 -0
  102. package/dist/orchestrator/plugins/teams/config.js.map +1 -0
  103. package/dist/orchestrator/plugins/types.d.ts +61 -0
  104. package/dist/orchestrator/plugins/types.d.ts.map +1 -0
  105. package/dist/orchestrator/plugins/types.js +2 -0
  106. package/dist/orchestrator/plugins/types.js.map +1 -0
  107. package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
  108. package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
  109. package/dist/orchestrator/plugins/vercel/config.js +32 -0
  110. package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
  111. package/package.json +1 -1
  112. package/src/cli/adapters/claude-code.ts +40 -6
  113. package/src/cli/adapters/cursor.ts +46 -6
  114. package/src/cli/adapters/opencode.ts +320 -0
  115. package/src/cli/adapters/vscode.ts +43 -9
  116. package/src/cli/copy.ts +32 -0
  117. package/src/cli/detect.ts +483 -0
  118. package/src/cli/gitignore.ts +0 -3
  119. package/src/cli/init.ts +169 -96
  120. package/src/cli/manifest.ts +2 -1
  121. package/src/cli/mcp.ts +131 -51
  122. package/src/cli/prompt.ts +299 -0
  123. package/src/cli/stack-config.ts +187 -145
  124. package/src/cli/types.ts +60 -9
  125. package/src/cli/update.ts +78 -20
  126. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  127. package/src/orchestrator/agent-workflows/README.md +1 -1
  128. package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
  129. package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
  130. package/src/orchestrator/agent-workflows/database-migration.md +11 -11
  131. package/src/orchestrator/agent-workflows/feature-implementation.md +10 -10
  132. package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
  133. package/src/orchestrator/agent-workflows/refactoring.md +10 -10
  134. package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
  135. package/src/orchestrator/agent-workflows/security-audit.md +12 -12
  136. package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
  137. package/src/orchestrator/agents/api-designer.agent.md +2 -2
  138. package/src/orchestrator/agents/architect.agent.md +2 -2
  139. package/src/orchestrator/agents/content-engineer.agent.md +4 -4
  140. package/src/orchestrator/agents/copywriter.agent.md +2 -2
  141. package/src/orchestrator/agents/data-expert.agent.md +6 -6
  142. package/src/orchestrator/agents/database-engineer.agent.md +4 -4
  143. package/src/orchestrator/agents/developer.agent.md +5 -5
  144. package/src/orchestrator/agents/devops-expert.agent.md +5 -5
  145. package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
  146. package/src/orchestrator/agents/performance-expert.agent.md +3 -3
  147. package/src/orchestrator/agents/release-manager.agent.md +4 -4
  148. package/src/orchestrator/agents/researcher.agent.md +19 -3
  149. package/src/orchestrator/agents/reviewer.agent.md +2 -4
  150. package/src/orchestrator/agents/security-expert.agent.md +4 -4
  151. package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
  152. package/src/orchestrator/agents/team-lead.agent.md +97 -101
  153. package/src/orchestrator/agents/testing-expert.agent.md +5 -5
  154. package/src/orchestrator/agents/ui-ux-expert.agent.md +7 -7
  155. package/src/orchestrator/copilot-instructions.md +1 -1
  156. package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
  157. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
  158. package/src/orchestrator/customizations/DISPUTES.md +5 -5
  159. package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
  160. package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
  161. package/src/orchestrator/customizations/README.md +5 -2
  162. package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
  163. package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
  164. package/src/orchestrator/customizations/logs/README.md +1 -1
  165. package/src/orchestrator/customizations/project/decisions.md +31 -0
  166. package/src/orchestrator/customizations/project/docs-structure.md +16 -5
  167. package/src/orchestrator/customizations/project/roadmap.md +24 -0
  168. package/src/orchestrator/customizations/project/tracker-config.md +1 -1
  169. package/src/orchestrator/customizations/stack/cms-config.md +1 -1
  170. package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
  171. package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
  172. package/src/orchestrator/instructions/general.instructions.md +102 -40
  173. package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
  174. package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
  175. package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
  176. package/src/orchestrator/plugins/contentful/config.ts +49 -0
  177. package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
  178. package/src/orchestrator/plugins/convex/config.ts +33 -0
  179. package/src/orchestrator/plugins/index.ts +85 -0
  180. package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
  181. package/src/orchestrator/plugins/jira/config.ts +30 -0
  182. package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
  183. package/src/orchestrator/plugins/linear/config.ts +34 -0
  184. package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
  185. package/src/orchestrator/plugins/nx/config.ts +29 -0
  186. package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
  187. package/src/orchestrator/plugins/sanity/config.ts +44 -0
  188. package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
  189. package/src/orchestrator/plugins/slack/config.ts +35 -0
  190. package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
  191. package/src/orchestrator/plugins/strapi/config.ts +41 -0
  192. package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
  193. package/src/orchestrator/plugins/supabase/config.ts +34 -0
  194. package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
  195. package/src/orchestrator/plugins/teams/config.ts +44 -0
  196. package/src/orchestrator/plugins/types.ts +79 -0
  197. package/src/orchestrator/plugins/vercel/config.ts +33 -0
  198. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +59 -12
  199. package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
  200. package/src/orchestrator/prompts/bug-fix.prompt.md +18 -18
  201. package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
  202. package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
  203. package/src/orchestrator/prompts/implement-feature.prompt.md +26 -26
  204. package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
  205. package/src/orchestrator/prompts/quick-refinement.prompt.md +16 -16
  206. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +2 -2
  207. package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
  208. package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
  209. package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
  210. package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
  211. package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
  212. package/src/orchestrator/skills/context-map/SKILL.md +4 -4
  213. package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
  214. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
  215. package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
  216. package/src/orchestrator/skills/fast-review/SKILL.md +3 -3
  217. package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
  218. package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
  219. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
  220. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
  221. package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
  222. package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
  223. package/src/orchestrator/skills/react-development/SKILL.md +3 -3
  224. package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
  225. package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
  226. package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
  227. package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
  228. package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
  229. package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
  230. package/src/orchestrator/skills/validation-gates/SKILL.md +8 -15
  231. package/src/orchestrator/mcp.json +0 -61
@@ -1,110 +1,43 @@
1
- import type { CmsChoice, DbChoice, PmChoice, NotifChoice, StackConfig, CopyDirOptions } 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
- };
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
+ }
61
20
 
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
- };
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>;
68
25
 
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
- };
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>;
76
30
 
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
- };
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[];
83
35
 
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: [],
36
+ /** MCP servers auto-included when detected in the repo. */
37
+ const DETECTED_MCP_MAP: Record<string, string> = {
38
+ vercel: 'Vercel',
89
39
  };
90
40
 
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
- };
97
-
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
- };
104
-
105
- /** Always-included MCP servers */
106
- const CORE_MCP_SERVERS = ['chrome-devtools', 'Vercel'];
107
-
108
41
  // ── MCP environment variable requirements ─────────────────────
109
42
 
110
43
  export interface McpEnvRequirement {
@@ -118,49 +51,85 @@ export interface McpEnvRequirement {
118
51
 
119
52
  /**
120
53
  * Registry of MCP servers that require API keys via environment variables.
121
- * Only servers with `env` fields in mcp.json need to be listed here.
122
- * HTTP-based servers (Sanity, Slack, Vercel, etc.) handle auth via OAuth.
54
+ * Derived from plugin configs only plugins with envVars are included.
123
55
  */
124
- const MCP_ENV_REQUIREMENTS: McpEnvRequirement[] = [
125
- {
126
- server: 'Linear',
127
- envVar: 'LINEAR_API_KEY',
128
- hint: 'Create at linear.app → Settings → API → Personal API keys',
129
- },
130
- ];
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 ──────────────────────────────────────────
131
67
 
68
+ /**
69
+ * Skills to EXCLUDE — all tool-specific skills that are NOT selected.
70
+ */
132
71
  export function getExcludedSkills(stack: StackConfig): Set<string> {
133
- return new Set([
134
- ...CMS_SKILL_MAP[stack.cms],
135
- ...DB_SKILL_MAP[stack.db],
136
- ...PM_SKILL_MAP[stack.pm ?? 'none'],
137
- ...NOTIF_SKILL_MAP[stack.notifications ?? 'none'],
138
- ]);
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)));
75
+ }
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]);
139
82
  }
140
83
 
84
+ /**
85
+ * Agents to EXCLUDE — content-engineer if no CMS, database-engineer if no DB.
86
+ */
141
87
  export function getExcludedAgents(stack: StackConfig): Set<string> {
142
- return new Set([
143
- ...CMS_AGENT_EXCLUSIONS[stack.cms],
144
- ...DB_AGENT_EXCLUSIONS[stack.db],
145
- ]);
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;
146
96
  }
147
97
 
148
- export function getIncludedMcpServers(stack: StackConfig): Set<string> {
149
- return new Set([
150
- ...CORE_MCP_SERVERS,
151
- ...CMS_MCP_MAP[stack.cms],
152
- ...DB_MCP_MAP[stack.db],
153
- ...PM_MCP_MAP[stack.pm ?? 'none'],
154
- ...NOTIF_MCP_MAP[stack.notifications ?? 'none'],
155
- ]);
98
+ /**
99
+ * MCP servers to INCLUDE — core + selected tools + auto-detected from repo.
100
+ */
101
+ export function getIncludedMcpServers(stack: StackConfig, repoInfo?: RepoInfo): Set<string> {
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
+ }
112
+
113
+ // Add servers for detected deployment targets
114
+ for (const dep of repoInfo?.deployment ?? []) {
115
+ const server = DETECTED_MCP_MAP[dep];
116
+ if (server) servers.add(server);
117
+ }
118
+
119
+ // Auto-detect NX from monorepo info
120
+ if (repoInfo?.monorepo === 'nx' && !stack.techTools.includes('nx')) {
121
+ servers.add('Nx');
122
+ }
123
+
124
+ return servers;
156
125
  }
157
126
 
158
127
  /**
159
128
  * Returns env var requirements for the MCP servers included in the stack.
160
129
  * Only returns entries for servers that actually need API keys.
161
130
  */
162
- export function getRequiredMcpEnvVars(stack: StackConfig): McpEnvRequirement[] {
163
- const included = getIncludedMcpServers(stack);
131
+ export function getRequiredMcpEnvVars(stack: StackConfig, repoInfo?: RepoInfo): McpEnvRequirement[] {
132
+ const included = getIncludedMcpServers(stack, repoInfo);
164
133
  return MCP_ENV_REQUIREMENTS.filter((req) => included.has(req.server));
165
134
  }
166
135
 
@@ -176,7 +145,6 @@ export function getCustomizationsTransform(
176
145
  stack: StackConfig
177
146
  ): NonNullable<CopyDirOptions['transform']> {
178
147
  return (content: string, srcPath: string) => {
179
- // Pre-fill skill matrix with CMS and DB bindings
180
148
  if (srcPath.endsWith('skill-matrix.md')) {
181
149
  return transformSkillMatrix(content, stack);
182
150
  }
@@ -191,23 +159,97 @@ export function getCustomizationsTransform(
191
159
  function transformSkillMatrix(content: string, stack: StackConfig): string {
192
160
  let result = content;
193
161
 
194
- // Fill the database row
195
- if (stack.db !== 'none') {
196
- const { tech, skill } = DB_LABELS[stack.db];
197
- result = result.replace(
198
- /(\| `database`\s*\|)\s*\|(\s*\|)/,
199
- `$1 ${tech} | \`${skill}\` $2`
200
- );
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
+ }
201
172
  }
202
173
 
203
- // Fill the CMS row
204
- if (stack.cms !== 'none') {
205
- const { tech, skill } = CMS_LABELS[stack.cms];
206
- result = result.replace(
207
- /(\| `cms`\s*\|)\s*\|(\s*\|)/,
208
- `$1 ${tech} | \`${skill}\` $2`
209
- );
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
+ }
210
184
  }
211
185
 
212
186
  return result;
213
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. */
@@ -37,14 +57,35 @@ export interface CopyDirOptions {
37
57
  ) => Promise<string | null> | string | null;
38
58
  }
39
59
 
60
+ /** Combined repository tooling info — auto-detected + user-declared. */
61
+ export interface RepoInfo {
62
+ packageManager?: string;
63
+ monorepo?: string;
64
+ language?: string;
65
+ frameworks?: string[];
66
+ databases?: string[];
67
+ cms?: string[];
68
+ deployment?: string[];
69
+ testing?: string[];
70
+ cicd?: string[];
71
+ styling?: string[];
72
+ auth?: string[];
73
+ pm?: string[];
74
+ notifications?: string[];
75
+ mcpConfig?: boolean;
76
+ configFiles?: string[];
77
+ }
78
+
40
79
  /** OpenCastle project manifest (.opencastle.json). */
41
80
  export interface Manifest {
42
81
  version: string;
43
82
  ide: string;
83
+ ides?: string[];
44
84
  installedAt: string;
45
85
  updatedAt: string;
46
86
  managedPaths?: ManagedPaths;
47
87
  stack?: StackConfig;
88
+ repoInfo?: RepoInfo;
48
89
  }
49
90
 
50
91
  /** Framework vs customizable file paths. */
@@ -55,7 +96,7 @@ export interface ManagedPaths {
55
96
 
56
97
  /** IDE adapter interface (init/update commands). */
57
98
  export interface IdeAdapter {
58
- install(_pkgRoot: string, _projectRoot: string, _stack?: StackConfig): Promise<CopyResults>;
99
+ install(_pkgRoot: string, _projectRoot: string, _stack?: StackConfig, _repoInfo?: RepoInfo): Promise<CopyResults>;
59
100
  update(_pkgRoot: string, _projectRoot: string, _stack?: StackConfig): Promise<CopyResults>;
60
101
  getManagedPaths(): ManagedPaths;
61
102
  }
@@ -65,6 +106,8 @@ export interface SelectOption {
65
106
  label: string;
66
107
  hint?: string;
67
108
  value: string;
109
+ /** Whether this option starts selected (preselected). */
110
+ selected?: boolean;
68
111
  }
69
112
 
70
113
  /** Scaffold result from MCP config. */
@@ -73,6 +116,14 @@ export interface ScaffoldResult {
73
116
  action: 'created' | 'skipped';
74
117
  }
75
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
+
76
127
  // ── Run command types ──────────────────────────────────────────
77
128
 
78
129
  /** Validated task spec from YAML. */
package/src/cli/update.ts CHANGED
@@ -1,18 +1,29 @@
1
1
  import { resolve } from 'node:path'
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { readManifest, writeManifest } from './manifest.js'
4
- import { confirm, closePrompts } from './prompt.js'
5
- import type { CliContext, IdeAdapter } from './types.js'
4
+ import { confirm, closePrompts, c } from './prompt.js'
5
+ import { isLegacyStack, migrateStackConfig } from './types.js'
6
+ import type { CliContext, IdeAdapter, IdeChoice } from './types.js'
6
7
 
7
8
  const ADAPTERS: Record<string, () => Promise<IdeAdapter>> = {
8
9
  vscode: () => import('./adapters/vscode.js') as Promise<IdeAdapter>,
9
10
  cursor: () => import('./adapters/cursor.js') as Promise<IdeAdapter>,
10
11
  'claude-code': () =>
11
12
  import('./adapters/claude-code.js') as Promise<IdeAdapter>,
13
+ opencode: () =>
14
+ import('./adapters/opencode.js') as Promise<IdeAdapter>,
12
15
  }
13
16
 
14
17
  const VALID_IDES = Object.keys(ADAPTERS)
15
18
 
19
+ /** IDE display labels */
20
+ const IDE_DISPLAY: Record<IdeChoice, string> = {
21
+ vscode: 'VS Code',
22
+ cursor: 'Cursor',
23
+ 'claude-code': 'Claude Code',
24
+ opencode: 'OpenCode',
25
+ }
26
+
16
27
  export default async function update({
17
28
  pkgRoot,
18
29
  args,
@@ -22,18 +33,27 @@ export default async function update({
22
33
  const manifest = await readManifest(projectRoot)
23
34
  if (!manifest) {
24
35
  console.error(
25
- ' ✗ No OpenCastle installation found. Run "npx opencastle init" first.'
36
+ ` ${c.red('✗')} No OpenCastle installation found. Run "npx opencastle init" first.`
26
37
  )
27
38
  process.exit(1)
28
39
  }
29
40
 
30
- if (!manifest.ide || !VALID_IDES.includes(manifest.ide)) {
41
+ // Determine list of IDEs to update (support legacy single-IDE manifests)
42
+ const ides = manifest.ides?.length ? manifest.ides : [manifest.ide]
43
+ const invalidIdes = ides.filter((id) => !VALID_IDES.includes(id))
44
+ if (invalidIdes.length > 0) {
31
45
  console.error(
32
- ` ✗ Invalid IDE "${manifest.ide}" in .opencastle.json. Valid options: ${VALID_IDES.join(', ')}`
46
+ ` ${c.red('')} Invalid IDE(s) "${invalidIdes.join(', ')}" in .opencastle.json. Valid: ${VALID_IDES.join(', ')}`
33
47
  )
34
48
  process.exit(1)
35
49
  }
36
50
 
51
+ // Migrate legacy stack config if needed
52
+ if (manifest.stack && isLegacyStack(manifest.stack)) {
53
+ manifest.stack = migrateStackConfig(manifest.stack, manifest.ide)
54
+ manifest.stack.ides = ides as IdeChoice[]
55
+ }
56
+
37
57
  const pkg = JSON.parse(
38
58
  await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
39
59
  ) as { version: string }
@@ -45,23 +65,24 @@ export default async function update({
45
65
  return
46
66
  }
47
67
 
68
+ const ideNames = ides.map((id) => IDE_DISPLAY[id as IdeChoice] ?? id).join(', ')
48
69
  console.log(
49
- `\n 🏰 OpenCastle ${dryRun ? 'dry-run' : 'update'}: v${manifest.version} → v${pkg.version}\n`
70
+ `\n 🏰 ${c.bold('OpenCastle')} ${dryRun ? 'dry-run' : 'update'}: ${c.dim(`v${manifest.version}`)}${c.green(`v${pkg.version}`)}\n`
50
71
  )
51
- console.log(` IDE: ${manifest.ide}`)
52
- console.log(' Framework files will be overwritten.')
53
- console.log(' Customization files will be preserved.\n')
72
+ console.log(` IDEs: ${c.cyan(ideNames)}`)
73
+ console.log(` ${c.dim('Framework files will be overwritten.')}`)
74
+ console.log(` ${c.dim('Customization files will be preserved.')}\n`)
54
75
 
55
76
  if (dryRun) {
56
- console.log(' [dry-run] Framework files that would be updated:\n')
77
+ console.log(` ${c.dim('[dry-run]')} Framework files that would be updated:\n`)
57
78
  for (const p of manifest.managedPaths?.framework ?? []) {
58
- console.log(` ↻ ${p}`)
79
+ console.log(` ${c.yellow('')} ${p}`)
59
80
  }
60
- console.log('\n Customization files that would be preserved:\n')
81
+ console.log(`\n ${c.dim('[dry-run]')} Customization files that would be preserved:\n`)
61
82
  for (const p of manifest.managedPaths?.customizable ?? []) {
62
- console.log(` ✓ ${p}`)
83
+ console.log(` ${c.green('')} ${p}`)
63
84
  }
64
- console.log('\n No files were written.\n')
85
+ console.log(`\n ${c.dim('No files were written.')}\n`)
65
86
  return
66
87
  }
67
88
 
@@ -71,18 +92,55 @@ export default async function update({
71
92
  return
72
93
  }
73
94
 
74
- const adapter = await ADAPTERS[manifest.ide]()
75
- const results = await adapter.update(pkgRoot, projectRoot, manifest.stack)
95
+ // Update each IDE
96
+ let totalCopied = 0
97
+ let totalCreated = 0
98
+ const allManagedPaths = { framework: [] as string[], customizable: [] as string[] }
99
+
100
+ for (const ide of ides) {
101
+ const adapter = await ADAPTERS[ide]()
102
+ const results = await adapter.update(pkgRoot, projectRoot, manifest.stack)
103
+ totalCopied += results.copied.length
104
+ totalCreated += results.created.length
105
+
106
+ const managed = adapter.getManagedPaths()
107
+ allManagedPaths.framework.push(...managed.framework)
108
+ allManagedPaths.customizable.push(...managed.customizable)
109
+ }
110
+
111
+ // Refresh repo research on update
112
+ const { detectRepoInfo, mergeStackIntoRepoInfo } = await import('./detect.js')
113
+ const repoInfo = await detectRepoInfo(projectRoot)
76
114
 
77
115
  // Update manifest
78
116
  manifest.version = pkg.version
117
+ manifest.ides = ides
79
118
  manifest.updatedAt = new Date().toISOString()
80
- manifest.managedPaths = adapter.getManagedPaths()
119
+ manifest.managedPaths = allManagedPaths
120
+ manifest.repoInfo = manifest.stack
121
+ ? mergeStackIntoRepoInfo(repoInfo, manifest.stack)
122
+ : repoInfo
81
123
  await writeManifest(projectRoot, manifest)
82
124
 
83
- console.log(`\n ✓ Updated ${results.copied.length} framework files`)
84
- if (results.created.length > 0) {
85
- console.log(` + Created ${results.created.length} new files`)
125
+ console.log(`\n ${c.green('')} Updated ${c.bold(String(totalCopied))} framework files`)
126
+ if (totalCreated > 0) {
127
+ console.log(` ${c.green('+')} Created ${c.bold(String(totalCreated))} new files`)
128
+ }
129
+
130
+ // ── Reload window message ─────────────────────────────────────
131
+ const needsReload = ides.filter((id) => ['vscode', 'cursor'].includes(id))
132
+ if (needsReload.length > 0) {
133
+ console.log()
134
+ if (needsReload.includes('vscode')) {
135
+ console.log(
136
+ ` ${c.yellow('⟳')} Reload VS Code window (Cmd+Shift+P → "Developer: Reload Window") to pick up changes`
137
+ )
138
+ }
139
+ if (needsReload.includes('cursor')) {
140
+ console.log(
141
+ ` ${c.yellow('⟳')} Reload Cursor window to pick up the updated rule files`
142
+ )
143
+ }
86
144
  }
87
145
  console.log()
88
146