musubi-sdd 6.2.0 → 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/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
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Steering Sync
|
|
3
|
+
*
|
|
4
|
+
* Automatically synchronizes steering files.
|
|
5
|
+
*
|
|
6
|
+
* Requirement: IMP-6.2-007-01, IMP-6.2-007-02
|
|
7
|
+
* Design: Section 5.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
|
+
steeringDir: 'steering',
|
|
18
|
+
steeringFiles: ['tech.md', 'structure.md', 'product.md'],
|
|
19
|
+
projectFile: 'project.yml',
|
|
20
|
+
backupDir: 'steering/backups'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SteeringSync
|
|
25
|
+
*
|
|
26
|
+
* Manages steering file synchronization.
|
|
27
|
+
*/
|
|
28
|
+
class SteeringSync {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} config - Configuration options
|
|
31
|
+
*/
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Update steering files for new version
|
|
38
|
+
* @param {Object} versionInfo - Version information
|
|
39
|
+
* @returns {Promise<Object>} Update result
|
|
40
|
+
*/
|
|
41
|
+
async updateForVersion(versionInfo) {
|
|
42
|
+
const updates = [];
|
|
43
|
+
|
|
44
|
+
// Backup current files
|
|
45
|
+
await this.backupFiles();
|
|
46
|
+
|
|
47
|
+
// Update project.yml
|
|
48
|
+
const projectUpdate = await this.updateProjectFile(versionInfo);
|
|
49
|
+
if (projectUpdate) updates.push(projectUpdate);
|
|
50
|
+
|
|
51
|
+
// Update product.md
|
|
52
|
+
const productUpdate = await this.updateProductFile(versionInfo);
|
|
53
|
+
if (productUpdate) updates.push(productUpdate);
|
|
54
|
+
|
|
55
|
+
// Update tech.md if needed
|
|
56
|
+
if (versionInfo.techChanges) {
|
|
57
|
+
const techUpdate = await this.updateTechFile(versionInfo);
|
|
58
|
+
if (techUpdate) updates.push(techUpdate);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Update structure.md if needed
|
|
62
|
+
if (versionInfo.structureChanges) {
|
|
63
|
+
const structureUpdate = await this.updateStructureFile(versionInfo);
|
|
64
|
+
if (structureUpdate) updates.push(structureUpdate);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
version: versionInfo.version,
|
|
69
|
+
updatedAt: new Date().toISOString(),
|
|
70
|
+
updates,
|
|
71
|
+
filesUpdated: updates.length
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check consistency between steering files
|
|
77
|
+
* @returns {Promise<Object>} Consistency check result
|
|
78
|
+
*/
|
|
79
|
+
async checkConsistency() {
|
|
80
|
+
const issues = [];
|
|
81
|
+
|
|
82
|
+
// Load all steering files
|
|
83
|
+
const project = await this.loadProjectFile();
|
|
84
|
+
const product = await this.loadSteeringFile('product.md');
|
|
85
|
+
const tech = await this.loadSteeringFile('tech.md');
|
|
86
|
+
const structure = await this.loadSteeringFile('structure.md');
|
|
87
|
+
|
|
88
|
+
// Check version consistency
|
|
89
|
+
if (project) {
|
|
90
|
+
const versionPattern = new RegExp(`v?${project.version?.replace('.', '\\.')}`, 'i');
|
|
91
|
+
|
|
92
|
+
if (product && !versionPattern.test(product)) {
|
|
93
|
+
issues.push({
|
|
94
|
+
type: 'version-mismatch',
|
|
95
|
+
file: 'product.md',
|
|
96
|
+
message: `product.mdのバージョン(${project.version})が不整合です`,
|
|
97
|
+
suggestion: `product.mdを更新してバージョン${project.version}を反映してください`
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for orphaned references
|
|
103
|
+
if (structure) {
|
|
104
|
+
// Check if directories mentioned in structure.md exist
|
|
105
|
+
const dirPattern = /`([a-z\-]+\/)`/g;
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = dirPattern.exec(structure)) !== null) {
|
|
108
|
+
const dirName = match[1].replace('/', '');
|
|
109
|
+
try {
|
|
110
|
+
await fs.access(dirName);
|
|
111
|
+
} catch {
|
|
112
|
+
issues.push({
|
|
113
|
+
type: 'missing-directory',
|
|
114
|
+
file: 'structure.md',
|
|
115
|
+
message: `structure.mdで参照されているディレクトリ "${dirName}" が存在しません`,
|
|
116
|
+
suggestion: `ディレクトリを作成するか、structure.mdから参照を削除してください`
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check tech stack consistency
|
|
123
|
+
if (tech && project?.techStack) {
|
|
124
|
+
for (const techItem of project.techStack || []) {
|
|
125
|
+
if (!tech.includes(techItem)) {
|
|
126
|
+
issues.push({
|
|
127
|
+
type: 'tech-mismatch',
|
|
128
|
+
file: 'tech.md',
|
|
129
|
+
message: `project.ymlの技術スタック "${techItem}" がtech.mdに記載されていません`,
|
|
130
|
+
suggestion: `tech.mdに "${techItem}" を追加してください`
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
consistent: issues.length === 0,
|
|
138
|
+
issues,
|
|
139
|
+
checkedAt: new Date().toISOString()
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Auto-fix consistency issues
|
|
145
|
+
* @param {Array} issues - Issues to fix
|
|
146
|
+
* @returns {Promise<Object>} Fix result
|
|
147
|
+
*/
|
|
148
|
+
async autoFix(issues) {
|
|
149
|
+
const fixed = [];
|
|
150
|
+
const failed = [];
|
|
151
|
+
|
|
152
|
+
for (const issue of issues) {
|
|
153
|
+
try {
|
|
154
|
+
switch (issue.type) {
|
|
155
|
+
case 'version-mismatch':
|
|
156
|
+
// Version updates require manual review
|
|
157
|
+
failed.push({
|
|
158
|
+
issue,
|
|
159
|
+
reason: 'バージョン更新は手動レビューが必要です'
|
|
160
|
+
});
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case 'missing-directory':
|
|
164
|
+
// Create missing directory
|
|
165
|
+
await fs.mkdir(issue.file, { recursive: true });
|
|
166
|
+
fixed.push(issue);
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
default:
|
|
170
|
+
failed.push({
|
|
171
|
+
issue,
|
|
172
|
+
reason: '自動修正がサポートされていません'
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
failed.push({
|
|
177
|
+
issue,
|
|
178
|
+
reason: error.message
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
fixed: fixed.length,
|
|
185
|
+
failed: failed.length,
|
|
186
|
+
details: { fixed, failed },
|
|
187
|
+
fixedAt: new Date().toISOString()
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Update project.yml file
|
|
193
|
+
* @param {Object} versionInfo - Version info
|
|
194
|
+
* @returns {Promise<Object|null>} Update result
|
|
195
|
+
*/
|
|
196
|
+
async updateProjectFile(versionInfo) {
|
|
197
|
+
const filePath = path.join(this.config.steeringDir, this.config.projectFile);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
201
|
+
|
|
202
|
+
// Update version
|
|
203
|
+
if (versionInfo.version) {
|
|
204
|
+
content = content.replace(
|
|
205
|
+
/version:\s*['"]?[\d.]+['"]?/,
|
|
206
|
+
`version: '${versionInfo.version}'`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Update status if provided
|
|
211
|
+
if (versionInfo.status) {
|
|
212
|
+
content = content.replace(
|
|
213
|
+
/status:\s*\w+/,
|
|
214
|
+
`status: ${versionInfo.status}`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
file: this.config.projectFile,
|
|
222
|
+
changes: ['version', 'status'].filter(k => versionInfo[k])
|
|
223
|
+
};
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Update product.md file
|
|
231
|
+
* @param {Object} versionInfo - Version info
|
|
232
|
+
* @returns {Promise<Object|null>} Update result
|
|
233
|
+
*/
|
|
234
|
+
async updateProductFile(versionInfo) {
|
|
235
|
+
const filePath = path.join(this.config.steeringDir, 'product.md');
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
239
|
+
const changes = [];
|
|
240
|
+
|
|
241
|
+
// Update version in header
|
|
242
|
+
if (versionInfo.version) {
|
|
243
|
+
const versionRegex = /\*\*Version\*\*:\s*[\d.]+/;
|
|
244
|
+
if (versionRegex.test(content)) {
|
|
245
|
+
content = content.replace(versionRegex, `**Version**: ${versionInfo.version}`);
|
|
246
|
+
changes.push('version');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Add new features to changelog section if exists
|
|
251
|
+
if (versionInfo.features && versionInfo.features.length > 0) {
|
|
252
|
+
const changelogMarker = '## Changelog';
|
|
253
|
+
if (content.includes(changelogMarker)) {
|
|
254
|
+
const featureList = versionInfo.features
|
|
255
|
+
.map(f => `- ${f}`)
|
|
256
|
+
.join('\n');
|
|
257
|
+
const changelogEntry = `\n### v${versionInfo.version}\n${featureList}\n`;
|
|
258
|
+
content = content.replace(
|
|
259
|
+
changelogMarker,
|
|
260
|
+
`${changelogMarker}${changelogEntry}`
|
|
261
|
+
);
|
|
262
|
+
changes.push('changelog');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (changes.length > 0) {
|
|
267
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
268
|
+
return { file: 'product.md', changes };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return null;
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Update tech.md file
|
|
279
|
+
* @param {Object} versionInfo - Version info
|
|
280
|
+
* @returns {Promise<Object|null>} Update result
|
|
281
|
+
*/
|
|
282
|
+
async updateTechFile(versionInfo) {
|
|
283
|
+
const filePath = path.join(this.config.steeringDir, 'tech.md');
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
287
|
+
const changes = [];
|
|
288
|
+
|
|
289
|
+
// Add new dependencies
|
|
290
|
+
if (versionInfo.techChanges?.newDependencies) {
|
|
291
|
+
for (const dep of versionInfo.techChanges.newDependencies) {
|
|
292
|
+
if (!content.includes(dep.name)) {
|
|
293
|
+
// Find dependencies section and add
|
|
294
|
+
const depsSection = '## Dependencies';
|
|
295
|
+
if (content.includes(depsSection)) {
|
|
296
|
+
const depEntry = `\n- **${dep.name}**: ${dep.description || dep.version || 'Added'}`;
|
|
297
|
+
content = content.replace(
|
|
298
|
+
depsSection,
|
|
299
|
+
`${depsSection}${depEntry}`
|
|
300
|
+
);
|
|
301
|
+
changes.push(`added-dep:${dep.name}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (changes.length > 0) {
|
|
308
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
309
|
+
return { file: 'tech.md', changes };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return null;
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Update structure.md file
|
|
320
|
+
* @param {Object} versionInfo - Version info
|
|
321
|
+
* @returns {Promise<Object|null>} Update result
|
|
322
|
+
*/
|
|
323
|
+
async updateStructureFile(versionInfo) {
|
|
324
|
+
const filePath = path.join(this.config.steeringDir, 'structure.md');
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
328
|
+
const changes = [];
|
|
329
|
+
|
|
330
|
+
// Add new directories
|
|
331
|
+
if (versionInfo.structureChanges?.newDirectories) {
|
|
332
|
+
for (const dir of versionInfo.structureChanges.newDirectories) {
|
|
333
|
+
if (!content.includes(dir.path)) {
|
|
334
|
+
// Find appropriate section
|
|
335
|
+
const dirEntry = `\n- \`${dir.path}/\`: ${dir.description || 'New directory'}`;
|
|
336
|
+
content += dirEntry;
|
|
337
|
+
changes.push(`added-dir:${dir.path}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (changes.length > 0) {
|
|
343
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
344
|
+
return { file: 'structure.md', changes };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return null;
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Backup steering files
|
|
355
|
+
* @returns {Promise<string>} Backup directory
|
|
356
|
+
*/
|
|
357
|
+
async backupFiles() {
|
|
358
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
359
|
+
const backupPath = path.join(this.config.backupDir, timestamp);
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
await fs.mkdir(backupPath, { recursive: true });
|
|
363
|
+
|
|
364
|
+
for (const file of this.config.steeringFiles) {
|
|
365
|
+
const sourcePath = path.join(this.config.steeringDir, file);
|
|
366
|
+
const destPath = path.join(backupPath, file);
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const content = await fs.readFile(sourcePath, 'utf-8');
|
|
370
|
+
await fs.writeFile(destPath, content, 'utf-8');
|
|
371
|
+
} catch {
|
|
372
|
+
// Skip files that don't exist
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Also backup project.yml
|
|
377
|
+
try {
|
|
378
|
+
const projectSource = path.join(this.config.steeringDir, this.config.projectFile);
|
|
379
|
+
const projectDest = path.join(backupPath, this.config.projectFile);
|
|
380
|
+
const content = await fs.readFile(projectSource, 'utf-8');
|
|
381
|
+
await fs.writeFile(projectDest, content, 'utf-8');
|
|
382
|
+
} catch {
|
|
383
|
+
// Skip if doesn't exist
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return backupPath;
|
|
387
|
+
} catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Load project.yml file
|
|
394
|
+
* @returns {Promise<Object|null>} Project config
|
|
395
|
+
*/
|
|
396
|
+
async loadProjectFile() {
|
|
397
|
+
try {
|
|
398
|
+
const filePath = path.join(this.config.steeringDir, this.config.projectFile);
|
|
399
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
400
|
+
|
|
401
|
+
// Simple YAML parsing for common fields
|
|
402
|
+
const version = content.match(/version:\s*['"]?([\d.]+)['"]?/)?.[1];
|
|
403
|
+
const name = content.match(/name:\s*['"]?([^'\n]+)['"]?/)?.[1];
|
|
404
|
+
const status = content.match(/status:\s*(\w+)/)?.[1];
|
|
405
|
+
|
|
406
|
+
return { version, name, status };
|
|
407
|
+
} catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Load steering file content
|
|
414
|
+
* @param {string} filename - File name
|
|
415
|
+
* @returns {Promise<string|null>} File content
|
|
416
|
+
*/
|
|
417
|
+
async loadSteeringFile(filename) {
|
|
418
|
+
try {
|
|
419
|
+
const filePath = path.join(this.config.steeringDir, filename);
|
|
420
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
421
|
+
} catch {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Generate sync report
|
|
428
|
+
* @returns {Promise<string>} Markdown report
|
|
429
|
+
*/
|
|
430
|
+
async generateReport() {
|
|
431
|
+
const consistency = await this.checkConsistency();
|
|
432
|
+
const project = await this.loadProjectFile();
|
|
433
|
+
const lines = [];
|
|
434
|
+
|
|
435
|
+
lines.push('# Steering Sync Report');
|
|
436
|
+
lines.push('');
|
|
437
|
+
lines.push(`**Generated:** ${new Date().toISOString()}`);
|
|
438
|
+
lines.push(`**Project Version:** ${project?.version || 'Unknown'}`);
|
|
439
|
+
lines.push('');
|
|
440
|
+
|
|
441
|
+
// Consistency status
|
|
442
|
+
lines.push('## Consistency Check');
|
|
443
|
+
lines.push('');
|
|
444
|
+
if (consistency.consistent) {
|
|
445
|
+
lines.push('✅ All steering files are consistent.');
|
|
446
|
+
} else {
|
|
447
|
+
lines.push(`⚠️ Found ${consistency.issues.length} inconsistency issues:`);
|
|
448
|
+
lines.push('');
|
|
449
|
+
for (const issue of consistency.issues) {
|
|
450
|
+
lines.push(`- **${issue.file}**: ${issue.message}`);
|
|
451
|
+
lines.push(` - Suggestion: ${issue.suggestion}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
lines.push('');
|
|
455
|
+
|
|
456
|
+
// File status
|
|
457
|
+
lines.push('## File Status');
|
|
458
|
+
lines.push('');
|
|
459
|
+
lines.push('| File | Status |');
|
|
460
|
+
lines.push('|------|--------|');
|
|
461
|
+
|
|
462
|
+
for (const file of this.config.steeringFiles) {
|
|
463
|
+
const content = await this.loadSteeringFile(file);
|
|
464
|
+
const status = content ? '✅ Present' : '❌ Missing';
|
|
465
|
+
lines.push(`| ${file} | ${status} |`);
|
|
466
|
+
}
|
|
467
|
+
lines.push('');
|
|
468
|
+
|
|
469
|
+
return lines.join('\n');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
module.exports = { SteeringSync };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Module
|
|
3
|
+
*
|
|
4
|
+
* Requirement: IMP-6.2-003
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { WorkflowDashboard, WORKFLOW_STAGES, STAGE_STATUS } = require('./workflow-dashboard');
|
|
8
|
+
const { TransitionRecorder } = require('./transition-recorder');
|
|
9
|
+
const { SprintPlanner, PRIORITY } = require('./sprint-planner');
|
|
10
|
+
const { SprintReporter } = require('./sprint-reporter');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
WorkflowDashboard,
|
|
14
|
+
TransitionRecorder,
|
|
15
|
+
SprintPlanner,
|
|
16
|
+
SprintReporter,
|
|
17
|
+
WORKFLOW_STAGES,
|
|
18
|
+
STAGE_STATUS,
|
|
19
|
+
PRIORITY
|
|
20
|
+
};
|