@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/preseed.js ADDED
@@ -0,0 +1,2302 @@
1
+ /**
2
+ * Bootspring Preseed Command
3
+ * Generate foundational documents from minimal input
4
+ *
5
+ * Commands:
6
+ * start Smart entry point - detects context & guides you
7
+ * setup Create context input folders for early docs
8
+ * init [--preset] Initialize preseed with interactive Q&A
9
+ * generate Generate/regenerate all documents
10
+ * sync Sync documents with codebase changes
11
+ * status Show preseed status
12
+ * update <path> Update a specific config value
13
+ * export Export preseed config
14
+ * pull Download documents from dashboard
15
+ * push Upload documents to dashboard
16
+ * merge Merge source docs from context folders
17
+ * wizard Run full guided wizard
18
+ * workflow start Begin guided approval workflow
19
+ * workflow resume Continue workflow from last point
20
+ * workflow status Show workflow progress
21
+ * workflow reset Reset workflow state
22
+ * doc <type> approve Approve a document
23
+ * doc <type> reject Reject a document with feedback
24
+ * doc <type> edit Open document in external editor
25
+ * doc <type> review Submit document for review
26
+ *
27
+ * @package bootspring
28
+ * @command preseed
29
+ */
30
+
31
+ const path = require('path');
32
+ const fs = require('fs');
33
+ const readline = require('readline');
34
+ const config = require('../core/config');
35
+ const utils = require('../core/utils');
36
+ const { PreseedEngine, PRESETS, DOCUMENT_TYPES } = require('../core/preseed');
37
+ const {
38
+ PreseedWorkflowEngine,
39
+ DOCUMENT_STATUS
40
+ } = require('../core/preseed-workflow');
41
+ const projectState = require('../core/project-state');
42
+ const checkpointEngine = require('../core/checkpoint-engine');
43
+ const apiClient = require('../core/api-client');
44
+ const auth = require('../core/auth');
45
+ const session = require('../core/session');
46
+ const preseedStart = require('./preseed-start');
47
+ const tierEnforcement = require('../core/tier-enforcement');
48
+
49
+ /**
50
+ * Create readline interface
51
+ */
52
+ function createInterface() {
53
+ return readline.createInterface({
54
+ input: process.stdin,
55
+ output: process.stdout
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Ask a text question
61
+ */
62
+ function askText(rl, question, defaultValue = '') {
63
+ const prompt = defaultValue
64
+ ? `${question} ${utils.COLORS.dim}[${defaultValue}]${utils.COLORS.reset}: `
65
+ : `${question}: `;
66
+
67
+ return new Promise((resolve) => {
68
+ rl.question(prompt, (answer) => {
69
+ resolve(answer.trim() || defaultValue);
70
+ });
71
+ });
72
+ }
73
+
74
+ /**
75
+ * Ask a multi-line text question
76
+ * @param {readline.Interface} rl - readline interface
77
+ * @param {string} question - the question to ask
78
+ * @param {string} hint - hint text
79
+ * @returns {Promise<string>}
80
+ */
81
+ function _askMultiLine(rl, question, hint = 'Enter text (empty line to finish)') {
82
+ return new Promise((resolve) => {
83
+ console.log(`\n${utils.COLORS.bold}${question}${utils.COLORS.reset}`);
84
+ console.log(`${utils.COLORS.dim}${hint}${utils.COLORS.reset}`);
85
+
86
+ const lines = [];
87
+ const onLine = (line) => {
88
+ if (line.trim() === '') {
89
+ rl.removeListener('line', onLine);
90
+ resolve(lines.join('\n'));
91
+ } else {
92
+ lines.push(line);
93
+ }
94
+ };
95
+ rl.on('line', onLine);
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Ask a list question (comma-separated)
101
+ */
102
+ function askList(rl, question, hint = 'Enter comma-separated values') {
103
+ return new Promise((resolve) => {
104
+ console.log(`\n${utils.COLORS.bold}${question}${utils.COLORS.reset}`);
105
+ console.log(`${utils.COLORS.dim}${hint}${utils.COLORS.reset}`);
106
+
107
+ rl.question('> ', (answer) => {
108
+ const items = answer
109
+ .split(',')
110
+ .map(s => s.trim())
111
+ .filter(s => s.length > 0);
112
+ resolve(items);
113
+ });
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Ask a choice question
119
+ */
120
+ function askChoice(rl, question, options, defaultIndex = 0) {
121
+ return new Promise((resolve) => {
122
+ console.log(`\n${utils.COLORS.bold}${question}${utils.COLORS.reset}`);
123
+
124
+ options.forEach((opt, i) => {
125
+ const marker = i === defaultIndex
126
+ ? `${utils.COLORS.cyan}>${utils.COLORS.reset}`
127
+ : ' ';
128
+ const label = typeof opt === 'object' ? opt.label : opt;
129
+ const desc = typeof opt === 'object' && opt.description
130
+ ? ` ${utils.COLORS.dim}- ${opt.description}${utils.COLORS.reset}`
131
+ : '';
132
+ console.log(` ${marker} ${i + 1}. ${label}${desc}`);
133
+ });
134
+
135
+ rl.question(`\n${utils.COLORS.dim}Select [1-${options.length}]${utils.COLORS.reset}: `, (answer) => {
136
+ const index = parseInt(answer, 10) - 1;
137
+ const selectedIndex = (index >= 0 && index < options.length) ? index : defaultIndex;
138
+ const selected = options[selectedIndex];
139
+ resolve(typeof selected === 'object' ? selected.value : selected);
140
+ });
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Display section header
146
+ */
147
+ function displaySection(number, total, title, description) {
148
+ console.log('\n');
149
+ console.log(`${utils.COLORS.cyan}${'━'.repeat(60)}${utils.COLORS.reset}`);
150
+ console.log(`${utils.COLORS.bold}Step ${number}/${total}: ${title}${utils.COLORS.reset}`);
151
+ if (description) {
152
+ console.log(`${utils.COLORS.dim}${description}${utils.COLORS.reset}`);
153
+ }
154
+ console.log(`${utils.COLORS.cyan}${'━'.repeat(60)}${utils.COLORS.reset}`);
155
+ }
156
+
157
+ /**
158
+ * Run the preseed wizard
159
+ */
160
+ async function runWizard(rl, preset = 'startup') {
161
+ const input = {};
162
+
163
+ console.log(`
164
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Wizard${utils.COLORS.reset}
165
+ ${utils.COLORS.dim}Let's build your foundational documents from scratch${utils.COLORS.reset}
166
+ ${utils.COLORS.dim}Preset: ${preset} (${PRESETS[preset].length} documents)${utils.COLORS.reset}
167
+ `);
168
+
169
+ const totalSteps = 8;
170
+
171
+ // Step 1: Identity
172
+ displaySection(1, totalSteps, 'Project Identity', 'Basic information about your project');
173
+
174
+ input.name = await askText(rl, 'Project name');
175
+ input.tagline = await askText(rl, 'One-line tagline');
176
+ input.description = await askText(rl, 'Brief description (2-3 sentences)');
177
+ input.category = await askChoice(rl, 'What type of product is this?', [
178
+ { label: 'SaaS', value: 'saas', description: 'Software as a Service' },
179
+ { label: 'Marketplace', value: 'marketplace', description: 'Two-sided marketplace' },
180
+ { label: 'E-commerce', value: 'ecommerce', description: 'Online store' },
181
+ { label: 'Mobile App', value: 'mobile', description: 'Mobile application' },
182
+ { label: 'Developer Tool', value: 'devtool', description: 'Tools for developers' },
183
+ { label: 'Consumer App', value: 'consumer', description: 'B2C application' },
184
+ { label: 'Enterprise', value: 'enterprise', description: 'Enterprise software' },
185
+ { label: 'Other', value: 'other', description: 'Something else' }
186
+ ]);
187
+
188
+ // Step 2: Problem
189
+ displaySection(2, totalSteps, 'The Problem', 'What problem are you solving?');
190
+
191
+ input.problem = await askText(rl, 'Describe the problem you\'re solving');
192
+ input.painPoints = await askList(rl, 'What are the top pain points?', 'Enter 3-5 pain points, comma-separated');
193
+ input.whyNow = await askText(rl, 'Why is now the right time to solve this?');
194
+
195
+ // Step 3: Solution
196
+ displaySection(3, totalSteps, 'Your Solution', 'How do you solve the problem?');
197
+
198
+ input.solution = await askText(rl, 'Describe your solution in one paragraph');
199
+ input.uniqueValue = await askText(rl, 'What makes your solution unique?');
200
+ input.keyFeatures = await askList(rl, 'What are the key features?', 'Enter 3-6 features, comma-separated');
201
+
202
+ // Step 4: Target Audience
203
+ displaySection(4, totalSteps, 'Target Audience', 'Who are you building this for?');
204
+
205
+ input.primaryAudience = await askText(rl, 'Describe your primary audience');
206
+ input.segments = await askList(rl, 'What market segments are you targeting?', 'Enter 2-4 segments, comma-separated');
207
+
208
+ console.log(`\n${utils.COLORS.bold}Let's create a user persona${utils.COLORS.reset}`);
209
+ const personaName = await askText(rl, 'Persona name (e.g., "Tech-Savvy Startup Founder")');
210
+ const personaRole = await askText(rl, 'Their role/job title');
211
+ const personaGoals = await askList(rl, 'What are their goals?', 'Enter 2-3 goals, comma-separated');
212
+ const personaPains = await askList(rl, 'What are their pain points?', 'Enter 2-3 pain points, comma-separated');
213
+
214
+ input.personas = [{
215
+ name: personaName,
216
+ role: personaRole,
217
+ goals: personaGoals,
218
+ painPoints: personaPains
219
+ }];
220
+
221
+ // Step 5: Market (if in preset)
222
+ if (PRESETS[preset].includes('market')) {
223
+ displaySection(5, totalSteps, 'Market Analysis', 'Understanding the market opportunity');
224
+
225
+ input.tam = await askText(rl, 'Total Addressable Market (TAM)', '$1B+');
226
+ input.sam = await askText(rl, 'Serviceable Addressable Market (SAM)', '$100M+');
227
+ input.som = await askText(rl, 'Serviceable Obtainable Market (SOM) in 3 years', '$10M+');
228
+ input.marketGrowth = await askText(rl, 'Market growth rate or trend');
229
+ input.trends = await askList(rl, 'Key market trends', 'Enter 2-4 trends, comma-separated');
230
+ }
231
+
232
+ // Step 6: Competition
233
+ if (PRESETS[preset].includes('competitors')) {
234
+ displaySection(6, totalSteps, 'Competition', 'Understanding the competitive landscape');
235
+
236
+ const competitors = await askList(rl, 'Who are your direct competitors?', 'Enter company names, comma-separated');
237
+ input.directCompetitors = competitors.map(name => ({ name, strengths: 'TBD', weaknesses: 'TBD' }));
238
+
239
+ input.positioning = await askText(rl, 'How do you position against competitors?');
240
+ input.differentiation = await askList(rl, 'Key differentiators', 'Enter 2-4 differentiators, comma-separated');
241
+ }
242
+
243
+ // Step 7: Business Model
244
+ displaySection(7, totalSteps, 'Business Model', 'How will you make money?');
245
+
246
+ input.businessModel = await askChoice(rl, 'What is your business model?', [
247
+ { label: 'Subscription', value: 'subscription', description: 'Recurring revenue' },
248
+ { label: 'Usage-based', value: 'usage', description: 'Pay per use' },
249
+ { label: 'Freemium', value: 'freemium', description: 'Free tier + paid upgrades' },
250
+ { label: 'Marketplace', value: 'marketplace', description: 'Transaction fees' },
251
+ { label: 'One-time', value: 'one-time', description: 'One-time purchase' },
252
+ { label: 'Enterprise', value: 'enterprise', description: 'Custom contracts' }
253
+ ]);
254
+
255
+ input.revenueStreams = await askList(rl, 'Revenue streams', 'Enter revenue sources, comma-separated');
256
+
257
+ console.log(`\n${utils.COLORS.bold}Pricing Tiers${utils.COLORS.reset}`);
258
+ const tiers = [];
259
+
260
+ const tier1Name = await askText(rl, 'Tier 1 name', 'Free');
261
+ const tier1Price = await askText(rl, 'Tier 1 price', '$0');
262
+ tiers.push({ name: tier1Name, price: tier1Price, features: ['Basic features'] });
263
+
264
+ const tier2Name = await askText(rl, 'Tier 2 name', 'Pro');
265
+ const tier2Price = await askText(rl, 'Tier 2 price', '$29/mo');
266
+ tiers.push({ name: tier2Name, price: tier2Price, features: ['Pro features'] });
267
+
268
+ const tier3Name = await askText(rl, 'Tier 3 name', 'Enterprise');
269
+ const tier3Price = await askText(rl, 'Tier 3 price', 'Custom');
270
+ tiers.push({ name: tier3Name, price: tier3Price, features: ['All features', 'Custom support'] });
271
+
272
+ input.pricing = { model: input.businessModel, tiers };
273
+
274
+ // Step 8: MVP Features
275
+ displaySection(8, totalSteps, 'MVP & Roadmap', 'What will you build first?');
276
+
277
+ input.productVision = await askText(rl, 'What is your product vision?');
278
+ input.mvpFeatures = await askList(rl, 'MVP features (must-have for launch)', 'Enter 5-8 features, comma-separated');
279
+ input.futureFeatures = await askList(rl, 'Future features (post-MVP)', 'Enter 3-5 features, comma-separated');
280
+
281
+ // Roadmap phases
282
+ console.log(`\n${utils.COLORS.bold}Development Phases${utils.COLORS.reset}`);
283
+ input.phases = [
284
+ {
285
+ name: 'MVP',
286
+ duration: await askText(rl, 'MVP duration', '4-6 weeks'),
287
+ goals: ['Launch core features', 'Validate with early users']
288
+ },
289
+ {
290
+ name: 'Growth',
291
+ duration: await askText(rl, 'Growth phase duration', '2-3 months'),
292
+ goals: ['Scale user base', 'Add key features']
293
+ },
294
+ {
295
+ name: 'Scale',
296
+ duration: await askText(rl, 'Scale phase duration', '3-6 months'),
297
+ goals: ['Enterprise features', 'Market expansion']
298
+ }
299
+ ];
300
+
301
+ input.milestones = await askList(rl, 'Key milestones', 'Enter milestone names, comma-separated');
302
+
303
+ return input;
304
+ }
305
+
306
+ /**
307
+ * Setup context input folders
308
+ */
309
+ async function preseedSetup(_args) {
310
+ const projectRoot = config.findProjectRoot();
311
+
312
+ console.log(`
313
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Setup${utils.COLORS.reset}
314
+ ${utils.COLORS.dim}Create context input folders for early documentation${utils.COLORS.reset}
315
+ `);
316
+
317
+ const engine = new PreseedEngine(projectRoot);
318
+ const spinner = utils.createSpinner('Creating context folders...').start();
319
+
320
+ const created = engine.setupContextFolders();
321
+ spinner.succeed(`Created ${created.length} folders`);
322
+
323
+ console.log(`
324
+ ${utils.COLORS.green}${utils.COLORS.bold}Setup complete!${utils.COLORS.reset}
325
+
326
+ ${utils.COLORS.bold}Context folder structure:${utils.COLORS.reset}
327
+ .bootspring/preseed/context/
328
+
329
+ │ ${utils.COLORS.yellow}Universal${utils.COLORS.reset}
330
+ ├── ${utils.COLORS.green}drop/${utils.COLORS.reset} ${utils.COLORS.dim}# ${utils.COLORS.bold}Drop anything here${utils.COLORS.reset}${utils.COLORS.dim} - AI sorts it out${utils.COLORS.reset}
331
+
332
+ │ ${utils.COLORS.yellow}General${utils.COLORS.reset}
333
+ ├── ideas/ ${utils.COLORS.dim}# Rough ideas, brainstorms, notes${utils.COLORS.reset}
334
+ ├── research/ ${utils.COLORS.dim}# Market research, industry reports${utils.COLORS.reset}
335
+
336
+ │ ${utils.COLORS.yellow}Document-specific (maps to output)${utils.COLORS.reset}
337
+ ├── vision/ ${utils.COLORS.dim}# → VISION.md${utils.COLORS.reset}
338
+ ├── audience/ ${utils.COLORS.dim}# → AUDIENCE.md${utils.COLORS.reset}
339
+ ├── market/ ${utils.COLORS.dim}# → MARKET.md${utils.COLORS.reset}
340
+ ├── competitors/ ${utils.COLORS.dim}# → COMPETITORS.md${utils.COLORS.reset}
341
+ ├── business/ ${utils.COLORS.dim}# → BUSINESS_MODEL.md${utils.COLORS.reset}
342
+ ├── prd/ ${utils.COLORS.dim}# → PRD.md${utils.COLORS.reset}
343
+ ├── technical/ ${utils.COLORS.dim}# → TECHNICAL_SPEC.md${utils.COLORS.reset}
344
+ └── roadmap/ ${utils.COLORS.dim}# → ROADMAP.md${utils.COLORS.reset}
345
+
346
+ ${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
347
+ 1. Drop files in the matching folder (e.g., vision/ for VISION.md sources)
348
+ 2. Or use ${utils.COLORS.green}drop/${utils.COLORS.reset} if unsure - AI will sort it out
349
+ 3. Run ${utils.COLORS.cyan}bootspring preseed start${utils.COLORS.reset}
350
+
351
+ ${utils.COLORS.dim}Tip: Each folder maps 1:1 to an output document for easy organization!${utils.COLORS.reset}
352
+ `);
353
+ }
354
+
355
+ /**
356
+ * Initialize preseed
357
+ */
358
+ async function preseedInit(args) {
359
+ const projectRoot = config.findProjectRoot();
360
+ const preset = args.preset || args.p || 'startup';
361
+ const quick = args.quick || args.q;
362
+
363
+ console.log(`
364
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Init${utils.COLORS.reset}
365
+ ${utils.COLORS.dim}Generate foundational documents from minimal input${utils.COLORS.reset}
366
+ `);
367
+
368
+ // Validate preset
369
+ if (!PRESETS[preset]) {
370
+ utils.print.error(`Invalid preset: ${preset}`);
371
+ console.log(`\nAvailable presets: ${Object.keys(PRESETS).join(', ')}`);
372
+ return;
373
+ }
374
+
375
+ const engine = new PreseedEngine(projectRoot, { preset });
376
+
377
+ // Auto-create folder structure
378
+ const setupSpinner = utils.createSpinner('Setting up folder structure...').start();
379
+ engine.setupContextFolders();
380
+ setupSpinner.succeed('Folder structure ready');
381
+
382
+ // Check for context documents
383
+ if (engine.hasContextDocuments()) {
384
+ const spinner = utils.createSpinner('Analyzing context documents...').start();
385
+ const context = engine.ingestContext();
386
+ spinner.succeed(`Found ${context.summary.totalFiles} context files`);
387
+
388
+ console.log(`\n${utils.COLORS.bold}Context Found:${utils.COLORS.reset}`);
389
+ for (const [category, count] of Object.entries(context.summary.byCategory)) {
390
+ if (count > 0) {
391
+ console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} ${category}: ${count} file(s)`);
392
+ }
393
+ }
394
+ console.log(`${utils.COLORS.dim}Your context will be used to enhance document generation.${utils.COLORS.reset}\n`);
395
+ }
396
+
397
+ console.log(`${utils.COLORS.bold}Preset:${utils.COLORS.reset} ${preset}`);
398
+ console.log(`${utils.COLORS.bold}Documents:${utils.COLORS.reset} ${PRESETS[preset].length}`);
399
+ console.log(`${utils.COLORS.dim}${PRESETS[preset].join(', ')}${utils.COLORS.reset}\n`);
400
+
401
+ const rl = createInterface();
402
+
403
+ try {
404
+ let input;
405
+
406
+ if (quick) {
407
+ // Quick mode - just essential info
408
+ console.log(`${utils.COLORS.yellow}Quick mode - minimal questions${utils.COLORS.reset}\n`);
409
+
410
+ input = {
411
+ name: await askText(rl, 'Project name'),
412
+ tagline: await askText(rl, 'Tagline'),
413
+ problem: await askText(rl, 'Problem you\'re solving'),
414
+ solution: await askText(rl, 'Your solution'),
415
+ primaryAudience: await askText(rl, 'Target audience'),
416
+ businessModel: await askChoice(rl, 'Business model?', [
417
+ { label: 'Subscription', value: 'subscription' },
418
+ { label: 'Freemium', value: 'freemium' },
419
+ { label: 'Usage-based', value: 'usage' },
420
+ { label: 'One-time', value: 'one-time' }
421
+ ]),
422
+ keyFeatures: await askList(rl, 'Key features', 'Comma-separated')
423
+ };
424
+ } else {
425
+ // Full wizard
426
+ input = await runWizard(rl, preset);
427
+ }
428
+
429
+ rl.close();
430
+
431
+ // Initialize engine
432
+ const engine = new PreseedEngine(projectRoot, { preset });
433
+ const spinner = utils.createSpinner('Initializing preseed...').start();
434
+
435
+ await engine.initialize(input);
436
+ spinner.succeed('Preseed initialized');
437
+
438
+ // Generate documents
439
+ const genSpinner = utils.createSpinner('Generating documents...').start();
440
+ const results = await engine.generateAll();
441
+ genSpinner.succeed(`Generated ${results.length} documents`);
442
+
443
+ // Show results
444
+ console.log(`
445
+ ${utils.COLORS.green}${utils.COLORS.bold}Preseed complete!${utils.COLORS.reset}
446
+
447
+ ${utils.COLORS.bold}Generated Documents:${utils.COLORS.reset}`);
448
+
449
+ for (const doc of results) {
450
+ console.log(` ${utils.COLORS.green}+${utils.COLORS.reset} ${doc.title}: ${utils.COLORS.cyan}.bootspring/preseed/${DOCUMENT_TYPES[doc.type].name}${utils.COLORS.reset}`);
451
+ }
452
+
453
+ // Auto-tag project as development type
454
+ try {
455
+ projectState.setProjectType(projectRoot, projectState.PROJECT_TYPES.DEVELOPMENT, {
456
+ autoTagged: true,
457
+ taggedBy: 'preseed'
458
+ });
459
+ checkpointEngine.syncCheckpoints(projectRoot, { verbose: false });
460
+ console.log(`\n ${utils.COLORS.cyan}●${utils.COLORS.reset} Project tagged as ${utils.COLORS.cyan}development${utils.COLORS.reset} type`);
461
+ } catch (err) {
462
+ utils.print.debug(`Auto-tagging failed: ${err.message}`);
463
+ }
464
+
465
+ console.log(`
466
+ ${utils.COLORS.bold}Next Steps:${utils.COLORS.reset}
467
+ 1. Review generated documents in ${utils.COLORS.cyan}.bootspring/preseed/${utils.COLORS.reset}
468
+ 2. Edit ${utils.COLORS.cyan}.bootspring/preseed/PRESEED_CONFIG.json${utils.COLORS.reset} to refine
469
+ 3. Run ${utils.COLORS.cyan}bootspring preseed sync${utils.COLORS.reset} to regenerate
470
+ 4. Run ${utils.COLORS.cyan}bootspring seed init${utils.COLORS.reset} to scaffold project
471
+
472
+ ${utils.COLORS.dim}Tip: These are living documents. Update the config and sync to keep them fresh.${utils.COLORS.reset}
473
+ `);
474
+ } catch (error) {
475
+ rl.close();
476
+ utils.print.error(`Preseed init failed: ${error.message}`);
477
+ if (process.env.DEBUG) {
478
+ console.error(error.stack);
479
+ }
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Generate/regenerate documents
485
+ */
486
+ async function preseedGenerate(args) {
487
+ const projectRoot = config.findProjectRoot();
488
+ const preset = args.preset || 'startup';
489
+ const doc = args.doc || args.d;
490
+
491
+ console.log(`
492
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Generate${utils.COLORS.reset}
493
+ ${utils.COLORS.dim}Regenerate preseed documents${utils.COLORS.reset}
494
+ `);
495
+
496
+ const engine = new PreseedEngine(projectRoot, { preset });
497
+
498
+ if (!engine.loadConfig()) {
499
+ utils.print.error('No preseed configuration found');
500
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed init${utils.COLORS.reset} first`);
501
+ return;
502
+ }
503
+
504
+ const spinner = utils.createSpinner('Generating documents...').start();
505
+
506
+ try {
507
+ if (doc) {
508
+ // Generate single document
509
+ if (!DOCUMENT_TYPES[doc]) {
510
+ spinner.fail(`Unknown document type: ${doc}`);
511
+ console.log(`\nAvailable types: ${Object.keys(DOCUMENT_TYPES).join(', ')}`);
512
+ return;
513
+ }
514
+
515
+ const content = engine.generateDocument(doc);
516
+ const filePath = path.join(engine.outputDir, DOCUMENT_TYPES[doc].name);
517
+ fs.writeFileSync(filePath, content);
518
+ spinner.succeed(`Generated ${DOCUMENT_TYPES[doc].name}`);
519
+ } else {
520
+ // Generate all documents
521
+ const results = await engine.generateAll();
522
+ spinner.succeed(`Generated ${results.length} documents`);
523
+
524
+ console.log(`\n${utils.COLORS.bold}Documents:${utils.COLORS.reset}`);
525
+ for (const doc of results) {
526
+ console.log(` ${utils.COLORS.green}+${utils.COLORS.reset} ${doc.title}`);
527
+ }
528
+ }
529
+ } catch (error) {
530
+ spinner.fail(`Generation failed: ${error.message}`);
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Sync documents with codebase
536
+ */
537
+ async function preseedSync(args) {
538
+ const projectRoot = config.findProjectRoot();
539
+ const preset = args.preset || 'startup';
540
+
541
+ console.log(`
542
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Sync${utils.COLORS.reset}
543
+ ${utils.COLORS.dim}Sync documents with latest configuration${utils.COLORS.reset}
544
+ `);
545
+
546
+ const engine = new PreseedEngine(projectRoot, { preset });
547
+
548
+ if (!engine.loadConfig()) {
549
+ utils.print.error('No preseed configuration found');
550
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed init${utils.COLORS.reset} first`);
551
+ return;
552
+ }
553
+
554
+ const spinner = utils.createSpinner('Syncing documents...').start();
555
+
556
+ try {
557
+ const result = await engine.sync();
558
+ spinner.succeed(`Synced ${result.synced} documents`);
559
+
560
+ console.log(`
561
+ ${utils.COLORS.bold}Synced Documents:${utils.COLORS.reset}`);
562
+ for (const doc of result.documents) {
563
+ console.log(` ${utils.COLORS.green}✓${utils.COLORS.reset} ${doc.title}`);
564
+ }
565
+ } catch (error) {
566
+ spinner.fail(`Sync failed: ${error.message}`);
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Show preseed status
572
+ */
573
+ function preseedStatus(args) {
574
+ const projectRoot = config.findProjectRoot();
575
+ const preset = args.preset || 'startup';
576
+
577
+ console.log(`
578
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Status${utils.COLORS.reset}
579
+ `);
580
+
581
+ const engine = new PreseedEngine(projectRoot, { preset });
582
+ const status = engine.getStatus();
583
+
584
+ if (!status.initialized) {
585
+ console.log(`${utils.COLORS.yellow}○${utils.COLORS.reset} Not initialized`);
586
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed init${utils.COLORS.reset} to get started`);
587
+ return;
588
+ }
589
+
590
+ // Show status based on init mode
591
+ if (status.initMode === 'merged') {
592
+ console.log(`${utils.COLORS.cyan}⊕${utils.COLORS.reset} ${status.projectName}`);
593
+ console.log(`${utils.COLORS.dim}Mode: Merged documents (no config file)${utils.COLORS.reset}`);
594
+ } else {
595
+ console.log(`${utils.COLORS.green}✓${utils.COLORS.reset} ${status.projectName}`);
596
+ console.log(`${utils.COLORS.dim}Preset: ${status.preset}${utils.COLORS.reset}`);
597
+ if (status.lastSync) {
598
+ console.log(`${utils.COLORS.dim}Last sync: ${status.lastSync}${utils.COLORS.reset}`);
599
+ }
600
+ }
601
+ console.log(`${utils.COLORS.dim}Output: ${status.outputDir}${utils.COLORS.reset}`);
602
+
603
+ console.log(`\n${utils.COLORS.bold}Documents:${utils.COLORS.reset}`);
604
+
605
+ for (const doc of status.documents) {
606
+ const icon = doc.exists
607
+ ? `${utils.COLORS.green}✓${utils.COLORS.reset}`
608
+ : doc.required
609
+ ? `${utils.COLORS.red}✗${utils.COLORS.reset}`
610
+ : `${utils.COLORS.yellow}○${utils.COLORS.reset}`;
611
+
612
+ const modified = doc.modified
613
+ ? ` ${utils.COLORS.dim}(${doc.modified.split('T')[0]})${utils.COLORS.reset}`
614
+ : '';
615
+
616
+ console.log(` ${icon} ${doc.title}${modified}`);
617
+ }
618
+
619
+ const existing = status.documents.filter(d => d.exists).length;
620
+ const total = status.documents.length;
621
+ console.log(`\n${utils.COLORS.bold}Progress:${utils.COLORS.reset} ${existing}/${total} documents`);
622
+
623
+ // Show next steps for merged mode
624
+ if (status.initMode === 'merged' && existing > 0) {
625
+ console.log(`\n${utils.COLORS.bold}Next steps:${utils.COLORS.reset}`);
626
+ console.log(` ${utils.COLORS.cyan}bootspring seed synthesize${utils.COLORS.reset} # Generate SEED.md`);
627
+ console.log(` ${utils.COLORS.cyan}bootspring seed scaffold${utils.COLORS.reset} # Generate project`);
628
+ }
629
+ }
630
+
631
+ /**
632
+ * Update config value
633
+ */
634
+ async function preseedUpdate(args) {
635
+ const projectRoot = config.findProjectRoot();
636
+ const configPath = args._[0];
637
+ const value = args._[1];
638
+
639
+ if (!configPath) {
640
+ utils.print.error('Please specify a config path');
641
+ console.log(`\nUsage: ${utils.COLORS.cyan}bootspring preseed update <path> <value>${utils.COLORS.reset}`);
642
+ console.log('\nExamples:');
643
+ console.log(' bootspring preseed update identity.name "My App"');
644
+ console.log(' bootspring preseed update problem.statement "Description..."');
645
+ return;
646
+ }
647
+
648
+ const engine = new PreseedEngine(projectRoot);
649
+
650
+ if (!engine.loadConfig()) {
651
+ utils.print.error('No preseed configuration found');
652
+ return;
653
+ }
654
+
655
+ if (value) {
656
+ engine.updateConfig(configPath, value);
657
+ utils.print.success(`Updated ${configPath}`);
658
+
659
+ // Ask if user wants to regenerate
660
+ console.log(`\n${utils.COLORS.dim}Run ${utils.COLORS.cyan}bootspring preseed sync${utils.COLORS.reset}${utils.COLORS.dim} to regenerate documents${utils.COLORS.reset}`);
661
+ } else {
662
+ // Show current value
663
+ const parts = configPath.split('.');
664
+ let current = engine.config;
665
+ for (const part of parts) {
666
+ current = current?.[part];
667
+ }
668
+
669
+ console.log(`\n${utils.COLORS.bold}${configPath}:${utils.COLORS.reset}`);
670
+ if (typeof current === 'object') {
671
+ console.log(JSON.stringify(current, null, 2));
672
+ } else {
673
+ console.log(current || '(not set)');
674
+ }
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Export preseed config
680
+ */
681
+ function preseedExport(args) {
682
+ const projectRoot = config.findProjectRoot();
683
+ const format = args.format || args.f || 'json';
684
+ const output = args.output || args.o;
685
+
686
+ const engine = new PreseedEngine(projectRoot);
687
+
688
+ if (!engine.loadConfig()) {
689
+ utils.print.error('No preseed configuration found');
690
+ return;
691
+ }
692
+
693
+ let exported;
694
+ if (format === 'yaml' || format === 'yml') {
695
+ const yaml = require('yaml');
696
+ exported = yaml.stringify(engine.config);
697
+ } else {
698
+ exported = JSON.stringify(engine.config, null, 2);
699
+ }
700
+
701
+ if (output) {
702
+ const outputPath = path.resolve(projectRoot, output);
703
+ fs.writeFileSync(outputPath, exported);
704
+ utils.print.success(`Exported to ${outputPath}`);
705
+ } else {
706
+ console.log(exported);
707
+ }
708
+ }
709
+
710
+ /**
711
+ * Pull preseed documents from dashboard
712
+ */
713
+ async function preseedPull(args) {
714
+ const projectRoot = config.findProjectRoot();
715
+ const projectId = args.project || args.p;
716
+ const docName = args.doc || args.d;
717
+ const force = args.force || args.f;
718
+
719
+ console.log(`
720
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Pull${utils.COLORS.reset}
721
+ ${utils.COLORS.dim}Sync preseed documents from dashboard${utils.COLORS.reset}
722
+ `);
723
+
724
+ // Check authentication
725
+ if (!auth.isAuthenticated()) {
726
+ utils.print.error('Authentication required');
727
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring auth login${utils.COLORS.reset} first`);
728
+ return;
729
+ }
730
+
731
+ // Get project ID
732
+ let targetProjectId = projectId;
733
+
734
+ if (!targetProjectId) {
735
+ // Try to get from session or local config
736
+ const currentProject = session.getEffectiveProject();
737
+ if (currentProject?.id) {
738
+ targetProjectId = currentProject.id;
739
+ console.log(`${utils.COLORS.dim}Using project: ${currentProject.name || targetProjectId}${utils.COLORS.reset}\n`);
740
+ } else {
741
+ // Try to find project by name from local config
742
+ const localConfig = config.loadConfig(projectRoot);
743
+ if (localConfig?.project?.id) {
744
+ targetProjectId = localConfig.project.id;
745
+ } else if (localConfig?.project?.name) {
746
+ // Look up project by name
747
+ const spinner = utils.createSpinner('Finding project...').start();
748
+ try {
749
+ const projects = await apiClient.listProjects();
750
+ const match = projects.projects?.find(p =>
751
+ p.name.toLowerCase() === localConfig.project.name.toLowerCase() ||
752
+ p.slug === localConfig.project.name.toLowerCase()
753
+ );
754
+ if (match) {
755
+ targetProjectId = match.id;
756
+ spinner.succeed(`Found project: ${match.name}`);
757
+ } else {
758
+ spinner.fail('Project not found');
759
+ console.log(`\nSpecify project with ${utils.COLORS.cyan}--project=<id>${utils.COLORS.reset}`);
760
+ return;
761
+ }
762
+ } catch (error) {
763
+ spinner.fail(`Failed to find project: ${error.message}`);
764
+ return;
765
+ }
766
+ }
767
+ }
768
+ }
769
+
770
+ if (!targetProjectId) {
771
+ utils.print.error('No project specified');
772
+ console.log(`
773
+ ${utils.COLORS.bold}Options:${utils.COLORS.reset}
774
+ 1. Specify project: ${utils.COLORS.cyan}bootspring preseed pull --project=<id>${utils.COLORS.reset}
775
+ 2. Set current project: ${utils.COLORS.cyan}bootspring project use <id>${utils.COLORS.reset}
776
+ 3. Run from a linked project directory
777
+ `);
778
+ return;
779
+ }
780
+
781
+ // Set up output directory
782
+ const outputDir = path.join(projectRoot, '.bootspring', 'preseed');
783
+ if (!fs.existsSync(outputDir)) {
784
+ fs.mkdirSync(outputDir, { recursive: true });
785
+ }
786
+
787
+ // Pull single document or all
788
+ if (docName) {
789
+ // Pull single document
790
+ const spinner = utils.createSpinner(`Fetching ${docName}...`).start();
791
+
792
+ try {
793
+ const result = await apiClient.getPreseedDocument(targetProjectId, docName);
794
+ const filename = docName.endsWith('.md') ? docName : `${docName}.md`;
795
+ const filePath = path.join(outputDir, filename);
796
+
797
+ // Check for existing file
798
+ if (fs.existsSync(filePath) && !force) {
799
+ const existingContent = fs.readFileSync(filePath, 'utf-8');
800
+ if (existingContent === result.content) {
801
+ spinner.succeed(`${docName} is already up to date`);
802
+ return;
803
+ }
804
+
805
+ spinner.stop();
806
+ const rl = createInterface();
807
+ const overwrite = await askChoice(rl, `${filename} exists locally. Overwrite?`, [
808
+ { label: 'Yes, overwrite', value: 'yes' },
809
+ { label: 'No, keep local', value: 'no' },
810
+ { label: 'Show diff', value: 'diff' }
811
+ ]);
812
+ rl.close();
813
+
814
+ if (overwrite === 'no') {
815
+ console.log(`${utils.COLORS.dim}Skipped ${filename}${utils.COLORS.reset}`);
816
+ return;
817
+ }
818
+
819
+ if (overwrite === 'diff') {
820
+ console.log(`\n${utils.COLORS.bold}Remote content:${utils.COLORS.reset}`);
821
+ console.log(`${utils.COLORS.dim}${'─'.repeat(60)}${utils.COLORS.reset}`);
822
+ console.log(result.content.slice(0, 500) + (result.content.length > 500 ? '...' : ''));
823
+ console.log(`${utils.COLORS.dim}${'─'.repeat(60)}${utils.COLORS.reset}`);
824
+ return;
825
+ }
826
+
827
+ spinner.start('Writing file...');
828
+ }
829
+
830
+ fs.writeFileSync(filePath, result.content, 'utf-8');
831
+ spinner.succeed(`Saved ${filename}`);
832
+ } catch (error) {
833
+ spinner.fail(`Failed to fetch ${docName}: ${error.message}`);
834
+ }
835
+ } else {
836
+ // Pull all documents
837
+ const spinner = utils.createSpinner('Fetching preseed documents...').start();
838
+
839
+ try {
840
+ // First, list available documents
841
+ const listResult = await apiClient.listPreseedDocuments(targetProjectId);
842
+
843
+ if (!listResult.documents || listResult.documents.length === 0) {
844
+ spinner.warn('No preseed documents found in dashboard');
845
+ console.log(`
846
+ ${utils.COLORS.dim}Generate documents in the dashboard first:${utils.COLORS.reset}
847
+ ${utils.COLORS.cyan}https://bootspring.com/dashboard/projects/${targetProjectId}/preseed/studio${utils.COLORS.reset}
848
+ `);
849
+ return;
850
+ }
851
+
852
+ spinner.text = `Found ${listResult.documents.length} documents. Downloading...`;
853
+
854
+ // Download each document
855
+ let downloaded = 0;
856
+ let skipped = 0;
857
+ const errors = [];
858
+
859
+ for (const doc of listResult.documents) {
860
+ try {
861
+ const result = await apiClient.getPreseedDocument(targetProjectId, doc.name);
862
+ const filename = doc.name.endsWith('.md') ? doc.name : `${doc.name}.md`;
863
+ const filePath = path.join(outputDir, filename);
864
+
865
+ // Check if file exists and is different
866
+ if (fs.existsSync(filePath) && !force) {
867
+ const existingContent = fs.readFileSync(filePath, 'utf-8');
868
+ if (existingContent === result.content) {
869
+ skipped++;
870
+ continue;
871
+ }
872
+ }
873
+
874
+ fs.writeFileSync(filePath, result.content, 'utf-8');
875
+ downloaded++;
876
+ } catch (err) {
877
+ errors.push({ doc: doc.name, error: err.message });
878
+ }
879
+ }
880
+
881
+ // Also try to get PRESEED_CONFIG.json
882
+ try {
883
+ const configResult = await apiClient.getPreseedDocument(targetProjectId, 'PRESEED_CONFIG.json');
884
+ const configPath = path.join(outputDir, 'PRESEED_CONFIG.json');
885
+ fs.writeFileSync(configPath, configResult.content, 'utf-8');
886
+ downloaded++;
887
+ } catch {
888
+ // Config may not exist, that's ok
889
+ }
890
+
891
+ if (downloaded > 0 || skipped > 0) {
892
+ spinner.succeed(`Downloaded ${downloaded} documents${skipped > 0 ? `, ${skipped} already up to date` : ''}`);
893
+ } else {
894
+ spinner.warn('No documents to download');
895
+ }
896
+
897
+ if (errors.length > 0) {
898
+ console.log(`\n${utils.COLORS.yellow}Some documents failed:${utils.COLORS.reset}`);
899
+ for (const err of errors) {
900
+ console.log(` ${utils.COLORS.red}✗${utils.COLORS.reset} ${err.doc}: ${err.error}`);
901
+ }
902
+ }
903
+
904
+ // Show summary
905
+ console.log(`
906
+ ${utils.COLORS.bold}Documents saved to:${utils.COLORS.reset} ${utils.COLORS.cyan}${outputDir}${utils.COLORS.reset}
907
+
908
+ ${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
909
+ ${utils.COLORS.dim}•${utils.COLORS.reset} Review documents in ${utils.COLORS.cyan}.bootspring/preseed/${utils.COLORS.reset}
910
+ ${utils.COLORS.dim}•${utils.COLORS.reset} Run ${utils.COLORS.cyan}bootspring preseed status${utils.COLORS.reset} to see document status
911
+ ${utils.COLORS.dim}•${utils.COLORS.reset} Run ${utils.COLORS.cyan}bootspring seed init${utils.COLORS.reset} to scaffold project
912
+ `);
913
+ } catch (error) {
914
+ spinner.fail(`Failed to fetch documents: ${error.message}`);
915
+
916
+ if (error.status === 404) {
917
+ console.log(`
918
+ ${utils.COLORS.dim}Project not found or no access. Check:${utils.COLORS.reset}
919
+ ${utils.COLORS.dim}•${utils.COLORS.reset} Project ID is correct
920
+ ${utils.COLORS.dim}•${utils.COLORS.reset} You have access to this project
921
+ ${utils.COLORS.dim}•${utils.COLORS.reset} Run ${utils.COLORS.cyan}bootspring auth login${utils.COLORS.reset} to refresh auth
922
+ `);
923
+ }
924
+ }
925
+ }
926
+ }
927
+
928
+ /**
929
+ * Push preseed documents to dashboard
930
+ */
931
+ async function preseedPush(args) {
932
+ const projectRoot = config.findProjectRoot();
933
+ const projectId = args.project || args.p;
934
+ const docName = args.doc || args.d;
935
+
936
+ console.log(`
937
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Push${utils.COLORS.reset}
938
+ ${utils.COLORS.dim}Upload preseed documents to dashboard${utils.COLORS.reset}
939
+ `);
940
+
941
+ // Check authentication
942
+ if (!auth.isAuthenticated()) {
943
+ utils.print.error('Authentication required');
944
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring auth login${utils.COLORS.reset} first`);
945
+ return;
946
+ }
947
+
948
+ // Get project ID (same logic as pull)
949
+ let targetProjectId = projectId;
950
+
951
+ if (!targetProjectId) {
952
+ const currentProject = session.getEffectiveProject();
953
+ if (currentProject?.id) {
954
+ targetProjectId = currentProject.id;
955
+ console.log(`${utils.COLORS.dim}Using project: ${currentProject.name || targetProjectId}${utils.COLORS.reset}\n`);
956
+ } else {
957
+ const localConfig = config.loadConfig(projectRoot);
958
+ if (localConfig?.project?.id) {
959
+ targetProjectId = localConfig.project.id;
960
+ }
961
+ }
962
+ }
963
+
964
+ if (!targetProjectId) {
965
+ utils.print.error('No project specified');
966
+ console.log(`\nSpecify project with ${utils.COLORS.cyan}--project=<id>${utils.COLORS.reset}`);
967
+ return;
968
+ }
969
+
970
+ const preseedDir = path.join(projectRoot, '.bootspring', 'preseed');
971
+ if (!fs.existsSync(preseedDir)) {
972
+ utils.print.error('No local preseed documents found');
973
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed init${utils.COLORS.reset} first`);
974
+ return;
975
+ }
976
+
977
+ const spinner = utils.createSpinner('Uploading preseed documents...').start();
978
+
979
+ try {
980
+ // Get list of local documents
981
+ const files = fs.readdirSync(preseedDir).filter(f =>
982
+ f.endsWith('.md') || f === 'PRESEED_CONFIG.json'
983
+ );
984
+
985
+ if (files.length === 0) {
986
+ spinner.warn('No documents to upload');
987
+ return;
988
+ }
989
+
990
+ let uploaded = 0;
991
+ const errors = [];
992
+
993
+ for (const file of files) {
994
+ if (docName && !file.startsWith(docName)) continue;
995
+
996
+ const filePath = path.join(preseedDir, file);
997
+ const content = fs.readFileSync(filePath, 'utf-8');
998
+ const name = file.replace('.md', '');
999
+
1000
+ try {
1001
+ // Use direct request to PUT documents
1002
+ await apiClient.directRequest('PUT', `/projects/${targetProjectId}/preseed/documents`, {
1003
+ name,
1004
+ content
1005
+ });
1006
+ uploaded++;
1007
+ } catch (err) {
1008
+ errors.push({ file, error: err.message });
1009
+ }
1010
+ }
1011
+
1012
+ if (uploaded > 0) {
1013
+ spinner.succeed(`Uploaded ${uploaded} documents`);
1014
+ } else {
1015
+ spinner.warn('No documents uploaded');
1016
+ }
1017
+
1018
+ if (errors.length > 0) {
1019
+ console.log(`\n${utils.COLORS.yellow}Some uploads failed:${utils.COLORS.reset}`);
1020
+ for (const err of errors) {
1021
+ console.log(` ${utils.COLORS.red}✗${utils.COLORS.reset} ${err.file}: ${err.error}`);
1022
+ }
1023
+ }
1024
+ } catch (error) {
1025
+ spinner.fail(`Upload failed: ${error.message}`);
1026
+ }
1027
+ }
1028
+
1029
+ /**
1030
+ * Execute document merges from context folders
1031
+ * Reads source files and merges them into preseed documents
1032
+ */
1033
+ async function preseedMerge(args) {
1034
+ const projectRoot = config.findProjectRoot();
1035
+ const preseedDir = path.join(projectRoot, '.bootspring', 'preseed');
1036
+ const contextDir = path.join(preseedDir, 'context');
1037
+ const manifestPath = path.join(preseedDir, 'MERGE_MANIFEST.json');
1038
+
1039
+ console.log(`
1040
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Merge${utils.COLORS.reset}
1041
+ ${utils.COLORS.dim}Merging source documents from context folders${utils.COLORS.reset}
1042
+ `);
1043
+
1044
+ // Check if context folder exists
1045
+ if (!fs.existsSync(contextDir)) {
1046
+ utils.print.error('No context folder found');
1047
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed setup${utils.COLORS.reset} first`);
1048
+ return;
1049
+ }
1050
+
1051
+ // Document type mappings
1052
+ const docMappings = {
1053
+ 'vision': { output: 'VISION.md', folder: 'vision', title: 'Vision Document' },
1054
+ 'audience': { output: 'AUDIENCE.md', folder: 'audience', title: 'Target Audience' },
1055
+ 'market': { output: 'MARKET.md', folder: 'market', title: 'Market Analysis' },
1056
+ 'competitors': { output: 'COMPETITORS.md', folder: 'competitors', title: 'Competitive Analysis' },
1057
+ 'business': { output: 'BUSINESS_MODEL.md', folder: 'business', title: 'Business Model' },
1058
+ 'prd': { output: 'PRD.md', folder: 'prd', title: 'Product Requirements Document' },
1059
+ 'technical': { output: 'TECHNICAL_SPEC.md', folder: 'technical', title: 'Technical Specification' },
1060
+ 'roadmap': { output: 'ROADMAP.md', folder: 'roadmap', title: 'Product Roadmap' }
1061
+ };
1062
+
1063
+ // Detect what needs to be merged
1064
+ const mergeTasks = [];
1065
+
1066
+ for (const [docType, mapping] of Object.entries(docMappings)) {
1067
+ const folderPath = path.join(contextDir, mapping.folder);
1068
+ if (!fs.existsSync(folderPath)) continue;
1069
+
1070
+ const files = fs.readdirSync(folderPath).filter(f =>
1071
+ !f.startsWith('.') && (f.endsWith('.md') || f.endsWith('.txt'))
1072
+ );
1073
+
1074
+ if (files.length === 0) continue;
1075
+
1076
+ // Read all source files
1077
+ const sources = [];
1078
+ for (const file of files) {
1079
+ const filePath = path.join(folderPath, file);
1080
+ try {
1081
+ const content = fs.readFileSync(filePath, 'utf-8');
1082
+ sources.push({ filename: file, content, path: filePath });
1083
+ } catch (err) {
1084
+ console.log(` ${utils.COLORS.yellow}!${utils.COLORS.reset} Could not read ${file}`);
1085
+ }
1086
+ }
1087
+
1088
+ if (sources.length > 0) {
1089
+ mergeTasks.push({
1090
+ docType,
1091
+ outputFile: mapping.output,
1092
+ outputPath: path.join(preseedDir, mapping.output),
1093
+ title: mapping.title,
1094
+ sources
1095
+ });
1096
+ }
1097
+ }
1098
+
1099
+ if (mergeTasks.length === 0) {
1100
+ utils.print.warn('No source files found in context folders');
1101
+ console.log(`\nAdd files to ${utils.COLORS.cyan}.bootspring/preseed/context/${utils.COLORS.reset} folders`);
1102
+ return;
1103
+ }
1104
+
1105
+ // Check for existing manifest with settings
1106
+ let mergeSettings = {
1107
+ strategy: 'conservative',
1108
+ conflictResolution: 'prefer-detailed',
1109
+ includeChangelog: true
1110
+ };
1111
+
1112
+ if (fs.existsSync(manifestPath)) {
1113
+ try {
1114
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
1115
+ if (manifest.settings) {
1116
+ mergeSettings = manifest.settings;
1117
+ }
1118
+ } catch (err) {}
1119
+ }
1120
+
1121
+ console.log(`${utils.COLORS.bold}Merge settings:${utils.COLORS.reset}`);
1122
+ console.log(` Strategy: ${utils.COLORS.cyan}${mergeSettings.strategy}${utils.COLORS.reset}`);
1123
+ console.log(` Conflicts: ${utils.COLORS.cyan}${mergeSettings.conflictResolution}${utils.COLORS.reset}`);
1124
+ console.log(` Changelog: ${utils.COLORS.cyan}${mergeSettings.includeChangelog ? 'Yes' : 'No'}${utils.COLORS.reset}`);
1125
+ console.log();
1126
+
1127
+ // Execute merges
1128
+ const spinner = utils.createSpinner('Merging documents...').start();
1129
+ let merged = 0;
1130
+ let copied = 0;
1131
+
1132
+ for (const task of mergeTasks) {
1133
+ try {
1134
+ let mergedContent;
1135
+
1136
+ if (task.sources.length === 1) {
1137
+ // Single file - copy directly with header
1138
+ const src = task.sources[0];
1139
+ mergedContent = `<!-- Source: context/${task.docType}/${src.filename} -->\n\n${src.content}`;
1140
+ copied++;
1141
+ } else {
1142
+ // Multiple files - merge them
1143
+ mergedContent = mergeDocuments(task, mergeSettings);
1144
+ merged++;
1145
+ }
1146
+
1147
+ // Write merged output
1148
+ fs.writeFileSync(task.outputPath, mergedContent);
1149
+ } catch (err) {
1150
+ spinner.fail(`Failed to merge ${task.outputFile}: ${err.message}`);
1151
+ return;
1152
+ }
1153
+ }
1154
+
1155
+ spinner.succeed(`Merged ${merged + copied} documents (${merged} merged, ${copied} copied)`);
1156
+
1157
+ // Create minimal PRESEED_CONFIG.json so status works
1158
+ const configPath = path.join(preseedDir, 'PRESEED_CONFIG.json');
1159
+ const minimalConfig = {
1160
+ _meta: {
1161
+ version: '1.0.0',
1162
+ created: new Date().toISOString(),
1163
+ updated: new Date().toISOString(),
1164
+ preset: 'merged',
1165
+ source: 'preseed-merge'
1166
+ },
1167
+ identity: {
1168
+ name: path.basename(projectRoot),
1169
+ tagline: '',
1170
+ description: 'Project created from merged preseed documents'
1171
+ },
1172
+ _sync: {
1173
+ lastSync: new Date().toISOString(),
1174
+ mergedDocuments: mergeTasks.map(t => ({
1175
+ type: t.docType,
1176
+ output: t.outputFile,
1177
+ sources: t.sources.map(s => s.filename),
1178
+ mergedAt: new Date().toISOString()
1179
+ }))
1180
+ }
1181
+ };
1182
+
1183
+ fs.writeFileSync(configPath, JSON.stringify(minimalConfig, null, 2));
1184
+
1185
+ // Show results
1186
+ console.log(`\n${utils.COLORS.bold}Created documents:${utils.COLORS.reset}\n`);
1187
+ for (const task of mergeTasks) {
1188
+ const icon = task.sources.length === 1
1189
+ ? `${utils.COLORS.green}✓${utils.COLORS.reset}`
1190
+ : `${utils.COLORS.cyan}⊕${utils.COLORS.reset}`;
1191
+ const action = task.sources.length === 1 ? 'copied' : `merged ${task.sources.length} sources`;
1192
+ console.log(` ${icon} ${task.outputFile} (${action})`);
1193
+ }
1194
+
1195
+ console.log(`\n${utils.COLORS.green}${utils.COLORS.bold}Merge complete!${utils.COLORS.reset}`);
1196
+ console.log(`\n${utils.COLORS.dim}Documents saved to: ${preseedDir}${utils.COLORS.reset}`);
1197
+ console.log(`\n${utils.COLORS.bold}Next steps:${utils.COLORS.reset}`);
1198
+ console.log(` ${utils.COLORS.cyan}bootspring seed synthesize${utils.COLORS.reset} # Generate SEED.md`);
1199
+ console.log(` ${utils.COLORS.cyan}bootspring seed scaffold${utils.COLORS.reset} # Generate project`);
1200
+ }
1201
+
1202
+ /**
1203
+ * Merge multiple source documents into one
1204
+ * Uses conservative strategy: keeps all content, removes only exact duplicates
1205
+ */
1206
+ function mergeDocuments(task, settings) {
1207
+ const sources = task.sources;
1208
+ const sections = [];
1209
+ const changelog = {
1210
+ sources: [],
1211
+ duplicatesRemoved: [],
1212
+ conflicts: []
1213
+ };
1214
+
1215
+ // For conservative mode, we combine all unique content
1216
+ // For each source, extract sections and track where they came from
1217
+
1218
+ // Build merged content
1219
+ sections.push(`# ${task.title}\n`);
1220
+ sections.push('**Merged Document** - Generated by Bootspring');
1221
+ sections.push(`**Date:** ${new Date().toISOString().split('T')[0]}`);
1222
+ sections.push(`**Sources:** ${sources.map(s => s.filename).join(', ')}\n`);
1223
+ sections.push('---\n');
1224
+
1225
+ // Track all content blocks we've seen (for deduplication)
1226
+ const seenContent = new Set();
1227
+
1228
+ for (const source of sources) {
1229
+ changelog.sources.push(source.filename);
1230
+
1231
+ // Split content into paragraphs/blocks
1232
+ const blocks = source.content.split(/\n\n+/);
1233
+
1234
+ for (const block of blocks) {
1235
+ const trimmedBlock = block.trim();
1236
+ if (!trimmedBlock) continue;
1237
+
1238
+ // Normalize for comparison (remove extra whitespace)
1239
+ const normalized = trimmedBlock.replace(/\s+/g, ' ').toLowerCase();
1240
+
1241
+ // Check if we've seen this exact content
1242
+ if (seenContent.has(normalized)) {
1243
+ changelog.duplicatesRemoved.push(`Duplicate block from ${source.filename}`);
1244
+ continue;
1245
+ }
1246
+
1247
+ seenContent.add(normalized);
1248
+ sections.push(trimmedBlock);
1249
+ sections.push(''); // Add spacing
1250
+ }
1251
+ }
1252
+
1253
+ // Add changelog if requested
1254
+ if (settings.includeChangelog) {
1255
+ sections.push('\n---\n');
1256
+ sections.push('## Merge Changelog\n');
1257
+ sections.push(`**Merge Strategy:** ${settings.strategy}`);
1258
+ sections.push(`**Conflict Resolution:** ${settings.conflictResolution}\n`);
1259
+
1260
+ sections.push('### Sources Used');
1261
+ for (const src of changelog.sources) {
1262
+ sections.push(`- ${src}`);
1263
+ }
1264
+
1265
+ if (changelog.duplicatesRemoved.length > 0) {
1266
+ sections.push('\n### Duplicates Removed');
1267
+ sections.push(`- ${changelog.duplicatesRemoved.length} duplicate blocks removed`);
1268
+ }
1269
+
1270
+ sections.push('\n---');
1271
+ sections.push('*Merged by Bootspring CLI*');
1272
+ }
1273
+
1274
+ return sections.join('\n');
1275
+ }
1276
+
1277
+ // =============================================================================
1278
+ // Workflow Commands
1279
+ // =============================================================================
1280
+
1281
+ /**
1282
+ * Start a new workflow or resume existing
1283
+ */
1284
+ async function workflowStart(args) {
1285
+ const projectRoot = config.findProjectRoot();
1286
+ const workflow = new PreseedWorkflowEngine(projectRoot);
1287
+
1288
+ console.log(`
1289
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Workflow${utils.COLORS.reset}
1290
+ ${utils.COLORS.dim}Professional-grade document approval workflow${utils.COLORS.reset}
1291
+ `);
1292
+
1293
+ // Check for existing workflow
1294
+ if (workflow.hasWorkflow()) {
1295
+ workflow.loadState();
1296
+ const resume = workflow.getResumePoint();
1297
+
1298
+ if (resume) {
1299
+ console.log(`${utils.COLORS.yellow}Existing workflow found${utils.COLORS.reset}`);
1300
+ console.log(`${utils.COLORS.dim}Last updated: ${utils.formatRelativeTime(new Date(resume.lastUpdated))}${utils.COLORS.reset}`);
1301
+ console.log(`${utils.COLORS.dim}Current: ${resume.phaseName} → ${resume.documentName}${utils.COLORS.reset}\n`);
1302
+
1303
+ const rl = createInterface();
1304
+ const choice = await askChoice(rl, 'What would you like to do?', [
1305
+ { label: 'Resume workflow', value: 'resume' },
1306
+ { label: 'View status', value: 'status' },
1307
+ { label: 'Start fresh (reset)', value: 'reset' }
1308
+ ]);
1309
+ rl.close();
1310
+
1311
+ if (choice === 'resume') {
1312
+ return runWorkflowLoop(workflow);
1313
+ } else if (choice === 'status') {
1314
+ return workflowStatus(args);
1315
+ } else if (choice === 'reset') {
1316
+ workflow.resetWorkflow();
1317
+ console.log(`${utils.COLORS.green}✓${utils.COLORS.reset} Workflow reset\n`);
1318
+ }
1319
+ }
1320
+ }
1321
+
1322
+ // Initialize new workflow
1323
+ const spinner = utils.createSpinner('Initializing workflow...').start();
1324
+ workflow.initializeWorkflow();
1325
+ spinner.succeed('Workflow initialized');
1326
+
1327
+ // Run the workflow loop
1328
+ return runWorkflowLoop(workflow);
1329
+ }
1330
+
1331
+ /**
1332
+ * Resume an existing workflow
1333
+ */
1334
+ async function workflowResume(_args) {
1335
+ const projectRoot = config.findProjectRoot();
1336
+ const workflow = new PreseedWorkflowEngine(projectRoot);
1337
+
1338
+ console.log(`
1339
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed Workflow${utils.COLORS.reset}
1340
+ ${utils.COLORS.dim}Resuming workflow...${utils.COLORS.reset}
1341
+ `);
1342
+
1343
+ if (!workflow.hasWorkflow()) {
1344
+ utils.print.error('No workflow found');
1345
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed workflow start${utils.COLORS.reset} to begin`);
1346
+ return;
1347
+ }
1348
+
1349
+ workflow.loadState();
1350
+ const resume = workflow.getResumePoint();
1351
+
1352
+ if (!resume) {
1353
+ utils.print.success('Workflow is complete!');
1354
+ return;
1355
+ }
1356
+
1357
+ console.log(`${utils.COLORS.bold}Resuming:${utils.COLORS.reset} ${resume.phaseName} → ${resume.documentName}`);
1358
+ console.log(`${utils.COLORS.dim}Status: ${resume.documentStatus}${utils.COLORS.reset}\n`);
1359
+
1360
+ return runWorkflowLoop(workflow);
1361
+ }
1362
+
1363
+ /**
1364
+ * Show workflow status
1365
+ */
1366
+ async function workflowStatus(_args) {
1367
+ const projectRoot = config.findProjectRoot();
1368
+ const workflow = new PreseedWorkflowEngine(projectRoot);
1369
+
1370
+ console.log(`
1371
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Preseed Workflow Status${utils.COLORS.reset}
1372
+ `);
1373
+
1374
+ if (!workflow.hasWorkflow()) {
1375
+ console.log(`${utils.COLORS.yellow}○${utils.COLORS.reset} No workflow started`);
1376
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed workflow start${utils.COLORS.reset} to begin`);
1377
+ return;
1378
+ }
1379
+
1380
+ workflow.loadState();
1381
+ const progress = workflow.getProgress();
1382
+
1383
+ if (!progress) {
1384
+ utils.print.error('Could not load workflow state');
1385
+ return;
1386
+ }
1387
+
1388
+ // Overall progress bar
1389
+ const barWidth = 30;
1390
+ const filledWidth = Math.round((progress.overall.percentage / 100) * barWidth);
1391
+ const progressBar = '█'.repeat(filledWidth) + '░'.repeat(barWidth - filledWidth);
1392
+ console.log(`${utils.COLORS.bold}Overall Progress:${utils.COLORS.reset} [${utils.COLORS.green}${progressBar}${utils.COLORS.reset}] ${progress.overall.percentage}%`);
1393
+ console.log(`${utils.COLORS.dim}${progress.overall.approved}/${progress.overall.total} documents approved${utils.COLORS.reset}\n`);
1394
+
1395
+ // Phase details
1396
+ console.log(`${utils.COLORS.bold}Phases:${utils.COLORS.reset}`);
1397
+
1398
+ for (const phase of progress.phases) {
1399
+ let statusIcon;
1400
+ let statusColor = utils.COLORS.reset;
1401
+
1402
+ if (phase.status === 'completed') {
1403
+ statusIcon = `${utils.COLORS.green}✓${utils.COLORS.reset}`;
1404
+ } else if (phase.id === progress.currentPhase) {
1405
+ statusIcon = `${utils.COLORS.cyan}▶${utils.COLORS.reset}`;
1406
+ statusColor = utils.COLORS.cyan;
1407
+ } else if (!phase.dependenciesMet) {
1408
+ statusIcon = `${utils.COLORS.dim}○${utils.COLORS.reset}`;
1409
+ statusColor = utils.COLORS.dim;
1410
+ } else {
1411
+ statusIcon = `${utils.COLORS.yellow}○${utils.COLORS.reset}`;
1412
+ }
1413
+
1414
+ console.log(`\n ${statusIcon} ${statusColor}${phase.name}${utils.COLORS.reset} ${utils.COLORS.dim}(${phase.progress.approved}/${phase.progress.total})${utils.COLORS.reset}`);
1415
+
1416
+ for (const doc of phase.documents) {
1417
+ let docIcon;
1418
+ let docColor = utils.COLORS.reset;
1419
+
1420
+ switch (doc.status) {
1421
+ case DOCUMENT_STATUS.APPROVED:
1422
+ case DOCUMENT_STATUS.LOCKED:
1423
+ docIcon = `${utils.COLORS.green}✓${utils.COLORS.reset}`;
1424
+ break;
1425
+ case DOCUMENT_STATUS.IN_REVIEW:
1426
+ docIcon = `${utils.COLORS.yellow}◎${utils.COLORS.reset}`;
1427
+ docColor = utils.COLORS.yellow;
1428
+ break;
1429
+ case DOCUMENT_STATUS.DRAFT:
1430
+ docIcon = `${utils.COLORS.blue}◇${utils.COLORS.reset}`;
1431
+ docColor = utils.COLORS.blue;
1432
+ break;
1433
+ case DOCUMENT_STATUS.REJECTED:
1434
+ docIcon = `${utils.COLORS.red}✗${utils.COLORS.reset}`;
1435
+ docColor = utils.COLORS.red;
1436
+ break;
1437
+ default:
1438
+ docIcon = `${utils.COLORS.dim}○${utils.COLORS.reset}`;
1439
+ docColor = utils.COLORS.dim;
1440
+ }
1441
+
1442
+ const qualityBadge = doc.qualityScore !== null
1443
+ ? ` ${utils.COLORS.dim}[${doc.qualityScore}%]${utils.COLORS.reset}`
1444
+ : '';
1445
+
1446
+ const currentMarker = doc.type === progress.currentDocument
1447
+ ? ` ${utils.COLORS.cyan}← current${utils.COLORS.reset}`
1448
+ : '';
1449
+
1450
+ console.log(` ${docIcon} ${docColor}${doc.name}${utils.COLORS.reset}${qualityBadge}${currentMarker}`);
1451
+ }
1452
+ }
1453
+
1454
+ // Legend
1455
+ console.log(`
1456
+ ${utils.COLORS.dim}Legend: ✓ approved ◎ in review ◇ draft ✗ rejected ○ pending${utils.COLORS.reset}`);
1457
+
1458
+ if (progress.isComplete) {
1459
+ console.log(`\n${utils.COLORS.green}${utils.COLORS.bold}🎉 Workflow Complete!${utils.COLORS.reset}`);
1460
+ console.log(`${utils.COLORS.dim}All documents have been approved.${utils.COLORS.reset}`);
1461
+ }
1462
+ }
1463
+
1464
+ /**
1465
+ * Reset workflow
1466
+ */
1467
+ async function workflowReset(_args) {
1468
+ const projectRoot = config.findProjectRoot();
1469
+ const workflow = new PreseedWorkflowEngine(projectRoot);
1470
+
1471
+ if (!workflow.hasWorkflow()) {
1472
+ console.log(`${utils.COLORS.yellow}○${utils.COLORS.reset} No workflow to reset`);
1473
+ return;
1474
+ }
1475
+
1476
+ const rl = createInterface();
1477
+ const confirm = await askChoice(rl, 'Are you sure you want to reset the workflow? This will lose all progress.', [
1478
+ { label: 'Cancel', value: 'cancel' },
1479
+ { label: 'Reset', value: 'reset' }
1480
+ ]);
1481
+ rl.close();
1482
+
1483
+ if (confirm === 'reset') {
1484
+ workflow.resetWorkflow();
1485
+ utils.print.success('Workflow reset');
1486
+ } else {
1487
+ console.log('Cancelled');
1488
+ }
1489
+ }
1490
+
1491
+ /**
1492
+ * Main workflow loop
1493
+ */
1494
+ async function runWorkflowLoop(workflow) {
1495
+ const projectRoot = config.findProjectRoot();
1496
+ const engine = new PreseedEngine(projectRoot);
1497
+
1498
+ // Load preseed config if available
1499
+ engine.loadConfig();
1500
+
1501
+ while (true) {
1502
+ const resume = workflow.getResumePoint();
1503
+ if (!resume) {
1504
+ // Workflow complete
1505
+ console.log(`\n${utils.COLORS.green}${utils.COLORS.bold}🎉 Workflow Complete!${utils.COLORS.reset}`);
1506
+ console.log(`${utils.COLORS.dim}All documents have been approved.${utils.COLORS.reset}`);
1507
+ console.log(`\n${utils.COLORS.bold}Approved documents:${utils.COLORS.reset} ${workflow.approvedDir}`);
1508
+ console.log(`\n${utils.COLORS.bold}Next steps:${utils.COLORS.reset}`);
1509
+ console.log(` ${utils.COLORS.cyan}bootspring seed init${utils.COLORS.reset} - Scaffold your project`);
1510
+ break;
1511
+ }
1512
+
1513
+ // Show current position
1514
+ console.log(`\n${utils.COLORS.cyan}${'─'.repeat(60)}${utils.COLORS.reset}`);
1515
+ console.log(`${utils.COLORS.bold}Phase: ${resume.phaseName}${utils.COLORS.reset}`);
1516
+ console.log(`${utils.COLORS.cyan}${'─'.repeat(60)}${utils.COLORS.reset}`);
1517
+ console.log(`\n${utils.COLORS.bold}Document: ${resume.documentName}${utils.COLORS.reset}`);
1518
+
1519
+ const doc = workflow.state.documents[resume.document];
1520
+ const docType = resume.document;
1521
+
1522
+ // Handle based on document status
1523
+ if (doc.status === DOCUMENT_STATUS.EMPTY || doc.status === DOCUMENT_STATUS.REJECTED) {
1524
+ // Need to generate or upload
1525
+ if (doc.status === DOCUMENT_STATUS.REJECTED && doc.feedback.length > 0) {
1526
+ const lastFeedback = doc.feedback[doc.feedback.length - 1];
1527
+ console.log(`\n${utils.COLORS.yellow}Previous rejection:${utils.COLORS.reset} ${lastFeedback.message}`);
1528
+ }
1529
+
1530
+ const rl = createInterface();
1531
+ const action = await askChoice(rl, '\nNo document found. What would you like to do?', [
1532
+ { label: 'Generate (answer questions)', value: 'generate', description: 'AI-assisted generation' },
1533
+ { label: 'Upload existing file', value: 'upload', description: 'Import from path' },
1534
+ { label: 'Skip (not recommended)', value: 'skip', description: 'Continue without this document' },
1535
+ { label: 'Save and exit', value: 'exit', description: 'Resume later' }
1536
+ ]);
1537
+
1538
+ if (action === 'exit') {
1539
+ rl.close();
1540
+ console.log(`\n${utils.COLORS.dim}Progress saved. Run ${utils.COLORS.cyan}bootspring preseed workflow resume${utils.COLORS.reset}${utils.COLORS.dim} to continue.${utils.COLORS.reset}`);
1541
+ break;
1542
+ }
1543
+
1544
+ if (action === 'skip') {
1545
+ // Ask for confirmation
1546
+ const confirmSkip = await askChoice(rl, 'Are you sure? Skipping may cause issues later.', [
1547
+ { label: 'Go back', value: 'back' },
1548
+ { label: 'Skip anyway', value: 'skip' }
1549
+ ]);
1550
+
1551
+ if (confirmSkip === 'skip') {
1552
+ // Override the phase gate
1553
+ const justification = await askText(rl, 'Provide a brief justification for skipping');
1554
+ workflow.overridePhaseGate(workflow.state.currentPhase, justification || 'User chose to skip');
1555
+
1556
+ // Create empty approved doc
1557
+ const content = `# ${resume.documentName}\n\n> Skipped during workflow\n\nJustification: ${justification || 'No justification provided'}\n`;
1558
+ workflow.createDraft(docType, content, 'skipped');
1559
+ workflow.approveDocument(docType);
1560
+ rl.close();
1561
+ continue;
1562
+ }
1563
+ rl.close();
1564
+ continue;
1565
+ }
1566
+
1567
+ if (action === 'upload') {
1568
+ const filePath = await askText(rl, 'Enter file path');
1569
+ rl.close();
1570
+
1571
+ if (!filePath || !fs.existsSync(filePath)) {
1572
+ utils.print.error('File not found');
1573
+ continue;
1574
+ }
1575
+
1576
+ try {
1577
+ const result = workflow.importDocument(docType, filePath);
1578
+ utils.print.success(`Imported ${result.path}`);
1579
+ } catch (err) {
1580
+ utils.print.error(`Import failed: ${err.message}`);
1581
+ continue;
1582
+ }
1583
+ }
1584
+
1585
+ if (action === 'generate') {
1586
+ rl.close();
1587
+
1588
+ // Generate using preseed engine
1589
+ console.log(`\n${utils.COLORS.dim}Generating document...${utils.COLORS.reset}`);
1590
+
1591
+ // If we have a config, use it to generate
1592
+ let content;
1593
+ if (engine.config) {
1594
+ content = engine.generateDocument(docType);
1595
+ } else {
1596
+ // Fallback: create a template
1597
+ content = generateDocumentTemplate(docType, resume.documentName);
1598
+ }
1599
+
1600
+ const result = workflow.createDraft(docType, content, 'generated');
1601
+ utils.print.success(`Draft created: ${result.path} (v${result.version})`);
1602
+ }
1603
+ }
1604
+
1605
+ // Now we should have a draft - show quality and review options
1606
+ if (doc.status === DOCUMENT_STATUS.DRAFT || doc.status === DOCUMENT_STATUS.IN_REVIEW) {
1607
+ const content = workflow.readDraft(docType);
1608
+ const quality = workflow.calculateQualityScore(docType, content);
1609
+
1610
+ console.log(`\n${utils.COLORS.bold}Quality Score:${utils.COLORS.reset} ${formatQualityScore(quality.score)}`);
1611
+ console.log(` Completeness: ${formatQualityScore(quality.completeness)}`);
1612
+ console.log(` Clarity: ${formatQualityScore(quality.clarity)}`);
1613
+
1614
+ // Show failed checks
1615
+ const allChecks = [
1616
+ ...(quality.breakdown.completeness?.checks || []),
1617
+ ...(quality.breakdown.clarity?.checks || [])
1618
+ ];
1619
+ const failedChecks = allChecks.filter(c => !c.passed);
1620
+
1621
+ if (failedChecks.length > 0 && failedChecks.length <= 5) {
1622
+ console.log(`\n${utils.COLORS.yellow}Suggestions:${utils.COLORS.reset}`);
1623
+ for (const check of failedChecks.slice(0, 5)) {
1624
+ console.log(` ${utils.COLORS.dim}○${utils.COLORS.reset} ${check.label}`);
1625
+ }
1626
+ }
1627
+
1628
+ const rl = createInterface();
1629
+ const reviewAction = await askChoice(rl, '\nWhat would you like to do?', [
1630
+ { label: 'Approve and continue', value: 'approve', description: 'Accept this version' },
1631
+ { label: 'Edit (opens in $EDITOR)', value: 'edit', description: 'Make changes' },
1632
+ { label: 'Regenerate', value: 'regenerate', description: 'Start fresh' },
1633
+ { label: 'View document', value: 'view', description: 'Show content' },
1634
+ { label: 'Save and exit', value: 'exit', description: 'Resume later' }
1635
+ ]);
1636
+
1637
+ if (reviewAction === 'exit') {
1638
+ rl.close();
1639
+ console.log(`\n${utils.COLORS.dim}Progress saved. Run ${utils.COLORS.cyan}bootspring preseed workflow resume${utils.COLORS.reset}${utils.COLORS.dim} to continue.${utils.COLORS.reset}`);
1640
+ break;
1641
+ }
1642
+
1643
+ if (reviewAction === 'view') {
1644
+ rl.close();
1645
+ console.log(`\n${utils.COLORS.dim}${'─'.repeat(60)}${utils.COLORS.reset}`);
1646
+ console.log(content);
1647
+ console.log(`${utils.COLORS.dim}${'─'.repeat(60)}${utils.COLORS.reset}`);
1648
+ continue;
1649
+ }
1650
+
1651
+ if (reviewAction === 'edit') {
1652
+ rl.close();
1653
+ console.log(`\n${utils.COLORS.dim}Opening in editor...${utils.COLORS.reset}`);
1654
+
1655
+ try {
1656
+ const editResult = await workflow.openInEditor(docType);
1657
+
1658
+ if (editResult.changed) {
1659
+ utils.print.success('Changes detected');
1660
+ } else {
1661
+ console.log(`${utils.COLORS.dim}No changes made${utils.COLORS.reset}`);
1662
+ }
1663
+ } catch (err) {
1664
+ utils.print.error(`Editor failed: ${err.message}`);
1665
+ }
1666
+ continue;
1667
+ }
1668
+
1669
+ if (reviewAction === 'regenerate') {
1670
+ rl.close();
1671
+
1672
+ let content;
1673
+ if (engine.config) {
1674
+ content = engine.generateDocument(docType);
1675
+ } else {
1676
+ content = generateDocumentTemplate(docType, resume.documentName);
1677
+ }
1678
+
1679
+ const result = workflow.createDraft(docType, content, 'regenerated');
1680
+ utils.print.success(`New draft created: v${result.version}`);
1681
+ continue;
1682
+ }
1683
+
1684
+ if (reviewAction === 'approve') {
1685
+ rl.close();
1686
+
1687
+ try {
1688
+ const result = workflow.approveDocument(docType);
1689
+ utils.print.success(`${resume.documentName} approved! (v${doc.version})`);
1690
+
1691
+ if (result.workflowComplete) {
1692
+ continue; // Will exit loop at top
1693
+ }
1694
+
1695
+ if (result.next) {
1696
+ console.log(`\n${utils.COLORS.dim}Moving to: ${result.next.document}${utils.COLORS.reset}`);
1697
+ }
1698
+ } catch (err) {
1699
+ utils.print.error(`Approval failed: ${err.message}`);
1700
+ }
1701
+ continue;
1702
+ }
1703
+
1704
+ rl.close();
1705
+ }
1706
+ }
1707
+ }
1708
+
1709
+ /**
1710
+ * Generate a document template
1711
+ */
1712
+ function generateDocumentTemplate(docType, name) {
1713
+ const date = new Date().toISOString().split('T')[0];
1714
+ const templates = {
1715
+ vision: `# Vision Document
1716
+
1717
+ **Generated:** ${date}
1718
+
1719
+ ## The Problem
1720
+
1721
+ *Describe the problem you're solving...*
1722
+
1723
+ ## Our Solution
1724
+
1725
+ *Describe your solution...*
1726
+
1727
+ ### Key Features
1728
+
1729
+ - Feature 1
1730
+ - Feature 2
1731
+ - Feature 3
1732
+
1733
+ ### Unique Value Proposition
1734
+
1735
+ *What makes your solution unique?*
1736
+
1737
+ ---
1738
+ *This is a draft. Edit and approve to continue.*
1739
+ `,
1740
+ audience: `# Target Audience
1741
+
1742
+ **Generated:** ${date}
1743
+
1744
+ ## Primary Audience
1745
+
1746
+ *Describe your primary audience...*
1747
+
1748
+ ## Market Segments
1749
+
1750
+ 1. Segment 1
1751
+ 2. Segment 2
1752
+
1753
+ ## User Personas
1754
+
1755
+ ### Persona 1
1756
+
1757
+ **Role:** *Job title*
1758
+ **Goals:** *What they want to achieve*
1759
+ **Pain Points:** *What frustrates them*
1760
+
1761
+ ---
1762
+ *This is a draft. Edit and approve to continue.*
1763
+ `,
1764
+ market: `# Market Analysis
1765
+
1766
+ **Generated:** ${date}
1767
+
1768
+ ## Market Size
1769
+
1770
+ | Metric | Value |
1771
+ |--------|-------|
1772
+ | TAM | $X billion |
1773
+ | SAM | $X million |
1774
+ | SOM | $X million |
1775
+
1776
+ ## Market Trends
1777
+
1778
+ - Trend 1
1779
+ - Trend 2
1780
+
1781
+ ---
1782
+ *This is a draft. Edit and approve to continue.*
1783
+ `,
1784
+ competitors: `# Competitive Analysis
1785
+
1786
+ **Generated:** ${date}
1787
+
1788
+ ## Direct Competitors
1789
+
1790
+ | Company | Strengths | Weaknesses |
1791
+ |---------|-----------|------------|
1792
+ | Competitor 1 | ... | ... |
1793
+
1794
+ ## Our Positioning
1795
+
1796
+ *How we differentiate...*
1797
+
1798
+ ## Key Differentiators
1799
+
1800
+ 1. Differentiator 1
1801
+ 2. Differentiator 2
1802
+
1803
+ ---
1804
+ *This is a draft. Edit and approve to continue.*
1805
+ `,
1806
+ 'business-model': `# Business Model
1807
+
1808
+ **Generated:** ${date}
1809
+
1810
+ ## Model Type
1811
+
1812
+ *Subscription / Freemium / Usage-based / etc.*
1813
+
1814
+ ## Revenue Streams
1815
+
1816
+ 1. Primary revenue source
1817
+ 2. Secondary revenue source
1818
+
1819
+ ## Pricing Strategy
1820
+
1821
+ | Tier | Price | Features |
1822
+ |------|-------|----------|
1823
+ | Free | $0 | Basic |
1824
+ | Pro | $X/mo | Advanced |
1825
+
1826
+ ---
1827
+ *This is a draft. Edit and approve to continue.*
1828
+ `,
1829
+ prd: `# Product Requirements Document
1830
+
1831
+ **Generated:** ${date}
1832
+ **Version:** 1.0
1833
+
1834
+ ## Product Vision
1835
+
1836
+ *What we're building and why...*
1837
+
1838
+ ## MVP Features
1839
+
1840
+ | Feature | Priority | Description |
1841
+ |---------|----------|-------------|
1842
+ | Feature 1 | P1 | ... |
1843
+ | Feature 2 | P1 | ... |
1844
+
1845
+ ## User Stories
1846
+
1847
+ ### US-001: User Login
1848
+
1849
+ **As a** user
1850
+ **I want to** log in to my account
1851
+ **So that** I can access my data
1852
+
1853
+ **Acceptance Criteria:**
1854
+ - [ ] User can enter email/password
1855
+ - [ ] System validates credentials
1856
+ - [ ] User is redirected to dashboard
1857
+
1858
+ ---
1859
+ *This is a draft. Edit and approve to continue.*
1860
+ `,
1861
+ 'technical-spec': `# Technical Specification
1862
+
1863
+ **Generated:** ${date}
1864
+
1865
+ ## Technology Stack
1866
+
1867
+ | Component | Technology |
1868
+ |-----------|------------|
1869
+ | Frontend | ... |
1870
+ | Backend | ... |
1871
+ | Database | ... |
1872
+
1873
+ ## Architecture Overview
1874
+
1875
+ *High-level architecture description...*
1876
+
1877
+ ## Third-Party Integrations
1878
+
1879
+ - Integration 1
1880
+ - Integration 2
1881
+
1882
+ ---
1883
+ *This is a draft. Edit and approve to continue.*
1884
+ `,
1885
+ roadmap: `# Product Roadmap
1886
+
1887
+ **Generated:** ${date}
1888
+
1889
+ ## Development Phases
1890
+
1891
+ ### Phase 1: MVP
1892
+
1893
+ **Duration:** X weeks
1894
+
1895
+ **Goals:**
1896
+ - Launch core features
1897
+ - Validate with early users
1898
+
1899
+ **Deliverables:**
1900
+ - [ ] Core feature 1
1901
+ - [ ] Core feature 2
1902
+
1903
+ ### Phase 2: Growth
1904
+
1905
+ **Duration:** X months
1906
+
1907
+ **Goals:**
1908
+ - Scale user base
1909
+ - Add key features
1910
+
1911
+ ## Key Milestones
1912
+
1913
+ | Milestone | Target Date | Status |
1914
+ |-----------|-------------|--------|
1915
+ | MVP Launch | TBD | Planned |
1916
+ | Beta | TBD | Planned |
1917
+
1918
+ ---
1919
+ *This is a draft. Edit and approve to continue.*
1920
+ `
1921
+ };
1922
+
1923
+ return templates[docType] || `# ${name}\n\n**Generated:** ${date}\n\n*Add content here...*\n`;
1924
+ }
1925
+
1926
+ /**
1927
+ * Format quality score with color
1928
+ */
1929
+ function formatQualityScore(score) {
1930
+ if (score >= 80) {
1931
+ return `${utils.COLORS.green}${score}%${utils.COLORS.reset}`;
1932
+ } else if (score >= 60) {
1933
+ return `${utils.COLORS.yellow}${score}%${utils.COLORS.reset}`;
1934
+ } else {
1935
+ return `${utils.COLORS.red}${score}%${utils.COLORS.reset}`;
1936
+ }
1937
+ }
1938
+
1939
+ // =============================================================================
1940
+ // Document Commands
1941
+ // =============================================================================
1942
+
1943
+ /**
1944
+ * Handle document-specific commands
1945
+ */
1946
+ async function handleDocCommand(args) {
1947
+ const projectRoot = config.findProjectRoot();
1948
+ const workflow = new PreseedWorkflowEngine(projectRoot);
1949
+ const docType = args._[0];
1950
+ const action = args._[1];
1951
+
1952
+ if (!docType || !action) {
1953
+ utils.print.error('Usage: bootspring preseed doc <type> <action>');
1954
+ console.log('\nActions: approve, reject, edit, review, view');
1955
+ console.log(`Types: ${Object.keys(DOCUMENT_TYPES).join(', ')}`);
1956
+ return;
1957
+ }
1958
+
1959
+ if (!DOCUMENT_TYPES[docType]) {
1960
+ utils.print.error(`Unknown document type: ${docType}`);
1961
+ console.log(`\nAvailable types: ${Object.keys(DOCUMENT_TYPES).join(', ')}`);
1962
+ return;
1963
+ }
1964
+
1965
+ if (!workflow.hasWorkflow()) {
1966
+ utils.print.error('No workflow found');
1967
+ console.log(`\nRun ${utils.COLORS.cyan}bootspring preseed workflow start${utils.COLORS.reset} first`);
1968
+ return;
1969
+ }
1970
+
1971
+ workflow.loadState();
1972
+
1973
+ switch (action) {
1974
+ case 'approve': {
1975
+ try {
1976
+ const result = workflow.approveDocument(docType);
1977
+ utils.print.success(`${DOCUMENT_TYPES[docType].name} approved!`);
1978
+
1979
+ if (result.next) {
1980
+ console.log(`\n${utils.COLORS.dim}Next: ${result.next.document}${utils.COLORS.reset}`);
1981
+ } else if (result.workflowComplete) {
1982
+ console.log(`\n${utils.COLORS.green}Workflow complete!${utils.COLORS.reset}`);
1983
+ }
1984
+ } catch (err) {
1985
+ utils.print.error(err.message);
1986
+ }
1987
+ break;
1988
+ }
1989
+
1990
+ case 'reject': {
1991
+ const rl = createInterface();
1992
+ const feedback = await askText(rl, 'Rejection feedback');
1993
+ rl.close();
1994
+
1995
+ try {
1996
+ workflow.rejectDocument(docType, feedback);
1997
+ utils.print.success(`${DOCUMENT_TYPES[docType].name} rejected`);
1998
+ console.log(`${utils.COLORS.dim}Feedback: ${feedback}${utils.COLORS.reset}`);
1999
+ } catch (err) {
2000
+ utils.print.error(err.message);
2001
+ }
2002
+ break;
2003
+ }
2004
+
2005
+ case 'edit': {
2006
+ try {
2007
+ console.log(`${utils.COLORS.dim}Opening in editor...${utils.COLORS.reset}`);
2008
+ const result = await workflow.openInEditor(docType);
2009
+
2010
+ if (result.changed) {
2011
+ utils.print.success('Changes saved');
2012
+ } else {
2013
+ console.log(`${utils.COLORS.dim}No changes made${utils.COLORS.reset}`);
2014
+ }
2015
+ } catch (err) {
2016
+ utils.print.error(err.message);
2017
+ }
2018
+ break;
2019
+ }
2020
+
2021
+ case 'review': {
2022
+ try {
2023
+ const quality = workflow.submitForReview(docType);
2024
+ utils.print.success(`${DOCUMENT_TYPES[docType].name} submitted for review`);
2025
+ console.log(`\n${utils.COLORS.bold}Quality Score:${utils.COLORS.reset} ${formatQualityScore(quality.score)}`);
2026
+ } catch (err) {
2027
+ utils.print.error(err.message);
2028
+ }
2029
+ break;
2030
+ }
2031
+
2032
+ case 'view': {
2033
+ const content = workflow.readDraft(docType);
2034
+ if (content) {
2035
+ console.log(`\n${utils.COLORS.dim}${'─'.repeat(60)}${utils.COLORS.reset}`);
2036
+ console.log(content);
2037
+ console.log(`${utils.COLORS.dim}${'─'.repeat(60)}${utils.COLORS.reset}`);
2038
+ } else {
2039
+ utils.print.error(`No draft found for ${docType}`);
2040
+ }
2041
+ break;
2042
+ }
2043
+
2044
+ default:
2045
+ utils.print.error(`Unknown action: ${action}`);
2046
+ console.log('\nActions: approve, reject, edit, review, view');
2047
+ }
2048
+ }
2049
+
2050
+ /**
2051
+ * Show help
2052
+ */
2053
+ function showHelp() {
2054
+ console.log(`
2055
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Preseed${utils.COLORS.reset}
2056
+ ${utils.COLORS.dim}Generate foundational documents from minimal input${utils.COLORS.reset}
2057
+
2058
+ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
2059
+ bootspring preseed <command> [options]
2060
+
2061
+ ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
2062
+ ${utils.COLORS.green}start${utils.COLORS.reset} ${utils.COLORS.bold}Smart entry point - detects context & guides you${utils.COLORS.reset} ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
2063
+ ${utils.COLORS.cyan}setup${utils.COLORS.reset} Create context input folders for early docs
2064
+ ${utils.COLORS.cyan}init${utils.COLORS.reset} Interactive wizard to create preseed
2065
+ ${utils.COLORS.cyan}generate${utils.COLORS.reset} Regenerate all documents
2066
+ ${utils.COLORS.cyan}sync${utils.COLORS.reset} Sync documents with config changes
2067
+ ${utils.COLORS.cyan}status${utils.COLORS.reset} Show preseed status (default)
2068
+ ${utils.COLORS.cyan}update${utils.COLORS.reset} <path> Update a config value
2069
+ ${utils.COLORS.cyan}export${utils.COLORS.reset} Export preseed configuration
2070
+ ${utils.COLORS.cyan}pull${utils.COLORS.reset} Download documents from dashboard ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
2071
+ ${utils.COLORS.cyan}push${utils.COLORS.reset} Upload documents to dashboard ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
2072
+ ${utils.COLORS.cyan}merge${utils.COLORS.reset} Merge source docs from context folders ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
2073
+
2074
+ ${utils.COLORS.bold}Init Options:${utils.COLORS.reset}
2075
+ --preset=<preset> Document preset (essential, startup, full, technical, investor)
2076
+ --quick Quick mode with minimal questions
2077
+
2078
+ ${utils.COLORS.bold}Generate Options:${utils.COLORS.reset}
2079
+ --doc=<type> Generate a single document type
2080
+
2081
+ ${utils.COLORS.bold}Export Options:${utils.COLORS.reset}
2082
+ --format=<fmt> Output format: json (default), yaml
2083
+ --output=<file> Write to file instead of stdout
2084
+
2085
+ ${utils.COLORS.bold}Pull/Push Options:${utils.COLORS.reset}
2086
+ --project=<id> Project ID (uses current project if not specified)
2087
+ --doc=<name> Pull/push a single document (e.g., VISION, PRD)
2088
+ --force Overwrite without prompting
2089
+
2090
+ ${utils.COLORS.bold}Document Types:${utils.COLORS.reset}
2091
+ vision Problem statement, solution overview
2092
+ audience Target audience, personas, ICP
2093
+ market TAM/SAM/SOM analysis
2094
+ competitors Competitive landscape
2095
+ business-model Revenue model, pricing strategy
2096
+ prd Product requirements, user stories
2097
+ technical-spec Architecture, tech stack
2098
+ roadmap Development phases, milestones
2099
+
2100
+ ${utils.COLORS.bold}Presets:${utils.COLORS.reset}
2101
+ essential ${PRESETS.essential.length} docs - Vision, Audience, Business Model, PRD
2102
+ startup ${PRESETS.startup.length} docs - Full startup pack (default)
2103
+ full ${PRESETS.full.length} docs - Complete documentation suite
2104
+ technical ${PRESETS.technical.length} docs - Technical focus
2105
+ investor ${PRESETS.investor.length} docs - Investor-ready materials
2106
+
2107
+ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
2108
+ ${utils.COLORS.green}bootspring preseed start${utils.COLORS.reset} # Smart guide (recommended)
2109
+ bootspring preseed setup # Create context folders first
2110
+ bootspring preseed init # Full interactive wizard
2111
+ bootspring preseed init --quick # Quick mode
2112
+ bootspring preseed init --preset=investor # Investor-ready docs
2113
+ bootspring preseed generate # Regenerate all docs
2114
+ bootspring preseed generate --doc=prd # Regenerate PRD only
2115
+ bootspring preseed sync # Sync after config changes
2116
+ bootspring preseed update identity.name "My App"
2117
+ bootspring preseed pull # Download all from dashboard
2118
+ bootspring preseed pull --doc=VISION # Download single document
2119
+ bootspring preseed push # Upload all to dashboard
2120
+ bootspring preseed push --doc=PRD # Upload single document
2121
+
2122
+ ${utils.COLORS.bold}Quick Start (Recommended):${utils.COLORS.reset}
2123
+ ${utils.COLORS.green}bootspring preseed start${utils.COLORS.reset} Smart guide that adapts to your situation
2124
+
2125
+ ${utils.COLORS.bold}Manual Workflow:${utils.COLORS.reset}
2126
+ 1. ${utils.COLORS.cyan}bootspring preseed setup${utils.COLORS.reset} Create context folders
2127
+ 2. Drop any existing docs in ${utils.COLORS.cyan}.bootspring/preseed/context/${utils.COLORS.reset}
2128
+ 3. ${utils.COLORS.cyan}bootspring preseed init${utils.COLORS.reset} Run wizard (uses your context)
2129
+ 4. Review documents in ${utils.COLORS.cyan}.bootspring/preseed/${utils.COLORS.reset}
2130
+ 5. ${utils.COLORS.cyan}bootspring preseed sync${utils.COLORS.reset} Keep docs updated
2131
+ 6. ${utils.COLORS.cyan}bootspring seed init${utils.COLORS.reset} Scaffold project
2132
+
2133
+ ${utils.COLORS.bold}Approval Workflow (Recommended):${utils.COLORS.reset}
2134
+ 1. ${utils.COLORS.cyan}bootspring preseed workflow start${utils.COLORS.reset} Begin guided workflow
2135
+ 2. Generate/upload → Review → Approve each document
2136
+ 3. ${utils.COLORS.cyan}bootspring preseed workflow resume${utils.COLORS.reset} Continue anytime
2137
+ 4. ${utils.COLORS.cyan}bootspring preseed workflow status${utils.COLORS.reset} Check progress
2138
+
2139
+ ${utils.COLORS.bold}Workflow Commands:${utils.COLORS.reset} ${utils.COLORS.yellow}[PRO]${utils.COLORS.reset}
2140
+ ${utils.COLORS.cyan}workflow start${utils.COLORS.reset} Begin or resume approval workflow
2141
+ ${utils.COLORS.cyan}workflow resume${utils.COLORS.reset} Continue from last point
2142
+ ${utils.COLORS.cyan}workflow status${utils.COLORS.reset} Show workflow progress
2143
+ ${utils.COLORS.cyan}workflow reset${utils.COLORS.reset} Reset workflow state
2144
+
2145
+ ${utils.COLORS.bold}Document Commands:${utils.COLORS.reset}
2146
+ ${utils.COLORS.cyan}doc <type> approve${utils.COLORS.reset} Approve a document
2147
+ ${utils.COLORS.cyan}doc <type> reject${utils.COLORS.reset} Reject with feedback
2148
+ ${utils.COLORS.cyan}doc <type> edit${utils.COLORS.reset} Open in $EDITOR
2149
+ ${utils.COLORS.cyan}doc <type> view${utils.COLORS.reset} View document content
2150
+ ${utils.COLORS.cyan}doc <type> review${utils.COLORS.reset} Submit for review
2151
+
2152
+ ${utils.COLORS.bold}Context Folders:${utils.COLORS.reset}
2153
+ Drop your existing docs in these folders before running init:
2154
+
2155
+ ${utils.COLORS.yellow}Universal:${utils.COLORS.reset}
2156
+ - ${utils.COLORS.green}drop/${utils.COLORS.reset} ${utils.COLORS.bold}Drop anything here${utils.COLORS.reset} - AI sorts it out
2157
+
2158
+ ${utils.COLORS.yellow}General:${utils.COLORS.reset}
2159
+ - ideas/ Rough ideas, brainstorms, notes
2160
+ - research/ Market research, industry reports
2161
+
2162
+ ${utils.COLORS.yellow}Document-specific (1:1 mapping to output):${utils.COLORS.reset}
2163
+ - vision/ → VISION.md
2164
+ - audience/ → AUDIENCE.md
2165
+ - market/ → MARKET.md
2166
+ - competitors/ → COMPETITORS.md
2167
+ - business/ → BUSINESS_MODEL.md
2168
+ - prd/ → PRD.md
2169
+ - technical/ → TECHNICAL_SPEC.md
2170
+ - roadmap/ → ROADMAP.md
2171
+
2172
+ ${utils.COLORS.bold}Cloud Sync (Dashboard):${utils.COLORS.reset}
2173
+ Sync documents between your local project and the Bootspring dashboard.
2174
+ Requires authentication: ${utils.COLORS.cyan}bootspring auth login${utils.COLORS.reset}
2175
+
2176
+ ${utils.COLORS.cyan}preseed pull${utils.COLORS.reset} Download documents from dashboard
2177
+ ${utils.COLORS.cyan}preseed push${utils.COLORS.reset} Upload local documents to dashboard
2178
+
2179
+ Dashboard features:
2180
+ - AI-powered document generation (Pro tier)
2181
+ - Visual document editor with markdown preview
2182
+ - Team collaboration and sharing
2183
+ - Version history and backups
2184
+
2185
+ ${utils.COLORS.bold}Living Documents:${utils.COLORS.reset}
2186
+ Preseed creates "living documents" that can be regenerated as your
2187
+ understanding evolves. Edit the config, run sync, and your documents
2188
+ stay up to date. This is the foundation for your SEED.md and beyond.
2189
+ `);
2190
+ }
2191
+
2192
+ /**
2193
+ * Run preseed command
2194
+ */
2195
+ async function run(args) {
2196
+ const parsedArgs = utils.parseArgs(args);
2197
+ const subcommand = parsedArgs._[0] || 'status';
2198
+
2199
+ // Check tier access for paid commands
2200
+ const paidCommands = ['start', 'begin', 'new', 'pull', 'download', 'push', 'upload', 'merge', 'workflow'];
2201
+ if (paidCommands.includes(subcommand)) {
2202
+ // Map aliases to actual command names for tier check
2203
+ const commandMap = {
2204
+ 'begin': 'start',
2205
+ 'new': 'start',
2206
+ 'download': 'pull',
2207
+ 'upload': 'push',
2208
+ };
2209
+ const actualCommand = commandMap[subcommand] || subcommand;
2210
+
2211
+ try {
2212
+ tierEnforcement.requirePreseedAccess(actualCommand);
2213
+ } catch (error) {
2214
+ if (error.code === 'TIER_REQUIRED') {
2215
+ console.log(error.upgradePrompt);
2216
+ return;
2217
+ }
2218
+ throw error;
2219
+ }
2220
+ }
2221
+
2222
+ switch (subcommand) {
2223
+ case 'start':
2224
+ case 'begin':
2225
+ case 'new':
2226
+ return preseedStart.start(parsedArgs);
2227
+
2228
+ case 'setup':
2229
+ return preseedSetup(parsedArgs);
2230
+
2231
+ case 'init':
2232
+ case 'wizard':
2233
+ return preseedInit(parsedArgs);
2234
+
2235
+ case 'generate':
2236
+ case 'gen':
2237
+ return preseedGenerate(parsedArgs);
2238
+
2239
+ case 'sync':
2240
+ return preseedSync(parsedArgs);
2241
+
2242
+ case 'status':
2243
+ return preseedStatus(parsedArgs);
2244
+
2245
+ case 'update':
2246
+ case 'set':
2247
+ parsedArgs._ = parsedArgs._.slice(1);
2248
+ return preseedUpdate(parsedArgs);
2249
+
2250
+ case 'export':
2251
+ return preseedExport(parsedArgs);
2252
+
2253
+ case 'pull':
2254
+ case 'download':
2255
+ return preseedPull(parsedArgs);
2256
+
2257
+ case 'push':
2258
+ case 'upload':
2259
+ return preseedPush(parsedArgs);
2260
+
2261
+ case 'merge':
2262
+ return preseedMerge(parsedArgs);
2263
+
2264
+ // Workflow commands
2265
+ case 'workflow': {
2266
+ const workflowCmd = parsedArgs._[1] || 'status';
2267
+ parsedArgs._ = parsedArgs._.slice(2);
2268
+
2269
+ switch (workflowCmd) {
2270
+ case 'start':
2271
+ return workflowStart(parsedArgs);
2272
+ case 'resume':
2273
+ return workflowResume(parsedArgs);
2274
+ case 'status':
2275
+ return workflowStatus(parsedArgs);
2276
+ case 'reset':
2277
+ return workflowReset(parsedArgs);
2278
+ default:
2279
+ utils.print.error(`Unknown workflow command: ${workflowCmd}`);
2280
+ console.log('\nCommands: start, resume, status, reset');
2281
+ }
2282
+ break;
2283
+ }
2284
+
2285
+ // Document commands
2286
+ case 'doc':
2287
+ case 'document':
2288
+ parsedArgs._ = parsedArgs._.slice(1);
2289
+ return handleDocCommand(parsedArgs);
2290
+
2291
+ case 'help':
2292
+ case '-h':
2293
+ case '--help':
2294
+ return showHelp();
2295
+
2296
+ default:
2297
+ utils.print.error(`Unknown subcommand: ${subcommand}`);
2298
+ showHelp();
2299
+ }
2300
+ }
2301
+
2302
+ module.exports = { run };