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