@cyclonedx/cdxgen 12.3.1 → 12.3.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.
- package/README.md +6 -0
- package/bin/cdxgen.js +1 -2
- package/data/rules/ai-agent-governance.yaml +43 -0
- package/data/rules/ci-permissions.yaml +132 -0
- package/data/rules/dependency-sources.yaml +65 -5
- package/data/rules/mcp-servers.yaml +36 -2
- package/data/rules/package-integrity.yaml +22 -0
- package/lib/cli/index.js +436 -56
- package/lib/cli/index.poku.js +875 -2
- package/lib/helpers/agentFormulationParser.js +10 -3
- package/lib/helpers/agentFormulationParser.poku.js +42 -0
- package/lib/helpers/aiInventory.js +262 -0
- package/lib/helpers/aiInventory.poku.js +111 -0
- package/lib/helpers/analyzer.js +413 -54
- package/lib/helpers/analyzer.poku.js +117 -0
- package/lib/helpers/auditCategories.js +76 -0
- package/lib/helpers/chromextutils.js +25 -3
- package/lib/helpers/chromextutils.poku.js +68 -0
- package/lib/helpers/ciParsers/githubActions.js +79 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +103 -0
- package/lib/helpers/communityAiConfigParser.js +15 -5
- package/lib/helpers/communityAiConfigParser.poku.js +71 -0
- package/lib/helpers/depsUtils.js +5 -0
- package/lib/helpers/depsUtils.poku.js +55 -0
- package/lib/helpers/display.js +50 -24
- package/lib/helpers/display.poku.js +70 -58
- package/lib/helpers/formulationParsers.js +26 -6
- package/lib/helpers/jsonLike.js +21 -20
- package/lib/helpers/jsonLike.poku.js +34 -0
- package/lib/helpers/mcpConfigParser.js +32 -16
- package/lib/helpers/mcpConfigParser.poku.js +104 -0
- package/lib/helpers/mcpDiscovery.js +13 -23
- package/lib/helpers/mcpDiscovery.poku.js +21 -0
- package/lib/helpers/propertySanitizer.js +121 -0
- package/lib/helpers/utils.js +953 -41
- package/lib/helpers/utils.poku.js +901 -1
- package/lib/managers/binary.js +16 -0
- package/lib/managers/binary.poku.js +1 -0
- package/lib/managers/docker.js +240 -16
- package/lib/managers/docker.poku.js +1142 -2
- package/lib/server/server.js +7 -4
- package/lib/server/server.poku.js +36 -1
- package/lib/stages/postgen/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +15 -0
- package/lib/stages/postgen/auditBom.js +12 -6
- package/lib/stages/postgen/auditBom.poku.js +755 -6
- package/lib/stages/postgen/postgen.js +229 -6
- package/lib/stages/postgen/postgen.poku.js +180 -0
- package/package.json +2 -1
- package/types/lib/cli/index.d.ts +1 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/agentFormulationParser.d.ts +19 -0
- package/types/lib/helpers/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/helpers/aiInventory.d.ts +23 -0
- package/types/lib/helpers/aiInventory.d.ts.map +1 -0
- package/types/lib/helpers/analyzer.d.ts +5 -0
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/auditCategories.d.ts +12 -0
- package/types/lib/helpers/auditCategories.d.ts.map +1 -0
- package/types/lib/helpers/chromextutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/communityAiConfigParser.d.ts +29 -0
- package/types/lib/helpers/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts +1 -0
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -1
- package/types/lib/helpers/jsonLike.d.ts +4 -0
- package/types/lib/helpers/jsonLike.d.ts.map +1 -0
- package/types/lib/helpers/mcp.d.ts +29 -0
- package/types/lib/helpers/mcp.d.ts.map +1 -0
- package/types/lib/helpers/mcpConfigParser.d.ts +30 -0
- package/types/lib/helpers/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/helpers/mcpDiscovery.d.ts +5 -0
- package/types/lib/helpers/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/helpers/propertySanitizer.d.ts +3 -0
- package/types/lib/helpers/propertySanitizer.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +31 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +3 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
providerNamesForText,
|
|
8
8
|
sanitizeMcpRefToken,
|
|
9
9
|
} from "./mcpDiscovery.js";
|
|
10
|
+
import { sanitizeBomUrl } from "./propertySanitizer.js";
|
|
10
11
|
import { scanTextForHiddenUnicode } from "./unicodeScan.js";
|
|
11
12
|
|
|
12
13
|
const AGENT_FILE_PATTERNS = [
|
|
@@ -125,7 +126,7 @@ function buildInferredMcpServices(filePath, mcpUrls, authHints, providerNames) {
|
|
|
125
126
|
"bom-ref": `urn:service:agent-mcp:${sanitizeMcpRefToken(hostname || basename(filePath))}:${index + 1}`,
|
|
126
127
|
group: "mcp",
|
|
127
128
|
name: hostname || `${basename(filePath)}-mcp-endpoint`,
|
|
128
|
-
endpoints: [urlValue],
|
|
129
|
+
endpoints: [sanitizeBomUrl(urlValue)],
|
|
129
130
|
properties,
|
|
130
131
|
version: "inferred",
|
|
131
132
|
};
|
|
@@ -167,7 +168,10 @@ export const agentFormulationParser = {
|
|
|
167
168
|
packageRefs.length > 0 ||
|
|
168
169
|
/\bmcp\b/i.test(raw) ||
|
|
169
170
|
/modelcontextprotocol/i.test(raw);
|
|
170
|
-
|
|
171
|
+
// Inventory all matched agent/instruction files, even when they do not
|
|
172
|
+
// yet contain hidden Unicode or explicit MCP references, so shipped files
|
|
173
|
+
// such as CLAUDE.md and AGENTS.md still surface in build/post-build BOMs.
|
|
174
|
+
if (!raw.trim().length) {
|
|
171
175
|
continue;
|
|
172
176
|
}
|
|
173
177
|
const hiddenComponentKinds = [];
|
|
@@ -230,10 +234,13 @@ export const agentFormulationParser = {
|
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
if (mcpUrls.length) {
|
|
237
|
+
const sanitizedMcpUrls = mcpUrls.map((urlValue) =>
|
|
238
|
+
sanitizeBomUrl(urlValue),
|
|
239
|
+
);
|
|
233
240
|
properties.push(
|
|
234
241
|
{
|
|
235
242
|
name: "cdx:agent:hiddenMcpUrls",
|
|
236
|
-
value:
|
|
243
|
+
value: sanitizedMcpUrls.join(","),
|
|
237
244
|
},
|
|
238
245
|
{
|
|
239
246
|
name: "cdx:agent:hiddenMcpHosts",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import esmock from "esmock";
|
|
2
|
+
import { assert, describe, it } from "poku";
|
|
3
|
+
import sinon from "sinon";
|
|
4
|
+
|
|
5
|
+
function getProp(obj, name) {
|
|
6
|
+
return obj?.properties?.find((property) => property.name === name)?.value;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe("agentFormulationParser", () => {
|
|
10
|
+
it("sanitizes inferred MCP URLs before emitting them", async () => {
|
|
11
|
+
const readFileSync = sinon.stub();
|
|
12
|
+
const scanTextForHiddenUnicode = sinon.stub().returns({
|
|
13
|
+
hasHiddenUnicode: false,
|
|
14
|
+
});
|
|
15
|
+
readFileSync
|
|
16
|
+
.withArgs("/repo/AGENTS.md", "utf-8")
|
|
17
|
+
.returns(
|
|
18
|
+
[
|
|
19
|
+
"Use the remote MCP endpoint at",
|
|
20
|
+
"https://user:pass@example.com/mcp?access_token=abc#frag",
|
|
21
|
+
"during release preparation.",
|
|
22
|
+
].join(" "),
|
|
23
|
+
);
|
|
24
|
+
const { agentFormulationParser } = await esmock(
|
|
25
|
+
"./agentFormulationParser.js",
|
|
26
|
+
{
|
|
27
|
+
"node:fs": { readFileSync },
|
|
28
|
+
"./unicodeScan.js": { scanTextForHiddenUnicode },
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const result = agentFormulationParser.parse(["/repo/AGENTS.md"]);
|
|
33
|
+
|
|
34
|
+
assert.strictEqual(
|
|
35
|
+
getProp(result.components[0], "cdx:agent:hiddenMcpUrls"),
|
|
36
|
+
"https://example.com/mcp",
|
|
37
|
+
);
|
|
38
|
+
assert.deepStrictEqual(result.services[0].endpoints, [
|
|
39
|
+
"https://example.com/mcp",
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { agentFormulationParser } from "./agentFormulationParser.js";
|
|
2
|
+
import { communityAiConfigParser } from "./communityAiConfigParser.js";
|
|
3
|
+
import { mergeServices, trimComponents } from "./depsUtils.js";
|
|
4
|
+
import { classifyMcpReference } from "./mcp.js";
|
|
5
|
+
import { mcpConfigParser } from "./mcpConfigParser.js";
|
|
6
|
+
import { getAllFiles } from "./utils.js";
|
|
7
|
+
|
|
8
|
+
export const AI_INVENTORY_PROJECT_TYPES = ["mcp", "ai-skill"];
|
|
9
|
+
export const AI_INSTRUCTION_FILE_KINDS = new Set([
|
|
10
|
+
"agent-config",
|
|
11
|
+
"agent-definition",
|
|
12
|
+
"agent-instructions",
|
|
13
|
+
"ai-agent-file",
|
|
14
|
+
"copilot-instructions",
|
|
15
|
+
"copilot-setup-workflow",
|
|
16
|
+
"crew-agent",
|
|
17
|
+
"crew-task",
|
|
18
|
+
"crew-tool",
|
|
19
|
+
"custom-command",
|
|
20
|
+
"custom-tool",
|
|
21
|
+
"graph-definition",
|
|
22
|
+
]);
|
|
23
|
+
export const AI_SKILL_FILE_KIND = "skill-file";
|
|
24
|
+
export const MCP_CONFIG_FILE_KIND = "mcp-config";
|
|
25
|
+
|
|
26
|
+
const AI_INVENTORY_FILE_KINDS = new Set([
|
|
27
|
+
"agent-config",
|
|
28
|
+
"agent-definition",
|
|
29
|
+
"agent-instructions",
|
|
30
|
+
"ai-agent-file",
|
|
31
|
+
"copilot-instructions",
|
|
32
|
+
"copilot-setup-workflow",
|
|
33
|
+
"crew-agent",
|
|
34
|
+
"crew-task",
|
|
35
|
+
"crew-tool",
|
|
36
|
+
"custom-command",
|
|
37
|
+
"custom-tool",
|
|
38
|
+
"graph-definition",
|
|
39
|
+
AI_SKILL_FILE_KIND,
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const AI_INVENTORY_PARSERS = [
|
|
43
|
+
{
|
|
44
|
+
id: agentFormulationParser.id,
|
|
45
|
+
parser: agentFormulationParser,
|
|
46
|
+
types: ["mcp", "ai-skill"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: mcpConfigParser.id,
|
|
50
|
+
parser: mcpConfigParser,
|
|
51
|
+
types: ["mcp"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: communityAiConfigParser.id,
|
|
55
|
+
parser: communityAiConfigParser,
|
|
56
|
+
types: ["ai-skill"],
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export function inventoryPropertyValue(subject, name) {
|
|
61
|
+
return subject?.properties?.find((property) => property.name === name)?.value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasPropertyPrefix(subject, prefix) {
|
|
65
|
+
return (subject?.properties || []).some((property) =>
|
|
66
|
+
property?.name?.startsWith(prefix),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function uniqueNonEmptyTypes(types) {
|
|
71
|
+
return [...new Set((types || []).filter(Boolean))];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function optionIncludesAiInventoryProjectType(optionValue, type) {
|
|
75
|
+
const values = Array.isArray(optionValue)
|
|
76
|
+
? optionValue
|
|
77
|
+
: optionValue
|
|
78
|
+
? [optionValue]
|
|
79
|
+
: [];
|
|
80
|
+
return values.some((value) => {
|
|
81
|
+
const normalizedValue = String(value).toLowerCase();
|
|
82
|
+
if (type === "ai-skill") {
|
|
83
|
+
return ["ai-skill", "skill", "skills"].includes(normalizedValue);
|
|
84
|
+
}
|
|
85
|
+
return normalizedValue === type;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function inventoryTypesForSubject(subject) {
|
|
90
|
+
const types = new Set();
|
|
91
|
+
const fileKind = inventoryPropertyValue(subject, "cdx:file:kind");
|
|
92
|
+
if (
|
|
93
|
+
subject?.group === "mcp" ||
|
|
94
|
+
classifyMcpReference(subject).isMcp ||
|
|
95
|
+
hasPropertyPrefix(subject, "cdx:mcp:") ||
|
|
96
|
+
(subject?.tags || []).some((tag) => String(tag || "").startsWith("mcp"))
|
|
97
|
+
) {
|
|
98
|
+
types.add("mcp");
|
|
99
|
+
}
|
|
100
|
+
if (
|
|
101
|
+
AI_INVENTORY_FILE_KINDS.has(fileKind) ||
|
|
102
|
+
hasPropertyPrefix(subject, "cdx:agent:") ||
|
|
103
|
+
hasPropertyPrefix(subject, "cdx:skill:") ||
|
|
104
|
+
hasPropertyPrefix(subject, "cdx:tool:") ||
|
|
105
|
+
hasPropertyPrefix(subject, "cdx:langgraph:") ||
|
|
106
|
+
hasPropertyPrefix(subject, "cdx:crewai:")
|
|
107
|
+
) {
|
|
108
|
+
types.add("ai-skill");
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
inventoryPropertyValue(subject, "cdx:mcp:inventorySource") === "agent-file"
|
|
112
|
+
) {
|
|
113
|
+
types.add("ai-skill");
|
|
114
|
+
}
|
|
115
|
+
return Array.from(types);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function matchesAiInventoryType(subject, type) {
|
|
119
|
+
return inventoryTypesForSubject(subject).includes(type);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function matchesAiInventoryExcludeType(subject, type) {
|
|
123
|
+
if (type === "mcp") {
|
|
124
|
+
const fileKind = inventoryPropertyValue(subject, "cdx:file:kind");
|
|
125
|
+
return (
|
|
126
|
+
fileKind === MCP_CONFIG_FILE_KIND ||
|
|
127
|
+
subject?.group === "mcp" ||
|
|
128
|
+
inventoryPropertyValue(subject, "cdx:mcp:inventorySource") !==
|
|
129
|
+
undefined ||
|
|
130
|
+
inventoryPropertyValue(subject, "cdx:mcp:role") !== undefined
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return matchesAiInventoryType(subject, type);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function filterInventorySubjectsByTypes(subjects, types) {
|
|
137
|
+
const allowedTypes = uniqueNonEmptyTypes(types);
|
|
138
|
+
if (!allowedTypes.length) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
return (subjects || []).filter((subject) =>
|
|
142
|
+
inventoryTypesForSubject(subject).some((type) =>
|
|
143
|
+
allowedTypes.includes(type),
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function filterInventoryDependencies(
|
|
149
|
+
dependencies,
|
|
150
|
+
components,
|
|
151
|
+
services,
|
|
152
|
+
) {
|
|
153
|
+
const allowedRefs = new Set(
|
|
154
|
+
[]
|
|
155
|
+
.concat(components || [])
|
|
156
|
+
.concat(services || [])
|
|
157
|
+
.map((subject) => subject?.["bom-ref"])
|
|
158
|
+
.filter(Boolean),
|
|
159
|
+
);
|
|
160
|
+
return (dependencies || [])
|
|
161
|
+
.filter((dependency) => allowedRefs.has(dependency.ref))
|
|
162
|
+
.map((dependency) => {
|
|
163
|
+
const filteredDependency = {
|
|
164
|
+
ref: dependency.ref,
|
|
165
|
+
};
|
|
166
|
+
if (dependency.dependsOn?.length) {
|
|
167
|
+
filteredDependency.dependsOn = dependency.dependsOn.filter((ref) =>
|
|
168
|
+
allowedRefs.has(ref),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (dependency.provides?.length) {
|
|
172
|
+
filteredDependency.provides = dependency.provides.filter((ref) =>
|
|
173
|
+
allowedRefs.has(ref),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return filteredDependency;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function collectAiInventory(discoveryPath, options, types) {
|
|
181
|
+
const requestedTypes = uniqueNonEmptyTypes(types);
|
|
182
|
+
if (!requestedTypes.length) {
|
|
183
|
+
return { components: [], dependencies: [], services: [] };
|
|
184
|
+
}
|
|
185
|
+
let components = [];
|
|
186
|
+
const dependencies = [];
|
|
187
|
+
let services = [];
|
|
188
|
+
for (const parserEntry of AI_INVENTORY_PARSERS) {
|
|
189
|
+
if (!parserEntry.types.some((type) => requestedTypes.includes(type))) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const matchedFiles = [];
|
|
193
|
+
for (const pattern of parserEntry.parser.patterns) {
|
|
194
|
+
const found = getAllFiles(discoveryPath, pattern, options);
|
|
195
|
+
if (found?.length) {
|
|
196
|
+
matchedFiles.push(...found);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const uniqueMatchedFiles = [...new Set(matchedFiles)];
|
|
200
|
+
if (!uniqueMatchedFiles.length) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
let result;
|
|
204
|
+
try {
|
|
205
|
+
result = parserEntry.parser.parse(uniqueMatchedFiles, options);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.warn(
|
|
208
|
+
`[aiInventory] Parser "${parserEntry.id}" threw an error:`,
|
|
209
|
+
err.message,
|
|
210
|
+
);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (result?.components?.length) {
|
|
214
|
+
components = components.concat(result.components);
|
|
215
|
+
}
|
|
216
|
+
if (result?.services?.length) {
|
|
217
|
+
services = mergeServices(services, result.services);
|
|
218
|
+
}
|
|
219
|
+
if (result?.dependencies?.length) {
|
|
220
|
+
dependencies.push(...result.dependencies);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
components = trimComponents(
|
|
224
|
+
filterInventorySubjectsByTypes(components, requestedTypes),
|
|
225
|
+
);
|
|
226
|
+
services = mergeServices(
|
|
227
|
+
[],
|
|
228
|
+
filterInventorySubjectsByTypes(services, requestedTypes),
|
|
229
|
+
);
|
|
230
|
+
return {
|
|
231
|
+
components,
|
|
232
|
+
dependencies: filterInventoryDependencies(
|
|
233
|
+
dependencies,
|
|
234
|
+
components,
|
|
235
|
+
services,
|
|
236
|
+
),
|
|
237
|
+
services,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function summarizeAiInventory(inventory) {
|
|
242
|
+
const components = inventory?.components || [];
|
|
243
|
+
const services = inventory?.services || [];
|
|
244
|
+
return {
|
|
245
|
+
instructionCount: components.filter((component) =>
|
|
246
|
+
AI_INSTRUCTION_FILE_KINDS.has(
|
|
247
|
+
inventoryPropertyValue(component, "cdx:file:kind"),
|
|
248
|
+
),
|
|
249
|
+
).length,
|
|
250
|
+
mcpConfigCount: components.filter(
|
|
251
|
+
(component) =>
|
|
252
|
+
inventoryPropertyValue(component, "cdx:file:kind") ===
|
|
253
|
+
MCP_CONFIG_FILE_KIND,
|
|
254
|
+
).length,
|
|
255
|
+
mcpServiceCount: services.length,
|
|
256
|
+
skillCount: components.filter(
|
|
257
|
+
(component) =>
|
|
258
|
+
inventoryPropertyValue(component, "cdx:file:kind") ===
|
|
259
|
+
AI_SKILL_FILE_KIND,
|
|
260
|
+
).length,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { assert, describe, it } from "poku";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
filterInventoryDependencies,
|
|
5
|
+
inventoryTypesForSubject,
|
|
6
|
+
matchesAiInventoryExcludeType,
|
|
7
|
+
matchesAiInventoryType,
|
|
8
|
+
summarizeAiInventory,
|
|
9
|
+
} from "./aiInventory.js";
|
|
10
|
+
|
|
11
|
+
describe("aiInventory", () => {
|
|
12
|
+
it("classifies agent-derived MCP services as both mcp and ai-skill", () => {
|
|
13
|
+
const service = {
|
|
14
|
+
"bom-ref": "urn:service:agent-mcp:demo:1",
|
|
15
|
+
group: "mcp",
|
|
16
|
+
properties: [
|
|
17
|
+
{ name: "cdx:mcp:inventorySource", value: "agent-file" },
|
|
18
|
+
{ name: "cdx:mcp:serviceType", value: "inferred-endpoint" },
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
assert.deepStrictEqual(inventoryTypesForSubject(service).sort(), [
|
|
22
|
+
"ai-skill",
|
|
23
|
+
"mcp",
|
|
24
|
+
]);
|
|
25
|
+
assert.strictEqual(matchesAiInventoryType(service, "mcp"), true);
|
|
26
|
+
assert.strictEqual(matchesAiInventoryType(service, "ai-skill"), true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("limits MCP exclusion matching to AI inventory services, files, and primitives", () => {
|
|
30
|
+
const mcpPackage = {
|
|
31
|
+
"bom-ref": "pkg:npm/@modelcontextprotocol/server-filesystem@1.0.0",
|
|
32
|
+
name: "@modelcontextprotocol/server-filesystem",
|
|
33
|
+
purl: "pkg:npm/%40modelcontextprotocol/server-filesystem@1.0.0",
|
|
34
|
+
};
|
|
35
|
+
const mcpPrimitive = {
|
|
36
|
+
"bom-ref": "urn:mcp:tool:docs:search",
|
|
37
|
+
properties: [{ name: "cdx:mcp:role", value: "tool" }],
|
|
38
|
+
tags: ["mcp", "mcp-tool"],
|
|
39
|
+
};
|
|
40
|
+
const mcpConfig = {
|
|
41
|
+
"bom-ref": "file:/repo/.vscode/mcp.json",
|
|
42
|
+
properties: [{ name: "cdx:file:kind", value: "mcp-config" }],
|
|
43
|
+
type: "file",
|
|
44
|
+
};
|
|
45
|
+
const mcpService = {
|
|
46
|
+
"bom-ref": "urn:service:mcp:docs:latest",
|
|
47
|
+
group: "mcp",
|
|
48
|
+
properties: [{ name: "cdx:mcp:inventorySource", value: "config-file" }],
|
|
49
|
+
};
|
|
50
|
+
assert.strictEqual(matchesAiInventoryExcludeType(mcpPackage, "mcp"), false);
|
|
51
|
+
assert.strictEqual(
|
|
52
|
+
matchesAiInventoryExcludeType(mcpPrimitive, "mcp"),
|
|
53
|
+
true,
|
|
54
|
+
);
|
|
55
|
+
assert.strictEqual(matchesAiInventoryExcludeType(mcpConfig, "mcp"), true);
|
|
56
|
+
assert.strictEqual(matchesAiInventoryExcludeType(mcpService, "mcp"), true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("filters dependencies to retained component and service refs", () => {
|
|
60
|
+
const components = [{ "bom-ref": "file:/repo/CLAUDE.md" }];
|
|
61
|
+
const services = [{ "bom-ref": "urn:service:mcp:docs:latest" }];
|
|
62
|
+
const filtered = filterInventoryDependencies(
|
|
63
|
+
[
|
|
64
|
+
{
|
|
65
|
+
ref: "urn:service:mcp:docs:latest",
|
|
66
|
+
provides: ["file:/repo/CLAUDE.md", "urn:service:mcp:other:latest"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
ref: "urn:service:mcp:missing:latest",
|
|
70
|
+
provides: ["file:/repo/CLAUDE.md"],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
components,
|
|
74
|
+
services,
|
|
75
|
+
);
|
|
76
|
+
assert.deepStrictEqual(filtered, [
|
|
77
|
+
{
|
|
78
|
+
ref: "urn:service:mcp:docs:latest",
|
|
79
|
+
provides: ["file:/repo/CLAUDE.md"],
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("summarizes AI inventory counts for instructions, skills, configs, and services", () => {
|
|
85
|
+
const summary = summarizeAiInventory({
|
|
86
|
+
components: [
|
|
87
|
+
{
|
|
88
|
+
properties: [{ name: "cdx:file:kind", value: "agent-instructions" }],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
properties: [
|
|
92
|
+
{ name: "cdx:file:kind", value: "copilot-instructions" },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
properties: [{ name: "cdx:file:kind", value: "skill-file" }],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
properties: [{ name: "cdx:file:kind", value: "mcp-config" }],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
services: [{ name: "releaseDocs" }, { name: "deployBot" }],
|
|
103
|
+
});
|
|
104
|
+
assert.deepStrictEqual(summary, {
|
|
105
|
+
instructionCount: 2,
|
|
106
|
+
mcpConfigCount: 1,
|
|
107
|
+
mcpServiceCount: 2,
|
|
108
|
+
skillCount: 1,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|