@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.
- package/README.md +306 -0
- package/README.zh.md +304 -0
- package/bin/pmspec.js +5 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +39 -0
- package/dist/commands/analyze.d.ts +4 -0
- package/dist/commands/analyze.js +240 -0
- package/dist/commands/breakdown.d.ts +4 -0
- package/dist/commands/breakdown.js +194 -0
- package/dist/commands/create.d.ts +4 -0
- package/dist/commands/create.js +529 -0
- package/dist/commands/history.d.ts +4 -0
- package/dist/commands/history.js +213 -0
- package/dist/commands/import.d.ts +4 -0
- package/dist/commands/import.js +196 -0
- package/dist/commands/index-legacy.d.ts +4 -0
- package/dist/commands/index-legacy.js +27 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +60 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.js +127 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +183 -0
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.js +68 -0
- package/dist/commands/show.d.ts +3 -0
- package/dist/commands/show.js +152 -0
- package/dist/commands/simple.d.ts +7 -0
- package/dist/commands/simple.js +360 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.js +247 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.js +74 -0
- package/dist/core/changelog-service.d.ts +88 -0
- package/dist/core/changelog-service.js +208 -0
- package/dist/core/changelog.d.ts +113 -0
- package/dist/core/changelog.js +147 -0
- package/dist/core/importers.d.ts +343 -0
- package/dist/core/importers.js +715 -0
- package/dist/core/parser.d.ts +50 -0
- package/dist/core/parser.js +246 -0
- package/dist/core/project.d.ts +155 -0
- package/dist/core/project.js +138 -0
- package/dist/core/search.d.ts +119 -0
- package/dist/core/search.js +299 -0
- package/dist/core/simple-model.d.ts +54 -0
- package/dist/core/simple-model.js +20 -0
- package/dist/core/team.d.ts +41 -0
- package/dist/core/team.js +57 -0
- package/dist/core/workload.d.ts +49 -0
- package/dist/core/workload.js +116 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/utils/csv-handler.d.ts +15 -0
- package/dist/utils/csv-handler.js +224 -0
- package/dist/utils/markdown.d.ts +43 -0
- package/dist/utils/markdown.js +202 -0
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.js +178 -0
- package/package.json +71 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Epic, Feature, Project, Milestone } from './project.js';
|
|
2
|
+
import type { Team } from './team.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse metadata from markdown frontmatter-style format
|
|
5
|
+
* Example:
|
|
6
|
+
* - **ID**: EPIC-001
|
|
7
|
+
* - **Status**: planning
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseMetadata(content: string): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Parse Epic from markdown file content
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseEpic(content: string): Epic;
|
|
14
|
+
/**
|
|
15
|
+
* Parse Feature from markdown file content
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseFeature(content: string): Feature;
|
|
18
|
+
/**
|
|
19
|
+
* Parse Project from project.md content
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseProject(content: string): Project;
|
|
22
|
+
/**
|
|
23
|
+
* Parse Team from team.md content
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseTeam(content: string): Team;
|
|
26
|
+
/**
|
|
27
|
+
* Parse Milestone from markdown file content
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseMilestone(content: string): Milestone;
|
|
30
|
+
/**
|
|
31
|
+
* Read and parse Epic file
|
|
32
|
+
*/
|
|
33
|
+
export declare function readEpicFile(filePath: string): Promise<Epic>;
|
|
34
|
+
/**
|
|
35
|
+
* Read and parse Feature file
|
|
36
|
+
*/
|
|
37
|
+
export declare function readFeatureFile(filePath: string): Promise<Feature>;
|
|
38
|
+
/**
|
|
39
|
+
* Read and parse Project file
|
|
40
|
+
*/
|
|
41
|
+
export declare function readProjectFile(filePath: string): Promise<Project>;
|
|
42
|
+
/**
|
|
43
|
+
* Read and parse Team file
|
|
44
|
+
*/
|
|
45
|
+
export declare function readTeamFile(filePath: string): Promise<Team>;
|
|
46
|
+
/**
|
|
47
|
+
* Read and parse Milestone file
|
|
48
|
+
*/
|
|
49
|
+
export declare function readMilestoneFile(filePath: string): Promise<Milestone>;
|
|
50
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
/**
|
|
3
|
+
* Parse metadata from markdown frontmatter-style format
|
|
4
|
+
* Example:
|
|
5
|
+
* - **ID**: EPIC-001
|
|
6
|
+
* - **Status**: planning
|
|
7
|
+
*/
|
|
8
|
+
export function parseMetadata(content) {
|
|
9
|
+
const metadata = {};
|
|
10
|
+
const metadataRegex = /^-\s+\*\*(.+?)\*\*:\s*(.+)$/gm;
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = metadataRegex.exec(content)) !== null) {
|
|
13
|
+
const key = match[1].trim();
|
|
14
|
+
const value = match[2].trim();
|
|
15
|
+
metadata[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return metadata;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse Epic from markdown file content
|
|
21
|
+
*/
|
|
22
|
+
export function parseEpic(content) {
|
|
23
|
+
const metadata = parseMetadata(content);
|
|
24
|
+
// Parse Features list
|
|
25
|
+
const featuresSection = content.match(/## Features\n([\s\S]*?)(?=\n##|$)/);
|
|
26
|
+
const features = [];
|
|
27
|
+
if (featuresSection) {
|
|
28
|
+
const featureRegex = /-\s+\[[x ]\]\s+(FEAT-\d+)/g;
|
|
29
|
+
let match;
|
|
30
|
+
while ((match = featureRegex.exec(featuresSection[1])) !== null) {
|
|
31
|
+
features.push(match[1]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Parse Description
|
|
35
|
+
const descSection = content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
|
|
36
|
+
const description = descSection ? descSection[1].trim() : undefined;
|
|
37
|
+
// Parse title from header
|
|
38
|
+
const titleMatch = content.match(/^#\s+Epic:\s+(.+)$/m);
|
|
39
|
+
const title = titleMatch ? titleMatch[1].trim() : metadata['Title'] || 'Untitled';
|
|
40
|
+
return {
|
|
41
|
+
id: metadata['ID'],
|
|
42
|
+
title,
|
|
43
|
+
status: metadata['Status'],
|
|
44
|
+
owner: metadata['Owner'],
|
|
45
|
+
estimate: parseFloat(metadata['Estimate']) || 0,
|
|
46
|
+
actual: parseFloat(metadata['Actual']) || 0,
|
|
47
|
+
description,
|
|
48
|
+
features,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse Feature from markdown file content
|
|
53
|
+
*/
|
|
54
|
+
export function parseFeature(content) {
|
|
55
|
+
const metadata = parseMetadata(content);
|
|
56
|
+
// Parse UserStories
|
|
57
|
+
const storiesSection = content.match(/## User Stories\n([\s\S]*?)(?=\n##|$)/);
|
|
58
|
+
const userStories = [];
|
|
59
|
+
if (storiesSection) {
|
|
60
|
+
const storyRegex = /-\s+\[[x ]\]\s+(STORY-\d+):\s+(.+?)\s+\((\d+)h\)/g;
|
|
61
|
+
let match;
|
|
62
|
+
while ((match = storyRegex.exec(storiesSection[1])) !== null) {
|
|
63
|
+
userStories.push({
|
|
64
|
+
id: match[1],
|
|
65
|
+
title: match[2].trim(),
|
|
66
|
+
estimate: parseInt(match[3], 10),
|
|
67
|
+
status: 'todo',
|
|
68
|
+
featureId: metadata['ID'],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Parse Acceptance Criteria
|
|
73
|
+
const criteriaSection = content.match(/## Acceptance Criteria\n([\s\S]*?)(?=\n##|$)/);
|
|
74
|
+
const acceptanceCriteria = [];
|
|
75
|
+
if (criteriaSection) {
|
|
76
|
+
const criteriaRegex = /-\s+\[[x ]\]\s+(.+)$/gm;
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = criteriaRegex.exec(criteriaSection[1])) !== null) {
|
|
79
|
+
acceptanceCriteria.push(match[1].trim());
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Parse Description
|
|
83
|
+
const descSection = content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
|
|
84
|
+
const description = descSection ? descSection[1].trim() : undefined;
|
|
85
|
+
// Parse title from header
|
|
86
|
+
const titleMatch = content.match(/^#\s+Feature:\s+(.+)$/m);
|
|
87
|
+
const title = titleMatch ? titleMatch[1].trim() : metadata['Title'] || 'Untitled';
|
|
88
|
+
// Parse skills required
|
|
89
|
+
const skillsStr = metadata['Skills Required'] || '';
|
|
90
|
+
const skillsRequired = skillsStr ? skillsStr.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
91
|
+
// Parse Dependencies
|
|
92
|
+
const depsSection = content.match(/## Dependencies\n([\s\S]*?)(?=\n##|$)/);
|
|
93
|
+
const dependencies = [];
|
|
94
|
+
if (depsSection) {
|
|
95
|
+
// Parse blocks: FEAT-002, FEAT-003
|
|
96
|
+
const blocksMatch = depsSection[1].match(/-\s+blocks:\s+(.+)/i);
|
|
97
|
+
if (blocksMatch) {
|
|
98
|
+
const blockIds = blocksMatch[1].split(',').map(s => s.trim()).filter(s => /^FEAT-\d+$/.test(s));
|
|
99
|
+
blockIds.forEach(id => dependencies.push({ featureId: id, type: 'blocks' }));
|
|
100
|
+
}
|
|
101
|
+
// Parse relates-to: FEAT-005
|
|
102
|
+
const relatesToMatch = depsSection[1].match(/-\s+relates-to:\s+(.+)/i);
|
|
103
|
+
if (relatesToMatch) {
|
|
104
|
+
const relatedIds = relatesToMatch[1].split(',').map(s => s.trim()).filter(s => /^FEAT-\d+$/.test(s));
|
|
105
|
+
relatedIds.forEach(id => dependencies.push({ featureId: id, type: 'relates-to' }));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
id: metadata['ID'],
|
|
110
|
+
title,
|
|
111
|
+
epicId: metadata['Epic'],
|
|
112
|
+
status: metadata['Status'],
|
|
113
|
+
assignee: metadata['Assignee'],
|
|
114
|
+
estimate: parseFloat(metadata['Estimate']) || 0,
|
|
115
|
+
actual: parseFloat(metadata['Actual']) || 0,
|
|
116
|
+
skillsRequired,
|
|
117
|
+
description,
|
|
118
|
+
userStories,
|
|
119
|
+
acceptanceCriteria,
|
|
120
|
+
dependencies,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse Project from project.md content
|
|
125
|
+
*/
|
|
126
|
+
export function parseProject(content) {
|
|
127
|
+
// Parse title from header
|
|
128
|
+
const titleMatch = content.match(/^#\s+Project:\s+(.+)$/m);
|
|
129
|
+
const name = titleMatch ? titleMatch[1].trim() : 'Untitled Project';
|
|
130
|
+
// Parse Overview
|
|
131
|
+
const overviewSection = content.match(/## Overview\n([\s\S]*?)(?=\n##|$)/);
|
|
132
|
+
const overview = overviewSection ? overviewSection[1].trim() : undefined;
|
|
133
|
+
// Parse Timeline
|
|
134
|
+
const timelineMatch = content.match(/## Timeline\n-\s+Start:\s+(.+)\n-\s+End:\s+(.+)/);
|
|
135
|
+
const timeline = timelineMatch ? {
|
|
136
|
+
start: timelineMatch[1].trim(),
|
|
137
|
+
end: timelineMatch[2].trim(),
|
|
138
|
+
} : undefined;
|
|
139
|
+
// Parse Team Capacity
|
|
140
|
+
const capacityMatch = content.match(/## Team Capacity\n-\s+Total:\s+(\d+)[^\n]*\n-\s+Available:\s+(\d+)/);
|
|
141
|
+
const teamCapacity = capacityMatch ? {
|
|
142
|
+
total: parseInt(capacityMatch[1], 10),
|
|
143
|
+
available: parseInt(capacityMatch[2], 10),
|
|
144
|
+
} : undefined;
|
|
145
|
+
return {
|
|
146
|
+
name,
|
|
147
|
+
overview,
|
|
148
|
+
timeline,
|
|
149
|
+
teamCapacity,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Parse Team from team.md content
|
|
154
|
+
*/
|
|
155
|
+
export function parseTeam(content) {
|
|
156
|
+
const members = [];
|
|
157
|
+
// Match each member section (### [Name])
|
|
158
|
+
const memberRegex = /###\s+(.+?)\n([\s\S]*?)(?=\n###|$)/g;
|
|
159
|
+
let match;
|
|
160
|
+
while ((match = memberRegex.exec(content)) !== null) {
|
|
161
|
+
const name = match[1].trim();
|
|
162
|
+
const memberContent = match[2];
|
|
163
|
+
// Parse skills
|
|
164
|
+
const skillsMatch = memberContent.match(/-\s+\*\*Skills\*\*:\s+(.+)/);
|
|
165
|
+
const skills = skillsMatch ? skillsMatch[1].split(',').map(s => s.trim()) : [];
|
|
166
|
+
// Parse capacity
|
|
167
|
+
const capacityMatch = memberContent.match(/-\s+\*\*Capacity\*\*:\s+(\d+)\s+hours?\/week/);
|
|
168
|
+
const capacity = capacityMatch ? parseInt(capacityMatch[1], 10) : 40;
|
|
169
|
+
// Parse current load
|
|
170
|
+
const loadMatch = memberContent.match(/-\s+\*\*Current Load\*\*:\s+(\d+)\s+hours?\/week/);
|
|
171
|
+
const currentLoad = loadMatch ? parseInt(loadMatch[1], 10) : 0;
|
|
172
|
+
members.push({
|
|
173
|
+
name,
|
|
174
|
+
skills,
|
|
175
|
+
capacity,
|
|
176
|
+
currentLoad,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return { members };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Parse Milestone from markdown file content
|
|
183
|
+
*/
|
|
184
|
+
export function parseMilestone(content) {
|
|
185
|
+
const metadata = parseMetadata(content);
|
|
186
|
+
// Parse title from header
|
|
187
|
+
const titleMatch = content.match(/^#\s+Milestone:\s+(.+)$/m);
|
|
188
|
+
const title = titleMatch ? titleMatch[1].trim() : metadata['Title'] || 'Untitled';
|
|
189
|
+
// Parse Description
|
|
190
|
+
const descSection = content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
|
|
191
|
+
const description = descSection ? descSection[1].trim() : undefined;
|
|
192
|
+
// Parse Features list (with checkboxes)
|
|
193
|
+
const featuresSection = content.match(/## Features\n([\s\S]*?)(?=\n##|$)/);
|
|
194
|
+
const features = [];
|
|
195
|
+
if (featuresSection) {
|
|
196
|
+
const featureRegex = /-\s+\[[x ]\]\s+(FEAT-\d+)/g;
|
|
197
|
+
let match;
|
|
198
|
+
while ((match = featureRegex.exec(featuresSection[1])) !== null) {
|
|
199
|
+
features.push(match[1]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
id: metadata['ID'],
|
|
204
|
+
title,
|
|
205
|
+
description,
|
|
206
|
+
targetDate: metadata['Target Date'],
|
|
207
|
+
status: metadata['Status'],
|
|
208
|
+
features,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Read and parse Epic file
|
|
213
|
+
*/
|
|
214
|
+
export async function readEpicFile(filePath) {
|
|
215
|
+
const content = await readFile(filePath, 'utf-8');
|
|
216
|
+
return parseEpic(content);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Read and parse Feature file
|
|
220
|
+
*/
|
|
221
|
+
export async function readFeatureFile(filePath) {
|
|
222
|
+
const content = await readFile(filePath, 'utf-8');
|
|
223
|
+
return parseFeature(content);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Read and parse Project file
|
|
227
|
+
*/
|
|
228
|
+
export async function readProjectFile(filePath) {
|
|
229
|
+
const content = await readFile(filePath, 'utf-8');
|
|
230
|
+
return parseProject(content);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Read and parse Team file
|
|
234
|
+
*/
|
|
235
|
+
export async function readTeamFile(filePath) {
|
|
236
|
+
const content = await readFile(filePath, 'utf-8');
|
|
237
|
+
return parseTeam(content);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Read and parse Milestone file
|
|
241
|
+
*/
|
|
242
|
+
export async function readMilestoneFile(filePath) {
|
|
243
|
+
const content = await readFile(filePath, 'utf-8');
|
|
244
|
+
return parseMilestone(content);
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const EpicStatus: z.ZodEnum<{
|
|
3
|
+
planning: "planning";
|
|
4
|
+
"in-progress": "in-progress";
|
|
5
|
+
completed: "completed";
|
|
6
|
+
}>;
|
|
7
|
+
export type EpicStatus = z.infer<typeof EpicStatus>;
|
|
8
|
+
export declare const MilestoneStatus: z.ZodEnum<{
|
|
9
|
+
completed: "completed";
|
|
10
|
+
upcoming: "upcoming";
|
|
11
|
+
active: "active";
|
|
12
|
+
missed: "missed";
|
|
13
|
+
}>;
|
|
14
|
+
export type MilestoneStatus = z.infer<typeof MilestoneStatus>;
|
|
15
|
+
export declare const FeatureStatus: z.ZodEnum<{
|
|
16
|
+
"in-progress": "in-progress";
|
|
17
|
+
todo: "todo";
|
|
18
|
+
done: "done";
|
|
19
|
+
}>;
|
|
20
|
+
export type FeatureStatus = z.infer<typeof FeatureStatus>;
|
|
21
|
+
export declare const StoryStatus: z.ZodEnum<{
|
|
22
|
+
"in-progress": "in-progress";
|
|
23
|
+
todo: "todo";
|
|
24
|
+
done: "done";
|
|
25
|
+
}>;
|
|
26
|
+
export type StoryStatus = z.infer<typeof StoryStatus>;
|
|
27
|
+
export declare const UserStorySchema: z.ZodObject<{
|
|
28
|
+
id: z.ZodString;
|
|
29
|
+
title: z.ZodString;
|
|
30
|
+
description: z.ZodOptional<z.ZodString>;
|
|
31
|
+
estimate: z.ZodNumber;
|
|
32
|
+
status: z.ZodEnum<{
|
|
33
|
+
"in-progress": "in-progress";
|
|
34
|
+
todo: "todo";
|
|
35
|
+
done: "done";
|
|
36
|
+
}>;
|
|
37
|
+
featureId: z.ZodString;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
export type UserStory = z.infer<typeof UserStorySchema>;
|
|
40
|
+
export declare const DependencyType: z.ZodEnum<{
|
|
41
|
+
blocks: "blocks";
|
|
42
|
+
"relates-to": "relates-to";
|
|
43
|
+
}>;
|
|
44
|
+
export type DependencyType = z.infer<typeof DependencyType>;
|
|
45
|
+
export declare const DependencySchema: z.ZodObject<{
|
|
46
|
+
featureId: z.ZodString;
|
|
47
|
+
type: z.ZodEnum<{
|
|
48
|
+
blocks: "blocks";
|
|
49
|
+
"relates-to": "relates-to";
|
|
50
|
+
}>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export type Dependency = z.infer<typeof DependencySchema>;
|
|
53
|
+
export declare const FeatureSchema: z.ZodObject<{
|
|
54
|
+
id: z.ZodString;
|
|
55
|
+
title: z.ZodString;
|
|
56
|
+
epicId: z.ZodString;
|
|
57
|
+
status: z.ZodEnum<{
|
|
58
|
+
"in-progress": "in-progress";
|
|
59
|
+
todo: "todo";
|
|
60
|
+
done: "done";
|
|
61
|
+
}>;
|
|
62
|
+
assignee: z.ZodOptional<z.ZodString>;
|
|
63
|
+
estimate: z.ZodNumber;
|
|
64
|
+
actual: z.ZodDefault<z.ZodNumber>;
|
|
65
|
+
skillsRequired: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
66
|
+
description: z.ZodOptional<z.ZodString>;
|
|
67
|
+
userStories: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
68
|
+
id: z.ZodString;
|
|
69
|
+
title: z.ZodString;
|
|
70
|
+
description: z.ZodOptional<z.ZodString>;
|
|
71
|
+
estimate: z.ZodNumber;
|
|
72
|
+
status: z.ZodEnum<{
|
|
73
|
+
"in-progress": "in-progress";
|
|
74
|
+
todo: "todo";
|
|
75
|
+
done: "done";
|
|
76
|
+
}>;
|
|
77
|
+
featureId: z.ZodString;
|
|
78
|
+
}, z.core.$strip>>>;
|
|
79
|
+
acceptanceCriteria: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
80
|
+
dependencies: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
81
|
+
featureId: z.ZodString;
|
|
82
|
+
type: z.ZodEnum<{
|
|
83
|
+
blocks: "blocks";
|
|
84
|
+
"relates-to": "relates-to";
|
|
85
|
+
}>;
|
|
86
|
+
}, z.core.$strip>>>;
|
|
87
|
+
}, z.core.$strip>;
|
|
88
|
+
export type Feature = z.infer<typeof FeatureSchema>;
|
|
89
|
+
export declare const EpicSchema: z.ZodObject<{
|
|
90
|
+
id: z.ZodString;
|
|
91
|
+
title: z.ZodString;
|
|
92
|
+
status: z.ZodEnum<{
|
|
93
|
+
planning: "planning";
|
|
94
|
+
"in-progress": "in-progress";
|
|
95
|
+
completed: "completed";
|
|
96
|
+
}>;
|
|
97
|
+
owner: z.ZodOptional<z.ZodString>;
|
|
98
|
+
estimate: z.ZodNumber;
|
|
99
|
+
actual: z.ZodDefault<z.ZodNumber>;
|
|
100
|
+
description: z.ZodOptional<z.ZodString>;
|
|
101
|
+
features: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
102
|
+
}, z.core.$strip>;
|
|
103
|
+
export type Epic = z.infer<typeof EpicSchema>;
|
|
104
|
+
export declare const MilestoneSchema: z.ZodObject<{
|
|
105
|
+
id: z.ZodString;
|
|
106
|
+
title: z.ZodString;
|
|
107
|
+
description: z.ZodOptional<z.ZodString>;
|
|
108
|
+
targetDate: z.ZodString;
|
|
109
|
+
status: z.ZodEnum<{
|
|
110
|
+
completed: "completed";
|
|
111
|
+
upcoming: "upcoming";
|
|
112
|
+
active: "active";
|
|
113
|
+
missed: "missed";
|
|
114
|
+
}>;
|
|
115
|
+
features: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
116
|
+
}, z.core.$strip>;
|
|
117
|
+
export type Milestone = z.infer<typeof MilestoneSchema>;
|
|
118
|
+
export declare const ProjectSchema: z.ZodObject<{
|
|
119
|
+
name: z.ZodString;
|
|
120
|
+
overview: z.ZodOptional<z.ZodString>;
|
|
121
|
+
timeline: z.ZodOptional<z.ZodObject<{
|
|
122
|
+
start: z.ZodOptional<z.ZodString>;
|
|
123
|
+
end: z.ZodOptional<z.ZodString>;
|
|
124
|
+
}, z.core.$strip>>;
|
|
125
|
+
teamCapacity: z.ZodOptional<z.ZodObject<{
|
|
126
|
+
total: z.ZodOptional<z.ZodNumber>;
|
|
127
|
+
available: z.ZodOptional<z.ZodNumber>;
|
|
128
|
+
}, z.core.$strip>>;
|
|
129
|
+
}, z.core.$strip>;
|
|
130
|
+
export type Project = z.infer<typeof ProjectSchema>;
|
|
131
|
+
/**
|
|
132
|
+
* Parse ID number from Epic/Feature/Story ID string
|
|
133
|
+
* @example parseIdNumber('EPIC-001') => 1
|
|
134
|
+
*/
|
|
135
|
+
export declare function parseIdNumber(id: string): number;
|
|
136
|
+
/**
|
|
137
|
+
* Generate next available ID based on existing IDs
|
|
138
|
+
* @example generateNextId('EPIC', ['EPIC-001', 'EPIC-003']) => 'EPIC-004'
|
|
139
|
+
*/
|
|
140
|
+
export declare function generateNextId(prefix: 'EPIC' | 'FEAT' | 'STORY' | 'MILE', existingIds: string[]): string;
|
|
141
|
+
/**
|
|
142
|
+
* Validate that all referenced IDs exist
|
|
143
|
+
*/
|
|
144
|
+
export declare function validateReferences(epics: Epic[], features: Feature[]): {
|
|
145
|
+
valid: boolean;
|
|
146
|
+
errors: string[];
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Check for duplicate IDs
|
|
150
|
+
*/
|
|
151
|
+
export declare function checkDuplicateIds(epics: Epic[], features: Feature[]): {
|
|
152
|
+
valid: boolean;
|
|
153
|
+
errors: string[];
|
|
154
|
+
};
|
|
155
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// Status enums
|
|
3
|
+
export const EpicStatus = z.enum(['planning', 'in-progress', 'completed']);
|
|
4
|
+
export const MilestoneStatus = z.enum(['upcoming', 'active', 'completed', 'missed']);
|
|
5
|
+
export const FeatureStatus = z.enum(['todo', 'in-progress', 'done']);
|
|
6
|
+
export const StoryStatus = z.enum(['todo', 'in-progress', 'done']);
|
|
7
|
+
// UserStory schema
|
|
8
|
+
export const UserStorySchema = z.object({
|
|
9
|
+
id: z.string().regex(/^STORY-\d+$/),
|
|
10
|
+
title: z.string().min(1),
|
|
11
|
+
description: z.string().optional(),
|
|
12
|
+
estimate: z.number().positive(),
|
|
13
|
+
status: StoryStatus,
|
|
14
|
+
featureId: z.string().regex(/^FEAT-\d+$/),
|
|
15
|
+
});
|
|
16
|
+
// Dependency type enum
|
|
17
|
+
export const DependencyType = z.enum(['blocks', 'relates-to']);
|
|
18
|
+
// Dependency schema
|
|
19
|
+
export const DependencySchema = z.object({
|
|
20
|
+
featureId: z.string().regex(/^FEAT-\d+$/),
|
|
21
|
+
type: DependencyType,
|
|
22
|
+
});
|
|
23
|
+
// Feature schema
|
|
24
|
+
export const FeatureSchema = z.object({
|
|
25
|
+
id: z.string().regex(/^FEAT-\d+$/),
|
|
26
|
+
title: z.string().min(1),
|
|
27
|
+
epicId: z.string().regex(/^EPIC-\d+$/),
|
|
28
|
+
status: FeatureStatus,
|
|
29
|
+
assignee: z.string().optional(),
|
|
30
|
+
estimate: z.number().positive(),
|
|
31
|
+
actual: z.number().nonnegative().default(0),
|
|
32
|
+
skillsRequired: z.array(z.string()).default([]),
|
|
33
|
+
description: z.string().optional(),
|
|
34
|
+
userStories: z.array(UserStorySchema).default([]),
|
|
35
|
+
acceptanceCriteria: z.array(z.string()).default([]),
|
|
36
|
+
dependencies: z.array(DependencySchema).default([]),
|
|
37
|
+
});
|
|
38
|
+
// Epic schema
|
|
39
|
+
export const EpicSchema = z.object({
|
|
40
|
+
id: z.string().regex(/^EPIC-\d+$/),
|
|
41
|
+
title: z.string().min(1),
|
|
42
|
+
status: EpicStatus,
|
|
43
|
+
owner: z.string().optional(),
|
|
44
|
+
estimate: z.number().positive(),
|
|
45
|
+
actual: z.number().nonnegative().default(0),
|
|
46
|
+
description: z.string().optional(),
|
|
47
|
+
features: z.array(z.string().regex(/^FEAT-\d+$/)).default([]), // Feature IDs
|
|
48
|
+
});
|
|
49
|
+
// Milestone schema
|
|
50
|
+
export const MilestoneSchema = z.object({
|
|
51
|
+
id: z.string().regex(/^MILE-\d{3}$/),
|
|
52
|
+
title: z.string().min(1),
|
|
53
|
+
description: z.string().optional(),
|
|
54
|
+
targetDate: z.string(), // ISO date YYYY-MM-DD
|
|
55
|
+
status: MilestoneStatus,
|
|
56
|
+
features: z.array(z.string().regex(/^FEAT-\d+$/)).default([]), // Feature IDs
|
|
57
|
+
});
|
|
58
|
+
// Project schema
|
|
59
|
+
export const ProjectSchema = z.object({
|
|
60
|
+
name: z.string().min(1),
|
|
61
|
+
overview: z.string().optional(),
|
|
62
|
+
timeline: z.object({
|
|
63
|
+
start: z.string().optional(), // YYYY-MM-DD
|
|
64
|
+
end: z.string().optional(),
|
|
65
|
+
}).optional(),
|
|
66
|
+
teamCapacity: z.object({
|
|
67
|
+
total: z.number().optional(),
|
|
68
|
+
available: z.number().optional(),
|
|
69
|
+
}).optional(),
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* Parse ID number from Epic/Feature/Story ID string
|
|
73
|
+
* @example parseIdNumber('EPIC-001') => 1
|
|
74
|
+
*/
|
|
75
|
+
export function parseIdNumber(id) {
|
|
76
|
+
const match = id.match(/-(\d+)$/);
|
|
77
|
+
if (!match) {
|
|
78
|
+
throw new Error(`Invalid ID format: ${id}`);
|
|
79
|
+
}
|
|
80
|
+
return parseInt(match[1], 10);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate next available ID based on existing IDs
|
|
84
|
+
* @example generateNextId('EPIC', ['EPIC-001', 'EPIC-003']) => 'EPIC-004'
|
|
85
|
+
*/
|
|
86
|
+
export function generateNextId(prefix, existingIds) {
|
|
87
|
+
if (existingIds.length === 0) {
|
|
88
|
+
return `${prefix}-001`;
|
|
89
|
+
}
|
|
90
|
+
const numbers = existingIds.map(id => parseIdNumber(id));
|
|
91
|
+
const maxNumber = Math.max(...numbers);
|
|
92
|
+
const nextNumber = maxNumber + 1;
|
|
93
|
+
return `${prefix}-${nextNumber.toString().padStart(3, '0')}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Validate that all referenced IDs exist
|
|
97
|
+
*/
|
|
98
|
+
export function validateReferences(epics, features) {
|
|
99
|
+
const errors = [];
|
|
100
|
+
const epicIds = new Set(epics.map(e => e.id));
|
|
101
|
+
const featureIds = new Set(features.map(f => f.id));
|
|
102
|
+
// Check that Features reference existing Epics
|
|
103
|
+
for (const feature of features) {
|
|
104
|
+
if (!epicIds.has(feature.epicId)) {
|
|
105
|
+
errors.push(`Feature ${feature.id} references non-existent Epic ${feature.epicId}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Check that Epics reference existing Features
|
|
109
|
+
for (const epic of epics) {
|
|
110
|
+
for (const featureId of epic.features) {
|
|
111
|
+
if (!featureIds.has(featureId)) {
|
|
112
|
+
errors.push(`Epic ${epic.id} references non-existent Feature ${featureId}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { valid: errors.length === 0, errors };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check for duplicate IDs
|
|
120
|
+
*/
|
|
121
|
+
export function checkDuplicateIds(epics, features) {
|
|
122
|
+
const errors = [];
|
|
123
|
+
const allIds = [];
|
|
124
|
+
epics.forEach(e => allIds.push(e.id));
|
|
125
|
+
features.forEach(f => {
|
|
126
|
+
allIds.push(f.id);
|
|
127
|
+
f.userStories.forEach(s => allIds.push(s.id));
|
|
128
|
+
});
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
for (const id of allIds) {
|
|
131
|
+
if (seen.has(id)) {
|
|
132
|
+
errors.push(`Duplicate ID found: ${id}`);
|
|
133
|
+
}
|
|
134
|
+
seen.add(id);
|
|
135
|
+
}
|
|
136
|
+
return { valid: errors.length === 0, errors };
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=project.js.map
|