musubi-sdd 6.1.2 → 6.2.1
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.ja.md +60 -1
- package/README.md +60 -1
- package/bin/musubi-dashboard.js +340 -0
- package/package.json +3 -2
- package/src/cli/dashboard-cli.js +536 -0
- package/src/constitutional/checker.js +633 -0
- package/src/constitutional/ci-reporter.js +336 -0
- package/src/constitutional/index.js +22 -0
- package/src/constitutional/phase-minus-one.js +404 -0
- package/src/constitutional/steering-sync.js +473 -0
- package/src/dashboard/index.js +20 -0
- package/src/dashboard/sprint-planner.js +361 -0
- package/src/dashboard/sprint-reporter.js +378 -0
- package/src/dashboard/transition-recorder.js +209 -0
- package/src/dashboard/workflow-dashboard.js +434 -0
- package/src/enterprise/error-recovery.js +524 -0
- package/src/enterprise/experiment-report.js +573 -0
- package/src/enterprise/index.js +57 -4
- package/src/enterprise/rollback-manager.js +584 -0
- package/src/enterprise/tech-article.js +509 -0
- package/src/orchestration/builtin-skills.js +425 -0
- package/src/templates/agents/claude-code/skills/design-reviewer/SKILL.md +1135 -0
- package/src/templates/agents/claude-code/skills/requirements-reviewer/SKILL.md +997 -0
- package/src/traceability/extractor.js +294 -0
- package/src/traceability/gap-detector.js +230 -0
- package/src/traceability/index.js +15 -0
- package/src/traceability/matrix-storage.js +368 -0
- package/src/validators/design-reviewer.js +1300 -0
- package/src/validators/requirements-reviewer.js +1019 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SprintPlanner Implementation
|
|
3
|
+
*
|
|
4
|
+
* Generates sprint planning templates based on tasks.
|
|
5
|
+
*
|
|
6
|
+
* Requirement: IMP-6.2-003-03
|
|
7
|
+
* Design: Section 4.3
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default configuration
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
storageDir: 'storage/sprints',
|
|
18
|
+
defaultSprintDuration: 14, // days
|
|
19
|
+
defaultVelocity: 20 // story points
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Task priority levels
|
|
24
|
+
*/
|
|
25
|
+
const PRIORITY = {
|
|
26
|
+
CRITICAL: 'critical',
|
|
27
|
+
HIGH: 'high',
|
|
28
|
+
MEDIUM: 'medium',
|
|
29
|
+
LOW: 'low'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* SprintPlanner
|
|
34
|
+
*
|
|
35
|
+
* Creates and manages sprint plans.
|
|
36
|
+
*/
|
|
37
|
+
class SprintPlanner {
|
|
38
|
+
/**
|
|
39
|
+
* @param {Object} config - Configuration options
|
|
40
|
+
*/
|
|
41
|
+
constructor(config = {}) {
|
|
42
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a new sprint plan
|
|
47
|
+
* @param {Object} options - Sprint options
|
|
48
|
+
* @returns {Promise<Object>} Created sprint plan
|
|
49
|
+
*/
|
|
50
|
+
async createSprint(options) {
|
|
51
|
+
const sprintId = options.sprintId || `SPRINT-${Date.now()}`;
|
|
52
|
+
|
|
53
|
+
const sprint = {
|
|
54
|
+
id: sprintId,
|
|
55
|
+
name: options.name || `Sprint ${sprintId}`,
|
|
56
|
+
featureId: options.featureId,
|
|
57
|
+
goal: options.goal || '',
|
|
58
|
+
startDate: options.startDate || new Date().toISOString().slice(0, 10),
|
|
59
|
+
endDate: options.endDate || this.calculateEndDate(options.startDate),
|
|
60
|
+
duration: options.duration || this.config.defaultSprintDuration,
|
|
61
|
+
velocity: options.velocity || this.config.defaultVelocity,
|
|
62
|
+
tasks: [],
|
|
63
|
+
status: 'planning',
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
updatedAt: new Date().toISOString()
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
await this.saveSprint(sprint);
|
|
69
|
+
|
|
70
|
+
return sprint;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add tasks to sprint
|
|
75
|
+
* @param {string} sprintId - Sprint ID
|
|
76
|
+
* @param {Array} tasks - Tasks to add
|
|
77
|
+
* @returns {Promise<Object>} Updated sprint
|
|
78
|
+
*/
|
|
79
|
+
async addTasks(sprintId, tasks) {
|
|
80
|
+
const sprint = await this.loadSprint(sprintId);
|
|
81
|
+
if (!sprint) {
|
|
82
|
+
throw new Error(`Sprint not found: ${sprintId}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const task of tasks) {
|
|
86
|
+
const sprintTask = {
|
|
87
|
+
id: task.id || `T-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`,
|
|
88
|
+
title: task.title,
|
|
89
|
+
description: task.description || '',
|
|
90
|
+
requirementId: task.requirementId || null,
|
|
91
|
+
storyPoints: task.storyPoints || 1,
|
|
92
|
+
priority: task.priority || PRIORITY.MEDIUM,
|
|
93
|
+
assignee: task.assignee || null,
|
|
94
|
+
status: 'todo',
|
|
95
|
+
dependencies: task.dependencies || [],
|
|
96
|
+
acceptanceCriteria: task.acceptanceCriteria || [],
|
|
97
|
+
addedAt: new Date().toISOString()
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
sprint.tasks.push(sprintTask);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
sprint.updatedAt = new Date().toISOString();
|
|
104
|
+
await this.saveSprint(sprint);
|
|
105
|
+
|
|
106
|
+
return sprint;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Update task status
|
|
111
|
+
* @param {string} sprintId - Sprint ID
|
|
112
|
+
* @param {string} taskId - Task ID
|
|
113
|
+
* @param {string} status - New status
|
|
114
|
+
* @returns {Promise<Object>} Updated sprint
|
|
115
|
+
*/
|
|
116
|
+
async updateTaskStatus(sprintId, taskId, status) {
|
|
117
|
+
const sprint = await this.loadSprint(sprintId);
|
|
118
|
+
if (!sprint) {
|
|
119
|
+
throw new Error(`Sprint not found: ${sprintId}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const task = sprint.tasks.find(t => t.id === taskId);
|
|
123
|
+
if (!task) {
|
|
124
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
task.status = status;
|
|
128
|
+
if (status === 'done') {
|
|
129
|
+
task.completedAt = new Date().toISOString();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
sprint.updatedAt = new Date().toISOString();
|
|
133
|
+
await this.saveSprint(sprint);
|
|
134
|
+
|
|
135
|
+
return sprint;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Start sprint
|
|
140
|
+
* @param {string} sprintId - Sprint ID
|
|
141
|
+
* @returns {Promise<Object>} Updated sprint
|
|
142
|
+
*/
|
|
143
|
+
async startSprint(sprintId) {
|
|
144
|
+
const sprint = await this.loadSprint(sprintId);
|
|
145
|
+
if (!sprint) {
|
|
146
|
+
throw new Error(`Sprint not found: ${sprintId}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
sprint.status = 'active';
|
|
150
|
+
sprint.startedAt = new Date().toISOString();
|
|
151
|
+
sprint.updatedAt = new Date().toISOString();
|
|
152
|
+
|
|
153
|
+
await this.saveSprint(sprint);
|
|
154
|
+
|
|
155
|
+
return sprint;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Complete sprint
|
|
160
|
+
* @param {string} sprintId - Sprint ID
|
|
161
|
+
* @returns {Promise<Object>} Updated sprint
|
|
162
|
+
*/
|
|
163
|
+
async completeSprint(sprintId) {
|
|
164
|
+
const sprint = await this.loadSprint(sprintId);
|
|
165
|
+
if (!sprint) {
|
|
166
|
+
throw new Error(`Sprint not found: ${sprintId}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
sprint.status = 'completed';
|
|
170
|
+
sprint.completedAt = new Date().toISOString();
|
|
171
|
+
sprint.updatedAt = new Date().toISOString();
|
|
172
|
+
|
|
173
|
+
await this.saveSprint(sprint);
|
|
174
|
+
|
|
175
|
+
return sprint;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get sprint by ID
|
|
180
|
+
* @param {string} sprintId - Sprint ID
|
|
181
|
+
* @returns {Promise<Object|null>} Sprint
|
|
182
|
+
*/
|
|
183
|
+
async getSprint(sprintId) {
|
|
184
|
+
return await this.loadSprint(sprintId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Calculate sprint metrics
|
|
189
|
+
* @param {string} sprintId - Sprint ID
|
|
190
|
+
* @returns {Promise<Object>} Sprint metrics
|
|
191
|
+
*/
|
|
192
|
+
async getMetrics(sprintId) {
|
|
193
|
+
const sprint = await this.loadSprint(sprintId);
|
|
194
|
+
if (!sprint) {
|
|
195
|
+
throw new Error(`Sprint not found: ${sprintId}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const tasks = sprint.tasks;
|
|
199
|
+
const totalPoints = tasks.reduce((sum, t) => sum + t.storyPoints, 0);
|
|
200
|
+
const completedPoints = tasks
|
|
201
|
+
.filter(t => t.status === 'done')
|
|
202
|
+
.reduce((sum, t) => sum + t.storyPoints, 0);
|
|
203
|
+
const inProgressPoints = tasks
|
|
204
|
+
.filter(t => t.status === 'in-progress')
|
|
205
|
+
.reduce((sum, t) => sum + t.storyPoints, 0);
|
|
206
|
+
|
|
207
|
+
const todoTasks = tasks.filter(t => t.status === 'todo').length;
|
|
208
|
+
const inProgressTasks = tasks.filter(t => t.status === 'in-progress').length;
|
|
209
|
+
const doneTasks = tasks.filter(t => t.status === 'done').length;
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
sprintId,
|
|
213
|
+
totalTasks: tasks.length,
|
|
214
|
+
todoTasks,
|
|
215
|
+
inProgressTasks,
|
|
216
|
+
doneTasks,
|
|
217
|
+
totalPoints,
|
|
218
|
+
completedPoints,
|
|
219
|
+
inProgressPoints,
|
|
220
|
+
remainingPoints: totalPoints - completedPoints,
|
|
221
|
+
completionPercentage: totalPoints > 0
|
|
222
|
+
? Math.round((completedPoints / totalPoints) * 100)
|
|
223
|
+
: 0,
|
|
224
|
+
velocity: sprint.velocity,
|
|
225
|
+
overCapacity: totalPoints > sprint.velocity
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generate sprint backlog template
|
|
231
|
+
* @param {string} sprintId - Sprint ID
|
|
232
|
+
* @returns {Promise<string>} Markdown template
|
|
233
|
+
*/
|
|
234
|
+
async generateBacklogTemplate(sprintId) {
|
|
235
|
+
const sprint = await this.loadSprint(sprintId);
|
|
236
|
+
if (!sprint) {
|
|
237
|
+
throw new Error(`Sprint not found: ${sprintId}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const metrics = await this.getMetrics(sprintId);
|
|
241
|
+
const lines = [];
|
|
242
|
+
|
|
243
|
+
lines.push(`# ${sprint.name}`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
lines.push(`**Feature:** ${sprint.featureId || 'N/A'}`);
|
|
246
|
+
lines.push(`**Goal:** ${sprint.goal || 'N/A'}`);
|
|
247
|
+
lines.push(`**Period:** ${sprint.startDate} - ${sprint.endDate}`);
|
|
248
|
+
lines.push(`**Velocity:** ${sprint.velocity} points`);
|
|
249
|
+
lines.push('');
|
|
250
|
+
|
|
251
|
+
// Summary
|
|
252
|
+
lines.push('## Summary');
|
|
253
|
+
lines.push('');
|
|
254
|
+
lines.push('| Metric | Value |');
|
|
255
|
+
lines.push('|--------|-------|');
|
|
256
|
+
lines.push(`| Total Tasks | ${metrics.totalTasks} |`);
|
|
257
|
+
lines.push(`| Total Points | ${metrics.totalPoints} |`);
|
|
258
|
+
lines.push(`| Completed Points | ${metrics.completedPoints} |`);
|
|
259
|
+
lines.push(`| Completion | ${metrics.completionPercentage}% |`);
|
|
260
|
+
lines.push('');
|
|
261
|
+
|
|
262
|
+
// Tasks by priority
|
|
263
|
+
lines.push('## Tasks');
|
|
264
|
+
lines.push('');
|
|
265
|
+
|
|
266
|
+
const priorityOrder = ['critical', 'high', 'medium', 'low'];
|
|
267
|
+
|
|
268
|
+
for (const priority of priorityOrder) {
|
|
269
|
+
const priorityTasks = sprint.tasks.filter(t => t.priority === priority);
|
|
270
|
+
if (priorityTasks.length > 0) {
|
|
271
|
+
lines.push(`### ${priority.charAt(0).toUpperCase() + priority.slice(1)} Priority`);
|
|
272
|
+
lines.push('');
|
|
273
|
+
|
|
274
|
+
for (const task of priorityTasks) {
|
|
275
|
+
const status = task.status === 'done' ? '✅' :
|
|
276
|
+
task.status === 'in-progress' ? '🔄' : '⬜';
|
|
277
|
+
lines.push(`- ${status} **${task.id}**: ${task.title} (${task.storyPoints}pt)`);
|
|
278
|
+
if (task.requirementId) {
|
|
279
|
+
lines.push(` - Requirement: ${task.requirementId}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
lines.push('');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return lines.join('\n');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Prioritize tasks by dependencies and priority
|
|
291
|
+
* @param {Array} tasks - Tasks to prioritize
|
|
292
|
+
* @returns {Array} Prioritized tasks
|
|
293
|
+
*/
|
|
294
|
+
prioritizeTasks(tasks) {
|
|
295
|
+
const priorityWeight = {
|
|
296
|
+
critical: 4,
|
|
297
|
+
high: 3,
|
|
298
|
+
medium: 2,
|
|
299
|
+
low: 1
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Sort by priority weight (descending) then by dependency count (ascending)
|
|
303
|
+
return [...tasks].sort((a, b) => {
|
|
304
|
+
const priorityDiff = priorityWeight[b.priority] - priorityWeight[a.priority];
|
|
305
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
306
|
+
|
|
307
|
+
return (a.dependencies?.length || 0) - (b.dependencies?.length || 0);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Calculate end date based on duration
|
|
313
|
+
* @param {string} startDate - Start date
|
|
314
|
+
* @returns {string} End date
|
|
315
|
+
*/
|
|
316
|
+
calculateEndDate(startDate) {
|
|
317
|
+
const start = startDate ? new Date(startDate) : new Date();
|
|
318
|
+
const end = new Date(start);
|
|
319
|
+
end.setDate(end.getDate() + this.config.defaultSprintDuration);
|
|
320
|
+
return end.toISOString().slice(0, 10);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Save sprint to storage
|
|
325
|
+
* @param {Object} sprint - Sprint to save
|
|
326
|
+
*/
|
|
327
|
+
async saveSprint(sprint) {
|
|
328
|
+
await this.ensureStorageDir();
|
|
329
|
+
|
|
330
|
+
const filePath = path.join(this.config.storageDir, `${sprint.id}.json`);
|
|
331
|
+
await fs.writeFile(filePath, JSON.stringify(sprint, null, 2), 'utf-8');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Load sprint from storage
|
|
336
|
+
* @param {string} sprintId - Sprint ID
|
|
337
|
+
* @returns {Promise<Object|null>} Sprint
|
|
338
|
+
*/
|
|
339
|
+
async loadSprint(sprintId) {
|
|
340
|
+
try {
|
|
341
|
+
const filePath = path.join(this.config.storageDir, `${sprintId}.json`);
|
|
342
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
343
|
+
return JSON.parse(content);
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Ensure storage directory exists
|
|
351
|
+
*/
|
|
352
|
+
async ensureStorageDir() {
|
|
353
|
+
try {
|
|
354
|
+
await fs.access(this.config.storageDir);
|
|
355
|
+
} catch {
|
|
356
|
+
await fs.mkdir(this.config.storageDir, { recursive: true });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
module.exports = { SprintPlanner, PRIORITY };
|