@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/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 };
|