@girardmedia/bootspring 2.0.21 → 2.0.23

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 (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,1285 @@
1
+ /**
2
+ * Bootspring Configuration
3
+ * Handles loading and validating bootspring.config.js
4
+ *
5
+ * @package bootspring
6
+ * @module core/config
7
+ */
8
+
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { z } from '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
+ export 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
+ export 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
+ export 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
+ export 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
+ export 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
+ export 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
+ export 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
+ export 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
+ export 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
+ export 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
+ export 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);
144
+
145
+ // Backward compatible alias
146
+ export const PluginSchema = BasePluginSchema;
147
+
148
+ // -----------------------------------------------------------------------------
149
+ // Agent Configuration Schema
150
+ // -----------------------------------------------------------------------------
151
+
152
+ /**
153
+ * Individual agent configuration
154
+ */
155
+ export 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
+ export const AgentsConfigSchema = z.object({
166
+ enabled: z.record(z.string(), z.boolean()).optional(),
167
+ custom: z.record(z.string(), AgentConfigSchema).optional(),
168
+ defaults: z.array(z.string()).optional(),
169
+ settings: z.object({
170
+ maxConcurrent: z.number().int().min(1).max(10).optional().default(3),
171
+ timeout: z.number().int().min(1000).max(300000).optional().default(60000),
172
+ verbose: z.boolean().optional().default(false)
173
+ }).optional()
174
+ }).passthrough();
175
+
176
+ // -----------------------------------------------------------------------------
177
+ // Skills Configuration Schema
178
+ // -----------------------------------------------------------------------------
179
+
180
+ /**
181
+ * Individual skill configuration
182
+ */
183
+ export const SkillConfigSchema = z.object({
184
+ enabled: z.boolean().optional().default(true),
185
+ tier: z.enum(['free', 'pro', 'premium']).optional().default('free'),
186
+ category: z.string().optional(),
187
+ maxChars: z.number().int().min(100).optional()
188
+ }).passthrough();
189
+
190
+ /**
191
+ * Skills section configuration
192
+ */
193
+ export const SkillsConfigSchema = z.object({
194
+ categories: z.record(z.string(), z.boolean()).optional(),
195
+ custom: z.record(z.string(), SkillConfigSchema).optional(),
196
+ external: z.object({
197
+ enabled: z.boolean().optional().default(false),
198
+ manifestUrl: z.string().url().optional(),
199
+ cacheDir: z.string().optional(),
200
+ requireSignature: z.boolean().optional().default(false)
201
+ }).optional(),
202
+ settings: z.object({
203
+ defaultMaxChars: z.number().int().min(100).optional().default(50000),
204
+ summaryByDefault: z.boolean().optional().default(false),
205
+ includeExternal: z.boolean().optional().default(false)
206
+ }).optional()
207
+ }).passthrough();
208
+
209
+ // -----------------------------------------------------------------------------
210
+ // Workflows Configuration Schema
211
+ // -----------------------------------------------------------------------------
212
+
213
+ /**
214
+ * Workflow phase schema
215
+ */
216
+ export const WorkflowPhaseSchema = z.object({
217
+ name: z.string().min(1, 'Phase name is required'),
218
+ agents: z.array(z.string()).min(1, 'At least one agent is required'),
219
+ duration: z.string().optional(),
220
+ parallel: z.boolean().optional().default(false),
221
+ description: z.string().optional()
222
+ }).passthrough();
223
+
224
+ /**
225
+ * Individual workflow configuration
226
+ */
227
+ export const WorkflowConfigSchema = z.object({
228
+ name: z.string().min(1, 'Workflow name is required'),
229
+ description: z.string().optional(),
230
+ tier: z.enum(['free', 'pro']).optional().default('free'),
231
+ pack: z.string().optional(),
232
+ outcomes: z.array(z.string()).optional(),
233
+ completionSignals: z.array(z.string()).optional(),
234
+ phases: z.array(WorkflowPhaseSchema).min(1, 'At least one phase is required')
235
+ }).passthrough();
236
+
237
+ /**
238
+ * Workflows section configuration
239
+ */
240
+ export const WorkflowsConfigSchema = z.object({
241
+ enabled: z.record(z.string(), z.boolean()).optional(),
242
+ custom: z.record(z.string(), WorkflowConfigSchema).optional(),
243
+ default: z.string().optional(),
244
+ settings: z.object({
245
+ autoAdvance: z.boolean().optional().default(true),
246
+ pauseBetweenPhases: z.boolean().optional().default(false),
247
+ trackSignals: z.boolean().optional().default(true),
248
+ emitTelemetry: z.boolean().optional().default(true)
249
+ }).optional()
250
+ }).passthrough();
251
+
252
+ // -----------------------------------------------------------------------------
253
+ // Quality Configuration Schema
254
+ // -----------------------------------------------------------------------------
255
+
256
+ /**
257
+ * Quality check configuration
258
+ */
259
+ export const QualityCheckSchema = z.union([
260
+ z.boolean(),
261
+ z.object({
262
+ enabled: z.boolean().optional().default(true),
263
+ checks: z.array(z.enum([
264
+ 'lint', 'typecheck', 'test', 'build', 'security', 'coverage', 'format'
265
+ ])).optional()
266
+ })
267
+ ]);
268
+
269
+ /**
270
+ * Quality section configuration
271
+ */
272
+ export const QualityConfigSchema = z.object({
273
+ preCommit: QualityCheckSchema.optional().default(true),
274
+ prePush: QualityCheckSchema.optional().default(false),
275
+ preDeploy: QualityCheckSchema.optional(),
276
+ strictMode: z.boolean().optional().default(false),
277
+ coverage: z.object({
278
+ statements: z.number().min(0).max(100).optional(),
279
+ branches: z.number().min(0).max(100).optional(),
280
+ functions: z.number().min(0).max(100).optional(),
281
+ lines: z.number().min(0).max(100).optional()
282
+ }).optional()
283
+ }).passthrough();
284
+
285
+ // -----------------------------------------------------------------------------
286
+ // Context Configuration Schema
287
+ // -----------------------------------------------------------------------------
288
+
289
+ /**
290
+ * Context generation configuration
291
+ */
292
+ export const ContextConfigSchema = z.object({
293
+ includeEnvVars: z.boolean().optional().default(true),
294
+ includeTechStack: z.boolean().optional().default(true),
295
+ includePlugins: z.boolean().optional().default(true),
296
+ includeGitInfo: z.boolean().optional().default(true),
297
+ includeTodos: z.boolean().optional().default(true),
298
+ includeLearnings: z.boolean().optional().default(true),
299
+ customSections: z.array(z.object({
300
+ title: z.string(),
301
+ content: z.string()
302
+ })).optional().default([]),
303
+ maxSize: z.number().int().min(1000).optional()
304
+ }).passthrough();
305
+
306
+ // -----------------------------------------------------------------------------
307
+ // Paths Configuration Schema
308
+ // -----------------------------------------------------------------------------
309
+
310
+ /**
311
+ * Paths configuration
312
+ */
313
+ export const PathsConfigSchema = z.object({
314
+ context: z.string().optional().default('CLAUDE.md'),
315
+ config: z.string().optional().default('bootspring.config.js'),
316
+ todo: z.string().optional().default('todo.md'),
317
+ roadmap: z.string().optional().default('ROADMAP.md'),
318
+ changelog: z.string().optional().default('CHANGELOG.md'),
319
+ state: z.string().optional().default('.bootspring')
320
+ }).passthrough();
321
+
322
+ // -----------------------------------------------------------------------------
323
+ // Dashboard Configuration Schema
324
+ // -----------------------------------------------------------------------------
325
+
326
+ /**
327
+ * Dashboard configuration
328
+ */
329
+ export const DashboardConfigSchema = z.object({
330
+ port: z.number().int().min(1024).max(65535).optional().default(3456),
331
+ autoOpen: z.boolean().optional().default(false),
332
+ host: z.string().optional().default('localhost'),
333
+ theme: z.enum(['light', 'dark', 'system']).optional().default('system')
334
+ }).passthrough();
335
+
336
+ // -----------------------------------------------------------------------------
337
+ // Project Configuration Schema
338
+ // -----------------------------------------------------------------------------
339
+
340
+ /**
341
+ * Project information schema
342
+ */
343
+ export const ProjectConfigSchema = z.object({
344
+ name: z.string().min(1, 'Project name is required'),
345
+ description: z.string().optional().default(''),
346
+ version: z.string().regex(/^\d+\.\d+\.\d+/, 'Version must be semver format').optional().default('1.0.0'),
347
+ author: z.string().optional(),
348
+ license: z.string().optional(),
349
+ repository: z.string().url().optional()
350
+ }).passthrough();
351
+
352
+ // -----------------------------------------------------------------------------
353
+ // Stack Configuration Schema
354
+ // -----------------------------------------------------------------------------
355
+
356
+ /**
357
+ * Technology stack configuration
358
+ */
359
+ export const StackConfigSchema = z.object({
360
+ framework: z.enum([
361
+ 'nextjs', 'remix', 'nuxt', 'sveltekit', 'astro', 'express', 'fastify', 'hono', 'custom'
362
+ ]).optional(),
363
+ language: z.enum(['typescript', 'javascript']).optional(),
364
+ database: z.enum(['postgresql', 'mysql', 'mongodb', 'sqlite', 'supabase', 'planetscale', 'none']).optional(),
365
+ hosting: z.enum([
366
+ 'vercel', 'railway', 'render', 'fly', 'aws', 'gcp', 'azure', 'cloudflare', 'self-hosted', 'custom'
367
+ ]).optional()
368
+ }).passthrough();
369
+
370
+ // -----------------------------------------------------------------------------
371
+ // Main Configuration Schema
372
+ // -----------------------------------------------------------------------------
373
+
374
+ /**
375
+ * Complete Bootspring configuration schema
376
+ */
377
+ export const ConfigSchema = z.object({
378
+ project: ProjectConfigSchema.optional(),
379
+ stack: StackConfigSchema.optional(),
380
+ plugins: PluginsSchema.optional(),
381
+ agents: AgentsConfigSchema.optional(),
382
+ skills: SkillsConfigSchema.optional(),
383
+ workflows: WorkflowsConfigSchema.optional(),
384
+ dashboard: DashboardConfigSchema.optional(),
385
+ quality: QualityConfigSchema.optional(),
386
+ context: ContextConfigSchema.optional(),
387
+ paths: PathsConfigSchema.optional(),
388
+ mcp: z.object({
389
+ enabled: z.boolean().optional().default(false),
390
+ servers: z.record(z.string(), z.object({
391
+ command: z.string(),
392
+ args: z.array(z.string()).optional(),
393
+ env: z.record(z.string(), z.string()).optional()
394
+ })).optional()
395
+ }).optional()
396
+ }).passthrough();
397
+
398
+ // =============================================================================
399
+ // TypeScript Types (using z.input for input types, z.infer for output types)
400
+ // =============================================================================
401
+
402
+ // Input types (what users provide - allows partial/missing defaults)
403
+ export type BasePluginInput = z.input<typeof BasePluginSchema>;
404
+ export type AuthPluginInput = z.input<typeof AuthPluginSchema>;
405
+ export type PaymentsPluginInput = z.input<typeof PaymentsPluginSchema>;
406
+ export type DatabasePluginInput = z.input<typeof DatabasePluginSchema>;
407
+ export type TestingPluginInput = z.input<typeof TestingPluginSchema>;
408
+ export type SecurityPluginInput = z.input<typeof SecurityPluginSchema>;
409
+ export type AIPluginInput = z.input<typeof AIPluginSchema>;
410
+ export type EmailPluginInput = z.input<typeof EmailPluginSchema>;
411
+ export type AnalyticsPluginInput = z.input<typeof AnalyticsPluginSchema>;
412
+ export type MonitoringPluginInput = z.input<typeof MonitoringPluginSchema>;
413
+ export type PluginsInput = z.input<typeof PluginsSchema>;
414
+ export type ConfigInput = z.input<typeof ConfigSchema>;
415
+
416
+ // Output types (after parsing with defaults applied)
417
+ export type BasePlugin = z.infer<typeof BasePluginSchema>;
418
+ export type AuthPlugin = z.infer<typeof AuthPluginSchema>;
419
+ export type PaymentsPlugin = z.infer<typeof PaymentsPluginSchema>;
420
+ export type DatabasePlugin = z.infer<typeof DatabasePluginSchema>;
421
+ export type TestingPlugin = z.infer<typeof TestingPluginSchema>;
422
+ export type SecurityPlugin = z.infer<typeof SecurityPluginSchema>;
423
+ export type AIPlugin = z.infer<typeof AIPluginSchema>;
424
+ export type EmailPlugin = z.infer<typeof EmailPluginSchema>;
425
+ export type AnalyticsPlugin = z.infer<typeof AnalyticsPluginSchema>;
426
+ export type MonitoringPlugin = z.infer<typeof MonitoringPluginSchema>;
427
+ export type Plugins = z.infer<typeof PluginsSchema>;
428
+
429
+ export type AgentConfig = z.infer<typeof AgentConfigSchema>;
430
+ export type AgentsConfig = z.infer<typeof AgentsConfigSchema>;
431
+
432
+ export type SkillConfig = z.infer<typeof SkillConfigSchema>;
433
+ export type SkillsConfig = z.infer<typeof SkillsConfigSchema>;
434
+
435
+ export type ConfigWorkflowPhase = z.infer<typeof WorkflowPhaseSchema>;
436
+ export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>;
437
+ export type WorkflowsConfig = z.infer<typeof WorkflowsConfigSchema>;
438
+
439
+ export type QualityCheck = z.infer<typeof QualityCheckSchema>;
440
+ export type QualityConfig = z.infer<typeof QualityConfigSchema>;
441
+
442
+ export type ContextConfig = z.infer<typeof ContextConfigSchema>;
443
+ export type PathsConfig = z.infer<typeof PathsConfigSchema>;
444
+ export type DashboardConfig = z.infer<typeof DashboardConfigSchema>;
445
+ export type ConfigProjectConfig = z.infer<typeof ProjectConfigSchema>;
446
+ export type ConfigStackConfig = z.infer<typeof StackConfigSchema>;
447
+
448
+ export type Config = z.infer<typeof ConfigSchema>;
449
+
450
+ /**
451
+ * Loaded configuration with internal metadata
452
+ */
453
+ export interface LoadedConfig extends ConfigInput {
454
+ _projectRoot?: string | undefined;
455
+ _configPath?: string | null | undefined;
456
+ _bootspringDir?: string | undefined;
457
+ _validation?: ValidationResult | { skipped: boolean } | undefined;
458
+ }
459
+
460
+ /**
461
+ * Validation result
462
+ */
463
+ export interface ValidationResult {
464
+ valid: boolean;
465
+ errors: string[];
466
+ warnings?: string[] | undefined;
467
+ data?: Config | null | undefined;
468
+ validatedAt?: string | undefined;
469
+ }
470
+
471
+ /**
472
+ * Section validation result
473
+ */
474
+ export interface SectionValidationResult {
475
+ valid: boolean;
476
+ errors: string[];
477
+ data: unknown;
478
+ }
479
+
480
+ /**
481
+ * Preset definition
482
+ */
483
+ export interface PresetDefinition {
484
+ name: string;
485
+ description: string;
486
+ tags: string[];
487
+ extends: string | null;
488
+ config: Partial<ConfigInput>;
489
+ }
490
+
491
+ /**
492
+ * Preset info for listing
493
+ */
494
+ export interface PresetInfo {
495
+ key: string;
496
+ name: string;
497
+ description: string;
498
+ tags?: string[] | undefined;
499
+ extends?: string | null | undefined;
500
+ }
501
+
502
+ /**
503
+ * List presets options
504
+ */
505
+ export interface ListPresetsOptions {
506
+ tag?: string | undefined;
507
+ verbose?: boolean | undefined;
508
+ }
509
+
510
+ /**
511
+ * Validate options
512
+ */
513
+ export interface ValidateOptions {
514
+ strict?: boolean | undefined;
515
+ sections?: string[] | undefined;
516
+ }
517
+
518
+ /**
519
+ * Load with validation options
520
+ */
521
+ export interface LoadWithValidationOptions {
522
+ validate?: boolean | undefined;
523
+ strict?: boolean | undefined;
524
+ silent?: boolean | undefined;
525
+ }
526
+
527
+ // =============================================================================
528
+ // Validation Helpers
529
+ // =============================================================================
530
+
531
+ /**
532
+ * Format Zod validation errors into user-friendly messages
533
+ */
534
+ export function formatValidationErrors(zodError: z.ZodError): string[] {
535
+ return zodError.issues.map(issue => {
536
+ const path = issue.path.join('.');
537
+ const prefix = path ? `${path}: ` : '';
538
+
539
+ switch (issue.code) {
540
+ case 'invalid_type':
541
+ return `${prefix}Expected ${issue.expected}, received ${issue.received}`;
542
+ case 'invalid_enum_value':
543
+ return `${prefix}Invalid value "${issue.received}". Expected one of: ${issue.options.join(', ')}`;
544
+ case 'too_small':
545
+ if (issue.type === 'string') {
546
+ return `${prefix}String must contain at least ${issue.minimum} character(s)`;
547
+ }
548
+ if (issue.type === 'array') {
549
+ return `${prefix}Array must contain at least ${issue.minimum} element(s)`;
550
+ }
551
+ return `${prefix}Value must be greater than or equal to ${issue.minimum}`;
552
+ case 'too_big':
553
+ return `${prefix}Value must be less than or equal to ${issue.maximum}`;
554
+ case 'invalid_string':
555
+ if (issue.validation === 'url') {
556
+ return `${prefix}Invalid URL format`;
557
+ }
558
+ if (issue.validation === 'regex') {
559
+ return `${prefix}Invalid format`;
560
+ }
561
+ return `${prefix}${issue.message}`;
562
+ default:
563
+ return `${prefix}${issue.message}`;
564
+ }
565
+ });
566
+ }
567
+
568
+ /**
569
+ * Section schema mapping
570
+ */
571
+ const sectionSchemas: Record<string, z.ZodTypeAny> = {
572
+ project: ProjectConfigSchema,
573
+ stack: StackConfigSchema,
574
+ plugins: PluginsSchema,
575
+ agents: AgentsConfigSchema,
576
+ skills: SkillsConfigSchema,
577
+ workflows: WorkflowsConfigSchema,
578
+ dashboard: DashboardConfigSchema,
579
+ quality: QualityConfigSchema,
580
+ context: ContextConfigSchema,
581
+ paths: PathsConfigSchema
582
+ };
583
+
584
+ /**
585
+ * Validate a specific config section
586
+ */
587
+ export function validateSection(section: string, data: unknown): SectionValidationResult {
588
+ const schema = sectionSchemas[section];
589
+ if (!schema) {
590
+ return {
591
+ valid: false,
592
+ errors: [`Unknown configuration section: ${section}`],
593
+ data: null
594
+ };
595
+ }
596
+
597
+ const result = schema.safeParse(data);
598
+
599
+ if (result.success) {
600
+ return {
601
+ valid: true,
602
+ errors: [],
603
+ data: result.data
604
+ };
605
+ }
606
+
607
+ return {
608
+ valid: false,
609
+ errors: formatValidationErrors(result.error),
610
+ data: null
611
+ };
612
+ }
613
+
614
+ // =============================================================================
615
+ // Default Configuration
616
+ // =============================================================================
617
+
618
+ export const DEFAULT_CONFIG: ConfigInput = {
619
+ project: {
620
+ name: 'My Project',
621
+ description: '',
622
+ version: '1.0.0'
623
+ },
624
+ stack: {
625
+ framework: 'nextjs',
626
+ language: 'typescript',
627
+ database: 'postgresql',
628
+ hosting: 'vercel'
629
+ },
630
+ plugins: {
631
+ auth: { enabled: false, provider: 'clerk' },
632
+ payments: { enabled: false, provider: 'stripe' },
633
+ database: { enabled: true, provider: 'prisma' },
634
+ testing: { enabled: true, provider: 'vitest' },
635
+ security: { enabled: true },
636
+ ai: { enabled: false }
637
+ },
638
+ dashboard: {
639
+ port: 3456,
640
+ autoOpen: false
641
+ },
642
+ quality: {
643
+ preCommit: true,
644
+ prePush: false,
645
+ strictMode: false
646
+ },
647
+ paths: {
648
+ context: 'CLAUDE.md',
649
+ config: 'bootspring.config.js',
650
+ todo: 'todo.md'
651
+ }
652
+ };
653
+
654
+ // Config file names to search for
655
+ export const CONFIG_FILES = [
656
+ 'bootspring.config.js',
657
+ 'bootspring.config.mjs',
658
+ 'bootspring.config.json',
659
+ '.bootspringrc',
660
+ '.bootspringrc.js',
661
+ '.bootspringrc.json'
662
+ ];
663
+
664
+ // =============================================================================
665
+ // Configuration Presets
666
+ // =============================================================================
667
+
668
+ export const CONFIG_PRESETS: Record<string, PresetDefinition> = {
669
+ 'saas-starter': {
670
+ name: 'SaaS Starter',
671
+ description: 'Full SaaS setup with auth, payments, and database',
672
+ tags: ['fullstack', 'production', 'monetization'],
673
+ extends: null,
674
+ config: {
675
+ stack: {
676
+ framework: 'nextjs',
677
+ language: 'typescript',
678
+ database: 'postgresql',
679
+ hosting: 'vercel'
680
+ },
681
+ plugins: {
682
+ auth: { enabled: true, provider: 'clerk', features: ['email_password', 'social_login'] },
683
+ payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions'] },
684
+ database: { enabled: true, provider: 'prisma', features: ['migrations', 'seeding'] },
685
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
686
+ security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
687
+ email: { enabled: true, provider: 'resend', features: ['transactional'] },
688
+ analytics: { enabled: true, provider: 'posthog', features: ['events', 'user_tracking'] },
689
+ ai: { enabled: false }
690
+ },
691
+ quality: {
692
+ preCommit: true,
693
+ prePush: false,
694
+ strictMode: false
695
+ }
696
+ }
697
+ },
698
+
699
+ 'api-only': {
700
+ name: 'API Only',
701
+ description: 'Headless API configuration without frontend',
702
+ tags: ['backend', 'headless', 'microservice'],
703
+ extends: null,
704
+ config: {
705
+ stack: {
706
+ framework: 'express',
707
+ language: 'typescript',
708
+ database: 'postgresql',
709
+ hosting: 'railway'
710
+ },
711
+ plugins: {
712
+ auth: { enabled: true, provider: 'jwt', features: ['email_password'] },
713
+ payments: { enabled: false },
714
+ database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions'] },
715
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration'] },
716
+ security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf'] },
717
+ monitoring: { enabled: true, provider: 'sentry', features: ['error_tracking', 'performance'] },
718
+ ai: { enabled: false }
719
+ },
720
+ quality: {
721
+ preCommit: true,
722
+ prePush: true,
723
+ strictMode: true
724
+ }
725
+ }
726
+ },
727
+
728
+ 'static-site': {
729
+ name: 'Static Site',
730
+ description: 'Simple static site setup with optional CMS',
731
+ tags: ['static', 'content', 'marketing'],
732
+ extends: null,
733
+ config: {
734
+ stack: {
735
+ framework: 'astro',
736
+ language: 'typescript',
737
+ database: 'none',
738
+ hosting: 'vercel'
739
+ },
740
+ plugins: {
741
+ auth: { enabled: false },
742
+ payments: { enabled: false },
743
+ database: { enabled: false },
744
+ testing: { enabled: true, provider: 'vitest', features: ['unit'] },
745
+ security: { enabled: true, features: ['xss'] },
746
+ analytics: { enabled: true, provider: 'google_analytics', features: ['page_views'] },
747
+ ai: { enabled: false }
748
+ },
749
+ quality: {
750
+ preCommit: true,
751
+ prePush: false,
752
+ strictMode: false
753
+ }
754
+ }
755
+ },
756
+
757
+ 'enterprise': {
758
+ name: 'Enterprise',
759
+ description: 'Enterprise-grade configuration with all security and quality features',
760
+ tags: ['enterprise', 'security', 'compliance'],
761
+ extends: 'saas-starter',
762
+ config: {
763
+ stack: {
764
+ framework: 'nextjs',
765
+ language: 'typescript',
766
+ database: 'postgresql',
767
+ hosting: 'aws'
768
+ },
769
+ plugins: {
770
+ auth: { enabled: true, provider: 'clerk', features: ['sso', 'mfa', 'rbac'] },
771
+ payments: { enabled: true, provider: 'stripe', features: ['checkout', 'subscriptions', 'invoices'] },
772
+ database: { enabled: true, provider: 'prisma', features: ['migrations', 'transactions', 'multi_tenant', 'soft_delete'] },
773
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'integration', 'e2e', 'coverage'] },
774
+ security: { enabled: true, features: ['input_validation', 'rate_limiting', 'csrf', 'audit', 'rbac', 'encryption'] },
775
+ monitoring: { enabled: true, provider: 'datadog', features: ['error_tracking', 'performance', 'logs', 'apm'] },
776
+ email: { enabled: true, provider: 'ses', features: ['transactional', 'tracking'] },
777
+ ai: { enabled: false }
778
+ },
779
+ quality: {
780
+ preCommit: true,
781
+ prePush: true,
782
+ preDeploy: { enabled: true, checks: ['lint', 'typecheck', 'test', 'build', 'security'] },
783
+ strictMode: true,
784
+ coverage: {
785
+ statements: 80,
786
+ branches: 75,
787
+ functions: 80,
788
+ lines: 80
789
+ }
790
+ }
791
+ }
792
+ },
793
+
794
+ 'minimal': {
795
+ name: 'Minimal',
796
+ description: 'Bare minimum setup for quick prototyping',
797
+ tags: ['prototype', 'minimal', 'quick'],
798
+ extends: null,
799
+ config: {
800
+ stack: {
801
+ framework: 'nextjs',
802
+ language: 'typescript',
803
+ database: 'none',
804
+ hosting: 'vercel'
805
+ },
806
+ plugins: {
807
+ auth: { enabled: false },
808
+ payments: { enabled: false },
809
+ database: { enabled: false },
810
+ testing: { enabled: false },
811
+ security: { enabled: true },
812
+ ai: { enabled: false }
813
+ },
814
+ quality: {
815
+ preCommit: false,
816
+ prePush: false,
817
+ strictMode: false
818
+ }
819
+ }
820
+ },
821
+
822
+ 'ai-app': {
823
+ name: 'AI Application',
824
+ description: 'AI-powered application with Claude integration',
825
+ tags: ['ai', 'llm', 'anthropic'],
826
+ extends: null,
827
+ config: {
828
+ stack: {
829
+ framework: 'nextjs',
830
+ language: 'typescript',
831
+ database: 'postgresql',
832
+ hosting: 'vercel'
833
+ },
834
+ plugins: {
835
+ auth: { enabled: true, provider: 'clerk', features: ['email_password'] },
836
+ payments: { enabled: false },
837
+ database: { enabled: true, provider: 'prisma', features: ['migrations'] },
838
+ testing: { enabled: true, provider: 'vitest', features: ['unit', 'mocking'] },
839
+ security: { enabled: true, features: ['input_validation', 'rate_limiting'] },
840
+ ai: { enabled: true, provider: 'anthropic', features: ['streaming', 'tool_use'] }
841
+ },
842
+ quality: {
843
+ preCommit: true,
844
+ prePush: false,
845
+ strictMode: false
846
+ }
847
+ }
848
+ }
849
+ };
850
+
851
+ // =============================================================================
852
+ // Helper Functions
853
+ // =============================================================================
854
+
855
+ /**
856
+ * Deep merge two objects
857
+ */
858
+ export function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
859
+ const result = { ...target } as T;
860
+
861
+ for (const key of Object.keys(source) as (keyof T)[]) {
862
+ const sourceValue = source[key];
863
+ const targetValue = target[key];
864
+
865
+ if (Array.isArray(sourceValue)) {
866
+ (result as Record<string, unknown>)[key as string] = [...sourceValue];
867
+ } else if (
868
+ sourceValue !== null &&
869
+ typeof sourceValue === 'object' &&
870
+ !Array.isArray(sourceValue) &&
871
+ targetValue !== null &&
872
+ typeof targetValue === 'object' &&
873
+ !Array.isArray(targetValue)
874
+ ) {
875
+ (result as Record<string, unknown>)[key as string] = deepMerge(
876
+ targetValue as Record<string, unknown>,
877
+ sourceValue as Record<string, unknown>
878
+ );
879
+ } else if (sourceValue !== undefined) {
880
+ (result as Record<string, unknown>)[key as string] = sourceValue;
881
+ }
882
+ }
883
+
884
+ return result;
885
+ }
886
+
887
+ /**
888
+ * Find the project root directory
889
+ */
890
+ export function findProjectRoot(): string {
891
+ let dir = process.cwd();
892
+ const root = path.parse(dir).root;
893
+
894
+ while (dir !== root) {
895
+ if (
896
+ fs.existsSync(path.join(dir, 'package.json')) ||
897
+ fs.existsSync(path.join(dir, 'bootspring.config.js')) ||
898
+ fs.existsSync(path.join(dir, '.git'))
899
+ ) {
900
+ return dir;
901
+ }
902
+ dir = path.dirname(dir);
903
+ }
904
+
905
+ return process.cwd();
906
+ }
907
+
908
+ /**
909
+ * Find config file in project
910
+ */
911
+ export function findConfigFile(projectRoot: string): string | null {
912
+ for (const filename of CONFIG_FILES) {
913
+ const filepath = path.join(projectRoot, filename);
914
+ if (fs.existsSync(filepath)) {
915
+ return filepath;
916
+ }
917
+ }
918
+ return null;
919
+ }
920
+
921
+ // =============================================================================
922
+ // Preset Functions
923
+ // =============================================================================
924
+
925
+ /**
926
+ * Get a preset configuration
927
+ */
928
+ export function getPreset(presetName: string): PresetDefinition | null {
929
+ return CONFIG_PRESETS[presetName] ?? null;
930
+ }
931
+
932
+ /**
933
+ * Resolve preset inheritance chain
934
+ */
935
+ export function resolvePresetChain(presetName: string, seen: Set<string> = new Set()): ConfigInput {
936
+ const preset = CONFIG_PRESETS[presetName];
937
+ if (!preset) {
938
+ throw new Error(`Unknown preset: ${presetName}. Available: ${Object.keys(CONFIG_PRESETS).join(', ')}`);
939
+ }
940
+
941
+ if (seen.has(presetName)) {
942
+ throw new Error(`Circular preset inheritance detected: ${presetName}`);
943
+ }
944
+ seen.add(presetName);
945
+
946
+ let config: ConfigInput = { ...DEFAULT_CONFIG };
947
+
948
+ if (preset.extends) {
949
+ const parentConfig = resolvePresetChain(preset.extends, seen);
950
+ config = deepMerge(config, parentConfig);
951
+ }
952
+
953
+ config = deepMerge(config, preset.config as ConfigInput);
954
+
955
+ return config;
956
+ }
957
+
958
+ /**
959
+ * Apply a preset to configuration
960
+ */
961
+ export function applyPreset(presetNames: string | string[], overrides: Partial<ConfigInput> = {}): ConfigInput {
962
+ const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
963
+
964
+ if (presets.length === 0) {
965
+ throw new Error('At least one preset name is required');
966
+ }
967
+
968
+ let config: ConfigInput = { ...DEFAULT_CONFIG };
969
+
970
+ for (const presetName of presets) {
971
+ const trimmedName = presetName.trim();
972
+ const presetConfig = resolvePresetChain(trimmedName);
973
+ config = deepMerge(config, presetConfig);
974
+ }
975
+
976
+ config = deepMerge(config, overrides as ConfigInput);
977
+
978
+ return config;
979
+ }
980
+
981
+ /**
982
+ * Combine multiple presets
983
+ */
984
+ export function combinePresets(presetNames: string[], overrides: Partial<ConfigInput> = {}): ConfigInput {
985
+ if (!Array.isArray(presetNames) || presetNames.length === 0) {
986
+ throw new Error('combinePresets requires a non-empty array of preset names');
987
+ }
988
+ return applyPreset(presetNames, overrides);
989
+ }
990
+
991
+ /**
992
+ * Parse preset string
993
+ */
994
+ export function parsePresetString(presetString: string): string[] {
995
+ if (!presetString || typeof presetString !== 'string') {
996
+ return [];
997
+ }
998
+ return presetString.split(',').map(s => s.trim()).filter(Boolean);
999
+ }
1000
+
1001
+ /**
1002
+ * Validate preset names
1003
+ */
1004
+ export function validatePresets(presetNames: string | string[]): { valid: boolean; errors: string[]; validPresets: string[] } {
1005
+ const presets = Array.isArray(presetNames) ? presetNames : [presetNames];
1006
+ const errors: string[] = [];
1007
+ const validPresets: string[] = [];
1008
+ const availablePresets = Object.keys(CONFIG_PRESETS);
1009
+
1010
+ for (const preset of presets) {
1011
+ if (CONFIG_PRESETS[preset]) {
1012
+ validPresets.push(preset);
1013
+ } else {
1014
+ errors.push(`Unknown preset: "${preset}". Available: ${availablePresets.join(', ')}`);
1015
+ }
1016
+ }
1017
+
1018
+ return { valid: errors.length === 0, errors, validPresets };
1019
+ }
1020
+
1021
+ /**
1022
+ * Get presets by tag
1023
+ */
1024
+ export function getPresetsByTag(tag: string): string[] {
1025
+ return Object.entries(CONFIG_PRESETS)
1026
+ .filter(([, preset]) => preset.tags && preset.tags.includes(tag))
1027
+ .map(([key]) => key);
1028
+ }
1029
+
1030
+ /**
1031
+ * Create custom preset
1032
+ */
1033
+ export function createCustomPreset(baseName: string, newName: string, customConfig: Partial<ConfigInput> = {}): PresetDefinition {
1034
+ const basePreset = CONFIG_PRESETS[baseName];
1035
+ if (!basePreset) {
1036
+ throw new Error(`Unknown base preset: ${baseName}`);
1037
+ }
1038
+
1039
+ return {
1040
+ name: newName,
1041
+ description: `Custom preset based on ${basePreset.name}`,
1042
+ tags: [...(basePreset.tags || []), 'custom'],
1043
+ extends: baseName,
1044
+ config: customConfig
1045
+ };
1046
+ }
1047
+
1048
+ /**
1049
+ * List available presets
1050
+ */
1051
+ export function listPresets(options: ListPresetsOptions = {}): PresetInfo[] {
1052
+ let entries = Object.entries(CONFIG_PRESETS);
1053
+
1054
+ if (options.tag) {
1055
+ entries = entries.filter(([, preset]) =>
1056
+ preset.tags && preset.tags.includes(options.tag!)
1057
+ );
1058
+ }
1059
+
1060
+ return entries.map(([key, preset]) => {
1061
+ const info: PresetInfo = {
1062
+ key,
1063
+ name: preset.name,
1064
+ description: preset.description
1065
+ };
1066
+
1067
+ if (options.verbose) {
1068
+ info.tags = preset.tags || [];
1069
+ info.extends = preset.extends;
1070
+ }
1071
+
1072
+ return info;
1073
+ });
1074
+ }
1075
+
1076
+ // =============================================================================
1077
+ // Core Functions
1078
+ // =============================================================================
1079
+
1080
+ /**
1081
+ * Load configuration from file
1082
+ */
1083
+ export function load(projectRoot: string | null = null): LoadedConfig {
1084
+ const root = projectRoot ?? findProjectRoot();
1085
+ const configPath = findConfigFile(root);
1086
+
1087
+ let userConfig: Partial<Config> = {};
1088
+
1089
+ if (configPath) {
1090
+ try {
1091
+ if (configPath.endsWith('.json') || configPath.endsWith('.bootspringrc')) {
1092
+ const content = fs.readFileSync(configPath, 'utf-8');
1093
+ userConfig = JSON.parse(content) as Partial<Config>;
1094
+ } else {
1095
+ delete require.cache[require.resolve(configPath)];
1096
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1097
+ userConfig = require(configPath) as Partial<Config>;
1098
+ }
1099
+ } catch (error) {
1100
+ const message = error instanceof Error ? error.message : String(error);
1101
+ console.warn(`Warning: Could not load config from ${configPath}: ${message}`);
1102
+ }
1103
+ }
1104
+
1105
+ const config: LoadedConfig = deepMerge(DEFAULT_CONFIG, userConfig);
1106
+
1107
+ config._projectRoot = root;
1108
+ config._configPath = configPath;
1109
+ config._bootspringDir = path.join(root, '.bootspring');
1110
+
1111
+ return config;
1112
+ }
1113
+
1114
+ /**
1115
+ * Save configuration to file
1116
+ */
1117
+ export function save(config: LoadedConfig, filepath: string | null = null): boolean {
1118
+ const root = config._projectRoot ?? findProjectRoot();
1119
+ const targetPath = filepath ?? path.join(root, 'bootspring.config.js');
1120
+
1121
+ const cleanConfig = { ...config };
1122
+ delete cleanConfig._projectRoot;
1123
+ delete cleanConfig._configPath;
1124
+ delete cleanConfig._bootspringDir;
1125
+ delete cleanConfig._validation;
1126
+
1127
+ const content = `/**
1128
+ * Bootspring Configuration
1129
+ * https://bootspring.com/docs/configuration
1130
+ */
1131
+
1132
+ module.exports = ${JSON.stringify(cleanConfig, null, 2)};
1133
+ `;
1134
+
1135
+ try {
1136
+ fs.writeFileSync(targetPath, content, 'utf-8');
1137
+ return true;
1138
+ } catch (error) {
1139
+ const message = error instanceof Error ? error.message : String(error);
1140
+ console.error(`Error saving config: ${message}`);
1141
+ return false;
1142
+ }
1143
+ }
1144
+
1145
+ /**
1146
+ * Validate configuration
1147
+ */
1148
+ export function validate(config: LoadedConfig, options: ValidateOptions = {}): ValidationResult {
1149
+ const configToValidate = { ...config };
1150
+ delete configToValidate._projectRoot;
1151
+ delete configToValidate._configPath;
1152
+ delete configToValidate._bootspringDir;
1153
+ delete configToValidate._validation;
1154
+
1155
+ const errors: string[] = [];
1156
+ const warnings: string[] = [];
1157
+
1158
+ if (options.sections && options.sections.length > 0) {
1159
+ let allValid = true;
1160
+ const validatedData: Record<string, unknown> = {};
1161
+
1162
+ for (const section of options.sections) {
1163
+ const sectionValue = (configToValidate as Record<string, unknown>)[section];
1164
+ if (sectionValue !== undefined) {
1165
+ const result = validateSection(section, sectionValue);
1166
+ if (!result.valid) {
1167
+ allValid = false;
1168
+ errors.push(...result.errors);
1169
+ } else {
1170
+ validatedData[section] = result.data;
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ return {
1176
+ valid: allValid,
1177
+ errors,
1178
+ warnings,
1179
+ data: allValid ? validatedData as unknown as Config : null
1180
+ };
1181
+ }
1182
+
1183
+ const result = ConfigSchema.safeParse(configToValidate);
1184
+
1185
+ if (result.success) {
1186
+ return {
1187
+ valid: true,
1188
+ errors: [],
1189
+ warnings,
1190
+ data: result.data
1191
+ };
1192
+ }
1193
+
1194
+ return {
1195
+ valid: false,
1196
+ errors: formatValidationErrors(result.error),
1197
+ warnings,
1198
+ data: null
1199
+ };
1200
+ }
1201
+
1202
+ /**
1203
+ * Load and optionally validate configuration
1204
+ */
1205
+ export function loadWithValidation(projectRoot: string | null = null, options: LoadWithValidationOptions = {}): LoadedConfig {
1206
+ const { validate: shouldValidate = true, strict = false, silent = false } = options;
1207
+
1208
+ const config = load(projectRoot);
1209
+
1210
+ if (!shouldValidate) {
1211
+ config._validation = { skipped: true };
1212
+ return config;
1213
+ }
1214
+
1215
+ const validation = validate(config);
1216
+
1217
+ config._validation = {
1218
+ valid: validation.valid,
1219
+ errors: validation.errors,
1220
+ warnings: validation.warnings ?? [],
1221
+ validatedAt: new Date().toISOString()
1222
+ };
1223
+
1224
+ if (!validation.valid) {
1225
+ if (strict) {
1226
+ const errorMessage = `Configuration validation failed:\n${validation.errors.map(e => ` - ${e}`).join('\n')}`;
1227
+ throw new Error(errorMessage);
1228
+ }
1229
+
1230
+ if (!silent) {
1231
+ console.warn('\x1b[33mWarning: Configuration has validation issues:\x1b[0m');
1232
+ validation.errors.forEach(error => {
1233
+ console.warn(` \x1b[33m- ${error}\x1b[0m`);
1234
+ });
1235
+ console.warn('\x1b[33mThe configuration will still be used, but some features may not work correctly.\x1b[0m');
1236
+ }
1237
+ }
1238
+
1239
+ return config;
1240
+ }
1241
+
1242
+ /**
1243
+ * Get validation hint for error path
1244
+ */
1245
+ export function getValidationHint(errorPath: string): string | null {
1246
+ const hints: Record<string, string> = {
1247
+ 'stack.framework': 'Valid frameworks: nextjs, remix, nuxt, sveltekit, astro, express, fastify, hono, custom',
1248
+ 'stack.language': 'Valid languages: typescript, javascript',
1249
+ 'stack.database': 'Valid databases: postgresql, mysql, mongodb, sqlite, supabase, planetscale, none',
1250
+ 'stack.hosting': 'Valid hosting options: vercel, railway, render, fly, aws, gcp, azure, cloudflare, self-hosted, custom',
1251
+ 'plugins.auth.provider': 'Valid auth providers: clerk, nextauth, auth0, supabase, jwt, custom',
1252
+ 'plugins.payments.provider': 'Valid payment providers: stripe, paddle, lemonsqueezy, paypal, custom',
1253
+ 'plugins.database.provider': 'Valid database ORMs: prisma, drizzle, typeorm, kysely, custom',
1254
+ 'plugins.testing.provider': 'Valid test frameworks: vitest, jest, playwright, cypress, custom',
1255
+ 'plugins.ai.providers': 'Valid AI providers: anthropic, openai, google, cohere, huggingface, custom',
1256
+ 'dashboard.port': 'Port must be between 1024 and 65535',
1257
+ 'project.version': 'Version must follow semver format (e.g., 1.0.0)'
1258
+ };
1259
+
1260
+ return hints[errorPath] ?? null;
1261
+ }
1262
+
1263
+ /**
1264
+ * Parse and validate configuration with defaults
1265
+ */
1266
+ export function parseConfig(config: LoadedConfig): Config {
1267
+ const configToParse = { ...config };
1268
+ delete configToParse._projectRoot;
1269
+ delete configToParse._configPath;
1270
+ delete configToParse._bootspringDir;
1271
+ delete configToParse._validation;
1272
+
1273
+ const result = ConfigSchema.safeParse(configToParse);
1274
+ if (result.success) {
1275
+ return result.data;
1276
+ }
1277
+ return configToParse as Config;
1278
+ }
1279
+
1280
+ /**
1281
+ * Get default configuration
1282
+ */
1283
+ export function getDefaults(): ConfigInput {
1284
+ return { ...DEFAULT_CONFIG };
1285
+ }