@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.
@@ -2,10 +2,11 @@
2
2
  * Cubis Foundry MCP Server – vault manifest.
3
3
  *
4
4
  * Browse/search operations extract frontmatter description only (truncated).
5
- * Full SKILL.md content is read only by skill_get.
5
+ * Full SKILL.md content (and direct referenced markdown files) is read by skill_get.
6
6
  */
7
7
 
8
- import { readFile } from "node:fs/promises";
8
+ import { readdir, readFile } from "node:fs/promises";
9
+ import path from "node:path";
9
10
  import type { SkillPointer, VaultManifest } from "./types.js";
10
11
  import { logger } from "../utils/logger.js";
11
12
  import { estimateTokensFromBytes } from "../telemetry/tokenBudget.js";
@@ -87,12 +88,136 @@ export function parseDescriptionFromFrontmatter(
87
88
 
88
89
  /**
89
90
  * Read the full content of a SKILL.md file.
90
- * This is the ONLY function that reads full file content (lazy model).
91
91
  */
92
92
  export async function readFullSkillContent(skillPath: string): Promise<string> {
93
93
  return readFile(skillPath, "utf8");
94
94
  }
95
95
 
96
+ const MARKDOWN_LINK_RE = /\[[^\]]+\]\(([^)]+)\)/g;
97
+ const MAX_REFERENCED_FILES = 25;
98
+
99
+ export interface ReferencedSkillFile {
100
+ relativePath: string;
101
+ content: string;
102
+ }
103
+
104
+ function normalizeLinkTarget(rawTarget: string): string | null {
105
+ let target = String(rawTarget || "").trim();
106
+ if (!target) return null;
107
+
108
+ // Support links wrapped in angle brackets: [x](<references/doc.md>)
109
+ if (target.startsWith("<") && target.endsWith(">")) {
110
+ target = target.slice(1, -1).trim();
111
+ }
112
+
113
+ // Strip optional title segment: [x](path "title")
114
+ const firstSpace = target.search(/\s/);
115
+ if (firstSpace > 0) {
116
+ target = target.slice(0, firstSpace).trim();
117
+ }
118
+
119
+ // Ignore anchors/query fragments for file loading
120
+ target = target.split("#")[0].split("?")[0].trim();
121
+ if (!target) return null;
122
+
123
+ // Skip URLs/protocol links and absolute paths
124
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(target)) return null;
125
+ if (/^[a-zA-Z]:[\\/]/.test(target)) return null; // Windows absolute path
126
+ if (path.isAbsolute(target)) return null;
127
+
128
+ return target;
129
+ }
130
+
131
+ function collectReferencedMarkdownTargets(skillContent: string): string[] {
132
+ const targets: string[] = [];
133
+ const seen = new Set<string>();
134
+
135
+ for (const match of skillContent.matchAll(MARKDOWN_LINK_RE)) {
136
+ const raw = match[1];
137
+ const normalized = normalizeLinkTarget(raw);
138
+ if (!normalized) continue;
139
+ if (!normalized.toLowerCase().endsWith(".md")) continue;
140
+ if (seen.has(normalized)) continue;
141
+ seen.add(normalized);
142
+ targets.push(normalized);
143
+ if (targets.length >= MAX_REFERENCED_FILES) break;
144
+ }
145
+
146
+ return targets;
147
+ }
148
+
149
+ async function readReferencedMarkdownFiles(
150
+ skillPath: string,
151
+ skillContent: string,
152
+ ): Promise<ReferencedSkillFile[]> {
153
+ const skillDir = path.dirname(skillPath);
154
+ let targets = collectReferencedMarkdownTargets(skillContent);
155
+ if (targets.length === 0) {
156
+ targets = await collectSiblingMarkdownTargets(skillDir);
157
+ }
158
+ const references: ReferencedSkillFile[] = [];
159
+
160
+ for (const target of targets) {
161
+ const resolved = path.resolve(skillDir, target);
162
+ const relative = path.relative(skillDir, resolved);
163
+
164
+ // Prevent path traversal outside the skill directory.
165
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
166
+ continue;
167
+ }
168
+
169
+ if (path.basename(resolved).toLowerCase() === "skill.md") continue;
170
+
171
+ try {
172
+ const content = await readFile(resolved, "utf8");
173
+ references.push({
174
+ relativePath: relative.split(path.sep).join("/"),
175
+ content,
176
+ });
177
+ } catch (err) {
178
+ logger.debug(`Failed to read referenced markdown ${resolved}: ${err}`);
179
+ }
180
+ }
181
+
182
+ return references;
183
+ }
184
+
185
+ async function collectSiblingMarkdownTargets(skillDir: string): Promise<string[]> {
186
+ const entries = await readdir(skillDir, { withFileTypes: true }).catch(
187
+ () => [],
188
+ );
189
+ const targets: string[] = [];
190
+
191
+ for (const entry of entries) {
192
+ if (!entry.isFile()) continue;
193
+ if (entry.name.startsWith(".")) continue;
194
+ if (!entry.name.toLowerCase().endsWith(".md")) continue;
195
+ if (entry.name.toLowerCase() === "skill.md") continue;
196
+ targets.push(entry.name);
197
+ if (targets.length >= MAX_REFERENCED_FILES) break;
198
+ }
199
+
200
+ targets.sort((a, b) => a.localeCompare(b));
201
+ return targets;
202
+ }
203
+
204
+ /**
205
+ * Read full SKILL.md content and any direct local markdown references declared
206
+ * in the skill document. Reference discovery is one-hop (no recursive crawling).
207
+ */
208
+ export async function readSkillContentWithReferences(
209
+ skillPath: string,
210
+ includeReferences = true,
211
+ ): Promise<{ skillContent: string; references: ReferencedSkillFile[] }> {
212
+ const skillContent = await readFullSkillContent(skillPath);
213
+ if (!includeReferences) {
214
+ return { skillContent, references: [] };
215
+ }
216
+
217
+ const references = await readReferencedMarkdownFiles(skillPath, skillContent);
218
+ return { skillContent, references };
219
+ }
220
+
96
221
  /**
97
222
  * Enrich skill pointers with descriptions (for browse/search results).
98
223
  */
@@ -22,6 +22,12 @@ function createSkill(root: string, id: string): void {
22
22
  );
23
23
  }
24
24
 
25
+ function createSkillFromContent(root: string, id: string, content: string): void {
26
+ const skillDir = path.join(root, id);
27
+ mkdirSync(skillDir, { recursive: true });
28
+ writeFileSync(path.join(skillDir, "SKILL.md"), content, "utf8");
29
+ }
30
+
25
31
  afterEach(() => {
26
32
  while (tempDirs.length > 0) {
27
33
  const dir = tempDirs.pop();
@@ -68,4 +74,33 @@ describe("scanVaultRoots", () => {
68
74
  expect(skills[0].id).toBe("qa-automation-engineer");
69
75
  expect(skills[0].category).toBe("testing");
70
76
  });
77
+
78
+ it("excludes codex wrapper skills from vault discovery", async () => {
79
+ const root = createTempDir("mcp-vault-wrapper-");
80
+
81
+ createSkill(root, "lint-and-validate");
82
+ createSkill(root, "tdd-workflow");
83
+ createSkill(root, "workflow-implement-track");
84
+ createSkill(root, "agent-backend-specialist");
85
+ createSkillFromContent(
86
+ root,
87
+ "custom-wrapper-id",
88
+ [
89
+ "---",
90
+ "name: custom-wrapper-id",
91
+ "description: Wrapper by metadata only",
92
+ "metadata:",
93
+ " wrapper: workflow",
94
+ "---",
95
+ "",
96
+ "# wrapper",
97
+ "",
98
+ ].join("\n"),
99
+ );
100
+
101
+ const skills = await scanVaultRoots([root], "/unused");
102
+ const ids = skills.map((s) => s.id).sort();
103
+
104
+ expect(ids).toEqual(["lint-and-validate", "tdd-workflow"]);
105
+ });
71
106
  });
@@ -5,7 +5,7 @@
5
5
  * Description extraction is deferred to browse/search operations.
6
6
  */
7
7
 
8
- import { readdir, stat } from "node:fs/promises";
8
+ import { open, readdir, stat } from "node:fs/promises";
9
9
  import path from "node:path";
10
10
  import { logger } from "../utils/logger.js";
11
11
  import type { SkillPointer } from "./types.js";
@@ -40,6 +40,14 @@ export async function scanVaultRoots(
40
40
  const skillStat = await stat(skillFile).catch(() => null);
41
41
  if (!skillStat?.isFile()) continue;
42
42
 
43
+ const wrapperKind = await detectWrapperKind(entry, skillFile);
44
+ if (wrapperKind) {
45
+ logger.debug(
46
+ `Skipping wrapper skill ${entry} (${wrapperKind}) at ${skillFile}`,
47
+ );
48
+ continue;
49
+ }
50
+
43
51
  // Derive category from the skill's frontmatter or default to "general"
44
52
  // At scan time we only store the path; category is derived from directory structure
45
53
  skills.push({
@@ -55,6 +63,88 @@ export async function scanVaultRoots(
55
63
  return skills;
56
64
  }
57
65
 
66
+ const WRAPPER_PREFIXES = ["workflow-", "agent-"] as const;
67
+ const WRAPPER_KINDS = new Set(["workflow", "agent"]);
68
+ const FRONTMATTER_PREVIEW_BYTES = 8192;
69
+
70
+ function extractWrapperKindFromId(skillId: string): "workflow" | "agent" | null {
71
+ const lower = skillId.toLowerCase();
72
+ if (lower.startsWith(WRAPPER_PREFIXES[0])) return "workflow";
73
+ if (lower.startsWith(WRAPPER_PREFIXES[1])) return "agent";
74
+ return null;
75
+ }
76
+
77
+ async function readFrontmatterPreview(skillFile: string): Promise<string | null> {
78
+ const handle = await open(skillFile, "r").catch(() => null);
79
+ if (!handle) return null;
80
+
81
+ try {
82
+ // Read only a small head chunk; wrapper metadata lives in frontmatter.
83
+ const buffer = Buffer.alloc(FRONTMATTER_PREVIEW_BYTES);
84
+ const { bytesRead } = await handle.read(
85
+ buffer,
86
+ 0,
87
+ FRONTMATTER_PREVIEW_BYTES,
88
+ 0,
89
+ );
90
+ return buffer.toString("utf8", 0, bytesRead);
91
+ } finally {
92
+ await handle.close();
93
+ }
94
+ }
95
+
96
+ function parseFrontmatter(rawPreview: string): string | null {
97
+ const match = rawPreview.match(/^---\s*\n([\s\S]*?)\n---/);
98
+ return match?.[1] ?? null;
99
+ }
100
+
101
+ function extractMetadataWrapper(frontmatter: string): string | null {
102
+ const lines = frontmatter.split(/\r?\n/);
103
+ let inMetadata = false;
104
+
105
+ for (const line of lines) {
106
+ if (!inMetadata) {
107
+ if (/^\s*metadata\s*:\s*$/.test(line)) {
108
+ inMetadata = true;
109
+ }
110
+ continue;
111
+ }
112
+
113
+ if (!line.trim()) continue;
114
+ if (!/^\s+/.test(line)) break;
115
+
116
+ const match = line.match(/^\s+wrapper\s*:\s*(.+)\s*$/);
117
+ if (!match) continue;
118
+
119
+ const value = match[1].trim().replace(/^['"]|['"]$/g, "").toLowerCase();
120
+ if (WRAPPER_KINDS.has(value)) {
121
+ return value;
122
+ }
123
+ }
124
+
125
+ return null;
126
+ }
127
+
128
+ async function detectWrapperKind(
129
+ skillId: string,
130
+ skillFile: string,
131
+ ): Promise<"workflow" | "agent" | null> {
132
+ const byId = extractWrapperKindFromId(skillId);
133
+ if (byId) return byId;
134
+
135
+ const rawPreview = await readFrontmatterPreview(skillFile);
136
+ if (!rawPreview) return null;
137
+ const frontmatter = parseFrontmatter(rawPreview);
138
+ if (!frontmatter) return null;
139
+
140
+ const byMetadata = extractMetadataWrapper(frontmatter);
141
+ if (byMetadata === "workflow" || byMetadata === "agent") {
142
+ return byMetadata;
143
+ }
144
+
145
+ return null;
146
+ }
147
+
58
148
  /**
59
149
  * Simple category derivation from skill ID conventions.
60
150
  * Skills with common prefixes are grouped together.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubis/foundry",
3
- "version": "0.3.46",
3
+ "version": "0.3.48",
4
4
  "description": "Cubis Foundry CLI for workflow-first AI agent environments",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,6 +36,22 @@
36
36
  "generate:platform-assets": "node scripts/generate-platform-assets.mjs",
37
37
  "sync:skill-mirrors": "node scripts/sync-skill-mirrors.mjs",
38
38
  "generate:skills-index": "node scripts/generate-skills-index.mjs",
39
+ "generate:mcp-manifest": "node scripts/generate-mcp-manifest.mjs",
40
+ "generate:mcp-rules-block": "node scripts/generate-mcp-rules-block.mjs",
41
+ "inject:mcp-rules:codex": "node scripts/generate-mcp-rules-block.mjs --inject workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md",
42
+ "inject:mcp-rules:antigravity": "node scripts/generate-mcp-rules-block.mjs --inject workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md",
43
+ "inject:mcp-rules:copilot-agents": "node scripts/generate-mcp-rules-block.mjs --inject workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md",
44
+ "inject:mcp-rules:copilot": "node scripts/generate-mcp-rules-block.mjs --inject workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md",
45
+ "inject:mcp-rules:all": "npm run inject:mcp-rules:codex && npm run inject:mcp-rules:antigravity && npm run inject:mcp-rules:copilot-agents && npm run inject:mcp-rules:copilot",
46
+ "check:mcp-rules:codex": "node scripts/generate-mcp-rules-block.mjs --check workflows/workflows/agent-environment-setup/platforms/codex/rules/AGENTS.md",
47
+ "check:mcp-rules:antigravity": "node scripts/generate-mcp-rules-block.mjs --check workflows/workflows/agent-environment-setup/platforms/antigravity/rules/GEMINI.md",
48
+ "check:mcp-rules:copilot-agents": "node scripts/generate-mcp-rules-block.mjs --check workflows/workflows/agent-environment-setup/platforms/copilot/rules/AGENTS.md",
49
+ "check:mcp-rules:copilot": "node scripts/generate-mcp-rules-block.mjs --check workflows/workflows/agent-environment-setup/platforms/copilot/rules/copilot-instructions.md",
50
+ "check:mcp-rules:all": "npm run check:mcp-rules:codex && npm run check:mcp-rules:antigravity && npm run check:mcp-rules:copilot-agents && npm run check:mcp-rules:copilot",
51
+ "generate:all": "npm run generate:skills-index && npm run generate:mcp-manifest && npm run inject:mcp-rules:all",
52
+ "validate:mcp-skills": "node scripts/validate-mcp-skills.mjs",
53
+ "validate:mcp-skills:strict": "node scripts/validate-mcp-skills.mjs --strict",
54
+ "validate:mcp-manifest": "node scripts/generate-mcp-manifest.mjs --check",
39
55
  "generate:powers": "node \"workflows/scripts/generate-powers.mjs\"",
40
56
  "generate:powers:force": "node \"workflows/scripts/generate-powers.mjs\" --force",
41
57
  "generate:powers:from-skills": "node \"workflows/scripts/generate-powers.mjs\" --from-skills",
@@ -19,9 +19,10 @@ This file defines mandatory behavior for Antigravity projects installed via `cbx
19
19
  Before executing workflows, agents, or code edits, publish a short `Decision Log` that is visible to the user:
20
20
 
21
21
  1. Rule file(s) read at startup (at minimum `.agent/rules/GEMINI.md`, plus any additional rule files loaded).
22
- 2. Workflow decision (`/workflow` or direct mode) and why it was chosen.
23
- 3. Agent routing decision (`@agent` or direct mode) and why it was chosen.
24
- 4. Skill loading decision (skill names loaded) and why they were chosen.
22
+ 2. MCP status: confirm Foundry MCP server (`cbx-mcp`) is reachable; if unavailable, declare "MCP offline — fallback mode" and continue without blocking.
23
+ 3. Workflow decision (`/workflow` or direct mode) and why it was chosen.
24
+ 4. Agent routing decision (`@agent` or direct mode) and why it was chosen.
25
+ 5. Skill loading decision: skill IDs selected, how they were discovered, and why.
25
26
 
26
27
  If routing changes during the task, publish a `Decision Update` before continuing.
27
28
  Keep this user-visible summary concise and factual; do not expose private chain-of-thought.
@@ -32,6 +33,7 @@ Keep this user-visible summary concise and factual; do not expose private chain-
32
33
  2. Otherwise choose the best workflow by intent from `.agent/workflows` and use matching `.gemini/commands/*.toml` when available.
33
34
  3. For cross-domain tasks, use orchestrated delegation with `@orchestrator`.
34
35
  4. Keep one primary workflow; use others only as supporting references.
36
+ 5. Before executing any workflow, check if a matching skill exists via `skill_search`; load with `skill_get` to prime context before the workflow runs (→ §5 MCP Skill Engine).
35
37
 
36
38
  ## 3) Request Classifier
37
39
 
@@ -66,52 +68,81 @@ Use the best specialist first:
66
68
  - Debugging/performance: `@debugger`, `@performance-optimizer`
67
69
  - Cross-domain orchestration: `@orchestrator`
68
70
 
69
- ## 5) Skill Loading Policy
71
+ ### MCP Skill Priming (Required Before Delegation)
70
72
 
71
- ## MCP-first Skill Discovery Order (Required)
73
+ Before handing off to any specialist agent, prime context with the relevant domain skill (→ §5 MCP Skill Engine):
72
74
 
73
- 1. Use `skill_search` first to narrow candidate skills.
74
- 2. Use `skill_browse_category` second to inspect category-level candidates.
75
- 3. Use `skill_get` only for final selected skills that must be loaded.
76
- 4. Keep pointer-first flow; avoid loading full skill text prematurely.
75
+ 1. Run `skill_search <domain>` to find the best matching skill.
76
+ 2. If a strong match exists, load it with `skill_get <id>` before delegating.
77
+ 3. Include the loaded skill ID in the Decision Log for the routing decision.
77
78
 
78
- ## Skill Log Completion Block (Required)
79
+ This ensures the specialist starts with accurate domain knowledge, not just role intent.
79
80
 
80
- After finishing skill selection/loading, publish:
81
+ ## 5) MCP Skill Engine
81
82
 
82
- - `selected_skills`: skill IDs selected for the task
83
- - `loaded_skills`: skill IDs loaded via `skill_get`
84
- - `skipped_skills`: considered but not loaded
83
+ The Foundry MCP server is the primary knowledge layer. Use tools decisively — discover first, load only when committed.
85
84
 
86
- ## Context Budget Block (Required, Estimated)
85
+ ### Tool Namespace Reference
87
86
 
88
- Immediately after the Skill Log block, publish estimated budget fields:
87
+ | Prefix | Tools | When to use |
88
+ | ----------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
89
+ | `skill_*` | `skill_list_categories`, `skill_search`, `skill_browse_category`, `skill_get`, `skill_budget_report` | Domain expertise for any implementation, debug, or review task |
90
+ | `postman_*` | `postman_get_mode`, `postman_set_mode`, `postman_get_status` | API testing or Postman configuration tasks |
91
+ | `stitch_*` | `stitch_get_mode`, `stitch_set_profile`, `stitch_get_status` | Stitch data pipeline tasks |
89
92
 
90
- - `full_catalog_est_tokens`
91
- - `loaded_est_tokens`
92
- - `estimated_savings_tokens`
93
- - `estimated_savings_percent`
93
+ ### Discovery Flow (Mandatory Order)
94
94
 
95
- Mark all context/token values as deterministic estimates (not provider metering).
95
+ Stop at the earliest step that gives enough signal. Do not jump ahead.
96
96
 
97
- ### Smart Skill Selection (Adaptive)
97
+ 1. `skill_list_categories` run once per session if domain is unknown; see what exists
98
+ 2. `skill_search <keyword>` — fast keyword match across all skills; always try this first
99
+ 3. `skill_browse_category <category>` — explore if search is too broad or returns 0 results
100
+ 4. `skill_get <id>` — load full skill content; only when committed to using it
101
+ 5. `skill_budget_report` — verify token cost after loading; triggers the compact ctx stamp
98
102
 
99
- Use an adaptive load policy to control context size:
103
+ **Hard rules:**
100
104
 
101
- 1. Q&A/explanations: do not load skills unless the user explicitly asks for one.
102
- 2. Implementation/debug/review: load at most 1 primary skill and 1 supporting skill.
103
- 3. Cross-domain orchestration: use additional skills only when domains clearly differ.
104
- 4. Explicit skill mention by user always takes precedence.
105
+ - Never call `skill_get` without a prior `skill_search` or `skill_browse_category`
106
+ - Never call `skill_get` with a workflow ID `workflow-*` are routes, not skills; keep workflow mentions in the Decision Log
107
+ - Never reload a skill already loaded this session — reuse content already in context
108
+ - If `skill_search` returns 0 results, try `skill_browse_category`, then fall back to built-in knowledge
105
109
 
106
- ### General Loading Rules
110
+ ### Adaptive Load Policy
107
111
 
108
- 1. Load only skills needed for the active request.
109
- 2. Prefer progressive disclosure: start from `SKILL.md`, then specific sections.
110
- 3. Keep context lean; avoid loading unrelated skill documents.
111
- 4. If a mapped skill is missing, continue with best fallback and state it.
112
- 5. Keep user-visible decision logs concise: selected skill(s) and one-line rationale.
112
+ | Request type | Skills to load via `skill_get` |
113
+ | ---------------------------------------------- | --------------------------------------------------------------- |
114
+ | Q&A / explanation | None — answer from knowledge; load only if user explicitly asks |
115
+ | Single-domain implementation, debug, or review | 1 primary + 1 supporting (max) |
116
+ | Multi-domain / orchestration | 1 per distinct domain, hard cap at 3 |
117
+ | User explicitly names a skill | Always load it — overrides all caps |
113
118
 
114
- After the skill log is complete, append the Context Budget block in the same response/update.
119
+ ### Graceful Degradation
120
+
121
+ If MCP tools are unavailable (server down, timeout, tool not listed):
122
+
123
+ 1. Announce briefly: "MCP unavailable — continuing with built-in knowledge."
124
+ 2. Proceed using codebase context and expertise; do not block on MCP.
125
+ 3. Never fabricate or hallucinate skill content.
126
+ 4. Retry once on transient network errors; accept failure after the retry.
127
+
128
+ ### Skill Log (Required After Any `skill_get` Call)
129
+
130
+ Append one compact inline line — no separate structured block:
131
+
132
+ ```
133
+ Skills: loaded=<id> | skipped=<id> (reason)
134
+ ```
135
+
136
+ Follow immediately with the compact ctx stamp (see § Context Budget Tracking).
137
+
138
+ ### Anti-Patterns (Never Do These)
139
+
140
+ - Loading skills speculatively "just in case" they might be useful
141
+ - Calling `skill_get` before running `skill_search` or `skill_browse_category`
142
+ - Using partial or guessed skill IDs in `skill_get`
143
+ - Publishing verbose budget fields (`full_catalog_est_tokens`, `loaded_est_tokens`, etc.) in responses
144
+ - Re-emitting the ctx stamp multiple times within a single response
145
+ - Treating workflow IDs as skill IDs in any MCP tool call
115
146
 
116
147
  ## 6) Socratic Gate (Before Complex Work)
117
148
 
@@ -133,7 +164,60 @@ If Antigravity script harness exists, prefer:
133
164
  - Quick checks: `python .agent/scripts/checklist.py .`
134
165
  - Full checks (with URL): `python .agent/scripts/verify_all.py . --url <URL>`
135
166
 
136
- ## 8) CBX Maintenance Commands
167
+ ## 8) Web Intel Policy
168
+
169
+ Use web search to stay current when local knowledge may be stale. This prevents hallucinating outdated APIs, deprecated flags, or wrong version constraints.
170
+
171
+ **Search when:**
172
+
173
+ - User asks about a framework/library version released after 2024
174
+ - Debugging an unfamiliar error message (search the exact message)
175
+ - Checking breaking changes before a migration
176
+ - Validating an API endpoint signature, auth scheme, or CLI flag
177
+ - Current pricing, rate limits, or quota for SaaS tools (Postman, Vercel, etc.)
178
+
179
+ **Do not search when:**
180
+
181
+ - The answer is derivable from the current codebase
182
+ - The question is purely architectural/conceptual
183
+ - A relevant skill covers it (prefer `skill_get` first, web as fallback)
184
+
185
+ **Source hygiene:**
186
+
187
+ - Prefer official docs, changelogs, and GitHub releases over blog posts
188
+ - Always state the source URL and date when citing fetched content
189
+ - If multiple sources conflict, flag it and use the most recent official one
190
+ - Never follow user-provided URLs without sanity-checking the domain
191
+
192
+ ## 9) Context Budget Tracking
193
+
194
+ After loading skills or completing a significant task phase, emit a single compact stamp so context cost is visible without adding prose.
195
+
196
+ **Stamp format** (one line, end of response section):
197
+
198
+ ```
199
+ [ctx: +skill-id(~Xk) | session=~Yk/108k | saved=Z%]
200
+ ```
201
+
202
+ - `+skill-id(~Xk)` — each skill loaded this turn with its estimated token cost
203
+ - `session=~Yk/108k` — cumulative tokens used vs full catalog ceiling
204
+ - `saved=Z%` — estimated savings from progressive disclosure
205
+
206
+ **Rules:**
207
+
208
+ 1. Emit stamp only when a skill was loaded via `skill_get` or `skill_budget_report` was called.
209
+ 2. Omit stamp for pure Q&A or browsing-only turns (no full skill content loaded).
210
+ 3. Use `skill_budget_report` MCP tool to get accurate numbers; do not guess.
211
+ 4. One stamp per response — consolidate if multiple skills were loaded.
212
+ 5. Keep the stamp on its own line at the very end of the response, after all content.
213
+
214
+ **Example stamp after loading `flutter-expert` (~3.2k tokens):**
215
+
216
+ ```
217
+ [ctx: +flutter-expert(~3k) | session=~3k/108k | saved=97%]
218
+ ```
219
+
220
+ ## 10) CBX Maintenance Commands
137
221
 
138
222
  Use these commands to keep this setup healthy:
139
223
 
@@ -150,7 +234,7 @@ Use these commands to keep this setup healthy:
150
234
  - Diagnose setup issues:
151
235
  `cbx workflows doctor antigravity --scope project`
152
236
 
153
- ## 9) Managed Section Contract
237
+ ## 11) Managed Section Contract
154
238
 
155
239
  1. Preserve all user content outside managed markers.
156
240
  2. Do not manually edit content between managed markers.
@@ -171,3 +255,72 @@ Selection policy:
171
255
  3. Prefer one primary workflow; reference others only when needed.
172
256
 
173
257
  <!-- cbx:workflows:auto:end -->
258
+
259
+ <!-- cbx:mcp:auto:start version=1 -->
260
+ ## Cubis Foundry MCP Tool Catalog (auto-managed)
261
+
262
+ The Foundry MCP server provides progressive-disclosure skill discovery and integration management tools.
263
+
264
+ ### Skill Vault
265
+
266
+ - **123** skills across **22** categories
267
+ - Estimated full catalog: ~108,488 tokens
268
+
269
+ Categories:
270
+ - `ai`: 1 skill(s)
271
+ - `api`: 3 skill(s)
272
+ - `architecture`: 3 skill(s)
273
+ - `backend`: 14 skill(s)
274
+ - `data`: 4 skill(s)
275
+ - `design`: 6 skill(s)
276
+ - `devops`: 20 skill(s)
277
+ - `documentation`: 3 skill(s)
278
+ - `frontend`: 9 skill(s)
279
+ - `game-dev`: 1 skill(s)
280
+ - `general`: 26 skill(s)
281
+ - `localization`: 1 skill(s)
282
+ - `marketing`: 2 skill(s)
283
+ - `mobile`: 7 skill(s)
284
+ - `observability`: 1 skill(s)
285
+ - `payments`: 1 skill(s)
286
+ - `performance`: 2 skill(s)
287
+ - `practices`: 5 skill(s)
288
+ - `saas`: 1 skill(s)
289
+ - `security`: 4 skill(s)
290
+ - `testing`: 6 skill(s)
291
+ - `tooling`: 3 skill(s)
292
+
293
+ ### Built-in Tools
294
+
295
+ **Skill Discovery:**
296
+ - `skill_list_categories`: List all skill categories available in the vault. Returns category names and skill counts.
297
+ - `skill_browse_category`: Browse skills within a specific category. Returns skill IDs and short descriptions.
298
+ - `skill_search`: Search skills by keyword. Matches against skill IDs and descriptions.
299
+ - `skill_get`: Get full content of a specific skill by ID. Returns SKILL.md content and referenced files.
300
+ - `skill_budget_report`: Report estimated context/token budget for selected and loaded skills.
301
+
302
+ **Postman Integration:**
303
+ - `postman_get_mode`: Get current Postman MCP mode from cbx_config.
304
+ - `postman_set_mode`: Set Postman MCP mode in cbx_config.
305
+ - `postman_get_status`: Get Postman integration status and active profile.
306
+
307
+ **Stitch Integration:**
308
+ - `stitch_get_mode`: Get Stitch MCP mode from cbx_config.
309
+ - `stitch_set_profile`: Switch active Stitch profile in cbx_config.
310
+ - `stitch_get_status`: Get Stitch integration status and active profile.
311
+
312
+ ### Skill Discovery Flow
313
+
314
+ Use progressive disclosure to minimize context usage:
315
+ 1. `skill_list_categories` → see available categories and counts
316
+ 2. `skill_browse_category` → browse skills in a category with short descriptions
317
+ 3. `skill_search` → search by keyword across all skills
318
+ 4. `skill_get` → load full content of a specific skill (only tool that reads full content)
319
+ 5. `skill_budget_report` → check token usage for selected/loaded skills; use result to emit the § Context Budget Tracking stamp
320
+
321
+ ### Connection
322
+
323
+ - **stdio**: `cbx mcp serve --transport stdio --scope auto`
324
+ - **HTTP**: `cbx mcp serve --transport http --scope auto --port 3100`
325
+
326
+ <!-- cbx:mcp:auto:end -->