@girardmedia/bootspring 2.0.21 → 2.0.23
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/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
package/src/cli/prd.ts
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring PRD Command
|
|
3
|
+
* Product Requirements Document management
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* create Create a new PRD
|
|
7
|
+
* list List all PRDs
|
|
8
|
+
* show Show a specific PRD
|
|
9
|
+
* import Import PRDs from .bootspring/inputs/prd
|
|
10
|
+
* to-tasks Convert PRD to todo items
|
|
11
|
+
* status Show PRD system status
|
|
12
|
+
*
|
|
13
|
+
* @package bootspring
|
|
14
|
+
* @command prd
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
|
|
20
|
+
// Type interfaces for JS modules
|
|
21
|
+
interface Colors {
|
|
22
|
+
reset: string;
|
|
23
|
+
bold: string;
|
|
24
|
+
dim: string;
|
|
25
|
+
cyan: string;
|
|
26
|
+
green: string;
|
|
27
|
+
yellow: string;
|
|
28
|
+
red: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface PrintModule {
|
|
32
|
+
error(msg: string): void;
|
|
33
|
+
success(msg: string): void;
|
|
34
|
+
warning(msg: string): void;
|
|
35
|
+
dim(msg: string): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface Spinner {
|
|
39
|
+
start(): Spinner;
|
|
40
|
+
stop(): void;
|
|
41
|
+
succeed(text: string): void;
|
|
42
|
+
fail(text: string): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface UtilsModule {
|
|
46
|
+
COLORS: Colors;
|
|
47
|
+
print: PrintModule;
|
|
48
|
+
createSpinner(text: string): Spinner;
|
|
49
|
+
parseArgs(args: string[]): ParsedArgs;
|
|
50
|
+
formatRelativeTime(date: Date): string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ConfigModule {
|
|
54
|
+
findProjectRoot(): string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface ParsedArgs {
|
|
58
|
+
_: string[];
|
|
59
|
+
name?: string | undefined;
|
|
60
|
+
title?: string | undefined;
|
|
61
|
+
description?: string | undefined;
|
|
62
|
+
force?: boolean | undefined;
|
|
63
|
+
[key: string]: string | boolean | string[] | undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface PrdInfo {
|
|
67
|
+
name: string;
|
|
68
|
+
title: string;
|
|
69
|
+
source: 'generated' | 'input';
|
|
70
|
+
status: string;
|
|
71
|
+
modified: Date;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface IngestedPrd {
|
|
75
|
+
file: string;
|
|
76
|
+
userStories?: string[] | undefined;
|
|
77
|
+
features?: string[] | undefined;
|
|
78
|
+
requirements?: {
|
|
79
|
+
mustHave?: string[] | undefined;
|
|
80
|
+
shouldHave?: string[] | undefined;
|
|
81
|
+
niceToHave?: string[] | undefined;
|
|
82
|
+
} | undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface IngestModule {
|
|
86
|
+
ingestPRD(projectRoot: string): Promise<IngestedPrd[]>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface TodoModule {
|
|
90
|
+
add(options: { text: string; priority: number }): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface Task {
|
|
94
|
+
text: string;
|
|
95
|
+
type: 'story' | 'requirement';
|
|
96
|
+
priority: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const utils = require('../core/utils') as UtilsModule;
|
|
100
|
+
const config = require('../core/config') as ConfigModule;
|
|
101
|
+
|
|
102
|
+
// Lazy load ingest module
|
|
103
|
+
let ingest: IngestModule | null = null;
|
|
104
|
+
function getIngest(): IngestModule {
|
|
105
|
+
if (!ingest) {
|
|
106
|
+
ingest = require('../core/ingest') as IngestModule;
|
|
107
|
+
}
|
|
108
|
+
return ingest;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Run PRD command
|
|
113
|
+
*/
|
|
114
|
+
export async function run(args: string[]): Promise<void> {
|
|
115
|
+
const parsedArgs = utils.parseArgs(args);
|
|
116
|
+
const subcommand = parsedArgs._[0] || 'status';
|
|
117
|
+
|
|
118
|
+
switch (subcommand) {
|
|
119
|
+
case 'create':
|
|
120
|
+
return prdCreate(parsedArgs);
|
|
121
|
+
case 'list':
|
|
122
|
+
return prdList(parsedArgs);
|
|
123
|
+
case 'show':
|
|
124
|
+
return prdShow(parsedArgs);
|
|
125
|
+
case 'import':
|
|
126
|
+
return prdImport(parsedArgs);
|
|
127
|
+
case 'to-tasks':
|
|
128
|
+
return prdToTasks(parsedArgs);
|
|
129
|
+
case 'status':
|
|
130
|
+
return prdStatus(parsedArgs);
|
|
131
|
+
case 'help':
|
|
132
|
+
case '-h':
|
|
133
|
+
case '--help':
|
|
134
|
+
return showHelp();
|
|
135
|
+
default:
|
|
136
|
+
utils.print.error(`Unknown subcommand: ${subcommand}`);
|
|
137
|
+
showHelp();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a new PRD
|
|
143
|
+
*/
|
|
144
|
+
function prdCreate(args: ParsedArgs): void {
|
|
145
|
+
const projectRoot = config.findProjectRoot();
|
|
146
|
+
const name = args._[1] || args.name;
|
|
147
|
+
const title = args.title;
|
|
148
|
+
const description = args.description;
|
|
149
|
+
|
|
150
|
+
console.log(`
|
|
151
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring PRD Create${utils.COLORS.reset}
|
|
152
|
+
${utils.COLORS.dim}Create a new Product Requirements Document${utils.COLORS.reset}
|
|
153
|
+
`);
|
|
154
|
+
|
|
155
|
+
if (!name) {
|
|
156
|
+
utils.print.error('PRD name required');
|
|
157
|
+
utils.print.dim('Usage: bootspring prd create <name> [--title="..."] [--description="..."]');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const prdDir = path.join(projectRoot, '.bootspring', 'generated', 'prd');
|
|
162
|
+
if (!fs.existsSync(prdDir)) {
|
|
163
|
+
fs.mkdirSync(prdDir, { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const fileName = `${name.toLowerCase().replace(/\s+/g, '-')}.md`;
|
|
167
|
+
const filePath = path.join(prdDir, fileName);
|
|
168
|
+
|
|
169
|
+
if (fs.existsSync(filePath) && !args.force) {
|
|
170
|
+
utils.print.warning(`PRD already exists: ${fileName}`);
|
|
171
|
+
utils.print.dim('Use --force to overwrite');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const date = new Date().toISOString().split('T')[0] || '';
|
|
176
|
+
const content = `# ${title || name}
|
|
177
|
+
|
|
178
|
+
**Status:** Draft
|
|
179
|
+
**Created:** ${date}
|
|
180
|
+
**Author:** ${process.env.USER || 'Bootspring'}
|
|
181
|
+
|
|
182
|
+
## Overview
|
|
183
|
+
|
|
184
|
+
${description || 'Add description here.'}
|
|
185
|
+
|
|
186
|
+
## Problem Statement
|
|
187
|
+
|
|
188
|
+
Describe the problem being solved and why it matters.
|
|
189
|
+
|
|
190
|
+
## Goals
|
|
191
|
+
|
|
192
|
+
1. Primary goal
|
|
193
|
+
2. Secondary goal
|
|
194
|
+
3. Success criteria
|
|
195
|
+
|
|
196
|
+
## User Stories
|
|
197
|
+
|
|
198
|
+
### As a user, I want to...
|
|
199
|
+
|
|
200
|
+
- [ ] User story 1
|
|
201
|
+
- [ ] User story 2
|
|
202
|
+
- [ ] User story 3
|
|
203
|
+
|
|
204
|
+
## Requirements
|
|
205
|
+
|
|
206
|
+
### Must Have (P0)
|
|
207
|
+
- Requirement 1
|
|
208
|
+
- Requirement 2
|
|
209
|
+
|
|
210
|
+
### Should Have (P1)
|
|
211
|
+
- Requirement 3
|
|
212
|
+
- Requirement 4
|
|
213
|
+
|
|
214
|
+
### Nice to Have (P2)
|
|
215
|
+
- Requirement 5
|
|
216
|
+
|
|
217
|
+
## Technical Considerations
|
|
218
|
+
|
|
219
|
+
- Architecture notes
|
|
220
|
+
- Dependencies
|
|
221
|
+
- Performance requirements
|
|
222
|
+
|
|
223
|
+
## Success Metrics
|
|
224
|
+
|
|
225
|
+
| Metric | Target | Current |
|
|
226
|
+
|--------|--------|---------|
|
|
227
|
+
| Metric 1 | X | - |
|
|
228
|
+
| Metric 2 | Y | - |
|
|
229
|
+
|
|
230
|
+
## Timeline
|
|
231
|
+
|
|
232
|
+
| Phase | Duration | Status |
|
|
233
|
+
|-------|----------|--------|
|
|
234
|
+
| Research | 1 week | Not started |
|
|
235
|
+
| Design | 1 week | Not started |
|
|
236
|
+
| Implementation | 2 weeks | Not started |
|
|
237
|
+
| Testing | 1 week | Not started |
|
|
238
|
+
|
|
239
|
+
## Open Questions
|
|
240
|
+
|
|
241
|
+
1. Question 1?
|
|
242
|
+
2. Question 2?
|
|
243
|
+
|
|
244
|
+
## References
|
|
245
|
+
|
|
246
|
+
- Link 1
|
|
247
|
+
- Link 2
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
*Generated by Bootspring PRD System*
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
fs.writeFileSync(filePath, content);
|
|
254
|
+
utils.print.success(`Created PRD: ${fileName}`);
|
|
255
|
+
|
|
256
|
+
console.log(`
|
|
257
|
+
${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
|
|
258
|
+
1. Edit ${utils.COLORS.cyan}.bootspring/generated/prd/${fileName}${utils.COLORS.reset}
|
|
259
|
+
2. Run ${utils.COLORS.cyan}bootspring prd to-tasks ${name}${utils.COLORS.reset} to create todos
|
|
260
|
+
`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* List all PRDs
|
|
265
|
+
*/
|
|
266
|
+
function prdList(_args: ParsedArgs): void {
|
|
267
|
+
const projectRoot = config.findProjectRoot();
|
|
268
|
+
const prdDir = path.join(projectRoot, '.bootspring', 'generated', 'prd');
|
|
269
|
+
const inputPrdDir = path.join(projectRoot, '.bootspring', 'inputs', 'prd');
|
|
270
|
+
|
|
271
|
+
console.log(`
|
|
272
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring PRD List${utils.COLORS.reset}
|
|
273
|
+
`);
|
|
274
|
+
|
|
275
|
+
const prds: PrdInfo[] = [];
|
|
276
|
+
|
|
277
|
+
// Check generated PRDs
|
|
278
|
+
if (fs.existsSync(prdDir)) {
|
|
279
|
+
const files = fs.readdirSync(prdDir).filter(f => f.endsWith('.md'));
|
|
280
|
+
for (const file of files) {
|
|
281
|
+
const filePath = path.join(prdDir, file);
|
|
282
|
+
const stats = fs.statSync(filePath);
|
|
283
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
284
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
285
|
+
const statusMatch = content.match(/\*\*Status:\*\*\s*(.+)/i);
|
|
286
|
+
|
|
287
|
+
prds.push({
|
|
288
|
+
name: file.replace('.md', ''),
|
|
289
|
+
title: titleMatch?.[1] || file,
|
|
290
|
+
source: 'generated',
|
|
291
|
+
status: statusMatch?.[1]?.trim() || 'Unknown',
|
|
292
|
+
modified: stats.mtime
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Check input PRDs
|
|
298
|
+
if (fs.existsSync(inputPrdDir)) {
|
|
299
|
+
const files = fs.readdirSync(inputPrdDir).filter(f =>
|
|
300
|
+
f.endsWith('.md') || f.endsWith('.txt')
|
|
301
|
+
);
|
|
302
|
+
for (const file of files) {
|
|
303
|
+
if (file === 'README.md') continue;
|
|
304
|
+
const filePath = path.join(inputPrdDir, file);
|
|
305
|
+
const stats = fs.statSync(filePath);
|
|
306
|
+
|
|
307
|
+
prds.push({
|
|
308
|
+
name: file.replace(/\.(md|txt)$/, ''),
|
|
309
|
+
title: file,
|
|
310
|
+
source: 'input',
|
|
311
|
+
status: 'Imported',
|
|
312
|
+
modified: stats.mtime
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (prds.length === 0) {
|
|
318
|
+
utils.print.dim('No PRDs found');
|
|
319
|
+
console.log(`
|
|
320
|
+
${utils.COLORS.bold}To create a PRD:${utils.COLORS.reset}
|
|
321
|
+
bootspring prd create <name> --title="My Feature"
|
|
322
|
+
|
|
323
|
+
${utils.COLORS.bold}To import PRDs:${utils.COLORS.reset}
|
|
324
|
+
1. Drop files in ${utils.COLORS.cyan}.bootspring/inputs/prd/${utils.COLORS.reset}
|
|
325
|
+
2. Run ${utils.COLORS.cyan}bootspring prd import${utils.COLORS.reset}
|
|
326
|
+
`);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(`${utils.COLORS.bold}PRDs:${utils.COLORS.reset}
|
|
331
|
+
`);
|
|
332
|
+
|
|
333
|
+
for (const prd of prds) {
|
|
334
|
+
const modified = utils.formatRelativeTime(prd.modified);
|
|
335
|
+
const sourceIcon = prd.source === 'generated' ? utils.COLORS.green + '+' : utils.COLORS.cyan + '>';
|
|
336
|
+
console.log(` ${sourceIcon}${utils.COLORS.reset} ${utils.COLORS.bold}${prd.name}${utils.COLORS.reset}`);
|
|
337
|
+
console.log(` ${utils.COLORS.dim}${prd.title.slice(0, 50)}${utils.COLORS.reset}`);
|
|
338
|
+
console.log(` ${utils.COLORS.dim}Status: ${prd.status} | Modified: ${modified}${utils.COLORS.reset}`);
|
|
339
|
+
console.log();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
console.log(`${utils.COLORS.dim}Total: ${prds.length} PRDs (${prds.filter(p => p.source === 'generated').length} generated, ${prds.filter(p => p.source === 'input').length} imported)${utils.COLORS.reset}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Show a specific PRD
|
|
347
|
+
*/
|
|
348
|
+
function prdShow(args: ParsedArgs): void {
|
|
349
|
+
const projectRoot = config.findProjectRoot();
|
|
350
|
+
const name = args._[1] || args.name;
|
|
351
|
+
|
|
352
|
+
console.log(`
|
|
353
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring PRD Show${utils.COLORS.reset}
|
|
354
|
+
`);
|
|
355
|
+
|
|
356
|
+
if (!name) {
|
|
357
|
+
utils.print.error('PRD name required');
|
|
358
|
+
utils.print.dim('Usage: bootspring prd show <name>');
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Try generated first, then input
|
|
363
|
+
const paths = [
|
|
364
|
+
path.join(projectRoot, '.bootspring', 'generated', 'prd', `${name}.md`),
|
|
365
|
+
path.join(projectRoot, '.bootspring', 'inputs', 'prd', `${name}.md`),
|
|
366
|
+
path.join(projectRoot, '.bootspring', 'inputs', 'prd', name)
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
let content: string | null = null;
|
|
370
|
+
let foundPath: string | null = null;
|
|
371
|
+
|
|
372
|
+
for (const p of paths) {
|
|
373
|
+
if (fs.existsSync(p)) {
|
|
374
|
+
content = fs.readFileSync(p, 'utf-8');
|
|
375
|
+
foundPath = p;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (!content) {
|
|
381
|
+
utils.print.error(`PRD not found: ${name}`);
|
|
382
|
+
utils.print.dim('Run "bootspring prd list" to see available PRDs');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.log(`${utils.COLORS.dim}File: ${foundPath}${utils.COLORS.reset}
|
|
387
|
+
`);
|
|
388
|
+
console.log(content);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Import PRDs from inputs folder
|
|
393
|
+
*/
|
|
394
|
+
async function prdImport(_args: ParsedArgs): Promise<void> {
|
|
395
|
+
const projectRoot = config.findProjectRoot();
|
|
396
|
+
const inputPrdDir = path.join(projectRoot, '.bootspring', 'inputs', 'prd');
|
|
397
|
+
|
|
398
|
+
console.log(`
|
|
399
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring PRD Import${utils.COLORS.reset}
|
|
400
|
+
${utils.COLORS.dim}Importing PRDs from .bootspring/inputs/prd${utils.COLORS.reset}
|
|
401
|
+
`);
|
|
402
|
+
|
|
403
|
+
if (!fs.existsSync(inputPrdDir)) {
|
|
404
|
+
utils.print.warning('No .bootspring/inputs/prd folder found');
|
|
405
|
+
utils.print.dim('Run "bootspring seed setup" to create the folder structure');
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const spinner = utils.createSpinner('Analyzing PRD files...').start();
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
const Ingest = getIngest();
|
|
413
|
+
const ingested = await Ingest.ingestPRD(projectRoot);
|
|
414
|
+
|
|
415
|
+
spinner.succeed('Analyzed PRD files');
|
|
416
|
+
|
|
417
|
+
if (!ingested || ingested.length === 0) {
|
|
418
|
+
utils.print.dim('No PRD files found in .bootspring/inputs/prd/');
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
console.log(`
|
|
423
|
+
${utils.COLORS.bold}Imported ${ingested.length} PRD files:${utils.COLORS.reset}
|
|
424
|
+
`);
|
|
425
|
+
|
|
426
|
+
for (const prd of ingested) {
|
|
427
|
+
console.log(` ${utils.COLORS.green}+${utils.COLORS.reset} ${prd.file}`);
|
|
428
|
+
if (prd.userStories && prd.userStories.length > 0) {
|
|
429
|
+
console.log(` ${utils.COLORS.dim}User Stories: ${prd.userStories.length}${utils.COLORS.reset}`);
|
|
430
|
+
}
|
|
431
|
+
if (prd.features && prd.features.length > 0) {
|
|
432
|
+
console.log(` ${utils.COLORS.dim}Features: ${prd.features.length}${utils.COLORS.reset}`);
|
|
433
|
+
}
|
|
434
|
+
if (prd.requirements?.mustHave && prd.requirements.mustHave.length > 0) {
|
|
435
|
+
console.log(` ${utils.COLORS.dim}Requirements: ${prd.requirements.mustHave.length} must-have${utils.COLORS.reset}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
console.log(`
|
|
440
|
+
${utils.COLORS.bold}Next steps:${utils.COLORS.reset}
|
|
441
|
+
1. Run ${utils.COLORS.cyan}bootspring prd to-tasks${utils.COLORS.reset} to create todos
|
|
442
|
+
2. Run ${utils.COLORS.cyan}bootspring seed generate${utils.COLORS.reset} to update context
|
|
443
|
+
`);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
spinner.fail(`Import failed: ${(error as Error).message}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Convert PRD to tasks
|
|
451
|
+
*/
|
|
452
|
+
async function prdToTasks(args: ParsedArgs): Promise<void> {
|
|
453
|
+
const projectRoot = config.findProjectRoot();
|
|
454
|
+
const name = args._[1] || args.name;
|
|
455
|
+
|
|
456
|
+
console.log(`
|
|
457
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring PRD to Tasks${utils.COLORS.reset}
|
|
458
|
+
${utils.COLORS.dim}Converting PRD requirements to todos${utils.COLORS.reset}
|
|
459
|
+
`);
|
|
460
|
+
|
|
461
|
+
let content: string | null = null;
|
|
462
|
+
|
|
463
|
+
if (name) {
|
|
464
|
+
// Load specific PRD
|
|
465
|
+
const paths = [
|
|
466
|
+
path.join(projectRoot, '.bootspring', 'generated', 'prd', `${name}.md`),
|
|
467
|
+
path.join(projectRoot, '.bootspring', 'inputs', 'prd', `${name}.md`)
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
for (const p of paths) {
|
|
471
|
+
if (fs.existsSync(p)) {
|
|
472
|
+
content = fs.readFileSync(p, 'utf-8');
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!content) {
|
|
478
|
+
utils.print.error(`PRD not found: ${name}`);
|
|
479
|
+
utils.print.dim('Run "bootspring prd list" to see available PRDs');
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
// Load all PRDs
|
|
484
|
+
const prdDir = path.join(projectRoot, '.bootspring', 'generated', 'prd');
|
|
485
|
+
if (fs.existsSync(prdDir)) {
|
|
486
|
+
const files = fs.readdirSync(prdDir).filter(f => f.endsWith('.md'));
|
|
487
|
+
content = files.map(f => fs.readFileSync(path.join(prdDir, f), 'utf-8')).join('\n\n');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (!content) {
|
|
492
|
+
utils.print.warning('No PRDs to process');
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Extract tasks from content
|
|
497
|
+
const tasks: Task[] = [];
|
|
498
|
+
|
|
499
|
+
// Extract user stories (lines with - [ ])
|
|
500
|
+
const storyMatches = content.matchAll(/^\s*-\s*\[\s*\]\s*(.+)$/gm);
|
|
501
|
+
for (const match of storyMatches) {
|
|
502
|
+
const text = match[1]?.trim();
|
|
503
|
+
if (text && text.length > 5) {
|
|
504
|
+
tasks.push({ text, type: 'story', priority: 2 });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Extract requirements from Must Have section
|
|
509
|
+
const mustHaveSection = content.match(/### Must Have.*?\n([\s\S]*?)(?=###|##|$)/i);
|
|
510
|
+
if (mustHaveSection?.[1]) {
|
|
511
|
+
const reqMatches = mustHaveSection[1].matchAll(/^\s*-\s*(.+)$/gm);
|
|
512
|
+
for (const match of reqMatches) {
|
|
513
|
+
const text = match[1]?.trim();
|
|
514
|
+
if (text && text.length > 5 && !text.startsWith('[')) {
|
|
515
|
+
tasks.push({ text, type: 'requirement', priority: 1 });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Extract requirements from Should Have section
|
|
521
|
+
const shouldHaveSection = content.match(/### Should Have.*?\n([\s\S]*?)(?=###|##|$)/i);
|
|
522
|
+
if (shouldHaveSection?.[1]) {
|
|
523
|
+
const reqMatches = shouldHaveSection[1].matchAll(/^\s*-\s*(.+)$/gm);
|
|
524
|
+
for (const match of reqMatches) {
|
|
525
|
+
const text = match[1]?.trim();
|
|
526
|
+
if (text && text.length > 5 && !text.startsWith('[')) {
|
|
527
|
+
tasks.push({ text, type: 'requirement', priority: 2 });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (tasks.length === 0) {
|
|
533
|
+
utils.print.warning('No actionable items found in PRD');
|
|
534
|
+
utils.print.dim('Add user stories with "- [ ] ..." or requirements with "- ..."');
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
console.log(`
|
|
539
|
+
${utils.COLORS.bold}Found ${tasks.length} tasks:${utils.COLORS.reset}
|
|
540
|
+
`);
|
|
541
|
+
|
|
542
|
+
// Load todo module
|
|
543
|
+
const todoModule = require('./todo') as TodoModule;
|
|
544
|
+
|
|
545
|
+
let added = 0;
|
|
546
|
+
for (const task of tasks.slice(0, 20)) {
|
|
547
|
+
const prefix = task.type === 'story' ? '[Story]' : '[Req]';
|
|
548
|
+
const priority = task.priority === 1 ? 'P0' : 'P1';
|
|
549
|
+
const todoText = `${prefix} ${priority}: ${task.text.slice(0, 80)}`;
|
|
550
|
+
|
|
551
|
+
console.log(` ${utils.COLORS.green}+${utils.COLORS.reset} ${todoText}`);
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
todoModule.add({ text: todoText, priority: task.priority });
|
|
555
|
+
added++;
|
|
556
|
+
} catch {
|
|
557
|
+
// Skip if todo fails
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (tasks.length > 20) {
|
|
562
|
+
console.log(` ${utils.COLORS.dim}... and ${tasks.length - 20} more (run with --all to add all)${utils.COLORS.reset}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
console.log(`
|
|
566
|
+
${utils.COLORS.green}${utils.COLORS.bold}Added ${added} tasks${utils.COLORS.reset}
|
|
567
|
+
|
|
568
|
+
Run ${utils.COLORS.cyan}bootspring todo list${utils.COLORS.reset} to see all tasks
|
|
569
|
+
`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Show PRD status
|
|
574
|
+
*/
|
|
575
|
+
function prdStatus(_args: ParsedArgs): void {
|
|
576
|
+
const projectRoot = config.findProjectRoot();
|
|
577
|
+
const prdDir = path.join(projectRoot, '.bootspring', 'generated', 'prd');
|
|
578
|
+
const inputPrdDir = path.join(projectRoot, '.bootspring', 'inputs', 'prd');
|
|
579
|
+
|
|
580
|
+
console.log(`
|
|
581
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring PRD Status${utils.COLORS.reset}
|
|
582
|
+
`);
|
|
583
|
+
|
|
584
|
+
const stats = {
|
|
585
|
+
generated: 0,
|
|
586
|
+
input: 0,
|
|
587
|
+
draft: 0,
|
|
588
|
+
approved: 0,
|
|
589
|
+
totalFeatures: 0,
|
|
590
|
+
totalStories: 0
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// Count generated PRDs
|
|
594
|
+
if (fs.existsSync(prdDir)) {
|
|
595
|
+
const files = fs.readdirSync(prdDir).filter(f => f.endsWith('.md'));
|
|
596
|
+
stats.generated = files.length;
|
|
597
|
+
|
|
598
|
+
for (const file of files) {
|
|
599
|
+
const content = fs.readFileSync(path.join(prdDir, file), 'utf-8');
|
|
600
|
+
if (content.includes('Status:** Draft')) stats.draft++;
|
|
601
|
+
if (content.includes('Status:** Approved')) stats.approved++;
|
|
602
|
+
|
|
603
|
+
// Count user stories
|
|
604
|
+
const storyCount = (content.match(/-\s*\[\s*\]/g) || []).length;
|
|
605
|
+
stats.totalStories += storyCount;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Count input PRDs
|
|
610
|
+
if (fs.existsSync(inputPrdDir)) {
|
|
611
|
+
const files = fs.readdirSync(inputPrdDir).filter(f =>
|
|
612
|
+
(f.endsWith('.md') || f.endsWith('.txt')) && f !== 'README.md'
|
|
613
|
+
);
|
|
614
|
+
stats.input = files.length;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
console.log(`${utils.COLORS.bold}Summary:${utils.COLORS.reset}`);
|
|
618
|
+
console.log(` Generated PRDs: ${utils.COLORS.cyan}${stats.generated}${utils.COLORS.reset}`);
|
|
619
|
+
console.log(` Input PRDs: ${utils.COLORS.cyan}${stats.input}${utils.COLORS.reset}`);
|
|
620
|
+
console.log(` Draft: ${stats.draft}`);
|
|
621
|
+
console.log(` Approved: ${stats.approved}`);
|
|
622
|
+
console.log(` Total User Stories: ${stats.totalStories}`);
|
|
623
|
+
|
|
624
|
+
const hasStructure = fs.existsSync(path.join(projectRoot, '.bootspring', 'inputs', 'prd'));
|
|
625
|
+
|
|
626
|
+
console.log(`
|
|
627
|
+
${utils.COLORS.bold}Status:${utils.COLORS.reset}`);
|
|
628
|
+
console.log(` ${hasStructure ? utils.COLORS.green + '✓' : utils.COLORS.yellow + '○'}${utils.COLORS.reset} Input folder exists`);
|
|
629
|
+
console.log(` ${stats.generated > 0 ? utils.COLORS.green + '✓' : utils.COLORS.yellow + '○'}${utils.COLORS.reset} PRDs created`);
|
|
630
|
+
|
|
631
|
+
console.log(`
|
|
632
|
+
${utils.COLORS.bold}Commands:${utils.COLORS.reset}
|
|
633
|
+
bootspring prd create <name> ${utils.COLORS.dim}# Create new PRD${utils.COLORS.reset}
|
|
634
|
+
bootspring prd list ${utils.COLORS.dim}# List all PRDs${utils.COLORS.reset}
|
|
635
|
+
bootspring prd show <name> ${utils.COLORS.dim}# View a PRD${utils.COLORS.reset}
|
|
636
|
+
bootspring prd import ${utils.COLORS.dim}# Import from inputs${utils.COLORS.reset}
|
|
637
|
+
bootspring prd to-tasks ${utils.COLORS.dim}# Convert to todos${utils.COLORS.reset}
|
|
638
|
+
`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Show help
|
|
643
|
+
*/
|
|
644
|
+
function showHelp(): void {
|
|
645
|
+
console.log(`
|
|
646
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring PRD${utils.COLORS.reset}
|
|
647
|
+
${utils.COLORS.dim}Product Requirements Document management${utils.COLORS.reset}
|
|
648
|
+
|
|
649
|
+
${utils.COLORS.bold}Usage:${utils.COLORS.reset}
|
|
650
|
+
bootspring prd <command> [options]
|
|
651
|
+
|
|
652
|
+
${utils.COLORS.bold}Commands:${utils.COLORS.reset}
|
|
653
|
+
${utils.COLORS.cyan}create${utils.COLORS.reset} Create a new PRD
|
|
654
|
+
${utils.COLORS.cyan}list${utils.COLORS.reset} List all PRDs
|
|
655
|
+
${utils.COLORS.cyan}show${utils.COLORS.reset} Show a specific PRD
|
|
656
|
+
${utils.COLORS.cyan}import${utils.COLORS.reset} Import PRDs from .bootspring/inputs/prd
|
|
657
|
+
${utils.COLORS.cyan}to-tasks${utils.COLORS.reset} Convert PRD to todo items
|
|
658
|
+
${utils.COLORS.cyan}status${utils.COLORS.reset} Show PRD system status (default)
|
|
659
|
+
|
|
660
|
+
${utils.COLORS.bold}Create Options:${utils.COLORS.reset}
|
|
661
|
+
--title="..." PRD title
|
|
662
|
+
--description="..." PRD description
|
|
663
|
+
--force Overwrite existing PRD
|
|
664
|
+
|
|
665
|
+
${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
666
|
+
bootspring prd create auth --title="User Authentication"
|
|
667
|
+
bootspring prd list
|
|
668
|
+
bootspring prd show auth
|
|
669
|
+
bootspring prd to-tasks auth
|
|
670
|
+
`);
|
|
671
|
+
}
|