@girardmedia/bootspring 1.2.0 → 2.0.3

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 (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
package/core/config.js CHANGED
@@ -8,6 +8,502 @@
8
8
 
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
+ const { z } = require('zod');
12
+
13
+ // =============================================================================
14
+ // Zod Schemas for Configuration Validation
15
+ // =============================================================================
16
+
17
+ // -----------------------------------------------------------------------------
18
+ // Plugin Schemas
19
+ // -----------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Base plugin schema - all plugins share these common fields
23
+ */
24
+ const BasePluginSchema = z.object({
25
+ enabled: z.boolean().optional().default(false),
26
+ provider: z.string().optional(),
27
+ features: z.array(z.string()).optional().default([])
28
+ }).passthrough();
29
+
30
+ /**
31
+ * Auth plugin schema with specific provider options
32
+ */
33
+ const AuthPluginSchema = z.object({
34
+ enabled: z.boolean().optional().default(false),
35
+ provider: z.enum(['clerk', 'nextauth', 'auth0', 'supabase', 'jwt', 'custom']).optional(),
36
+ features: z.array(z.enum([
37
+ 'social_login', 'email_password', 'magic_link', 'sso', 'mfa', 'rbac', 'passwordless'
38
+ ])).optional().default([])
39
+ }).passthrough();
40
+
41
+ /**
42
+ * Payments plugin schema
43
+ */
44
+ const PaymentsPluginSchema = z.object({
45
+ enabled: z.boolean().optional().default(false),
46
+ provider: z.enum(['stripe', 'paddle', 'lemonsqueezy', 'paypal', 'custom']).optional(),
47
+ features: z.array(z.enum([
48
+ 'checkout', 'subscriptions', 'invoices', 'usage_billing', 'trials', 'coupons'
49
+ ])).optional().default([])
50
+ }).passthrough();
51
+
52
+ /**
53
+ * Database plugin schema
54
+ */
55
+ const DatabasePluginSchema = z.object({
56
+ enabled: z.boolean().optional().default(true),
57
+ provider: z.enum(['prisma', 'drizzle', 'typeorm', 'kysely', 'custom']).optional(),
58
+ features: z.array(z.enum([
59
+ 'migrations', 'transactions', 'seeding', 'multi_tenant', 'full_text_search', 'soft_delete'
60
+ ])).optional().default([])
61
+ }).passthrough();
62
+
63
+ /**
64
+ * Testing plugin schema
65
+ */
66
+ const TestingPluginSchema = z.object({
67
+ enabled: z.boolean().optional().default(true),
68
+ provider: z.enum(['vitest', 'jest', 'playwright', 'cypress', 'custom']).optional(),
69
+ features: z.array(z.enum([
70
+ 'unit', 'integration', 'e2e', 'coverage', 'snapshot', 'mocking'
71
+ ])).optional().default([])
72
+ }).passthrough();
73
+
74
+ /**
75
+ * Security plugin schema
76
+ */
77
+ const SecurityPluginSchema = z.object({
78
+ enabled: z.boolean().optional().default(true),
79
+ provider: z.string().optional(),
80
+ features: z.array(z.enum([
81
+ 'input_validation', 'rate_limiting', 'csrf', 'xss', 'sql_injection',
82
+ 'audit', 'rbac', 'encryption', 'secrets_management'
83
+ ])).optional().default([])
84
+ }).passthrough();
85
+
86
+ /**
87
+ * AI plugin schema
88
+ */
89
+ const AIPluginSchema = z.object({
90
+ enabled: z.boolean().optional().default(false),
91
+ provider: z.enum(['anthropic', 'openai', 'google', 'cohere', 'custom']).optional(),
92
+ features: z.array(z.enum([
93
+ 'streaming', 'tool_use', 'embeddings', 'rag', 'agents', 'vision'
94
+ ])).optional().default([])
95
+ }).passthrough();
96
+
97
+ /**
98
+ * Email plugin schema
99
+ */
100
+ const EmailPluginSchema = z.object({
101
+ enabled: z.boolean().optional().default(false),
102
+ provider: z.enum(['resend', 'sendgrid', 'postmark', 'ses', 'mailgun', 'custom']).optional(),
103
+ features: z.array(z.enum([
104
+ 'transactional', 'marketing', 'templates', 'tracking'
105
+ ])).optional().default([])
106
+ }).passthrough();
107
+
108
+ /**
109
+ * Analytics plugin schema
110
+ */
111
+ const AnalyticsPluginSchema = z.object({
112
+ enabled: z.boolean().optional().default(false),
113
+ provider: z.enum(['posthog', 'amplitude', 'mixpanel', 'google_analytics', 'custom']).optional(),
114
+ features: z.array(z.enum([
115
+ 'page_views', 'events', 'user_tracking', 'funnels', 'experiments'
116
+ ])).optional().default([])
117
+ }).passthrough();
118
+
119
+ /**
120
+ * Monitoring plugin schema
121
+ */
122
+ const MonitoringPluginSchema = z.object({
123
+ enabled: z.boolean().optional().default(false),
124
+ provider: z.enum(['sentry', 'datadog', 'newrelic', 'logrocket', 'custom']).optional(),
125
+ features: z.array(z.enum([
126
+ 'error_tracking', 'performance', 'logs', 'alerts', 'apm'
127
+ ])).optional().default([])
128
+ }).passthrough();
129
+
130
+ /**
131
+ * Combined plugins schema with specific plugin types
132
+ */
133
+ const PluginsSchema = z.object({
134
+ auth: AuthPluginSchema.optional(),
135
+ payments: PaymentsPluginSchema.optional(),
136
+ database: DatabasePluginSchema.optional(),
137
+ testing: TestingPluginSchema.optional(),
138
+ security: SecurityPluginSchema.optional(),
139
+ ai: AIPluginSchema.optional(),
140
+ email: EmailPluginSchema.optional(),
141
+ analytics: AnalyticsPluginSchema.optional(),
142
+ monitoring: MonitoringPluginSchema.optional()
143
+ }).catchall(BasePluginSchema); // Allow additional plugins with base schema
144
+
145
+ // Backward compatible alias
146
+ const PluginSchema = BasePluginSchema;
147
+
148
+ // -----------------------------------------------------------------------------
149
+ // Agent Configuration Schema
150
+ // -----------------------------------------------------------------------------
151
+
152
+ /**
153
+ * Individual agent configuration
154
+ */
155
+ const AgentConfigSchema = z.object({
156
+ enabled: z.boolean().optional().default(true),
157
+ expertise: z.array(z.string()).optional(),
158
+ customInstructions: z.string().optional(),
159
+ priority: z.enum(['high', 'medium', 'low']).optional().default('medium')
160
+ }).passthrough();
161
+
162
+ /**
163
+ * Agents section configuration
164
+ */
165
+ const AgentsConfigSchema = z.object({
166
+ // Enable/disable specific agents
167
+ enabled: z.record(z.string(), z.boolean()).optional(),
168
+ // Custom agent configurations
169
+ custom: z.record(z.string(), AgentConfigSchema).optional(),
170
+ // Default agents to suggest based on project type
171
+ defaults: z.array(z.string()).optional(),
172
+ // Agent invocation settings
173
+ settings: z.object({
174
+ maxConcurrent: z.number().int().min(1).max(10).optional().default(3),
175
+ timeout: z.number().int().min(1000).max(300000).optional().default(60000),
176
+ verbose: z.boolean().optional().default(false)
177
+ }).optional()
178
+ }).passthrough();
179
+
180
+ // -----------------------------------------------------------------------------
181
+ // Skills Configuration Schema
182
+ // -----------------------------------------------------------------------------
183
+
184
+ /**
185
+ * Individual skill configuration
186
+ */
187
+ const SkillConfigSchema = z.object({
188
+ enabled: z.boolean().optional().default(true),
189
+ tier: z.enum(['free', 'pro', 'premium']).optional().default('free'),
190
+ category: z.string().optional(),
191
+ maxChars: z.number().int().min(100).optional()
192
+ }).passthrough();
193
+
194
+ /**
195
+ * Skills section configuration
196
+ */
197
+ const SkillsConfigSchema = z.object({
198
+ // Enable/disable skill categories
199
+ categories: z.record(z.string(), z.boolean()).optional(),
200
+ // Custom skill configurations
201
+ custom: z.record(z.string(), SkillConfigSchema).optional(),
202
+ // External skills settings
203
+ external: z.object({
204
+ enabled: z.boolean().optional().default(false),
205
+ manifestUrl: z.string().url().optional(),
206
+ cacheDir: z.string().optional(),
207
+ requireSignature: z.boolean().optional().default(false)
208
+ }).optional(),
209
+ // Skill invocation settings
210
+ settings: z.object({
211
+ defaultMaxChars: z.number().int().min(100).optional().default(50000),
212
+ summaryByDefault: z.boolean().optional().default(false),
213
+ includeExternal: z.boolean().optional().default(false)
214
+ }).optional()
215
+ }).passthrough();
216
+
217
+ // -----------------------------------------------------------------------------
218
+ // Workflows Configuration Schema
219
+ // -----------------------------------------------------------------------------
220
+
221
+ /**
222
+ * Workflow phase schema
223
+ */
224
+ const WorkflowPhaseSchema = z.object({
225
+ name: z.string().min(1, 'Phase name is required'),
226
+ agents: z.array(z.string()).min(1, 'At least one agent is required'),
227
+ duration: z.string().optional(),
228
+ parallel: z.boolean().optional().default(false),
229
+ description: z.string().optional()
230
+ }).passthrough();
231
+
232
+ /**
233
+ * Individual workflow configuration
234
+ */
235
+ const WorkflowConfigSchema = z.object({
236
+ name: z.string().min(1, 'Workflow name is required'),
237
+ description: z.string().optional(),
238
+ tier: z.enum(['free', 'pro']).optional().default('free'),
239
+ pack: z.string().optional(),
240
+ outcomes: z.array(z.string()).optional(),
241
+ completionSignals: z.array(z.string()).optional(),
242
+ phases: z.array(WorkflowPhaseSchema).min(1, 'At least one phase is required')
243
+ }).passthrough();
244
+
245
+ /**
246
+ * Workflows section configuration
247
+ */
248
+ const WorkflowsConfigSchema = z.object({
249
+ // Enable/disable specific workflows
250
+ enabled: z.record(z.string(), z.boolean()).optional(),
251
+ // Custom workflow definitions
252
+ custom: z.record(z.string(), WorkflowConfigSchema).optional(),
253
+ // Default workflow for the project
254
+ default: z.string().optional(),
255
+ // Workflow settings
256
+ settings: z.object({
257
+ autoAdvance: z.boolean().optional().default(true),
258
+ pauseBetweenPhases: z.boolean().optional().default(false),
259
+ trackSignals: z.boolean().optional().default(true),
260
+ emitTelemetry: z.boolean().optional().default(true)
261
+ }).optional()
262
+ }).passthrough();
263
+
264
+ // -----------------------------------------------------------------------------
265
+ // Quality Configuration Schema
266
+ // -----------------------------------------------------------------------------
267
+
268
+ /**
269
+ * Quality check configuration
270
+ */
271
+ const QualityCheckSchema = z.union([
272
+ z.boolean(),
273
+ z.object({
274
+ enabled: z.boolean().optional().default(true),
275
+ checks: z.array(z.enum([
276
+ 'lint', 'typecheck', 'test', 'build', 'security', 'coverage', 'format'
277
+ ])).optional()
278
+ })
279
+ ]);
280
+
281
+ /**
282
+ * Quality section configuration (extended)
283
+ */
284
+ const QualityConfigSchema = z.object({
285
+ preCommit: QualityCheckSchema.optional().default(true),
286
+ prePush: QualityCheckSchema.optional().default(false),
287
+ preDeploy: QualityCheckSchema.optional(),
288
+ strictMode: z.boolean().optional().default(false),
289
+ // Coverage thresholds
290
+ coverage: z.object({
291
+ statements: z.number().min(0).max(100).optional(),
292
+ branches: z.number().min(0).max(100).optional(),
293
+ functions: z.number().min(0).max(100).optional(),
294
+ lines: z.number().min(0).max(100).optional()
295
+ }).optional()
296
+ }).passthrough();
297
+
298
+ // -----------------------------------------------------------------------------
299
+ // Context Configuration Schema
300
+ // -----------------------------------------------------------------------------
301
+
302
+ /**
303
+ * Context generation configuration
304
+ */
305
+ const ContextConfigSchema = z.object({
306
+ includeEnvVars: z.boolean().optional().default(true),
307
+ includeTechStack: z.boolean().optional().default(true),
308
+ includePlugins: z.boolean().optional().default(true),
309
+ includeGitInfo: z.boolean().optional().default(true),
310
+ includeTodos: z.boolean().optional().default(true),
311
+ includeLearnings: z.boolean().optional().default(true),
312
+ customSections: z.array(z.object({
313
+ title: z.string(),
314
+ content: z.string()
315
+ })).optional().default([]),
316
+ // Maximum context file size
317
+ maxSize: z.number().int().min(1000).optional()
318
+ }).passthrough();
319
+
320
+ // -----------------------------------------------------------------------------
321
+ // Paths Configuration Schema
322
+ // -----------------------------------------------------------------------------
323
+
324
+ /**
325
+ * Paths configuration
326
+ */
327
+ const PathsConfigSchema = z.object({
328
+ context: z.string().optional().default('CLAUDE.md'),
329
+ config: z.string().optional().default('bootspring.config.js'),
330
+ todo: z.string().optional().default('todo.md'),
331
+ roadmap: z.string().optional().default('ROADMAP.md'),
332
+ changelog: z.string().optional().default('CHANGELOG.md'),
333
+ state: z.string().optional().default('.bootspring')
334
+ }).passthrough();
335
+
336
+ // -----------------------------------------------------------------------------
337
+ // Dashboard Configuration Schema
338
+ // -----------------------------------------------------------------------------
339
+
340
+ /**
341
+ * Dashboard configuration
342
+ */
343
+ const DashboardConfigSchema = z.object({
344
+ port: z.number().int().min(1024).max(65535).optional().default(3456),
345
+ autoOpen: z.boolean().optional().default(false),
346
+ host: z.string().optional().default('localhost'),
347
+ theme: z.enum(['light', 'dark', 'system']).optional().default('system')
348
+ }).passthrough();
349
+
350
+ // -----------------------------------------------------------------------------
351
+ // Project Configuration Schema
352
+ // -----------------------------------------------------------------------------
353
+
354
+ /**
355
+ * Project information schema
356
+ */
357
+ const ProjectConfigSchema = z.object({
358
+ name: z.string().min(1, 'Project name is required'),
359
+ description: z.string().optional().default(''),
360
+ version: z.string().regex(/^\d+\.\d+\.\d+/, 'Version must be semver format').optional().default('1.0.0'),
361
+ author: z.string().optional(),
362
+ license: z.string().optional(),
363
+ repository: z.string().url().optional()
364
+ }).passthrough();
365
+
366
+ // -----------------------------------------------------------------------------
367
+ // Stack Configuration Schema
368
+ // -----------------------------------------------------------------------------
369
+
370
+ /**
371
+ * Technology stack configuration
372
+ */
373
+ const StackConfigSchema = z.object({
374
+ framework: z.enum([
375
+ 'nextjs', 'remix', 'nuxt', 'sveltekit', 'astro', 'express', 'fastify', 'hono', 'custom'
376
+ ]).optional(),
377
+ language: z.enum(['typescript', 'javascript']).optional(),
378
+ database: z.enum(['postgresql', 'mysql', 'mongodb', 'sqlite', 'supabase', 'planetscale', 'none']).optional(),
379
+ hosting: z.enum([
380
+ 'vercel', 'railway', 'render', 'fly', 'aws', 'gcp', 'azure', 'cloudflare', 'self-hosted', 'custom'
381
+ ]).optional()
382
+ }).passthrough();
383
+
384
+ // -----------------------------------------------------------------------------
385
+ // Main Configuration Schema
386
+ // -----------------------------------------------------------------------------
387
+
388
+ /**
389
+ * Complete Bootspring configuration schema
390
+ */
391
+ const ConfigSchema = z.object({
392
+ // Core project settings
393
+ project: ProjectConfigSchema.optional(),
394
+ stack: StackConfigSchema.optional(),
395
+
396
+ // Feature configurations
397
+ plugins: PluginsSchema.optional(),
398
+ agents: AgentsConfigSchema.optional(),
399
+ skills: SkillsConfigSchema.optional(),
400
+ workflows: WorkflowsConfigSchema.optional(),
401
+
402
+ // Tool configurations
403
+ dashboard: DashboardConfigSchema.optional(),
404
+ quality: QualityConfigSchema.optional(),
405
+ context: ContextConfigSchema.optional(),
406
+ paths: PathsConfigSchema.optional(),
407
+
408
+ // MCP configuration
409
+ mcp: z.object({
410
+ enabled: z.boolean().optional().default(false),
411
+ servers: z.record(z.string(), z.object({
412
+ command: z.string(),
413
+ args: z.array(z.string()).optional(),
414
+ env: z.record(z.string(), z.string()).optional()
415
+ })).optional()
416
+ }).optional()
417
+ }).passthrough();
418
+
419
+ // =============================================================================
420
+ // Validation Helpers
421
+ // =============================================================================
422
+
423
+ /**
424
+ * Format Zod validation errors into user-friendly messages
425
+ * @param {z.ZodError} zodError - Zod error object
426
+ * @returns {string[]} Array of formatted error messages
427
+ */
428
+ function formatValidationErrors(zodError) {
429
+ return zodError.issues.map(issue => {
430
+ const path = issue.path.join('.');
431
+ const prefix = path ? `${path}: ` : '';
432
+
433
+ switch (issue.code) {
434
+ case 'invalid_type':
435
+ return `${prefix}Expected ${issue.expected}, received ${issue.received}`;
436
+ case 'invalid_enum_value':
437
+ return `${prefix}Invalid value "${issue.received}". Expected one of: ${issue.options.join(', ')}`;
438
+ case 'too_small':
439
+ if (issue.type === 'string') {
440
+ return `${prefix}String must contain at least ${issue.minimum} character(s)`;
441
+ }
442
+ if (issue.type === 'array') {
443
+ return `${prefix}Array must contain at least ${issue.minimum} element(s)`;
444
+ }
445
+ return `${prefix}Value must be greater than or equal to ${issue.minimum}`;
446
+ case 'too_big':
447
+ return `${prefix}Value must be less than or equal to ${issue.maximum}`;
448
+ case 'invalid_string':
449
+ if (issue.validation === 'url') {
450
+ return `${prefix}Invalid URL format`;
451
+ }
452
+ if (issue.validation === 'regex') {
453
+ return `${prefix}Invalid format`;
454
+ }
455
+ return `${prefix}${issue.message}`;
456
+ default:
457
+ return `${prefix}${issue.message}`;
458
+ }
459
+ });
460
+ }
461
+
462
+ /**
463
+ * Validate a specific config section
464
+ * @param {string} section - Section name (e.g., 'plugins', 'agents')
465
+ * @param {object} data - Section data to validate
466
+ * @returns {object} Validation result { valid, errors, data }
467
+ */
468
+ function validateSection(section, data) {
469
+ const sectionSchemas = {
470
+ project: ProjectConfigSchema,
471
+ stack: StackConfigSchema,
472
+ plugins: PluginsSchema,
473
+ agents: AgentsConfigSchema,
474
+ skills: SkillsConfigSchema,
475
+ workflows: WorkflowsConfigSchema,
476
+ dashboard: DashboardConfigSchema,
477
+ quality: QualityConfigSchema,
478
+ context: ContextConfigSchema,
479
+ paths: PathsConfigSchema
480
+ };
481
+
482
+ const schema = sectionSchemas[section];
483
+ if (!schema) {
484
+ return {
485
+ valid: false,
486
+ errors: [`Unknown configuration section: ${section}`],
487
+ data: null
488
+ };
489
+ }
490
+
491
+ const result = schema.safeParse(data);
492
+
493
+ if (result.success) {
494
+ return {
495
+ valid: true,
496
+ errors: [],
497
+ data: result.data
498
+ };
499
+ }
500
+
501
+ return {
502
+ valid: false,
503
+ errors: formatValidationErrors(result.error),
504
+ data: null
505
+ };
506
+ }
11
507
 
12
508
  // Default configuration
13
509
  const DEFAULT_CONFIG = {
@@ -30,7 +526,7 @@ const DEFAULT_CONFIG = {
30
526
  database: { enabled: true, provider: 'prisma' },
31
527
  testing: { enabled: true, provider: 'vitest' },
32
528
  security: { enabled: true },
33
- ai: { enabled: false, provider: 'anthropic' }
529
+ ai: { enabled: false, providers: [] }
34
530
  },
35
531
 
36
532
  dashboard: {
@@ -61,6 +557,397 @@ const CONFIG_FILES = [
61
557
  '.bootspringrc.json'
62
558
  ];
63
559
 
560
+ /**
561
+ * Predefined configuration presets
562
+ * Use with --preset flag or applyPreset() function
563
+ *
564
+ * Available presets:
565
+ * - saas-starter: Full SaaS setup (auth, payments, database)
566
+ * - api-only: Headless API configuration
567
+ * - static-site: Simple static site setup
568
+ * - enterprise: Enterprise-grade configuration
569
+ * - minimal: Bare minimum for prototyping
570
+ * - ai-app: AI-powered application
571
+ *
572
+ * Usage: bootspring init --preset saas-starter
573
+ * Combine: bootspring init --preset saas-starter,ai-app
574
+ */
575
+ const CONFIG_PRESETS = {
576
+ 'saas-starter': {
577
+ name: 'SaaS Starter',
578
+ description: 'Full SaaS setup with auth, payments, and database',
579
+ tags: ['fullstack', 'production', 'monetization'],
580
+ extends: null,
581
+ config: {
582
+ stack: {
583
+ framework: 'nextjs',
584
+ language: 'typescript',
585
+ database: 'postgresql',
586
+ hosting: 'vercel'
587
+ },
588
+ plugins: {
589
+ auth: { enabled: true, provider: 'clerk', features: ['email_password', 'social_login'] },
590
+ payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions'] },
591
+ database: { enabled: true, provider: 'prisma', features: ['migrations', 'seeding'] },
592
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
593
+ security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
594
+ email: { enabled: true, provider: 'resend', features: ['transactional'] },
595
+ analytics: { enabled: true, provider: 'posthog', features: ['events', 'user_tracking'] },
596
+ ai: { enabled: false }
597
+ },
598
+ quality: {
599
+ preCommit: true,
600
+ prePush: false,
601
+ strictMode: false
602
+ }
603
+ }
604
+ },
605
+
606
+ 'api-only': {
607
+ name: 'API Only',
608
+ description: 'Headless API configuration without frontend',
609
+ tags: ['backend', 'headless', 'microservice'],
610
+ extends: null,
611
+ config: {
612
+ stack: {
613
+ framework: 'express',
614
+ language: 'typescript',
615
+ database: 'postgresql',
616
+ hosting: 'railway'
617
+ },
618
+ plugins: {
619
+ auth: { enabled: true, provider: 'jwt', features: ['email_password'] },
620
+ payments: { enabled: false },
621
+ database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions'] },
622
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
623
+ security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf'] },
624
+ monitoring: { enabled: true, provider: 'sentry', features: ['error_tracking', 'performance'] },
625
+ ai: { enabled: false }
626
+ },
627
+ quality: {
628
+ preCommit: true,
629
+ prePush: true,
630
+ strictMode: true
631
+ }
632
+ }
633
+ },
634
+
635
+ 'static-site': {
636
+ name: 'Static Site',
637
+ description: 'Simple static site setup with optional CMS',
638
+ tags: ['static', 'content', 'marketing'],
639
+ extends: null,
640
+ config: {
641
+ stack: {
642
+ framework: 'astro',
643
+ language: 'typescript',
644
+ database: 'none',
645
+ hosting: 'vercel'
646
+ },
647
+ plugins: {
648
+ auth: { enabled: false },
649
+ payments: { enabled: false },
650
+ database: { enabled: false },
651
+ testing: { enabled: true, provider: 'vitest', features: ['unit'] },
652
+ security: { enabled: true, features: ['xss'] },
653
+ analytics: { enabled: true, provider: 'google_analytics', features: ['page_views'] },
654
+ ai: { enabled: false }
655
+ },
656
+ quality: {
657
+ preCommit: true,
658
+ prePush: false,
659
+ strictMode: false
660
+ }
661
+ }
662
+ },
663
+
664
+ 'enterprise': {
665
+ name: 'Enterprise',
666
+ description: 'Enterprise-grade configuration with all security and quality features',
667
+ tags: ['enterprise', 'security', 'compliance'],
668
+ extends: 'saas-starter',
669
+ config: {
670
+ stack: {
671
+ framework: 'nextjs',
672
+ language: 'typescript',
673
+ database: 'postgresql',
674
+ hosting: 'aws'
675
+ },
676
+ plugins: {
677
+ auth: { enabled: true, provider: 'clerk', features: ['sso', 'mfa', 'rbac'] },
678
+ payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions', 'invoices'] },
679
+ database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions', 'multi_tenant', 'soft_delete'] },
680
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration', 'e2e', 'coverage'] },
681
+ security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf', 'audit', 'rbac', 'encryption'] },
682
+ monitoring: { enabled: true, provider: 'datadog', features: ['error_tracking', 'performance', 'logs', 'apm'] },
683
+ email: { enabled: true, provider: 'ses', features: ['transactional', 'tracking'] },
684
+ ai: { enabled: false }
685
+ },
686
+ quality: {
687
+ preCommit: true,
688
+ prePush: true,
689
+ preDeploy: { enabled: true, checks: ['lint', 'typecheck', 'test', 'build', 'security'] },
690
+ strictMode: true,
691
+ coverage: {
692
+ statements: 80,
693
+ branches: 75,
694
+ functions: 80,
695
+ lines: 80
696
+ }
697
+ }
698
+ }
699
+ },
700
+
701
+ 'minimal': {
702
+ name: 'Minimal',
703
+ description: 'Bare minimum setup for quick prototyping',
704
+ tags: ['prototype', 'minimal', 'quick'],
705
+ extends: null,
706
+ config: {
707
+ stack: {
708
+ framework: 'nextjs',
709
+ language: 'typescript',
710
+ database: 'none',
711
+ hosting: 'vercel'
712
+ },
713
+ plugins: {
714
+ auth: { enabled: false },
715
+ payments: { enabled: false },
716
+ database: { enabled: false },
717
+ testing: { enabled: false },
718
+ security: { enabled: true },
719
+ ai: { enabled: false }
720
+ },
721
+ quality: {
722
+ preCommit: false,
723
+ prePush: false,
724
+ strictMode: false
725
+ }
726
+ }
727
+ },
728
+
729
+ 'ai-app': {
730
+ name: 'AI Application',
731
+ description: 'AI-powered application with Claude integration',
732
+ tags: ['ai', 'llm', 'anthropic'],
733
+ extends: null,
734
+ config: {
735
+ stack: {
736
+ framework: 'nextjs',
737
+ language: 'typescript',
738
+ database: 'postgresql',
739
+ hosting: 'vercel'
740
+ },
741
+ plugins: {
742
+ auth: { enabled: true, provider: 'clerk', features: ['email_password'] },
743
+ payments: { enabled: false },
744
+ database: { enabled: true, provider: 'prisma', features: ['migrations'] },
745
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'mocking'] },
746
+ security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
747
+ ai: { enabled: true, providers: ['anthropic'], features: ['streaming', 'tool_use'] }
748
+ },
749
+ quality: {
750
+ preCommit: true,
751
+ prePush: false,
752
+ strictMode: false
753
+ }
754
+ }
755
+ }
756
+ };
757
+
758
+ /**
759
+ * Get a preset configuration
760
+ * @param {string} presetName - Preset name
761
+ * @returns {object|null} Preset config or null if not found
762
+ */
763
+ function getPreset(presetName) {
764
+ return CONFIG_PRESETS[presetName] || null;
765
+ }
766
+
767
+ /**
768
+ * Resolve preset inheritance chain
769
+ * @param {string} presetName - Preset name to resolve
770
+ * @param {Set} seen - Set of already seen presets (for circular detection)
771
+ * @returns {object} Resolved configuration including all parent presets
772
+ */
773
+ function resolvePresetChain(presetName, seen = new Set()) {
774
+ const preset = CONFIG_PRESETS[presetName];
775
+ if (!preset) {
776
+ throw new Error(`Unknown preset: ${presetName}. Available: ${Object.keys(CONFIG_PRESETS).join(', ')}`);
777
+ }
778
+
779
+ // Detect circular inheritance
780
+ if (seen.has(presetName)) {
781
+ throw new Error(`Circular preset inheritance detected: ${presetName}`);
782
+ }
783
+ seen.add(presetName);
784
+
785
+ // Start with defaults
786
+ let config = { ...DEFAULT_CONFIG };
787
+
788
+ // If this preset extends another, resolve the parent first
789
+ if (preset.extends) {
790
+ const parentConfig = resolvePresetChain(preset.extends, seen);
791
+ config = deepMerge(config, parentConfig);
792
+ }
793
+
794
+ // Apply this preset's config on top
795
+ config = deepMerge(config, preset.config);
796
+
797
+ return config;
798
+ }
799
+
800
+ /**
801
+ * Apply a preset to the current configuration
802
+ * Supports single preset string or array of presets
803
+ * @param {string|string[]} presetNames - Preset name(s)
804
+ * @param {object} overrides - Additional overrides to apply
805
+ * @returns {object} Merged configuration
806
+ */
807
+ function applyPreset(presetNames, overrides = {}) {
808
+ // Normalize to array
809
+ const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
810
+
811
+ if (presets.length === 0) {
812
+ throw new Error('At least one preset name is required');
813
+ }
814
+
815
+ // Start with defaults
816
+ let config = { ...DEFAULT_CONFIG };
817
+
818
+ // Apply each preset in order (later presets override earlier ones)
819
+ for (const presetName of presets) {
820
+ const trimmedName = presetName.trim();
821
+ const presetConfig = resolvePresetChain(trimmedName);
822
+ config = deepMerge(config, presetConfig);
823
+ }
824
+
825
+ // Apply any overrides
826
+ config = deepMerge(config, overrides);
827
+
828
+ return config;
829
+ }
830
+
831
+ /**
832
+ * Combine multiple presets into a single configuration
833
+ * This is an alias for applyPreset with array input
834
+ * @param {string[]} presetNames - Array of preset names to combine
835
+ * @param {object} overrides - Additional overrides to apply
836
+ * @returns {object} Combined configuration
837
+ */
838
+ function combinePresets(presetNames, overrides = {}) {
839
+ if (!Array.isArray(presetNames) || presetNames.length === 0) {
840
+ throw new Error('combinePresets requires a non-empty array of preset names');
841
+ }
842
+ return applyPreset(presetNames, overrides);
843
+ }
844
+
845
+ /**
846
+ * Parse preset string (supports comma-separated presets)
847
+ * @param {string} presetString - Preset string (e.g., "saas-starter" or "saas-starter,ai-app")
848
+ * @returns {string[]} Array of preset names
849
+ */
850
+ function parsePresetString(presetString) {
851
+ if (!presetString || typeof presetString !== 'string') {
852
+ return [];
853
+ }
854
+ return presetString.split(',').map(s => s.trim()).filter(Boolean);
855
+ }
856
+
857
+ /**
858
+ * Validate preset name(s)
859
+ * @param {string|string[]} presetNames - Preset name(s) to validate
860
+ * @returns {object} Validation result { valid, errors, validPresets }
861
+ */
862
+ function validatePresets(presetNames) {
863
+ const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
864
+ const errors = [];
865
+ const validPresets = [];
866
+ const availablePresets = Object.keys(CONFIG_PRESETS);
867
+
868
+ for (const preset of presets) {
869
+ if (CONFIG_PRESETS[preset]) {
870
+ validPresets.push(preset);
871
+ } else {
872
+ errors.push(`Unknown preset: "${preset}". Available: ${availablePresets.join(', ')}`);
873
+ }
874
+ }
875
+
876
+ return {
877
+ valid: errors.length === 0,
878
+ errors,
879
+ validPresets
880
+ };
881
+ }
882
+
883
+ /**
884
+ * Get preset by tag
885
+ * @param {string} tag - Tag to search for
886
+ * @returns {Array} Array of preset keys that have this tag
887
+ */
888
+ function getPresetsByTag(tag) {
889
+ return Object.entries(CONFIG_PRESETS)
890
+ .filter(([_key, preset]) => preset.tags && preset.tags.includes(tag))
891
+ .map(([key]) => key);
892
+ }
893
+
894
+ /**
895
+ * Create a custom preset from an existing preset
896
+ * @param {string} baseName - Base preset name to extend
897
+ * @param {string} newName - Name for the new preset
898
+ * @param {object} customConfig - Custom configuration to apply
899
+ * @returns {object} The new preset definition
900
+ */
901
+ function createCustomPreset(baseName, newName, customConfig = {}) {
902
+ const basePreset = CONFIG_PRESETS[baseName];
903
+ if (!basePreset) {
904
+ throw new Error(`Unknown base preset: ${baseName}`);
905
+ }
906
+
907
+ const customPreset = {
908
+ name: newName,
909
+ description: `Custom preset based on ${basePreset.name}`,
910
+ tags: [...(basePreset.tags || []), 'custom'],
911
+ extends: baseName,
912
+ config: customConfig
913
+ };
914
+
915
+ return customPreset;
916
+ }
917
+
918
+ /**
919
+ * List available presets
920
+ * @param {object} options - List options
921
+ * @param {string} options.tag - Filter by tag
922
+ * @param {boolean} options.verbose - Include full descriptions and tags
923
+ * @returns {Array} Array of preset info objects
924
+ */
925
+ function listPresets(options = {}) {
926
+ let entries = Object.entries(CONFIG_PRESETS);
927
+
928
+ // Filter by tag if specified
929
+ if (options.tag) {
930
+ entries = entries.filter(([_key, preset]) =>
931
+ preset.tags && preset.tags.includes(options.tag)
932
+ );
933
+ }
934
+
935
+ return entries.map(([key, preset]) => {
936
+ const info = {
937
+ key,
938
+ name: preset.name,
939
+ description: preset.description
940
+ };
941
+
942
+ if (options.verbose) {
943
+ info.tags = preset.tags || [];
944
+ info.extends = preset.extends || null;
945
+ }
946
+
947
+ return info;
948
+ });
949
+ }
950
+
64
951
  /**
65
952
  * Find the project root directory
66
953
  * @returns {string|null} Project root path or null
@@ -101,6 +988,7 @@ function findConfigFile(projectRoot) {
101
988
 
102
989
  /**
103
990
  * Deep merge two objects
991
+ * Arrays are replaced (not merged) to maintain preset integrity
104
992
  * @param {object} target - Target object
105
993
  * @param {object} source - Source object
106
994
  * @returns {object} Merged object
@@ -109,10 +997,27 @@ function deepMerge(target, source) {
109
997
  const result = { ...target };
110
998
 
111
999
  for (const key of Object.keys(source)) {
112
- if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
113
- result[key] = deepMerge(target[key], source[key]);
114
- } else {
115
- result[key] = source[key];
1000
+ const sourceValue = source[key];
1001
+ const targetValue = target[key];
1002
+
1003
+ // Arrays are replaced entirely (not merged element by element)
1004
+ if (Array.isArray(sourceValue)) {
1005
+ result[key] = [...sourceValue];
1006
+ }
1007
+ // Objects are deep merged (but not arrays, null, or other non-plain objects)
1008
+ else if (
1009
+ sourceValue !== null &&
1010
+ typeof sourceValue === 'object' &&
1011
+ !Array.isArray(sourceValue) &&
1012
+ targetValue !== null &&
1013
+ typeof targetValue === 'object' &&
1014
+ !Array.isArray(targetValue)
1015
+ ) {
1016
+ result[key] = deepMerge(targetValue, sourceValue);
1017
+ }
1018
+ // Primitives and other values are replaced
1019
+ else {
1020
+ result[key] = sourceValue;
116
1021
  }
117
1022
  }
118
1023
 
@@ -190,39 +1095,162 @@ module.exports = ${JSON.stringify(cleanConfig, null, 2)};
190
1095
  }
191
1096
 
192
1097
  /**
193
- * Validate configuration
1098
+ * Validate configuration using Zod schema
194
1099
  * @param {object} config - Configuration object
195
- * @returns {object} Validation result { valid, errors }
1100
+ * @param {object} options - Validation options
1101
+ * @param {boolean} options.strict - If true, fail on unknown fields
1102
+ * @param {string[]} options.sections - Only validate specific sections
1103
+ * @returns {object} Validation result { valid, errors, warnings, data }
196
1104
  */
197
- function validate(config) {
1105
+ function validate(config, options = {}) {
1106
+ // Remove internal properties before validation
1107
+ const configToValidate = { ...config };
1108
+ delete configToValidate._projectRoot;
1109
+ delete configToValidate._configPath;
1110
+ delete configToValidate._bootspringDir;
1111
+
198
1112
  const errors = [];
1113
+ const warnings = [];
199
1114
 
200
- // Required fields
201
- if (!config.project?.name) {
202
- errors.push('project.name is required');
1115
+ // If specific sections requested, validate only those
1116
+ if (options.sections && options.sections.length > 0) {
1117
+ let allValid = true;
1118
+ const validatedData = {};
1119
+
1120
+ for (const section of options.sections) {
1121
+ if (configToValidate[section] !== undefined) {
1122
+ const result = validateSection(section, configToValidate[section]);
1123
+ if (!result.valid) {
1124
+ allValid = false;
1125
+ errors.push(...result.errors);
1126
+ } else {
1127
+ validatedData[section] = result.data;
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+ return {
1133
+ valid: allValid,
1134
+ errors,
1135
+ warnings,
1136
+ data: allValid ? validatedData : null
1137
+ };
203
1138
  }
204
1139
 
205
- // Valid framework
206
- const validFrameworks = ['nextjs', 'remix', 'nuxt', 'sveltekit', 'express', 'fastify'];
207
- if (config.stack?.framework && !validFrameworks.includes(config.stack.framework)) {
208
- errors.push(`Invalid framework: ${config.stack.framework}. Valid: ${validFrameworks.join(', ')}`);
1140
+ // Full config validation
1141
+ const result = ConfigSchema.safeParse(configToValidate);
1142
+
1143
+ if (result.success) {
1144
+ return {
1145
+ valid: true,
1146
+ errors: [],
1147
+ warnings,
1148
+ data: result.data
1149
+ };
209
1150
  }
210
1151
 
211
- // Valid language
212
- const validLanguages = ['typescript', 'javascript'];
213
- if (config.stack?.language && !validLanguages.includes(config.stack.language)) {
214
- errors.push(`Invalid language: ${config.stack.language}. Valid: ${validLanguages.join(', ')}`);
1152
+ return {
1153
+ valid: false,
1154
+ errors: formatValidationErrors(result.error),
1155
+ warnings,
1156
+ data: null
1157
+ };
1158
+ }
1159
+
1160
+ /**
1161
+ * Load and optionally validate configuration
1162
+ * Validation is graceful by default - invalid configs still load with warnings
1163
+ * @param {string} [projectRoot] - Optional project root path
1164
+ * @param {object} [options] - Load options
1165
+ * @param {boolean} [options.validate] - Whether to validate (default: true)
1166
+ * @param {boolean} [options.strict] - If true, throw on validation errors (default: false)
1167
+ * @param {boolean} [options.silent] - If true, suppress validation warnings (default: false)
1168
+ * @returns {object} Configuration object with _validation property
1169
+ */
1170
+ function loadWithValidation(projectRoot = null, options = {}) {
1171
+ const { validate: shouldValidate = true, strict = false, silent = false } = options;
1172
+
1173
+ // Load the config normally
1174
+ const config = load(projectRoot);
1175
+
1176
+ // Skip validation if not requested
1177
+ if (!shouldValidate) {
1178
+ config._validation = { skipped: true };
1179
+ return config;
215
1180
  }
216
1181
 
217
- // Port range
218
- if (config.dashboard?.port && (config.dashboard.port < 1024 || config.dashboard.port > 65535)) {
219
- errors.push('dashboard.port must be between 1024 and 65535');
1182
+ // Validate the loaded config
1183
+ const validation = validate(config);
1184
+
1185
+ // Attach validation results
1186
+ config._validation = {
1187
+ valid: validation.valid,
1188
+ errors: validation.errors,
1189
+ warnings: validation.warnings || [],
1190
+ validatedAt: new Date().toISOString()
1191
+ };
1192
+
1193
+ // Handle validation results
1194
+ if (!validation.valid) {
1195
+ if (strict) {
1196
+ const errorMessage = `Configuration validation failed:\n${validation.errors.map(e => ` - ${e}`).join('\n')}`;
1197
+ throw new Error(errorMessage);
1198
+ }
1199
+
1200
+ if (!silent) {
1201
+ console.warn('\x1b[33mWarning: Configuration has validation issues:\x1b[0m');
1202
+ validation.errors.forEach(error => {
1203
+ console.warn(` \x1b[33m- ${error}\x1b[0m`);
1204
+ });
1205
+ console.warn('\x1b[33mThe configuration will still be used, but some features may not work correctly.\x1b[0m');
1206
+ }
220
1207
  }
221
1208
 
222
- return {
223
- valid: errors.length === 0,
224
- errors
1209
+ return config;
1210
+ }
1211
+
1212
+ /**
1213
+ * Get a helpful error message for a validation error
1214
+ * @param {string} errorPath - Dot-notation path to the error
1215
+ * @param {string} errorMessage - The error message
1216
+ * @returns {string} A helpful message with suggestions
1217
+ */
1218
+ function getValidationHint(errorPath, errorMessage) {
1219
+ const hints = {
1220
+ 'stack.framework': 'Valid frameworks: nextjs, remix, nuxt, sveltekit, astro, express, fastify, hono, custom',
1221
+ 'stack.language': 'Valid languages: typescript, javascript',
1222
+ 'stack.database': 'Valid databases: postgresql, mysql, mongodb, sqlite, supabase, planetscale, none',
1223
+ 'stack.hosting': 'Valid hosting options: vercel, railway, render, fly, aws, gcp, azure, cloudflare, self-hosted, custom',
1224
+ 'plugins.auth.provider': 'Valid auth providers: clerk, nextauth, auth0, supabase, jwt, custom',
1225
+ 'plugins.payments.provider': 'Valid payment providers: stripe, paddle, lemonsqueezy, paypal, custom',
1226
+ 'plugins.database.provider': 'Valid database ORMs: prisma, drizzle, typeorm, kysely, custom',
1227
+ 'plugins.testing.provider': 'Valid test frameworks: vitest, jest, playwright, cypress, custom',
1228
+ 'plugins.ai.providers': 'Valid AI providers: anthropic, openai, google, cohere, huggingface, custom',
1229
+ 'dashboard.port': 'Port must be between 1024 and 65535',
1230
+ 'project.version': 'Version must follow semver format (e.g., 1.0.0)'
225
1231
  };
1232
+
1233
+ return hints[errorPath] || null;
1234
+ }
1235
+
1236
+ /**
1237
+ * Parse and validate configuration with defaults
1238
+ * @param {object} config - Raw configuration object
1239
+ * @returns {object} Parsed configuration with defaults applied
1240
+ */
1241
+ function parseConfig(config) {
1242
+ // Remove internal properties before parsing
1243
+ const configToParse = { ...config };
1244
+ delete configToParse._projectRoot;
1245
+ delete configToParse._configPath;
1246
+ delete configToParse._bootspringDir;
1247
+
1248
+ const result = ConfigSchema.safeParse(configToParse);
1249
+ if (result.success) {
1250
+ return result.data;
1251
+ }
1252
+ // Return original config if parsing fails (for backwards compatibility)
1253
+ return configToParse;
226
1254
  }
227
1255
 
228
1256
  /**
@@ -234,12 +1262,67 @@ function getDefaults() {
234
1262
  }
235
1263
 
236
1264
  module.exports = {
1265
+ // Core functions
237
1266
  load,
1267
+ loadWithValidation,
238
1268
  save,
239
1269
  validate,
1270
+ validateSection,
1271
+ parseConfig,
240
1272
  getDefaults,
241
1273
  findProjectRoot,
242
1274
  findConfigFile,
1275
+
1276
+ // Preset functions
1277
+ getPreset,
1278
+ applyPreset,
1279
+ combinePresets,
1280
+ listPresets,
1281
+ parsePresetString,
1282
+ validatePresets,
1283
+ getPresetsByTag,
1284
+ createCustomPreset,
1285
+ resolvePresetChain,
1286
+
1287
+ // Validation helpers
1288
+ formatValidationErrors,
1289
+ getValidationHint,
1290
+
1291
+ // Constants
243
1292
  DEFAULT_CONFIG,
244
- CONFIG_FILES
1293
+ CONFIG_FILES,
1294
+ CONFIG_PRESETS,
1295
+
1296
+ // Schemas - Main
1297
+ ConfigSchema,
1298
+
1299
+ // Schemas - Plugins
1300
+ PluginSchema,
1301
+ PluginsSchema,
1302
+ AuthPluginSchema,
1303
+ PaymentsPluginSchema,
1304
+ DatabasePluginSchema,
1305
+ TestingPluginSchema,
1306
+ SecurityPluginSchema,
1307
+ AIPluginSchema,
1308
+ EmailPluginSchema,
1309
+ AnalyticsPluginSchema,
1310
+ MonitoringPluginSchema,
1311
+
1312
+ // Schemas - Features
1313
+ AgentsConfigSchema,
1314
+ AgentConfigSchema,
1315
+ SkillsConfigSchema,
1316
+ SkillConfigSchema,
1317
+ WorkflowsConfigSchema,
1318
+ WorkflowConfigSchema,
1319
+ WorkflowPhaseSchema,
1320
+
1321
+ // Schemas - Settings
1322
+ ProjectConfigSchema,
1323
+ StackConfigSchema,
1324
+ QualityConfigSchema,
1325
+ DashboardConfigSchema,
1326
+ ContextConfigSchema,
1327
+ PathsConfigSchema
245
1328
  };