musubi-sdd 3.10.0 → 5.1.0
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.md +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file steering-auto-update.js
|
|
3
|
+
* @description Automatic steering file update engine
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*
|
|
6
|
+
* Part of MUSUBI v5.0.0 - Phase 5 Advanced Features
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { EventEmitter } = require('events');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Update trigger types
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
const TRIGGER = {
|
|
20
|
+
AGENT_WORK: 'agent-work',
|
|
21
|
+
CODE_CHANGE: 'code-change',
|
|
22
|
+
DEPENDENCY_UPDATE: 'dependency-update',
|
|
23
|
+
CONFIG_CHANGE: 'config-change',
|
|
24
|
+
MANUAL: 'manual',
|
|
25
|
+
SCHEDULED: 'scheduled'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Steering file types
|
|
30
|
+
* @enum {string}
|
|
31
|
+
*/
|
|
32
|
+
const STEERING_TYPE = {
|
|
33
|
+
STRUCTURE: 'structure',
|
|
34
|
+
TECH: 'tech',
|
|
35
|
+
PRODUCT: 'product',
|
|
36
|
+
RULES: 'rules',
|
|
37
|
+
CUSTOM: 'custom'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} UpdateRule
|
|
42
|
+
* @property {string} id - Rule identifier
|
|
43
|
+
* @property {string} name - Rule name
|
|
44
|
+
* @property {string} trigger - What triggers this update
|
|
45
|
+
* @property {string} target - Target steering file
|
|
46
|
+
* @property {Function} condition - Condition to check
|
|
47
|
+
* @property {Function} update - Update function
|
|
48
|
+
* @property {number} [priority=0] - Rule priority
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {Object} UpdateResult
|
|
53
|
+
* @property {string} id - Update identifier
|
|
54
|
+
* @property {boolean} success - Whether update succeeded
|
|
55
|
+
* @property {string} file - Updated file path
|
|
56
|
+
* @property {string} trigger - What triggered the update
|
|
57
|
+
* @property {string[]} changes - List of changes made
|
|
58
|
+
* @property {number} timestamp - Update timestamp
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {Object} SteeringAutoUpdateOptions
|
|
63
|
+
* @property {string} [steeringPath='steering'] - Path to steering directory
|
|
64
|
+
* @property {boolean} [autoSave=true] - Auto-save changes
|
|
65
|
+
* @property {boolean} [backup=true] - Create backups before updates
|
|
66
|
+
* @property {Object} [rules={}] - Custom update rules
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Default update rules
|
|
71
|
+
*/
|
|
72
|
+
const DEFAULT_RULES = [
|
|
73
|
+
{
|
|
74
|
+
id: 'tech-deps-update',
|
|
75
|
+
name: 'Update tech.md on dependency changes',
|
|
76
|
+
trigger: TRIGGER.DEPENDENCY_UPDATE,
|
|
77
|
+
target: STEERING_TYPE.TECH,
|
|
78
|
+
priority: 10,
|
|
79
|
+
condition: (context) => context.packageJsonChanged,
|
|
80
|
+
update: async (steering, context) => {
|
|
81
|
+
const changes = [];
|
|
82
|
+
|
|
83
|
+
if (context.newDependencies?.length > 0) {
|
|
84
|
+
changes.push(`Added dependencies: ${context.newDependencies.join(', ')}`);
|
|
85
|
+
}
|
|
86
|
+
if (context.removedDependencies?.length > 0) {
|
|
87
|
+
changes.push(`Removed dependencies: ${context.removedDependencies.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
if (context.updatedDependencies?.length > 0) {
|
|
90
|
+
changes.push(`Updated dependencies: ${context.updatedDependencies.join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { section: 'dependencies', changes };
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'structure-dirs-update',
|
|
98
|
+
name: 'Update structure.md on new directories',
|
|
99
|
+
trigger: TRIGGER.CODE_CHANGE,
|
|
100
|
+
target: STEERING_TYPE.STRUCTURE,
|
|
101
|
+
priority: 5,
|
|
102
|
+
condition: (context) => context.newDirectories?.length > 0,
|
|
103
|
+
update: async (steering, context) => {
|
|
104
|
+
const changes = context.newDirectories.map(dir => `Added directory: ${dir}`);
|
|
105
|
+
return { section: 'directories', changes };
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'structure-files-update',
|
|
110
|
+
name: 'Update structure.md on significant file changes',
|
|
111
|
+
trigger: TRIGGER.CODE_CHANGE,
|
|
112
|
+
target: STEERING_TYPE.STRUCTURE,
|
|
113
|
+
priority: 4,
|
|
114
|
+
condition: (context) => context.significantFileChanges,
|
|
115
|
+
update: async (steering, context) => {
|
|
116
|
+
const changes = [];
|
|
117
|
+
if (context.newEntryPoints?.length > 0) {
|
|
118
|
+
changes.push(`New entry points: ${context.newEntryPoints.join(', ')}`);
|
|
119
|
+
}
|
|
120
|
+
if (context.newModules?.length > 0) {
|
|
121
|
+
changes.push(`New modules: ${context.newModules.join(', ')}`);
|
|
122
|
+
}
|
|
123
|
+
return { section: 'files', changes };
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'product-features-update',
|
|
128
|
+
name: 'Update product.md on feature completion',
|
|
129
|
+
trigger: TRIGGER.AGENT_WORK,
|
|
130
|
+
target: STEERING_TYPE.PRODUCT,
|
|
131
|
+
priority: 8,
|
|
132
|
+
condition: (context) => context.featureCompleted,
|
|
133
|
+
update: async (steering, context) => {
|
|
134
|
+
const changes = [`Completed feature: ${context.featureName}`];
|
|
135
|
+
if (context.featureDescription) {
|
|
136
|
+
changes.push(`Description: ${context.featureDescription}`);
|
|
137
|
+
}
|
|
138
|
+
return { section: 'features', changes };
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'rules-patterns-update',
|
|
143
|
+
name: 'Update rules when new patterns detected',
|
|
144
|
+
trigger: TRIGGER.AGENT_WORK,
|
|
145
|
+
target: STEERING_TYPE.RULES,
|
|
146
|
+
priority: 3,
|
|
147
|
+
condition: (context) => context.newPatterns?.length > 0,
|
|
148
|
+
update: async (steering, context) => {
|
|
149
|
+
const changes = context.newPatterns.map(p => `New pattern: ${p}`);
|
|
150
|
+
return { section: 'patterns', changes };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Steering Auto-Update Engine
|
|
157
|
+
* @extends EventEmitter
|
|
158
|
+
*/
|
|
159
|
+
class SteeringAutoUpdate extends EventEmitter {
|
|
160
|
+
/**
|
|
161
|
+
* Create steering auto-update engine
|
|
162
|
+
* @param {SteeringAutoUpdateOptions} [options={}] - Options
|
|
163
|
+
*/
|
|
164
|
+
constructor(options = {}) {
|
|
165
|
+
super();
|
|
166
|
+
|
|
167
|
+
this.steeringPath = options.steeringPath || 'steering';
|
|
168
|
+
this.autoSave = options.autoSave !== false;
|
|
169
|
+
this.backup = options.backup !== false;
|
|
170
|
+
this.rules = [...DEFAULT_RULES, ...(options.rules || [])];
|
|
171
|
+
|
|
172
|
+
// Sort rules by priority
|
|
173
|
+
this.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
174
|
+
|
|
175
|
+
// State
|
|
176
|
+
this.updates = new Map();
|
|
177
|
+
this.updateCounter = 0;
|
|
178
|
+
this.steering = new Map();
|
|
179
|
+
this.pendingChanges = new Map();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Load steering files
|
|
184
|
+
* @param {string} [basePath='.'] - Base path
|
|
185
|
+
* @returns {Promise<Map>} Loaded steering files
|
|
186
|
+
*/
|
|
187
|
+
async loadSteering(basePath = '.') {
|
|
188
|
+
const steeringDir = path.join(basePath, this.steeringPath);
|
|
189
|
+
|
|
190
|
+
const files = {
|
|
191
|
+
[STEERING_TYPE.STRUCTURE]: 'structure.md',
|
|
192
|
+
[STEERING_TYPE.TECH]: 'tech.md',
|
|
193
|
+
[STEERING_TYPE.PRODUCT]: 'product.md',
|
|
194
|
+
[STEERING_TYPE.RULES]: 'rules/constitution.md'
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
for (const [type, file] of Object.entries(files)) {
|
|
198
|
+
const filePath = path.join(steeringDir, file);
|
|
199
|
+
try {
|
|
200
|
+
if (fs.existsSync(filePath)) {
|
|
201
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
202
|
+
this.steering.set(type, {
|
|
203
|
+
path: filePath,
|
|
204
|
+
content,
|
|
205
|
+
parsed: this.parseMarkdown(content),
|
|
206
|
+
lastModified: fs.statSync(filePath).mtime
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.emit('error', { type, file, error });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Load custom steering files
|
|
215
|
+
const customDir = path.join(steeringDir, 'custom');
|
|
216
|
+
if (fs.existsSync(customDir)) {
|
|
217
|
+
const customFiles = fs.readdirSync(customDir).filter(f => f.endsWith('.md'));
|
|
218
|
+
for (const file of customFiles) {
|
|
219
|
+
const filePath = path.join(customDir, file);
|
|
220
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
221
|
+
this.steering.set(`custom:${file}`, {
|
|
222
|
+
path: filePath,
|
|
223
|
+
content,
|
|
224
|
+
parsed: this.parseMarkdown(content),
|
|
225
|
+
lastModified: fs.statSync(filePath).mtime
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.emit('steering:loaded', { count: this.steering.size });
|
|
231
|
+
return this.steering;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Parse markdown into sections
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
parseMarkdown(content) {
|
|
239
|
+
const sections = new Map();
|
|
240
|
+
const lines = content.split('\n');
|
|
241
|
+
let currentSection = 'header';
|
|
242
|
+
let currentContent = [];
|
|
243
|
+
|
|
244
|
+
for (const line of lines) {
|
|
245
|
+
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
246
|
+
if (headerMatch) {
|
|
247
|
+
// Save previous section
|
|
248
|
+
if (currentContent.length > 0) {
|
|
249
|
+
sections.set(currentSection, currentContent.join('\n'));
|
|
250
|
+
}
|
|
251
|
+
currentSection = headerMatch[2].toLowerCase().replace(/\s+/g, '-');
|
|
252
|
+
currentContent = [line];
|
|
253
|
+
} else {
|
|
254
|
+
currentContent.push(line);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Save last section
|
|
259
|
+
if (currentContent.length > 0) {
|
|
260
|
+
sections.set(currentSection, currentContent.join('\n'));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return sections;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Process an update trigger
|
|
268
|
+
* @param {string} trigger - Trigger type
|
|
269
|
+
* @param {Object} context - Trigger context
|
|
270
|
+
* @returns {Promise<UpdateResult[]>} Update results
|
|
271
|
+
*/
|
|
272
|
+
async processTrigger(trigger, context = {}) {
|
|
273
|
+
const id = this.generateId();
|
|
274
|
+
this.emit('trigger:received', { id, trigger, context });
|
|
275
|
+
|
|
276
|
+
const results = [];
|
|
277
|
+
|
|
278
|
+
// Find applicable rules
|
|
279
|
+
const applicableRules = this.rules.filter(rule => {
|
|
280
|
+
if (rule.trigger !== trigger) return false;
|
|
281
|
+
try {
|
|
282
|
+
return rule.condition(context);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
this.emit('rule:error', { rule: rule.id, error });
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Apply each rule
|
|
290
|
+
for (const rule of applicableRules) {
|
|
291
|
+
try {
|
|
292
|
+
this.emit('rule:applying', { rule: rule.id, target: rule.target });
|
|
293
|
+
|
|
294
|
+
const steering = this.steering.get(rule.target);
|
|
295
|
+
if (!steering) {
|
|
296
|
+
this.emit('rule:skipped', { rule: rule.id, reason: 'target not found' });
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const updateResult = await rule.update(steering, context);
|
|
301
|
+
|
|
302
|
+
if (updateResult && updateResult.changes?.length > 0) {
|
|
303
|
+
// Queue changes
|
|
304
|
+
if (!this.pendingChanges.has(rule.target)) {
|
|
305
|
+
this.pendingChanges.set(rule.target, []);
|
|
306
|
+
}
|
|
307
|
+
this.pendingChanges.get(rule.target).push({
|
|
308
|
+
rule: rule.id,
|
|
309
|
+
section: updateResult.section,
|
|
310
|
+
changes: updateResult.changes,
|
|
311
|
+
timestamp: Date.now()
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const result = {
|
|
315
|
+
id: `update-${++this.updateCounter}`,
|
|
316
|
+
success: true,
|
|
317
|
+
file: steering.path,
|
|
318
|
+
trigger,
|
|
319
|
+
rule: rule.id,
|
|
320
|
+
changes: updateResult.changes,
|
|
321
|
+
timestamp: Date.now()
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
results.push(result);
|
|
325
|
+
this.updates.set(result.id, result);
|
|
326
|
+
|
|
327
|
+
this.emit('rule:applied', { rule: rule.id, changes: updateResult.changes });
|
|
328
|
+
}
|
|
329
|
+
} catch (error) {
|
|
330
|
+
const result = {
|
|
331
|
+
id: `update-${++this.updateCounter}`,
|
|
332
|
+
success: false,
|
|
333
|
+
file: rule.target,
|
|
334
|
+
trigger,
|
|
335
|
+
rule: rule.id,
|
|
336
|
+
error: error.message,
|
|
337
|
+
timestamp: Date.now()
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
results.push(result);
|
|
341
|
+
this.emit('rule:failed', { rule: rule.id, error });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Auto-save if enabled
|
|
346
|
+
if (this.autoSave && results.some(r => r.success)) {
|
|
347
|
+
await this.applyPendingChanges();
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.emit('trigger:processed', { id, results });
|
|
351
|
+
return results;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Apply pending changes to files
|
|
356
|
+
* @returns {Promise<Object>} Apply result
|
|
357
|
+
*/
|
|
358
|
+
async applyPendingChanges() {
|
|
359
|
+
const applied = [];
|
|
360
|
+
|
|
361
|
+
for (const [target, changes] of this.pendingChanges.entries()) {
|
|
362
|
+
const steering = this.steering.get(target);
|
|
363
|
+
if (!steering) continue;
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
// Create backup
|
|
367
|
+
if (this.backup) {
|
|
368
|
+
const backupPath = `${steering.path}.backup`;
|
|
369
|
+
fs.writeFileSync(backupPath, steering.content);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Generate changelog section
|
|
373
|
+
const changelog = this.generateChangelog(changes);
|
|
374
|
+
|
|
375
|
+
// Update content
|
|
376
|
+
let newContent = steering.content;
|
|
377
|
+
|
|
378
|
+
// Add or update changelog section
|
|
379
|
+
if (newContent.includes('## Changelog')) {
|
|
380
|
+
newContent = newContent.replace(
|
|
381
|
+
/## Changelog\n/,
|
|
382
|
+
`## Changelog\n\n${changelog}\n`
|
|
383
|
+
);
|
|
384
|
+
} else {
|
|
385
|
+
newContent += `\n\n## Changelog\n\n${changelog}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Write file
|
|
389
|
+
fs.writeFileSync(steering.path, newContent);
|
|
390
|
+
|
|
391
|
+
// Update in-memory state
|
|
392
|
+
steering.content = newContent;
|
|
393
|
+
steering.parsed = this.parseMarkdown(newContent);
|
|
394
|
+
steering.lastModified = new Date();
|
|
395
|
+
|
|
396
|
+
applied.push({ target, changesCount: changes.length });
|
|
397
|
+
|
|
398
|
+
this.emit('changes:applied', { target, changes });
|
|
399
|
+
} catch (error) {
|
|
400
|
+
this.emit('changes:failed', { target, error });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.pendingChanges.clear();
|
|
405
|
+
return { applied };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Generate changelog entry
|
|
410
|
+
* @private
|
|
411
|
+
*/
|
|
412
|
+
generateChangelog(changes) {
|
|
413
|
+
const date = new Date().toISOString().split('T')[0];
|
|
414
|
+
const entries = [];
|
|
415
|
+
|
|
416
|
+
for (const change of changes) {
|
|
417
|
+
for (const item of change.changes) {
|
|
418
|
+
entries.push(`- ${item}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return `### ${date}\n${entries.join('\n')}`;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Add custom rule
|
|
427
|
+
* @param {UpdateRule} rule - Rule to add
|
|
428
|
+
*/
|
|
429
|
+
addRule(rule) {
|
|
430
|
+
if (!rule.id || !rule.trigger || !rule.target) {
|
|
431
|
+
throw new Error('Rule must have id, trigger, and target');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
this.rules.push(rule);
|
|
435
|
+
this.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
436
|
+
|
|
437
|
+
this.emit('rule:added', { ruleId: rule.id });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Remove rule
|
|
442
|
+
* @param {string} ruleId - Rule ID to remove
|
|
443
|
+
*/
|
|
444
|
+
removeRule(ruleId) {
|
|
445
|
+
const index = this.rules.findIndex(r => r.id === ruleId);
|
|
446
|
+
if (index !== -1) {
|
|
447
|
+
this.rules.splice(index, 1);
|
|
448
|
+
this.emit('rule:removed', { ruleId });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get update history
|
|
454
|
+
* @param {Object} [filter={}] - Filter options
|
|
455
|
+
* @returns {UpdateResult[]}
|
|
456
|
+
*/
|
|
457
|
+
getHistory(filter = {}) {
|
|
458
|
+
let results = Array.from(this.updates.values());
|
|
459
|
+
|
|
460
|
+
if (filter.trigger) {
|
|
461
|
+
results = results.filter(r => r.trigger === filter.trigger);
|
|
462
|
+
}
|
|
463
|
+
if (filter.file) {
|
|
464
|
+
results = results.filter(r => r.file === filter.file);
|
|
465
|
+
}
|
|
466
|
+
if (filter.success !== undefined) {
|
|
467
|
+
results = results.filter(r => r.success === filter.success);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return results.sort((a, b) => b.timestamp - a.timestamp);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Get statistics
|
|
475
|
+
* @returns {Object}
|
|
476
|
+
*/
|
|
477
|
+
getStats() {
|
|
478
|
+
const updates = Array.from(this.updates.values());
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
totalUpdates: updates.length,
|
|
482
|
+
successful: updates.filter(u => u.success).length,
|
|
483
|
+
failed: updates.filter(u => !u.success).length,
|
|
484
|
+
byTrigger: updates.reduce((acc, u) => {
|
|
485
|
+
acc[u.trigger] = (acc[u.trigger] || 0) + 1;
|
|
486
|
+
return acc;
|
|
487
|
+
}, {}),
|
|
488
|
+
rulesCount: this.rules.length,
|
|
489
|
+
steeringFilesLoaded: this.steering.size,
|
|
490
|
+
pendingChanges: this.pendingChanges.size
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Generate unique ID
|
|
496
|
+
* @private
|
|
497
|
+
*/
|
|
498
|
+
generateId() {
|
|
499
|
+
return `trigger-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 6)}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Clear history
|
|
504
|
+
*/
|
|
505
|
+
clearHistory() {
|
|
506
|
+
this.updates.clear();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Validate steering consistency
|
|
511
|
+
* @returns {Object} Validation result
|
|
512
|
+
*/
|
|
513
|
+
validateConsistency() {
|
|
514
|
+
const issues = [];
|
|
515
|
+
|
|
516
|
+
// Check cross-file references
|
|
517
|
+
const structure = this.steering.get(STEERING_TYPE.STRUCTURE);
|
|
518
|
+
const tech = this.steering.get(STEERING_TYPE.TECH);
|
|
519
|
+
|
|
520
|
+
if (structure && tech) {
|
|
521
|
+
// Check if tech references match structure
|
|
522
|
+
const structureDirs = this.extractDirectories(structure.content);
|
|
523
|
+
const techDirs = this.extractDirectories(tech.content);
|
|
524
|
+
|
|
525
|
+
for (const dir of techDirs) {
|
|
526
|
+
if (!structureDirs.includes(dir)) {
|
|
527
|
+
issues.push({
|
|
528
|
+
type: 'mismatch',
|
|
529
|
+
file: STEERING_TYPE.TECH,
|
|
530
|
+
message: `Directory "${dir}" referenced in tech.md but not in structure.md`
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
valid: issues.length === 0,
|
|
538
|
+
issues
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Extract directories from content
|
|
544
|
+
* @private
|
|
545
|
+
*/
|
|
546
|
+
extractDirectories(content) {
|
|
547
|
+
const dirs = [];
|
|
548
|
+
const regex = /`([^`]+\/)`/g;
|
|
549
|
+
let match;
|
|
550
|
+
while ((match = regex.exec(content)) !== null) {
|
|
551
|
+
dirs.push(match[1]);
|
|
552
|
+
}
|
|
553
|
+
return dirs;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Create steering auto-update instance
|
|
559
|
+
* @param {SteeringAutoUpdateOptions} [options={}] - Options
|
|
560
|
+
* @returns {SteeringAutoUpdate}
|
|
561
|
+
*/
|
|
562
|
+
function createSteeringAutoUpdate(options = {}) {
|
|
563
|
+
return new SteeringAutoUpdate(options);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
module.exports = {
|
|
567
|
+
SteeringAutoUpdate,
|
|
568
|
+
createSteeringAutoUpdate,
|
|
569
|
+
TRIGGER,
|
|
570
|
+
STEERING_TYPE,
|
|
571
|
+
DEFAULT_RULES
|
|
572
|
+
};
|