huaweicloud-devkit 1.0.2-next.2 → 1.0.2-next.3

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 (97) hide show
  1. package/.agents/plugins/marketplace.json +20 -20
  2. package/LICENSE +201 -201
  3. package/README.md +163 -163
  4. package/README.zh-CN.md +161 -161
  5. package/bin/setup.cjs +8 -8
  6. package/integrations/opencode/commands/huaweicloud-doctor.md +14 -14
  7. package/integrations/opencode/opencode.json +14 -14
  8. package/integrations/opencode/skills/huaweicloud-core/SKILL.md +32 -32
  9. package/package.json +47 -46
  10. package/plugins/huaweicloud-core/.claude-plugin/plugin.json +44 -44
  11. package/plugins/huaweicloud-core/.codex-plugin/plugin.json +43 -43
  12. package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +44 -44
  13. package/plugins/huaweicloud-core/.mcp.json +15 -15
  14. package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +44 -44
  15. package/plugins/huaweicloud-core/hooks/hooks.json +26 -26
  16. package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +136 -136
  17. package/plugins/huaweicloud-core/safety/policy.json +99 -99
  18. package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +199 -199
  19. package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +164 -164
  20. package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +48 -48
  21. package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +50 -50
  22. package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +101 -101
  23. package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +90 -90
  24. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +190 -190
  25. package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +124 -124
  26. package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +55 -55
  27. package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +70 -70
  28. package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +56 -56
  29. package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +97 -97
  30. package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +29 -29
  31. package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +23 -23
  32. package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +185 -185
  33. package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +133 -133
  34. package/plugins/huaweicloud-core/skills/huawei-ecs/references/evs.md +31 -31
  35. package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +55 -55
  36. package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +52 -52
  37. package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +131 -131
  38. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +58 -58
  39. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +71 -71
  40. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/deploy-workflow.md +119 -119
  41. package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +142 -142
  42. package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +56 -56
  43. package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +65 -65
  44. package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +111 -111
  45. package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +122 -122
  46. package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +56 -56
  47. package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +65 -65
  48. package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +133 -133
  49. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +49 -49
  50. package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +26 -26
  51. package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +100 -100
  52. package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +40 -40
  53. package/plugins/huaweicloud-core/skills/huawei-obs/references/static-website.md +56 -56
  54. package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +224 -224
  55. package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +139 -135
  56. package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +69 -69
  57. package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +84 -84
  58. package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +76 -76
  59. package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +59 -59
  60. package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +50 -50
  61. package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +63 -63
  62. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +56 -56
  63. package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +4 -4
  64. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +51 -51
  65. package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +4 -4
  66. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +128 -128
  67. package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +4 -4
  68. package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +110 -110
  69. package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +4 -4
  70. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +19 -19
  71. package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +27 -27
  72. package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +60 -60
  73. package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +4 -4
  74. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +83 -83
  75. package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +4 -4
  76. package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +105 -105
  77. package/plugins/huaweicloud-core/src/auth/credentials.mjs +95 -95
  78. package/plugins/huaweicloud-core/src/auth/service.mjs +63 -63
  79. package/plugins/huaweicloud-core/src/hcloud-cli.mjs +265 -265
  80. package/plugins/huaweicloud-core/src/mcp-server.mjs +129 -129
  81. package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +155 -155
  82. package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +87 -87
  83. package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +137 -137
  84. package/plugins/huaweicloud-core/src/safety-policy.mjs +302 -302
  85. package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +91 -91
  86. package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +162 -162
  87. package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +112 -112
  88. package/plugins/huaweicloud-core/src/search-market.mjs +174 -174
  89. package/plugins/huaweicloud-core/src/setup-cli.mjs +2017 -2017
  90. package/plugins/huaweicloud-core/src/tools.mjs +1008 -1008
  91. package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +427 -427
  92. package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +132 -132
  93. package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +227 -227
  94. package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +202 -202
  95. package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +158 -158
  96. package/plugins/huaweicloud-core/src/ws-exec/index.js +19 -19
  97. package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +338 -338
@@ -1,87 +1,87 @@
1
- import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { homedir } from 'node:os';
4
-
5
- function baseHome() {
6
- return process.env.HUAWEICLOUD_HOME || homedir();
7
- }
8
-
9
- export function proxyConfigPath() {
10
- return join(baseHome(), '.config', 'huaweicloud', 'proxy.json');
11
- }
12
-
13
- export function readProxyConfig() {
14
- const path = proxyConfigPath();
15
- if (!existsSync(path)) return null;
16
- try {
17
- return JSON.parse(readFileSync(path, 'utf8'));
18
- } catch {
19
- return null;
20
- }
21
- }
22
-
23
- export function writeProxyConfig(config = {}) {
24
- const path = proxyConfigPath();
25
- mkdirSync(dirname(path), { recursive: true });
26
- const payload = {
27
- https_proxy: String(config.https_proxy || ''),
28
- http_proxy: String(config.http_proxy || ''),
29
- no_proxy: String(config.no_proxy || ''),
30
- };
31
- writeFileSync(path, JSON.stringify(payload, null, 2), { encoding: 'utf8' });
32
- return path;
33
- }
34
-
35
- export function clearProxyConfig() {
36
- const path = proxyConfigPath();
37
- if (!existsSync(path)) return false;
38
- rmSync(path);
39
- return true;
40
- }
41
-
42
- function shouldBypassProxy(hostname, noProxyList) {
43
- if (!noProxyList.length) return false;
44
- const lower = hostname.toLowerCase();
45
- for (const pattern of noProxyList) {
46
- const p = pattern.trim().toLowerCase();
47
- if (!p) continue;
48
- if (p === lower) return true;
49
- if (p === '*') return true;
50
- if (p.startsWith('.') && lower.endsWith(p)) return true;
51
- if (lower.endsWith('.' + p)) return true;
52
- }
53
- return false;
54
- }
55
-
56
- export function getProxySettings(targetUrl) {
57
- const envHttps = process.env.HTTPS_PROXY || process.env.https_proxy || '';
58
- const envHttp = process.env.HTTP_PROXY || process.env.http_proxy || '';
59
- const envNoProxy = process.env.NO_PROXY || process.env.no_proxy || '';
60
-
61
- const fileConfig = readProxyConfig();
62
-
63
- const https_proxy = envHttps || (fileConfig?.https_proxy || '');
64
- const http_proxy = envHttp || (fileConfig?.http_proxy || '');
65
- const no_proxy = envNoProxy || (fileConfig?.no_proxy || '');
66
-
67
- if (!https_proxy && !http_proxy) return null;
68
-
69
- const noProxyList = no_proxy.split(',').map((s) => s.trim()).filter(Boolean);
70
-
71
- if (targetUrl) {
72
- const parsed = new URL(targetUrl);
73
- if (shouldBypassProxy(parsed.hostname, noProxyList)) return null;
74
- const isHttps = parsed.protocol === 'https:';
75
- const proxyUrl = isHttps ? https_proxy : http_proxy;
76
- if (!proxyUrl) return null;
77
- return { proxyUrl, noProxyList, targetProtocol: parsed.protocol };
78
- }
79
-
80
- return { https_proxy, http_proxy, no_proxy, noProxyList };
81
- }
82
-
83
- export function getProxyUrlForTarget(targetUrl) {
84
- const settings = getProxySettings(targetUrl);
85
- if (!settings) return null;
86
- return settings.proxyUrl || null;
87
- }
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ function baseHome() {
6
+ return process.env.HUAWEICLOUD_HOME || homedir();
7
+ }
8
+
9
+ export function proxyConfigPath() {
10
+ return join(baseHome(), '.config', 'huaweicloud', 'proxy.json');
11
+ }
12
+
13
+ export function readProxyConfig() {
14
+ const path = proxyConfigPath();
15
+ if (!existsSync(path)) return null;
16
+ try {
17
+ return JSON.parse(readFileSync(path, 'utf8'));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function writeProxyConfig(config = {}) {
24
+ const path = proxyConfigPath();
25
+ mkdirSync(dirname(path), { recursive: true });
26
+ const payload = {
27
+ https_proxy: String(config.https_proxy || ''),
28
+ http_proxy: String(config.http_proxy || ''),
29
+ no_proxy: String(config.no_proxy || ''),
30
+ };
31
+ writeFileSync(path, JSON.stringify(payload, null, 2), { encoding: 'utf8' });
32
+ return path;
33
+ }
34
+
35
+ export function clearProxyConfig() {
36
+ const path = proxyConfigPath();
37
+ if (!existsSync(path)) return false;
38
+ rmSync(path);
39
+ return true;
40
+ }
41
+
42
+ function shouldBypassProxy(hostname, noProxyList) {
43
+ if (!noProxyList.length) return false;
44
+ const lower = hostname.toLowerCase();
45
+ for (const pattern of noProxyList) {
46
+ const p = pattern.trim().toLowerCase();
47
+ if (!p) continue;
48
+ if (p === lower) return true;
49
+ if (p === '*') return true;
50
+ if (p.startsWith('.') && lower.endsWith(p)) return true;
51
+ if (lower.endsWith('.' + p)) return true;
52
+ }
53
+ return false;
54
+ }
55
+
56
+ export function getProxySettings(targetUrl) {
57
+ const envHttps = process.env.HTTPS_PROXY || process.env.https_proxy || '';
58
+ const envHttp = process.env.HTTP_PROXY || process.env.http_proxy || '';
59
+ const envNoProxy = process.env.NO_PROXY || process.env.no_proxy || '';
60
+
61
+ const fileConfig = readProxyConfig();
62
+
63
+ const https_proxy = envHttps || (fileConfig?.https_proxy || '');
64
+ const http_proxy = envHttp || (fileConfig?.http_proxy || '');
65
+ const no_proxy = envNoProxy || (fileConfig?.no_proxy || '');
66
+
67
+ if (!https_proxy && !http_proxy) return null;
68
+
69
+ const noProxyList = no_proxy.split(',').map((s) => s.trim()).filter(Boolean);
70
+
71
+ if (targetUrl) {
72
+ const parsed = new URL(targetUrl);
73
+ if (shouldBypassProxy(parsed.hostname, noProxyList)) return null;
74
+ const isHttps = parsed.protocol === 'https:';
75
+ const proxyUrl = isHttps ? https_proxy : http_proxy;
76
+ if (!proxyUrl) return null;
77
+ return { proxyUrl, noProxyList, targetProtocol: parsed.protocol };
78
+ }
79
+
80
+ return { https_proxy, http_proxy, no_proxy, noProxyList };
81
+ }
82
+
83
+ export function getProxyUrlForTarget(targetUrl) {
84
+ const settings = getProxySettings(targetUrl);
85
+ if (!settings) return null;
86
+ return settings.proxyUrl || null;
87
+ }
@@ -1,137 +1,137 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const defaultRulesPath = join(__dirname, '..', 'safety', 'rules', 'cloud-risk-rules.json');
7
-
8
- const SEVERITY_RANK = {
9
- deny: 3,
10
- warn: 2,
11
- info: 1,
12
- };
13
-
14
- export function loadRiskRules(options = {}) {
15
- const path = options.path || defaultRulesPath;
16
- return JSON.parse(readFileSync(path, 'utf8'));
17
- }
18
-
19
- function redactEvidence(text) {
20
- return String(text)
21
- .replace(/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1<redacted>')
22
- .replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
23
- }
24
-
25
- function normalizeText(value) {
26
- if (typeof value === 'string') return value;
27
- return JSON.stringify(value, null, 2);
28
- }
29
-
30
- function evaluationContext(stage, input) {
31
- if (stage === 'command') {
32
- const command = normalizeText(input.command || input.text || '');
33
- return { text: command, command };
34
- }
35
- if (stage === 'artifact') {
36
- const path = String(input.path || '');
37
- const content = normalizeText(input.content || '');
38
- return { text: `${path}\n${content}`, path, content };
39
- }
40
- if (stage === 'deploy_plan') {
41
- const plan = normalizeText(input.plan || input.text || input);
42
- return { text: plan, plan };
43
- }
44
- return { text: normalizeText(input) };
45
- }
46
-
47
- function conditionMatches(condition, context) {
48
- const field = condition.field || 'text';
49
- const value = Object.hasOwn(context, field) ? context[field] : context.text;
50
- return new RegExp(condition.regex, 'ims').test(String(value || ''));
51
- }
52
-
53
- function ruleMatches(rule, context) {
54
- const match = rule.match || {};
55
- const all = match.all;
56
- const any = match.any;
57
- const none = match.none;
58
- if (Array.isArray(all) && !all.every((condition) => conditionMatches(condition, context))) {
59
- return false;
60
- }
61
- if (Array.isArray(any) && !any.some((condition) => conditionMatches(condition, context))) {
62
- return false;
63
- }
64
- if (Array.isArray(none) && none.some((condition) => conditionMatches(condition, context))) {
65
- return false;
66
- }
67
- return Array.isArray(all) || Array.isArray(any);
68
- }
69
-
70
- function excerpt(text) {
71
- const compact = redactEvidence(String(text).replace(/\s+/g, ' ').trim());
72
- if (compact.length <= 240) return compact;
73
- return `${compact.slice(0, 237)}...`;
74
- }
75
-
76
- function evaluate(stage, inputs, options = {}) {
77
- const catalog = options.catalog || loadRiskRules(options);
78
- const items = Array.isArray(inputs) ? inputs : [inputs];
79
- const findings = [];
80
-
81
- for (const input of items) {
82
- const context = evaluationContext(stage, input || {});
83
- for (const rule of catalog.rules) {
84
- if (!rule.stages.includes(stage)) continue;
85
- if (!ruleMatches(rule, context)) continue;
86
- findings.push({
87
- ruleId: rule.id,
88
- title: rule.title,
89
- category: rule.category,
90
- severity: rule.severity,
91
- message: rule.message,
92
- remediation: rule.remediation,
93
- source: input?.path || stage,
94
- evidence: excerpt(context.text),
95
- });
96
- }
97
- }
98
-
99
- findings.sort((a, b) => SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]);
100
- const hasDeny = findings.some((finding) => finding.severity === 'deny');
101
- const hasWarn = findings.some((finding) => finding.severity === 'warn');
102
- return {
103
- decision: hasDeny ? 'deny' : hasWarn ? 'warn' : 'allow',
104
- findings,
105
- };
106
- }
107
-
108
- export function evaluateCommandRisk(command, options = {}) {
109
- return evaluate('command', { command }, options);
110
- }
111
-
112
- export function evaluateArtifacts(artifacts, options = {}) {
113
- return evaluate('artifact', Array.isArray(artifacts) ? artifacts : [], options);
114
- }
115
-
116
- export function evaluateDeployPlan(plan, options = {}) {
117
- return evaluate('deploy_plan', { plan }, options);
118
- }
119
-
120
- export function mergeRiskDecision(base, risk) {
121
- if (!risk || !risk.findings?.length) return base;
122
- if (risk.decision === 'deny') {
123
- const topFinding = risk.findings[0];
124
- return {
125
- ...base,
126
- decision: 'deny',
127
- risk: topFinding.category,
128
- reason: topFinding.message,
129
- blockedByRiskRule: true,
130
- findings: risk.findings,
131
- };
132
- }
133
- return {
134
- ...base,
135
- warnings: [...(base.warnings || []), ...risk.findings],
136
- };
137
- }
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const defaultRulesPath = join(__dirname, '..', 'safety', 'rules', 'cloud-risk-rules.json');
7
+
8
+ const SEVERITY_RANK = {
9
+ deny: 3,
10
+ warn: 2,
11
+ info: 1,
12
+ };
13
+
14
+ export function loadRiskRules(options = {}) {
15
+ const path = options.path || defaultRulesPath;
16
+ return JSON.parse(readFileSync(path, 'utf8'));
17
+ }
18
+
19
+ function redactEvidence(text) {
20
+ return String(text)
21
+ .replace(/((?:access[_-]?key|secret[_-]?key|security[_-]?token|x[_-]?auth[_-]?token|authorization|password|passwd|adminPass|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;]+)/gi, '$1<redacted>')
22
+ .replace(/(AK|SK)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;]+)/g, '$1=<redacted>');
23
+ }
24
+
25
+ function normalizeText(value) {
26
+ if (typeof value === 'string') return value;
27
+ return JSON.stringify(value, null, 2);
28
+ }
29
+
30
+ function evaluationContext(stage, input) {
31
+ if (stage === 'command') {
32
+ const command = normalizeText(input.command || input.text || '');
33
+ return { text: command, command };
34
+ }
35
+ if (stage === 'artifact') {
36
+ const path = String(input.path || '');
37
+ const content = normalizeText(input.content || '');
38
+ return { text: `${path}\n${content}`, path, content };
39
+ }
40
+ if (stage === 'deploy_plan') {
41
+ const plan = normalizeText(input.plan || input.text || input);
42
+ return { text: plan, plan };
43
+ }
44
+ return { text: normalizeText(input) };
45
+ }
46
+
47
+ function conditionMatches(condition, context) {
48
+ const field = condition.field || 'text';
49
+ const value = Object.hasOwn(context, field) ? context[field] : context.text;
50
+ return new RegExp(condition.regex, 'ims').test(String(value || ''));
51
+ }
52
+
53
+ function ruleMatches(rule, context) {
54
+ const match = rule.match || {};
55
+ const all = match.all;
56
+ const any = match.any;
57
+ const none = match.none;
58
+ if (Array.isArray(all) && !all.every((condition) => conditionMatches(condition, context))) {
59
+ return false;
60
+ }
61
+ if (Array.isArray(any) && !any.some((condition) => conditionMatches(condition, context))) {
62
+ return false;
63
+ }
64
+ if (Array.isArray(none) && none.some((condition) => conditionMatches(condition, context))) {
65
+ return false;
66
+ }
67
+ return Array.isArray(all) || Array.isArray(any);
68
+ }
69
+
70
+ function excerpt(text) {
71
+ const compact = redactEvidence(String(text).replace(/\s+/g, ' ').trim());
72
+ if (compact.length <= 240) return compact;
73
+ return `${compact.slice(0, 237)}...`;
74
+ }
75
+
76
+ function evaluate(stage, inputs, options = {}) {
77
+ const catalog = options.catalog || loadRiskRules(options);
78
+ const items = Array.isArray(inputs) ? inputs : [inputs];
79
+ const findings = [];
80
+
81
+ for (const input of items) {
82
+ const context = evaluationContext(stage, input || {});
83
+ for (const rule of catalog.rules) {
84
+ if (!rule.stages.includes(stage)) continue;
85
+ if (!ruleMatches(rule, context)) continue;
86
+ findings.push({
87
+ ruleId: rule.id,
88
+ title: rule.title,
89
+ category: rule.category,
90
+ severity: rule.severity,
91
+ message: rule.message,
92
+ remediation: rule.remediation,
93
+ source: input?.path || stage,
94
+ evidence: excerpt(context.text),
95
+ });
96
+ }
97
+ }
98
+
99
+ findings.sort((a, b) => SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]);
100
+ const hasDeny = findings.some((finding) => finding.severity === 'deny');
101
+ const hasWarn = findings.some((finding) => finding.severity === 'warn');
102
+ return {
103
+ decision: hasDeny ? 'deny' : hasWarn ? 'warn' : 'allow',
104
+ findings,
105
+ };
106
+ }
107
+
108
+ export function evaluateCommandRisk(command, options = {}) {
109
+ return evaluate('command', { command }, options);
110
+ }
111
+
112
+ export function evaluateArtifacts(artifacts, options = {}) {
113
+ return evaluate('artifact', Array.isArray(artifacts) ? artifacts : [], options);
114
+ }
115
+
116
+ export function evaluateDeployPlan(plan, options = {}) {
117
+ return evaluate('deploy_plan', { plan }, options);
118
+ }
119
+
120
+ export function mergeRiskDecision(base, risk) {
121
+ if (!risk || !risk.findings?.length) return base;
122
+ if (risk.decision === 'deny') {
123
+ const topFinding = risk.findings[0];
124
+ return {
125
+ ...base,
126
+ decision: 'deny',
127
+ risk: topFinding.category,
128
+ reason: topFinding.message,
129
+ blockedByRiskRule: true,
130
+ findings: risk.findings,
131
+ };
132
+ }
133
+ return {
134
+ ...base,
135
+ warnings: [...(base.warnings || []), ...risk.findings],
136
+ };
137
+ }