@cubis/foundry 0.3.61 → 0.3.63

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.
@@ -1,7 +1,7 @@
1
1
  function hasMcpSelection(selectedMcps, mcpId) {
2
2
  return selectedMcps.includes(mcpId);
3
3
  }
4
- export function buildInitExecutionPlan({ selections, dryRun, target, }) {
4
+ export function buildInitExecutionPlan({ selections, dryRun, overwrite, target, }) {
5
5
  const planItems = [];
6
6
  const wantsPostman = hasMcpSelection(selections.selectedMcps, "postman");
7
7
  const wantsStitch = hasMcpSelection(selections.selectedMcps, "stitch");
@@ -20,6 +20,7 @@ export function buildInitExecutionPlan({ selections, dryRun, target, }) {
20
20
  skillProfile: selections.skillProfile,
21
21
  allSkills: selections.skillProfile === "full",
22
22
  dryRun,
23
+ overwrite,
23
24
  yes: true,
24
25
  target,
25
26
  postman: wantsPostman,
@@ -1 +1 @@
1
- {"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/cli/init/execute.ts"],"names":[],"mappings":"AAOA,SAAS,eAAe,CAAC,YAAyB,EAAE,KAAgB;IAClE,OAAO,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,EACrC,UAAU,EACV,MAAM,EACN,MAAM,GAKP;IACC,MAAM,SAAS,GAA4B,EAAE,CAAC;IAC9C,MAAM,YAAY,GAAG,eAAe,CAAC,UAAU,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACzE,MAAM,WAAW,GAAG,eAAe,CAAC,UAAU,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACvE,MAAM,YAAY,GAAG,eAAe,CAAC,UAAU,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAE/E,KAAK,MAAM,QAAQ,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QAC5C,MAAM,eAAe,GAAG,QAAQ,KAAK,aAAa,CAAC;QACnD,MAAM,aAAa,GAAG,WAAW,IAAI,eAAe,CAAC;QACrD,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,WAAW,IAAI,CAAC,eAAe,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CACX,+BAA+B,QAAQ,0CAA0C,CAClF,CAAC;QACJ,CAAC;QAED,MAAM,cAAc,GAA4B;YAC9C,QAAQ;YACR,KAAK,EAAE,UAAU,CAAC,WAAW;YAC7B,MAAM,EAAE,UAAU,CAAC,QAAQ;YAC3B,YAAY,EAAE,UAAU,CAAC,YAAY;YACrC,SAAS,EAAE,UAAU,CAAC,YAAY,KAAK,MAAM;YAC7C,MAAM;YACN,GAAG,EAAE,IAAI;YACT,MAAM;YACN,OAAO,EAAE,YAAY;YACrB,MAAM,EAAE,aAAa;YACrB,2BAA2B,EAAE,KAAK;YAClC,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,UAAU,EAAE,YAAY;YACxB,WAAW,EAAE,YAAY,IAAI,aAAa;YAC1C,UAAU,EAAE,YAAY,IAAI,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO;YAC3E,WAAW,EAAE,OAAO;YACpB,aAAa,EACX,YAAY,IAAI,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK;YAClE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YAC9D,kBAAkB,EAAE,YAAY;gBAC9B,CAAC,CAAC,UAAU,CAAC,kBAAkB;gBAC/B,CAAC,CAAC,SAAS;YACb,cAAc,EAAE,IAAI;SACrB,CAAC;QAEF,SAAS,CAAC,IAAI,CAAC;YACb,QAAQ;YACR,cAAc;YACd,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,KAAK,EAAE,SAAS;KACjB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAgC;IAChE,MAAM,eAAe,GAAG,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACpE,OAAO;QACL,oBAAoB;QACpB,aAAa,UAAU,CAAC,QAAQ,EAAE;QAClC,gBAAgB,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACjD,oBAAoB,UAAU,CAAC,YAAY,EAAE;QAC7C,mBAAmB,UAAU,CAAC,WAAW,EAAE;QAC3C,gBAAgB,UAAU,CAAC,QAAQ,EAAE;QACrC,kBAAkB,UAAU,CAAC,UAAU,GAAG,UAAU,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;QACzJ,qBAAqB,UAAU,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACzG,mBAAmB,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,EAAE;QAChF,wBAAwB,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,kBAAkB,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,gBAAgB,EAAE;KAC/I,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/cli/init/execute.ts"],"names":[],"mappings":"AAOA,SAAS,eAAe,CAAC,YAAyB,EAAE,KAAgB;IAClE,OAAO,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,EACrC,UAAU,EACV,MAAM,EACN,SAAS,EACT,MAAM,GAMP;IACC,MAAM,SAAS,GAA4B,EAAE,CAAC;IAC9C,MAAM,YAAY,GAAG,eAAe,CAAC,UAAU,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACzE,MAAM,WAAW,GAAG,eAAe,CAAC,UAAU,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACvE,MAAM,YAAY,GAAG,eAAe,CAAC,UAAU,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;IAE/E,KAAK,MAAM,QAAQ,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QAC5C,MAAM,eAAe,GAAG,QAAQ,KAAK,aAAa,CAAC;QACnD,MAAM,aAAa,GAAG,WAAW,IAAI,eAAe,CAAC;QACrD,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,WAAW,IAAI,CAAC,eAAe,EAAE,CAAC;YACpC,QAAQ,CAAC,IAAI,CACX,+BAA+B,QAAQ,0CAA0C,CAClF,CAAC;QACJ,CAAC;QAED,MAAM,cAAc,GAA4B;YAC9C,QAAQ;YACR,KAAK,EAAE,UAAU,CAAC,WAAW;YAC7B,MAAM,EAAE,UAAU,CAAC,QAAQ;YAC3B,YAAY,EAAE,UAAU,CAAC,YAAY;YACrC,SAAS,EAAE,UAAU,CAAC,YAAY,KAAK,MAAM;YAC7C,MAAM;YACN,SAAS;YACT,GAAG,EAAE,IAAI;YACT,MAAM;YACN,OAAO,EAAE,YAAY;YACrB,MAAM,EAAE,aAAa;YACrB,2BAA2B,EAAE,KAAK;YAClC,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,UAAU,EAAE,YAAY;YACxB,WAAW,EAAE,YAAY,IAAI,aAAa;YAC1C,UAAU,EAAE,YAAY,IAAI,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO;YAC3E,WAAW,EAAE,OAAO;YACpB,aAAa,EACX,YAAY,IAAI,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK;YAClE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;YAC9D,kBAAkB,EAAE,YAAY;gBAC9B,CAAC,CAAC,UAAU,CAAC,kBAAkB;gBAC/B,CAAC,CAAC,SAAS;YACb,cAAc,EAAE,IAAI;SACrB,CAAC;QAEF,SAAS,CAAC,IAAI,CAAC;YACb,QAAQ;YACR,cAAc;YACd,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,KAAK,EAAE,SAAS;KACjB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAgC;IAChE,MAAM,eAAe,GAAG,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACpE,OAAO;QACL,oBAAoB;QACpB,aAAa,UAAU,CAAC,QAAQ,EAAE;QAClC,gBAAgB,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACjD,oBAAoB,UAAU,CAAC,YAAY,EAAE;QAC7C,mBAAmB,UAAU,CAAC,WAAW,EAAE;QAC3C,gBAAgB,UAAU,CAAC,QAAQ,EAAE;QACrC,kBAAkB,UAAU,CAAC,UAAU,GAAG,UAAU,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;QACzJ,qBAAqB,UAAU,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACzG,mBAAmB,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,EAAE;QAChF,wBAAwB,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,kBAAkB,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,gBAAgB,EAAE;KAC/I,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
package/mcp/README.md CHANGED
@@ -24,13 +24,13 @@ This server exposes built-in tools plus dynamic passthrough tools discovered fro
24
24
 
25
25
  | Domain | Tools | Purpose |
26
26
  | ----------- | ----- | ------------------------------------------------------------- |
27
- | **Skills** | 5 | Browse/search/get + budget reporting for skill definitions |
27
+ | **Skills** | 7 | Browse/search/validate/get + targeted reference loading for skills |
28
28
  | **Postman config** | 3 | Read/write Postman MCP mode in `cbx_config.json` |
29
29
  | **Stitch config** | 3 | Read/write Stitch active profile in `cbx_config.json` |
30
30
  | **Postman passthrough** | dynamic | `postman.<tool_name>` for all discovered upstream Postman tools |
31
31
  | **Stitch passthrough** | dynamic | `stitch.<tool_name>` for all discovered upstream Stitch tools |
32
32
 
33
- The skill vault uses a **lazy content model**: startup only scans metadata (category/name/path). Full `SKILL.md` content is loaded on-demand via `skill_get` only.
33
+ The skill vault uses a **lazy content model**: startup only scans metadata (category/name/path). Exact skill selection is validated via `skill_validate`, `skill_get` loads the core `SKILL.md`, and sidecar markdown is loaded only when needed via `skill_get_reference`.
34
34
 
35
35
  ## Architecture
36
36
 
@@ -67,7 +67,9 @@ mcp/
67
67
  │ │ ├── skillListCategories.ts
68
68
  │ │ ├── skillBrowseCategory.ts
69
69
  │ │ ├── skillSearch.ts
70
+ │ │ ├── skillValidate.ts
70
71
  │ │ ├── skillGet.ts
72
+ │ │ ├── skillGetReference.ts
71
73
  │ │ ├── skillBudgetReport.ts
72
74
  │ │ ├── postmanModes.ts # Mode ↔ URL mapping
73
75
  │ │ ├── postmanGetMode.ts
@@ -269,6 +271,32 @@ Search skills by keyword.
269
271
  }
270
272
  ```
271
273
 
274
+ #### `skill_validate`
275
+
276
+ Validate an exact skill ID before loading it.
277
+
278
+ **Input**:
279
+
280
+ ```json
281
+ { "id": "flutter-expert" }
282
+ ```
283
+
284
+ **Output**:
285
+
286
+ ```json
287
+ {
288
+ "id": "flutter-expert",
289
+ "exists": true,
290
+ "canonicalId": "flutter-expert",
291
+ "category": "mobile",
292
+ "description": "Flutter app architecture...",
293
+ "isWrapper": false,
294
+ "isAlias": false,
295
+ "replacementId": null,
296
+ "availableReferences": ["references/project-structure.md"]
297
+ }
298
+ ```
299
+
272
300
  #### `skill_get`
273
301
 
274
302
  Get the full SKILL.md content for a specific skill.
@@ -276,12 +304,30 @@ Get the full SKILL.md content for a specific skill.
276
304
  **Input**:
277
305
 
278
306
  ```json
279
- { "id": "nestjs-expert" }
307
+ { "id": "nestjs-expert", "includeReferences": false }
280
308
  ```
281
309
 
282
310
  **Output**: Full markdown content of the skill file (as `type: "text"` content block).
283
311
  `structuredContent.metrics` includes `loadedSkillEstimatedTokens` and estimated savings vs full catalog.
284
312
 
313
+ By policy, agents should call `includeReferences: false` by default and fetch sidecar docs only when needed.
314
+
315
+ #### `skill_get_reference`
316
+
317
+ Get one validated markdown sidecar file for a skill.
318
+
319
+ **Input**:
320
+
321
+ ```json
322
+ {
323
+ "id": "flutter-expert",
324
+ "path": "references/project-structure.md"
325
+ }
326
+ ```
327
+
328
+ **Output**: Raw markdown content of the requested reference file (as `type: "text"` content block).
329
+ `structuredContent` includes the resolved relative path and token metrics.
330
+
285
331
  #### `skill_budget_report`
286
332
 
287
333
  Consolidated Skill Log + Context Budget report for selected/loaded skill IDs.
package/mcp/dist/index.js CHANGED
@@ -433,6 +433,36 @@ async function readFullSkillContent(skillPath) {
433
433
  }
434
434
  var MARKDOWN_LINK_RE = /\[[^\]]+\]\(([^)]+)\)/g;
435
435
  var MAX_REFERENCED_FILES = 25;
436
+ function parseFrontmatter2(content) {
437
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
438
+ if (!match) return { raw: "", body: content };
439
+ return { raw: match[1], body: content.slice(match[0].length) };
440
+ }
441
+ function parseMetadataFromFrontmatter(frontmatter) {
442
+ const lines = frontmatter.split(/\r?\n/);
443
+ const metadata = {};
444
+ let inMetadata = false;
445
+ for (const line of lines) {
446
+ if (/^metadata\s*:\s*$/.test(line)) {
447
+ inMetadata = true;
448
+ continue;
449
+ }
450
+ if (!inMetadata) continue;
451
+ if (!line.trim()) continue;
452
+ if (!/^\s+/.test(line)) break;
453
+ const kv = line.match(/^\s+([A-Za-z0-9_-]+)\s*:\s*(.+)\s*$/);
454
+ if (!kv) continue;
455
+ metadata[kv[1]] = kv[2].trim().replace(/^['"]|['"]$/g, "");
456
+ }
457
+ return metadata;
458
+ }
459
+ function parseSkillFrontmatter(content) {
460
+ const { raw } = parseFrontmatter2(content);
461
+ return {
462
+ description: parseDescriptionFromFrontmatter(content, Number.MAX_SAFE_INTEGER),
463
+ metadata: parseMetadataFromFrontmatter(raw)
464
+ };
465
+ }
436
466
  function normalizeLinkTarget(rawTarget) {
437
467
  let target = String(rawTarget || "").trim();
438
468
  if (!target) return null;
@@ -519,6 +549,36 @@ async function collectSiblingMarkdownTargets(skillDir) {
519
549
  targets.sort((a, b) => a.localeCompare(b));
520
550
  return targets;
521
551
  }
552
+ async function listReferencedMarkdownPaths(skillPath, skillContent) {
553
+ const source = skillContent ?? await readFullSkillContent(skillPath);
554
+ const skillDir = path3.dirname(skillPath);
555
+ const explicitTargets = collectReferencedMarkdownTargets(source);
556
+ const siblingTargets = await collectSiblingMarkdownTargets(skillDir);
557
+ const merged = /* @__PURE__ */ new Set([...explicitTargets, ...siblingTargets]);
558
+ return [...merged].sort((a, b) => a.localeCompare(b)).slice(0, MAX_REFERENCED_FILES);
559
+ }
560
+ async function readSkillReferenceFile(skillPath, relativePath) {
561
+ const normalized = String(relativePath || "").trim();
562
+ if (!normalized || !normalized.toLowerCase().endsWith(".md")) {
563
+ throw new Error("Reference path must be a non-empty relative markdown file.");
564
+ }
565
+ const available = await listReferencedMarkdownPaths(skillPath);
566
+ if (!available.includes(normalized)) {
567
+ throw new Error(
568
+ `Reference path "${normalized}" is not available for this skill.`
569
+ );
570
+ }
571
+ const skillDir = path3.dirname(skillPath);
572
+ const resolved = path3.resolve(skillDir, normalized);
573
+ const relative = path3.relative(skillDir, resolved);
574
+ if (relative.startsWith("..") || path3.isAbsolute(relative)) {
575
+ throw new Error(`Reference path "${normalized}" escapes the skill directory.`);
576
+ }
577
+ return {
578
+ relativePath: relative.split(path3.sep).join("/"),
579
+ content: await readFile(resolved, "utf8")
580
+ };
581
+ }
522
582
  async function readSkillContentWithReferences(skillPath, includeReferences = true) {
523
583
  const skillContent = await readFullSkillContent(skillPath);
524
584
  if (!includeReferences) {
@@ -527,6 +587,10 @@ async function readSkillContentWithReferences(skillPath, includeReferences = tru
527
587
  const references = await readReferencedMarkdownFiles(skillPath, skillContent);
528
588
  return { skillContent, references };
529
589
  }
590
+ async function readSkillFrontmatter(skillPath) {
591
+ const content = await readFullSkillContent(skillPath);
592
+ return parseSkillFrontmatter(content);
593
+ }
530
594
  async function enrichWithDescriptions(skills, maxLength) {
531
595
  return Promise.all(
532
596
  skills.map(async (skill) => {
@@ -539,7 +603,7 @@ async function enrichWithDescriptions(skills, maxLength) {
539
603
 
540
604
  // src/server.ts
541
605
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
542
- import { z as z13 } from "zod";
606
+ import { z as z15 } from "zod";
543
607
 
544
608
  // src/tools/skillListCategories.ts
545
609
  import { z as z2 } from "zod";
@@ -709,13 +773,85 @@ async function handleSkillSearch(args, manifest, summaryMaxLength, charsPerToken
709
773
  };
710
774
  }
711
775
 
712
- // src/tools/skillGet.ts
776
+ // src/tools/skillValidate.ts
713
777
  import { z as z5 } from "zod";
778
+ var skillValidateName = "skill_validate";
779
+ var skillValidateDescription = "Validate an exact skill ID before loading it. Returns alias metadata and discoverable reference markdown paths.";
780
+ var skillValidateSchema = z5.object({
781
+ id: z5.string().describe("The exact skill ID (directory name) to validate")
782
+ });
783
+ function assertConcreteSkillId(id) {
784
+ if (id.startsWith("workflow-") || id.startsWith("agent-")) {
785
+ invalidInput(
786
+ `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.`
787
+ );
788
+ }
789
+ }
790
+ async function handleSkillValidate(args, manifest, charsPerToken) {
791
+ const { id } = args;
792
+ assertConcreteSkillId(id);
793
+ const skill = manifest.skills.find((entry) => entry.id === id);
794
+ if (!skill) {
795
+ const payload2 = {
796
+ id,
797
+ exists: false,
798
+ canonicalId: null,
799
+ category: null,
800
+ description: null,
801
+ isWrapper: false,
802
+ isAlias: false,
803
+ replacementId: null,
804
+ availableReferences: []
805
+ };
806
+ const text2 = JSON.stringify(payload2, null, 2);
807
+ const metrics2 = buildSkillToolMetrics({
808
+ charsPerToken,
809
+ fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
810
+ responseEstimatedTokens: estimateTokensFromText(text2, charsPerToken),
811
+ responseCharacterCount: text2.length
812
+ });
813
+ return {
814
+ content: [{ type: "text", text: text2 }],
815
+ structuredContent: { ...payload2, metrics: metrics2 },
816
+ _meta: { metrics: metrics2 }
817
+ };
818
+ }
819
+ const frontmatter = await readSkillFrontmatter(skill.path);
820
+ const replacementId = frontmatter.metadata.replaced_by || frontmatter.metadata.alias_of || null;
821
+ const isAlias = Boolean(replacementId || frontmatter.metadata.deprecated);
822
+ const availableReferences = await listReferencedMarkdownPaths(skill.path);
823
+ const payload = {
824
+ id,
825
+ exists: true,
826
+ canonicalId: replacementId || skill.id,
827
+ category: skill.category,
828
+ description: frontmatter.description || skill.description || null,
829
+ isWrapper: false,
830
+ isAlias,
831
+ replacementId,
832
+ availableReferences
833
+ };
834
+ const text = JSON.stringify(payload, null, 2);
835
+ const metrics = buildSkillToolMetrics({
836
+ charsPerToken,
837
+ fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
838
+ responseEstimatedTokens: estimateTokensFromText(text, charsPerToken),
839
+ responseCharacterCount: text.length
840
+ });
841
+ return {
842
+ content: [{ type: "text", text }],
843
+ structuredContent: { ...payload, metrics },
844
+ _meta: { metrics }
845
+ };
846
+ }
847
+
848
+ // src/tools/skillGet.ts
849
+ import { z as z6 } from "zod";
714
850
  var skillGetName = "skill_get";
715
851
  var skillGetDescription = "Get full content of a specific skill by ID. Returns SKILL.md content and optionally direct referenced markdown files.";
716
- var skillGetSchema = z5.object({
717
- id: z5.string().describe("The skill ID (directory name) to retrieve"),
718
- includeReferences: z5.boolean().optional().describe(
852
+ var skillGetSchema = z6.object({
853
+ id: z6.string().describe("The skill ID (directory name) to retrieve"),
854
+ includeReferences: z6.boolean().optional().describe(
719
855
  "Whether to include direct local markdown references from SKILL.md (default: true)"
720
856
  )
721
857
  });
@@ -780,13 +916,63 @@ async function handleSkillGet(args, manifest, charsPerToken) {
780
916
  };
781
917
  }
782
918
 
919
+ // src/tools/skillGetReference.ts
920
+ import { z as z7 } from "zod";
921
+ var skillGetReferenceName = "skill_get_reference";
922
+ var skillGetReferenceDescription = "Get one validated markdown reference file for a skill by exact relative path.";
923
+ var skillGetReferenceSchema = z7.object({
924
+ id: z7.string().describe("The exact skill ID (directory name)"),
925
+ path: z7.string().describe("Exact relative markdown reference path exposed by skill_validate")
926
+ });
927
+ function assertConcreteSkillId2(id) {
928
+ if (id.startsWith("workflow-") || id.startsWith("agent-")) {
929
+ invalidInput(
930
+ `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.`
931
+ );
932
+ }
933
+ }
934
+ async function handleSkillGetReference(args, manifest, charsPerToken) {
935
+ const { id, path: path8 } = args;
936
+ assertConcreteSkillId2(id);
937
+ const skill = manifest.skills.find((entry) => entry.id === id);
938
+ if (!skill) {
939
+ notFound("Skill", id);
940
+ }
941
+ let reference;
942
+ try {
943
+ reference = await readSkillReferenceFile(skill.path, path8);
944
+ } catch (error) {
945
+ invalidInput(error instanceof Error ? error.message : String(error));
946
+ }
947
+ const metrics = buildSkillToolMetrics({
948
+ charsPerToken,
949
+ fullCatalogEstimatedTokens: manifest.fullCatalogEstimatedTokens,
950
+ responseEstimatedTokens: estimateTokensFromText(
951
+ reference.content,
952
+ charsPerToken
953
+ ),
954
+ responseCharacterCount: reference.content.length
955
+ });
956
+ return {
957
+ content: [{ type: "text", text: reference.content }],
958
+ structuredContent: {
959
+ skillId: id,
960
+ path: reference.relativePath,
961
+ metrics
962
+ },
963
+ _meta: {
964
+ metrics
965
+ }
966
+ };
967
+ }
968
+
783
969
  // src/tools/skillBudgetReport.ts
784
- import { z as z6 } from "zod";
970
+ import { z as z8 } from "zod";
785
971
  var skillBudgetReportName = "skill_budget_report";
786
972
  var skillBudgetReportDescription = "Report estimated context/token budget for selected and loaded skills compared to the full skill catalog.";
787
- var skillBudgetReportSchema = z6.object({
788
- selectedSkillIds: z6.array(z6.string()).default([]).describe("Skill IDs selected after search/browse."),
789
- loadedSkillIds: z6.array(z6.string()).default([]).describe("Skill IDs loaded via skill_get.")
973
+ var skillBudgetReportSchema = z8.object({
974
+ selectedSkillIds: z8.array(z8.string()).default([]).describe("Skill IDs selected after search/browse."),
975
+ loadedSkillIds: z8.array(z8.string()).default([]).describe("Skill IDs loaded via skill_get.")
790
976
  });
791
977
  function uniqueStrings(values) {
792
978
  return [...new Set(values.map((value) => String(value)))];
@@ -876,7 +1062,7 @@ function handleSkillBudgetReport(args, manifest, charsPerToken) {
876
1062
  }
877
1063
 
878
1064
  // src/tools/postmanGetMode.ts
879
- import { z as z7 } from "zod";
1065
+ import { z as z9 } from "zod";
880
1066
 
881
1067
  // src/cbxConfig/paths.ts
882
1068
  import path4 from "path";
@@ -1136,8 +1322,8 @@ function isValidMode(mode) {
1136
1322
  // src/tools/postmanGetMode.ts
1137
1323
  var postmanGetModeName = "postman_get_mode";
1138
1324
  var postmanGetModeDescription = "Get the current Postman MCP mode from cbx_config.json. Returns the friendly mode name and URL.";
1139
- var postmanGetModeSchema = z7.object({
1140
- scope: z7.enum(["global", "project", "auto"]).optional().describe(
1325
+ var postmanGetModeSchema = z9.object({
1326
+ scope: z9.enum(["global", "project", "auto"]).optional().describe(
1141
1327
  "Config scope to read. Default: auto (project if exists, else global)"
1142
1328
  )
1143
1329
  });
@@ -1192,12 +1378,12 @@ function handlePostmanGetMode(args) {
1192
1378
  }
1193
1379
 
1194
1380
  // src/tools/postmanSetMode.ts
1195
- import { z as z8 } from "zod";
1381
+ import { z as z10 } from "zod";
1196
1382
  var postmanSetModeName = "postman_set_mode";
1197
1383
  var postmanSetModeDescription = "Set the Postman MCP mode in cbx_config.json. Modes: minimal, code, full.";
1198
- var postmanSetModeSchema = z8.object({
1199
- mode: z8.enum(["minimal", "code", "full"]).describe("Postman MCP mode to set: minimal, code, or full"),
1200
- scope: z8.enum(["global", "project", "auto"]).optional().describe(
1384
+ var postmanSetModeSchema = z10.object({
1385
+ mode: z10.enum(["minimal", "code", "full"]).describe("Postman MCP mode to set: minimal, code, or full"),
1386
+ scope: z10.enum(["global", "project", "auto"]).optional().describe(
1201
1387
  "Config scope to write. Default: auto (project if exists, else global)"
1202
1388
  )
1203
1389
  });
@@ -1236,11 +1422,11 @@ function handlePostmanSetMode(args) {
1236
1422
  }
1237
1423
 
1238
1424
  // src/tools/postmanGetStatus.ts
1239
- import { z as z9 } from "zod";
1425
+ import { z as z11 } from "zod";
1240
1426
  var postmanGetStatusName = "postman_get_status";
1241
1427
  var postmanGetStatusDescription = "Get full Postman configuration status including mode, URL, and workspace ID.";
1242
- var postmanGetStatusSchema = z9.object({
1243
- scope: z9.enum(["global", "project", "auto"]).optional().describe(
1428
+ var postmanGetStatusSchema = z11.object({
1429
+ scope: z11.enum(["global", "project", "auto"]).optional().describe(
1244
1430
  "Config scope to read. Default: auto (project if exists, else global)"
1245
1431
  )
1246
1432
  });
@@ -1280,11 +1466,11 @@ function handlePostmanGetStatus(args) {
1280
1466
  }
1281
1467
 
1282
1468
  // src/tools/stitchGetMode.ts
1283
- import { z as z10 } from "zod";
1469
+ import { z as z12 } from "zod";
1284
1470
  var stitchGetModeName = "stitch_get_mode";
1285
1471
  var stitchGetModeDescription = "Get the active Stitch profile name and URL from cbx_config.json. Never exposes API keys.";
1286
- var stitchGetModeSchema = z10.object({
1287
- scope: z10.enum(["global", "project", "auto"]).optional().describe(
1472
+ var stitchGetModeSchema = z12.object({
1473
+ scope: z12.enum(["global", "project", "auto"]).optional().describe(
1288
1474
  "Config scope to read. Default: auto (project if exists, else global)"
1289
1475
  )
1290
1476
  });
@@ -1319,12 +1505,12 @@ function handleStitchGetMode(args) {
1319
1505
  }
1320
1506
 
1321
1507
  // src/tools/stitchSetProfile.ts
1322
- import { z as z11 } from "zod";
1508
+ import { z as z13 } from "zod";
1323
1509
  var stitchSetProfileName = "stitch_set_profile";
1324
1510
  var stitchSetProfileDescription = "Set the active Stitch profile in cbx_config.json. The profile must already exist in the config.";
1325
- var stitchSetProfileSchema = z11.object({
1326
- profileName: z11.string().min(1).describe("Name of the Stitch profile to activate"),
1327
- scope: z11.enum(["global", "project", "auto"]).optional().describe(
1511
+ var stitchSetProfileSchema = z13.object({
1512
+ profileName: z13.string().min(1).describe("Name of the Stitch profile to activate"),
1513
+ scope: z13.enum(["global", "project", "auto"]).optional().describe(
1328
1514
  "Config scope to write. Default: auto (project if exists, else global)"
1329
1515
  )
1330
1516
  });
@@ -1369,11 +1555,11 @@ function handleStitchSetProfile(args) {
1369
1555
  }
1370
1556
 
1371
1557
  // src/tools/stitchGetStatus.ts
1372
- import { z as z12 } from "zod";
1558
+ import { z as z14 } from "zod";
1373
1559
  var stitchGetStatusName = "stitch_get_status";
1374
1560
  var stitchGetStatusDescription = "Get full Stitch configuration status including active profile, all profile names, and URLs. Never exposes API keys.";
1375
- var stitchGetStatusSchema = z12.object({
1376
- scope: z12.enum(["global", "project", "auto"]).optional().describe(
1561
+ var stitchGetStatusSchema = z14.object({
1562
+ scope: z14.enum(["global", "project", "auto"]).optional().describe(
1377
1563
  "Config scope to read. Default: auto (project if exists, else global)"
1378
1564
  )
1379
1565
  });
@@ -1458,6 +1644,17 @@ var TOOL_REGISTRY = [
1458
1644
  ctx.charsPerToken
1459
1645
  )
1460
1646
  },
1647
+ {
1648
+ name: skillValidateName,
1649
+ description: skillValidateDescription,
1650
+ schema: skillValidateSchema,
1651
+ category: "skill",
1652
+ createHandler: (ctx) => async (args) => handleSkillValidate(
1653
+ args,
1654
+ ctx.manifest,
1655
+ ctx.charsPerToken
1656
+ )
1657
+ },
1461
1658
  {
1462
1659
  name: skillGetName,
1463
1660
  description: skillGetDescription,
@@ -1469,6 +1666,17 @@ var TOOL_REGISTRY = [
1469
1666
  ctx.charsPerToken
1470
1667
  )
1471
1668
  },
1669
+ {
1670
+ name: skillGetReferenceName,
1671
+ description: skillGetReferenceDescription,
1672
+ schema: skillGetReferenceSchema,
1673
+ category: "skill",
1674
+ createHandler: (ctx) => async (args) => handleSkillGetReference(
1675
+ args,
1676
+ ctx.manifest,
1677
+ ctx.charsPerToken
1678
+ )
1679
+ },
1472
1680
  {
1473
1681
  name: skillBudgetReportName,
1474
1682
  description: skillBudgetReportDescription,
@@ -1850,7 +2058,7 @@ async function createServer({
1850
2058
  `Registered ${TOOL_REGISTRY.length} built-in tools from registry`
1851
2059
  );
1852
2060
  const upstreamCatalogs = await discoverUpstreamCatalogs(defaultConfigScope);
1853
- const dynamicSchema = z13.object({}).passthrough();
2061
+ const dynamicSchema = z15.object({}).passthrough();
1854
2062
  const registeredDynamicToolNames = /* @__PURE__ */ new Set();
1855
2063
  for (const catalog of [upstreamCatalogs.postman, upstreamCatalogs.stitch]) {
1856
2064
  for (const tool of catalog.tools) {
@@ -40,6 +40,13 @@ export {
40
40
  handleSkillSearch,
41
41
  } from "./skillSearch.js";
42
42
 
43
+ export {
44
+ skillValidateName,
45
+ skillValidateDescription,
46
+ skillValidateSchema,
47
+ handleSkillValidate,
48
+ } from "./skillValidate.js";
49
+
43
50
  export {
44
51
  skillGetName,
45
52
  skillGetDescription,
@@ -47,6 +54,13 @@ export {
47
54
  handleSkillGet,
48
55
  } from "./skillGet.js";
49
56
 
57
+ export {
58
+ skillGetReferenceName,
59
+ skillGetReferenceDescription,
60
+ skillGetReferenceSchema,
61
+ handleSkillGetReference,
62
+ } from "./skillGetReference.js";
63
+
50
64
  export {
51
65
  skillBudgetReportName,
52
66
  skillBudgetReportDescription,
@@ -38,7 +38,9 @@ describe("tool registry", () => {
38
38
  expect(names).toContain("skill_list_categories");
39
39
  expect(names).toContain("skill_browse_category");
40
40
  expect(names).toContain("skill_search");
41
+ expect(names).toContain("skill_validate");
41
42
  expect(names).toContain("skill_get");
43
+ expect(names).toContain("skill_get_reference");
42
44
  expect(names).toContain("skill_budget_report");
43
45
  expect(names).toContain("postman_get_mode");
44
46
  expect(names).toContain("postman_set_mode");
@@ -48,8 +50,8 @@ describe("tool registry", () => {
48
50
  expect(names).toContain("stitch_get_status");
49
51
  });
50
52
 
51
- it("has exactly 11 built-in tools", () => {
52
- expect(TOOL_REGISTRY).toHaveLength(11);
53
+ it("has exactly 13 built-in tools", () => {
54
+ expect(TOOL_REGISTRY).toHaveLength(13);
53
55
  });
54
56
 
55
57
  it("has no duplicate tool names", () => {
@@ -70,7 +72,7 @@ describe("tool registry", () => {
70
72
 
71
73
  it("filters by category", () => {
72
74
  const skillTools = getToolsByCategory("skill");
73
- expect(skillTools).toHaveLength(5);
75
+ expect(skillTools).toHaveLength(7);
74
76
  expect(skillTools.every((t) => t.category === "skill")).toBe(true);
75
77
 
76
78
  const postmanTools = getToolsByCategory("postman");
@@ -103,11 +105,11 @@ describe("tool registry", () => {
103
105
 
104
106
  it("buildRegistrySummary produces correct structure", () => {
105
107
  const summary = buildRegistrySummary();
106
- expect(summary.totalTools).toBe(11);
108
+ expect(summary.totalTools).toBe(13);
107
109
  expect(summary.categories).toHaveProperty("skill");
108
110
  expect(summary.categories).toHaveProperty("postman");
109
111
  expect(summary.categories).toHaveProperty("stitch");
110
- expect(summary.categories.skill.tools).toHaveLength(5);
112
+ expect(summary.categories.skill.tools).toHaveLength(7);
111
113
  expect(summary.categories.postman.tools).toHaveLength(3);
112
114
  expect(summary.categories.stitch.tools).toHaveLength(3);
113
115
  });
@@ -67,6 +67,13 @@ import {
67
67
  handleSkillSearch,
68
68
  } from "./skillSearch.js";
69
69
 
70
+ import {
71
+ skillValidateName,
72
+ skillValidateDescription,
73
+ skillValidateSchema,
74
+ handleSkillValidate,
75
+ } from "./skillValidate.js";
76
+
70
77
  import {
71
78
  skillGetName,
72
79
  skillGetDescription,
@@ -74,6 +81,13 @@ import {
74
81
  handleSkillGet,
75
82
  } from "./skillGet.js";
76
83
 
84
+ import {
85
+ skillGetReferenceName,
86
+ skillGetReferenceDescription,
87
+ skillGetReferenceSchema,
88
+ handleSkillGetReference,
89
+ } from "./skillGetReference.js";
90
+
77
91
  import {
78
92
  skillBudgetReportName,
79
93
  skillBudgetReportDescription,
@@ -175,6 +189,18 @@ export const TOOL_REGISTRY: readonly ToolRegistryEntry[] = [
175
189
  ctx.charsPerToken,
176
190
  ),
177
191
  },
192
+ {
193
+ name: skillValidateName,
194
+ description: skillValidateDescription,
195
+ schema: skillValidateSchema,
196
+ category: "skill",
197
+ createHandler: (ctx) => async (args) =>
198
+ handleSkillValidate(
199
+ args as z.infer<typeof skillValidateSchema>,
200
+ ctx.manifest,
201
+ ctx.charsPerToken,
202
+ ),
203
+ },
178
204
  {
179
205
  name: skillGetName,
180
206
  description: skillGetDescription,
@@ -187,6 +213,18 @@ export const TOOL_REGISTRY: readonly ToolRegistryEntry[] = [
187
213
  ctx.charsPerToken,
188
214
  ),
189
215
  },
216
+ {
217
+ name: skillGetReferenceName,
218
+ description: skillGetReferenceDescription,
219
+ schema: skillGetReferenceSchema,
220
+ category: "skill",
221
+ createHandler: (ctx) => async (args) =>
222
+ handleSkillGetReference(
223
+ args as z.infer<typeof skillGetReferenceSchema>,
224
+ ctx.manifest,
225
+ ctx.charsPerToken,
226
+ ),
227
+ },
190
228
  {
191
229
  name: skillBudgetReportName,
192
230
  description: skillBudgetReportDescription,