@cubis/foundry 0.3.46 → 0.3.48

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.
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Cubis Foundry MCP Server – declarative tool registry.
3
+ *
4
+ * Defines all built-in tools in a single registry array.
5
+ * Server.ts reads from this registry to auto-register tools,
6
+ * eliminating per-tool import boilerplate.
7
+ *
8
+ * When adding a new tool:
9
+ * 1. Create toolName.ts with name/description/schema/handler exports
10
+ * 2. Add a ToolRegistryEntry here
11
+ * 3. Done — server.ts picks it up automatically.
12
+ */
13
+
14
+ import { z } from "zod";
15
+ import type { VaultManifest } from "../vault/types.js";
16
+ import type { ConfigScope } from "../cbxConfig/types.js";
17
+
18
+ // ─── Core types ─────────────────────────────────────────────
19
+
20
+ export type ToolCategory = "skill" | "postman" | "stitch";
21
+
22
+ export interface ToolRegistryEntry {
23
+ /** Tool name exposed to MCP clients. */
24
+ name: string;
25
+ /** One-line description for MCP discovery. */
26
+ description: string;
27
+ /** Zod schema (the `.shape` is extracted at registration time). */
28
+ schema: z.ZodObject<z.ZodRawShape>;
29
+ /** Tool category for grouping and documentation. */
30
+ category: ToolCategory;
31
+ /**
32
+ * Handler factory. Receives shared runtime context and returns the
33
+ * concrete async handler that server.tool() expects.
34
+ */
35
+ createHandler: (
36
+ ctx: ToolRuntimeContext,
37
+ ) => (args: unknown) => Promise<unknown>;
38
+ }
39
+
40
+ export interface ToolRuntimeContext {
41
+ manifest: VaultManifest;
42
+ charsPerToken: number;
43
+ summaryMaxLength: number;
44
+ defaultConfigScope: ConfigScope | "auto";
45
+ }
46
+
47
+ // ─── Tool imports ───────────────────────────────────────────
48
+
49
+ import {
50
+ skillListCategoriesName,
51
+ skillListCategoriesDescription,
52
+ skillListCategoriesSchema,
53
+ handleSkillListCategories,
54
+ } from "./skillListCategories.js";
55
+
56
+ import {
57
+ skillBrowseCategoryName,
58
+ skillBrowseCategoryDescription,
59
+ skillBrowseCategorySchema,
60
+ handleSkillBrowseCategory,
61
+ } from "./skillBrowseCategory.js";
62
+
63
+ import {
64
+ skillSearchName,
65
+ skillSearchDescription,
66
+ skillSearchSchema,
67
+ handleSkillSearch,
68
+ } from "./skillSearch.js";
69
+
70
+ import {
71
+ skillGetName,
72
+ skillGetDescription,
73
+ skillGetSchema,
74
+ handleSkillGet,
75
+ } from "./skillGet.js";
76
+
77
+ import {
78
+ skillBudgetReportName,
79
+ skillBudgetReportDescription,
80
+ skillBudgetReportSchema,
81
+ handleSkillBudgetReport,
82
+ } from "./skillBudgetReport.js";
83
+
84
+ import {
85
+ postmanGetModeName,
86
+ postmanGetModeDescription,
87
+ postmanGetModeSchema,
88
+ handlePostmanGetMode,
89
+ } from "./postmanGetMode.js";
90
+
91
+ import {
92
+ postmanSetModeName,
93
+ postmanSetModeDescription,
94
+ postmanSetModeSchema,
95
+ handlePostmanSetMode,
96
+ } from "./postmanSetMode.js";
97
+
98
+ import {
99
+ postmanGetStatusName,
100
+ postmanGetStatusDescription,
101
+ postmanGetStatusSchema,
102
+ handlePostmanGetStatus,
103
+ } from "./postmanGetStatus.js";
104
+
105
+ import {
106
+ stitchGetModeName,
107
+ stitchGetModeDescription,
108
+ stitchGetModeSchema,
109
+ handleStitchGetMode,
110
+ } from "./stitchGetMode.js";
111
+
112
+ import {
113
+ stitchSetProfileName,
114
+ stitchSetProfileDescription,
115
+ stitchSetProfileSchema,
116
+ handleStitchSetProfile,
117
+ } from "./stitchSetProfile.js";
118
+
119
+ import {
120
+ stitchGetStatusName,
121
+ stitchGetStatusDescription,
122
+ stitchGetStatusSchema,
123
+ handleStitchGetStatus,
124
+ } from "./stitchGetStatus.js";
125
+
126
+ // ─── Scope helper ───────────────────────────────────────────
127
+
128
+ function withDefaultScope(
129
+ args: unknown,
130
+ defaultScope: ConfigScope | "auto",
131
+ ): Record<string, unknown> {
132
+ const safeArgs =
133
+ args && typeof args === "object" ? (args as Record<string, unknown>) : {};
134
+ return {
135
+ ...safeArgs,
136
+ scope: typeof safeArgs.scope === "string" ? safeArgs.scope : defaultScope,
137
+ };
138
+ }
139
+
140
+ // ─── Registry ───────────────────────────────────────────────
141
+
142
+ export const TOOL_REGISTRY: readonly ToolRegistryEntry[] = [
143
+ // ── Skill vault tools ─────────────────────────────────────
144
+ {
145
+ name: skillListCategoriesName,
146
+ description: skillListCategoriesDescription,
147
+ schema: skillListCategoriesSchema,
148
+ category: "skill",
149
+ createHandler: (ctx) => async () =>
150
+ handleSkillListCategories(ctx.manifest, ctx.charsPerToken),
151
+ },
152
+ {
153
+ name: skillBrowseCategoryName,
154
+ description: skillBrowseCategoryDescription,
155
+ schema: skillBrowseCategorySchema,
156
+ category: "skill",
157
+ createHandler: (ctx) => async (args) =>
158
+ handleSkillBrowseCategory(
159
+ args as z.infer<typeof skillBrowseCategorySchema>,
160
+ ctx.manifest,
161
+ ctx.summaryMaxLength,
162
+ ctx.charsPerToken,
163
+ ),
164
+ },
165
+ {
166
+ name: skillSearchName,
167
+ description: skillSearchDescription,
168
+ schema: skillSearchSchema,
169
+ category: "skill",
170
+ createHandler: (ctx) => async (args) =>
171
+ handleSkillSearch(
172
+ args as z.infer<typeof skillSearchSchema>,
173
+ ctx.manifest,
174
+ ctx.summaryMaxLength,
175
+ ctx.charsPerToken,
176
+ ),
177
+ },
178
+ {
179
+ name: skillGetName,
180
+ description: skillGetDescription,
181
+ schema: skillGetSchema,
182
+ category: "skill",
183
+ createHandler: (ctx) => async (args) =>
184
+ handleSkillGet(
185
+ args as z.infer<typeof skillGetSchema>,
186
+ ctx.manifest,
187
+ ctx.charsPerToken,
188
+ ),
189
+ },
190
+ {
191
+ name: skillBudgetReportName,
192
+ description: skillBudgetReportDescription,
193
+ schema: skillBudgetReportSchema,
194
+ category: "skill",
195
+ createHandler: (ctx) => async (args) =>
196
+ handleSkillBudgetReport(
197
+ args as z.infer<typeof skillBudgetReportSchema>,
198
+ ctx.manifest,
199
+ ctx.charsPerToken,
200
+ ),
201
+ },
202
+
203
+ // ── Postman tools ─────────────────────────────────────────
204
+ {
205
+ name: postmanGetModeName,
206
+ description: postmanGetModeDescription,
207
+ schema: postmanGetModeSchema,
208
+ category: "postman",
209
+ createHandler: (ctx) => async (args) =>
210
+ handlePostmanGetMode(
211
+ withDefaultScope(args, ctx.defaultConfigScope) as z.infer<
212
+ typeof postmanGetModeSchema
213
+ >,
214
+ ),
215
+ },
216
+ {
217
+ name: postmanSetModeName,
218
+ description: postmanSetModeDescription,
219
+ schema: postmanSetModeSchema,
220
+ category: "postman",
221
+ createHandler: (ctx) => async (args) =>
222
+ handlePostmanSetMode(
223
+ withDefaultScope(args, ctx.defaultConfigScope) as z.infer<
224
+ typeof postmanSetModeSchema
225
+ >,
226
+ ),
227
+ },
228
+ {
229
+ name: postmanGetStatusName,
230
+ description: postmanGetStatusDescription,
231
+ schema: postmanGetStatusSchema,
232
+ category: "postman",
233
+ createHandler: (ctx) => async (args) =>
234
+ handlePostmanGetStatus(
235
+ withDefaultScope(args, ctx.defaultConfigScope) as z.infer<
236
+ typeof postmanGetStatusSchema
237
+ >,
238
+ ),
239
+ },
240
+
241
+ // ── Stitch tools ──────────────────────────────────────────
242
+ {
243
+ name: stitchGetModeName,
244
+ description: stitchGetModeDescription,
245
+ schema: stitchGetModeSchema,
246
+ category: "stitch",
247
+ createHandler: (ctx) => async (args) =>
248
+ handleStitchGetMode(
249
+ withDefaultScope(args, ctx.defaultConfigScope) as z.infer<
250
+ typeof stitchGetModeSchema
251
+ >,
252
+ ),
253
+ },
254
+ {
255
+ name: stitchSetProfileName,
256
+ description: stitchSetProfileDescription,
257
+ schema: stitchSetProfileSchema,
258
+ category: "stitch",
259
+ createHandler: (ctx) => async (args) =>
260
+ handleStitchSetProfile(
261
+ withDefaultScope(args, ctx.defaultConfigScope) as z.infer<
262
+ typeof stitchSetProfileSchema
263
+ >,
264
+ ),
265
+ },
266
+ {
267
+ name: stitchGetStatusName,
268
+ description: stitchGetStatusDescription,
269
+ schema: stitchGetStatusSchema,
270
+ category: "stitch",
271
+ createHandler: (ctx) => async (args) =>
272
+ handleStitchGetStatus(
273
+ withDefaultScope(args, ctx.defaultConfigScope) as z.infer<
274
+ typeof stitchGetStatusSchema
275
+ >,
276
+ ),
277
+ },
278
+ ];
279
+
280
+ // ─── Helpers ────────────────────────────────────────────────
281
+
282
+ /** Get tools filtered by category. */
283
+ export function getToolsByCategory(
284
+ category: ToolCategory,
285
+ ): ToolRegistryEntry[] {
286
+ return TOOL_REGISTRY.filter((t) => t.category === category);
287
+ }
288
+
289
+ /** Get all registered tool names. */
290
+ export function getRegisteredToolNames(): string[] {
291
+ return TOOL_REGISTRY.map((t) => t.name);
292
+ }
293
+
294
+ /** Build a summary of the registry for documentation/rule-file generation. */
295
+ export function buildRegistrySummary(): {
296
+ categories: Record<
297
+ string,
298
+ { tools: Array<{ name: string; description: string }> }
299
+ >;
300
+ totalTools: number;
301
+ } {
302
+ const categories: Record<
303
+ string,
304
+ { tools: Array<{ name: string; description: string }> }
305
+ > = {};
306
+
307
+ for (const tool of TOOL_REGISTRY) {
308
+ if (!categories[tool.category]) {
309
+ categories[tool.category] = { tools: [] };
310
+ }
311
+ categories[tool.category].tools.push({
312
+ name: tool.name,
313
+ description: tool.description,
314
+ });
315
+ }
316
+
317
+ return { categories, totalTools: TOOL_REGISTRY.length };
318
+ }
@@ -7,8 +7,8 @@
7
7
 
8
8
  import { z } from "zod";
9
9
  import type { VaultManifest } from "../vault/types.js";
10
- import { readFullSkillContent } from "../vault/manifest.js";
11
- import { notFound } from "../utils/errors.js";
10
+ import { readSkillContentWithReferences } from "../vault/manifest.js";
11
+ import { invalidInput, notFound } from "../utils/errors.js";
12
12
  import {
13
13
  buildSkillToolMetrics,
14
14
  estimateTokensFromText,
@@ -17,10 +17,16 @@ import {
17
17
  export const skillGetName = "skill_get";
18
18
 
19
19
  export const skillGetDescription =
20
- "Get the full content of a specific skill by ID. Returns the complete SKILL.md file content.";
20
+ "Get full content of a specific skill by ID. Returns SKILL.md content and optionally direct referenced markdown files.";
21
21
 
22
22
  export const skillGetSchema = z.object({
23
23
  id: z.string().describe("The skill ID (directory name) to retrieve"),
24
+ includeReferences: z
25
+ .boolean()
26
+ .optional()
27
+ .describe(
28
+ "Whether to include direct local markdown references from SKILL.md (default: true)",
29
+ ),
24
30
  });
25
31
 
26
32
  export async function handleSkillGet(
@@ -28,14 +34,38 @@ export async function handleSkillGet(
28
34
  manifest: VaultManifest,
29
35
  charsPerToken: number,
30
36
  ) {
31
- const { id } = args;
37
+ const { id, includeReferences = true } = args;
38
+
39
+ if (id.startsWith("workflow-") || id.startsWith("agent-")) {
40
+ invalidInput(
41
+ `Skill id "${id}" appears to be a wrapper id. Use workflow/agent routing (for example $workflow-implement-track or $agent-backend-specialist) and call skill_get only for concrete skill ids.`,
42
+ );
43
+ }
32
44
 
33
45
  const skill = manifest.skills.find((s) => s.id === id);
34
46
  if (!skill) {
35
47
  notFound("Skill", id);
36
48
  }
37
49
 
38
- const content = await readFullSkillContent(skill.path);
50
+ const { skillContent, references } = await readSkillContentWithReferences(
51
+ skill.path,
52
+ includeReferences,
53
+ );
54
+ const referenceSection =
55
+ references.length > 0
56
+ ? [
57
+ "",
58
+ "## Referenced Files",
59
+ "",
60
+ ...references.flatMap((ref) => [
61
+ `### ${ref.relativePath}`,
62
+ "",
63
+ ref.content.trimEnd(),
64
+ "",
65
+ ]),
66
+ ].join("\n")
67
+ : "";
68
+ const content = `${skillContent}${referenceSection}`;
39
69
  const loadedSkillEstimatedTokens = estimateTokensFromText(
40
70
  content,
41
71
  charsPerToken,
@@ -55,6 +85,7 @@ export async function handleSkillGet(
55
85
  },
56
86
  ],
57
87
  structuredContent: {
88
+ references: references.map((ref) => ({ path: ref.relativePath })),
58
89
  metrics,
59
90
  },
60
91
  };
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { mkdtempSync, statSync, writeFileSync } from "node:fs";
2
+ import { mkdirSync, mkdtempSync, statSync, writeFileSync } from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import type { VaultManifest } from "../vault/types.js";
@@ -147,6 +147,134 @@ describe("skill tools", () => {
147
147
  expect(toolMetrics.loadedSkillEstimatedTokens).toBeGreaterThan(0);
148
148
  });
149
149
 
150
+ it("includes referenced markdown files in skill_get by default", async () => {
151
+ const dir = mkdtempSync(path.join(os.tmpdir(), "mcp-skill-ref-default-"));
152
+ const skillFile = path.join(dir, "SKILL.md");
153
+ const referencesDir = path.join(dir, "references");
154
+ mkdirSync(referencesDir, { recursive: true });
155
+ writeFileSync(
156
+ skillFile,
157
+ [
158
+ "---",
159
+ "name: referenced-skill",
160
+ "description: skill with refs",
161
+ "---",
162
+ "# Skill",
163
+ "See [Guide](references/guide.md).",
164
+ ].join("\n"),
165
+ "utf8",
166
+ );
167
+ writeFileSync(
168
+ path.join(referencesDir, "guide.md"),
169
+ "# Guide\nReferenced content",
170
+ "utf8",
171
+ );
172
+
173
+ const skillBytes = statSync(skillFile).size;
174
+ const manifest: VaultManifest = {
175
+ categories: ["general"],
176
+ skills: [
177
+ {
178
+ id: "referenced-skill",
179
+ category: "general",
180
+ path: skillFile,
181
+ fileBytes: skillBytes,
182
+ },
183
+ ],
184
+ fullCatalogBytes: skillBytes,
185
+ fullCatalogEstimatedTokens: Math.ceil(skillBytes / 4),
186
+ };
187
+
188
+ const result = await handleSkillGet({ id: "referenced-skill" }, manifest, 4);
189
+ expect(result.content[0].text).toContain("## Referenced Files");
190
+ expect(result.content[0].text).toContain("### references/guide.md");
191
+ expect(result.content[0].text).toContain("Referenced content");
192
+ });
193
+
194
+ it("skips referenced markdown files when includeReferences is false", async () => {
195
+ const dir = mkdtempSync(path.join(os.tmpdir(), "mcp-skill-ref-skip-"));
196
+ const skillFile = path.join(dir, "SKILL.md");
197
+ const referencesDir = path.join(dir, "references");
198
+ mkdirSync(referencesDir, { recursive: true });
199
+ writeFileSync(
200
+ skillFile,
201
+ [
202
+ "---",
203
+ "name: referenced-skill-no-refs",
204
+ "description: skill with refs",
205
+ "---",
206
+ "# Skill",
207
+ "See [Guide](references/guide.md).",
208
+ ].join("\n"),
209
+ "utf8",
210
+ );
211
+ writeFileSync(path.join(referencesDir, "guide.md"), "# Guide", "utf8");
212
+
213
+ const skillBytes = statSync(skillFile).size;
214
+ const manifest: VaultManifest = {
215
+ categories: ["general"],
216
+ skills: [
217
+ {
218
+ id: "referenced-skill-no-refs",
219
+ category: "general",
220
+ path: skillFile,
221
+ fileBytes: skillBytes,
222
+ },
223
+ ],
224
+ fullCatalogBytes: skillBytes,
225
+ fullCatalogEstimatedTokens: Math.ceil(skillBytes / 4),
226
+ };
227
+
228
+ const result = await handleSkillGet(
229
+ { id: "referenced-skill-no-refs", includeReferences: false },
230
+ manifest,
231
+ 4,
232
+ );
233
+ expect(result.content[0].text).not.toContain("## Referenced Files");
234
+ expect(result.content[0].text).not.toContain("### references/guide.md");
235
+ });
236
+
237
+ it("loads sibling markdown files when SKILL.md has no explicit links", async () => {
238
+ const dir = mkdtempSync(path.join(os.tmpdir(), "mcp-skill-ref-fallback-"));
239
+ const skillFile = path.join(dir, "SKILL.md");
240
+ writeFileSync(
241
+ skillFile,
242
+ [
243
+ "---",
244
+ "name: sibling-fallback-skill",
245
+ "description: fallback sibling",
246
+ "---",
247
+ "# Skill",
248
+ ].join("\n"),
249
+ "utf8",
250
+ );
251
+ writeFileSync(path.join(dir, "overview.md"), "# Overview\nSibling", "utf8");
252
+
253
+ const skillBytes = statSync(skillFile).size;
254
+ const manifest: VaultManifest = {
255
+ categories: ["general"],
256
+ skills: [
257
+ {
258
+ id: "sibling-fallback-skill",
259
+ category: "general",
260
+ path: skillFile,
261
+ fileBytes: skillBytes,
262
+ },
263
+ ],
264
+ fullCatalogBytes: skillBytes,
265
+ fullCatalogEstimatedTokens: Math.ceil(skillBytes / 4),
266
+ };
267
+
268
+ const result = await handleSkillGet(
269
+ { id: "sibling-fallback-skill" },
270
+ manifest,
271
+ 4,
272
+ );
273
+ expect(result.content[0].text).toContain("## Referenced Files");
274
+ expect(result.content[0].text).toContain("### overview.md");
275
+ expect(result.content[0].text).toContain("Sibling");
276
+ });
277
+
150
278
  it("throws when skill_get cannot find the requested skill", async () => {
151
279
  const manifest = createManifest();
152
280
  await expect(handleSkillGet({ id: "missing" }, manifest, 4)).rejects.toThrow(
@@ -154,6 +282,20 @@ describe("skill tools", () => {
154
282
  );
155
283
  });
156
284
 
285
+ it("throws a wrapper guidance error when skill_get receives workflow id", async () => {
286
+ const manifest = createManifest();
287
+ await expect(
288
+ handleSkillGet({ id: "workflow-implement-track" }, manifest, 4),
289
+ ).rejects.toThrow("appears to be a wrapper id");
290
+ });
291
+
292
+ it("throws a wrapper guidance error when skill_get receives agent id", async () => {
293
+ const manifest = createManifest();
294
+ await expect(
295
+ handleSkillGet({ id: "agent-backend-specialist" }, manifest, 4),
296
+ ).rejects.toThrow("appears to be a wrapper id");
297
+ });
298
+
157
299
  it("returns consolidated budget rollup for selected and loaded skills", () => {
158
300
  const manifest = createManifest();
159
301
  const result = handleSkillBudgetReport(
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, it } from "vitest";
2
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import {
@@ -8,6 +8,7 @@ import {
8
8
  extractDescription,
9
9
  parseDescriptionFromFrontmatter,
10
10
  readFullSkillContent,
11
+ readSkillContentWithReferences,
11
12
  } from "./manifest.js";
12
13
 
13
14
  const tempDirs: string[] = [];
@@ -90,6 +91,75 @@ describe("skill content IO", () => {
90
91
 
91
92
  await expect(readFullSkillContent(file)).resolves.toBe(body);
92
93
  });
94
+
95
+ it("loads direct local markdown references from SKILL.md", async () => {
96
+ const dir = createTempDir("mcp-refs-read-");
97
+ const file = path.join(dir, "SKILL.md");
98
+ const refsDir = path.join(dir, "references");
99
+ const refFile = path.join(refsDir, "guide.md");
100
+ const nestedRefFile = path.join(dir, "notes.md");
101
+
102
+ writeFileSync(
103
+ file,
104
+ [
105
+ "---",
106
+ "name: ref-read",
107
+ "description: Read refs",
108
+ "---",
109
+ "# Skill",
110
+ "See [Guide](references/guide.md).",
111
+ "See [Notes](notes.md#section).",
112
+ "Ignore [External](https://example.com).",
113
+ ].join("\n"),
114
+ "utf8",
115
+ );
116
+ mkdirSync(refsDir, { recursive: true });
117
+ writeFileSync(refFile, "# Guide\nReference body", "utf8");
118
+ writeFileSync(nestedRefFile, "# Notes\nMore details", "utf8");
119
+
120
+ const result = await readSkillContentWithReferences(file, true);
121
+ expect(result.skillContent).toContain("# Skill");
122
+ expect(result.references).toEqual([
123
+ { relativePath: "references/guide.md", content: "# Guide\nReference body" },
124
+ { relativePath: "notes.md", content: "# Notes\nMore details" },
125
+ ]);
126
+ });
127
+
128
+ it("skips references when includeReferences is false", async () => {
129
+ const dir = createTempDir("mcp-refs-skip-");
130
+ const file = path.join(dir, "SKILL.md");
131
+ const refFile = path.join(dir, "reference.md");
132
+ writeFileSync(
133
+ file,
134
+ ["---", "name: skip", "description: skip", "---", "[Ref](reference.md)"].join(
135
+ "\n",
136
+ ),
137
+ "utf8",
138
+ );
139
+ writeFileSync(refFile, "ref", "utf8");
140
+
141
+ const result = await readSkillContentWithReferences(file, false);
142
+ expect(result.references).toEqual([]);
143
+ });
144
+
145
+ it("falls back to sibling markdown files when SKILL.md has no links", async () => {
146
+ const dir = createTempDir("mcp-refs-fallback-");
147
+ const file = path.join(dir, "SKILL.md");
148
+ const siblingRef = path.join(dir, "overview.md");
149
+ writeFileSync(
150
+ file,
151
+ ["---", "name: fallback", "description: fallback", "---", "# Skill"].join(
152
+ "\n",
153
+ ),
154
+ "utf8",
155
+ );
156
+ writeFileSync(siblingRef, "# Overview\nSibling content", "utf8");
157
+
158
+ const result = await readSkillContentWithReferences(file, true);
159
+ expect(result.references).toEqual([
160
+ { relativePath: "overview.md", content: "# Overview\nSibling content" },
161
+ ]);
162
+ });
93
163
  });
94
164
 
95
165
  describe("manifest enrichment", () => {