@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
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP PRD tool module
|
|
3
|
+
* Provides PRD operations via MCP: create, list, show, import, to-tasks, status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function getToolDefinition() {
|
|
7
|
+
return {
|
|
8
|
+
name: 'bootspring_prd',
|
|
9
|
+
description: 'Manage Product Requirements Documents. Create PRDs, import existing documents, convert to tasks, and track status.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
action: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
enum: ['create', 'list', 'show', 'import', 'to-tasks', 'status', 'analyze'],
|
|
16
|
+
description: 'Action to perform: create (new PRD), list (show all), show (view specific), import (process input files), to-tasks (convert to todos), status (PRD health), analyze (extract insights)'
|
|
17
|
+
},
|
|
18
|
+
name: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'PRD name for create/show actions'
|
|
21
|
+
},
|
|
22
|
+
title: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'PRD title for create action'
|
|
25
|
+
},
|
|
26
|
+
description: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'PRD description for create action'
|
|
29
|
+
},
|
|
30
|
+
features: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: { type: 'string' },
|
|
33
|
+
description: 'Feature list for create action'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
required: ['action']
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createHandler({ ingestModule, todoModule, config, format }) {
|
|
42
|
+
return async (args) => {
|
|
43
|
+
const { action, name, title, description, features } = args;
|
|
44
|
+
const cfg = config.load();
|
|
45
|
+
const projectRoot = cfg._projectRoot;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
switch (action) {
|
|
49
|
+
case 'create':
|
|
50
|
+
return handleCreate(projectRoot, { name, title, description, features }, format);
|
|
51
|
+
|
|
52
|
+
case 'list':
|
|
53
|
+
return handleList(projectRoot, format);
|
|
54
|
+
|
|
55
|
+
case 'show':
|
|
56
|
+
return handleShow(projectRoot, name, format);
|
|
57
|
+
|
|
58
|
+
case 'import':
|
|
59
|
+
return handleImport(projectRoot, ingestModule, format);
|
|
60
|
+
|
|
61
|
+
case 'to-tasks':
|
|
62
|
+
return handleToTasks(projectRoot, name, todoModule, format);
|
|
63
|
+
|
|
64
|
+
case 'status':
|
|
65
|
+
return handleStatus(projectRoot, format);
|
|
66
|
+
|
|
67
|
+
case 'analyze':
|
|
68
|
+
return handleAnalyze(projectRoot, ingestModule, format);
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
return format.error(`Unknown action: ${action}`, [
|
|
72
|
+
'Valid actions: create, list, show, import, to-tasks, status, analyze'
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return format.error(`PRD operation failed: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleCreate(projectRoot, prdData, format) {
|
|
82
|
+
const fs = require('fs');
|
|
83
|
+
const path = require('path');
|
|
84
|
+
|
|
85
|
+
if (!prdData.name) {
|
|
86
|
+
return format.error('PRD name required', [
|
|
87
|
+
'Provide name parameter for the PRD'
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const prdDir = path.join(projectRoot, '.bootspring', 'generated', 'prd');
|
|
92
|
+
if (!fs.existsSync(prdDir)) {
|
|
93
|
+
fs.mkdirSync(prdDir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const fileName = `${prdData.name.toLowerCase().replace(/\s+/g, '-')}.md`;
|
|
97
|
+
const filePath = path.join(prdDir, fileName);
|
|
98
|
+
|
|
99
|
+
const featureList = (prdData.features || [])
|
|
100
|
+
.map((f, i) => `${i + 1}. ${f}`)
|
|
101
|
+
.join('\n');
|
|
102
|
+
|
|
103
|
+
const content = `# ${prdData.title || prdData.name}
|
|
104
|
+
|
|
105
|
+
**Status:** Draft
|
|
106
|
+
**Created:** ${new Date().toISOString().split('T')[0]}
|
|
107
|
+
**Author:** Bootspring
|
|
108
|
+
|
|
109
|
+
## Overview
|
|
110
|
+
|
|
111
|
+
${prdData.description || 'Add description here.'}
|
|
112
|
+
|
|
113
|
+
## Features
|
|
114
|
+
|
|
115
|
+
${featureList || '1. Feature 1\n2. Feature 2\n3. Feature 3'}
|
|
116
|
+
|
|
117
|
+
## User Stories
|
|
118
|
+
|
|
119
|
+
### As a user, I want to...
|
|
120
|
+
|
|
121
|
+
- [ ] User story 1
|
|
122
|
+
- [ ] User story 2
|
|
123
|
+
- [ ] User story 3
|
|
124
|
+
|
|
125
|
+
## Requirements
|
|
126
|
+
|
|
127
|
+
### Must Have
|
|
128
|
+
- Requirement 1
|
|
129
|
+
- Requirement 2
|
|
130
|
+
|
|
131
|
+
### Should Have
|
|
132
|
+
- Requirement 3
|
|
133
|
+
|
|
134
|
+
### Nice to Have
|
|
135
|
+
- Requirement 4
|
|
136
|
+
|
|
137
|
+
## Success Metrics
|
|
138
|
+
|
|
139
|
+
- Metric 1
|
|
140
|
+
- Metric 2
|
|
141
|
+
|
|
142
|
+
## Open Questions
|
|
143
|
+
|
|
144
|
+
1. Question 1?
|
|
145
|
+
2. Question 2?
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
*Generated by Bootspring PRD System*
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
fs.writeFileSync(filePath, content);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
content: [{
|
|
155
|
+
type: 'text',
|
|
156
|
+
text: `## PRD Created
|
|
157
|
+
|
|
158
|
+
**File:** \`.bootspring/generated/prd/${fileName}\`
|
|
159
|
+
|
|
160
|
+
**Title:** ${prdData.title || prdData.name}
|
|
161
|
+
|
|
162
|
+
**Features:** ${(prdData.features || []).length || 3}
|
|
163
|
+
|
|
164
|
+
**Next Steps:**
|
|
165
|
+
1. Edit the PRD to add details
|
|
166
|
+
2. Run \`bootspring_prd\` with action: "to-tasks" to convert to todos
|
|
167
|
+
3. Share with stakeholders for review`
|
|
168
|
+
}]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleList(projectRoot, _format) {
|
|
173
|
+
const fs = require('fs');
|
|
174
|
+
const path = require('path');
|
|
175
|
+
|
|
176
|
+
const prdDir = path.join(projectRoot, '.bootspring', 'generated', 'prd');
|
|
177
|
+
const inputPrdDir = path.join(projectRoot, '.bootspring', 'inputs', 'prd');
|
|
178
|
+
|
|
179
|
+
const prds = [];
|
|
180
|
+
|
|
181
|
+
// Check generated PRDs
|
|
182
|
+
if (fs.existsSync(prdDir)) {
|
|
183
|
+
const files = fs.readdirSync(prdDir).filter(f => f.endsWith('.md'));
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
const filePath = path.join(prdDir, file);
|
|
186
|
+
const stats = fs.statSync(filePath);
|
|
187
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
188
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
189
|
+
|
|
190
|
+
prds.push({
|
|
191
|
+
name: file.replace('.md', ''),
|
|
192
|
+
title: titleMatch ? titleMatch[1] : file,
|
|
193
|
+
source: 'generated',
|
|
194
|
+
modified: stats.mtime.toISOString().split('T')[0]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Check input PRDs
|
|
200
|
+
if (fs.existsSync(inputPrdDir)) {
|
|
201
|
+
const files = fs.readdirSync(inputPrdDir).filter(f =>
|
|
202
|
+
f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.pdf')
|
|
203
|
+
);
|
|
204
|
+
for (const file of files) {
|
|
205
|
+
const filePath = path.join(inputPrdDir, file);
|
|
206
|
+
const stats = fs.statSync(filePath);
|
|
207
|
+
|
|
208
|
+
prds.push({
|
|
209
|
+
name: file.replace(/\.(md|txt|pdf)$/, ''),
|
|
210
|
+
title: file,
|
|
211
|
+
source: 'input',
|
|
212
|
+
modified: stats.mtime.toISOString().split('T')[0]
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (prds.length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
content: [{
|
|
220
|
+
type: 'text',
|
|
221
|
+
text: `## PRD List
|
|
222
|
+
|
|
223
|
+
No PRDs found.
|
|
224
|
+
|
|
225
|
+
**To add PRDs:**
|
|
226
|
+
1. Create new: \`bootspring_prd\` action: "create" name: "my-prd"
|
|
227
|
+
2. Import existing: Drop files in \`.bootspring/inputs/prd/\` then run action: "import"`
|
|
228
|
+
}]
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const prdTable = prds
|
|
233
|
+
.map(p => `| ${p.name} | ${p.title.slice(0, 40)} | ${p.source} | ${p.modified} |`)
|
|
234
|
+
.join('\n');
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
content: [{
|
|
238
|
+
type: 'text',
|
|
239
|
+
text: `## PRD List
|
|
240
|
+
|
|
241
|
+
| Name | Title | Source | Modified |
|
|
242
|
+
|------|-------|--------|----------|
|
|
243
|
+
${prdTable}
|
|
244
|
+
|
|
245
|
+
**Total:** ${prds.length} PRDs
|
|
246
|
+
|
|
247
|
+
**Commands:**
|
|
248
|
+
- Show: \`bootspring_prd\` action: "show" name: "prd-name"
|
|
249
|
+
- Convert: \`bootspring_prd\` action: "to-tasks" name: "prd-name"`
|
|
250
|
+
}]
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function handleShow(projectRoot, name, format) {
|
|
255
|
+
const fs = require('fs');
|
|
256
|
+
const path = require('path');
|
|
257
|
+
|
|
258
|
+
if (!name) {
|
|
259
|
+
return format.error('PRD name required', [
|
|
260
|
+
'Use name parameter to specify which PRD to show'
|
|
261
|
+
]);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Try generated first, then input
|
|
265
|
+
const paths = [
|
|
266
|
+
path.join(projectRoot, '.bootspring', 'generated', 'prd', `${name}.md`),
|
|
267
|
+
path.join(projectRoot, '.bootspring', 'inputs', 'prd', `${name}.md`),
|
|
268
|
+
path.join(projectRoot, '.bootspring', 'inputs', 'prd', name)
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
let content = null;
|
|
272
|
+
let foundPath = null;
|
|
273
|
+
|
|
274
|
+
for (const p of paths) {
|
|
275
|
+
if (fs.existsSync(p)) {
|
|
276
|
+
content = fs.readFileSync(p, 'utf-8');
|
|
277
|
+
foundPath = p;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!content) {
|
|
283
|
+
return format.error(`PRD not found: ${name}`, [
|
|
284
|
+
'Use action: "list" to see available PRDs'
|
|
285
|
+
]);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Extract key sections
|
|
289
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
290
|
+
const statusMatch = content.match(/\*\*Status:\*\*\s*(.+)/i);
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
content: [{
|
|
294
|
+
type: 'text',
|
|
295
|
+
text: `## ${titleMatch ? titleMatch[1] : name}
|
|
296
|
+
|
|
297
|
+
**Source:** ${foundPath.includes('generated') ? 'Generated' : 'Input'}
|
|
298
|
+
**Status:** ${statusMatch ? statusMatch[1] : 'Unknown'}
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
${content.slice(0, 4000)}${content.length > 4000 ? '\n\n... (truncated)' : ''}`
|
|
303
|
+
}]
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function handleImport(projectRoot, ingestModule, format) {
|
|
308
|
+
const ingested = await ingestModule.ingestPRD(projectRoot);
|
|
309
|
+
|
|
310
|
+
if (!ingested || ingested.length === 0) {
|
|
311
|
+
return format.warning(
|
|
312
|
+
'No PRD files found in .bootspring/inputs/prd/',
|
|
313
|
+
['Drop your PRD files there and run again']
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const imported = [];
|
|
318
|
+
for (const prd of ingested) {
|
|
319
|
+
imported.push({
|
|
320
|
+
file: prd.file,
|
|
321
|
+
userStories: prd.userStories?.length || 0,
|
|
322
|
+
features: prd.features?.length || 0,
|
|
323
|
+
requirements: (prd.requirements?.mustHave?.length || 0) +
|
|
324
|
+
(prd.requirements?.shouldHave?.length || 0) +
|
|
325
|
+
(prd.requirements?.niceToHave?.length || 0)
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const importTable = imported
|
|
330
|
+
.map(p => `| ${p.file} | ${p.userStories} | ${p.features} | ${p.requirements} |`)
|
|
331
|
+
.join('\n');
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
content: [{
|
|
335
|
+
type: 'text',
|
|
336
|
+
text: `## PRD Import Complete
|
|
337
|
+
|
|
338
|
+
| File | User Stories | Features | Requirements |
|
|
339
|
+
|------|--------------|----------|--------------|
|
|
340
|
+
${importTable}
|
|
341
|
+
|
|
342
|
+
**Total Imported:** ${imported.length} files
|
|
343
|
+
|
|
344
|
+
**Extracted:**
|
|
345
|
+
- User Stories: ${imported.reduce((a, p) => a + p.userStories, 0)}
|
|
346
|
+
- Features: ${imported.reduce((a, p) => a + p.features, 0)}
|
|
347
|
+
- Requirements: ${imported.reduce((a, p) => a + p.requirements, 0)}
|
|
348
|
+
|
|
349
|
+
**Next Steps:**
|
|
350
|
+
1. Run \`bootspring_prd\` action: "analyze" for insights
|
|
351
|
+
2. Run \`bootspring_prd\` action: "to-tasks" to create todos`
|
|
352
|
+
}]
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function handleToTasks(projectRoot, name, todoModule, format) {
|
|
357
|
+
const fs = require('fs');
|
|
358
|
+
const path = require('path');
|
|
359
|
+
|
|
360
|
+
// Find the PRD
|
|
361
|
+
const paths = [
|
|
362
|
+
path.join(projectRoot, '.bootspring', 'generated', 'prd', `${name || ''}.md`),
|
|
363
|
+
path.join(projectRoot, '.bootspring', 'inputs', 'prd', `${name || ''}.md`)
|
|
364
|
+
];
|
|
365
|
+
|
|
366
|
+
let content = null;
|
|
367
|
+
for (const p of paths) {
|
|
368
|
+
if (name && fs.existsSync(p)) {
|
|
369
|
+
content = fs.readFileSync(p, 'utf-8');
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (!content && name) {
|
|
375
|
+
return format.error(`PRD not found: ${name}`, [
|
|
376
|
+
'Use action: "list" to see available PRDs'
|
|
377
|
+
]);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Extract user stories and requirements
|
|
381
|
+
const tasks = [];
|
|
382
|
+
|
|
383
|
+
// Extract user stories (lines starting with - [ ] or numbered items)
|
|
384
|
+
const storyMatches = content?.matchAll(/(?:^|\n)\s*(?:-\s*\[[ x]\]|\d+\.)\s*(.+)/gi) || [];
|
|
385
|
+
for (const match of storyMatches) {
|
|
386
|
+
const text = match[1].trim();
|
|
387
|
+
if (text.length > 10 && !text.startsWith('#')) {
|
|
388
|
+
tasks.push({
|
|
389
|
+
text: text.slice(0, 100),
|
|
390
|
+
type: 'story'
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Extract requirements (lines starting with -)
|
|
396
|
+
const reqMatches = content?.matchAll(/(?:^|\n)\s*-\s+(?![[\d])(.+)/gi) || [];
|
|
397
|
+
for (const match of reqMatches) {
|
|
398
|
+
const text = match[1].trim();
|
|
399
|
+
if (text.length > 10 && !text.startsWith('[') && !text.startsWith('#')) {
|
|
400
|
+
tasks.push({
|
|
401
|
+
text: text.slice(0, 100),
|
|
402
|
+
type: 'requirement'
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (tasks.length === 0) {
|
|
408
|
+
return format.warning(
|
|
409
|
+
'No actionable items found in PRD',
|
|
410
|
+
['Add user stories or requirements in standard format']
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Add tasks to todo
|
|
415
|
+
let added = 0;
|
|
416
|
+
for (const task of tasks.slice(0, 20)) {
|
|
417
|
+
try {
|
|
418
|
+
todoModule.add({ text: `[PRD] ${task.text}`, priority: 2 });
|
|
419
|
+
added++;
|
|
420
|
+
} catch {
|
|
421
|
+
// Skip if todo fails
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
content: [{
|
|
427
|
+
type: 'text',
|
|
428
|
+
text: `## Tasks Created from PRD
|
|
429
|
+
|
|
430
|
+
**Source:** ${name || 'All PRDs'}
|
|
431
|
+
**Tasks Found:** ${tasks.length}
|
|
432
|
+
**Tasks Added:** ${added}
|
|
433
|
+
|
|
434
|
+
**Sample Tasks:**
|
|
435
|
+
${tasks.slice(0, 10).map(t => `- [${t.type}] ${t.text}`).join('\n')}
|
|
436
|
+
${tasks.length > 10 ? `\n... and ${tasks.length - 10} more` : ''}
|
|
437
|
+
|
|
438
|
+
**Next Steps:**
|
|
439
|
+
1. Run \`bootspring_todo\` action: "list" to see all tasks
|
|
440
|
+
2. Prioritize and assign tasks`
|
|
441
|
+
}]
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async function handleStatus(projectRoot, _format) {
|
|
446
|
+
const fs = require('fs');
|
|
447
|
+
const path = require('path');
|
|
448
|
+
|
|
449
|
+
const prdDir = path.join(projectRoot, '.bootspring', 'generated', 'prd');
|
|
450
|
+
const inputPrdDir = path.join(projectRoot, '.bootspring', 'inputs', 'prd');
|
|
451
|
+
|
|
452
|
+
const stats = {
|
|
453
|
+
generated: 0,
|
|
454
|
+
input: 0,
|
|
455
|
+
draft: 0,
|
|
456
|
+
approved: 0,
|
|
457
|
+
totalFeatures: 0,
|
|
458
|
+
totalStories: 0
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// Count generated PRDs
|
|
462
|
+
if (fs.existsSync(prdDir)) {
|
|
463
|
+
const files = fs.readdirSync(prdDir).filter(f => f.endsWith('.md'));
|
|
464
|
+
stats.generated = files.length;
|
|
465
|
+
|
|
466
|
+
for (const file of files) {
|
|
467
|
+
const content = fs.readFileSync(path.join(prdDir, file), 'utf-8');
|
|
468
|
+
if (content.includes('Status:** Draft')) stats.draft++;
|
|
469
|
+
if (content.includes('Status:** Approved')) stats.approved++;
|
|
470
|
+
|
|
471
|
+
// Count features (numbered items in Features section)
|
|
472
|
+
const featureSection = content.match(/## Features[\s\S]*?(?=##|$)/i);
|
|
473
|
+
if (featureSection) {
|
|
474
|
+
const featureCount = (featureSection[0].match(/^\d+\./gm) || []).length;
|
|
475
|
+
stats.totalFeatures += featureCount;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Count user stories
|
|
479
|
+
const storyCount = (content.match(/-\s*\[[ x]\]/g) || []).length;
|
|
480
|
+
stats.totalStories += storyCount;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Count input PRDs
|
|
485
|
+
if (fs.existsSync(inputPrdDir)) {
|
|
486
|
+
const files = fs.readdirSync(inputPrdDir).filter(f =>
|
|
487
|
+
f.endsWith('.md') || f.endsWith('.txt')
|
|
488
|
+
);
|
|
489
|
+
stats.input = files.length;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
content: [{
|
|
494
|
+
type: 'text',
|
|
495
|
+
text: `## PRD Status
|
|
496
|
+
|
|
497
|
+
| Metric | Count |
|
|
498
|
+
|--------|-------|
|
|
499
|
+
| Generated PRDs | ${stats.generated} |
|
|
500
|
+
| Input PRDs | ${stats.input} |
|
|
501
|
+
| Draft Status | ${stats.draft} |
|
|
502
|
+
| Approved Status | ${stats.approved} |
|
|
503
|
+
| Total Features | ${stats.totalFeatures} |
|
|
504
|
+
| Total User Stories | ${stats.totalStories} |
|
|
505
|
+
|
|
506
|
+
**Health:** ${stats.generated > 0 ? '✓ PRDs exist' : '○ No PRDs yet'}
|
|
507
|
+
|
|
508
|
+
**Commands:**
|
|
509
|
+
- Create: \`bootspring_prd\` action: "create" name: "feature-x"
|
|
510
|
+
- Import: \`bootspring_prd\` action: "import"
|
|
511
|
+
- Analyze: \`bootspring_prd\` action: "analyze"`
|
|
512
|
+
}]
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function handleAnalyze(projectRoot, ingestModule, format) {
|
|
517
|
+
const ingested = await ingestModule.ingestPRD(projectRoot);
|
|
518
|
+
|
|
519
|
+
if (!ingested || ingested.length === 0) {
|
|
520
|
+
return format.warning(
|
|
521
|
+
'No PRDs to analyze',
|
|
522
|
+
['Create or import PRDs first']
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Aggregate analysis
|
|
527
|
+
const allStories = [];
|
|
528
|
+
const allFeatures = [];
|
|
529
|
+
const allQuestions = [];
|
|
530
|
+
const requirements = {
|
|
531
|
+
mustHave: [],
|
|
532
|
+
shouldHave: [],
|
|
533
|
+
niceToHave: []
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
for (const prd of ingested) {
|
|
537
|
+
if (prd.userStories) allStories.push(...prd.userStories);
|
|
538
|
+
if (prd.features) allFeatures.push(...prd.features);
|
|
539
|
+
if (prd.openQuestions) allQuestions.push(...prd.openQuestions);
|
|
540
|
+
if (prd.requirements) {
|
|
541
|
+
if (prd.requirements.mustHave) requirements.mustHave.push(...prd.requirements.mustHave);
|
|
542
|
+
if (prd.requirements.shouldHave) requirements.shouldHave.push(...prd.requirements.shouldHave);
|
|
543
|
+
if (prd.requirements.niceToHave) requirements.niceToHave.push(...prd.requirements.niceToHave);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
content: [{
|
|
549
|
+
type: 'text',
|
|
550
|
+
text: `## PRD Analysis
|
|
551
|
+
|
|
552
|
+
**Documents Analyzed:** ${ingested.length}
|
|
553
|
+
|
|
554
|
+
### User Stories (${allStories.length})
|
|
555
|
+
${allStories.slice(0, 5).map(s => `- As a ${s.role}, I want ${s.action.slice(0, 50)}...`).join('\n')}
|
|
556
|
+
${allStories.length > 5 ? `\n... and ${allStories.length - 5} more` : ''}
|
|
557
|
+
|
|
558
|
+
### Features (${allFeatures.length})
|
|
559
|
+
${allFeatures.slice(0, 5).map(f => `- ${f.slice(0, 60)}`).join('\n')}
|
|
560
|
+
${allFeatures.length > 5 ? `\n... and ${allFeatures.length - 5} more` : ''}
|
|
561
|
+
|
|
562
|
+
### Requirements
|
|
563
|
+
- **Must Have:** ${requirements.mustHave.length}
|
|
564
|
+
- **Should Have:** ${requirements.shouldHave.length}
|
|
565
|
+
- **Nice to Have:** ${requirements.niceToHave.length}
|
|
566
|
+
|
|
567
|
+
### Open Questions (${allQuestions.length})
|
|
568
|
+
${allQuestions.slice(0, 3).map(q => `- ${q.slice(0, 60)}...`).join('\n')}
|
|
569
|
+
${allQuestions.length > 3 ? `\n... and ${allQuestions.length - 3} more` : ''}
|
|
570
|
+
|
|
571
|
+
**Completeness Score:** ${Math.round((allStories.length > 0 ? 25 : 0) + (allFeatures.length > 0 ? 25 : 0) + (requirements.mustHave.length > 0 ? 25 : 0) + (allQuestions.length < 5 ? 25 : 10))}%`
|
|
572
|
+
}]
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
module.exports = {
|
|
577
|
+
getToolDefinition,
|
|
578
|
+
createHandler
|
|
579
|
+
};
|