@baselineos/persona 0.1.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/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ export { BaselinePersonaEngine } from './engine.js';
2
+ export { PersonaUISystem } from './ui.js';
3
+ export { AgileProductManagerPersona } from './personas/agile-pm.js';
4
+ export { SoftwareDevelopmentLeadPersona } from './personas/dev-lead.js';
5
+
6
+ export type {
7
+ PersonaConfig,
8
+ Persona,
9
+ ActivePersona,
10
+ StudioConfig,
11
+ AgilePMConfig,
12
+ BehaviorData,
13
+ BehaviorPattern,
14
+ PersonaEngineConfig,
15
+ } from './engine.js';
16
+
17
+ export type {
18
+ PersonaOption,
19
+ WorkflowInstance,
20
+ } from './ui.js';
21
+
22
+ export type {
23
+ Sprint,
24
+ Story,
25
+ BacklogItem,
26
+ } from './personas/agile-pm.js';
27
+
28
+ export type {
29
+ TeamMember,
30
+ Task,
31
+ CodeReview,
32
+ ReviewComment,
33
+ } from './personas/dev-lead.js';
@@ -0,0 +1,399 @@
1
+ import { EventEmitter } from 'events';
2
+ import { randomUUID } from 'crypto';
3
+ import { readFile, writeFile } from 'fs/promises';
4
+
5
+ // ─── Type Definitions ────────────────────────────────────────────────────────
6
+
7
+ export interface Story {
8
+ id: string;
9
+ title: string;
10
+ description: string;
11
+ points: number;
12
+ priority: string;
13
+ assignee?: string;
14
+ status: string;
15
+ acceptance: string[];
16
+ completedAt?: string;
17
+ updatedAt?: string;
18
+ }
19
+
20
+ export interface Sprint {
21
+ id: string;
22
+ name: string;
23
+ startDate: string;
24
+ endDate: string;
25
+ goals: string[];
26
+ stories: Story[];
27
+ team: string[];
28
+ status: string;
29
+ }
30
+
31
+ export interface BacklogItem {
32
+ id: string;
33
+ title: string;
34
+ description: string;
35
+ type: string;
36
+ priority: string;
37
+ points: number;
38
+ epic?: string;
39
+ createdAt: string;
40
+ }
41
+
42
+ export interface VelocityData {
43
+ sprintId: string;
44
+ sprintName: string;
45
+ plannedPoints: number;
46
+ completedPoints: number;
47
+ velocity: number;
48
+ }
49
+
50
+ export interface BurndownEntry {
51
+ day: number;
52
+ date: string;
53
+ idealRemaining: number;
54
+ actualRemaining: number;
55
+ }
56
+
57
+ export interface SprintReport {
58
+ sprint: Sprint;
59
+ totalStories: number;
60
+ completedStories: number;
61
+ totalPoints: number;
62
+ completedPoints: number;
63
+ completionRate: number;
64
+ velocity: number;
65
+ storiesByStatus: Record<string, number>;
66
+ storiesByPriority: Record<string, number>;
67
+ }
68
+
69
+ export interface WorkspaceData {
70
+ sprints: Sprint[];
71
+ backlog: BacklogItem[];
72
+ lastUpdated: string;
73
+ }
74
+
75
+ export interface PersonaStatus {
76
+ persona: string;
77
+ activeSprints: number;
78
+ totalStories: number;
79
+ backlogSize: number;
80
+ averageVelocity: number;
81
+ completedSprints: number;
82
+ }
83
+
84
+ // ─── Agile Product Manager Persona ──────────────────────────────────────────
85
+
86
+ export class AgileProductManagerPersona extends EventEmitter {
87
+ private sprints: Map<string, Sprint>;
88
+ private backlog: BacklogItem[];
89
+ private velocityHistory: VelocityData[];
90
+ private workspacePath: string;
91
+
92
+ constructor(workspacePath: string = './workspace') {
93
+ super();
94
+ this.sprints = new Map();
95
+ this.backlog = [];
96
+ this.velocityHistory = [];
97
+ this.workspacePath = workspacePath;
98
+ console.log('[AgileProductManagerPersona] Persona initialized');
99
+ }
100
+
101
+ // ─── Sprint Management ────────────────────────────────────────────────────
102
+
103
+ createSprint(name: string, startDate: string, duration: number, goals: string[], team: string[]): Sprint {
104
+ const endDate = this.calculateEndDate(startDate, duration);
105
+
106
+ const sprint: Sprint = {
107
+ id: randomUUID(),
108
+ name,
109
+ startDate,
110
+ endDate,
111
+ goals,
112
+ stories: [],
113
+ team,
114
+ status: 'planning',
115
+ };
116
+
117
+ this.sprints.set(sprint.id, sprint);
118
+ this.emit('sprint:created', { sprintId: sprint.id, name });
119
+ console.log(`[AgileProductManagerPersona] Sprint created: ${name} (${startDate} - ${endDate})`);
120
+ return sprint;
121
+ }
122
+
123
+ addStoryToSprint(sprintId: string, story: Omit<Story, 'id' | 'status' | 'updatedAt'>): Story {
124
+ const sprint = this.sprints.get(sprintId);
125
+ if (!sprint) {
126
+ throw new Error(`Sprint '${sprintId}' not found`);
127
+ }
128
+
129
+ const fullStory: Story = {
130
+ ...story,
131
+ id: randomUUID(),
132
+ status: 'todo',
133
+ updatedAt: new Date().toISOString(),
134
+ };
135
+
136
+ sprint.stories.push(fullStory);
137
+ this.emit('story:added', { sprintId, storyId: fullStory.id, title: fullStory.title });
138
+ console.log(`[AgileProductManagerPersona] Story added to sprint ${sprint.name}: ${fullStory.title}`);
139
+ return fullStory;
140
+ }
141
+
142
+ updateStoryStatus(sprintId: string, storyId: string, newStatus: string): Story {
143
+ const sprint = this.sprints.get(sprintId);
144
+ if (!sprint) {
145
+ throw new Error(`Sprint '${sprintId}' not found`);
146
+ }
147
+
148
+ const story = sprint.stories.find((s) => s.id === storyId);
149
+ if (!story) {
150
+ throw new Error(`Story '${storyId}' not found in sprint '${sprintId}'`);
151
+ }
152
+
153
+ const oldStatus = story.status;
154
+ story.status = newStatus;
155
+ story.updatedAt = new Date().toISOString();
156
+
157
+ if (newStatus === 'done') {
158
+ story.completedAt = new Date().toISOString();
159
+ }
160
+
161
+ this.emit('story:statusChanged', { sprintId, storyId, oldStatus, newStatus });
162
+ console.log(`[AgileProductManagerPersona] Story ${story.title}: ${oldStatus} -> ${newStatus}`);
163
+ return story;
164
+ }
165
+
166
+ // ─── Backlog Management ───────────────────────────────────────────────────
167
+
168
+ addToBacklog(item: Omit<BacklogItem, 'id' | 'createdAt'>): BacklogItem {
169
+ const fullItem: BacklogItem = {
170
+ ...item,
171
+ id: randomUUID(),
172
+ createdAt: new Date().toISOString(),
173
+ };
174
+
175
+ this.backlog.push(fullItem);
176
+ this.emit('backlog:itemAdded', { itemId: fullItem.id, title: fullItem.title });
177
+ console.log(`[AgileProductManagerPersona] Backlog item added: ${fullItem.title}`);
178
+ return fullItem;
179
+ }
180
+
181
+ prioritizeBacklog(criteria: string = 'priority'): BacklogItem[] {
182
+ const priorityOrder: Record<string, number> = {
183
+ critical: 0,
184
+ high: 1,
185
+ medium: 2,
186
+ low: 3,
187
+ };
188
+
189
+ switch (criteria) {
190
+ case 'priority':
191
+ this.backlog.sort((a, b) => {
192
+ const aPriority = priorityOrder[a.priority] ?? 4;
193
+ const bPriority = priorityOrder[b.priority] ?? 4;
194
+ return aPriority - bPriority;
195
+ });
196
+ break;
197
+ case 'points':
198
+ this.backlog.sort((a, b) => b.points - a.points);
199
+ break;
200
+ case 'created':
201
+ this.backlog.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
202
+ break;
203
+ case 'type':
204
+ this.backlog.sort((a, b) => a.type.localeCompare(b.type));
205
+ break;
206
+ default:
207
+ this.backlog.sort((a, b) => {
208
+ const aPriority = priorityOrder[a.priority] ?? 4;
209
+ const bPriority = priorityOrder[b.priority] ?? 4;
210
+ return aPriority - bPriority;
211
+ });
212
+ }
213
+
214
+ this.emit('backlog:prioritized', { criteria, itemCount: this.backlog.length });
215
+ console.log(`[AgileProductManagerPersona] Backlog prioritized by ${criteria}: ${this.backlog.length} items`);
216
+ return [...this.backlog];
217
+ }
218
+
219
+ // ─── Velocity & Metrics ───────────────────────────────────────────────────
220
+
221
+ calculateVelocity(sprintId: string): VelocityData {
222
+ const sprint = this.sprints.get(sprintId);
223
+ if (!sprint) {
224
+ throw new Error(`Sprint '${sprintId}' not found`);
225
+ }
226
+
227
+ const totalPoints = sprint.stories.reduce((sum, s) => sum + s.points, 0);
228
+ const completedPoints = sprint.stories
229
+ .filter((s) => s.status === 'done')
230
+ .reduce((sum, s) => sum + s.points, 0);
231
+
232
+ const velocityData: VelocityData = {
233
+ sprintId: sprint.id,
234
+ sprintName: sprint.name,
235
+ plannedPoints: totalPoints,
236
+ completedPoints,
237
+ velocity: completedPoints,
238
+ };
239
+
240
+ this.velocityHistory.push(velocityData);
241
+ this.emit('velocity:calculated', { sprintId, velocity: completedPoints });
242
+ return velocityData;
243
+ }
244
+
245
+ // ─── Reporting ────────────────────────────────────────────────────────────
246
+
247
+ generateSprintReport(sprintId: string): SprintReport {
248
+ const sprint = this.sprints.get(sprintId);
249
+ if (!sprint) {
250
+ throw new Error(`Sprint '${sprintId}' not found`);
251
+ }
252
+
253
+ const totalStories = sprint.stories.length;
254
+ const completedStories = sprint.stories.filter((s) => s.status === 'done').length;
255
+ const totalPoints = sprint.stories.reduce((sum, s) => sum + s.points, 0);
256
+ const completedPoints = sprint.stories
257
+ .filter((s) => s.status === 'done')
258
+ .reduce((sum, s) => sum + s.points, 0);
259
+
260
+ const storiesByStatus: Record<string, number> = {};
261
+ for (const story of sprint.stories) {
262
+ storiesByStatus[story.status] = (storiesByStatus[story.status] || 0) + 1;
263
+ }
264
+
265
+ const storiesByPriority: Record<string, number> = {};
266
+ for (const story of sprint.stories) {
267
+ storiesByPriority[story.priority] = (storiesByPriority[story.priority] || 0) + 1;
268
+ }
269
+
270
+ const report: SprintReport = {
271
+ sprint,
272
+ totalStories,
273
+ completedStories,
274
+ totalPoints,
275
+ completedPoints,
276
+ completionRate: totalStories > 0 ? (completedStories / totalStories) * 100 : 0,
277
+ velocity: completedPoints,
278
+ storiesByStatus,
279
+ storiesByPriority,
280
+ };
281
+
282
+ this.emit('report:generated', { sprintId, type: 'sprint' });
283
+ console.log(`[AgileProductManagerPersona] Sprint report generated for: ${sprint.name}`);
284
+ return report;
285
+ }
286
+
287
+ generateBurndownData(sprintId: string): BurndownEntry[] {
288
+ const sprint = this.sprints.get(sprintId);
289
+ if (!sprint) {
290
+ throw new Error(`Sprint '${sprintId}' not found`);
291
+ }
292
+
293
+ const totalPoints = sprint.stories.reduce((sum, s) => sum + s.points, 0);
294
+ const startDate = new Date(sprint.startDate);
295
+ const endDate = new Date(sprint.endDate);
296
+ const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24));
297
+ const dailyIdealBurn = totalPoints / totalDays;
298
+
299
+ const burndownData: BurndownEntry[] = [];
300
+ let actualRemaining = totalPoints;
301
+
302
+ for (let day = 0; day <= totalDays; day++) {
303
+ const currentDate = new Date(startDate);
304
+ currentDate.setDate(currentDate.getDate() + day);
305
+ const dateStr = currentDate.toISOString().split('T')[0];
306
+
307
+ const storiesCompletedByDate = sprint.stories.filter((s) => {
308
+ if (!s.completedAt) return false;
309
+ return new Date(s.completedAt) <= currentDate;
310
+ });
311
+
312
+ const completedPoints = storiesCompletedByDate.reduce((sum, s) => sum + s.points, 0);
313
+ actualRemaining = totalPoints - completedPoints;
314
+
315
+ burndownData.push({
316
+ day,
317
+ date: dateStr,
318
+ idealRemaining: Math.max(0, totalPoints - dailyIdealBurn * day),
319
+ actualRemaining,
320
+ });
321
+ }
322
+
323
+ this.emit('burndown:generated', { sprintId, dataPoints: burndownData.length });
324
+ return burndownData;
325
+ }
326
+
327
+ // ─── Utility Methods ──────────────────────────────────────────────────────
328
+
329
+ private calculateEndDate(startDate: string, durationDays: number): string {
330
+ const date = new Date(startDate);
331
+ date.setDate(date.getDate() + durationDays);
332
+ return date.toISOString().split('T')[0];
333
+ }
334
+
335
+ async loadWorkspaceData(): Promise<WorkspaceData | null> {
336
+ try {
337
+ const filePath = `${this.workspacePath}/agile-pm-data.json`;
338
+ const data = await readFile(filePath, 'utf-8');
339
+ const parsed: WorkspaceData = JSON.parse(data);
340
+
341
+ for (const sprint of parsed.sprints) {
342
+ this.sprints.set(sprint.id, sprint);
343
+ }
344
+ this.backlog = parsed.backlog || [];
345
+
346
+ console.log(`[AgileProductManagerPersona] Workspace data loaded: ${this.sprints.size} sprints, ${this.backlog.length} backlog items`);
347
+ return parsed;
348
+ } catch (error: unknown) {
349
+ const message = error instanceof Error ? error.message : String(error);
350
+ console.log(`[AgileProductManagerPersona] No existing workspace data found: ${message}`);
351
+ return null;
352
+ }
353
+ }
354
+
355
+ async saveWorkspaceData(): Promise<void> {
356
+ try {
357
+ const data: WorkspaceData = {
358
+ sprints: Array.from(this.sprints.values()),
359
+ backlog: this.backlog,
360
+ lastUpdated: new Date().toISOString(),
361
+ };
362
+
363
+ const filePath = `${this.workspacePath}/agile-pm-data.json`;
364
+ await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
365
+ console.log('[AgileProductManagerPersona] Workspace data saved');
366
+ } catch (error: unknown) {
367
+ const message = error instanceof Error ? error.message : String(error);
368
+ console.log(`[AgileProductManagerPersona] Failed to save workspace data: ${message}`);
369
+ }
370
+ }
371
+
372
+ getPersonaStatus(): PersonaStatus {
373
+ const activeSprints = Array.from(this.sprints.values()).filter(
374
+ (s) => s.status === 'active' || s.status === 'planning'
375
+ );
376
+ const completedSprints = Array.from(this.sprints.values()).filter(
377
+ (s) => s.status === 'completed'
378
+ );
379
+
380
+ let totalStories = 0;
381
+ for (const sprint of this.sprints.values()) {
382
+ totalStories += sprint.stories.length;
383
+ }
384
+
385
+ const avgVelocity =
386
+ this.velocityHistory.length > 0
387
+ ? this.velocityHistory.reduce((sum, v) => sum + v.velocity, 0) / this.velocityHistory.length
388
+ : 0;
389
+
390
+ return {
391
+ persona: 'Agile Product Manager',
392
+ activeSprints: activeSprints.length,
393
+ totalStories,
394
+ backlogSize: this.backlog.length,
395
+ averageVelocity: Math.round(avgVelocity * 100) / 100,
396
+ completedSprints: completedSprints.length,
397
+ };
398
+ }
399
+ }