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.
Files changed (44) hide show
  1. package/README.md +24 -19
  2. package/package.json +1 -1
  3. package/src/agents/agent-loop.js +532 -0
  4. package/src/agents/agentic/code-generator.js +767 -0
  5. package/src/agents/agentic/code-reviewer.js +698 -0
  6. package/src/agents/agentic/index.js +43 -0
  7. package/src/agents/function-tool.js +432 -0
  8. package/src/agents/index.js +45 -0
  9. package/src/agents/schema-generator.js +514 -0
  10. package/src/analyzers/ast-extractor.js +870 -0
  11. package/src/analyzers/context-optimizer.js +681 -0
  12. package/src/analyzers/repository-map.js +692 -0
  13. package/src/integrations/index.js +7 -1
  14. package/src/integrations/mcp/index.js +175 -0
  15. package/src/integrations/mcp/mcp-context-provider.js +472 -0
  16. package/src/integrations/mcp/mcp-discovery.js +436 -0
  17. package/src/integrations/mcp/mcp-tool-registry.js +467 -0
  18. package/src/integrations/mcp-connector.js +818 -0
  19. package/src/integrations/tool-discovery.js +589 -0
  20. package/src/managers/index.js +7 -0
  21. package/src/managers/skill-tools.js +565 -0
  22. package/src/monitoring/cost-tracker.js +7 -0
  23. package/src/monitoring/incident-manager.js +10 -0
  24. package/src/monitoring/observability.js +10 -0
  25. package/src/monitoring/quality-dashboard.js +491 -0
  26. package/src/monitoring/release-manager.js +10 -0
  27. package/src/orchestration/agent-skill-binding.js +655 -0
  28. package/src/orchestration/error-handler.js +827 -0
  29. package/src/orchestration/index.js +235 -1
  30. package/src/orchestration/mcp-tool-adapters.js +896 -0
  31. package/src/orchestration/reasoning/index.js +58 -0
  32. package/src/orchestration/reasoning/planning-engine.js +831 -0
  33. package/src/orchestration/reasoning/reasoning-engine.js +710 -0
  34. package/src/orchestration/reasoning/self-correction.js +751 -0
  35. package/src/orchestration/skill-executor.js +665 -0
  36. package/src/orchestration/skill-registry.js +650 -0
  37. package/src/orchestration/workflow-examples.js +1072 -0
  38. package/src/orchestration/workflow-executor.js +779 -0
  39. package/src/phase4-integration.js +248 -0
  40. package/src/phase5-integration.js +402 -0
  41. package/src/steering/steering-auto-update.js +572 -0
  42. package/src/steering/steering-validator.js +547 -0
  43. package/src/templates/template-constraints.js +646 -0
  44. package/src/validators/advanced-validation.js +580 -0
package/README.md CHANGED
@@ -71,31 +71,36 @@ musubi init --windsurf # Windsurf IDE
71
71
 
72
72
  ---
73
73
 
74
- ## 📊 What's New in v3.9.0
75
-
76
- - 🛡️ **Guardrails System** - OpenAI Agents SDK inspired input/output validation and safety checks
77
- - **InputGuardrail** - Input validation, PII detection, injection attack prevention
78
- - ✅ **OutputGuardrail** - Output sanitization, sensitive data redaction, content policy enforcement
79
- - ⚖️ **SafetyCheckGuardrail** - Constitutional Articles compliance, content safety analysis
80
- - 🔧 **GuardrailRules DSL** - Fluent API for building validation rules with RuleBuilder
81
- - 🔗 **GuardrailChain** - Compose multiple guardrails with sequential/parallel execution
82
- - 🖥️ **CLI Commands** - `musubi-validate guardrails` and `guardrails-chain` commands
74
+ ## 📊 What's New in v5.0.0
75
+
76
+ ### Phase 5: Advanced Features 🚀
77
+ - 🔄 **Steering Auto-Update** - Automatic project memory synchronization with 5 trigger types
78
+ - ✅ **Steering Validator** - Comprehensive validation for steering documents
79
+ - 📋 **Template Constraints** - Enforce structure requirements with ThinkingChecklist
80
+ - 📊 **Quality Dashboard** - A-F grade metrics with health status monitoring
81
+ - 🔍 **Advanced Validation** - Cross-artifact consistency, gap detection, traceability validation
82
+ - 🎯 **Phase 5 Integration** - Unified access to all advanced features
83
+
84
+ ### Phase 4: Agent Loop & Agentic Features 🤖
85
+ - 🧠 **Codebase Intelligence** - RepositoryMap, ASTExtractor, ContextOptimizer
86
+ - 💭 **Agentic Reasoning** - ReasoningEngine, PlanningEngine, SelfCorrection
87
+ - ⚡ **Code Generation** - CodeGenerator, CodeReviewer with multiple modes
88
+ - 🔗 **Integrated Agent** - Unified agent with all Phase 4 capabilities
83
89
 
84
90
  ```bash
85
- # Input validation with security preset
86
- npx musubi-validate guardrails "user input" --type input --preset security
87
-
88
- # Output validation with PII redaction
89
- npx musubi-validate guardrails "output" --type output --redact
91
+ # Use advanced features via orchestrator
92
+ const { Phase5Integration, createIntegratedAgent } = require('musubi-sdd');
90
93
 
91
- # Safety check with constitutional compliance
92
- npx musubi-validate guardrails "code" --type safety --constitutional --level high
94
+ # Quality dashboard
95
+ const dashboard = new QualityDashboard();
96
+ const metrics = await dashboard.collectMetrics(projectPath);
93
97
 
94
- # Run guardrail chain in parallel
95
- npx musubi-validate guardrails-chain "content" --parallel
98
+ # Advanced validation
99
+ const validator = new AdvancedValidation();
100
+ const result = await validator.validateAll(projectPath);
96
101
  ```
97
102
 
98
- ### Previous (v3.7.1)
103
+ ### Previous Highlights (v3.11.0)
99
104
 
100
105
  - 🌐 **WebSocket Real-time GUI** - Live replanning updates with `musubi-browser` dashboard
101
106
  - 📋 **GUI Quick Actions** - Modal dialog for New Requirement, Validate Project, Export Report
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musubi-sdd",
3
- "version": "3.10.0",
3
+ "version": "5.1.0",
4
4
  "description": "Ultimate Specification Driven Development Tool with 27 Agents for 7 AI Coding Platforms + MCP Integration (Claude Code, GitHub Copilot, Cursor, Gemini CLI, Windsurf, Codex, Qwen Code)",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,532 @@
1
+ /**
2
+ * MUSUBI Agent Loop
3
+ *
4
+ * Implements agentic tool-calling loop inspired by OpenAI Agents SDK
5
+ * and AutoGen patterns. Executes tool calls, processes results, and
6
+ * continues until completion or limit is reached.
7
+ *
8
+ * @module agents/agent-loop
9
+ */
10
+
11
+ const EventEmitter = require('events');
12
+
13
+ /**
14
+ * @typedef {Object} AgentLoopConfig
15
+ * @property {number} [maxIterations=10] - Maximum tool calling iterations
16
+ * @property {number} [timeout=60000] - Total timeout in milliseconds
17
+ * @property {number} [iterationTimeout=30000] - Per-iteration timeout
18
+ * @property {boolean} [continueOnError=false] - Continue on tool errors
19
+ * @property {Function} [completionCheck] - Custom completion checker
20
+ * @property {Object} [guardrails] - Input/output guardrails
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} ToolDefinition
25
+ * @property {string} name - Tool name
26
+ * @property {string} description - Tool description
27
+ * @property {Object} parameters - JSON Schema for parameters
28
+ * @property {Function} handler - Async function to execute
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} ToolCall
33
+ * @property {string} id - Unique call ID
34
+ * @property {string} tool - Tool name
35
+ * @property {Object} arguments - Tool arguments
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} LoopResult
40
+ * @property {string} status - 'completed' | 'max_iterations' | 'timeout' | 'error'
41
+ * @property {Array} messages - Conversation history
42
+ * @property {Array} toolCalls - All tool calls made
43
+ * @property {*} finalOutput - Final result
44
+ * @property {Object} metrics - Execution metrics
45
+ */
46
+
47
+ /**
48
+ * AgentLoop class for managing tool-calling agentic workflows
49
+ */
50
+ class AgentLoop extends EventEmitter {
51
+ /**
52
+ * @param {AgentLoopConfig} config
53
+ */
54
+ constructor(config = {}) {
55
+ super();
56
+
57
+ this.maxIterations = config.maxIterations ?? 10;
58
+ this.timeout = config.timeout ?? 60000;
59
+ this.iterationTimeout = config.iterationTimeout ?? 30000;
60
+ this.continueOnError = config.continueOnError ?? false;
61
+ this.completionCheck = config.completionCheck ?? this.defaultCompletionCheck.bind(this);
62
+ this.guardrails = config.guardrails ?? null;
63
+
64
+ /** @type {Map<string, ToolDefinition>} */
65
+ this.tools = new Map();
66
+
67
+ /** @type {Array} */
68
+ this.messages = [];
69
+
70
+ /** @type {Array<ToolCall>} */
71
+ this.toolCallHistory = [];
72
+
73
+ /** @type {boolean} */
74
+ this.isRunning = false;
75
+
76
+ /** @type {AbortController|null} */
77
+ this.abortController = null;
78
+ }
79
+
80
+ /**
81
+ * Register a tool for the agent to use
82
+ * @param {ToolDefinition} tool
83
+ */
84
+ registerTool(tool) {
85
+ if (!tool.name || typeof tool.handler !== 'function') {
86
+ throw new Error('Tool must have name and handler function');
87
+ }
88
+
89
+ this.tools.set(tool.name, {
90
+ name: tool.name,
91
+ description: tool.description || '',
92
+ parameters: tool.parameters || { type: 'object', properties: {} },
93
+ handler: tool.handler
94
+ });
95
+
96
+ this.emit('tool:registered', { name: tool.name });
97
+ return this;
98
+ }
99
+
100
+ /**
101
+ * Register multiple tools at once
102
+ * @param {ToolDefinition[]} tools
103
+ */
104
+ registerTools(tools) {
105
+ for (const tool of tools) {
106
+ this.registerTool(tool);
107
+ }
108
+ return this;
109
+ }
110
+
111
+ /**
112
+ * Unregister a tool
113
+ * @param {string} name
114
+ */
115
+ unregisterTool(name) {
116
+ const deleted = this.tools.delete(name);
117
+ if (deleted) {
118
+ this.emit('tool:unregistered', { name });
119
+ }
120
+ return deleted;
121
+ }
122
+
123
+ /**
124
+ * Get all registered tools in OpenAI function format
125
+ * @returns {Array}
126
+ */
127
+ getToolSchemas() {
128
+ return Array.from(this.tools.values()).map(tool => ({
129
+ type: 'function',
130
+ function: {
131
+ name: tool.name,
132
+ description: tool.description,
133
+ parameters: tool.parameters
134
+ }
135
+ }));
136
+ }
137
+
138
+ /**
139
+ * Run the agent loop with an LLM provider
140
+ * @param {Object} options
141
+ * @param {Object} options.llmProvider - LLM provider with chat() method
142
+ * @param {string} options.systemPrompt - System prompt for the agent
143
+ * @param {string} options.userMessage - Initial user message
144
+ * @param {Object} [options.context] - Additional context
145
+ * @returns {Promise<LoopResult>}
146
+ */
147
+ async run({ llmProvider, systemPrompt, userMessage, context = {} }) {
148
+ if (this.isRunning) {
149
+ throw new Error('Agent loop is already running');
150
+ }
151
+
152
+ this.isRunning = true;
153
+ this.abortController = new AbortController();
154
+ this.messages = [];
155
+ this.toolCallHistory = [];
156
+
157
+ const startTime = Date.now();
158
+ const timeoutId = setTimeout(() => this.abort('timeout'), this.timeout);
159
+
160
+ const metrics = {
161
+ iterations: 0,
162
+ toolCalls: 0,
163
+ startTime,
164
+ endTime: null,
165
+ duration: null,
166
+ tokensUsed: 0
167
+ };
168
+
169
+ try {
170
+ // Initialize messages
171
+ this.messages.push({ role: 'system', content: systemPrompt });
172
+
173
+ // Apply input guardrails if configured
174
+ const processedInput = await this.applyInputGuardrails(userMessage, context);
175
+ this.messages.push({ role: 'user', content: processedInput });
176
+
177
+ let iteration = 0;
178
+ let completed = false;
179
+ let finalOutput = null;
180
+
181
+ while (iteration < this.maxIterations && !completed) {
182
+ if (this.abortController.signal.aborted) {
183
+ return this.createResult('aborted', finalOutput, metrics);
184
+ }
185
+
186
+ iteration++;
187
+ metrics.iterations = iteration;
188
+ this.emit('iteration:start', { iteration, maxIterations: this.maxIterations });
189
+
190
+ // Call LLM
191
+ const response = await this.callLLMWithTimeout(llmProvider, {
192
+ messages: this.messages,
193
+ tools: this.getToolSchemas(),
194
+ context
195
+ });
196
+
197
+ if (response.usage) {
198
+ metrics.tokensUsed += response.usage.total_tokens || 0;
199
+ }
200
+
201
+ // Check for tool calls
202
+ if (response.toolCalls && response.toolCalls.length > 0) {
203
+ // Execute tool calls
204
+ const toolResults = await this.executeToolCalls(response.toolCalls);
205
+ metrics.toolCalls += response.toolCalls.length;
206
+
207
+ // Add assistant message with tool calls
208
+ this.messages.push({
209
+ role: 'assistant',
210
+ content: response.content || null,
211
+ tool_calls: response.toolCalls.map(tc => ({
212
+ id: tc.id,
213
+ type: 'function',
214
+ function: {
215
+ name: tc.tool || tc.name,
216
+ arguments: JSON.stringify(tc.arguments)
217
+ }
218
+ }))
219
+ });
220
+
221
+ // Add tool results
222
+ for (const result of toolResults) {
223
+ this.messages.push({
224
+ role: 'tool',
225
+ tool_call_id: result.callId,
226
+ content: JSON.stringify(result.output)
227
+ });
228
+ }
229
+
230
+ this.emit('iteration:complete', {
231
+ iteration,
232
+ toolCalls: response.toolCalls.length,
233
+ hasMore: true
234
+ });
235
+ } else {
236
+ // No tool calls - LLM response is final
237
+ finalOutput = response.content;
238
+
239
+ // Apply output guardrails
240
+ finalOutput = await this.applyOutputGuardrails(finalOutput, context);
241
+
242
+ // Add final assistant message
243
+ this.messages.push({
244
+ role: 'assistant',
245
+ content: finalOutput
246
+ });
247
+
248
+ // Check completion
249
+ completed = await this.completionCheck(finalOutput, this.messages, context);
250
+
251
+ this.emit('iteration:complete', {
252
+ iteration,
253
+ toolCalls: 0,
254
+ hasMore: !completed
255
+ });
256
+ }
257
+ }
258
+
259
+ metrics.endTime = Date.now();
260
+ metrics.duration = metrics.endTime - startTime;
261
+
262
+ if (iteration >= this.maxIterations && !completed) {
263
+ return this.createResult('max_iterations', finalOutput, metrics);
264
+ }
265
+
266
+ return this.createResult('completed', finalOutput, metrics);
267
+
268
+ } catch (error) {
269
+ metrics.endTime = Date.now();
270
+ metrics.duration = metrics.endTime - startTime;
271
+
272
+ this.emit('error', error);
273
+
274
+ if (error.message === 'timeout') {
275
+ return this.createResult('timeout', null, metrics);
276
+ }
277
+
278
+ return this.createResult('error', null, metrics, error);
279
+ } finally {
280
+ clearTimeout(timeoutId);
281
+ this.isRunning = false;
282
+ this.abortController = null;
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Call LLM with per-iteration timeout
288
+ * @param {Object} llmProvider
289
+ * @param {Object} options
290
+ * @returns {Promise<Object>}
291
+ */
292
+ async callLLMWithTimeout(llmProvider, options) {
293
+ return new Promise((resolve, reject) => {
294
+ const timeoutId = setTimeout(() => {
295
+ reject(new Error('Iteration timeout'));
296
+ }, this.iterationTimeout);
297
+
298
+ llmProvider.chat(options)
299
+ .then(response => {
300
+ clearTimeout(timeoutId);
301
+ resolve(response);
302
+ })
303
+ .catch(error => {
304
+ clearTimeout(timeoutId);
305
+ reject(error);
306
+ });
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Execute tool calls and collect results
312
+ * @param {ToolCall[]} toolCalls
313
+ * @returns {Promise<Array>}
314
+ */
315
+ async executeToolCalls(toolCalls) {
316
+ const results = [];
317
+
318
+ for (const call of toolCalls) {
319
+ const toolName = call.tool || call.name;
320
+ const callId = call.id || `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
321
+
322
+ this.emit('tool:call', { name: toolName, arguments: call.arguments, id: callId });
323
+
324
+ try {
325
+ const tool = this.tools.get(toolName);
326
+
327
+ if (!tool) {
328
+ throw new Error(`Unknown tool: ${toolName}`);
329
+ }
330
+
331
+ const output = await tool.handler(call.arguments);
332
+
333
+ this.toolCallHistory.push({
334
+ id: callId,
335
+ tool: toolName,
336
+ arguments: call.arguments,
337
+ output,
338
+ status: 'success'
339
+ });
340
+
341
+ results.push({
342
+ callId,
343
+ tool: toolName,
344
+ output,
345
+ status: 'success'
346
+ });
347
+
348
+ this.emit('tool:result', { name: toolName, output, id: callId, status: 'success' });
349
+
350
+ } catch (error) {
351
+ const errorResult = {
352
+ callId,
353
+ tool: toolName,
354
+ error: error.message,
355
+ status: 'error'
356
+ };
357
+
358
+ this.toolCallHistory.push({
359
+ id: callId,
360
+ tool: toolName,
361
+ arguments: call.arguments,
362
+ error: error.message,
363
+ status: 'error'
364
+ });
365
+
366
+ this.emit('tool:error', { name: toolName, error: error.message, id: callId });
367
+
368
+ if (this.continueOnError) {
369
+ results.push({
370
+ callId,
371
+ tool: toolName,
372
+ output: { error: error.message },
373
+ status: 'error'
374
+ });
375
+ } else {
376
+ throw error;
377
+ }
378
+ }
379
+ }
380
+
381
+ return results;
382
+ }
383
+
384
+ /**
385
+ * Default completion check - returns true when LLM provides final response
386
+ * @param {*} output
387
+ * @param {Array} messages
388
+ * @param {Object} context
389
+ * @returns {Promise<boolean>}
390
+ */
391
+ async defaultCompletionCheck(output, messages, context) {
392
+ // By default, if we reach here (no tool calls), we're done
393
+ return true;
394
+ }
395
+
396
+ /**
397
+ * Apply input guardrails if configured
398
+ * @param {string} input
399
+ * @param {Object} context
400
+ * @returns {Promise<string>}
401
+ */
402
+ async applyInputGuardrails(input, context) {
403
+ if (!this.guardrails?.input) {
404
+ return input;
405
+ }
406
+
407
+ try {
408
+ const result = await this.guardrails.input.validate(input, context);
409
+ if (!result.valid) {
410
+ throw new Error(`Input guardrail violation: ${result.reason}`);
411
+ }
412
+ return result.transformed || input;
413
+ } catch (error) {
414
+ this.emit('guardrail:input:error', error);
415
+ throw error;
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Apply output guardrails if configured
421
+ * @param {string} output
422
+ * @param {Object} context
423
+ * @returns {Promise<string>}
424
+ */
425
+ async applyOutputGuardrails(output, context) {
426
+ if (!this.guardrails?.output) {
427
+ return output;
428
+ }
429
+
430
+ try {
431
+ const result = await this.guardrails.output.validate(output, context);
432
+ if (!result.valid) {
433
+ this.emit('guardrail:output:violation', { output, reason: result.reason });
434
+ return result.fallback || '[Output blocked by guardrails]';
435
+ }
436
+ return result.transformed || output;
437
+ } catch (error) {
438
+ this.emit('guardrail:output:error', error);
439
+ return output;
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Create standardized result object
445
+ * @param {string} status
446
+ * @param {*} finalOutput
447
+ * @param {Object} metrics
448
+ * @param {Error} [error]
449
+ * @returns {LoopResult}
450
+ */
451
+ createResult(status, finalOutput, metrics, error = null) {
452
+ return {
453
+ status,
454
+ messages: this.messages,
455
+ toolCalls: this.toolCallHistory,
456
+ finalOutput,
457
+ metrics: {
458
+ ...metrics,
459
+ toolCallCount: this.toolCallHistory.length,
460
+ successfulCalls: this.toolCallHistory.filter(tc => tc.status === 'success').length,
461
+ failedCalls: this.toolCallHistory.filter(tc => tc.status === 'error').length
462
+ },
463
+ ...(error && { error: error.message })
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Abort the running loop
469
+ * @param {string} [reason='aborted']
470
+ */
471
+ abort(reason = 'aborted') {
472
+ if (this.abortController) {
473
+ this.abortController.abort();
474
+ this.emit('abort', { reason });
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Get current loop state
480
+ * @returns {Object}
481
+ */
482
+ getState() {
483
+ return {
484
+ isRunning: this.isRunning,
485
+ messageCount: this.messages.length,
486
+ toolCallCount: this.toolCallHistory.length,
487
+ registeredTools: Array.from(this.tools.keys())
488
+ };
489
+ }
490
+
491
+ /**
492
+ * Reset the loop state
493
+ */
494
+ reset() {
495
+ if (this.isRunning) {
496
+ throw new Error('Cannot reset while loop is running');
497
+ }
498
+
499
+ this.messages = [];
500
+ this.toolCallHistory = [];
501
+ this.emit('reset');
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Create a simple mock LLM provider for testing
507
+ * @param {Array} responses - Pre-defined responses
508
+ * @returns {Object}
509
+ */
510
+ function createMockLLMProvider(responses) {
511
+ let callIndex = 0;
512
+
513
+ return {
514
+ async chat(options) {
515
+ if (callIndex >= responses.length) {
516
+ return { content: 'Done', toolCalls: [] };
517
+ }
518
+
519
+ const response = responses[callIndex++];
520
+ return {
521
+ content: response.content || null,
522
+ toolCalls: response.toolCalls || [],
523
+ usage: response.usage || { total_tokens: 100 }
524
+ };
525
+ }
526
+ };
527
+ }
528
+
529
+ module.exports = {
530
+ AgentLoop,
531
+ createMockLLMProvider
532
+ };