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.
- package/bin/musubi-change.js +623 -10
- package/bin/musubi-orchestrate.js +456 -0
- package/bin/musubi-trace.js +393 -0
- package/package.json +3 -2
- package/src/analyzers/impact-analyzer.js +682 -0
- package/src/integrations/cicd.js +782 -0
- package/src/integrations/documentation.js +740 -0
- package/src/integrations/examples.js +789 -0
- package/src/integrations/index.js +23 -0
- package/src/integrations/platforms.js +929 -0
- package/src/managers/delta-spec.js +484 -0
- package/src/monitoring/incident-manager.js +890 -0
- package/src/monitoring/index.js +633 -0
- package/src/monitoring/observability.js +938 -0
- package/src/monitoring/release-manager.js +622 -0
- package/src/orchestration/index.js +168 -0
- package/src/orchestration/orchestration-engine.js +409 -0
- package/src/orchestration/pattern-registry.js +319 -0
- package/src/orchestration/patterns/auto.js +386 -0
- package/src/orchestration/patterns/group-chat.js +395 -0
- package/src/orchestration/patterns/human-in-loop.js +506 -0
- package/src/orchestration/patterns/nested.js +322 -0
- package/src/orchestration/patterns/sequential.js +278 -0
- package/src/orchestration/patterns/swarm.js +395 -0
- package/src/orchestration/workflow-orchestrator.js +738 -0
- package/src/reporters/coverage-report.js +452 -0
- package/src/reporters/traceability-matrix-report.js +684 -0
- package/src/steering/advanced-validation.js +812 -0
- package/src/steering/auto-updater.js +670 -0
- package/src/steering/index.js +119 -0
- package/src/steering/quality-metrics.js +650 -0
- package/src/steering/template-constraints.js +789 -0
- package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +22 -0
- package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +21 -0
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +90 -28
- package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +32 -0
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +27 -0
- package/src/templates/agents/claude-code/skills/steering/SKILL.md +30 -0
- package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +21 -0
- package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +27 -0
- package/src/templates/agents/codex/AGENTS.md +36 -1
- package/src/templates/agents/cursor/AGENTS.md +36 -1
- package/src/templates/agents/gemini-cli/GEMINI.md +36 -1
- package/src/templates/agents/github-copilot/AGENTS.md +65 -1
- package/src/templates/agents/qwen-code/QWEN.md +36 -1
- package/src/templates/agents/windsurf/AGENTS.md +36 -1
- package/src/templates/shared/delta-spec-template.md +246 -0
- package/src/validators/delta-format.js +474 -0
- package/src/validators/traceability-validator.js +561 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Steering Auto-Update Module
|
|
3
|
+
*
|
|
4
|
+
* Automatically updates steering files based on agent work:
|
|
5
|
+
* - Detects project changes
|
|
6
|
+
* - Updates structure.md, tech.md, product.md
|
|
7
|
+
* - Maintains project.yml consistency
|
|
8
|
+
* - Supports domain-specific custom rules
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { EventEmitter } = require('events');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Steering File Types
|
|
17
|
+
*/
|
|
18
|
+
const SteeringFileType = {
|
|
19
|
+
STRUCTURE: 'structure',
|
|
20
|
+
TECH: 'tech',
|
|
21
|
+
PRODUCT: 'product',
|
|
22
|
+
PROJECT: 'project',
|
|
23
|
+
RULES: 'rules',
|
|
24
|
+
CUSTOM: 'custom'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Update Trigger Types
|
|
29
|
+
*/
|
|
30
|
+
const UpdateTrigger = {
|
|
31
|
+
FILE_ADDED: 'file-added',
|
|
32
|
+
FILE_MODIFIED: 'file-modified',
|
|
33
|
+
FILE_DELETED: 'file-deleted',
|
|
34
|
+
DEPENDENCY_ADDED: 'dependency-added',
|
|
35
|
+
DEPENDENCY_REMOVED: 'dependency-removed',
|
|
36
|
+
CONFIG_CHANGED: 'config-changed',
|
|
37
|
+
MANUAL: 'manual'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Change detection for steering updates
|
|
42
|
+
*/
|
|
43
|
+
class ChangeDetector {
|
|
44
|
+
constructor(options = {}) {
|
|
45
|
+
this.patterns = options.patterns || {
|
|
46
|
+
structure: [
|
|
47
|
+
/^src\//,
|
|
48
|
+
/^lib\//,
|
|
49
|
+
/^packages\//,
|
|
50
|
+
/^components\//
|
|
51
|
+
],
|
|
52
|
+
tech: [
|
|
53
|
+
/package\.json$/,
|
|
54
|
+
/requirements\.txt$/,
|
|
55
|
+
/Gemfile$/,
|
|
56
|
+
/go\.mod$/,
|
|
57
|
+
/Cargo\.toml$/,
|
|
58
|
+
/\.config\.(js|ts|json)$/
|
|
59
|
+
],
|
|
60
|
+
product: [
|
|
61
|
+
/README\.md$/,
|
|
62
|
+
/docs\//,
|
|
63
|
+
/\.env\.example$/
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Detect which steering files need updates based on changed files
|
|
70
|
+
*/
|
|
71
|
+
detectAffectedSteering(changedFiles) {
|
|
72
|
+
const affected = new Set();
|
|
73
|
+
|
|
74
|
+
for (const file of changedFiles) {
|
|
75
|
+
for (const [steeringType, patterns] of Object.entries(this.patterns)) {
|
|
76
|
+
for (const pattern of patterns) {
|
|
77
|
+
if (pattern.test(file)) {
|
|
78
|
+
affected.add(steeringType);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [...affected];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Analyze file changes
|
|
90
|
+
*/
|
|
91
|
+
analyzeChanges(changes) {
|
|
92
|
+
const analysis = {
|
|
93
|
+
addedFiles: [],
|
|
94
|
+
modifiedFiles: [],
|
|
95
|
+
deletedFiles: [],
|
|
96
|
+
addedDependencies: [],
|
|
97
|
+
removedDependencies: [],
|
|
98
|
+
affectedSteering: []
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (const change of changes) {
|
|
102
|
+
if (change.type === 'add') {
|
|
103
|
+
analysis.addedFiles.push(change.path);
|
|
104
|
+
} else if (change.type === 'modify') {
|
|
105
|
+
analysis.modifiedFiles.push(change.path);
|
|
106
|
+
} else if (change.type === 'delete') {
|
|
107
|
+
analysis.deletedFiles.push(change.path);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const allFiles = [
|
|
112
|
+
...analysis.addedFiles,
|
|
113
|
+
...analysis.modifiedFiles,
|
|
114
|
+
...analysis.deletedFiles
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
analysis.affectedSteering = this.detectAffectedSteering(allFiles);
|
|
118
|
+
|
|
119
|
+
return analysis;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Steering file updater
|
|
125
|
+
*/
|
|
126
|
+
class SteeringUpdater {
|
|
127
|
+
constructor(options = {}) {
|
|
128
|
+
this.steeringDir = options.steeringDir || 'steering';
|
|
129
|
+
this.dryRun = options.dryRun || false;
|
|
130
|
+
this.backupEnabled = options.backup !== false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update structure.md based on project analysis
|
|
135
|
+
*/
|
|
136
|
+
generateStructureUpdate(analysis) {
|
|
137
|
+
const updates = [];
|
|
138
|
+
|
|
139
|
+
// Detect new directories
|
|
140
|
+
const newDirs = new Set();
|
|
141
|
+
for (const file of analysis.addedFiles) {
|
|
142
|
+
const dir = path.dirname(file);
|
|
143
|
+
if (dir !== '.') {
|
|
144
|
+
newDirs.add(dir.split('/')[0]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (newDirs.size > 0) {
|
|
149
|
+
updates.push({
|
|
150
|
+
section: 'directories',
|
|
151
|
+
action: 'add',
|
|
152
|
+
content: [...newDirs].map(d => `- \`${d}/\` - [TODO: Add description]`)
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Detect removed directories
|
|
157
|
+
const removedDirs = new Set();
|
|
158
|
+
for (const file of analysis.deletedFiles) {
|
|
159
|
+
const dir = path.dirname(file);
|
|
160
|
+
if (dir !== '.') {
|
|
161
|
+
removedDirs.add(dir.split('/')[0]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (removedDirs.size > 0) {
|
|
166
|
+
updates.push({
|
|
167
|
+
section: 'directories',
|
|
168
|
+
action: 'review',
|
|
169
|
+
content: [...removedDirs].map(d => `- \`${d}/\` may need removal or update`)
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return updates;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Update tech.md based on dependency changes
|
|
178
|
+
*/
|
|
179
|
+
generateTechUpdate(analysis, packageJson = null) {
|
|
180
|
+
const updates = [];
|
|
181
|
+
|
|
182
|
+
if (packageJson) {
|
|
183
|
+
const deps = packageJson.dependencies || {};
|
|
184
|
+
const devDeps = packageJson.devDependencies || {};
|
|
185
|
+
|
|
186
|
+
updates.push({
|
|
187
|
+
section: 'dependencies',
|
|
188
|
+
action: 'sync',
|
|
189
|
+
content: {
|
|
190
|
+
runtime: Object.keys(deps),
|
|
191
|
+
development: Object.keys(devDeps)
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Detect framework
|
|
196
|
+
const frameworks = [];
|
|
197
|
+
if (deps.react || deps['react-dom']) frameworks.push('React');
|
|
198
|
+
if (deps.vue) frameworks.push('Vue.js');
|
|
199
|
+
if (deps.next) frameworks.push('Next.js');
|
|
200
|
+
if (deps.express) frameworks.push('Express');
|
|
201
|
+
if (deps.fastify) frameworks.push('Fastify');
|
|
202
|
+
if (deps.jest || devDeps.jest) frameworks.push('Jest');
|
|
203
|
+
if (deps.typescript || devDeps.typescript) frameworks.push('TypeScript');
|
|
204
|
+
|
|
205
|
+
if (frameworks.length > 0) {
|
|
206
|
+
updates.push({
|
|
207
|
+
section: 'frameworks',
|
|
208
|
+
action: 'update',
|
|
209
|
+
content: frameworks
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return updates;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Update product.md based on README changes
|
|
219
|
+
*/
|
|
220
|
+
generateProductUpdate(analysis, readme = null) {
|
|
221
|
+
const updates = [];
|
|
222
|
+
|
|
223
|
+
if (readme) {
|
|
224
|
+
// Extract title
|
|
225
|
+
const titleMatch = readme.match(/^#\s+(.+)$/m);
|
|
226
|
+
if (titleMatch) {
|
|
227
|
+
updates.push({
|
|
228
|
+
section: 'name',
|
|
229
|
+
action: 'sync',
|
|
230
|
+
content: titleMatch[1]
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Extract description
|
|
235
|
+
const descMatch = readme.match(/^#\s+.+\n\n(.+?)(?:\n\n|$)/s);
|
|
236
|
+
if (descMatch) {
|
|
237
|
+
updates.push({
|
|
238
|
+
section: 'description',
|
|
239
|
+
action: 'sync',
|
|
240
|
+
content: descMatch[1].trim()
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return updates;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Apply updates to a steering file
|
|
250
|
+
*/
|
|
251
|
+
applyUpdates(filePath, updates) {
|
|
252
|
+
const results = [];
|
|
253
|
+
|
|
254
|
+
for (const update of updates) {
|
|
255
|
+
results.push({
|
|
256
|
+
file: filePath,
|
|
257
|
+
section: update.section,
|
|
258
|
+
action: update.action,
|
|
259
|
+
applied: !this.dryRun,
|
|
260
|
+
content: update.content
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Project.yml synchronizer
|
|
270
|
+
*/
|
|
271
|
+
class ProjectYmlSync {
|
|
272
|
+
constructor(options = {}) {
|
|
273
|
+
this.projectYmlPath = options.path || 'steering/project.yml';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Parse project.yml content
|
|
278
|
+
*/
|
|
279
|
+
parse(content) {
|
|
280
|
+
const data = {
|
|
281
|
+
name: '',
|
|
282
|
+
version: '',
|
|
283
|
+
description: '',
|
|
284
|
+
tech_stack: [],
|
|
285
|
+
features: [],
|
|
286
|
+
agents: []
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Simple YAML-like parsing
|
|
290
|
+
const lines = content.split('\n');
|
|
291
|
+
let currentKey = null;
|
|
292
|
+
let inList = false;
|
|
293
|
+
|
|
294
|
+
for (const line of lines) {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
|
|
297
|
+
if (trimmed.startsWith('#') || trimmed === '') continue;
|
|
298
|
+
|
|
299
|
+
const keyMatch = line.match(/^(\w+):\s*(.*)$/);
|
|
300
|
+
if (keyMatch) {
|
|
301
|
+
currentKey = keyMatch[1];
|
|
302
|
+
const value = keyMatch[2].trim();
|
|
303
|
+
|
|
304
|
+
if (value && !value.startsWith('-')) {
|
|
305
|
+
data[currentKey] = value;
|
|
306
|
+
inList = false;
|
|
307
|
+
} else {
|
|
308
|
+
inList = true;
|
|
309
|
+
if (!Array.isArray(data[currentKey])) {
|
|
310
|
+
data[currentKey] = [];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} else if (trimmed.startsWith('-') && currentKey) {
|
|
314
|
+
const item = trimmed.slice(1).trim();
|
|
315
|
+
if (!Array.isArray(data[currentKey])) {
|
|
316
|
+
data[currentKey] = [];
|
|
317
|
+
}
|
|
318
|
+
data[currentKey].push(item);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return data;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Generate project.yml content
|
|
327
|
+
*/
|
|
328
|
+
generate(data) {
|
|
329
|
+
let content = '';
|
|
330
|
+
|
|
331
|
+
content += `# Project Configuration\n`;
|
|
332
|
+
content += `# Auto-generated by MUSUBI Steering Auto-Update\n\n`;
|
|
333
|
+
|
|
334
|
+
if (data.name) content += `name: ${data.name}\n`;
|
|
335
|
+
if (data.version) content += `version: ${data.version}\n`;
|
|
336
|
+
if (data.description) content += `description: ${data.description}\n`;
|
|
337
|
+
|
|
338
|
+
if (data.tech_stack && data.tech_stack.length > 0) {
|
|
339
|
+
content += `\ntech_stack:\n`;
|
|
340
|
+
for (const tech of data.tech_stack) {
|
|
341
|
+
content += ` - ${tech}\n`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (data.features && data.features.length > 0) {
|
|
346
|
+
content += `\nfeatures:\n`;
|
|
347
|
+
for (const feature of data.features) {
|
|
348
|
+
content += ` - ${feature}\n`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (data.agents && data.agents.length > 0) {
|
|
353
|
+
content += `\nagents:\n`;
|
|
354
|
+
for (const agent of data.agents) {
|
|
355
|
+
content += ` - ${agent}\n`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return content;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Sync project.yml with package.json
|
|
364
|
+
*/
|
|
365
|
+
syncWithPackageJson(projectData, packageJson) {
|
|
366
|
+
const updated = { ...projectData };
|
|
367
|
+
|
|
368
|
+
if (packageJson.name) updated.name = packageJson.name;
|
|
369
|
+
if (packageJson.version) updated.version = packageJson.version;
|
|
370
|
+
if (packageJson.description) updated.description = packageJson.description;
|
|
371
|
+
|
|
372
|
+
// Sync tech stack from dependencies
|
|
373
|
+
const techStack = new Set(updated.tech_stack || []);
|
|
374
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
375
|
+
|
|
376
|
+
const techMapping = {
|
|
377
|
+
'react': 'React',
|
|
378
|
+
'vue': 'Vue.js',
|
|
379
|
+
'angular': 'Angular',
|
|
380
|
+
'next': 'Next.js',
|
|
381
|
+
'express': 'Express',
|
|
382
|
+
'fastify': 'Fastify',
|
|
383
|
+
'typescript': 'TypeScript',
|
|
384
|
+
'jest': 'Jest',
|
|
385
|
+
'mocha': 'Mocha',
|
|
386
|
+
'webpack': 'Webpack',
|
|
387
|
+
'vite': 'Vite',
|
|
388
|
+
'tailwindcss': 'Tailwind CSS',
|
|
389
|
+
'prisma': 'Prisma',
|
|
390
|
+
'mongoose': 'Mongoose'
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
for (const [dep, tech] of Object.entries(techMapping)) {
|
|
394
|
+
if (deps[dep]) {
|
|
395
|
+
techStack.add(tech);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
updated.tech_stack = [...techStack];
|
|
400
|
+
|
|
401
|
+
return updated;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Custom steering rules
|
|
407
|
+
*/
|
|
408
|
+
class CustomSteeringRules {
|
|
409
|
+
constructor(options = {}) {
|
|
410
|
+
this.rulesDir = options.rulesDir || 'steering/custom';
|
|
411
|
+
this.rules = new Map();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Load custom rules from directory
|
|
416
|
+
*/
|
|
417
|
+
loadRules(content) {
|
|
418
|
+
// Parse custom rules from markdown format
|
|
419
|
+
const rules = [];
|
|
420
|
+
const rulePattern = /## Rule: (.+)\n([\s\S]*?)(?=## Rule:|$)/g;
|
|
421
|
+
|
|
422
|
+
let match;
|
|
423
|
+
while ((match = rulePattern.exec(content)) !== null) {
|
|
424
|
+
const name = match[1].trim();
|
|
425
|
+
const body = match[2].trim();
|
|
426
|
+
|
|
427
|
+
const rule = {
|
|
428
|
+
name,
|
|
429
|
+
pattern: null,
|
|
430
|
+
action: 'warn',
|
|
431
|
+
message: ''
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
// Parse pattern
|
|
435
|
+
const patternMatch = body.match(/Pattern:\s*`([^`]+)`/);
|
|
436
|
+
if (patternMatch) {
|
|
437
|
+
rule.pattern = patternMatch[1];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Parse action
|
|
441
|
+
const actionMatch = body.match(/Action:\s*(warn|error|update)/);
|
|
442
|
+
if (actionMatch) {
|
|
443
|
+
rule.action = actionMatch[1];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Parse message
|
|
447
|
+
const messageMatch = body.match(/Message:\s*(.+)/);
|
|
448
|
+
if (messageMatch) {
|
|
449
|
+
rule.message = messageMatch[1];
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
rules.push(rule);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return rules;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Register a custom rule
|
|
460
|
+
*/
|
|
461
|
+
registerRule(rule) {
|
|
462
|
+
this.rules.set(rule.name, rule);
|
|
463
|
+
return this;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Apply custom rules to changes
|
|
468
|
+
*/
|
|
469
|
+
applyRules(changes) {
|
|
470
|
+
const results = [];
|
|
471
|
+
|
|
472
|
+
for (const [name, rule] of this.rules) {
|
|
473
|
+
for (const change of changes) {
|
|
474
|
+
if (rule.pattern && new RegExp(rule.pattern).test(change.path)) {
|
|
475
|
+
results.push({
|
|
476
|
+
rule: name,
|
|
477
|
+
file: change.path,
|
|
478
|
+
action: rule.action,
|
|
479
|
+
message: rule.message
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return results;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Steering Auto-Updater
|
|
491
|
+
*/
|
|
492
|
+
class SteeringAutoUpdater extends EventEmitter {
|
|
493
|
+
constructor(options = {}) {
|
|
494
|
+
super();
|
|
495
|
+
this.projectRoot = options.projectRoot || process.cwd();
|
|
496
|
+
this.steeringDir = options.steeringDir || 'steering';
|
|
497
|
+
this.dryRun = options.dryRun || false;
|
|
498
|
+
|
|
499
|
+
this.detector = new ChangeDetector(options.detectorOptions);
|
|
500
|
+
this.updater = new SteeringUpdater({
|
|
501
|
+
steeringDir: this.steeringDir,
|
|
502
|
+
dryRun: this.dryRun,
|
|
503
|
+
backup: options.backup
|
|
504
|
+
});
|
|
505
|
+
this.projectSync = new ProjectYmlSync({
|
|
506
|
+
path: path.join(this.steeringDir, 'project.yml')
|
|
507
|
+
});
|
|
508
|
+
this.customRules = new CustomSteeringRules({
|
|
509
|
+
rulesDir: path.join(this.steeringDir, 'custom')
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
this.updateHistory = [];
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Analyze project and generate update suggestions
|
|
517
|
+
*/
|
|
518
|
+
analyze(changes, context = {}) {
|
|
519
|
+
const analysis = this.detector.analyzeChanges(changes);
|
|
520
|
+
|
|
521
|
+
const suggestions = {
|
|
522
|
+
structure: [],
|
|
523
|
+
tech: [],
|
|
524
|
+
product: [],
|
|
525
|
+
project: [],
|
|
526
|
+
custom: []
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// Generate structure updates
|
|
530
|
+
if (analysis.affectedSteering.includes('structure')) {
|
|
531
|
+
suggestions.structure = this.updater.generateStructureUpdate(analysis);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Generate tech updates
|
|
535
|
+
if (analysis.affectedSteering.includes('tech') || context.packageJson) {
|
|
536
|
+
suggestions.tech = this.updater.generateTechUpdate(analysis, context.packageJson);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Generate product updates
|
|
540
|
+
if (analysis.affectedSteering.includes('product') || context.readme) {
|
|
541
|
+
suggestions.product = this.updater.generateProductUpdate(analysis, context.readme);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Apply custom rules
|
|
545
|
+
suggestions.custom = this.customRules.applyRules(changes);
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
analysis,
|
|
549
|
+
suggestions,
|
|
550
|
+
affectedFiles: analysis.affectedSteering.map(type =>
|
|
551
|
+
path.join(this.steeringDir, `${type}.md`)
|
|
552
|
+
)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Apply updates based on suggestions
|
|
558
|
+
*/
|
|
559
|
+
applyUpdates(suggestions) {
|
|
560
|
+
const results = {
|
|
561
|
+
applied: [],
|
|
562
|
+
skipped: [],
|
|
563
|
+
errors: []
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
for (const [type, updates] of Object.entries(suggestions)) {
|
|
567
|
+
if (updates.length === 0) continue;
|
|
568
|
+
|
|
569
|
+
try {
|
|
570
|
+
const filePath = path.join(this.steeringDir, `${type}.md`);
|
|
571
|
+
const applied = this.updater.applyUpdates(filePath, updates);
|
|
572
|
+
results.applied.push(...applied);
|
|
573
|
+
|
|
574
|
+
this.emit('updated', { type, updates: applied });
|
|
575
|
+
} catch (error) {
|
|
576
|
+
results.errors.push({
|
|
577
|
+
type,
|
|
578
|
+
error: error.message
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Record in history
|
|
584
|
+
this.updateHistory.push({
|
|
585
|
+
timestamp: new Date(),
|
|
586
|
+
results
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
return results;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Sync project.yml with package.json
|
|
594
|
+
*/
|
|
595
|
+
syncProjectYml(projectData, packageJson) {
|
|
596
|
+
const updated = this.projectSync.syncWithPackageJson(projectData, packageJson);
|
|
597
|
+
|
|
598
|
+
this.emit('projectSynced', {
|
|
599
|
+
before: projectData,
|
|
600
|
+
after: updated
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
return updated;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Register a custom rule
|
|
608
|
+
*/
|
|
609
|
+
registerCustomRule(rule) {
|
|
610
|
+
this.customRules.registerRule(rule);
|
|
611
|
+
return this;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Get update history
|
|
616
|
+
*/
|
|
617
|
+
getHistory() {
|
|
618
|
+
return [...this.updateHistory];
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Validate steering consistency
|
|
623
|
+
*/
|
|
624
|
+
validateConsistency(steeringFiles) {
|
|
625
|
+
const issues = [];
|
|
626
|
+
|
|
627
|
+
// Check for missing files
|
|
628
|
+
const requiredFiles = ['structure.md', 'tech.md', 'product.md'];
|
|
629
|
+
for (const file of requiredFiles) {
|
|
630
|
+
if (!steeringFiles.includes(file)) {
|
|
631
|
+
issues.push({
|
|
632
|
+
type: 'missing',
|
|
633
|
+
file,
|
|
634
|
+
severity: 'error',
|
|
635
|
+
message: `Required steering file ${file} is missing`
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Check for empty sections (would require file content)
|
|
641
|
+
|
|
642
|
+
return {
|
|
643
|
+
valid: issues.filter(i => i.severity === 'error').length === 0,
|
|
644
|
+
issues
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Create steering auto-updater
|
|
651
|
+
*/
|
|
652
|
+
function createSteeringAutoUpdater(options = {}) {
|
|
653
|
+
return new SteeringAutoUpdater(options);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
module.exports = {
|
|
657
|
+
// Classes
|
|
658
|
+
ChangeDetector,
|
|
659
|
+
SteeringUpdater,
|
|
660
|
+
ProjectYmlSync,
|
|
661
|
+
CustomSteeringRules,
|
|
662
|
+
SteeringAutoUpdater,
|
|
663
|
+
|
|
664
|
+
// Constants
|
|
665
|
+
SteeringFileType,
|
|
666
|
+
UpdateTrigger,
|
|
667
|
+
|
|
668
|
+
// Factory
|
|
669
|
+
createSteeringAutoUpdater
|
|
670
|
+
};
|