prd-cli 1.0.0

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.
@@ -0,0 +1,76 @@
1
+ # {{workspaceName}}
2
+
3
+ Product documentation workspace powered by [Product OS Framework](https://github.com/nimidev/product-OS-framework).
4
+
5
+ ## Projects
6
+
7
+ | Project | Description |
8
+ |---------|-------------|
9
+ | [{{projectName}}](./{{projectName}}/) | {{projectDescription}} |
10
+
11
+ ## Story ID Registry
12
+
13
+ **Next Available ID: {{nextId}}**
14
+
15
+ When creating a new story, use the next available ID. The `/create` command in Cursor handles this automatically.
16
+
17
+ ## Quick Start
18
+
19
+ ### For Product Managers
20
+
21
+ 1. Open the workspace file in Cursor (`.code-workspace`)
22
+ 2. Create a story: `/create "feature description" @{{projectName}}/backlog.md`
23
+ 3. Refine requirements through conversation with the AI agent
24
+ 4. Approve the PRD when ready
25
+
26
+ ### For Developers
27
+
28
+ 1. Open the workspace file in Cursor (`.code-workspace`)
29
+ 2. Start development: `/dev US-{ID}`
30
+ 3. The agent reads RULES.md and follows project standards
31
+ 4. Ship it: `/release US-{ID}`
32
+
33
+ ### Dashboard
34
+
35
+ View all stories from any terminal:
36
+
37
+ ```bash
38
+ prd # Full dashboard
39
+ prd list # Compact view
40
+ prd stats # Quick numbers
41
+ ```
42
+
43
+ ## Process
44
+
45
+ See [PROCESS.md](./PROCESS.md) for the complete product development workflow.
46
+
47
+ ## Story File Format
48
+
49
+ Stories use YAML frontmatter for metadata:
50
+
51
+ ```yaml
52
+ ---
53
+ id: US-001
54
+ project: {{projectName}}
55
+ status: create
56
+ phase: planning
57
+ progress: 0
58
+ priority: P1
59
+ ---
60
+
61
+ # Story Title
62
+
63
+ ## Goal
64
+ What problem does this solve?
65
+
66
+ ## Acceptance Criteria
67
+ 1. ...
68
+ ```
69
+
70
+ ## Adding a New Project
71
+
72
+ ```bash
73
+ prd add-project
74
+ ```
75
+
76
+ This creates the project folder, backlog, RULES.md, and updates the workspace.
@@ -0,0 +1,53 @@
1
+ # {{projectName}} - Technical Standards
2
+
3
+ This file defines the technical standards and patterns for the {{projectName}} project.
4
+ AI agents read this file during `/dev` to ensure consistent implementation.
5
+
6
+ ## Technology Stack
7
+
8
+ <!-- List your tech stack with versions -->
9
+ - Language:
10
+ - Framework:
11
+ - Database:
12
+ - Testing:
13
+
14
+ ## Code Organization
15
+
16
+ <!-- Describe your folder structure and naming conventions -->
17
+
18
+ ```
19
+ src/
20
+ ├── ...
21
+ ```
22
+
23
+ ## Patterns & Conventions
24
+
25
+ <!-- Document coding patterns the team follows -->
26
+ - Naming conventions:
27
+ - Error handling:
28
+ - Logging:
29
+
30
+ ## Testing Requirements
31
+
32
+ <!-- Define test coverage expectations -->
33
+ - Unit tests: Required for all business logic
34
+ - Integration tests: Required for API endpoints
35
+ - E2E tests: Critical paths only
36
+
37
+ ## Performance Standards
38
+
39
+ <!-- Set performance targets -->
40
+ - Response time:
41
+ - Bundle size:
42
+
43
+ ## Security Practices
44
+
45
+ <!-- Document security requirements -->
46
+ - Authentication:
47
+ - Authorization:
48
+ - Data handling:
49
+
50
+ ---
51
+
52
+ > **Tip:** Fill this out based on your project's actual tech stack.
53
+ > The more specific you are, the better the AI agent will implement features.
@@ -0,0 +1,6 @@
1
+ # {{projectName}} - Backlog
2
+
3
+ | ID | Title | Status | Priority | Phase |
4
+ |----|-------|--------|----------|-------|
5
+
6
+ <!-- Stories are added automatically by the /create command in Cursor -->
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * End-to-end test: Simulates "Sarah" - a new PM setting up the framework.
4
+ * Run from a clean directory to test the full flow.
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const chalk = require('chalk');
10
+
11
+ const TEST_DIR = '/tmp/sarah-test';
12
+ const WORKSPACE_NAME = 'food-delivery-docs';
13
+ const PROJECT_NAME = 'delivery-api';
14
+
15
+ // Import framework modules directly
16
+ const { CONFIG_FILENAME, loadConfig, saveConfig } = require('../lib/config');
17
+ const { scanAllStories, getStats } = require('../lib/parser');
18
+ const { dashboard } = require('../lib/commands/dashboard');
19
+
20
+ function assert(condition, message) {
21
+ if (!condition) {
22
+ console.log(chalk.red(` ❌ FAIL: ${message}`));
23
+ process.exit(1);
24
+ }
25
+ console.log(chalk.green(` ✅ ${message}`));
26
+ }
27
+
28
+ function header(text) {
29
+ console.log('\n' + chalk.bold.cyan(`─── ${text} ───`));
30
+ }
31
+
32
+ // --- Setup ---
33
+ console.log(chalk.bold.white('\n🧪 Sarah Test: End-to-End Framework Test\n'));
34
+
35
+ // Clean up
36
+ if (fs.existsSync(TEST_DIR)) {
37
+ fs.rmSync(TEST_DIR, { recursive: true });
38
+ }
39
+ fs.mkdirSync(TEST_DIR, { recursive: true });
40
+
41
+ // Create fake dev repo
42
+ const devRepoPath = path.join(TEST_DIR, 'delivery-api');
43
+ fs.mkdirSync(path.join(devRepoPath, 'src'), { recursive: true });
44
+ fs.writeFileSync(path.join(devRepoPath, 'package.json'), '{"name":"delivery-api"}');
45
+ fs.writeFileSync(path.join(devRepoPath, 'src', 'index.js'), '// app entry');
46
+
47
+ // --- Step 1: Simulate prd init ---
48
+ header('Step 1: prd init');
49
+
50
+ const templatesDir = path.join(__dirname, '..', 'templates');
51
+ const wsDir = path.join(TEST_DIR, WORKSPACE_NAME);
52
+
53
+ // Create workspace directory
54
+ fs.mkdirSync(wsDir, { recursive: true });
55
+ fs.mkdirSync(path.join(wsDir, PROJECT_NAME), { recursive: true });
56
+
57
+ // Create config
58
+ const config = {
59
+ name: WORKSPACE_NAME,
60
+ nextId: 1,
61
+ ignoreDirs: ['.git', 'node_modules', 'scripts', 'docs'],
62
+ projects: {
63
+ [PROJECT_NAME]: {
64
+ description: 'Backend API for food delivery platform',
65
+ repoPath: devRepoPath
66
+ }
67
+ },
68
+ processVersion: '2.0'
69
+ };
70
+ fs.writeFileSync(path.join(wsDir, CONFIG_FILENAME), JSON.stringify(config, null, 2) + '\n');
71
+ assert(fs.existsSync(path.join(wsDir, CONFIG_FILENAME)), 'Config created');
72
+
73
+ // Copy PROCESS.md
74
+ fs.copyFileSync(
75
+ path.join(templatesDir, 'PROCESS.md'),
76
+ path.join(wsDir, 'PROCESS.md')
77
+ );
78
+ assert(fs.existsSync(path.join(wsDir, 'PROCESS.md')), 'PROCESS.md created');
79
+
80
+ // Create README from template
81
+ let readme = fs.readFileSync(path.join(templatesDir, 'README.md.tmpl'), 'utf-8');
82
+ readme = readme.replace(/\{\{workspaceName\}\}/g, WORKSPACE_NAME);
83
+ readme = readme.replace(/\{\{projectName\}\}/g, PROJECT_NAME);
84
+ readme = readme.replace(/\{\{projectDescription\}\}/g, 'Backend API for food delivery platform');
85
+ readme = readme.replace(/\{\{nextId\}\}/g, 'US-001');
86
+ fs.writeFileSync(path.join(wsDir, 'README.md'), readme);
87
+ assert(fs.existsSync(path.join(wsDir, 'README.md')), 'README.md created');
88
+
89
+ // Create backlog from template
90
+ let backlog = fs.readFileSync(path.join(templatesDir, 'backlog.md.tmpl'), 'utf-8');
91
+ backlog = backlog.replace(/\{\{projectName\}\}/g, PROJECT_NAME);
92
+ fs.writeFileSync(path.join(wsDir, PROJECT_NAME, 'backlog.md'), backlog);
93
+ assert(fs.existsSync(path.join(wsDir, PROJECT_NAME, 'backlog.md')), 'backlog.md created');
94
+
95
+ // Create RULES.md from template
96
+ let rules = fs.readFileSync(path.join(templatesDir, 'RULES.md.tmpl'), 'utf-8');
97
+ rules = rules.replace(/\{\{projectName\}\}/g, PROJECT_NAME);
98
+ fs.writeFileSync(path.join(wsDir, PROJECT_NAME, 'RULES.md'), rules);
99
+ assert(fs.existsSync(path.join(wsDir, PROJECT_NAME, 'RULES.md')), 'RULES.md created');
100
+
101
+ // Create .code-workspace file
102
+ const workspace = {
103
+ folders: [
104
+ { name: 'product-docs', path: WORKSPACE_NAME },
105
+ { name: PROJECT_NAME, path: PROJECT_NAME }
106
+ ],
107
+ settings: {}
108
+ };
109
+ const wsFilePath = path.join(TEST_DIR, `${WORKSPACE_NAME}.code-workspace`);
110
+ fs.writeFileSync(wsFilePath, JSON.stringify(workspace, null, 2) + '\n');
111
+ assert(fs.existsSync(wsFilePath), '.code-workspace file created');
112
+
113
+ // --- Step 2: Simulate creating stories (what /create would do in Cursor) ---
114
+ header('Step 2: Create stories (simulating /create in Cursor)');
115
+
116
+ function createStory(id, title, priority, status) {
117
+ const story = `---
118
+ id: US-${String(id).padStart(3, '0')}
119
+ project: ${PROJECT_NAME}
120
+ status: ${status}
121
+ phase: ${status === 'done' ? 'deployed' : status === 'dev' ? 'development' : 'planning'}
122
+ progress: ${status === 'done' ? '100' : status === 'dev' ? '50' : '0'}
123
+ priority: ${priority}
124
+ created: 2026-02-09
125
+ updated: 2026-02-09
126
+ ---
127
+
128
+ # US-${String(id).padStart(3, '0')}: ${title}
129
+
130
+ ## Goal
131
+ ${title} for the food delivery platform.
132
+
133
+ ## Acceptance Criteria
134
+ 1. Feature works as expected
135
+ 2. Tests pass
136
+ 3. Documentation updated
137
+ `;
138
+ fs.writeFileSync(path.join(wsDir, PROJECT_NAME, `US-${String(id).padStart(3, '0')}.md`), story);
139
+ }
140
+
141
+ createStory(1, 'Real-time order tracking', 'P0', 'done');
142
+ createStory(2, 'Restaurant menu management', 'P1', 'dev');
143
+ createStory(3, 'Payment processing with Stripe', 'P0', 'create');
144
+ createStory(4, 'Push notifications for order updates', 'P1', 'backlog');
145
+
146
+ assert(fs.existsSync(path.join(wsDir, PROJECT_NAME, 'US-001.md')), 'US-001.md created');
147
+ assert(fs.existsSync(path.join(wsDir, PROJECT_NAME, 'US-004.md')), 'US-004.md created');
148
+
149
+ // --- Step 3: Test dashboard ---
150
+ header('Step 3: prd dashboard');
151
+
152
+ const data = scanAllStories(wsDir);
153
+ assert(data.stories.length === 4, `Found ${data.stories.length} stories (expected 4)`);
154
+ assert(data.projects.length === 1, `Found ${data.projects.length} project(s) (expected 1)`);
155
+ assert(data.projects[0].name === PROJECT_NAME, `Project name: ${data.projects[0].name}`);
156
+
157
+ const stats = getStats(data.stories);
158
+ assert(stats.total === 4, `Total stories: ${stats.total}`);
159
+ assert(stats.byStatus.done === 1, `Done: ${stats.byStatus.done}`);
160
+ assert(stats.byStatus.dev === 1, `Dev: ${stats.byStatus.dev}`);
161
+ assert(stats.byStatus.create === 1, `Create: ${stats.byStatus.create}`);
162
+ assert(stats.byStatus.backlog === 1, `Backlog: ${stats.byStatus.backlog}`);
163
+ assert(stats.avgProgress === 38, `Avg progress: ${stats.avgProgress}% (expected 38%)`);
164
+
165
+ // Display the actual dashboard
166
+ console.log('');
167
+ dashboard(wsDir);
168
+
169
+ // --- Step 4: Simulate add-project ---
170
+ header('Step 4: Add second project');
171
+
172
+ const proj2 = 'customer-app';
173
+ fs.mkdirSync(path.join(wsDir, proj2), { recursive: true });
174
+
175
+ let backlog2 = fs.readFileSync(path.join(templatesDir, 'backlog.md.tmpl'), 'utf-8');
176
+ backlog2 = backlog2.replace(/\{\{projectName\}\}/g, proj2);
177
+ fs.writeFileSync(path.join(wsDir, proj2, 'backlog.md'), backlog2);
178
+
179
+ let rules2 = fs.readFileSync(path.join(templatesDir, 'RULES.md.tmpl'), 'utf-8');
180
+ rules2 = rules2.replace(/\{\{projectName\}\}/g, proj2);
181
+ fs.writeFileSync(path.join(wsDir, proj2, 'RULES.md'), rules2);
182
+
183
+ createStoryInProject(5, 'Order placement flow', 'P0', 'create', proj2);
184
+
185
+ function createStoryInProject(id, title, priority, status, project) {
186
+ const story = `---
187
+ id: US-${String(id).padStart(3, '0')}
188
+ project: ${project}
189
+ status: ${status}
190
+ phase: planning
191
+ progress: 0
192
+ priority: ${priority}
193
+ created: 2026-02-09
194
+ ---
195
+
196
+ # US-${String(id).padStart(3, '0')}: ${title}
197
+
198
+ ## Goal
199
+ ${title}.
200
+
201
+ ## Acceptance Criteria
202
+ 1. Feature works as expected
203
+ `;
204
+ fs.writeFileSync(path.join(wsDir, project, `US-${String(id).padStart(3, '0')}.md`), story);
205
+ }
206
+
207
+ assert(fs.existsSync(path.join(wsDir, proj2, 'US-005.md')), 'customer-app/US-005.md created');
208
+
209
+ // --- Step 5: Multi-project dashboard ---
210
+ header('Step 5: Multi-project dashboard');
211
+
212
+ const data2 = scanAllStories(wsDir);
213
+ assert(data2.stories.length === 5, `Found ${data2.stories.length} stories across all projects`);
214
+ assert(data2.projects.length === 2, `Found ${data2.projects.length} projects`);
215
+
216
+ console.log('');
217
+ dashboard(wsDir);
218
+
219
+ // --- Step 6: Config auto-detection ---
220
+ header('Step 6: Config auto-detection');
221
+
222
+ const loadedConfig = loadConfig(wsDir);
223
+ assert(loadedConfig !== null, 'Config loaded from workspace');
224
+ assert(loadedConfig.name === WORKSPACE_NAME, `Workspace name: ${loadedConfig.name}`);
225
+ assert(loadedConfig.projects[PROJECT_NAME] !== undefined, `Project "${PROJECT_NAME}" in config`);
226
+
227
+ // --- Summary ---
228
+ header('TEST RESULTS');
229
+ console.log(chalk.bold.green('\n ✅ ALL TESTS PASSED!\n'));
230
+ console.log(chalk.white(' Sarah\'s workspace:'));
231
+ console.log(chalk.gray(` ${wsDir}/`));
232
+ console.log(chalk.gray(` ├── .prd.config.json`));
233
+ console.log(chalk.gray(` ├── PROCESS.md`));
234
+ console.log(chalk.gray(` ├── README.md`));
235
+ console.log(chalk.gray(` ├── delivery-api/ (4 stories)`));
236
+ console.log(chalk.gray(` └── customer-app/ (1 story)\n`));
237
+ console.log(chalk.gray(` Workspace: ${wsFilePath}\n`));