@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,119 @@
1
+ /**
2
+ * Searchable document type
3
+ */
4
+ export type SearchableType = 'epic' | 'feature' | 'story' | 'milestone';
5
+ /**
6
+ * Searchable document interface
7
+ */
8
+ export interface Searchable {
9
+ id: string;
10
+ type: SearchableType;
11
+ title: string;
12
+ description?: string;
13
+ content?: string;
14
+ parentId?: string;
15
+ }
16
+ /**
17
+ * Search options
18
+ */
19
+ export interface SearchOptions {
20
+ type?: SearchableType | SearchableType[];
21
+ fuzzy?: number | boolean;
22
+ prefix?: boolean;
23
+ limit?: number;
24
+ boost?: Record<string, number>;
25
+ }
26
+ /**
27
+ * Search result with match information
28
+ */
29
+ export interface SearchResult {
30
+ id: string;
31
+ type: SearchableType;
32
+ title: string;
33
+ description?: string;
34
+ score: number;
35
+ matches: SearchMatch[];
36
+ parentId?: string;
37
+ }
38
+ /**
39
+ * Match information for highlighting
40
+ */
41
+ export interface SearchMatch {
42
+ field: string;
43
+ term: string;
44
+ positions?: number[];
45
+ }
46
+ /**
47
+ * Search Service using MiniSearch
48
+ */
49
+ export declare class SearchService {
50
+ private miniSearch;
51
+ private documents;
52
+ private projectRoot;
53
+ private initialized;
54
+ constructor(projectRoot?: string);
55
+ /**
56
+ * Initialize and index all documents from the project
57
+ */
58
+ index(): Promise<void>;
59
+ /**
60
+ * Index all epic files
61
+ */
62
+ private indexEpics;
63
+ /**
64
+ * Index all feature files and their user stories
65
+ */
66
+ private indexFeatures;
67
+ /**
68
+ * Index all milestone files
69
+ */
70
+ private indexMilestones;
71
+ /**
72
+ * Add a document to the search index
73
+ */
74
+ addDocument(doc: Searchable): void;
75
+ /**
76
+ * Remove a document from the search index
77
+ */
78
+ removeDocument(id: string): void;
79
+ /**
80
+ * Update a document in the search index
81
+ */
82
+ updateDocument(doc: Searchable): void;
83
+ /**
84
+ * Search for documents matching the query
85
+ */
86
+ search(query: string, options?: SearchOptions): SearchResult[];
87
+ /**
88
+ * Get document by ID
89
+ */
90
+ getDocument(id: string): Searchable | undefined;
91
+ /**
92
+ * Get all documents
93
+ */
94
+ getAllDocuments(): Searchable[];
95
+ /**
96
+ * Get document count
97
+ */
98
+ getDocumentCount(): number;
99
+ /**
100
+ * Check if the service is initialized
101
+ */
102
+ isInitialized(): boolean;
103
+ /**
104
+ * Clear all documents from the index
105
+ */
106
+ clear(): void;
107
+ }
108
+ /**
109
+ * Highlight matching terms in text
110
+ */
111
+ export declare function highlightMatches(text: string, matches: SearchMatch[], highlightStart?: string, // Yellow ANSI
112
+ highlightEnd?: string): string;
113
+ /**
114
+ * Highlight matches with HTML tags (for web)
115
+ */
116
+ export declare function highlightMatchesHtml(text: string, matches: SearchMatch[], className?: string): string;
117
+ export declare function getSearchService(projectRoot?: string): SearchService;
118
+ export default SearchService;
119
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1,299 @@
1
+ import MiniSearch from 'minisearch';
2
+ import { readdir, readFile } from 'fs/promises';
3
+ import path from 'path';
4
+ import { parseEpic, parseFeature, parseMilestone } from './parser.js';
5
+ /**
6
+ * Default search options
7
+ */
8
+ const DEFAULT_OPTIONS = {
9
+ fuzzy: 0.2,
10
+ prefix: true,
11
+ limit: 20,
12
+ boost: {
13
+ title: 2,
14
+ description: 1,
15
+ content: 0.5,
16
+ },
17
+ };
18
+ /**
19
+ * Search Service using MiniSearch
20
+ */
21
+ export class SearchService {
22
+ miniSearch;
23
+ documents = new Map();
24
+ projectRoot;
25
+ initialized = false;
26
+ constructor(projectRoot = process.cwd()) {
27
+ this.projectRoot = projectRoot;
28
+ this.miniSearch = new MiniSearch({
29
+ fields: ['title', 'description', 'content'],
30
+ storeFields: ['id', 'type', 'title', 'description', 'parentId'],
31
+ idField: 'id',
32
+ // Custom tokenizer for Chinese text support
33
+ tokenize: (text) => {
34
+ // Split on spaces, punctuation, and support Chinese character segmentation
35
+ return text.toLowerCase().split(/[\s\p{P}]+/u).filter(Boolean);
36
+ },
37
+ // Process search terms similarly
38
+ processTerm: (term) => term.toLowerCase(),
39
+ });
40
+ }
41
+ /**
42
+ * Initialize and index all documents from the project
43
+ */
44
+ async index() {
45
+ const pmspaceDir = path.join(this.projectRoot, 'pmspace');
46
+ // Clear existing index
47
+ this.miniSearch.removeAll();
48
+ this.documents.clear();
49
+ // Index epics
50
+ await this.indexEpics(pmspaceDir);
51
+ // Index features (and their user stories)
52
+ await this.indexFeatures(pmspaceDir);
53
+ // Index milestones
54
+ await this.indexMilestones(pmspaceDir);
55
+ this.initialized = true;
56
+ }
57
+ /**
58
+ * Index all epic files
59
+ */
60
+ async indexEpics(pmspaceDir) {
61
+ try {
62
+ const epicsDir = path.join(pmspaceDir, 'epics');
63
+ const files = await readdir(epicsDir);
64
+ for (const file of files) {
65
+ if (!file.endsWith('.md'))
66
+ continue;
67
+ try {
68
+ const content = await readFile(path.join(epicsDir, file), 'utf-8');
69
+ const epic = parseEpic(content);
70
+ if (epic.id) {
71
+ const doc = {
72
+ id: epic.id,
73
+ type: 'epic',
74
+ title: epic.title,
75
+ description: epic.description,
76
+ };
77
+ this.addDocument(doc);
78
+ }
79
+ }
80
+ catch (err) {
81
+ // Skip invalid files
82
+ }
83
+ }
84
+ }
85
+ catch (err) {
86
+ // Epics directory doesn't exist
87
+ }
88
+ }
89
+ /**
90
+ * Index all feature files and their user stories
91
+ */
92
+ async indexFeatures(pmspaceDir) {
93
+ try {
94
+ const featuresDir = path.join(pmspaceDir, 'features');
95
+ const files = await readdir(featuresDir);
96
+ for (const file of files) {
97
+ if (!file.endsWith('.md'))
98
+ continue;
99
+ try {
100
+ const content = await readFile(path.join(featuresDir, file), 'utf-8');
101
+ const feature = parseFeature(content);
102
+ if (feature.id) {
103
+ // Build content from acceptance criteria
104
+ const contentParts = [];
105
+ if (feature.acceptanceCriteria) {
106
+ contentParts.push(...feature.acceptanceCriteria);
107
+ }
108
+ const doc = {
109
+ id: feature.id,
110
+ type: 'feature',
111
+ title: feature.title,
112
+ description: feature.description,
113
+ content: contentParts.join(' '),
114
+ parentId: feature.epicId,
115
+ };
116
+ this.addDocument(doc);
117
+ // Index user stories
118
+ if (feature.userStories) {
119
+ for (const story of feature.userStories) {
120
+ const storyDoc = {
121
+ id: story.id,
122
+ type: 'story',
123
+ title: story.title,
124
+ description: story.description,
125
+ parentId: feature.id,
126
+ };
127
+ this.addDocument(storyDoc);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ catch (err) {
133
+ // Skip invalid files
134
+ }
135
+ }
136
+ }
137
+ catch (err) {
138
+ // Features directory doesn't exist
139
+ }
140
+ }
141
+ /**
142
+ * Index all milestone files
143
+ */
144
+ async indexMilestones(pmspaceDir) {
145
+ try {
146
+ const milestonesDir = path.join(pmspaceDir, 'milestones');
147
+ const files = await readdir(milestonesDir);
148
+ for (const file of files) {
149
+ if (!file.endsWith('.md'))
150
+ continue;
151
+ try {
152
+ const content = await readFile(path.join(milestonesDir, file), 'utf-8');
153
+ const milestone = parseMilestone(content);
154
+ if (milestone.id) {
155
+ const doc = {
156
+ id: milestone.id,
157
+ type: 'milestone',
158
+ title: milestone.title,
159
+ description: milestone.description,
160
+ };
161
+ this.addDocument(doc);
162
+ }
163
+ }
164
+ catch (err) {
165
+ // Skip invalid files
166
+ }
167
+ }
168
+ }
169
+ catch (err) {
170
+ // Milestones directory doesn't exist
171
+ }
172
+ }
173
+ /**
174
+ * Add a document to the search index
175
+ */
176
+ addDocument(doc) {
177
+ // Remove existing document if present
178
+ if (this.documents.has(doc.id)) {
179
+ this.removeDocument(doc.id);
180
+ }
181
+ this.documents.set(doc.id, doc);
182
+ this.miniSearch.add(doc);
183
+ }
184
+ /**
185
+ * Remove a document from the search index
186
+ */
187
+ removeDocument(id) {
188
+ const doc = this.documents.get(id);
189
+ if (doc) {
190
+ this.miniSearch.remove(doc);
191
+ this.documents.delete(id);
192
+ }
193
+ }
194
+ /**
195
+ * Update a document in the search index
196
+ */
197
+ updateDocument(doc) {
198
+ this.addDocument(doc); // addDocument handles removal if exists
199
+ }
200
+ /**
201
+ * Search for documents matching the query
202
+ */
203
+ search(query, options = {}) {
204
+ const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
205
+ // Build filter for type restriction
206
+ const typeFilter = mergedOptions.type
207
+ ? Array.isArray(mergedOptions.type)
208
+ ? mergedOptions.type
209
+ : [mergedOptions.type]
210
+ : null;
211
+ const searchOptions = {
212
+ fuzzy: mergedOptions.fuzzy,
213
+ prefix: mergedOptions.prefix,
214
+ boost: mergedOptions.boost,
215
+ };
216
+ // Add filter if type is specified
217
+ if (typeFilter) {
218
+ searchOptions.filter = (result) => typeFilter.includes(result.type);
219
+ }
220
+ const results = this.miniSearch.search(query, searchOptions);
221
+ return results
222
+ .slice(0, mergedOptions.limit)
223
+ .map((result) => ({
224
+ id: result.id,
225
+ type: result.type,
226
+ title: result.title,
227
+ description: result.description,
228
+ score: result.score,
229
+ parentId: result.parentId,
230
+ matches: Object.entries(result.match).map(([term, fields]) => ({
231
+ field: fields[0] || 'title',
232
+ term,
233
+ })),
234
+ }));
235
+ }
236
+ /**
237
+ * Get document by ID
238
+ */
239
+ getDocument(id) {
240
+ return this.documents.get(id);
241
+ }
242
+ /**
243
+ * Get all documents
244
+ */
245
+ getAllDocuments() {
246
+ return Array.from(this.documents.values());
247
+ }
248
+ /**
249
+ * Get document count
250
+ */
251
+ getDocumentCount() {
252
+ return this.documents.size;
253
+ }
254
+ /**
255
+ * Check if the service is initialized
256
+ */
257
+ isInitialized() {
258
+ return this.initialized;
259
+ }
260
+ /**
261
+ * Clear all documents from the index
262
+ */
263
+ clear() {
264
+ this.miniSearch.removeAll();
265
+ this.documents.clear();
266
+ this.initialized = false;
267
+ }
268
+ }
269
+ /**
270
+ * Highlight matching terms in text
271
+ */
272
+ export function highlightMatches(text, matches, highlightStart = '\x1b[33m', // Yellow ANSI
273
+ highlightEnd = '\x1b[0m' // Reset ANSI
274
+ ) {
275
+ if (!text || matches.length === 0)
276
+ return text;
277
+ const terms = matches.map((m) => m.term.toLowerCase());
278
+ // Create regex pattern for all matching terms
279
+ const pattern = new RegExp(`(${terms.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})`, 'gi');
280
+ return text.replace(pattern, `${highlightStart}$1${highlightEnd}`);
281
+ }
282
+ /**
283
+ * Highlight matches with HTML tags (for web)
284
+ */
285
+ export function highlightMatchesHtml(text, matches, className = 'search-highlight') {
286
+ return highlightMatches(text, matches, `<mark class="${className}">`, '</mark>');
287
+ }
288
+ /**
289
+ * Create a singleton search service instance
290
+ */
291
+ let searchServiceInstance = null;
292
+ export function getSearchService(projectRoot) {
293
+ if (!searchServiceInstance || (projectRoot && projectRoot !== searchServiceInstance['projectRoot'])) {
294
+ searchServiceInstance = new SearchService(projectRoot);
295
+ }
296
+ return searchServiceInstance;
297
+ }
298
+ export default SearchService;
299
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ export declare const SimpleFeatureSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ name: z.ZodString;
5
+ description: z.ZodString;
6
+ estimate: z.ZodNumber;
7
+ assignee: z.ZodString;
8
+ priority: z.ZodEnum<{
9
+ low: "low";
10
+ medium: "medium";
11
+ high: "high";
12
+ critical: "critical";
13
+ }>;
14
+ status: z.ZodEnum<{
15
+ "in-progress": "in-progress";
16
+ todo: "todo";
17
+ done: "done";
18
+ blocked: "blocked";
19
+ }>;
20
+ category: z.ZodOptional<z.ZodString>;
21
+ tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
22
+ createdDate: z.ZodOptional<z.ZodString>;
23
+ dueDate: z.ZodOptional<z.ZodString>;
24
+ }, z.core.$strip>;
25
+ export type SimpleFeature = z.infer<typeof SimpleFeatureSchema>;
26
+ export declare const FeaturesTableSchema: z.ZodObject<{
27
+ features: z.ZodArray<z.ZodObject<{
28
+ id: z.ZodString;
29
+ name: z.ZodString;
30
+ description: z.ZodString;
31
+ estimate: z.ZodNumber;
32
+ assignee: z.ZodString;
33
+ priority: z.ZodEnum<{
34
+ low: "low";
35
+ medium: "medium";
36
+ high: "high";
37
+ critical: "critical";
38
+ }>;
39
+ status: z.ZodEnum<{
40
+ "in-progress": "in-progress";
41
+ todo: "todo";
42
+ done: "done";
43
+ blocked: "blocked";
44
+ }>;
45
+ category: z.ZodOptional<z.ZodString>;
46
+ tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
47
+ createdDate: z.ZodOptional<z.ZodString>;
48
+ dueDate: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$strip>>;
50
+ lastUpdated: z.ZodOptional<z.ZodString>;
51
+ version: z.ZodDefault<z.ZodString>;
52
+ }, z.core.$strip>;
53
+ export type FeaturesTable = z.infer<typeof FeaturesTableSchema>;
54
+ //# sourceMappingURL=simple-model.d.ts.map
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export const SimpleFeatureSchema = z.object({
3
+ id: z.string(),
4
+ name: z.string(),
5
+ description: z.string(),
6
+ estimate: z.number().positive(),
7
+ assignee: z.string(),
8
+ priority: z.enum(['low', 'medium', 'high', 'critical']),
9
+ status: z.enum(['todo', 'in-progress', 'done', 'blocked']),
10
+ category: z.string().optional(), // 用于分组,相当于 Epic
11
+ tags: z.array(z.string()).default([]),
12
+ createdDate: z.string().optional(),
13
+ dueDate: z.string().optional(),
14
+ });
15
+ export const FeaturesTableSchema = z.object({
16
+ features: z.array(SimpleFeatureSchema),
17
+ lastUpdated: z.string().optional(),
18
+ version: z.string().default('1.0'),
19
+ });
20
+ //# sourceMappingURL=simple-model.js.map
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ export declare const TeamMemberSchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
5
+ capacity: z.ZodNumber;
6
+ currentLoad: z.ZodDefault<z.ZodNumber>;
7
+ }, z.core.$strip>;
8
+ export type TeamMember = z.infer<typeof TeamMemberSchema>;
9
+ export declare const TeamSchema: z.ZodObject<{
10
+ members: z.ZodDefault<z.ZodArray<z.ZodObject<{
11
+ name: z.ZodString;
12
+ skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
13
+ capacity: z.ZodNumber;
14
+ currentLoad: z.ZodDefault<z.ZodNumber>;
15
+ }, z.core.$strip>>>;
16
+ }, z.core.$strip>;
17
+ export type Team = z.infer<typeof TeamSchema>;
18
+ /**
19
+ * Calculate skill match score between member skills and required skills
20
+ * Uses Jaccard similarity: |intersection| / |union|
21
+ * @returns score between 0 and 1
22
+ */
23
+ export declare function calculateSkillMatch(memberSkills: string[], requiredSkills: string[]): number;
24
+ /**
25
+ * Get missing skills (required but not possessed by member)
26
+ */
27
+ export declare function getMissingSkills(memberSkills: string[], requiredSkills: string[]): string[];
28
+ /**
29
+ * Calculate current load percentage
30
+ * @returns percentage between 0 and 100+
31
+ */
32
+ export declare function calculateLoadPercentage(currentLoad: number, capacity: number): number;
33
+ /**
34
+ * Check if member is overallocated
35
+ */
36
+ export declare function isOverallocated(member: TeamMember): boolean;
37
+ /**
38
+ * Get available hours for a team member
39
+ */
40
+ export declare function getAvailableHours(member: TeamMember): number;
41
+ //# sourceMappingURL=team.d.ts.map
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ // TeamMember schema
3
+ export const TeamMemberSchema = z.object({
4
+ name: z.string().min(1),
5
+ skills: z.array(z.string()).default([]),
6
+ capacity: z.number().positive(), // hours per week
7
+ currentLoad: z.number().nonnegative().default(0), // hours currently assigned
8
+ });
9
+ // Team schema
10
+ export const TeamSchema = z.object({
11
+ members: z.array(TeamMemberSchema).default([]),
12
+ });
13
+ /**
14
+ * Calculate skill match score between member skills and required skills
15
+ * Uses Jaccard similarity: |intersection| / |union|
16
+ * @returns score between 0 and 1
17
+ */
18
+ export function calculateSkillMatch(memberSkills, requiredSkills) {
19
+ if (requiredSkills.length === 0) {
20
+ return 1.0; // No skill requirements = perfect match
21
+ }
22
+ const memberSet = new Set(memberSkills.map(s => s.toLowerCase()));
23
+ const requiredSet = new Set(requiredSkills.map(s => s.toLowerCase()));
24
+ const intersection = new Set([...memberSet].filter(s => requiredSet.has(s)));
25
+ const union = new Set([...memberSet, ...requiredSet]);
26
+ return intersection.size / union.size;
27
+ }
28
+ /**
29
+ * Get missing skills (required but not possessed by member)
30
+ */
31
+ export function getMissingSkills(memberSkills, requiredSkills) {
32
+ const memberSet = new Set(memberSkills.map(s => s.toLowerCase()));
33
+ return requiredSkills.filter(skill => !memberSet.has(skill.toLowerCase()));
34
+ }
35
+ /**
36
+ * Calculate current load percentage
37
+ * @returns percentage between 0 and 100+
38
+ */
39
+ export function calculateLoadPercentage(currentLoad, capacity) {
40
+ if (capacity === 0) {
41
+ return 0;
42
+ }
43
+ return (currentLoad / capacity) * 100;
44
+ }
45
+ /**
46
+ * Check if member is overallocated
47
+ */
48
+ export function isOverallocated(member) {
49
+ return member.currentLoad > member.capacity;
50
+ }
51
+ /**
52
+ * Get available hours for a team member
53
+ */
54
+ export function getAvailableHours(member) {
55
+ return Math.max(0, member.capacity - member.currentLoad);
56
+ }
57
+ //# sourceMappingURL=team.js.map
@@ -0,0 +1,49 @@
1
+ import type { TeamMember } from './team.js';
2
+ import type { Feature } from './project.js';
3
+ export interface AssignmentScore {
4
+ member: TeamMember;
5
+ score: number;
6
+ skillMatch: number;
7
+ loadPercentage: number;
8
+ availableHours: number;
9
+ missingSkills: string[];
10
+ }
11
+ export interface WorkloadSummary {
12
+ member: TeamMember;
13
+ assignedFeatures: string[];
14
+ totalEstimate: number;
15
+ loadPercentage: number;
16
+ availableHours: number;
17
+ isOverallocated: boolean;
18
+ }
19
+ /**
20
+ * WorkloadAnalyzer calculates assignment scores and workload summaries
21
+ */
22
+ export declare class WorkloadAnalyzer {
23
+ /**
24
+ * Calculate assignment score for a member and feature
25
+ * score = skillMatch * (1 - loadPercentage/100)
26
+ */
27
+ calculateAssignmentScore(member: TeamMember, feature: Feature): AssignmentScore;
28
+ /**
29
+ * Rank team members by assignment score for a given feature
30
+ * Returns top N candidates sorted by score (descending)
31
+ */
32
+ rankCandidates(team: TeamMember[], feature: Feature, topN?: number): AssignmentScore[];
33
+ /**
34
+ * Generate workload summary for all team members
35
+ */
36
+ generateWorkloadSummary(team: TeamMember[], features: Feature[]): WorkloadSummary[];
37
+ /**
38
+ * Find skills that are required but missing from the team
39
+ */
40
+ findSkillGaps(team: TeamMember[], features: Feature[]): Map<string, string[]>;
41
+ /**
42
+ * Find skills with high demand (required by many features but few team members have it)
43
+ */
44
+ findHighDemandSkills(team: TeamMember[], features: Feature[]): Map<string, {
45
+ demandCount: number;
46
+ memberCount: number;
47
+ }>;
48
+ }
49
+ //# sourceMappingURL=workload.d.ts.map