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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nimrod Margalit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # Product OS Framework
2
+
3
+ An AI-native product development lifecycle for [Cursor](https://cursor.com). From idea to production with quality gates, slash commands, and real-time visibility.
4
+
5
+ ## What It Does
6
+
7
+ Product OS gives your team a structured workflow for building features:
8
+
9
+ ```
10
+ PM creates & verifies PRD → Dev builds & tests → Team releases & documents
11
+ ```
12
+
13
+ **Four slash commands in Cursor:**
14
+
15
+ | Command | Owner | What Happens |
16
+ |---------|-------|--------------|
17
+ | `/create` | PM | Define → Verify → Extract Features → PRD approved |
18
+ | `/verify` | PM | Quality check any PRD |
19
+ | `/dev` | Developer | Kickoff → Code → Test → PR ready |
20
+ | `/release` | Team | Review → Merge → Deploy → Document |
21
+
22
+ **Plus a CLI dashboard:**
23
+
24
+ ```
25
+ $ prd
26
+
27
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
28
+ 📊 PRODUCT STORIES DASHBOARD
29
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
30
+
31
+ Summary:
32
+ Total Stories: 5
33
+ Average Progress: 30%
34
+ Blocked: 0
35
+
36
+ delivery-api (4 stories)
37
+ US-001 ✅ Done P0
38
+ Real-time order tracking
39
+ ████████████ 100%
40
+
41
+ US-002 🔨 In Dev P1
42
+ Restaurant menu management
43
+ ██████░░░░░░ 50%
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ### 1. Install
49
+
50
+ ```bash
51
+ npm install -g prd-cli
52
+ ```
53
+
54
+ ### 2. Create Your Workspace
55
+
56
+ ```bash
57
+ prd init
58
+ ```
59
+
60
+ Answer the prompts:
61
+ - Workspace name (e.g., `product-docs`)
62
+ - First project name (e.g., `my-app`)
63
+ - Path to dev repo (optional)
64
+
65
+ This creates your product docs folder, templates, and a `.code-workspace` file.
66
+
67
+ ### 3. Open in Cursor
68
+
69
+ ```bash
70
+ cursor your-workspace.code-workspace
71
+ ```
72
+
73
+ Both your product docs and dev repo are connected automatically.
74
+
75
+ ### 4. Create Your First Story
76
+
77
+ In Cursor chat:
78
+ ```
79
+ /create "User authentication with OAuth" @my-app/backlog.md
80
+ ```
81
+
82
+ The AI agent walks you through defining requirements, verifying quality, and extracting features.
83
+
84
+ ### 5. View Dashboard
85
+
86
+ ```bash
87
+ prd
88
+ ```
89
+
90
+ ## How It Works
91
+
92
+ ### For Product Managers
93
+
94
+ 1. **`/create`** - Interactive PRD creation with AI-powered refinement
95
+ 2. **`/verify`** - Automated quality checks for your PRD
96
+ 3. **`prd dashboard`** - See all stories across all projects
97
+
98
+ ### For Developers
99
+
100
+ 1. **`/dev US-001`** - AI reads the PRD + RULES.md, generates tasks, builds iteratively
101
+ 2. **`/release US-001`** - Merge, deploy, document
102
+
103
+ ### Workspace Structure
104
+
105
+ ```
106
+ your-product-docs/
107
+ ├── .prd.config.json # Workspace config
108
+ ├── PROCESS.md # The methodology
109
+ ├── README.md # Project registry
110
+ └── my-app/ # Project folder
111
+ ├── backlog.md # Story tracking
112
+ ├── RULES.md # Technical standards
113
+ ├── US-001.md # Story files
114
+ └── US-002.md
115
+ ```
116
+
117
+ Each project folder contains stories (`US-*.md`) with YAML frontmatter that powers the dashboard and slash commands.
118
+
119
+ ## CLI Commands
120
+
121
+ | Command | Description |
122
+ |---------|-------------|
123
+ | `prd init` | Create a new product workspace |
124
+ | `prd add-project` | Add another project |
125
+ | `prd` or `prd dashboard` | Full stories dashboard |
126
+ | `prd list` | Compact list view |
127
+ | `prd stats` | Quick statistics |
128
+ | `prd dashboard --status done` | Filter by status |
129
+ | `prd dashboard --phase dev` | Filter by phase |
130
+
131
+ ## Key Concepts
132
+
133
+ ### Quality Gates
134
+ Every PRD goes through automated verification before development starts. Gaps are identified and suggested fixes offered.
135
+
136
+ ### RULES.md
137
+ Each project has a `RULES.md` defining technical standards (tech stack, patterns, testing). AI agents read this during `/dev` to ensure consistent implementation.
138
+
139
+ ### YAML Frontmatter
140
+ Stories include metadata that powers the dashboard:
141
+
142
+ ```yaml
143
+ ---
144
+ id: US-001
145
+ project: my-app
146
+ status: dev
147
+ phase: development
148
+ progress: 50
149
+ priority: P0
150
+ ---
151
+ ```
152
+
153
+ ### Global Story IDs
154
+ Stories use globally unique IDs (`US-001`, `US-002`, ...) across all projects. No conflicts, easy cross-referencing.
155
+
156
+ ## Built For
157
+
158
+ - **Cursor IDE** - Slash commands powered by AI agents
159
+ - **Small teams** - PMs + Developers working together
160
+ - **Multiple projects** - One workspace, many products
161
+ - **AI-native development** - Quality gates designed for LLM-assisted workflows
162
+
163
+ ## Industry Alignment
164
+
165
+ | Product OS | Maps To |
166
+ |------------|---------|
167
+ | `/create` + `/verify` | **Dual-Track Agile** Discovery / **Shape Up** Shaping |
168
+ | `/dev` | **Dual-Track Agile** Delivery / **Shape Up** Building |
169
+ | `/release` | **Lean** Measure / **Shape Up** Shipping |
170
+
171
+ ## Contributing
172
+
173
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
174
+
175
+ ## License
176
+
177
+ [MIT](LICENSE)
178
+
179
+ ---
180
+
181
+ **Product OS Framework** - Built by [Nimrod Margalit](https://github.com/nimidev)
package/bin/prd.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Product OS Framework CLI
5
+ * AI-native product development lifecycle for Cursor
6
+ */
7
+
8
+ const { Command } = require('commander');
9
+ const path = require('path');
10
+ const { findWorkspaceRoot, loadConfig } = require('../lib/config');
11
+ const { dashboard } = require('../lib/commands/dashboard');
12
+
13
+ const program = new Command();
14
+
15
+ program
16
+ .name('prd')
17
+ .description('Product OS Framework - AI-native product development lifecycle for Cursor')
18
+ .version('1.0.0');
19
+
20
+ // --- prd init ---
21
+ program
22
+ .command('init')
23
+ .description('Create a new product workspace')
24
+ .action(async () => {
25
+ const { init } = require('../lib/commands/init');
26
+ await init();
27
+ });
28
+
29
+ // --- prd add-project ---
30
+ program
31
+ .command('add-project')
32
+ .description('Add a new project to the workspace')
33
+ .action(async () => {
34
+ const { addProject } = require('../lib/commands/add-project');
35
+ await addProject();
36
+ });
37
+
38
+ // --- prd dashboard ---
39
+ program
40
+ .command('dashboard')
41
+ .description('Display product stories dashboard')
42
+ .option('-p, --path <path>', 'Path to product-docs workspace')
43
+ .option('-s, --status <status>', 'Filter by status (backlog, dev, done, etc.)')
44
+ .option('--phase <phase>', 'Filter by phase (create, dev, release)')
45
+ .option('-c, --compact', 'Compact output')
46
+ .action((options) => {
47
+ const docsPath = resolveDocsPath(options.path);
48
+ dashboard(docsPath, options);
49
+ });
50
+
51
+ // --- prd list ---
52
+ program
53
+ .command('list')
54
+ .description('List all stories (compact view)')
55
+ .option('-p, --path <path>', 'Path to product-docs workspace')
56
+ .action((options) => {
57
+ const docsPath = resolveDocsPath(options.path);
58
+ dashboard(docsPath, { ...options, compact: true });
59
+ });
60
+
61
+ // --- prd stats ---
62
+ program
63
+ .command('stats')
64
+ .description('Show statistics summary')
65
+ .option('-p, --path <path>', 'Path to product-docs workspace')
66
+ .action((options) => {
67
+ const chalk = require('chalk');
68
+ const { scanAllStories, getStats } = require('../lib/parser');
69
+
70
+ const docsPath = resolveDocsPath(options.path);
71
+ try {
72
+ const data = scanAllStories(docsPath);
73
+ const stats = getStats(data.stories);
74
+
75
+ console.log('\n' + chalk.bold.cyan('📊 Statistics'));
76
+ console.log(chalk.gray('─'.repeat(40)));
77
+ console.log(`Total: ${chalk.green.bold(stats.total)}`);
78
+ console.log(`Progress: ${chalk.yellow.bold(stats.avgProgress + '%')}`);
79
+ console.log(`Blocked: ${stats.blocked > 0 ? chalk.red.bold(stats.blocked) : chalk.gray('0')}`);
80
+ console.log('');
81
+ } catch (err) {
82
+ console.error(chalk.red('Error:'), err.message);
83
+ process.exit(1);
84
+ }
85
+ });
86
+
87
+ // --- Default: show dashboard if no command given ---
88
+ if (process.argv.length === 2) {
89
+ const docsPath = resolveDocsPath();
90
+ if (docsPath) {
91
+ dashboard(docsPath);
92
+ } else {
93
+ const chalk = require('chalk');
94
+ console.log('\n' + chalk.bold.cyan('Product OS Framework'));
95
+ console.log(chalk.gray('─'.repeat(40)));
96
+ console.log(chalk.white('No product workspace found.\n'));
97
+ console.log(chalk.white('Get started:'));
98
+ console.log(chalk.cyan(' prd init') + chalk.gray(' Create a new workspace'));
99
+ console.log(chalk.cyan(' prd dashboard') + chalk.gray(' View stories dashboard'));
100
+ console.log(chalk.cyan(' prd --help') + chalk.gray(' See all commands\n'));
101
+ }
102
+ } else {
103
+ program.parse();
104
+ }
105
+
106
+ /**
107
+ * Resolve the product-docs workspace path
108
+ * Priority: explicit --path > auto-detect via config > cwd
109
+ */
110
+ function resolveDocsPath(explicitPath) {
111
+ if (explicitPath) return path.resolve(explicitPath);
112
+
113
+ const root = findWorkspaceRoot(process.cwd());
114
+ if (root) return root;
115
+
116
+ // Fallback: current directory
117
+ return process.cwd();
118
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * prd add-project - Add a new project to the workspace
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const chalk = require('chalk');
8
+ const inquirer = require('inquirer');
9
+ const { loadConfig, saveConfig, CONFIG_FILENAME } = require('../config');
10
+
11
+ /**
12
+ * Update .code-workspace file to include new project's dev repo
13
+ */
14
+ function updateWorkspaceFile(workspaceRoot, projects) {
15
+ // Find .code-workspace files in parent directory
16
+ const parentDir = path.dirname(workspaceRoot);
17
+ const wsFiles = fs.readdirSync(parentDir).filter(f => f.endsWith('.code-workspace'));
18
+
19
+ for (const wsFile of wsFiles) {
20
+ const wsPath = path.join(parentDir, wsFile);
21
+ try {
22
+ const ws = JSON.parse(fs.readFileSync(wsPath, 'utf-8'));
23
+
24
+ // Add any new project repos that aren't already in the workspace
25
+ for (const [name, proj] of Object.entries(projects)) {
26
+ if (!proj.repoPath) continue;
27
+ const relPath = path.relative(parentDir, proj.repoPath);
28
+ const alreadyIncluded = ws.folders.some(f => f.path === relPath || f.name === name);
29
+ if (!alreadyIncluded) {
30
+ ws.folders.push({ name, path: relPath });
31
+ }
32
+ }
33
+
34
+ fs.writeFileSync(wsPath, JSON.stringify(ws, null, 2) + '\n', 'utf-8');
35
+ return wsPath;
36
+ } catch (err) {
37
+ // Skip malformed workspace files
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Main add-project command
45
+ */
46
+ async function addProject(options = {}) {
47
+ const config = loadConfig();
48
+ if (!config) {
49
+ console.log(chalk.red('\nNo Product OS workspace found.'));
50
+ console.log(chalk.gray(`Run ${chalk.cyan('prd init')} to create one.\n`));
51
+ return;
52
+ }
53
+
54
+ const workspaceRoot = config._root;
55
+ const templatesDir = path.join(__dirname, '..', '..', 'templates');
56
+
57
+ console.log(chalk.gray('\nAdding a new project to your workspace.\n'));
58
+
59
+ const answers = await inquirer.prompt([
60
+ {
61
+ type: 'input',
62
+ name: 'projectName',
63
+ message: 'Project name:',
64
+ validate: (v) => {
65
+ if (!v.trim()) return 'Project name is required';
66
+ if (/[^a-zA-Z0-9_-]/.test(v.trim())) return 'Use only letters, numbers, hyphens, underscores';
67
+ if (config.projects[v.trim()]) return 'Project already exists';
68
+ return true;
69
+ }
70
+ },
71
+ {
72
+ type: 'input',
73
+ name: 'description',
74
+ message: 'Project description (optional):',
75
+ default: ''
76
+ },
77
+ {
78
+ type: 'confirm',
79
+ name: 'hasRepo',
80
+ message: 'Do you have an existing dev repo for this project?',
81
+ default: false
82
+ },
83
+ {
84
+ type: 'input',
85
+ name: 'repoPath',
86
+ message: 'Path to the dev repo:',
87
+ when: (a) => a.hasRepo,
88
+ validate: (v) => {
89
+ const resolved = path.resolve(v);
90
+ if (!fs.existsSync(resolved)) return `Path not found: ${resolved}`;
91
+ return true;
92
+ }
93
+ }
94
+ ]);
95
+
96
+ const projName = answers.projectName.trim();
97
+ const projDesc = answers.description.trim();
98
+ const repoPath = answers.repoPath ? path.resolve(answers.repoPath) : null;
99
+
100
+ // Create project directory
101
+ const projDir = path.join(workspaceRoot, projName);
102
+ fs.mkdirSync(projDir, { recursive: true });
103
+
104
+ // Create backlog
105
+ const backlogTmpl = path.join(templatesDir, 'backlog.md.tmpl');
106
+ if (fs.existsSync(backlogTmpl)) {
107
+ let content = fs.readFileSync(backlogTmpl, 'utf-8');
108
+ content = content.replace(/\{\{projectName\}\}/g, projName);
109
+ fs.writeFileSync(path.join(projDir, 'backlog.md'), content, 'utf-8');
110
+ } else {
111
+ fs.writeFileSync(path.join(projDir, 'backlog.md'),
112
+ `# ${projName} - Backlog\n\n| ID | Title | Status | Priority |\n|----|-------|--------|----------|\n`, 'utf-8');
113
+ }
114
+ console.log(chalk.green(' ✅ Created ') + chalk.white(`${projName}/backlog.md`));
115
+
116
+ // Create RULES.md
117
+ const rulesTmpl = path.join(templatesDir, 'RULES.md.tmpl');
118
+ if (fs.existsSync(rulesTmpl)) {
119
+ let content = fs.readFileSync(rulesTmpl, 'utf-8');
120
+ content = content.replace(/\{\{projectName\}\}/g, projName);
121
+ fs.writeFileSync(path.join(projDir, 'RULES.md'), content, 'utf-8');
122
+ } else {
123
+ fs.writeFileSync(path.join(projDir, 'RULES.md'),
124
+ `# ${projName} - Technical Standards\n\nDocument your project's tech stack, patterns, and conventions here.\n`, 'utf-8');
125
+ }
126
+ console.log(chalk.green(' ✅ Created ') + chalk.white(`${projName}/RULES.md`));
127
+
128
+ // Update config
129
+ config.projects[projName] = {
130
+ description: projDesc,
131
+ repoPath
132
+ };
133
+ saveConfig(config);
134
+ console.log(chalk.green(' ✅ Updated ') + chalk.white(CONFIG_FILENAME));
135
+
136
+ // Update README project table
137
+ const readmePath = path.join(workspaceRoot, 'README.md');
138
+ if (fs.existsSync(readmePath)) {
139
+ let readme = fs.readFileSync(readmePath, 'utf-8');
140
+ const tableRow = `| [${projName}](./${projName}/) | ${projDesc || ''} |`;
141
+ // Insert before the empty line after the last table row
142
+ readme = readme.replace(
143
+ /(## Projects\n\n\|[^\n]+\n\|[^\n]+\n)([\s\S]*?)(\n\n)/,
144
+ (match, header, rows, gap) => `${header}${rows}\n${tableRow}${gap}`
145
+ );
146
+ fs.writeFileSync(readmePath, readme, 'utf-8');
147
+ console.log(chalk.green(' ✅ Updated ') + chalk.white('README.md'));
148
+ }
149
+
150
+ // Update workspace file
151
+ if (repoPath) {
152
+ const wsFile = updateWorkspaceFile(workspaceRoot, config.projects);
153
+ if (wsFile) {
154
+ console.log(chalk.green(' ✅ Updated ') + chalk.white(path.basename(wsFile)));
155
+ }
156
+ }
157
+
158
+ console.log('\n' + chalk.bold.green(`🎉 Project "${projName}" added!\n`));
159
+ console.log(chalk.white('Create stories in Cursor:'));
160
+ console.log(chalk.cyan(` /create "feature" @${projName}/backlog.md\n`));
161
+ }
162
+
163
+ module.exports = { addProject };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Dashboard command - Rich terminal output
3
+ */
4
+
5
+ const chalk = require('chalk');
6
+ const { scanAllStories, getStats } = require('../parser');
7
+
8
+ const STATUS_CONFIG = {
9
+ backlog: { icon: '📋', color: 'gray', label: 'Backlog' },
10
+ create: { icon: '✏️', color: 'blue', label: 'Creating' },
11
+ approved: { icon: '✅', color: 'green', label: 'Approved' },
12
+ dev: { icon: '🔨', color: 'yellow', label: 'In Dev' },
13
+ in_progress: { icon: '🔄', color: 'yellow', label: 'In Progress' },
14
+ testing: { icon: '🧪', color: 'cyan', label: 'Testing' },
15
+ review: { icon: '👀', color: 'magenta', label: 'Review' },
16
+ release: { icon: '🚀', color: 'green', label: 'Release' },
17
+ done: { icon: '✅', color: 'green', label: 'Done' },
18
+ blocked: { icon: '🚫', color: 'red', label: 'Blocked' }
19
+ };
20
+
21
+ const PRIORITY_COLORS = {
22
+ P0: 'red',
23
+ P1: 'yellow',
24
+ P2: 'blue',
25
+ P3: 'gray'
26
+ };
27
+
28
+ function formatProgressBar(progress) {
29
+ const [completed, total] = progress.split('/').map(Number);
30
+ if (total === 0) return chalk.gray('N/A');
31
+
32
+ const percentage = Math.round((completed / total) * 100);
33
+ const barLength = 12;
34
+ const filledLength = Math.round((completed / total) * barLength);
35
+ const emptyLength = barLength - filledLength;
36
+
37
+ const filled = '█'.repeat(filledLength);
38
+ const empty = '░'.repeat(emptyLength);
39
+
40
+ let color = 'red';
41
+ if (percentage >= 80) color = 'green';
42
+ else if (percentage >= 50) color = 'yellow';
43
+
44
+ return chalk[color](filled) + chalk.gray(empty) + chalk.white(` ${percentage}%`);
45
+ }
46
+
47
+ function formatStatus(status) {
48
+ const config = STATUS_CONFIG[status] || { icon: '❓', color: 'gray', label: status };
49
+ return config.icon + ' ' + chalk[config.color](config.label);
50
+ }
51
+
52
+ function formatPriority(priority) {
53
+ const color = PRIORITY_COLORS[priority] || 'gray';
54
+ return chalk[color].bold(priority);
55
+ }
56
+
57
+ function displaySummary(stats) {
58
+ console.log('\n' + chalk.bold.cyan('━'.repeat(60)));
59
+ console.log(chalk.bold.cyan(' 📊 PRODUCT STORIES DASHBOARD'));
60
+ console.log(chalk.bold.cyan('━'.repeat(60)) + '\n');
61
+
62
+ console.log(chalk.white.bold('Summary:'));
63
+ console.log(` Total Stories: ${chalk.green.bold(stats.total)}`);
64
+ console.log(` Average Progress: ${chalk.yellow.bold(stats.avgProgress + '%')}`);
65
+ console.log(` Blocked: ${stats.blocked > 0 ? chalk.red.bold(stats.blocked) : chalk.gray('0')}`);
66
+
67
+ console.log('\n' + chalk.white.bold('By Status:'));
68
+ Object.entries(stats.byStatus).forEach(([status, count]) => {
69
+ const config = STATUS_CONFIG[status] || { icon: '❓', color: 'gray' };
70
+ console.log(` ${config.icon} ${chalk[config.color](status.padEnd(12))} ${count}`);
71
+ });
72
+
73
+ console.log('\n' + chalk.white.bold('By Priority:'));
74
+ Object.entries(stats.byPriority).forEach(([priority, count]) => {
75
+ console.log(` ${formatPriority(priority)} ${count}`);
76
+ });
77
+
78
+ console.log('\n' + chalk.gray('─'.repeat(60)) + '\n');
79
+ }
80
+
81
+ function displayProjects(projects, options = {}) {
82
+ for (const project of projects) {
83
+ console.log(chalk.bold.white(`\n${project.name}`) + chalk.gray(` (${project.storyCount} stories)`));
84
+ console.log(chalk.gray('─'.repeat(60)));
85
+
86
+ const stories = project.stories.sort((a, b) => a.id.localeCompare(b.id));
87
+
88
+ let filtered = stories;
89
+ if (options.status) {
90
+ filtered = filtered.filter(s => s.status === options.status);
91
+ }
92
+ if (options.phase) {
93
+ filtered = filtered.filter(s => s.phase === options.phase);
94
+ }
95
+
96
+ if (filtered.length === 0) {
97
+ console.log(chalk.gray(' No stories match filters\n'));
98
+ continue;
99
+ }
100
+
101
+ for (const story of filtered) {
102
+ const statusDisplay = formatStatus(story.status);
103
+ const priorityDisplay = formatPriority(story.priority);
104
+ const progressBar = formatProgressBar(story.progress);
105
+ const blockerIndicator = story.blockers.length > 0 ? chalk.red.bold(' 🚫') : '';
106
+
107
+ console.log(` ${chalk.cyan.bold(story.id)} ${statusDisplay} ${priorityDisplay}${blockerIndicator}`);
108
+ console.log(` ${chalk.white(story.title)}`);
109
+ console.log(` ${progressBar} ${chalk.gray(story.progress + ' AC')}`);
110
+
111
+ if (story.blockers.length > 0) {
112
+ console.log(` ${chalk.red('Blocked by:')} ${story.blockers.join(', ')}`);
113
+ }
114
+
115
+ console.log('');
116
+ }
117
+ }
118
+ }
119
+
120
+ function displayCompact(projects) {
121
+ for (const project of projects) {
122
+ console.log(chalk.bold.white(`\n${project.name}`));
123
+
124
+ const stories = project.stories.sort((a, b) => a.id.localeCompare(b.id));
125
+
126
+ for (const story of stories) {
127
+ const config = STATUS_CONFIG[story.status] || { icon: '❓' };
128
+ const [completed, total] = story.progress.split('/').map(Number);
129
+ const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
130
+ const blockerIndicator = story.blockers.length > 0 ? chalk.red(' 🚫') : '';
131
+
132
+ console.log(` ${config.icon} ${chalk.cyan(story.id)} ${chalk.gray(story.title)} ${chalk.yellow(percentage + '%')}${blockerIndicator}`);
133
+ }
134
+ }
135
+ console.log('');
136
+ }
137
+
138
+ /**
139
+ * Main dashboard command
140
+ * @param {string} docsPath - Path to product-docs workspace
141
+ * @param {Object} options
142
+ */
143
+ function dashboard(docsPath, options = {}) {
144
+ try {
145
+ const data = scanAllStories(docsPath);
146
+ const stats = getStats(data.stories);
147
+
148
+ if (data.stories.length === 0) {
149
+ console.log(chalk.yellow('\nNo stories found in ' + docsPath));
150
+ console.log(chalk.gray('Create your first story in Cursor: /create "feature" @project/backlog.md\n'));
151
+ return;
152
+ }
153
+
154
+ if (!options.compact) {
155
+ displaySummary(stats);
156
+ }
157
+
158
+ if (options.compact) {
159
+ displayCompact(data.projects);
160
+ } else {
161
+ displayProjects(data.projects, options);
162
+ }
163
+
164
+ console.log(chalk.gray(`Last updated: ${data.lastUpdated.toLocaleString()}`));
165
+ console.log('');
166
+
167
+ } catch (err) {
168
+ console.error(chalk.red('Error:'), err.message);
169
+ process.exit(1);
170
+ }
171
+ }
172
+
173
+ module.exports = { dashboard };