musubi-sdd 3.0.1 → 3.5.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.
Files changed (49) hide show
  1. package/bin/musubi-change.js +623 -10
  2. package/bin/musubi-orchestrate.js +456 -0
  3. package/bin/musubi-trace.js +393 -0
  4. package/package.json +3 -2
  5. package/src/analyzers/impact-analyzer.js +682 -0
  6. package/src/integrations/cicd.js +782 -0
  7. package/src/integrations/documentation.js +740 -0
  8. package/src/integrations/examples.js +789 -0
  9. package/src/integrations/index.js +23 -0
  10. package/src/integrations/platforms.js +929 -0
  11. package/src/managers/delta-spec.js +484 -0
  12. package/src/monitoring/incident-manager.js +890 -0
  13. package/src/monitoring/index.js +633 -0
  14. package/src/monitoring/observability.js +938 -0
  15. package/src/monitoring/release-manager.js +622 -0
  16. package/src/orchestration/index.js +168 -0
  17. package/src/orchestration/orchestration-engine.js +409 -0
  18. package/src/orchestration/pattern-registry.js +319 -0
  19. package/src/orchestration/patterns/auto.js +386 -0
  20. package/src/orchestration/patterns/group-chat.js +395 -0
  21. package/src/orchestration/patterns/human-in-loop.js +506 -0
  22. package/src/orchestration/patterns/nested.js +322 -0
  23. package/src/orchestration/patterns/sequential.js +278 -0
  24. package/src/orchestration/patterns/swarm.js +395 -0
  25. package/src/orchestration/workflow-orchestrator.js +738 -0
  26. package/src/reporters/coverage-report.js +452 -0
  27. package/src/reporters/traceability-matrix-report.js +684 -0
  28. package/src/steering/advanced-validation.js +812 -0
  29. package/src/steering/auto-updater.js +670 -0
  30. package/src/steering/index.js +119 -0
  31. package/src/steering/quality-metrics.js +650 -0
  32. package/src/steering/template-constraints.js +789 -0
  33. package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +22 -0
  34. package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +21 -0
  35. package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +90 -28
  36. package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +32 -0
  37. package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +27 -0
  38. package/src/templates/agents/claude-code/skills/steering/SKILL.md +30 -0
  39. package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +21 -0
  40. package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +27 -0
  41. package/src/templates/agents/codex/AGENTS.md +36 -1
  42. package/src/templates/agents/cursor/AGENTS.md +36 -1
  43. package/src/templates/agents/gemini-cli/GEMINI.md +36 -1
  44. package/src/templates/agents/github-copilot/AGENTS.md +65 -1
  45. package/src/templates/agents/qwen-code/QWEN.md +36 -1
  46. package/src/templates/agents/windsurf/AGENTS.md +36 -1
  47. package/src/templates/shared/delta-spec-template.md +246 -0
  48. package/src/validators/delta-format.js +474 -0
  49. package/src/validators/traceability-validator.js +561 -0
@@ -0,0 +1,484 @@
1
+ /**
2
+ * Delta Specification Manager
3
+ * Handles ADDED/MODIFIED/REMOVED/RENAMED delta specifications for brownfield projects
4
+ *
5
+ * @module managers/delta-spec
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Delta operation types
13
+ */
14
+ const DeltaType = {
15
+ ADDED: 'ADDED',
16
+ MODIFIED: 'MODIFIED',
17
+ REMOVED: 'REMOVED',
18
+ RENAMED: 'RENAMED'
19
+ };
20
+
21
+ /**
22
+ * Delta specification structure
23
+ * @typedef {Object} DeltaSpec
24
+ * @property {string} id - Unique delta ID (e.g., DELTA-AUTH-001)
25
+ * @property {string} type - Delta type (ADDED, MODIFIED, REMOVED, RENAMED)
26
+ * @property {string} target - Target requirement/component ID
27
+ * @property {string} description - Change description
28
+ * @property {string} rationale - Reason for change
29
+ * @property {string[]} impactedAreas - Areas affected by change
30
+ * @property {Object} before - Previous state (for MODIFIED/RENAMED)
31
+ * @property {Object} after - New state (for ADDED/MODIFIED/RENAMED)
32
+ * @property {string} status - Status (proposed, approved, implemented, archived)
33
+ * @property {string} createdAt - Creation timestamp
34
+ * @property {string} updatedAt - Last update timestamp
35
+ */
36
+
37
+ class DeltaSpecManager {
38
+ constructor(projectRoot = process.cwd()) {
39
+ this.projectRoot = projectRoot;
40
+ this.changesDir = path.join(projectRoot, 'storage', 'changes');
41
+ this.specsDir = path.join(projectRoot, 'storage', 'specs');
42
+ this.archiveDir = path.join(projectRoot, 'storage', 'archive');
43
+ }
44
+
45
+ /**
46
+ * Initialize delta spec directories
47
+ */
48
+ init() {
49
+ const dirs = [this.changesDir, this.specsDir, this.archiveDir];
50
+ dirs.forEach(dir => {
51
+ if (!fs.existsSync(dir)) {
52
+ fs.mkdirSync(dir, { recursive: true });
53
+ }
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Create a new delta specification
59
+ * @param {Object} options - Delta spec options
60
+ * @returns {DeltaSpec} Created delta spec
61
+ */
62
+ create(options) {
63
+ const {
64
+ id,
65
+ type,
66
+ target,
67
+ description,
68
+ rationale,
69
+ impactedAreas = [],
70
+ before = null,
71
+ after = null
72
+ } = options;
73
+
74
+ // Validate required fields
75
+ if (!id) throw new Error('Delta ID is required');
76
+ if (!type || !Object.values(DeltaType).includes(type)) {
77
+ throw new Error(`Invalid delta type. Must be one of: ${Object.values(DeltaType).join(', ')}`);
78
+ }
79
+ if (!target) throw new Error('Target is required');
80
+ if (!description) throw new Error('Description is required');
81
+
82
+ // Validate type-specific requirements
83
+ if (type === DeltaType.MODIFIED && !before) {
84
+ throw new Error('MODIFIED delta requires "before" state');
85
+ }
86
+ if (type === DeltaType.RENAMED && (!before || !after)) {
87
+ throw new Error('RENAMED delta requires both "before" and "after" states');
88
+ }
89
+
90
+ const now = new Date().toISOString();
91
+ const deltaSpec = {
92
+ id,
93
+ type,
94
+ target,
95
+ description,
96
+ rationale: rationale || '',
97
+ impactedAreas,
98
+ before,
99
+ after,
100
+ status: 'proposed',
101
+ createdAt: now,
102
+ updatedAt: now
103
+ };
104
+
105
+ // Save delta spec
106
+ this.save(deltaSpec);
107
+
108
+ return deltaSpec;
109
+ }
110
+
111
+ /**
112
+ * Save delta spec to file
113
+ * @param {DeltaSpec} deltaSpec - Delta spec to save
114
+ */
115
+ save(deltaSpec) {
116
+ this.init();
117
+
118
+ const changeDir = path.join(this.changesDir, deltaSpec.id);
119
+ if (!fs.existsSync(changeDir)) {
120
+ fs.mkdirSync(changeDir, { recursive: true });
121
+ }
122
+
123
+ const content = this.formatDeltaSpec(deltaSpec);
124
+ const filePath = path.join(changeDir, 'delta.md');
125
+ fs.writeFileSync(filePath, content, 'utf-8');
126
+
127
+ // Also save as JSON for programmatic access
128
+ const jsonPath = path.join(changeDir, 'delta.json');
129
+ fs.writeFileSync(jsonPath, JSON.stringify(deltaSpec, null, 2), 'utf-8');
130
+ }
131
+
132
+ /**
133
+ * Format delta spec as markdown
134
+ * @param {DeltaSpec} deltaSpec - Delta spec to format
135
+ * @returns {string} Markdown content
136
+ */
137
+ formatDeltaSpec(deltaSpec) {
138
+ const lines = [
139
+ `# Delta Specification: ${deltaSpec.id}`,
140
+ '',
141
+ `**Type**: ${deltaSpec.type}`,
142
+ `**Target**: ${deltaSpec.target}`,
143
+ `**Status**: ${deltaSpec.status}`,
144
+ `**Created**: ${deltaSpec.createdAt}`,
145
+ `**Updated**: ${deltaSpec.updatedAt}`,
146
+ '',
147
+ '## Description',
148
+ '',
149
+ deltaSpec.description,
150
+ '',
151
+ ];
152
+
153
+ if (deltaSpec.rationale) {
154
+ lines.push('## Rationale', '', deltaSpec.rationale, '');
155
+ }
156
+
157
+ if (deltaSpec.impactedAreas.length > 0) {
158
+ lines.push('## Impacted Areas', '');
159
+ deltaSpec.impactedAreas.forEach(area => {
160
+ lines.push(`- ${area}`);
161
+ });
162
+ lines.push('');
163
+ }
164
+
165
+ if (deltaSpec.before) {
166
+ lines.push('## Before State', '', '```');
167
+ if (typeof deltaSpec.before === 'object') {
168
+ lines.push(JSON.stringify(deltaSpec.before, null, 2));
169
+ } else {
170
+ lines.push(String(deltaSpec.before));
171
+ }
172
+ lines.push('```', '');
173
+ }
174
+
175
+ if (deltaSpec.after) {
176
+ lines.push('## After State', '', '```');
177
+ if (typeof deltaSpec.after === 'object') {
178
+ lines.push(JSON.stringify(deltaSpec.after, null, 2));
179
+ } else {
180
+ lines.push(String(deltaSpec.after));
181
+ }
182
+ lines.push('```', '');
183
+ }
184
+
185
+ return lines.join('\n');
186
+ }
187
+
188
+ /**
189
+ * Load delta spec from file
190
+ * @param {string} id - Delta ID
191
+ * @returns {DeltaSpec|null} Loaded delta spec or null
192
+ */
193
+ load(id) {
194
+ const jsonPath = path.join(this.changesDir, id, 'delta.json');
195
+ if (!fs.existsSync(jsonPath)) {
196
+ return null;
197
+ }
198
+ const content = fs.readFileSync(jsonPath, 'utf-8');
199
+ return JSON.parse(content);
200
+ }
201
+
202
+ /**
203
+ * List all delta specs
204
+ * @param {Object} options - Filter options
205
+ * @returns {DeltaSpec[]} List of delta specs
206
+ */
207
+ list(options = {}) {
208
+ const { status, type } = options;
209
+
210
+ if (!fs.existsSync(this.changesDir)) {
211
+ return [];
212
+ }
213
+
214
+ const entries = fs.readdirSync(this.changesDir, { withFileTypes: true });
215
+ const deltas = [];
216
+
217
+ for (const entry of entries) {
218
+ if (entry.isDirectory()) {
219
+ const delta = this.load(entry.name);
220
+ if (delta) {
221
+ // Apply filters
222
+ if (status && delta.status !== status) continue;
223
+ if (type && delta.type !== type) continue;
224
+ deltas.push(delta);
225
+ }
226
+ }
227
+ }
228
+
229
+ return deltas.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
230
+ }
231
+
232
+ /**
233
+ * Update delta spec status
234
+ * @param {string} id - Delta ID
235
+ * @param {string} status - New status
236
+ * @returns {DeltaSpec} Updated delta spec
237
+ */
238
+ updateStatus(id, status) {
239
+ const validStatuses = ['proposed', 'approved', 'rejected', 'implemented', 'archived'];
240
+ if (!validStatuses.includes(status)) {
241
+ throw new Error(`Invalid status. Must be one of: ${validStatuses.join(', ')}`);
242
+ }
243
+
244
+ const delta = this.load(id);
245
+ if (!delta) {
246
+ throw new Error(`Delta not found: ${id}`);
247
+ }
248
+
249
+ delta.status = status;
250
+ delta.updatedAt = new Date().toISOString();
251
+ this.save(delta);
252
+
253
+ return delta;
254
+ }
255
+
256
+ /**
257
+ * Archive a completed delta spec
258
+ * Merges delta into specs and moves to archive
259
+ * @param {string} id - Delta ID
260
+ * @returns {Object} Archive result
261
+ */
262
+ archive(id) {
263
+ const delta = this.load(id);
264
+ if (!delta) {
265
+ throw new Error(`Delta not found: ${id}`);
266
+ }
267
+
268
+ if (delta.status !== 'implemented') {
269
+ throw new Error('Only implemented deltas can be archived');
270
+ }
271
+
272
+ // Create archive entry
273
+ this.init();
274
+ const archiveFile = path.join(this.archiveDir, `${id}.json`);
275
+ delta.status = 'archived';
276
+ delta.archivedAt = new Date().toISOString();
277
+ fs.writeFileSync(archiveFile, JSON.stringify(delta, null, 2), 'utf-8');
278
+
279
+ // Remove from active changes
280
+ const changeDir = path.join(this.changesDir, id);
281
+ if (fs.existsSync(changeDir)) {
282
+ fs.rmSync(changeDir, { recursive: true });
283
+ }
284
+
285
+ return {
286
+ archived: true,
287
+ id,
288
+ archivePath: archiveFile
289
+ };
290
+ }
291
+
292
+ /**
293
+ * Parse delta markers from markdown content
294
+ * @param {string} content - Markdown content
295
+ * @returns {Object[]} Parsed delta markers
296
+ */
297
+ parseDeltas(content) {
298
+ const deltaPattern = /\[([A-Z]+)\]\s*([A-Z]+-[A-Z0-9-]+):\s*(.+)/g;
299
+ const deltas = [];
300
+ let match;
301
+
302
+ while ((match = deltaPattern.exec(content)) !== null) {
303
+ const [, type, id, description] = match;
304
+ if (Object.values(DeltaType).includes(type)) {
305
+ deltas.push({
306
+ type,
307
+ id,
308
+ description: description.trim(),
309
+ line: content.substring(0, match.index).split('\n').length
310
+ });
311
+ }
312
+ }
313
+
314
+ return deltas;
315
+ }
316
+
317
+ /**
318
+ * Validate delta spec format
319
+ * @param {DeltaSpec} deltaSpec - Delta spec to validate
320
+ * @returns {Object} Validation result
321
+ */
322
+ validate(deltaSpec) {
323
+ const errors = [];
324
+ const warnings = [];
325
+
326
+ // Required fields
327
+ if (!deltaSpec.id) errors.push('Missing delta ID');
328
+ if (!deltaSpec.type) errors.push('Missing delta type');
329
+ if (!deltaSpec.target) errors.push('Missing target');
330
+ if (!deltaSpec.description) errors.push('Missing description');
331
+
332
+ // ID format
333
+ if (deltaSpec.id && !/^[A-Z]+-[A-Z0-9-]+$/.test(deltaSpec.id)) {
334
+ errors.push('Invalid ID format. Expected: PREFIX-IDENTIFIER (e.g., DELTA-AUTH-001)');
335
+ }
336
+
337
+ // Type validation
338
+ if (deltaSpec.type && !Object.values(DeltaType).includes(deltaSpec.type)) {
339
+ errors.push(`Invalid type: ${deltaSpec.type}`);
340
+ }
341
+
342
+ // Type-specific validation
343
+ if (deltaSpec.type === DeltaType.MODIFIED) {
344
+ if (!deltaSpec.before) warnings.push('MODIFIED delta should have "before" state');
345
+ if (!deltaSpec.after) warnings.push('MODIFIED delta should have "after" state');
346
+ }
347
+
348
+ if (deltaSpec.type === DeltaType.RENAMED) {
349
+ if (!deltaSpec.before) errors.push('RENAMED delta requires "before" state');
350
+ if (!deltaSpec.after) errors.push('RENAMED delta requires "after" state');
351
+ }
352
+
353
+ // Rationale recommended
354
+ if (!deltaSpec.rationale) {
355
+ warnings.push('Rationale is recommended for traceability');
356
+ }
357
+
358
+ // Impacted areas recommended
359
+ if (!deltaSpec.impactedAreas || deltaSpec.impactedAreas.length === 0) {
360
+ warnings.push('Impacted areas should be specified');
361
+ }
362
+
363
+ return {
364
+ valid: errors.length === 0,
365
+ errors,
366
+ warnings
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Generate impact report for a delta
372
+ * @param {string} id - Delta ID
373
+ * @returns {Object} Impact report
374
+ */
375
+ generateImpactReport(id) {
376
+ const delta = this.load(id);
377
+ if (!delta) {
378
+ throw new Error(`Delta not found: ${id}`);
379
+ }
380
+
381
+ return {
382
+ id: delta.id,
383
+ type: delta.type,
384
+ target: delta.target,
385
+ description: delta.description,
386
+ impactedAreas: delta.impactedAreas,
387
+ status: delta.status,
388
+ affectedFiles: this.findAffectedFiles(delta),
389
+ recommendations: this.generateRecommendations(delta)
390
+ };
391
+ }
392
+
393
+ /**
394
+ * Find files affected by a delta
395
+ * @param {DeltaSpec} delta - Delta spec
396
+ * @returns {string[]} List of affected files
397
+ */
398
+ findAffectedFiles(delta) {
399
+ // This would scan the codebase for references to the target
400
+ // Simplified implementation for now
401
+ const affected = [];
402
+ const targetPattern = new RegExp(delta.target.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
403
+
404
+ const scanDir = (dir) => {
405
+ if (!fs.existsSync(dir)) return;
406
+
407
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
408
+ for (const entry of entries) {
409
+ const fullPath = path.join(dir, entry.name);
410
+
411
+ // Skip node_modules and hidden directories
412
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
413
+
414
+ if (entry.isDirectory()) {
415
+ scanDir(fullPath);
416
+ } else if (entry.isFile() && /\.(js|ts|md|json)$/.test(entry.name)) {
417
+ try {
418
+ const content = fs.readFileSync(fullPath, 'utf-8');
419
+ if (targetPattern.test(content)) {
420
+ affected.push(path.relative(this.projectRoot, fullPath));
421
+ }
422
+ } catch (e) {
423
+ // Skip files that can't be read
424
+ }
425
+ }
426
+ }
427
+ };
428
+
429
+ scanDir(path.join(this.projectRoot, 'src'));
430
+ scanDir(path.join(this.projectRoot, 'storage'));
431
+
432
+ return affected;
433
+ }
434
+
435
+ /**
436
+ * Generate recommendations for a delta
437
+ * @param {DeltaSpec} delta - Delta spec
438
+ * @returns {string[]} Recommendations
439
+ */
440
+ generateRecommendations(delta) {
441
+ const recommendations = [];
442
+
443
+ switch (delta.type) {
444
+ case DeltaType.ADDED:
445
+ recommendations.push('Create new requirement document');
446
+ recommendations.push('Update traceability matrix');
447
+ recommendations.push('Add test cases for new functionality');
448
+ break;
449
+
450
+ case DeltaType.MODIFIED:
451
+ recommendations.push('Review existing tests for compatibility');
452
+ recommendations.push('Update design documents if architecture affected');
453
+ recommendations.push('Check for breaking changes in APIs');
454
+ break;
455
+
456
+ case DeltaType.REMOVED:
457
+ recommendations.push('Remove obsolete test cases');
458
+ recommendations.push('Update documentation to reflect removal');
459
+ recommendations.push('Check for orphaned code dependencies');
460
+ break;
461
+
462
+ case DeltaType.RENAMED:
463
+ recommendations.push('Update all references to renamed entity');
464
+ recommendations.push('Update import statements');
465
+ recommendations.push('Update documentation and comments');
466
+ break;
467
+ }
468
+
469
+ if (delta.impactedAreas.includes('api')) {
470
+ recommendations.push('Consider API versioning for backward compatibility');
471
+ }
472
+
473
+ if (delta.impactedAreas.includes('database')) {
474
+ recommendations.push('Create database migration script');
475
+ }
476
+
477
+ return recommendations;
478
+ }
479
+ }
480
+
481
+ module.exports = {
482
+ DeltaSpecManager,
483
+ DeltaType
484
+ };