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.
@@ -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
+ };