@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/cli/seed.js CHANGED
@@ -3,8 +3,12 @@
3
3
  * Project scaffolding and configuration management
4
4
  *
5
5
  * Commands:
6
+ * setup Create .bootspring/inputs folder structure
6
7
  * init Full Q&A, generate SEED.md
8
+ * synthesize Create SEED.md from preseed documents
9
+ * generate Ingest files and generate documents
7
10
  * scaffold Generate project structure from SEED.md
11
+ * build Build from seed docs (--loop for continuous)
8
12
  * update Re-run questionnaire, update SEED.md
9
13
  * status Show current seed configuration
10
14
  * export Export seed config as JSON/YAML
@@ -15,12 +19,97 @@
15
19
 
16
20
  const path = require('path');
17
21
  const fs = require('fs');
22
+ const readline = require('readline');
18
23
  const yaml = require('yaml');
19
24
  const config = require('../core/config');
20
25
  const utils = require('../core/utils');
21
26
  const { runQuestionnaire } = require('../generators/questionnaire');
22
27
  const seedTemplate = require('../generators/templates/seed.template');
23
28
  const scaffold = require('../core/scaffold');
29
+ const projectState = require('../core/project-state');
30
+ const checkpointEngine = require('../core/checkpoint-engine');
31
+ const tierEnforcement = require('../core/tier-enforcement');
32
+
33
+ // Lazy load ingest module
34
+ let ingest = null;
35
+ function getIngest() {
36
+ if (!ingest) {
37
+ ingest = require('../core/ingest');
38
+ }
39
+ return ingest;
40
+ }
41
+
42
+ /**
43
+ * Seed folder structure
44
+ */
45
+ const SEED_FOLDERS = [
46
+ '.bootspring',
47
+ '.bootspring/inputs',
48
+ '.bootspring/inputs/mvp',
49
+ '.bootspring/inputs/mvp/source',
50
+ '.bootspring/inputs/business',
51
+ '.bootspring/inputs/prd',
52
+ '.bootspring/inputs/prd/features',
53
+ '.bootspring/inputs/designs',
54
+ '.bootspring/inputs/designs/figma-exports',
55
+ '.bootspring/inputs/designs/wireframes',
56
+ '.bootspring/inputs/legal',
57
+ '.bootspring/inputs/api',
58
+ '.bootspring/inputs/data',
59
+ '.bootspring/generated',
60
+ '.bootspring/generated/prd',
61
+ '.bootspring/generated/master-plan',
62
+ '.bootspring/generated/business',
63
+ '.bootspring/generated/architecture',
64
+ '.bootspring/generated/legal',
65
+ '.bootspring/context',
66
+ '.bootspring/logs',
67
+ '.bootspring/logs/agents',
68
+ '.bootspring/logs/decisions',
69
+ '.bootspring/logs/code',
70
+ '.bootspring/config'
71
+ ];
72
+
73
+ /**
74
+ * Create seed folder structure (used by both setup and init)
75
+ * @param {string} projectRoot - Project root path
76
+ * @returns {number} Number of folders created
77
+ */
78
+ function createSeedFolders(projectRoot) {
79
+ let dirsCreated = 0;
80
+
81
+ for (const folder of SEED_FOLDERS) {
82
+ const folderPath = path.join(projectRoot, folder);
83
+ if (!fs.existsSync(folderPath)) {
84
+ fs.mkdirSync(folderPath, { recursive: true });
85
+ dirsCreated++;
86
+ }
87
+ }
88
+
89
+ // Create README for inputs
90
+ const inputsReadmePath = path.join(projectRoot, '.bootspring/inputs/README.md');
91
+ if (!fs.existsSync(inputsReadmePath)) {
92
+ const readme = `# Input Files
93
+
94
+ Drop your existing files here before running \`bootspring seed generate\`.
95
+
96
+ ## Folders
97
+
98
+ | Folder | What to put here |
99
+ |--------|------------------|
100
+ | \`mvp/source/\` | AI-generated MVP code (Lovable, Bolt, V0, etc.) |
101
+ | \`business/\` | Business plans, pitch decks, market research |
102
+ | \`prd/\` | Product requirements documents |
103
+ | \`designs/\` | Figma exports, wireframes, mockups |
104
+ | \`legal/\` | Existing terms, privacy policies |
105
+ | \`api/\` | OpenAPI specs, API documentation |
106
+ | \`data/\` | Sample data, database schemas |
107
+ `;
108
+ fs.writeFileSync(inputsReadmePath, readme);
109
+ }
110
+
111
+ return dirsCreated;
112
+ }
24
113
 
25
114
  /**
26
115
  * Run seed command
@@ -30,17 +119,46 @@ async function run(args) {
30
119
  const parsedArgs = utils.parseArgs(args);
31
120
  const subcommand = parsedArgs._[0] || 'status';
32
121
 
122
+ // Check tier access for paid commands
123
+ const paidCommands = ['scaffold', 'synthesize', 'from-preseed', 'generate', 'build'];
124
+ if (paidCommands.includes(subcommand)) {
125
+ // Map aliases to actual command names for tier check
126
+ const commandMap = {
127
+ 'from-preseed': 'synthesize',
128
+ };
129
+ const actualCommand = commandMap[subcommand] || subcommand;
130
+
131
+ try {
132
+ tierEnforcement.requireSeedAccess(actualCommand);
133
+ } catch (error) {
134
+ if (error.code === 'TIER_REQUIRED') {
135
+ console.log(error.upgradePrompt);
136
+ return;
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+
33
142
  switch (subcommand) {
143
+ case 'setup':
144
+ return seedSetup(parsedArgs);
34
145
  case 'init':
35
146
  return seedInit(parsedArgs);
147
+ case 'generate':
148
+ return seedGenerate(parsedArgs);
36
149
  case 'scaffold':
37
150
  return seedScaffold(parsedArgs);
151
+ case 'build':
152
+ return seedBuild(parsedArgs);
38
153
  case 'update':
39
154
  return seedUpdate(parsedArgs);
40
155
  case 'status':
41
156
  return seedStatus(parsedArgs);
42
157
  case 'export':
43
158
  return seedExport(parsedArgs);
159
+ case 'synthesize':
160
+ case 'from-preseed':
161
+ return seedSynthesize(parsedArgs);
44
162
  case 'help':
45
163
  case '-h':
46
164
  case '--help':
@@ -51,6 +169,145 @@ async function run(args) {
51
169
  }
52
170
  }
53
171
 
172
+ /**
173
+ * Setup .bootspring/inputs folder structure
174
+ */
175
+ async function seedSetup(_args) {
176
+ const projectRoot = config.findProjectRoot();
177
+
178
+ console.log(`
179
+ ${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring Seed Setup${utils.COLORS.reset}
180
+ ${utils.COLORS.dim}Creating input folder structure${utils.COLORS.reset}
181
+ `);
182
+
183
+ const spinner = utils.createSpinner('Creating folder structure...').start();
184
+ const dirsCreated = createSeedFolders(projectRoot);
185
+ spinner.succeed(`Created ${dirsCreated} folders`);
186
+
187
+ console.log(`
188
+ ${utils.COLORS.green}${utils.COLORS.bold}Setup complete!${utils.COLORS.reset}
189
+
190
+ ${utils.COLORS.bold}Folder structure:${utils.COLORS.reset}
191
+ .bootspring/
192
+ ├── inputs/ ${utils.COLORS.dim}# Your files go here${utils.COLORS.reset}
193
+ │ ├── mvp/source/ ${utils.COLORS.dim}# MVP code from Lovable, Bolt, V0${utils.COLORS.reset}
194
+ │ ├── business/ ${utils.COLORS.dim}# Business plans, pitch decks${utils.COLORS.reset}
195
+ │ ├── prd/ ${utils.COLORS.dim}# Product requirements${utils.COLORS.reset}
196
+ │ ├── designs/ ${utils.COLORS.dim}# Figma exports, wireframes${utils.COLORS.reset}
197
+ │ ├── legal/ ${utils.COLORS.dim}# Terms, privacy policies${utils.COLORS.reset}
198
+ │ ├── api/ ${utils.COLORS.dim}# OpenAPI specs${utils.COLORS.reset}
199
+ │ └── data/ ${utils.COLORS.dim}# Sample data, schemas${utils.COLORS.reset}
200
+ ├── generated/ ${utils.COLORS.dim}# Bootspring output${utils.COLORS.reset}
201
+ ├── context/ ${utils.COLORS.dim}# AI context index${utils.COLORS.reset}
202
+ └── logs/ ${utils.COLORS.dim}# Action history${utils.COLORS.reset}
203
+
204
+ ${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
205
+ 1. Drop your files in ${utils.COLORS.cyan}.bootspring/inputs/${utils.COLORS.reset} subfolders
206
+ 2. Run ${utils.COLORS.cyan}bootspring seed generate${utils.COLORS.reset} to process them
207
+ `);
208
+ }
209
+
210
+ /**
211
+ * Generate documents from input files
212
+ */
213
+ async function seedGenerate(_args) {
214
+ const projectRoot = config.findProjectRoot();
215
+ const inputsDir = path.join(projectRoot, '.bootspring', 'inputs');
216
+ const generatedDir = path.join(projectRoot, '.bootspring', 'generated');
217
+
218
+ console.log(`
219
+ ${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring Seed Generate${utils.COLORS.reset}
220
+ ${utils.COLORS.dim}Processing input files and generating documents${utils.COLORS.reset}
221
+ `);
222
+
223
+ // Check if inputs folder exists
224
+ if (!fs.existsSync(inputsDir)) {
225
+ utils.print.warning('.bootspring/inputs folder not found');
226
+ utils.print.dim('Run "bootspring seed setup" first');
227
+ return;
228
+ }
229
+
230
+ // Ensure generated folder exists
231
+ if (!fs.existsSync(generatedDir)) {
232
+ fs.mkdirSync(generatedDir, { recursive: true });
233
+ }
234
+
235
+ try {
236
+ const Ingest = getIngest();
237
+ const spinner = utils.createSpinner('Analyzing input files...').start();
238
+
239
+ // Ingest all input files
240
+ const ingested = await Ingest.ingestAll(projectRoot);
241
+
242
+ spinner.succeed('Analyzed input files');
243
+
244
+ // Show what was found
245
+ console.log(`
246
+ ${utils.COLORS.bold}Files Found:${utils.COLORS.reset}`);
247
+
248
+ const counts = {
249
+ mvp: ingested.mvp?.files?.length || 0,
250
+ business: ingested.business?.length || 0,
251
+ prd: ingested.prd?.length || 0,
252
+ designs: ingested.designs?.files?.length || 0,
253
+ api: ingested.api?.length || 0,
254
+ data: ingested.data?.length || 0
255
+ };
256
+
257
+ console.log(` MVP code: ${counts.mvp > 0 ? utils.COLORS.green + counts.mvp + ' files' : utils.COLORS.dim + 'none'}${utils.COLORS.reset}`);
258
+ console.log(` Business docs: ${counts.business > 0 ? utils.COLORS.green + counts.business + ' files' : utils.COLORS.dim + 'none'}${utils.COLORS.reset}`);
259
+ console.log(` PRD docs: ${counts.prd > 0 ? utils.COLORS.green + counts.prd + ' files' : utils.COLORS.dim + 'none'}${utils.COLORS.reset}`);
260
+ console.log(` Design files: ${counts.designs > 0 ? utils.COLORS.green + counts.designs + ' files' : utils.COLORS.dim + 'none'}${utils.COLORS.reset}`);
261
+ console.log(` API specs: ${counts.api > 0 ? utils.COLORS.green + counts.api + ' files' : utils.COLORS.dim + 'none'}${utils.COLORS.reset}`);
262
+ console.log(` Data files: ${counts.data > 0 ? utils.COLORS.green + counts.data + ' files' : utils.COLORS.dim + 'none'}${utils.COLORS.reset}`);
263
+
264
+ // Load SEED.md config if exists
265
+ const seedPath = path.join(projectRoot, 'SEED.md');
266
+ let seedConfig = {};
267
+ if (fs.existsSync(seedPath)) {
268
+ seedConfig = parseSeedFile(seedPath) || {};
269
+ }
270
+
271
+ // Generate documents
272
+ console.log(`
273
+ ${utils.COLORS.bold}Generating documents...${utils.COLORS.reset}`);
274
+
275
+ const generateSpinner = utils.createSpinner('Generating...').start();
276
+ const outputs = await Ingest.generateDocuments(ingested, seedConfig, projectRoot);
277
+ generateSpinner.succeed('Generated documents');
278
+
279
+ // Show what was generated
280
+ let generatedCount = 0;
281
+ for (const [name, content] of Object.entries(outputs)) {
282
+ if (content) {
283
+ generatedCount++;
284
+ console.log(` ${utils.COLORS.green}+${utils.COLORS.reset} ${name}`);
285
+ }
286
+ }
287
+
288
+ // Update context index
289
+ const contextSpinner = utils.createSpinner('Updating context index...').start();
290
+ await Ingest.updateContextIndex(projectRoot, ingested);
291
+ contextSpinner.succeed('Updated context index');
292
+
293
+ console.log(`
294
+ ${utils.COLORS.green}${utils.COLORS.bold}Generation complete!${utils.COLORS.reset}
295
+
296
+ Generated ${generatedCount} documents in ${utils.COLORS.cyan}.bootspring/generated/${utils.COLORS.reset}
297
+
298
+ ${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
299
+ 1. Review generated documents
300
+ 2. Run ${utils.COLORS.cyan}bootspring seed scaffold${utils.COLORS.reset} to create project structure
301
+ 3. Your CLAUDE.md is updated with context
302
+ `);
303
+ } catch (error) {
304
+ utils.print.error(`Generation failed: ${error.message}`);
305
+ if (process.env.DEBUG) {
306
+ console.error(error.stack);
307
+ }
308
+ }
309
+ }
310
+
54
311
  /**
55
312
  * Initialize seed - run questionnaire and generate SEED.md
56
313
  */
@@ -64,6 +321,11 @@ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Seed Init${utils.COLORS.r
64
321
  ${utils.COLORS.dim}Interactive project configuration${utils.COLORS.reset}
65
322
  `);
66
323
 
324
+ // Auto-create folder structure
325
+ const setupSpinner = utils.createSpinner('Setting up folder structure...').start();
326
+ createSeedFolders(projectRoot);
327
+ setupSpinner.succeed('Folder structure ready');
328
+
67
329
  // Check if SEED.md already exists
68
330
  if (utils.fileExists(seedPath) && !force) {
69
331
  utils.print.warning('SEED.md already exists');
@@ -108,6 +370,18 @@ ${utils.COLORS.dim}Interactive project configuration${utils.COLORS.reset}
108
370
  }
109
371
  }
110
372
 
373
+ // Auto-tag project as development type
374
+ try {
375
+ projectState.setProjectType(projectRoot, projectState.PROJECT_TYPES.DEVELOPMENT, {
376
+ autoTagged: true,
377
+ taggedBy: 'seed'
378
+ });
379
+ checkpointEngine.syncCheckpoints(projectRoot, { verbose: false });
380
+ utils.print.success('Project tagged as development type');
381
+ } catch (err) {
382
+ utils.print.debug(`Auto-tagging failed: ${err.message}`);
383
+ }
384
+
111
385
  console.log(`
112
386
  ${utils.COLORS.green}${utils.COLORS.bold}✓ Seed initialized successfully!${utils.COLORS.reset}
113
387
 
@@ -129,6 +403,7 @@ async function seedScaffold(args) {
129
403
  const preset = args.preset || args.p;
130
404
  const fromConfig = args['from-config'] || args.c;
131
405
  const dryRun = args['dry-run'] || args.d;
406
+ const skipConfirm = args.yes || args.y;
132
407
 
133
408
  console.log(`
134
409
  ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Scaffold${utils.COLORS.reset}
@@ -136,6 +411,7 @@ ${utils.COLORS.dim}Generate project structure${utils.COLORS.reset}
136
411
  `);
137
412
 
138
413
  let scaffoldConfig;
414
+ let detectedPreset = null;
139
415
 
140
416
  if (preset) {
141
417
  // Use preset
@@ -148,6 +424,7 @@ ${utils.COLORS.dim}Generate project structure${utils.COLORS.reset}
148
424
 
149
425
  utils.print.info(`Using preset: ${preset}`);
150
426
  scaffoldConfig = scaffold.getPresetConfig(preset);
427
+ detectedPreset = preset;
151
428
  } else if (fromConfig) {
152
429
  // Load from bootspring.config.js
153
430
  const cfg = config.load();
@@ -188,6 +465,7 @@ ${utils.COLORS.dim}Example: bootspring seed scaffold --preset=nextjs${utils.COLO
188
465
 
189
466
  // Show what will be created
190
467
  const plan = scaffold.planScaffold(scaffoldConfig, projectRoot);
468
+ detectedPreset = plan.preset;
191
469
 
192
470
  console.log(`
193
471
  ${utils.COLORS.bold}Scaffold Plan:${utils.COLORS.reset}
@@ -220,6 +498,42 @@ ${utils.COLORS.yellow}Dry run - no files created${utils.COLORS.reset}
220
498
  return;
221
499
  }
222
500
 
501
+ // Show detected preset and ask for confirmation
502
+ const presetInfo = scaffold.getPresetInfo(detectedPreset || 'nextjs');
503
+ console.log(`
504
+ ${utils.COLORS.bold}Detected preset:${utils.COLORS.reset} ${utils.COLORS.cyan}${detectedPreset || 'nextjs'}${utils.COLORS.reset}
505
+ ${utils.COLORS.dim}${presetInfo?.description || ''}${utils.COLORS.reset}
506
+ `);
507
+
508
+ if (!skipConfirm) {
509
+ const rl = readline.createInterface({
510
+ input: process.stdin,
511
+ output: process.stdout
512
+ });
513
+
514
+ const answer = await new Promise((resolve) => {
515
+ rl.question(`Proceed with ${utils.COLORS.cyan}${detectedPreset || 'nextjs'}${utils.COLORS.reset} preset? [Y/n] `, (ans) => {
516
+ rl.close();
517
+ resolve(ans.trim().toLowerCase());
518
+ });
519
+ });
520
+
521
+ if (answer === 'n' || answer === 'no') {
522
+ console.log(`
523
+ ${utils.COLORS.dim}Scaffold cancelled. To use a different preset:${utils.COLORS.reset}
524
+ bootspring seed scaffold --preset=<preset>
525
+
526
+ ${utils.COLORS.bold}Available presets:${utils.COLORS.reset}`);
527
+ const presets = scaffold.getPresets();
528
+ for (const p of presets) {
529
+ const info = scaffold.getPresetInfo(p);
530
+ console.log(` ${utils.COLORS.cyan}${p}${utils.COLORS.reset} - ${info.description}`);
531
+ }
532
+ console.log('');
533
+ return;
534
+ }
535
+ }
536
+
223
537
  // Execute scaffold
224
538
  console.log('');
225
539
  const spinner = utils.createSpinner('Scaffolding project...').start();
@@ -248,6 +562,15 @@ ${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
248
562
  }
249
563
  }
250
564
 
565
+ /**
566
+ * Build from seed documents - redirects to interactive build command
567
+ */
568
+ async function seedBuild(args) {
569
+ // Just run the interactive build command
570
+ const build = require('./build');
571
+ await build.run(args._?.slice(1) || []);
572
+ }
573
+
251
574
  /**
252
575
  * Update existing SEED.md
253
576
  */
@@ -404,6 +727,1100 @@ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Seed Export${utils.COLORS
404
727
  }
405
728
  }
406
729
 
730
+ /**
731
+ * Synthesize SEED.md from preseed documents
732
+ */
733
+ async function seedSynthesize(args) {
734
+ const projectRoot = config.findProjectRoot();
735
+ const preseedDir = path.join(projectRoot, '.bootspring', 'preseed');
736
+ const seedPath = path.join(projectRoot, 'SEED.md');
737
+ const force = args.force || args.f;
738
+
739
+ console.log(`
740
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Seed Synthesize${utils.COLORS.reset}
741
+ ${utils.COLORS.dim}Create SEED.md from your preseed documents${utils.COLORS.reset}
742
+ `);
743
+
744
+ // Check for preseed documents
745
+ if (!fs.existsSync(preseedDir)) {
746
+ utils.print.error('No preseed documents found');
747
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed start${utils.COLORS.reset} first`);
748
+ return;
749
+ }
750
+
751
+ // Valid preseed document names (only these should be synthesized)
752
+ const validPreseedDocs = [
753
+ 'VISION.md',
754
+ 'AUDIENCE.md',
755
+ 'MARKET.md',
756
+ 'COMPETITORS.md',
757
+ 'BUSINESS_MODEL.md',
758
+ 'PRD.md',
759
+ 'TECHNICAL_SPEC.md',
760
+ 'ROADMAP.md'
761
+ ];
762
+
763
+ // Find all preseed docs (only valid ones, exclude MERGE_INSTRUCTIONS etc)
764
+ const preseedFiles = fs.readdirSync(preseedDir).filter(f =>
765
+ validPreseedDocs.includes(f)
766
+ );
767
+
768
+ if (preseedFiles.length === 0) {
769
+ utils.print.error('No preseed documents found');
770
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed init${utils.COLORS.reset} first`);
771
+ return;
772
+ }
773
+
774
+ console.log(`${utils.COLORS.bold}Found ${preseedFiles.length} preseed documents:${utils.COLORS.reset}`);
775
+ for (const file of preseedFiles) {
776
+ console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} ${file}`);
777
+ }
778
+
779
+ // Check if SEED.md already exists
780
+ if (fs.existsSync(seedPath) && !force) {
781
+ utils.print.warning('SEED.md already exists');
782
+ console.log(`\nUse ${utils.COLORS.cyan}--force${utils.COLORS.reset} to overwrite`);
783
+ return;
784
+ }
785
+
786
+ // Read all preseed docs
787
+ const docs = {};
788
+ for (const file of preseedFiles) {
789
+ const filePath = path.join(preseedDir, file);
790
+ docs[file.replace('.md', '')] = fs.readFileSync(filePath, 'utf-8');
791
+ }
792
+
793
+ // Also try to read PRESEED_CONFIG.json
794
+ let preseedConfig = {};
795
+ const configPath = path.join(preseedDir, 'PRESEED_CONFIG.json');
796
+ if (fs.existsSync(configPath)) {
797
+ try {
798
+ preseedConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
799
+ } catch {}
800
+ }
801
+
802
+ // Generate SEED.md from preseed docs
803
+ const spinner = utils.createSpinner('Synthesizing SEED.md...').start();
804
+
805
+ try {
806
+ const seedContent = generateSeedFromPreseed(docs, preseedConfig);
807
+ fs.writeFileSync(seedPath, seedContent);
808
+ spinner.succeed('Created SEED.md');
809
+
810
+ console.log(`
811
+ ${utils.COLORS.green}${utils.COLORS.bold}✓ SEED.md synthesized successfully!${utils.COLORS.reset}
812
+
813
+ ${utils.COLORS.bold}Extracted from:${utils.COLORS.reset}
814
+ ${preseedFiles.map(f => ` • ${f}`).join('\n')}
815
+
816
+ ${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
817
+ 1. Review ${utils.COLORS.cyan}SEED.md${utils.COLORS.reset} - your project spec is ready
818
+ 2. Run ${utils.COLORS.cyan}bootspring seed scaffold --preset=nextjs${utils.COLORS.reset} to generate project
819
+ `);
820
+ } catch (error) {
821
+ spinner.fail(`Synthesis failed: ${error.message}`);
822
+ }
823
+ }
824
+
825
+ /**
826
+ * Extract a section from markdown content by heading
827
+ */
828
+ function extractSection(content, headingPattern, options = {}) {
829
+ const { maxLength = 2000, stopAt = null } = options;
830
+
831
+ // Match the heading (## or ### or ####)
832
+ // Wrap headingPattern in non-capturing group to ensure | alternation works correctly
833
+ // Handle optional section numbers like "## 10. Application Structure"
834
+ const headingRegex = new RegExp(`^(#{1,4})\\s*(?:\\d+\\.\\s*)?(?:${headingPattern})[^\\n]*\\n`, 'im');
835
+ const match = content.match(headingRegex);
836
+ if (!match || !match[1]) return null;
837
+
838
+ const startIndex = match.index + match[0].length;
839
+ const headingLevel = match[1].length;
840
+
841
+ // Find the next heading of same or higher level
842
+ const restContent = content.slice(startIndex);
843
+ const nextHeadingRegex = new RegExp(`^#{1,${headingLevel}}\\s+`, 'm');
844
+ const nextMatch = restContent.match(nextHeadingRegex);
845
+
846
+ let sectionContent = nextMatch
847
+ ? restContent.slice(0, nextMatch.index)
848
+ : restContent;
849
+
850
+ // Stop at a specific pattern if provided
851
+ if (stopAt) {
852
+ const stopMatch = sectionContent.match(new RegExp(stopAt, 'i'));
853
+ if (stopMatch) {
854
+ sectionContent = sectionContent.slice(0, stopMatch.index);
855
+ }
856
+ }
857
+
858
+ // Clean and truncate
859
+ sectionContent = sectionContent.trim();
860
+ if (sectionContent.length > maxLength) {
861
+ sectionContent = sectionContent.slice(0, maxLength) + '...';
862
+ }
863
+
864
+ return sectionContent || null;
865
+ }
866
+
867
+ /**
868
+ * Extract project name from preseed documents
869
+ */
870
+ function extractProjectName(docs) {
871
+ // Try VISION first
872
+ const visionDoc = docs['VISION'] || docs['vision'];
873
+ if (visionDoc) {
874
+ // Look for "Application Name:" or "**Application Name:**"
875
+ const nameMatch = visionDoc.match(/\*{0,2}Application\s+Name:?\*{0,2}\s*(.+)/i);
876
+ if (nameMatch) return nameMatch[1].trim();
877
+
878
+ // Look for "# ProjectName —" pattern
879
+ const titleMatch = visionDoc.match(/^#\s+([^—\-\n]+)/m);
880
+ if (titleMatch) return titleMatch[1].trim();
881
+ }
882
+
883
+ // Try PRD
884
+ const prdDoc = docs['PRD'] || docs['prd'];
885
+ if (prdDoc) {
886
+ const nameMatch = prdDoc.match(/\*{0,2}Application\s+Name:?\*{0,2}\s*(.+)/i);
887
+ if (nameMatch) return nameMatch[1].trim();
888
+
889
+ const titleMatch = prdDoc.match(/^#\s+([^—\-\n]+)/m);
890
+ if (titleMatch) return titleMatch[1].trim();
891
+ }
892
+
893
+ return 'My Project';
894
+ }
895
+
896
+ /**
897
+ * Extract tagline/description from documents
898
+ */
899
+ function extractTagline(docs) {
900
+ const visionDoc = docs['VISION'] || docs['vision'];
901
+ if (visionDoc) {
902
+ // Look for "is a..." or "is an..." sentence - capture the whole description
903
+ // Pattern: "is an **agentic AI platform** that makes it simple..."
904
+ const isMatch = visionDoc.match(/is\s+an?\s+\*{0,2}([^*]+?)\*{0,2}\s+that\s+([^.—\n]+)/i);
905
+ if (isMatch) {
906
+ let tagline = `${isMatch[1].trim()} that ${isMatch[2].trim()}`;
907
+ tagline = tagline.replace(/\*+/g, '');
908
+ if (tagline.length > 200) tagline = tagline.slice(0, 200) + '...';
909
+ if (tagline.length > 20) return tagline;
910
+ }
911
+
912
+ // Fallback: just get "is an X" pattern without "that" clause
913
+ const simpleMatch = visionDoc.match(/is\s+an?\s+\*{0,2}([^*\n—:]+?)\*{0,2}(?:\s|—|:|$)/i);
914
+ if (simpleMatch) {
915
+ let tagline = simpleMatch[1].replace(/\*+/g, '').trim();
916
+ if (tagline.length > 200) tagline = tagline.slice(0, 200) + '...';
917
+ if (tagline.length > 10) return tagline;
918
+ }
919
+
920
+ // Look for executive summary first paragraph
921
+ const summarySection = extractSection(visionDoc, 'Executive\\s+Vision|Vision\\s+Summary|Summary', { maxLength: 800 });
922
+ if (summarySection) {
923
+ const firstPara = summarySection.split(/\n\n/)[0].replace(/\*+/g, '').trim();
924
+ if (firstPara.length > 20 && firstPara.length < 250) return firstPara;
925
+ }
926
+ }
927
+ return '';
928
+ }
929
+
930
+ /**
931
+ * Extract problem statement from documents
932
+ */
933
+ function extractProblem(docs) {
934
+ const visionDoc = docs['VISION'] || docs['vision'];
935
+ const prdDoc = docs['PRD'] || docs['prd'];
936
+
937
+ // Try VISION first
938
+ if (visionDoc) {
939
+ const problemSection = extractSection(visionDoc, '(The\\s+)?Problem|Pain\\s+Points|Market\\s+Reality', { maxLength: 800 });
940
+ if (problemSection) {
941
+ // Clean up and get first substantial paragraph
942
+ const cleaned = problemSection
943
+ // Remove subsection headers like "### 2.1 The market reality"
944
+ .replace(/^#{1,4}\s+[\d.]+\s+[^\n]+\n/gm, '')
945
+ // Remove section number prefixes like "2.1 " at start of content
946
+ .replace(/^[\d.]+\s+[A-Z][a-z]+\s+[a-z]+\n/m, '')
947
+ // Convert bullet points to flowing text
948
+ .replace(/^[\-\*]\s+\*{0,2}([^*\n:]+):?\*{0,2}\s*/gm, '$1: ')
949
+ // Remove remaining markdown
950
+ .replace(/\*+/g, '')
951
+ .trim();
952
+
953
+ // Get first substantial paragraph or combine bullet points
954
+ const paragraphs = cleaned.split(/\n\n/).filter(p => p.trim().length > 30);
955
+ if (paragraphs.length > 0) {
956
+ let result = paragraphs[0].replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
957
+ // Limit length
958
+ if (result.length > 500) result = result.slice(0, 500) + '...';
959
+ return result;
960
+ }
961
+ }
962
+ }
963
+
964
+ // Try PRD
965
+ if (prdDoc) {
966
+ const problemSection = extractSection(prdDoc, '(The\\s+)?Problem|Pain\\s+Points', { maxLength: 800 });
967
+ if (problemSection) {
968
+ const cleaned = problemSection
969
+ .replace(/^#{1,4}\s+[\d.]+\s+[^\n]+\n/gm, '')
970
+ .replace(/^[\d.]+\s+[A-Z][a-z]+\s+[a-z]+\n/m, '')
971
+ .replace(/^[\-\*]\s+\*{0,2}([^*\n:]+):?\*{0,2}\s*/gm, '$1: ')
972
+ .replace(/\*+/g, '')
973
+ .trim();
974
+
975
+ const paragraphs = cleaned.split(/\n\n/).filter(p => p.trim().length > 30);
976
+ if (paragraphs.length > 0) {
977
+ let result = paragraphs[0].replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
978
+ if (result.length > 500) result = result.slice(0, 500) + '...';
979
+ return result;
980
+ }
981
+ }
982
+ }
983
+
984
+ return '';
985
+ }
986
+
987
+ /**
988
+ * Extract solution description from documents
989
+ */
990
+ function extractSolution(docs) {
991
+ const visionDoc = docs['VISION'] || docs['vision'];
992
+
993
+ if (visionDoc) {
994
+ // Look for "Our Solution" or "The Solution" or "What We Build"
995
+ const solutionSection = extractSection(visionDoc, '(Our\\s+)?Solution|What\\s+We\\s+Build|Core\\s+Product', { maxLength: 1000 });
996
+ if (solutionSection) {
997
+ const paragraphs = solutionSection.split(/\n\n/).filter(p => p.trim().length > 50);
998
+ if (paragraphs.length > 0) {
999
+ return paragraphs[0].replace(/^[#\*\-\d\.]+\s*/gm, '').replace(/\*+/g, '').trim();
1000
+ }
1001
+ }
1002
+
1003
+ // Look for "in one sentence" pattern
1004
+ const oneLineMatch = visionDoc.match(/in\s+one\s+sentence[:\s]*\*{0,2}([^*\n]+(?:\*{0,2}[^*\n]+)?)/i);
1005
+ if (oneLineMatch) {
1006
+ return oneLineMatch[1].replace(/\*+/g, '').trim();
1007
+ }
1008
+ }
1009
+
1010
+ return '';
1011
+ }
1012
+
1013
+ /**
1014
+ * Extract tech stack from technical spec
1015
+ */
1016
+ function extractTechStack(docs) {
1017
+ const techDoc = docs['TECHNICAL_SPEC'] || docs['TECHNICAL-SPEC'] || docs['technical_spec'];
1018
+
1019
+ const stack = {
1020
+ framework: 'nextjs',
1021
+ language: 'typescript',
1022
+ database: 'postgresql',
1023
+ styling: 'tailwind',
1024
+ uiLibrary: 'shadcn',
1025
+ orm: 'prisma',
1026
+ auth: 'clerk',
1027
+ payments: 'stripe',
1028
+ hosting: 'vercel',
1029
+ vector: null
1030
+ };
1031
+
1032
+ if (!techDoc) return stack;
1033
+
1034
+ // Framework detection
1035
+ if (techDoc.match(/Next\.?js/i)) stack.framework = 'nextjs';
1036
+ else if (techDoc.match(/Nuxt/i)) stack.framework = 'nuxt';
1037
+ else if (techDoc.match(/Vue/i)) stack.framework = 'vue';
1038
+ else if (techDoc.match(/Remix/i)) stack.framework = 'remix';
1039
+ else if (techDoc.match(/\bReact\b/) && !techDoc.match(/Next\.?js/i)) stack.framework = 'react';
1040
+ else if (techDoc.match(/Express|Fastify|Hono/i)) stack.framework = 'node';
1041
+
1042
+ // Language
1043
+ if (techDoc.match(/TypeScript/i)) stack.language = 'typescript';
1044
+ else if (techDoc.match(/JavaScript/i) && !techDoc.match(/TypeScript/i)) stack.language = 'javascript';
1045
+
1046
+ // Database
1047
+ if (techDoc.match(/Postgres|PostgreSQL/i)) stack.database = 'postgresql';
1048
+ else if (techDoc.match(/MongoDB/i)) stack.database = 'mongodb';
1049
+ else if (techDoc.match(/MySQL/i)) stack.database = 'mysql';
1050
+ else if (techDoc.match(/Supabase/i)) stack.database = 'supabase';
1051
+ else if (techDoc.match(/PlanetScale/i)) stack.database = 'planetscale';
1052
+ else if (techDoc.match(/Firebase/i)) stack.database = 'firebase';
1053
+
1054
+ // Vector store
1055
+ if (techDoc.match(/pgvector/i)) stack.vector = 'pgvector';
1056
+ else if (techDoc.match(/Pinecone/i)) stack.vector = 'pinecone';
1057
+ else if (techDoc.match(/Weaviate/i)) stack.vector = 'weaviate';
1058
+
1059
+ // Styling
1060
+ if (techDoc.match(/Tailwind/i)) stack.styling = 'tailwind';
1061
+ else if (techDoc.match(/styled-components/i)) stack.styling = 'styled-components';
1062
+ else if (techDoc.match(/CSS\s+Modules/i)) stack.styling = 'css-modules';
1063
+
1064
+ // UI Library
1065
+ if (techDoc.match(/Shadcn|shadcn\/ui/i)) stack.uiLibrary = 'shadcn';
1066
+ else if (techDoc.match(/Chakra/i)) stack.uiLibrary = 'chakra';
1067
+ else if (techDoc.match(/MUI|Material[- ]UI/i)) stack.uiLibrary = 'mui';
1068
+ else if (techDoc.match(/Radix/i)) stack.uiLibrary = 'radix';
1069
+
1070
+ // ORM
1071
+ if (techDoc.match(/Prisma/i)) stack.orm = 'prisma';
1072
+ else if (techDoc.match(/Drizzle/i)) stack.orm = 'drizzle';
1073
+ else if (techDoc.match(/TypeORM/i)) stack.orm = 'typeorm';
1074
+
1075
+ // Auth
1076
+ if (techDoc.match(/Clerk/i)) stack.auth = 'clerk';
1077
+ else if (techDoc.match(/NextAuth|Auth\.js/i)) stack.auth = 'nextauth';
1078
+ else if (techDoc.match(/Supabase\s+Auth/i)) stack.auth = 'supabase';
1079
+ else if (techDoc.match(/Firebase\s+Auth/i)) stack.auth = 'firebase';
1080
+
1081
+ // Payments
1082
+ if (techDoc.match(/Stripe/i)) stack.payments = 'stripe';
1083
+ else if (techDoc.match(/Paddle/i)) stack.payments = 'paddle';
1084
+ else if (techDoc.match(/Lemon\s*Squeezy/i)) stack.payments = 'lemonsqueezy';
1085
+
1086
+ // Hosting
1087
+ if (techDoc.match(/Vercel/i)) stack.hosting = 'vercel';
1088
+ else if (techDoc.match(/Netlify/i)) stack.hosting = 'netlify';
1089
+ else if (techDoc.match(/Railway/i)) stack.hosting = 'railway';
1090
+ else if (techDoc.match(/Fly\.io/i)) stack.hosting = 'fly';
1091
+ else if (techDoc.match(/AWS/i)) stack.hosting = 'aws';
1092
+
1093
+ return stack;
1094
+ }
1095
+
1096
+ /**
1097
+ * Extract MVP features from PRD
1098
+ */
1099
+ function extractMVPFeatures(docs) {
1100
+ const prdDoc = docs['PRD'] || docs['prd'];
1101
+ if (!prdDoc) return [];
1102
+
1103
+ const features = [];
1104
+
1105
+ // Look for MVP Features section
1106
+ const mvpSection = extractSection(prdDoc, 'MVP\\s+Features|P1\\s+.*Features|Must\\s+Ship', { maxLength: 5000 });
1107
+ if (mvpSection) {
1108
+ // Extract feature names from headers like "#### F-001: Feature Name"
1109
+ const featureHeaders = mvpSection.match(/^#{1,4}\s+F-\d+:\s*(.+)$/gm);
1110
+ if (featureHeaders && featureHeaders.length > 0) {
1111
+ featureHeaders.forEach(h => {
1112
+ const match = h.match(/F-\d+:\s*(.+)/);
1113
+ if (match) features.push(match[1].trim());
1114
+ });
1115
+ }
1116
+ }
1117
+
1118
+ // Also look for Feature Requirements section
1119
+ const featureReqSection = extractSection(prdDoc, 'Feature\\s+Requirements', { maxLength: 5000 });
1120
+ if (featureReqSection && features.length === 0) {
1121
+ // Look for **FR-XX:** patterns
1122
+ const frMatches = featureReqSection.match(/\*{0,2}FR-\d+:?\*{0,2}\s*(.+)/g);
1123
+ if (frMatches) {
1124
+ frMatches.forEach(m => {
1125
+ const match = m.match(/FR-\d+:?\*{0,2}\s*(.+)/);
1126
+ if (match) features.push(match[1].replace(/\*+/g, '').trim());
1127
+ });
1128
+ }
1129
+ }
1130
+
1131
+ // Fallback: look for numbered Core User Journeys
1132
+ if (features.length === 0) {
1133
+ const journeySection = extractSection(prdDoc, 'Core\\s+User\\s+Journeys|User\\s+Journeys', { maxLength: 3000 });
1134
+ if (journeySection) {
1135
+ const journeyHeaders = journeySection.match(/^#{1,4}\s+\d+\.\d+\s+Journey\s+\d+\s*[—\-]\s*(.+)$/gm);
1136
+ if (journeyHeaders) {
1137
+ journeyHeaders.forEach(h => {
1138
+ const match = h.match(/Journey\s+\d+\s*[—\-]\s*(.+)/);
1139
+ if (match) features.push(match[1].trim());
1140
+ });
1141
+ }
1142
+ }
1143
+ }
1144
+
1145
+ // Limit to top 15 features
1146
+ return features.slice(0, 15);
1147
+ }
1148
+
1149
+ /**
1150
+ * Extract target users/personas from documents
1151
+ */
1152
+ function extractTargetUsers(docs) {
1153
+ const visionDoc = docs['VISION'] || docs['vision'];
1154
+ const prdDoc = docs['PRD'] || docs['prd'];
1155
+ const audienceDoc = docs['AUDIENCE'] || docs['audience'];
1156
+
1157
+ const personas = [];
1158
+
1159
+ // Try AUDIENCE doc first - look for "## X.X Persona PX — Name" pattern
1160
+ if (audienceDoc) {
1161
+ const personaMatches = audienceDoc.match(/^#{1,4}\s+\d+\.\d+\s+Persona\s+P?\d+\s*[—\-]\s*(.+)$/gm);
1162
+ if (personaMatches) {
1163
+ personaMatches.forEach(m => {
1164
+ const match = m.match(/[—\-]\s*(.+)/);
1165
+ if (match && match[1]) personas.push(match[1].trim());
1166
+ });
1167
+ }
1168
+ // Also look for ICP definitions like "## 3.1 ICP-A: AI Agency Operator"
1169
+ if (personas.length === 0) {
1170
+ const icpMatches = audienceDoc.match(/^#{1,4}\s+\d+\.\d+\s+ICP-[A-Z]:\s*(.+?)(?:\s*\(|$)/gm);
1171
+ if (icpMatches) {
1172
+ icpMatches.forEach(m => {
1173
+ const match = m.match(/ICP-[A-Z]:\s*(.+?)(?:\s*\(|$)/);
1174
+ if (match && match[1]) personas.push(match[1].trim());
1175
+ });
1176
+ }
1177
+ }
1178
+ }
1179
+
1180
+ // Try VISION
1181
+ if (visionDoc && personas.length === 0) {
1182
+ const targetSection = extractSection(visionDoc, 'Target\\s+Users|Who\\s+This\\s+Is\\s+For', { maxLength: 2000 });
1183
+ if (targetSection) {
1184
+ // Look for bold text patterns like "- **AI Agencies & AI Arbitrage Specialists**"
1185
+ const boldMatches = targetSection.match(/^\s*[\-\*]\s*\*{2}([^*]+)\*{2}/gm);
1186
+ if (boldMatches) {
1187
+ boldMatches.forEach(m => {
1188
+ const match = m.match(/\*{2}([^*]+)\*{2}/);
1189
+ if (match && match[1]) personas.push(match[1].trim());
1190
+ });
1191
+ }
1192
+ }
1193
+ }
1194
+
1195
+ // Try PRD
1196
+ if (prdDoc && personas.length === 0) {
1197
+ const personaSection = extractSection(prdDoc, 'Personas|Who\\s+We\\s+Serve', { maxLength: 2000 });
1198
+ if (personaSection) {
1199
+ const personaMatches = personaSection.match(/^#{1,4}\s+\d+\.\d+\s+Persona\s+[A-Z]\s*[—\-]\s*(.+)$/gm);
1200
+ if (personaMatches) {
1201
+ personaMatches.forEach(m => {
1202
+ const match = m.match(/[—\-]\s*(.+)/);
1203
+ if (match && match[1]) personas.push(match[1].trim());
1204
+ });
1205
+ }
1206
+ }
1207
+ }
1208
+
1209
+ return personas.slice(0, 6);
1210
+ }
1211
+
1212
+ /**
1213
+ * Extract database entities from technical spec
1214
+ */
1215
+ function extractDatabaseEntities(docs) {
1216
+ const techDoc = docs['TECHNICAL_SPEC'] || docs['TECHNICAL-SPEC'] || docs['technical_spec'];
1217
+ if (!techDoc) return [];
1218
+
1219
+ const entities = [];
1220
+
1221
+ // Strategy 1: Look specifically for "Key tables" or "Data Model" sections
1222
+ // This avoids capturing run types like `interactive_chat` which are not tables
1223
+ const tableSection = extractSection(techDoc, 'Key\\s+tables|Data\\s+Model|Database\\s+Schema', { maxLength: 3000 });
1224
+ if (tableSection) {
1225
+ // Extract backticked table names from bullet points in this specific section
1226
+ const bulletTableMatches = tableSection.match(/^\s*[\-\*]\s*`(\w+)`/gm);
1227
+ if (bulletTableMatches) {
1228
+ bulletTableMatches.forEach(m => {
1229
+ const match = m.match(/`(\w+)`/);
1230
+ if (match && match[1] && match[1].length > 2) {
1231
+ const word = match[1].toLowerCase();
1232
+ // Filter out common non-entity words and known run types
1233
+ const skipWords = [
1234
+ 'and', 'the', 'for', 'with', 'from', 'optional', 'static', 'primary', 'key', 'index', 'indexes',
1235
+ 'interactive_chat', 'widget_chat', 'lead_qualify_async', 'sequence_send_async', 'batch_eval'
1236
+ ];
1237
+ if (!skipWords.includes(word)) {
1238
+ entities.push(match[1]);
1239
+ }
1240
+ }
1241
+ });
1242
+ }
1243
+ }
1244
+
1245
+ // Strategy 2: Extract model names from Prisma schema blocks
1246
+ if (entities.length === 0) {
1247
+ const prismaModels = techDoc.match(/model\s+(\w+)\s*\{/g);
1248
+ if (prismaModels) {
1249
+ prismaModels.forEach(m => {
1250
+ const match = m.match(/model\s+(\w+)/);
1251
+ if (match && match[1]) {
1252
+ entities.push(match[1]);
1253
+ }
1254
+ });
1255
+ }
1256
+ }
1257
+
1258
+ // Strategy 3: Fallback - look for backticked snake_case names in general text
1259
+ // but only if they look like table names (contain underscore or end with 's')
1260
+ if (entities.length === 0) {
1261
+ const generalMatches = techDoc.match(/`([a-z][a-z_]*(?:_[a-z]+)+)`/g);
1262
+ if (generalMatches) {
1263
+ generalMatches.forEach(m => {
1264
+ const match = m.match(/`([a-z_]+)`/);
1265
+ if (match && match[1] && match[1].length > 4) {
1266
+ const word = match[1].toLowerCase();
1267
+ // Skip known run types
1268
+ const runTypes = ['interactive_chat', 'widget_chat', 'lead_qualify_async', 'sequence_send_async', 'batch_eval'];
1269
+ if (!runTypes.includes(word) && !entities.includes(match[1])) {
1270
+ entities.push(match[1]);
1271
+ }
1272
+ }
1273
+ });
1274
+ }
1275
+ }
1276
+
1277
+ // Deduplicate and return
1278
+ return [...new Set(entities)].slice(0, 25);
1279
+ }
1280
+
1281
+ /**
1282
+ * Extract implementation phases from ROADMAP
1283
+ */
1284
+ function extractImplementationPhases(docs) {
1285
+ const roadmapDoc = docs['ROADMAP'] || docs['roadmap'];
1286
+ if (!roadmapDoc) return [];
1287
+
1288
+ const phases = [];
1289
+ const seenPhases = new Set();
1290
+
1291
+ // Prioritize full phase sections: "## X. Phase N —" (with section number)
1292
+ // These have the actual deliverables, unlike brief summaries
1293
+ const fullPhaseMatches = roadmapDoc.match(/^##\s+\d+\.\s+Phase\s+\d+\s*[—\-]+\s*(.+)$/gm);
1294
+
1295
+ if (fullPhaseMatches) {
1296
+ fullPhaseMatches.forEach(m => {
1297
+ const match = m.match(/Phase\s+(\d+)\s*[—\-]+\s*(.+)$/);
1298
+ if (match && match[1] && match[2]) {
1299
+ const phaseNum = match[1];
1300
+ if (seenPhases.has(phaseNum)) return;
1301
+ seenPhases.add(phaseNum);
1302
+
1303
+ // Clean up the name - remove parenthetical suffix
1304
+ const phaseName = match[2].trim().replace(/\s*\([^)]+\)\s*$/, '').trim();
1305
+
1306
+ // Try to get deliverables for this phase
1307
+ const deliverables = [];
1308
+
1309
+ // Find the full section for this specific phase (## X. Phase N — until next ## X. Phase)
1310
+ const phaseRegex = new RegExp(`##\\s+\\d+\\.\\s+Phase\\s+${phaseNum}\\s*[—\\-][\\s\\S]*?(?=##\\s+\\d+\\.\\s+Phase\\s+\\d+|$)`, 'i');
1311
+ const phaseSectionMatch = roadmapDoc.match(phaseRegex);
1312
+
1313
+ if (phaseSectionMatch) {
1314
+ const phaseContent = phaseSectionMatch[0];
1315
+
1316
+ // Look for "### X.X Deliverables" subsection
1317
+ const deliverablesRegex = /###\s+[\d.]+\s*Deliverables[\s\S]*?(?=###|##|$)/i;
1318
+ const deliverablesMatch = phaseContent.match(deliverablesRegex);
1319
+
1320
+ if (deliverablesMatch) {
1321
+ // Extract bullet points
1322
+ const bullets = deliverablesMatch[0].match(/^[\-\*]\s+(.+)$/gm);
1323
+ if (bullets) {
1324
+ bullets.slice(0, 6).forEach(b => {
1325
+ const text = b.replace(/^[\-\*]\s+/, '').trim();
1326
+ if (text.length > 3) deliverables.push(text);
1327
+ });
1328
+ }
1329
+ }
1330
+
1331
+ // If no deliverables found, try "MVP feature set" or "feature set"
1332
+ if (deliverables.length === 0) {
1333
+ // Look for #### A) B) C) patterns anywhere in phase content
1334
+ const featureHeaders = phaseContent.match(/^####\s+[A-Z]\)\s+(.+)$/gm);
1335
+ if (featureHeaders && featureHeaders.length > 0) {
1336
+ featureHeaders.slice(0, 6).forEach(h => {
1337
+ const hMatch = h.match(/####\s+[A-Z]\)\s+(.+)/);
1338
+ if (hMatch && hMatch[1]) {
1339
+ // Clean up the title - remove trailing parenthetical like "(prebuilt)"
1340
+ const title = hMatch[1].trim();
1341
+ deliverables.push(title);
1342
+ }
1343
+ });
1344
+ }
1345
+ }
1346
+
1347
+ // Strategy 3: Look for Exit Criteria items
1348
+ if (deliverables.length === 0) {
1349
+ const exitCriteriaRegex = /###\s+[\d.]+\s*Exit\s+Criteria[\s\S]*?(?=###|##|$)/i;
1350
+ const exitMatch = phaseContent.match(exitCriteriaRegex);
1351
+ if (exitMatch) {
1352
+ // Extract checkmark items like "- ✅ Description"
1353
+ const checkItems = exitMatch[0].match(/^[\-\*]\s+[✅✓]\s*(.+)$/gm);
1354
+ if (checkItems) {
1355
+ checkItems.slice(0, 5).forEach(item => {
1356
+ const text = item.replace(/^[\-\*]\s+[✅✓]\s*/, '').trim();
1357
+ if (text.length > 5) deliverables.push(text);
1358
+ });
1359
+ }
1360
+ }
1361
+ }
1362
+
1363
+ // Strategy 3: Look for "### X.X Phase goal" and extract key items
1364
+ if (deliverables.length === 0) {
1365
+ const goalRegex = /###\s+[\d.]+\s*(?:Phase\s+)?goal[\s\S]*?(?=###|$)/i;
1366
+ const goalMatch = phaseContent.match(goalRegex);
1367
+ if (goalMatch) {
1368
+ // Extract bullet points or bold items
1369
+ const bullets = goalMatch[0].match(/^[\-\*]\s+(.+)$/gm);
1370
+ if (bullets) {
1371
+ bullets.slice(0, 4).forEach(b => {
1372
+ const text = b.replace(/^[\-\*]\s+/, '').trim();
1373
+ if (text.length > 3) deliverables.push(text);
1374
+ });
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+
1380
+ phases.push({
1381
+ phase: phaseNum,
1382
+ name: phaseName,
1383
+ deliverables
1384
+ });
1385
+ }
1386
+ });
1387
+ }
1388
+
1389
+ return phases.slice(0, 5);
1390
+ }
1391
+
1392
+ /**
1393
+ * Extract third-party integrations from technical spec
1394
+ */
1395
+ function extractIntegrations(docs) {
1396
+ const techDoc = docs['TECHNICAL_SPEC'] || docs['TECHNICAL-SPEC'] || docs['technical_spec'];
1397
+ const prdDoc = docs['PRD'] || docs['prd'];
1398
+ const allDocs = (techDoc || '') + '\n' + (prdDoc || '');
1399
+
1400
+ const integrations = {
1401
+ voice: [],
1402
+ email: [],
1403
+ calendar: [],
1404
+ crm: [],
1405
+ payments: [],
1406
+ auth: [],
1407
+ ai: [],
1408
+ other: []
1409
+ };
1410
+
1411
+ // Voice/Telephony
1412
+ if (allDocs.match(/\bTwilio\b/i)) integrations.voice.push('Twilio');
1413
+ if (allDocs.match(/\bRetell\b/i)) integrations.voice.push('Retell');
1414
+ if (allDocs.match(/\bVapi\b/i)) integrations.voice.push('Vapi');
1415
+ if (allDocs.match(/\bElevenLabs\b/i)) integrations.voice.push('ElevenLabs');
1416
+ if (allDocs.match(/\bDeepgram\b/i)) integrations.voice.push('Deepgram');
1417
+
1418
+ // Email
1419
+ if (allDocs.match(/\bGmail\b/i)) integrations.email.push('Gmail');
1420
+ if (allDocs.match(/\bSendGrid\b/i)) integrations.email.push('SendGrid');
1421
+ if (allDocs.match(/\bResend\b/i)) integrations.email.push('Resend');
1422
+ if (allDocs.match(/\bPostmark\b/i)) integrations.email.push('Postmark');
1423
+
1424
+ // Calendar
1425
+ if (allDocs.match(/Google\s*Calendar/i)) integrations.calendar.push('Google Calendar');
1426
+ if (allDocs.match(/\bCalendly\b/i)) integrations.calendar.push('Calendly');
1427
+ if (allDocs.match(/\bCal\.com\b/i)) integrations.calendar.push('Cal.com');
1428
+
1429
+ // CRM
1430
+ if (allDocs.match(/\bHubSpot\b/i)) integrations.crm.push('HubSpot');
1431
+ if (allDocs.match(/\bSalesforce\b/i)) integrations.crm.push('Salesforce');
1432
+ if (allDocs.match(/\bAttio\b/i)) integrations.crm.push('Attio');
1433
+ if (allDocs.match(/\bPipedrive\b/i)) integrations.crm.push('Pipedrive');
1434
+
1435
+ // Payments
1436
+ if (allDocs.match(/\bStripe\b/i)) integrations.payments.push('Stripe');
1437
+ if (allDocs.match(/\bPaddle\b/i)) integrations.payments.push('Paddle');
1438
+
1439
+ // Auth
1440
+ if (allDocs.match(/\bClerk\b/i)) integrations.auth.push('Clerk');
1441
+ if (allDocs.match(/\bNextAuth\b|Auth\.js/i)) integrations.auth.push('NextAuth');
1442
+ if (allDocs.match(/\bSupabase\s*Auth/i)) integrations.auth.push('Supabase Auth');
1443
+
1444
+ // AI/LLM
1445
+ if (allDocs.match(/\bOpenAI\b|GPT-4/i)) integrations.ai.push('OpenAI');
1446
+ if (allDocs.match(/\bAnthropic\b|Claude/i)) integrations.ai.push('Anthropic');
1447
+ if (allDocs.match(/\bGemini\b/i)) integrations.ai.push('Google Gemini');
1448
+
1449
+ // Other
1450
+ if (allDocs.match(/\bZapier\b/i)) integrations.other.push('Zapier');
1451
+ if (allDocs.match(/\bSlack\b/i)) integrations.other.push('Slack');
1452
+ if (allDocs.match(/\bWhatsApp\b/i)) integrations.other.push('WhatsApp');
1453
+
1454
+ return integrations;
1455
+ }
1456
+
1457
+ /**
1458
+ * Extract API routes from PRD/technical spec
1459
+ */
1460
+ function extractAPIRoutes(docs) {
1461
+ const techDoc = docs['TECHNICAL_SPEC'] || docs['TECHNICAL-SPEC'] || docs['technical_spec'];
1462
+
1463
+ const routes = [];
1464
+
1465
+ if (techDoc) {
1466
+ // Look for Application Structure section with app/ routes
1467
+ const appStructureSection = extractSection(techDoc, 'Application\\s+Structure|Directory\\s+Structure', { maxLength: 3000 });
1468
+
1469
+ if (appStructureSection) {
1470
+ // Extract page routes like "- `/dashboard`" or "- `/agents`" or "- `/agents/[id]`"
1471
+ const pageRoutes = appStructureSection.match(/[-*]\s*`\/([\w\/\[\]]+)`/g);
1472
+ if (pageRoutes) {
1473
+ pageRoutes.forEach(r => {
1474
+ const match = r.match(/`(\/[\w\/\[\]]+)`/);
1475
+ if (match && match[1]) {
1476
+ const route = match[1];
1477
+ // Skip if it looks like a citation URL
1478
+ if (!route.includes('docs/') && !route.includes('guides/')) {
1479
+ if (!routes.includes(route)) routes.push(route);
1480
+ }
1481
+ }
1482
+ });
1483
+ }
1484
+
1485
+ // Extract API route handler descriptions like "agents CRUD", "runs endpoints"
1486
+ const apiHandlers = appStructureSection.match(/app\/api\/.*?handlers?:[\s\S]*?(?=###|##|$)/i);
1487
+ if (apiHandlers) {
1488
+ const handlerLines = apiHandlers[0].match(/[-*]\s+(\w[\w\s]+)(?:endpoints?|CRUD|callbacks?|handlers?)?/g);
1489
+ if (handlerLines) {
1490
+ handlerLines.forEach(line => {
1491
+ const match = line.match(/[-*]\s+([\w\s]+)/);
1492
+ if (match && match[1]) {
1493
+ const name = match[1].trim().split(/\s+/)[0].toLowerCase();
1494
+ if (name.length > 2 && !['auth', 'the', 'for', 'and'].includes(name)) {
1495
+ const route = `/api/${name}`;
1496
+ if (!routes.includes(route)) routes.push(route);
1497
+ }
1498
+ }
1499
+ });
1500
+ }
1501
+ }
1502
+ }
1503
+
1504
+ // Also look for explicit /api/ routes in backticks
1505
+ const explicitRoutes = techDoc.match(/`\/api\/[\w\/\[\]-]+`/g);
1506
+ if (explicitRoutes) {
1507
+ explicitRoutes.forEach(r => {
1508
+ const route = r.replace(/`/g, '');
1509
+ // Skip citation URLs
1510
+ if (!route.includes('docs/') && !route.includes('guides/')) {
1511
+ if (!routes.includes(route)) routes.push(route);
1512
+ }
1513
+ });
1514
+ }
1515
+ }
1516
+
1517
+ return routes.slice(0, 20);
1518
+ }
1519
+
1520
+ /**
1521
+ * Extract core architecture components from technical spec
1522
+ */
1523
+ function extractArchitecture(docs) {
1524
+ const techDoc = docs['TECHNICAL_SPEC'] || docs['TECHNICAL-SPEC'] || docs['technical_spec'];
1525
+ if (!techDoc) return [];
1526
+
1527
+ const components = [];
1528
+
1529
+ // Strategy 1: Look specifically for "Core system components" section first
1530
+ // This is more specific than "Architecture" which may be a high-level overview
1531
+ const coreSection = extractSection(techDoc, 'Core\\s+system\\s+components', { maxLength: 1500 });
1532
+ if (coreSection) {
1533
+ // Extract numbered items like "1) Web App (Next.js)"
1534
+ const compMatches = coreSection.match(/^\s*\d+\)\s*(.+)$/gm);
1535
+ if (compMatches) {
1536
+ compMatches.forEach(m => {
1537
+ const match = m.match(/\d+\)\s*(.+)/);
1538
+ if (match && match[1]) {
1539
+ components.push(match[1].trim());
1540
+ }
1541
+ });
1542
+ }
1543
+ }
1544
+
1545
+ // Strategy 2: If no components found, look within "Architectural Overview" for subsections
1546
+ if (components.length === 0) {
1547
+ // Search the entire doc for numbered component patterns within Architecture context
1548
+ const archMatch = techDoc.match(/(?:Architectural|Architecture)\s+Overview[\s\S]*?Core\s+system\s+components[\s\S]*?(?=##\s+\d+\.|$)/i);
1549
+ if (archMatch) {
1550
+ const compMatches = archMatch[0].match(/^\s*\d+\)\s*(.+)$/gm);
1551
+ if (compMatches) {
1552
+ compMatches.forEach(m => {
1553
+ const match = m.match(/\d+\)\s*(.+)/);
1554
+ if (match && match[1]) {
1555
+ components.push(match[1].trim());
1556
+ }
1557
+ });
1558
+ }
1559
+ }
1560
+ }
1561
+
1562
+ // Strategy 3: Look for bullet-point architecture components
1563
+ if (components.length === 0) {
1564
+ const archSection = extractSection(techDoc, 'Architecture|System\\s+Components', { maxLength: 2000 });
1565
+ if (archSection) {
1566
+ // Look for bold component names like "- **Component Name**"
1567
+ const boldMatches = archSection.match(/^\s*[\-\*]\s+\*{2}([^*]+)\*{2}/gm);
1568
+ if (boldMatches) {
1569
+ boldMatches.forEach(m => {
1570
+ const match = m.match(/\*{2}([^*]+)\*{2}/);
1571
+ if (match && match[1] && match[1].length > 3) {
1572
+ components.push(match[1].trim());
1573
+ }
1574
+ });
1575
+ }
1576
+ }
1577
+ }
1578
+
1579
+ return components.slice(0, 10);
1580
+ }
1581
+
1582
+ /**
1583
+ * Generate SEED.md content from preseed documents
1584
+ */
1585
+ function generateSeedFromPreseed(docs, preseedConfig) {
1586
+ // Extract from documents (preferred) with config fallback
1587
+ const projectName = extractProjectName(docs) || preseedConfig.identity?.name || 'My Project';
1588
+ const tagline = extractTagline(docs) || preseedConfig.identity?.tagline || '';
1589
+ const problem = extractProblem(docs) || preseedConfig.problem?.statement || '';
1590
+ const solution = extractSolution(docs) || preseedConfig.solution?.description || '';
1591
+ const techStack = extractTechStack(docs);
1592
+ const mvpFeatures = extractMVPFeatures(docs);
1593
+ const targetUsers = extractTargetUsers(docs);
1594
+ const dbEntities = extractDatabaseEntities(docs);
1595
+ const phases = extractImplementationPhases(docs);
1596
+ const integrations = extractIntegrations(docs);
1597
+ const apiRoutes = extractAPIRoutes(docs);
1598
+ const architecture = extractArchitecture(docs);
1599
+
1600
+ // Build tech stack YAML with detected values
1601
+ let stackYaml = `stack:
1602
+ framework: ${techStack.framework}
1603
+ language: ${techStack.language}
1604
+ database: ${techStack.database}
1605
+ hosting: ${techStack.hosting}
1606
+
1607
+ frontend:
1608
+ uiLibrary: ${techStack.uiLibrary}
1609
+ styling: ${techStack.styling}
1610
+
1611
+ backend:
1612
+ orm: ${techStack.orm}
1613
+ auth: ${techStack.auth}
1614
+ payments: ${techStack.payments}`;
1615
+
1616
+ if (techStack.vector) {
1617
+ stackYaml += `
1618
+ vector: ${techStack.vector}`;
1619
+ }
1620
+
1621
+ // Build features section
1622
+ let featuresSection = '';
1623
+ if (mvpFeatures.length > 0) {
1624
+ featuresSection = mvpFeatures.map((f, i) => `${i + 1}. ${f}`).join('\n');
1625
+ } else {
1626
+ featuresSection = `1. User authentication and workspace management
1627
+ 2. Core dashboard with analytics
1628
+ 3. Data management and CRUD operations
1629
+ 4. API endpoints for integrations
1630
+ 5. Admin interface and settings`;
1631
+ }
1632
+
1633
+ // Build target users section
1634
+ let usersSection = '';
1635
+ if (targetUsers.length > 0) {
1636
+ usersSection = targetUsers.map(u => `- ${u}`).join('\n');
1637
+ }
1638
+
1639
+ // Build database entities section
1640
+ let entitiesSection = '';
1641
+ if (dbEntities.length > 0) {
1642
+ entitiesSection = dbEntities.map(e => `- \`${e}\``).join('\n');
1643
+ }
1644
+
1645
+ // Build architecture section
1646
+ let architectureSection = '';
1647
+ if (architecture.length > 0) {
1648
+ architectureSection = architecture.map((c, i) => `${i + 1}. ${c}`).join('\n');
1649
+ }
1650
+
1651
+ // Build integrations section
1652
+ let integrationsSection = '';
1653
+ const intCats = Object.entries(integrations).filter(([_, v]) => v.length > 0);
1654
+ if (intCats.length > 0) {
1655
+ integrationsSection = intCats.map(([cat, services]) => {
1656
+ const catName = cat.charAt(0).toUpperCase() + cat.slice(1);
1657
+ return `**${catName}:** ${services.join(', ')}`;
1658
+ }).join('\n');
1659
+ }
1660
+
1661
+ // Build phases section
1662
+ let phasesSection = '';
1663
+ if (phases.length > 0) {
1664
+ phasesSection = phases.map(p => {
1665
+ let section = `### Phase ${p.phase}: ${p.name}`;
1666
+ if (p.deliverables.length > 0) {
1667
+ section += '\n' + p.deliverables.map(d => `- ${d}`).join('\n');
1668
+ }
1669
+ return section;
1670
+ }).join('\n\n');
1671
+ }
1672
+
1673
+ // Build API routes section
1674
+ let apiRoutesSection = '';
1675
+ if (apiRoutes.length > 0) {
1676
+ apiRoutesSection = apiRoutes.map(r => `- \`${r}\``).join('\n');
1677
+ }
1678
+
1679
+ const content = `# ${projectName}
1680
+
1681
+ ${tagline ? `> ${tagline}` : ''}
1682
+
1683
+ ## Overview
1684
+
1685
+ ${problem ? `**The Problem:** ${problem}\n\n` : ''}${solution ? `**Our Solution:** ${solution}` : ''}
1686
+
1687
+ ---
1688
+
1689
+ ## Tech Stack
1690
+
1691
+ \`\`\`yaml
1692
+ ${stackYaml}
1693
+ \`\`\`
1694
+
1695
+ ---
1696
+ ${architecture.length > 0 ? `
1697
+ ## Architecture
1698
+
1699
+ ${architectureSection}
1700
+
1701
+ ---
1702
+ ` : ''}
1703
+ ${intCats.length > 0 ? `
1704
+ ## Integrations
1705
+
1706
+ ${integrationsSection}
1707
+
1708
+ ---
1709
+ ` : ''}
1710
+ ## MVP Features
1711
+
1712
+ ${featuresSection}
1713
+
1714
+ ---
1715
+ ${phases.length > 0 ? `
1716
+ ## Implementation Phases
1717
+
1718
+ ${phasesSection}
1719
+
1720
+ ---
1721
+ ` : ''}
1722
+ ${targetUsers.length > 0 ? `
1723
+ ## Target Users
1724
+
1725
+ ${usersSection}
1726
+
1727
+ ---
1728
+ ` : ''}
1729
+ ${dbEntities.length > 0 ? `
1730
+ ## Core Data Entities
1731
+
1732
+ ${entitiesSection}
1733
+
1734
+ ---
1735
+ ` : ''}
1736
+ ${apiRoutes.length > 0 ? `
1737
+ ## API Routes
1738
+
1739
+ ${apiRoutesSection}
1740
+
1741
+ ---
1742
+ ` : ''}
1743
+ ## Development Standards
1744
+
1745
+ ### Code Style
1746
+ - Use ${techStack.language === 'typescript' ? 'TypeScript' : 'JavaScript'} for all new code
1747
+ - Follow existing naming conventions in the codebase
1748
+ - Keep files focused and under 300 lines when possible
1749
+
1750
+ ### Best Practices
1751
+ ${techStack.framework === 'nextjs' ? `- Use Server Components by default
1752
+ - Prefer Server Actions over API routes for mutations` : `- Follow ${techStack.framework} best practices`}
1753
+ - Use Zod for all input validation
1754
+ - Never expose API keys to client-side code
1755
+ - Write tests for new features
1756
+
1757
+ ### Git Commits
1758
+ - Use conventional format: \`feat:\`, \`fix:\`, \`docs:\`, \`refactor:\`
1759
+ - Keep commits focused and atomic
1760
+ - Never commit sensitive data or API keys
1761
+
1762
+ ---
1763
+
1764
+ ## Project Structure
1765
+
1766
+ \`\`\`
1767
+ ${projectName.toLowerCase().replace(/\s+/g, '-')}/
1768
+ ├── app/ # Next.js App Router
1769
+ │ ├── (auth)/ # Auth pages${techStack.auth === 'clerk' ? ' (Clerk)' : ''}
1770
+ │ ├── (dashboard)/ # Main dashboard
1771
+ │ ├── (marketing)/ # Landing pages
1772
+ │ └── api/ # API routes
1773
+ ├── components/
1774
+ │ ├── ui/ # UI components${techStack.uiLibrary === 'shadcn' ? ' (shadcn/ui)' : ''}
1775
+ │ └── [feature]/ # Feature components
1776
+ ├── lib/
1777
+ │ ├── db.ts # Database client${techStack.orm === 'prisma' ? ' (Prisma)' : ''}
1778
+ │ ├── auth.ts # Auth utilities
1779
+ │ └── utils.ts # Helpers
1780
+ ├── prisma/
1781
+ │ └── schema.prisma # Database schema
1782
+ └── public/ # Static assets
1783
+ \`\`\`
1784
+
1785
+ ---
1786
+
1787
+ ## Source Documents
1788
+
1789
+ This SEED.md was synthesized from:
1790
+ ${Object.keys(docs).map(name => `- \`.bootspring/preseed/${name}.md\``).join('\n')}
1791
+
1792
+ ---
1793
+
1794
+ *Generated with [Bootspring](https://bootspring.com)*
1795
+ `;
1796
+
1797
+ return content;
1798
+ }
1799
+
1800
+ /**
1801
+ * Generate AI enhancement prompt
1802
+ */
1803
+ function generateEnhancementPrompt(docs, preseedConfig) {
1804
+ return `# Enhance SEED.md
1805
+
1806
+ I've created a basic SEED.md from my preseed documents. Please review and enhance it with:
1807
+
1808
+ 1. **More specific tech stack recommendations** based on my requirements
1809
+ 2. **Detailed file structure** for the project
1810
+ 3. **Implementation order** - which features to build first
1811
+ 4. **API endpoints** we'll need
1812
+ 5. **Database schema** suggestions
1813
+ 6. **Third-party services** recommendations
1814
+
1815
+ Here are my preseed documents for context:
1816
+
1817
+ ${Object.entries(docs).map(([name, content]) => `## ${name}\n\n${content}`).join('\n\n---\n\n')}
1818
+
1819
+ ---
1820
+
1821
+ Please provide an enhanced SEED.md that a developer (or AI assistant) can follow to build this project from scratch.`;
1822
+ }
1823
+
407
1824
  /**
408
1825
  * Parse SEED.md file
409
1826
  */
@@ -443,8 +1860,12 @@ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
443
1860
  bootspring seed <command> [options]
444
1861
 
445
1862
  ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
1863
+ ${utils.COLORS.cyan}setup${utils.COLORS.reset} Create .bootspring/inputs folder structure
446
1864
  ${utils.COLORS.cyan}init${utils.COLORS.reset} Run questionnaire, create SEED.md
447
- ${utils.COLORS.cyan}scaffold${utils.COLORS.reset} Generate project structure
1865
+ ${utils.COLORS.cyan}synthesize${utils.COLORS.reset} Create SEED.md from preseed documents ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
1866
+ ${utils.COLORS.cyan}generate${utils.COLORS.reset} Ingest input files and generate documents ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
1867
+ ${utils.COLORS.cyan}scaffold${utils.COLORS.reset} Generate project structure ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
1868
+ ${utils.COLORS.cyan}build${utils.COLORS.reset} Build from seed docs (--loop for continuous) ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
448
1869
  ${utils.COLORS.cyan}update${utils.COLORS.reset} Re-run questionnaire, update SEED.md
449
1870
  ${utils.COLORS.cyan}status${utils.COLORS.reset} Show current configuration (default)
450
1871
  ${utils.COLORS.cyan}export${utils.COLORS.reset} Export config as JSON/YAML
@@ -458,15 +1879,30 @@ ${utils.COLORS.bold}Scaffold Options:${utils.COLORS.reset}
458
1879
  --from-config Use bootspring.config.js instead of SEED.md
459
1880
  --dry-run Show plan without creating files
460
1881
 
1882
+ ${utils.COLORS.bold}Build Options:${utils.COLORS.reset}
1883
+ --loop Start continuous build loop until MVP complete
1884
+ --iterations=<n> Max iterations for loop (default: 50)
1885
+ --force Reinitialize existing build state
1886
+ --live Stream AI output in real-time
1887
+ --verbose Show detailed output
1888
+
461
1889
  ${utils.COLORS.bold}Export Options:${utils.COLORS.reset}
462
1890
  --format=<fmt> Output format: json (default), yaml
463
1891
  --output=<file> Write to file instead of stdout
464
1892
 
465
1893
  ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
466
- bootspring seed init --preset=startup
467
- bootspring seed scaffold --preset=nextjs
468
- bootspring seed scaffold --dry-run
469
- bootspring seed export --format=yaml --output=seed.yaml
1894
+ bootspring seed setup # Create input folder structure
1895
+ bootspring seed generate # Process input files
1896
+ bootspring seed init --preset=startup # Full questionnaire
1897
+ ${utils.COLORS.green}bootspring seed synthesize${utils.COLORS.reset} # Create SEED.md from preseed docs
1898
+ bootspring seed scaffold --preset=nextjs # Generate project
1899
+ ${utils.COLORS.green}bootspring seed build --loop${utils.COLORS.reset} # Start autonomous build
1900
+ bootspring seed export --format=yaml # Export config
1901
+
1902
+ ${utils.COLORS.bold}Complete Workflow:${utils.COLORS.reset}
1903
+ 1. ${utils.COLORS.cyan}bootspring preseed start${utils.COLORS.reset} # Capture your idea
1904
+ 2. ${utils.COLORS.cyan}bootspring seed synthesize${utils.COLORS.reset} # Create SEED.md from preseed
1905
+ 3. ${utils.COLORS.cyan}bootspring seed scaffold${utils.COLORS.reset} # Generate project code
470
1906
  `);
471
1907
  }
472
1908