musubi-sdd 3.7.1 → 3.9.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.ja.md +8 -8
- package/README.md +8 -7
- package/bin/musubi-orchestrate.js +317 -1
- package/bin/musubi-validate.js +234 -0
- package/package.json +1 -1
- package/src/orchestration/guardrails/base-guardrail.js +358 -0
- package/src/orchestration/guardrails/guardrail-rules.js +532 -0
- package/src/orchestration/guardrails/index.js +61 -0
- package/src/orchestration/guardrails/input-guardrail.js +378 -0
- package/src/orchestration/guardrails/output-guardrail.js +514 -0
- package/src/orchestration/guardrails/safety-check.js +471 -0
- package/src/orchestration/index.js +82 -1
- package/src/orchestration/orchestration-engine.js +5 -1
- package/src/orchestration/patterns/handoff.js +558 -0
- package/src/orchestration/patterns/triage.js +818 -0
package/README.ja.md
CHANGED
|
@@ -71,14 +71,14 @@ musubi init --windsurf # Windsurf IDE
|
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
74
|
-
## 📊
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
74
|
+
## 📊 v3.7.1 の新機能
|
|
75
|
+
|
|
76
|
+
- 🌐 **WebSocketリアルタイムGUI** - `musubi-browser`ダッシュボードでライブ更新
|
|
77
|
+
- 📋 **GUIクイックアクション** - 新規要件モーダル、プロジェクト検証、レポートエクスポート
|
|
78
|
+
- 🔄 **GitHub Actions統合** - `musubi-action`でCI/CD検証
|
|
79
|
+
- 🔧 **OpenAPIコンバーター** - OpenAPI 3.x/Swagger 2.xスペックをMUSUBI形式に変換
|
|
80
|
+
- 🌍 **多言語テンプレート** - 7言語対応(EN, JA, ZH, KO, ES, DE, FR)
|
|
81
|
+
- 🤖 **Ollama統合** - 9つのモデルプリセットでローカルLLMをサポート
|
|
82
82
|
|
|
83
83
|
### 以前のバージョン (v2.0.0)
|
|
84
84
|
|
package/README.md
CHANGED
|
@@ -71,13 +71,14 @@ musubi init --windsurf # Windsurf IDE
|
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
|
|
74
|
-
## 📊 What's New in v3.
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
- 🔄 **
|
|
79
|
-
-
|
|
80
|
-
-
|
|
74
|
+
## 📊 What's New in v3.7.1
|
|
75
|
+
|
|
76
|
+
- 🌐 **WebSocket Real-time GUI** - Live replanning updates with `musubi-browser` dashboard
|
|
77
|
+
- 📋 **GUI Quick Actions** - Modal dialog for New Requirement, Validate Project, Export Report
|
|
78
|
+
- 🔄 **GitHub Actions Integration** - `musubi-action` for CI/CD with MUSUBI validation
|
|
79
|
+
- 🔧 **OpenAPI Converter** - Convert OpenAPI 3.x/Swagger 2.x specs to MUSUBI format
|
|
80
|
+
- 🌍 **Multi-language Templates** - 7 language support (EN, JA, ZH, KO, ES, DE, FR)
|
|
81
|
+
- 🤖 **Ollama Integration** - Local LLM support with 9 model presets
|
|
81
82
|
|
|
82
83
|
### Previous (v3.6.0)
|
|
83
84
|
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* musubi-orchestrate run <pattern> --skills <skills...> # Execute pattern with skills
|
|
10
10
|
* musubi-orchestrate auto <task> # Auto-select and execute skill
|
|
11
11
|
* musubi-orchestrate sequential --skills <skills...> # Execute skills sequentially
|
|
12
|
+
* musubi-orchestrate handoff --from <agent> --to <agent> # Delegate to another agent
|
|
13
|
+
* musubi-orchestrate triage --message <msg> # Classify and route request
|
|
12
14
|
* musubi-orchestrate list-patterns # List available patterns
|
|
13
15
|
* musubi-orchestrate list-skills # List available skills
|
|
14
16
|
* musubi-orchestrate status # Show orchestration status
|
|
@@ -23,7 +25,15 @@ const {
|
|
|
23
25
|
createOrchestrationEngine,
|
|
24
26
|
PatternType,
|
|
25
27
|
ExecutionStatus,
|
|
26
|
-
Priority
|
|
28
|
+
Priority,
|
|
29
|
+
HandoffPattern,
|
|
30
|
+
HandoffFilters,
|
|
31
|
+
HandoffConfig,
|
|
32
|
+
handoff,
|
|
33
|
+
TriagePattern,
|
|
34
|
+
TriageCategory,
|
|
35
|
+
TriageStrategy,
|
|
36
|
+
AgentCapability
|
|
27
37
|
} = require('../src/orchestration');
|
|
28
38
|
|
|
29
39
|
const {
|
|
@@ -758,6 +768,312 @@ program
|
|
|
758
768
|
}
|
|
759
769
|
});
|
|
760
770
|
|
|
771
|
+
// Handoff command - Explicit agent-to-agent delegation
|
|
772
|
+
program
|
|
773
|
+
.command('handoff')
|
|
774
|
+
.description('Execute handoff pattern - Delegate task to another agent')
|
|
775
|
+
.requiredOption('-t, --task <task>', 'Task to handoff')
|
|
776
|
+
.requiredOption('--from <agent>', 'Source agent')
|
|
777
|
+
.requiredOption('--to <agent>', 'Target agent')
|
|
778
|
+
.option('-s, --skills <skills...>', 'Available skills/agents')
|
|
779
|
+
.option('--filter <filter>', 'Input filter (removeAllTools|userMessagesOnly|lastN|summarize|keepAll)', 'keepAll')
|
|
780
|
+
.option('--filter-n <n>', 'N value for lastN filter', '10')
|
|
781
|
+
.option('--reason <reason>', 'Handoff reason')
|
|
782
|
+
.option('--priority <priority>', 'Priority (low|normal|high|urgent)', 'normal')
|
|
783
|
+
.option('-i, --input <json>', 'Additional input data as JSON')
|
|
784
|
+
.option('-f, --format <type>', 'Output format (text|json)', 'text')
|
|
785
|
+
.action(async (options) => {
|
|
786
|
+
try {
|
|
787
|
+
console.log(chalk.bold('\n🤝 Handoff Pattern - Agent Delegation\n'));
|
|
788
|
+
console.log(chalk.dim(`From: ${options.from} → To: ${options.to}\n`));
|
|
789
|
+
|
|
790
|
+
const engine = await createEngine(process.cwd());
|
|
791
|
+
|
|
792
|
+
// Parse additional input
|
|
793
|
+
const additionalInput = options.input ? JSON.parse(options.input) : {};
|
|
794
|
+
|
|
795
|
+
// Select input filter
|
|
796
|
+
let inputFilter;
|
|
797
|
+
switch (options.filter) {
|
|
798
|
+
case 'removeAllTools':
|
|
799
|
+
inputFilter = HandoffFilters.removeAllTools;
|
|
800
|
+
break;
|
|
801
|
+
case 'userMessagesOnly':
|
|
802
|
+
inputFilter = HandoffFilters.userMessagesOnly;
|
|
803
|
+
break;
|
|
804
|
+
case 'lastN':
|
|
805
|
+
inputFilter = HandoffFilters.lastN(parseInt(options.filterN, 10));
|
|
806
|
+
break;
|
|
807
|
+
case 'summarize':
|
|
808
|
+
inputFilter = HandoffFilters.summarize;
|
|
809
|
+
break;
|
|
810
|
+
default:
|
|
811
|
+
inputFilter = HandoffFilters.keepAll;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Create handoff configuration
|
|
815
|
+
const handoffConfig = new HandoffConfig({
|
|
816
|
+
targetAgent: options.to,
|
|
817
|
+
reason: options.reason || `Handoff from ${options.from} to ${options.to}`,
|
|
818
|
+
inputFilter,
|
|
819
|
+
priority: options.priority
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// Prepare agent definitions
|
|
823
|
+
const agents = [];
|
|
824
|
+
const skills = options.skills || [options.from, options.to];
|
|
825
|
+
|
|
826
|
+
for (const skillName of skills) {
|
|
827
|
+
const skill = engine.getSkill(skillName);
|
|
828
|
+
if (skill) {
|
|
829
|
+
agents.push({
|
|
830
|
+
name: skillName,
|
|
831
|
+
...skill,
|
|
832
|
+
handoffs: skillName === options.from ? [handoffConfig] : []
|
|
833
|
+
});
|
|
834
|
+
} else {
|
|
835
|
+
// Create placeholder agent
|
|
836
|
+
agents.push({
|
|
837
|
+
name: skillName,
|
|
838
|
+
description: `${skillName} agent`,
|
|
839
|
+
execute: async (input) => ({ agent: skillName, input, executed: true }),
|
|
840
|
+
handoffs: skillName === options.from ? [handoffConfig] : []
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Create target agents array as required by HandoffPattern
|
|
846
|
+
const targetAgent = agents.find(a => a.name === options.to) || {
|
|
847
|
+
name: options.to,
|
|
848
|
+
description: `${options.to} agent`,
|
|
849
|
+
execute: async (input) => ({ agent: options.to, input, executed: true })
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
const context = await engine.execute(PatternType.HANDOFF, {
|
|
853
|
+
task: options.task,
|
|
854
|
+
input: {
|
|
855
|
+
sourceAgent: options.from,
|
|
856
|
+
targetAgents: [targetAgent], // Array of target agents
|
|
857
|
+
agents,
|
|
858
|
+
reason: options.reason || `Handoff from ${options.from} to ${options.to}`,
|
|
859
|
+
...additionalInput
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
if (options.format === 'json') {
|
|
864
|
+
console.log(JSON.stringify({
|
|
865
|
+
success: context.status === ExecutionStatus.COMPLETED,
|
|
866
|
+
sourceAgent: options.from,
|
|
867
|
+
targetAgent: options.to,
|
|
868
|
+
result: context.output,
|
|
869
|
+
handoffChain: context.output?.handoffChain || []
|
|
870
|
+
}, null, 2));
|
|
871
|
+
} else {
|
|
872
|
+
if (context.status === ExecutionStatus.COMPLETED) {
|
|
873
|
+
console.log(chalk.green('✓ Handoff completed successfully\n'));
|
|
874
|
+
|
|
875
|
+
if (context.output?.handoffChain) {
|
|
876
|
+
console.log(chalk.bold('Handoff Chain:'));
|
|
877
|
+
for (const hop of context.output.handoffChain) {
|
|
878
|
+
console.log(` ${chalk.cyan(hop.from)} → ${chalk.yellow(hop.to)}`);
|
|
879
|
+
if (hop.reason) {
|
|
880
|
+
console.log(chalk.dim(` Reason: ${hop.reason}`));
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
console.log('');
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
console.log(chalk.bold('Result:'));
|
|
887
|
+
console.log(` Final Agent: ${chalk.cyan(context.output?.finalAgent || options.to)}`);
|
|
888
|
+
console.log(` Status: ${chalk.green('Completed')}`);
|
|
889
|
+
} else {
|
|
890
|
+
console.log(chalk.red(`✗ Handoff failed: ${context.error}\n`));
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
console.log('');
|
|
896
|
+
|
|
897
|
+
} catch (error) {
|
|
898
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}\n`));
|
|
899
|
+
if (process.env.DEBUG) {
|
|
900
|
+
console.error(error.stack);
|
|
901
|
+
}
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
// Triage command - Request classification and routing
|
|
907
|
+
program
|
|
908
|
+
.command('triage')
|
|
909
|
+
.description('Execute triage pattern - Classify and route request to appropriate agent')
|
|
910
|
+
.requiredOption('-m, --message <message>', 'Message/request to classify')
|
|
911
|
+
.option('-s, --skills <skills...>', 'Available agents/skills to route to')
|
|
912
|
+
.option('--strategy <strategy>', 'Classification strategy (keyword|intent|capability|hybrid|llm)', 'hybrid')
|
|
913
|
+
.option('--default-category <category>', 'Default category if no match', 'general')
|
|
914
|
+
.option('--threshold <threshold>', 'Confidence threshold (0-1)', '0.3')
|
|
915
|
+
.option('--auto-handoff', 'Automatically handoff to selected agent', false)
|
|
916
|
+
.option('-f, --format <type>', 'Output format (text|json)', 'text')
|
|
917
|
+
.action(async (options) => {
|
|
918
|
+
try {
|
|
919
|
+
console.log(chalk.bold('\n🎯 Triage Pattern - Request Classification\n'));
|
|
920
|
+
console.log(chalk.dim(`Message: "${options.message.substring(0, 50)}${options.message.length > 50 ? '...' : ''}"\n`));
|
|
921
|
+
|
|
922
|
+
const engine = await createEngine(process.cwd());
|
|
923
|
+
|
|
924
|
+
// Map strategy string to enum
|
|
925
|
+
const strategyMap = {
|
|
926
|
+
'keyword': TriageStrategy.KEYWORD,
|
|
927
|
+
'intent': TriageStrategy.INTENT,
|
|
928
|
+
'capability': TriageStrategy.CAPABILITY,
|
|
929
|
+
'hybrid': TriageStrategy.HYBRID,
|
|
930
|
+
'llm': TriageStrategy.LLM
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
const strategy = strategyMap[options.strategy] || TriageStrategy.HYBRID;
|
|
934
|
+
|
|
935
|
+
// Prepare agents with capabilities
|
|
936
|
+
const agents = [];
|
|
937
|
+
const defaultSkills = [
|
|
938
|
+
{ name: 'billing-agent', categories: [TriageCategory.BILLING, TriageCategory.REFUND], keywords: ['invoice', 'payment', 'refund', 'charge', 'billing'] },
|
|
939
|
+
{ name: 'support-agent', categories: [TriageCategory.SUPPORT], keywords: ['help', 'issue', 'problem', 'broken', 'support'] },
|
|
940
|
+
{ name: 'sales-agent', categories: [TriageCategory.SALES], keywords: ['buy', 'purchase', 'pricing', 'discount', 'order'] },
|
|
941
|
+
{ name: 'technical-agent', categories: [TriageCategory.TECHNICAL], keywords: ['api', 'bug', 'error', 'integration', 'code'] },
|
|
942
|
+
{ name: 'general-agent', categories: [TriageCategory.GENERAL], keywords: [] }
|
|
943
|
+
];
|
|
944
|
+
|
|
945
|
+
const skillNames = options.skills || defaultSkills.map(s => s.name);
|
|
946
|
+
|
|
947
|
+
for (const skillName of skillNames) {
|
|
948
|
+
const skill = engine.getSkill(skillName);
|
|
949
|
+
const defaultSkill = defaultSkills.find(s => s.name === skillName);
|
|
950
|
+
|
|
951
|
+
const agentObj = {
|
|
952
|
+
name: skillName,
|
|
953
|
+
description: skill?.description || `${skillName} agent`,
|
|
954
|
+
execute: skill?.execute || (async (input) => ({ agent: skillName, input, executed: true }))
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
agentObj.capability = new AgentCapability({
|
|
958
|
+
agent: agentObj, // Reference to agent object
|
|
959
|
+
categories: defaultSkill?.categories || [TriageCategory.GENERAL],
|
|
960
|
+
keywords: defaultSkill?.keywords || [],
|
|
961
|
+
priority: 1
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
agents.push(agentObj);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
const context = await engine.execute(PatternType.TRIAGE, {
|
|
968
|
+
task: 'Classify and route request',
|
|
969
|
+
input: {
|
|
970
|
+
message: options.message,
|
|
971
|
+
agents,
|
|
972
|
+
strategy,
|
|
973
|
+
defaultCategory: options.defaultCategory.toUpperCase(),
|
|
974
|
+
confidenceThreshold: parseFloat(options.threshold),
|
|
975
|
+
enableHandoff: options.autoHandoff // Only handoff if explicitly requested
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
if (options.format === 'json') {
|
|
980
|
+
console.log(JSON.stringify({
|
|
981
|
+
success: context.status === ExecutionStatus.COMPLETED,
|
|
982
|
+
classification: context.output?.classification,
|
|
983
|
+
selectedAgent: context.output?.selectedAgent,
|
|
984
|
+
confidence: context.output?.confidence,
|
|
985
|
+
category: context.output?.category
|
|
986
|
+
}, null, 2));
|
|
987
|
+
} else {
|
|
988
|
+
if (context.status === ExecutionStatus.COMPLETED) {
|
|
989
|
+
console.log(chalk.green('✓ Triage completed\n'));
|
|
990
|
+
|
|
991
|
+
const classification = context.output?.classification || context.output;
|
|
992
|
+
|
|
993
|
+
console.log(chalk.bold('Classification Result:'));
|
|
994
|
+
console.log(` Category: ${chalk.cyan(classification?.category || 'N/A')}`);
|
|
995
|
+
console.log(` Confidence: ${formatConfidence(classification?.confidence || 0)}`);
|
|
996
|
+
if (classification?.reasoning) {
|
|
997
|
+
console.log(` Reasoning: ${chalk.dim(classification.reasoning)}`);
|
|
998
|
+
}
|
|
999
|
+
console.log('');
|
|
1000
|
+
|
|
1001
|
+
if (context.output?.selectedAgent) {
|
|
1002
|
+
console.log(chalk.bold('Routing Decision:'));
|
|
1003
|
+
console.log(` Selected Agent: ${chalk.yellow(context.output.selectedAgent)}`);
|
|
1004
|
+
console.log(` Agent Score: ${(context.output.agentScore || 0).toFixed(2)}`);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (options.autoHandoff && context.output?.handoffResult) {
|
|
1008
|
+
console.log('');
|
|
1009
|
+
console.log(chalk.bold('Handoff Result:'));
|
|
1010
|
+
console.log(` Status: ${chalk.green('Executed')}`);
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
console.log(chalk.red(`✗ Triage failed: ${context.error}\n`));
|
|
1014
|
+
process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
console.log('');
|
|
1019
|
+
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}\n`));
|
|
1022
|
+
if (process.env.DEBUG) {
|
|
1023
|
+
console.error(error.stack);
|
|
1024
|
+
}
|
|
1025
|
+
process.exit(1);
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
// Triage categories command - List available categories
|
|
1030
|
+
program
|
|
1031
|
+
.command('triage-categories')
|
|
1032
|
+
.description('List available triage categories')
|
|
1033
|
+
.option('-f, --format <type>', 'Output format (text|json)', 'text')
|
|
1034
|
+
.action(async (options) => {
|
|
1035
|
+
try {
|
|
1036
|
+
console.log(chalk.bold('\n📋 Triage Categories\n'));
|
|
1037
|
+
|
|
1038
|
+
const categories = Object.entries(TriageCategory).map(([key, value]) => ({
|
|
1039
|
+
key,
|
|
1040
|
+
value,
|
|
1041
|
+
description: getCategoryDescription(value)
|
|
1042
|
+
}));
|
|
1043
|
+
|
|
1044
|
+
if (options.format === 'json') {
|
|
1045
|
+
console.log(JSON.stringify(categories, null, 2));
|
|
1046
|
+
} else {
|
|
1047
|
+
for (const cat of categories) {
|
|
1048
|
+
console.log(` ${chalk.cyan(cat.value.padEnd(12))} - ${cat.description}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
console.log('');
|
|
1053
|
+
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}\n`));
|
|
1056
|
+
process.exit(1);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Get category description
|
|
1062
|
+
*/
|
|
1063
|
+
function getCategoryDescription(category) {
|
|
1064
|
+
const descriptions = {
|
|
1065
|
+
[TriageCategory.BILLING]: 'Billing, invoices, payments, refunds',
|
|
1066
|
+
[TriageCategory.SUPPORT]: 'General support, help requests, issues',
|
|
1067
|
+
[TriageCategory.SALES]: 'Sales inquiries, purchases, pricing',
|
|
1068
|
+
[TriageCategory.TECHNICAL]: 'Technical issues, bugs, API, integrations',
|
|
1069
|
+
[TriageCategory.REFUND]: 'Refund requests and processing',
|
|
1070
|
+
[TriageCategory.GENERAL]: 'General inquiries, catch-all category',
|
|
1071
|
+
[TriageCategory.ESCALATION]: 'Escalated issues requiring supervisor attention',
|
|
1072
|
+
[TriageCategory.UNKNOWN]: 'Unclassified requests'
|
|
1073
|
+
};
|
|
1074
|
+
return descriptions[category] || 'Unknown category';
|
|
1075
|
+
}
|
|
1076
|
+
|
|
761
1077
|
program.parse(process.argv);
|
|
762
1078
|
|
|
763
1079
|
if (!process.argv.slice(2).length) {
|
package/bin/musubi-validate.js
CHANGED
|
@@ -11,12 +11,20 @@
|
|
|
11
11
|
* musubi-validate article <1-9> # Validate specific article
|
|
12
12
|
* musubi-validate gates # Validate Phase -1 Gates
|
|
13
13
|
* musubi-validate complexity # Validate complexity limits
|
|
14
|
+
* musubi-validate guardrails # Validate content with guardrails
|
|
14
15
|
* musubi-validate all # Run all validations
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
const { Command } = require('commander');
|
|
18
19
|
const chalk = require('chalk');
|
|
19
20
|
const ConstitutionValidator = require('../src/validators/constitution');
|
|
21
|
+
const {
|
|
22
|
+
createInputGuardrail,
|
|
23
|
+
createOutputGuardrail,
|
|
24
|
+
createSafetyCheckGuardrail,
|
|
25
|
+
GuardrailChain,
|
|
26
|
+
SafetyLevel
|
|
27
|
+
} = require('../src/orchestration/guardrails');
|
|
20
28
|
|
|
21
29
|
const program = new Command();
|
|
22
30
|
|
|
@@ -107,6 +115,134 @@ program
|
|
|
107
115
|
}
|
|
108
116
|
});
|
|
109
117
|
|
|
118
|
+
// Guardrails validation
|
|
119
|
+
program
|
|
120
|
+
.command('guardrails')
|
|
121
|
+
.description('Validate content with input/output guardrails')
|
|
122
|
+
.argument('[content]', 'Content to validate (or use --file)')
|
|
123
|
+
.option('--file <path>', 'Read content from file')
|
|
124
|
+
.option('-t, --type <type>', 'Guardrail type (input|output|safety)', 'input')
|
|
125
|
+
.option('-l, --level <level>', 'Safety level (basic|standard|strict|paranoid)', 'standard')
|
|
126
|
+
.option('--constitutional', 'Enable constitutional compliance checks')
|
|
127
|
+
.option('--redact', 'Enable redaction for output guardrails')
|
|
128
|
+
.option('-f, --format <type>', 'Output format (console|json)', 'console')
|
|
129
|
+
.action(async (content, options) => {
|
|
130
|
+
try {
|
|
131
|
+
// Get content from argument, file, or stdin
|
|
132
|
+
let inputContent = content;
|
|
133
|
+
|
|
134
|
+
if (options.file) {
|
|
135
|
+
const fs = require('fs');
|
|
136
|
+
const path = require('path');
|
|
137
|
+
const filePath = path.resolve(process.cwd(), options.file);
|
|
138
|
+
inputContent = fs.readFileSync(filePath, 'utf-8');
|
|
139
|
+
} else if (!inputContent && !process.stdin.isTTY) {
|
|
140
|
+
// Read from stdin
|
|
141
|
+
const chunks = [];
|
|
142
|
+
for await (const chunk of process.stdin) {
|
|
143
|
+
chunks.push(chunk);
|
|
144
|
+
}
|
|
145
|
+
inputContent = Buffer.concat(chunks).toString('utf-8');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!inputContent) {
|
|
149
|
+
console.error(chalk.red('✗ No content provided. Use --file or pipe content.'));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let guardrail;
|
|
154
|
+
const guardrailType = options.type.toLowerCase();
|
|
155
|
+
|
|
156
|
+
switch (guardrailType) {
|
|
157
|
+
case 'input':
|
|
158
|
+
guardrail = createInputGuardrail('userInput', {
|
|
159
|
+
sanitize: true
|
|
160
|
+
});
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case 'output':
|
|
164
|
+
guardrail = createOutputGuardrail(options.redact ? 'redact' : 'safe');
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case 'safety':
|
|
168
|
+
guardrail = createSafetyCheckGuardrail(options.level, {
|
|
169
|
+
enforceConstitution: options.constitutional
|
|
170
|
+
});
|
|
171
|
+
break;
|
|
172
|
+
|
|
173
|
+
default:
|
|
174
|
+
console.error(chalk.red(`✗ Unknown guardrail type: ${guardrailType}`));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.dim(`\n🛡️ Running ${guardrailType} guardrail validation...\n`));
|
|
179
|
+
|
|
180
|
+
const result = await guardrail.run(inputContent);
|
|
181
|
+
|
|
182
|
+
displayGuardrailResults(result, options);
|
|
183
|
+
process.exit(result.passed ? 0 : 1);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error(chalk.red('✗ Guardrail validation error:'), error.message);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Guardrails chain validation
|
|
191
|
+
program
|
|
192
|
+
.command('guardrails-chain')
|
|
193
|
+
.description('Run a chain of guardrails on content')
|
|
194
|
+
.argument('[content]', 'Content to validate')
|
|
195
|
+
.option('--file <path>', 'Read content from file')
|
|
196
|
+
.option('--parallel', 'Run guardrails in parallel')
|
|
197
|
+
.option('--stop-on-failure', 'Stop on first failure')
|
|
198
|
+
.option('-f, --format <type>', 'Output format (console|json)', 'console')
|
|
199
|
+
.action(async (content, options) => {
|
|
200
|
+
try {
|
|
201
|
+
// Get content
|
|
202
|
+
let inputContent = content;
|
|
203
|
+
|
|
204
|
+
if (options.file) {
|
|
205
|
+
const fs = require('fs');
|
|
206
|
+
const path = require('path');
|
|
207
|
+
const filePath = path.resolve(process.cwd(), options.file);
|
|
208
|
+
inputContent = fs.readFileSync(filePath, 'utf-8');
|
|
209
|
+
} else if (!inputContent && !process.stdin.isTTY) {
|
|
210
|
+
const chunks = [];
|
|
211
|
+
for await (const chunk of process.stdin) {
|
|
212
|
+
chunks.push(chunk);
|
|
213
|
+
}
|
|
214
|
+
inputContent = Buffer.concat(chunks).toString('utf-8');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!inputContent) {
|
|
218
|
+
console.error(chalk.red('✗ No content provided. Use --file or pipe content.'));
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create guardrail chain
|
|
223
|
+
const chain = new GuardrailChain({
|
|
224
|
+
name: 'ValidationChain',
|
|
225
|
+
parallel: options.parallel || false,
|
|
226
|
+
stopOnFirstFailure: options.stopOnFailure || false
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Add default guardrails
|
|
230
|
+
chain.add(createInputGuardrail('security'));
|
|
231
|
+
chain.add(createSafetyCheckGuardrail('standard'));
|
|
232
|
+
chain.add(createOutputGuardrail('safe'));
|
|
233
|
+
|
|
234
|
+
console.log(chalk.dim('\n🔗 Running guardrail chain validation...\n'));
|
|
235
|
+
|
|
236
|
+
const result = await chain.run(inputContent);
|
|
237
|
+
|
|
238
|
+
displayChainResults(result, options);
|
|
239
|
+
process.exit(result.passed ? 0 : 1);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error(chalk.red('✗ Guardrail chain error:'), error.message);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
110
246
|
// Score validation (numeric score output for CI/CD)
|
|
111
247
|
program
|
|
112
248
|
.command('score')
|
|
@@ -345,6 +481,104 @@ function displayMarkdown(title, results) {
|
|
|
345
481
|
}
|
|
346
482
|
}
|
|
347
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Display guardrail validation results
|
|
486
|
+
*/
|
|
487
|
+
function displayGuardrailResults(result, options) {
|
|
488
|
+
if (options.format === 'json') {
|
|
489
|
+
console.log(JSON.stringify(result, null, 2));
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log(chalk.bold('Guardrail Validation Results'));
|
|
494
|
+
console.log(chalk.bold('━'.repeat(50)));
|
|
495
|
+
|
|
496
|
+
if (result.passed) {
|
|
497
|
+
console.log(chalk.green(`\n✓ PASSED - ${result.guardrailName}\n`));
|
|
498
|
+
} else {
|
|
499
|
+
console.log(chalk.red(`\n✗ FAILED - ${result.guardrailName}\n`));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (result.message) {
|
|
503
|
+
console.log(chalk.dim(`Message: ${result.message}`));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
console.log(chalk.dim(`Execution time: ${result.executionTimeMs}ms\n`));
|
|
507
|
+
|
|
508
|
+
if (result.violations && result.violations.length > 0) {
|
|
509
|
+
console.log(chalk.bold.red('Violations:'));
|
|
510
|
+
result.violations.forEach(violation => {
|
|
511
|
+
const severityIcon = violation.severity === 'error' ? '✗' :
|
|
512
|
+
violation.severity === 'warning' ? '⚠' : 'ℹ';
|
|
513
|
+
const severityColor = violation.severity === 'error' ? chalk.red :
|
|
514
|
+
violation.severity === 'warning' ? chalk.yellow : chalk.blue;
|
|
515
|
+
console.log(severityColor(` ${severityIcon} [${violation.code}] ${violation.message}`));
|
|
516
|
+
});
|
|
517
|
+
console.log();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Show metadata if redaction was applied
|
|
521
|
+
if (result.metadata?.redactionApplied) {
|
|
522
|
+
console.log(chalk.bold.blue('Redaction Applied:'));
|
|
523
|
+
console.log(chalk.dim(` ${result.metadata.redactionCount} item(s) redacted`));
|
|
524
|
+
if (result.metadata.redactions) {
|
|
525
|
+
result.metadata.redactions.forEach(r => {
|
|
526
|
+
console.log(chalk.dim(` - ${r.type}: ${r.count}`));
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
console.log();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Show quality scores if available
|
|
533
|
+
if (result.metadata?.qualityScores) {
|
|
534
|
+
console.log(chalk.bold.blue('Quality Scores:'));
|
|
535
|
+
Object.entries(result.metadata.qualityScores).forEach(([name, score]) => {
|
|
536
|
+
console.log(chalk.dim(` ${name}: ${(score * 100).toFixed(1)}%`));
|
|
537
|
+
});
|
|
538
|
+
console.log();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Display guardrail chain results
|
|
544
|
+
*/
|
|
545
|
+
function displayChainResults(result, options) {
|
|
546
|
+
if (options.format === 'json') {
|
|
547
|
+
console.log(JSON.stringify(result, null, 2));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
console.log(chalk.bold('Guardrail Chain Results'));
|
|
552
|
+
console.log(chalk.bold('━'.repeat(50)));
|
|
553
|
+
|
|
554
|
+
if (result.passed) {
|
|
555
|
+
console.log(chalk.green(`\n✓ PASSED - ${result.chainName}\n`));
|
|
556
|
+
} else {
|
|
557
|
+
console.log(chalk.red(`\n✗ FAILED - ${result.chainName}\n`));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
console.log(chalk.dim(`Guardrails executed: ${result.executedCount}/${result.guardrailCount}`));
|
|
561
|
+
console.log(chalk.dim(`Total time: ${result.executionTimeMs}ms\n`));
|
|
562
|
+
|
|
563
|
+
// Show individual guardrail results
|
|
564
|
+
console.log(chalk.bold('Individual Results:'));
|
|
565
|
+
result.results.forEach((r, index) => {
|
|
566
|
+
const icon = r.passed ? chalk.green('✓') : chalk.red('✗');
|
|
567
|
+
console.log(` ${icon} ${r.guardrailName} (${r.executionTimeMs}ms)`);
|
|
568
|
+
});
|
|
569
|
+
console.log();
|
|
570
|
+
|
|
571
|
+
// Show all violations
|
|
572
|
+
if (result.violations && result.violations.length > 0) {
|
|
573
|
+
console.log(chalk.bold.red('All Violations:'));
|
|
574
|
+
result.violations.forEach(violation => {
|
|
575
|
+
const severityColor = violation.severity === 'error' ? chalk.red : chalk.yellow;
|
|
576
|
+
console.log(severityColor(` • [${violation.code}] ${violation.message}`));
|
|
577
|
+
});
|
|
578
|
+
console.log();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
348
582
|
// Parse arguments
|
|
349
583
|
program.parse(process.argv);
|
|
350
584
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musubi-sdd",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.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": {
|