@lusipad/pmspec 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.
Files changed (60) hide show
  1. package/README.md +306 -0
  2. package/README.zh.md +304 -0
  3. package/bin/pmspec.js +5 -0
  4. package/dist/cli/index.d.ts +3 -0
  5. package/dist/cli/index.js +39 -0
  6. package/dist/commands/analyze.d.ts +4 -0
  7. package/dist/commands/analyze.js +240 -0
  8. package/dist/commands/breakdown.d.ts +4 -0
  9. package/dist/commands/breakdown.js +194 -0
  10. package/dist/commands/create.d.ts +4 -0
  11. package/dist/commands/create.js +529 -0
  12. package/dist/commands/history.d.ts +4 -0
  13. package/dist/commands/history.js +213 -0
  14. package/dist/commands/import.d.ts +4 -0
  15. package/dist/commands/import.js +196 -0
  16. package/dist/commands/index-legacy.d.ts +4 -0
  17. package/dist/commands/index-legacy.js +27 -0
  18. package/dist/commands/init.d.ts +3 -0
  19. package/dist/commands/init.js +60 -0
  20. package/dist/commands/list.d.ts +3 -0
  21. package/dist/commands/list.js +127 -0
  22. package/dist/commands/search.d.ts +7 -0
  23. package/dist/commands/search.js +183 -0
  24. package/dist/commands/serve.d.ts +3 -0
  25. package/dist/commands/serve.js +68 -0
  26. package/dist/commands/show.d.ts +3 -0
  27. package/dist/commands/show.js +152 -0
  28. package/dist/commands/simple.d.ts +7 -0
  29. package/dist/commands/simple.js +360 -0
  30. package/dist/commands/update.d.ts +4 -0
  31. package/dist/commands/update.js +247 -0
  32. package/dist/commands/validate.d.ts +3 -0
  33. package/dist/commands/validate.js +74 -0
  34. package/dist/core/changelog-service.d.ts +88 -0
  35. package/dist/core/changelog-service.js +208 -0
  36. package/dist/core/changelog.d.ts +113 -0
  37. package/dist/core/changelog.js +147 -0
  38. package/dist/core/importers.d.ts +343 -0
  39. package/dist/core/importers.js +715 -0
  40. package/dist/core/parser.d.ts +50 -0
  41. package/dist/core/parser.js +246 -0
  42. package/dist/core/project.d.ts +155 -0
  43. package/dist/core/project.js +138 -0
  44. package/dist/core/search.d.ts +119 -0
  45. package/dist/core/search.js +299 -0
  46. package/dist/core/simple-model.d.ts +54 -0
  47. package/dist/core/simple-model.js +20 -0
  48. package/dist/core/team.d.ts +41 -0
  49. package/dist/core/team.js +57 -0
  50. package/dist/core/workload.d.ts +49 -0
  51. package/dist/core/workload.js +116 -0
  52. package/dist/index.d.ts +15 -0
  53. package/dist/index.js +11 -0
  54. package/dist/utils/csv-handler.d.ts +15 -0
  55. package/dist/utils/csv-handler.js +224 -0
  56. package/dist/utils/markdown.d.ts +43 -0
  57. package/dist/utils/markdown.js +202 -0
  58. package/dist/utils/validation.d.ts +35 -0
  59. package/dist/utils/validation.js +178 -0
  60. package/package.json +71 -0
@@ -0,0 +1,213 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { ChangelogService } from '../core/changelog-service.js';
4
+ const historyCommand = new Command('history')
5
+ .description('Show change history for entities')
6
+ .argument('[entityId]', 'Entity ID to show history for (e.g., FEAT-001, EPIC-001)')
7
+ .option('-a, --all', 'Show all change history')
8
+ .option('--since <date>', 'Show changes since date (YYYY-MM-DD)')
9
+ .option('--until <date>', 'Show changes until date (YYYY-MM-DD)')
10
+ .option('-t, --type <type>', 'Filter by entity type (epic, feature, milestone, story)')
11
+ .option('--action <action>', 'Filter by action (create, update, delete)')
12
+ .option('-n, --limit <number>', 'Limit number of entries', '50')
13
+ .option('--json', 'Output as JSON')
14
+ .option('--diff', 'Show detailed diff view for updates')
15
+ .option('--stats', 'Show changelog statistics')
16
+ .action(async (entityId, options) => {
17
+ try {
18
+ const service = new ChangelogService();
19
+ // Show stats if requested
20
+ if (options.stats) {
21
+ await showStats(service);
22
+ return;
23
+ }
24
+ // Validate options
25
+ if (!entityId && !options.all && !options.since && !options.type) {
26
+ console.error(chalk.red('Error: Please specify an entity ID, use --all, or provide filter options'));
27
+ console.log(chalk.yellow('Usage examples:'));
28
+ console.log(chalk.gray(' pmspec history FEAT-001 # Show history for FEAT-001'));
29
+ console.log(chalk.gray(' pmspec history --all # Show all changes'));
30
+ console.log(chalk.gray(' pmspec history --since 2024-01-01'));
31
+ console.log(chalk.gray(' pmspec history --type feature'));
32
+ console.log(chalk.gray(' pmspec history --stats # Show statistics'));
33
+ process.exit(1);
34
+ }
35
+ // Build query options
36
+ const queryOptions = {
37
+ limit: parseInt(options.limit) || 50,
38
+ };
39
+ if (entityId) {
40
+ queryOptions.entityId = entityId.toUpperCase();
41
+ }
42
+ if (options.since) {
43
+ queryOptions.since = new Date(options.since).toISOString();
44
+ }
45
+ if (options.until) {
46
+ queryOptions.until = new Date(options.until).toISOString();
47
+ }
48
+ if (options.type) {
49
+ queryOptions.entityType = options.type.toLowerCase();
50
+ }
51
+ if (options.action) {
52
+ queryOptions.action = options.action.toLowerCase();
53
+ }
54
+ const entries = await service.query(queryOptions);
55
+ if (entries.length === 0) {
56
+ console.log(chalk.yellow('No change history found matching the criteria'));
57
+ return;
58
+ }
59
+ if (options.json) {
60
+ console.log(JSON.stringify(entries, null, 2));
61
+ return;
62
+ }
63
+ // Display header
64
+ if (entityId) {
65
+ console.log(chalk.blue.bold(`\n📜 Change History for ${entityId.toUpperCase()}\n`));
66
+ }
67
+ else {
68
+ console.log(chalk.blue.bold(`\n📜 Change History (${entries.length} entries)\n`));
69
+ }
70
+ // Display entries
71
+ if (options.diff) {
72
+ displayDiffView(entries);
73
+ }
74
+ else {
75
+ displayTimelineView(entries);
76
+ }
77
+ }
78
+ catch (error) {
79
+ console.error(chalk.red('Error:'), error.message);
80
+ process.exit(1);
81
+ }
82
+ });
83
+ function displayTimelineView(entries) {
84
+ // Group entries by date
85
+ const grouped = new Map();
86
+ for (const entry of entries) {
87
+ const date = entry.timestamp.split('T')[0];
88
+ if (!grouped.has(date)) {
89
+ grouped.set(date, []);
90
+ }
91
+ grouped.get(date).push(entry);
92
+ }
93
+ for (const [date, dateEntries] of grouped) {
94
+ console.log(chalk.yellow(`\n${formatDate(date)}`));
95
+ console.log(chalk.gray('─'.repeat(40)));
96
+ for (const entry of dateEntries) {
97
+ const time = formatTime(entry.timestamp);
98
+ const icon = getActionIcon(entry.action);
99
+ const color = getActionColor(entry.action);
100
+ console.log(`${chalk.gray(time)} ${icon} ${color(formatEntryDescription(entry))}`);
101
+ if (entry.action === 'update' && entry.field) {
102
+ const oldVal = formatDisplayValue(entry.oldValue);
103
+ const newVal = formatDisplayValue(entry.newValue);
104
+ console.log(chalk.gray(` ${entry.field}: ${oldVal} → ${newVal}`));
105
+ }
106
+ }
107
+ }
108
+ console.log('');
109
+ }
110
+ function displayDiffView(entries) {
111
+ for (const entry of entries) {
112
+ console.log(chalk.gray('─'.repeat(60)));
113
+ console.log(`${chalk.bold(entry.id)} - ${formatDateTime(entry.timestamp)}`);
114
+ console.log(`${getActionIcon(entry.action)} ${getActionColor(entry.action)(entry.action.toUpperCase())} ${entry.entityType} ${chalk.cyan(entry.entityId)}`);
115
+ if (entry.user) {
116
+ console.log(chalk.gray(` by ${entry.user}`));
117
+ }
118
+ if (entry.action === 'update' && entry.field) {
119
+ console.log('');
120
+ console.log(chalk.yellow(` Field: ${entry.field}`));
121
+ console.log(chalk.red(` - ${formatDisplayValue(entry.oldValue)}`));
122
+ console.log(chalk.green(` + ${formatDisplayValue(entry.newValue)}`));
123
+ }
124
+ console.log('');
125
+ }
126
+ }
127
+ async function showStats(service) {
128
+ const stats = await service.getStats();
129
+ console.log(chalk.blue.bold('\n📊 Changelog Statistics\n'));
130
+ console.log(`Total entries: ${chalk.cyan(stats.totalEntries)}`);
131
+ console.log(chalk.yellow('\nBy Entity Type:'));
132
+ for (const [type, count] of Object.entries(stats.byEntityType)) {
133
+ console.log(` ${type}: ${count}`);
134
+ }
135
+ console.log(chalk.yellow('\nBy Action:'));
136
+ for (const [action, count] of Object.entries(stats.byAction)) {
137
+ const icon = getActionIcon(action);
138
+ console.log(` ${icon} ${action}: ${count}`);
139
+ }
140
+ console.log(chalk.yellow('\nRecent Activity:'));
141
+ console.log(` Last 24 hours: ${stats.recentActivity.last24h}`);
142
+ console.log(` Last 7 days: ${stats.recentActivity.last7d}`);
143
+ console.log(` Last 30 days: ${stats.recentActivity.last30d}`);
144
+ console.log('');
145
+ }
146
+ function getActionIcon(action) {
147
+ switch (action) {
148
+ case 'create':
149
+ return '✨';
150
+ case 'update':
151
+ return '📝';
152
+ case 'delete':
153
+ return '🗑️';
154
+ default:
155
+ return '•';
156
+ }
157
+ }
158
+ function getActionColor(action) {
159
+ switch (action) {
160
+ case 'create':
161
+ return (text) => chalk.green(text);
162
+ case 'update':
163
+ return (text) => chalk.blue(text);
164
+ case 'delete':
165
+ return (text) => chalk.red(text);
166
+ default:
167
+ return (text) => text;
168
+ }
169
+ }
170
+ function formatEntryDescription(entry) {
171
+ const entityInfo = `${entry.entityType} ${chalk.cyan(entry.entityId)}`;
172
+ switch (entry.action) {
173
+ case 'create':
174
+ return `Created ${entityInfo}`;
175
+ case 'delete':
176
+ return `Deleted ${entityInfo}`;
177
+ case 'update':
178
+ return `Updated ${entityInfo}${entry.field ? ` (${entry.field})` : ''}`;
179
+ default:
180
+ return `${entry.action} ${entityInfo}`;
181
+ }
182
+ }
183
+ function formatDisplayValue(value) {
184
+ if (value === undefined || value === null) {
185
+ return chalk.gray('(empty)');
186
+ }
187
+ if (typeof value === 'object') {
188
+ return JSON.stringify(value);
189
+ }
190
+ return String(value);
191
+ }
192
+ function formatDate(dateStr) {
193
+ const date = new Date(dateStr);
194
+ return date.toLocaleDateString('en-US', {
195
+ weekday: 'long',
196
+ year: 'numeric',
197
+ month: 'long',
198
+ day: 'numeric',
199
+ });
200
+ }
201
+ function formatTime(timestamp) {
202
+ const date = new Date(timestamp);
203
+ return date.toLocaleTimeString('en-US', {
204
+ hour: '2-digit',
205
+ minute: '2-digit',
206
+ });
207
+ }
208
+ function formatDateTime(timestamp) {
209
+ const date = new Date(timestamp);
210
+ return date.toLocaleString();
211
+ }
212
+ export { historyCommand };
213
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ declare const importCommand: Command;
3
+ export { importCommand };
4
+ //# sourceMappingURL=import.d.ts.map
@@ -0,0 +1,196 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { getImporter, getAllImporters, isValidSource, } from '../core/importers.js';
4
+ const importCommand = new Command('import')
5
+ .description('从外部工具导入功能数据')
6
+ .argument('<source>', '导入源: jira, linear, github')
7
+ .option('--file <path>', '导入文件路径')
8
+ .option('--output <file>', '输出文件路径', 'features.csv')
9
+ .option('--dry-run', '预览导入结果,不实际写入文件')
10
+ .option('--merge', '合并到现有项目而不是覆盖')
11
+ .action(async (source, options) => {
12
+ try {
13
+ // Validate source
14
+ if (!isValidSource(source)) {
15
+ const availableSources = getAllImporters().map(i => i.source).join(', ');
16
+ console.error(chalk.red(`错误: 不支持的导入源 "${source}"`));
17
+ console.error(chalk.yellow(`可用的导入源: ${availableSources}`));
18
+ process.exit(1);
19
+ }
20
+ // Validate file option
21
+ if (!options.file) {
22
+ console.error(chalk.red('错误: 必须指定 --file 参数'));
23
+ process.exit(1);
24
+ }
25
+ const importer = getImporter(source);
26
+ console.log(chalk.blue(`\n📥 ${importer.name}`));
27
+ console.log(chalk.gray(`${importer.description}\n`));
28
+ // Perform import
29
+ const result = await importer.import({
30
+ file: options.file,
31
+ dryRun: options.dryRun || false,
32
+ merge: options.merge || false,
33
+ outputFile: options.output,
34
+ });
35
+ // Display results
36
+ displayImportResult(result, options.dryRun);
37
+ }
38
+ catch (error) {
39
+ console.error(chalk.red('导入失败:'), error.message);
40
+ process.exit(1);
41
+ }
42
+ });
43
+ // Jira subcommand
44
+ const jiraCommand = new Command('jira')
45
+ .description('从 Jira JSON 导出文件导入')
46
+ .option('--file <path>', 'Jira 导出文件路径 (必需)')
47
+ .option('--output <file>', '输出文件路径', 'features.csv')
48
+ .option('--dry-run', '预览导入结果')
49
+ .option('--merge', '合并到现有项目')
50
+ .action(async (options) => {
51
+ await runImport('jira', options);
52
+ });
53
+ // Linear subcommand
54
+ const linearCommand = new Command('linear')
55
+ .description('从 Linear JSON 导出文件导入')
56
+ .option('--file <path>', 'Linear 导出文件路径 (必需)')
57
+ .option('--output <file>', '输出文件路径', 'features.csv')
58
+ .option('--dry-run', '预览导入结果')
59
+ .option('--merge', '合并到现有项目')
60
+ .action(async (options) => {
61
+ await runImport('linear', options);
62
+ });
63
+ // GitHub subcommand
64
+ const githubCommand = new Command('github')
65
+ .description('从 GitHub Issues JSON 文件导入')
66
+ .option('--file <path>', 'GitHub 导出文件路径 (必需)')
67
+ .option('--output <file>', '输出文件路径', 'features.csv')
68
+ .option('--dry-run', '预览导入结果')
69
+ .option('--merge', '合并到现有项目')
70
+ .action(async (options) => {
71
+ await runImport('github', options);
72
+ });
73
+ // Add subcommands
74
+ importCommand.addCommand(jiraCommand);
75
+ importCommand.addCommand(linearCommand);
76
+ importCommand.addCommand(githubCommand);
77
+ async function runImport(source, options) {
78
+ try {
79
+ if (!options.file) {
80
+ console.error(chalk.red('错误: 必须指定 --file 参数'));
81
+ process.exit(1);
82
+ }
83
+ const importer = getImporter(source);
84
+ console.log(chalk.blue(`\n📥 ${importer.name}`));
85
+ console.log(chalk.gray(`${importer.description}\n`));
86
+ const result = await importer.import({
87
+ file: options.file,
88
+ dryRun: options.dryRun || false,
89
+ merge: options.merge || false,
90
+ outputFile: options.output,
91
+ });
92
+ displayImportResult(result, options.dryRun);
93
+ }
94
+ catch (error) {
95
+ console.error(chalk.red('导入失败:'), error.message);
96
+ process.exit(1);
97
+ }
98
+ }
99
+ function displayImportResult(result, dryRun) {
100
+ const { stats, errors, warnings, features, epics, milestones } = result;
101
+ // Header
102
+ if (dryRun) {
103
+ console.log(chalk.yellow('🔍 预览模式 - 不会写入任何文件\n'));
104
+ }
105
+ // Statistics
106
+ console.log(chalk.blue.bold('📊 导入统计'));
107
+ console.log(chalk.gray('─'.repeat(40)));
108
+ console.log(`总项目数: ${stats.totalItems}`);
109
+ console.log(`导入功能: ${chalk.green(stats.featuresImported)}`);
110
+ console.log(`导入 Epic: ${chalk.cyan(stats.epicsImported)}`);
111
+ console.log(`导入 Milestone: ${chalk.cyan(stats.milestonesImported)}`);
112
+ console.log(`跳过: ${stats.skipped}`);
113
+ console.log(`错误: ${stats.errors > 0 ? chalk.red(stats.errors) : stats.errors}`);
114
+ console.log(chalk.gray('─'.repeat(40)));
115
+ // Epics summary
116
+ if (epics.length > 0) {
117
+ console.log(chalk.cyan.bold('\n📁 Epic/分类'));
118
+ for (const epic of epics) {
119
+ console.log(` • ${epic.name} ${chalk.gray(`(${epic.originalId})`)}`);
120
+ }
121
+ }
122
+ // Milestones summary
123
+ if (milestones.length > 0) {
124
+ console.log(chalk.cyan.bold('\n🎯 Milestones'));
125
+ for (const milestone of milestones) {
126
+ const dueInfo = milestone.dueDate ? chalk.gray(` 截止: ${milestone.dueDate}`) : '';
127
+ console.log(` • ${milestone.name}${dueInfo}`);
128
+ }
129
+ }
130
+ // Features preview (first 5)
131
+ if (features.length > 0) {
132
+ console.log(chalk.green.bold('\n✨ 功能预览 (前5项)'));
133
+ const previewFeatures = features.slice(0, 5);
134
+ for (const feature of previewFeatures) {
135
+ const priorityColor = getPriorityColor(feature.priority);
136
+ const statusColor = getStatusColor(feature.status);
137
+ console.log(` ${chalk.gray(feature.id)} ${feature.name}`);
138
+ console.log(` ${priorityColor(feature.priority)} | ${statusColor(feature.status)} | ${feature.assignee} | ${feature.estimate}h`);
139
+ if (feature.category) {
140
+ console.log(` ${chalk.gray('分类:')} ${feature.category}`);
141
+ }
142
+ }
143
+ if (features.length > 5) {
144
+ console.log(chalk.gray(` ... 还有 ${features.length - 5} 个功能`));
145
+ }
146
+ }
147
+ // Errors
148
+ if (errors.length > 0) {
149
+ console.log(chalk.red.bold('\n❌ 错误'));
150
+ for (const error of errors) {
151
+ const location = error.field ? `[${error.field}] ` : '';
152
+ console.log(` • ${location}${error.message}`);
153
+ }
154
+ }
155
+ // Warnings
156
+ if (warnings.length > 0) {
157
+ console.log(chalk.yellow.bold('\n⚠️ 警告'));
158
+ for (const warning of warnings) {
159
+ const location = warning.field ? `[${warning.field}] ` : '';
160
+ console.log(` • ${location}${warning.message}`);
161
+ }
162
+ }
163
+ // Final status
164
+ console.log();
165
+ if (result.success) {
166
+ if (dryRun) {
167
+ console.log(chalk.green('✓ 预览完成 - 移除 --dry-run 以执行实际导入'));
168
+ }
169
+ else {
170
+ console.log(chalk.green(`✓ 导入成功 - 已保存到 features.csv`));
171
+ }
172
+ }
173
+ else {
174
+ console.log(chalk.red('✗ 导入存在错误,请检查并修复'));
175
+ }
176
+ }
177
+ function getPriorityColor(priority) {
178
+ switch (priority) {
179
+ case 'critical': return (text) => chalk.red(text);
180
+ case 'high': return (text) => chalk.yellow(text);
181
+ case 'medium': return (text) => chalk.blue(text);
182
+ case 'low': return (text) => chalk.gray(text);
183
+ default: return (text) => text;
184
+ }
185
+ }
186
+ function getStatusColor(status) {
187
+ switch (status) {
188
+ case 'done': return (text) => chalk.green(text);
189
+ case 'in-progress': return (text) => chalk.blue(text);
190
+ case 'blocked': return (text) => chalk.red(text);
191
+ case 'todo': return (text) => chalk.gray(text);
192
+ default: return (text) => text;
193
+ }
194
+ }
195
+ export { importCommand };
196
+ //# sourceMappingURL=import.js.map
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ export declare const legacyCommands: Command;
4
+ //# sourceMappingURL=index-legacy.d.ts.map
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ // Import legacy commands for compatibility
4
+ import { initCommand } from '../commands/init.js';
5
+ import { listCommand } from '../commands/list.js';
6
+ import { showCommand } from '../commands/show.js';
7
+ import { validateCommand } from '../commands/validate.js';
8
+ import { createCommand } from '../commands/create.js';
9
+ import { updateCommand } from '../commands/update.js';
10
+ import { breakdownCommand } from '../commands/breakdown.js';
11
+ import { analyzeCommand } from '../commands/analyze.js';
12
+ export const legacyCommands = new Command('legacy')
13
+ .description('传统命令兼容模式 - 使用完整功能')
14
+ .description('所有原始命令的兼容版本,支持完整的项目管理功能')
15
+ .version('0.1.0');
16
+ // Add all legacy commands
17
+ legacyCommands.addCommand(initCommand);
18
+ legacyCommands.addCommand(listCommand);
19
+ legacyCommands.addCommand(showCommand);
20
+ legacyCommands.addCommand(validateCommand);
21
+ legacyCommands.addCommand(createCommand);
22
+ legacyCommands.addCommand(updateCommand);
23
+ legacyCommands.addCommand(breakdownCommand);
24
+ legacyCommands.addCommand(analyzeCommand);
25
+ // Error handling
26
+ legacyCommands.exitOverride();
27
+ //# sourceMappingURL=index-legacy.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const initCommand: Command;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1,60 @@
1
+ import { Command } from 'commander';
2
+ import { mkdir, access, constants } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { writeProjectFile, writeTeamFile } from '../utils/markdown.js';
7
+ export const initCommand = new Command('init')
8
+ .description('Initialize a new PMSpec project')
9
+ .option('-f, --force', 'Overwrite existing project')
10
+ .action(async (options) => {
11
+ const pmspaceDir = join(process.cwd(), 'pmspace');
12
+ const epicsDir = join(pmspaceDir, 'epics');
13
+ const featuresDir = join(pmspaceDir, 'features');
14
+ // Check if already exists
15
+ try {
16
+ await access(pmspaceDir, constants.F_OK);
17
+ if (!options.force) {
18
+ console.error(chalk.red('PMSpec project already exists. Use --force to reinitialize.'));
19
+ process.exit(1);
20
+ }
21
+ }
22
+ catch {
23
+ // Directory doesn't exist, proceed
24
+ }
25
+ const spinner = ora('Initializing PMSpec project...').start();
26
+ try {
27
+ // Create directories
28
+ await mkdir(epicsDir, { recursive: true });
29
+ await mkdir(featuresDir, { recursive: true });
30
+ // Create template project.md
31
+ const project = {
32
+ name: 'My Project',
33
+ overview: 'Project overview goes here.',
34
+ timeline: {
35
+ start: new Date().toISOString().split('T')[0],
36
+ end: '',
37
+ },
38
+ teamCapacity: {
39
+ total: 0,
40
+ available: 0,
41
+ },
42
+ };
43
+ await writeProjectFile(join(pmspaceDir, 'project.md'), project);
44
+ // Create template team.md
45
+ const team = {
46
+ members: [],
47
+ };
48
+ await writeTeamFile(join(pmspaceDir, 'team.md'), team);
49
+ spinner.succeed(chalk.green('PMSpec project initialized in pmspace/'));
50
+ console.log(chalk.gray('\nNext steps:'));
51
+ console.log(chalk.gray(' 1. Edit pmspace/project.md to set up your project'));
52
+ console.log(chalk.gray(' 2. Edit pmspace/team.md to add team members'));
53
+ console.log(chalk.gray(' 3. Run `pmspec create epic` to create your first Epic'));
54
+ }
55
+ catch (error) {
56
+ spinner.fail(chalk.red('Failed to initialize project'));
57
+ throw error;
58
+ }
59
+ });
60
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const listCommand: Command;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1,127 @@
1
+ import { Command } from 'commander';
2
+ import { readdir } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import chalk from 'chalk';
5
+ import { readEpicFile, readFeatureFile, readMilestoneFile } from '../core/parser.js';
6
+ export const listCommand = new Command('list')
7
+ .description('List Epics, Features, or Milestones')
8
+ .argument('[type]', 'Type to list: epics, features, or milestones', 'epics')
9
+ .option('-s, --status <status>', 'Filter by status')
10
+ .option('-a, --assignee <name>', 'Filter by assignee (features only)')
11
+ .action(async (type, options) => {
12
+ const pmspaceDir = join(process.cwd(), 'pmspace');
13
+ try {
14
+ if (type === 'epics') {
15
+ await listEpics(pmspaceDir, options);
16
+ }
17
+ else if (type === 'features') {
18
+ await listFeatures(pmspaceDir, options);
19
+ }
20
+ else if (type === 'milestones') {
21
+ await listMilestones(pmspaceDir, options);
22
+ }
23
+ else {
24
+ console.error(chalk.red(`Unknown type: ${type}. Use 'epics', 'features', or 'milestones'.`));
25
+ process.exit(1);
26
+ }
27
+ }
28
+ catch (error) {
29
+ console.error(chalk.red('Error:'), error.message);
30
+ process.exit(1);
31
+ }
32
+ });
33
+ async function listEpics(pmspaceDir, options) {
34
+ const epicsDir = join(pmspaceDir, 'epics');
35
+ const files = await readdir(epicsDir);
36
+ const epicFiles = files.filter(f => f.endsWith('.md'));
37
+ if (epicFiles.length === 0) {
38
+ console.log(chalk.yellow('No Epics found.'));
39
+ return;
40
+ }
41
+ const epics = [];
42
+ for (const file of epicFiles) {
43
+ const epic = await readEpicFile(join(epicsDir, file));
44
+ if (!options.status || epic.status === options.status) {
45
+ epics.push(epic);
46
+ }
47
+ }
48
+ // Sort by ID
49
+ epics.sort((a, b) => a.id.localeCompare(b.id));
50
+ console.log(chalk.bold('\nEpics:\n'));
51
+ console.log(chalk.gray(`${'ID'.padEnd(12)} ${'Title'.padEnd(30)} ${'Status'.padEnd(15)} ${'Owner'.padEnd(15)} ${'Est'.padEnd(8)} ${'Act'.padEnd(8)}`));
52
+ console.log(chalk.gray('-'.repeat(100)));
53
+ for (const epic of epics) {
54
+ const statusColor = epic.status === 'completed' ? chalk.green : epic.status === 'in-progress' ? chalk.yellow : chalk.white;
55
+ console.log(`${epic.id.padEnd(12)} ${epic.title.substring(0, 30).padEnd(30)} ${statusColor(epic.status.padEnd(15))} ${(epic.owner || '-').padEnd(15)} ${`${epic.estimate}h`.padEnd(8)} ${`${epic.actual}h`.padEnd(8)}`);
56
+ }
57
+ console.log(chalk.gray(`\nTotal: ${epics.length} Epic(s)`));
58
+ }
59
+ async function listFeatures(pmspaceDir, options) {
60
+ const featuresDir = join(pmspaceDir, 'features');
61
+ const files = await readdir(featuresDir);
62
+ const featureFiles = files.filter(f => f.endsWith('.md'));
63
+ if (featureFiles.length === 0) {
64
+ console.log(chalk.yellow('No Features found.'));
65
+ return;
66
+ }
67
+ const features = [];
68
+ for (const file of featureFiles) {
69
+ const feature = await readFeatureFile(join(featuresDir, file));
70
+ let include = true;
71
+ if (options.status && feature.status !== options.status)
72
+ include = false;
73
+ if (options.assignee && feature.assignee !== options.assignee)
74
+ include = false;
75
+ if (include) {
76
+ features.push(feature);
77
+ }
78
+ }
79
+ // Sort by ID
80
+ features.sort((a, b) => a.id.localeCompare(b.id));
81
+ console.log(chalk.bold('\nFeatures:\n'));
82
+ console.log(chalk.gray(`${'ID'.padEnd(12)} ${'Title'.padEnd(30)} ${'Status'.padEnd(15)} ${'Assignee'.padEnd(15)} ${'Est'.padEnd(8)}`));
83
+ console.log(chalk.gray('-'.repeat(100)));
84
+ for (const feature of features) {
85
+ const statusColor = feature.status === 'done' ? chalk.green : feature.status === 'in-progress' ? chalk.yellow : chalk.white;
86
+ console.log(`${feature.id.padEnd(12)} ${feature.title.substring(0, 30).padEnd(30)} ${statusColor(feature.status.padEnd(15))} ${(feature.assignee || '-').padEnd(15)} ${`${feature.estimate}h`.padEnd(8)}`);
87
+ }
88
+ const totalHours = features.reduce((sum, f) => sum + f.estimate, 0);
89
+ console.log(chalk.gray(`\nTotal: ${features.length} Feature(s), ${totalHours}h estimated`));
90
+ }
91
+ async function listMilestones(pmspaceDir, options) {
92
+ const milestonesDir = join(pmspaceDir, 'milestones');
93
+ let files = [];
94
+ try {
95
+ files = await readdir(milestonesDir);
96
+ }
97
+ catch {
98
+ console.log(chalk.yellow('No Milestones found. Directory does not exist.'));
99
+ return;
100
+ }
101
+ const milestoneFiles = files.filter(f => f.endsWith('.md'));
102
+ if (milestoneFiles.length === 0) {
103
+ console.log(chalk.yellow('No Milestones found.'));
104
+ return;
105
+ }
106
+ const milestones = [];
107
+ for (const file of milestoneFiles) {
108
+ const milestone = await readMilestoneFile(join(milestonesDir, file));
109
+ if (!options.status || milestone.status === options.status) {
110
+ milestones.push(milestone);
111
+ }
112
+ }
113
+ // Sort by target date
114
+ milestones.sort((a, b) => a.targetDate.localeCompare(b.targetDate));
115
+ console.log(chalk.bold('\nMilestones:\n'));
116
+ console.log(chalk.gray(`${'ID'.padEnd(12)} ${'Title'.padEnd(30)} ${'Target Date'.padEnd(15)} ${'Status'.padEnd(12)} ${'Features'.padEnd(10)}`));
117
+ console.log(chalk.gray('-'.repeat(90)));
118
+ for (const milestone of milestones) {
119
+ const statusColor = milestone.status === 'completed' ? chalk.green :
120
+ milestone.status === 'active' ? chalk.yellow :
121
+ milestone.status === 'missed' ? chalk.red :
122
+ chalk.white;
123
+ console.log(`${milestone.id.padEnd(12)} ${milestone.title.substring(0, 30).padEnd(30)} ${milestone.targetDate.padEnd(15)} ${statusColor(milestone.status.padEnd(12))} ${`${milestone.features.length}`.padEnd(10)}`);
124
+ }
125
+ console.log(chalk.gray(`\nTotal: ${milestones.length} Milestone(s)`));
126
+ }
127
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Search command for CLI
4
+ */
5
+ declare const searchCommand: Command;
6
+ export { searchCommand };
7
+ //# sourceMappingURL=search.d.ts.map