@foundation0/api 1.1.5 → 1.1.6

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/mcp/cli.ts CHANGED
@@ -67,6 +67,8 @@ const parseListArg = (value: string | undefined): string[] => {
67
67
  const serverName = getArgValue('--server-name', 'f0-mcp')
68
68
  const serverVersion = getArgValue('--server-version', '1.0.0')
69
69
  const toolsPrefix = getArgValue('--tools-prefix') ?? process.env.MCP_TOOLS_PREFIX
70
+ const repoRoot =
71
+ getArgValue('--repo-root') ?? process.env.MCP_REPO_ROOT ?? process.env.F0_REPO_ROOT
70
72
  const allowedRootEndpoints = parseListArg(
71
73
  getArgValue('--allowed-root-endpoints') ?? process.env.MCP_ALLOWED_ROOT_ENDPOINTS,
72
74
  )
@@ -80,6 +82,9 @@ if (hasFlag('--help') || hasFlag('-h')) {
80
82
  console.log(' --server-name <name>')
81
83
  console.log(' --server-version <version>')
82
84
  console.log(' --tools-prefix <prefix>')
85
+ console.log(' --repo-root <path>')
86
+ console.log(' Default repo root on the server filesystem (contains /api and /projects).')
87
+ console.log(' Env: MCP_REPO_ROOT or F0_REPO_ROOT.')
83
88
  console.log(' --allowed-root-endpoints <csv>')
84
89
  console.log(' Example: --allowed-root-endpoints projects')
85
90
  console.log(' Example: --allowed-root-endpoints agents,projects')
@@ -96,6 +101,7 @@ void runExampleMcpServer({
96
101
  serverName: serverName ?? undefined,
97
102
  serverVersion: serverVersion ?? undefined,
98
103
  toolsPrefix,
104
+ repoRoot: repoRoot ?? undefined,
99
105
  allowedRootEndpoints,
100
106
  disableWrite,
101
107
  enableIssues,
package/mcp/manual.md CHANGED
@@ -11,6 +11,8 @@ Assume the server is already configured in your MCP host and you can call its to
11
11
  4. When a tool needs `repoRoot`, it must be the **repo root** containing both `api/` and `projects/` (not a single project folder). (Legacy alias: `processRoot` is still accepted.) If omitted, the server will try to auto-detect a repo root from its current working directory (best effort).
12
12
  5. Tool results are returned as a **JSON text envelope**: parse the text as JSON and check `ok`.
13
13
 
14
+ Note: `repoRoot` is a path on the **server's filesystem** (not the client/LLM). If you're unsure what the server can see, call `mcp.workspace`.
15
+
14
16
  ## 1) Tool naming (prefixes + aliases)
15
17
 
16
18
  Depending on how the server was started, tool names may be:
@@ -24,7 +26,7 @@ Some tools also have OpenAI-safe underscore aliases (no dots). Example:
24
26
 
25
27
  - `net.curl` may also be available as `net_curl`
26
28
 
27
- ## 2) The 3 discovery calls
29
+ ## 2) The 4 discovery calls
28
30
 
29
31
  ### A) List all tools
30
32
 
@@ -60,6 +62,14 @@ Tool call:
60
62
  }
61
63
  ```
62
64
 
65
+ ### D) `mcp.workspace` to debug repoRoot/cwd issues
66
+
67
+ Tool call:
68
+
69
+ ```json
70
+ { "name": "mcp.workspace", "arguments": {} }
71
+ ```
72
+
63
73
  ## 3) Payload shapes (important)
64
74
 
65
75
  Most tools accept either:
@@ -138,6 +148,7 @@ If you get:
138
148
  - **Unknown tool**: use the `suggestions` from the error (when present), or call `mcp.listTools` again and retry.
139
149
  - **Missing project name**: pass `projectName` (or set `args[0]`).
140
150
  - **Project folder not found: ...projects/.../projects/...**: you likely passed the wrong `repoRoot` (it must be the repo root; legacy alias `processRoot`).
151
+ - **`projects.listProjects()` returns `[]` unexpectedly**: call `mcp.workspace` to confirm the server’s `cwd`/`repoRoot` and whether `/projects` exists.
141
152
 
142
153
  ## 6) Tool availability (read/write/admin)
143
154
 
@@ -42,6 +42,7 @@ describe("createExampleMcpServer endpoint whitelist", () => {
42
42
  expect(names).toContain("mcp.usage");
43
43
  expect(names).toContain("mcp.listTools");
44
44
  expect(names).toContain("mcp.describeTool");
45
+ expect(names).toContain("mcp.workspace");
45
46
  expect(names).not.toContain("mcp.search");
46
47
  });
47
48
 
@@ -98,6 +99,7 @@ describe("createExampleMcpServer endpoint whitelist", () => {
98
99
  expect(names).toContain("mcp.usage");
99
100
  expect(names).toContain("mcp.listTools");
100
101
  expect(names).toContain("mcp.describeTool");
102
+ expect(names).toContain("mcp.workspace");
101
103
  expect(names).not.toContain("mcp.search");
102
104
  });
103
105
 
@@ -357,6 +359,78 @@ describe("createExampleMcpServer request handling", () => {
357
359
  }
358
360
  });
359
361
 
362
+ it("uses server default repoRoot when repoRoot is omitted", async () => {
363
+ const tempDir = await fs.mkdtemp(
364
+ path.join(os.tmpdir(), "f0-mcp-server-default-reporoot-"),
365
+ );
366
+ try {
367
+ await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
368
+ await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
369
+ recursive: true,
370
+ });
371
+ await fs.mkdir(path.join(tempDir, "projects", "beta", "docs"), {
372
+ recursive: true,
373
+ });
374
+
375
+ const instance = createExampleMcpServer({ repoRoot: tempDir });
376
+ const handler = getToolHandler(instance);
377
+
378
+ const result = await handler(
379
+ {
380
+ method: "tools/call",
381
+ params: {
382
+ name: "projects.listProjects",
383
+ arguments: {},
384
+ },
385
+ },
386
+ {},
387
+ );
388
+
389
+ expect(result.isError).toBe(false);
390
+ const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
391
+ expect(payload.ok).toBe(true);
392
+ expect(payload.result).toContain("adl");
393
+ expect(payload.result).toContain("beta");
394
+ } finally {
395
+ await fs.rm(tempDir, { recursive: true, force: true });
396
+ }
397
+ });
398
+
399
+ it("exposes mcp.workspace to explain server filesystem context", async () => {
400
+ const tempDir = await fs.mkdtemp(
401
+ path.join(os.tmpdir(), "f0-mcp-server-workspace-"),
402
+ );
403
+ try {
404
+ await fs.mkdir(path.join(tempDir, "api"), { recursive: true });
405
+ await fs.mkdir(path.join(tempDir, "projects", "adl", "docs"), {
406
+ recursive: true,
407
+ });
408
+
409
+ const instance = createExampleMcpServer({ repoRoot: tempDir });
410
+ const handler = getToolHandler(instance);
411
+
412
+ const result = await handler(
413
+ {
414
+ method: "tools/call",
415
+ params: {
416
+ name: "mcp.workspace",
417
+ arguments: {},
418
+ },
419
+ },
420
+ {},
421
+ );
422
+
423
+ expect(result.isError).toBe(false);
424
+ const payload = JSON.parse(result.content?.[0]?.text ?? "{}");
425
+ expect(payload.ok).toBe(true);
426
+ expect(payload.result.repoRoot).toBe(path.resolve(tempDir));
427
+ expect(payload.result.hasProjectsDir).toBe(true);
428
+ expect(Array.isArray(payload.result.projects)).toBe(true);
429
+ } finally {
430
+ await fs.rm(tempDir, { recursive: true, force: true });
431
+ }
432
+ });
433
+
360
434
  it('parses continueOnError from string "false" (fails fast)', async () => {
361
435
  const instance = createExampleMcpServer();
362
436
  const handler = getToolHandler(instance);
package/mcp/server.ts CHANGED
@@ -48,7 +48,7 @@ type ApiEndpoint = {
48
48
  mcp: ToolNamespace;
49
49
  };
50
50
 
51
- const MCP_HELPER_TOOL_KEYS = ["usage", "listTools", "describeTool"] as const;
51
+ const MCP_HELPER_TOOL_KEYS = ["usage", "listTools", "describeTool", "workspace"] as const;
52
52
  type McpHelperToolKey = (typeof MCP_HELPER_TOOL_KEYS)[number];
53
53
  const MCP_HELPER_TOOL_NAMES = new Set<string>(
54
54
  MCP_HELPER_TOOL_KEYS.map((key) => `mcp.${key}`),
@@ -176,17 +176,6 @@ const isDir = (candidate: string): boolean => {
176
176
  const looksLikeRepoRoot = (candidate: string): boolean =>
177
177
  isDir(path.join(candidate, "projects")) && isDir(path.join(candidate, "api"));
178
178
 
179
- const detectRepoRootFromCwd = (): string | null => {
180
- let current = process.cwd();
181
- for (let depth = 0; depth < 12; depth += 1) {
182
- if (looksLikeRepoRoot(current)) return current;
183
- const parent = path.dirname(current);
184
- if (parent === current) break;
185
- current = parent;
186
- }
187
- return null;
188
- };
189
-
190
179
  const normalizeRepoRoot = (raw: string): string => {
191
180
  const resolved = path.resolve(raw);
192
181
  if (looksLikeRepoRoot(resolved)) return resolved;
@@ -220,8 +209,10 @@ const normalizeRepoRootOption = (
220
209
  const raw = rawRepoRoot ?? rawProcessRoot;
221
210
 
222
211
  if (typeof raw !== "string" || raw.trim().length === 0) {
223
- const detected = detectRepoRootFromCwd();
224
- return detected ? { ...options, repoRoot: detected } : options;
212
+ const next = { ...options };
213
+ delete next.repoRoot;
214
+ delete next.processRoot;
215
+ return next;
225
216
  }
226
217
 
227
218
  const trimmed = raw.trim();
@@ -890,7 +881,7 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
890
881
  repoRoot: {
891
882
  type: "string",
892
883
  description:
893
- "Repo root containing /projects and /agents. If omitted, uses server cwd.",
884
+ "Repo root on the server filesystem (contains /projects and /agents). If omitted, uses the server default (auto-detected from cwd).",
894
885
  },
895
886
  args: {
896
887
  type: "array",
@@ -1362,6 +1353,21 @@ const TOOL_INPUT_SCHEMA_OVERRIDES: Record<string, unknown> = {
1362
1353
  },
1363
1354
  required: ["projectName", "title"],
1364
1355
  },
1356
+ "mcp.workspace": {
1357
+ type: "object",
1358
+ additionalProperties: true,
1359
+ properties: {
1360
+ repoRoot: {
1361
+ type: "string",
1362
+ description:
1363
+ "Optional repo root on the server filesystem. If omitted, uses the server default (auto-detected by walking up from cwd). Legacy alias: processRoot.",
1364
+ },
1365
+ },
1366
+ required: [],
1367
+ $comment: safeJsonStringify({
1368
+ example: {},
1369
+ }),
1370
+ },
1365
1371
  "mcp.search": {
1366
1372
  type: "object",
1367
1373
  additionalProperties: true,
@@ -1627,7 +1633,7 @@ const defaultToolInputSchema = (toolName: string) => ({
1627
1633
  repoRoot: {
1628
1634
  type: "string",
1629
1635
  description:
1630
- 'Repo root containing /projects and /agents. If you have a project root like ".../projects/adl", omit this or pass its parent.',
1636
+ 'Repo root on the server filesystem (contains /projects and /agents). If omitted, uses the server default (auto-detected from cwd). If you have a project root like ".../projects/adl", omit this or pass its parent.',
1631
1637
  },
1632
1638
  },
1633
1639
  $comment: safeJsonStringify({
@@ -1743,11 +1749,13 @@ const buildToolList = (
1743
1749
  type ToolInvoker = (
1744
1750
  args: unknown[],
1745
1751
  options: Record<string, unknown>,
1752
+ defaultRepoRoot: string,
1746
1753
  ) => unknown[];
1747
1754
 
1748
1755
  const buildOptionsOnly = (
1749
1756
  args: unknown[],
1750
1757
  options: Record<string, unknown>,
1758
+ _defaultRepoRoot: string,
1751
1759
  ): unknown[] => {
1752
1760
  const invocationArgs: unknown[] = [...args];
1753
1761
  if (Object.keys(options).length > 0) {
@@ -1759,6 +1767,7 @@ const buildOptionsOnly = (
1759
1767
  const buildOptionsThenRepoRoot = (
1760
1768
  args: unknown[],
1761
1769
  options: Record<string, unknown>,
1770
+ defaultRepoRoot: string,
1762
1771
  ): unknown[] => {
1763
1772
  const invocationArgs: unknown[] = [...args];
1764
1773
  const remaining = { ...options };
@@ -1766,16 +1775,16 @@ const buildOptionsThenRepoRoot = (
1766
1775
  if (typeof repoRoot === "string") {
1767
1776
  delete remaining.repoRoot;
1768
1777
  }
1778
+ const resolvedRepoRoot =
1779
+ typeof repoRoot === "string" ? repoRoot : defaultRepoRoot;
1769
1780
 
1770
1781
  if (Object.keys(remaining).length > 0) {
1771
1782
  invocationArgs.push(remaining);
1772
- } else if (typeof repoRoot === "string") {
1783
+ } else if (resolvedRepoRoot) {
1773
1784
  // Preserve positional slot for signatures like fn(projectName, options?, repoRoot?).
1774
1785
  invocationArgs.push({});
1775
1786
  }
1776
- if (typeof repoRoot === "string") {
1777
- invocationArgs.push(repoRoot);
1778
- }
1787
+ invocationArgs.push(resolvedRepoRoot);
1779
1788
 
1780
1789
  return invocationArgs;
1781
1790
  };
@@ -1783,6 +1792,7 @@ const buildOptionsThenRepoRoot = (
1783
1792
  const buildRepoRootThenOptions = (
1784
1793
  args: unknown[],
1785
1794
  options: Record<string, unknown>,
1795
+ defaultRepoRoot: string,
1786
1796
  ): unknown[] => {
1787
1797
  const invocationArgs: unknown[] = [...args];
1788
1798
  const remaining = { ...options };
@@ -1791,9 +1801,7 @@ const buildRepoRootThenOptions = (
1791
1801
  delete remaining.repoRoot;
1792
1802
  }
1793
1803
 
1794
- if (typeof repoRoot === "string") {
1795
- invocationArgs.push(repoRoot);
1796
- }
1804
+ invocationArgs.push(typeof repoRoot === "string" ? repoRoot : defaultRepoRoot);
1797
1805
  if (Object.keys(remaining).length > 0) {
1798
1806
  invocationArgs.push(remaining);
1799
1807
  }
@@ -1804,12 +1812,11 @@ const buildRepoRootThenOptions = (
1804
1812
  const buildRepoRootOnly = (
1805
1813
  args: unknown[],
1806
1814
  options: Record<string, unknown>,
1815
+ defaultRepoRoot: string,
1807
1816
  ): unknown[] => {
1808
1817
  const invocationArgs: unknown[] = [...args];
1809
1818
  const repoRoot = options.repoRoot;
1810
- if (typeof repoRoot === "string") {
1811
- invocationArgs.push(repoRoot);
1812
- }
1819
+ invocationArgs.push(typeof repoRoot === "string" ? repoRoot : defaultRepoRoot);
1813
1820
  return invocationArgs;
1814
1821
  };
1815
1822
 
@@ -1828,7 +1835,7 @@ const toolInvocationPlans: Record<string, ToolInvoker> = {
1828
1835
  "projects.resolveProjectTargetFile": buildOptionsOnly,
1829
1836
  "agents.loadAgent": buildRepoRootOnly,
1830
1837
  "agents.loadAgentPrompt": buildRepoRootOnly,
1831
- "projects.resolveImplementationPlan": (args, options) => {
1838
+ "projects.resolveImplementationPlan": (args, options, _defaultRepoRoot) => {
1832
1839
  const invocationArgs: unknown[] = [...args];
1833
1840
  const remaining = { ...options };
1834
1841
  const repoRoot = remaining.repoRoot;
@@ -1861,19 +1868,20 @@ const toolInvocationPlans: Record<string, ToolInvoker> = {
1861
1868
  const invokeTool = async (
1862
1869
  tool: ToolDefinition,
1863
1870
  payload: unknown,
1871
+ defaultRepoRoot: string,
1864
1872
  ): Promise<unknown> => {
1865
1873
  const normalized = normalizePayload(payload);
1866
1874
  const { args, options } = coercePayloadForTool(tool.name, normalized);
1867
1875
  const invoke =
1868
1876
  toolInvocationPlans[tool.name] ??
1869
- ((rawArgs, rawOptions) => {
1877
+ ((rawArgs, rawOptions, _repoRoot) => {
1870
1878
  const invocationArgs = [...rawArgs];
1871
1879
  if (Object.keys(rawOptions).length > 0) {
1872
1880
  invocationArgs.push(rawOptions);
1873
1881
  }
1874
1882
  return invocationArgs;
1875
1883
  });
1876
- const invocationArgs = invoke(args, options);
1884
+ const invocationArgs = invoke(args, options, defaultRepoRoot);
1877
1885
 
1878
1886
  return Promise.resolve(tool.method(...invocationArgs));
1879
1887
  };
@@ -1882,6 +1890,11 @@ export interface ExampleMcpServerOptions {
1882
1890
  serverName?: string;
1883
1891
  serverVersion?: string;
1884
1892
  toolsPrefix?: string;
1893
+ /**
1894
+ * Optional default repo root on the server filesystem.
1895
+ * If omitted, the server attempts to auto-detect by walking up from cwd.
1896
+ */
1897
+ repoRoot?: string;
1885
1898
  allowedRootEndpoints?: string[];
1886
1899
  disableWrite?: boolean;
1887
1900
  enableIssues?: boolean;
@@ -1922,6 +1935,7 @@ const READ_ONLY_TOOL_NAMES = new Set<string>([
1922
1935
  "mcp.usage",
1923
1936
  "mcp.listTools",
1924
1937
  "mcp.describeTool",
1938
+ "mcp.workspace",
1925
1939
  "mcp.search",
1926
1940
  ]);
1927
1941
 
@@ -2012,6 +2026,12 @@ export const createExampleMcpServer = (
2012
2026
  options: ExampleMcpServerOptions = {},
2013
2027
  ): ExampleMcpServerInstance => {
2014
2028
  let toolCatalog: unknown[] = [];
2029
+ const defaultRepoRoot = normalizeRepoRoot(
2030
+ options.repoRoot ??
2031
+ process.env.MCP_REPO_ROOT ??
2032
+ process.env.F0_REPO_ROOT ??
2033
+ process.cwd(),
2034
+ );
2015
2035
 
2016
2036
  const parseString = (value: unknown): string | null => {
2017
2037
  if (typeof value !== "string") return null;
@@ -2051,11 +2071,63 @@ export const createExampleMcpServer = (
2051
2071
  "F0 MCP helper tools:",
2052
2072
  "- mcp.listTools: returns tool catalog with access + invocation hints",
2053
2073
  "- mcp.describeTool: describe one tool by name (prefixed or unprefixed)",
2074
+ "- mcp.workspace: explain server filesystem context (cwd, repoRoot, projects)",
2054
2075
  "- mcp.search: LLM-friendly search over project docs/spec (local-first)",
2055
2076
  "",
2056
2077
  'Tip: Prefer mcp.search for "search spec/docs" requests.',
2057
2078
  ].join("\n"),
2058
2079
  listTools: () => ({ tools: toolCatalog }),
2080
+ workspace: (input?: unknown) => {
2081
+ const payload = isRecord(input) ? input : {};
2082
+ const received = isRecord(input)
2083
+ ? {
2084
+ keys: Object.keys(input),
2085
+ repoRoot: (input as any).repoRoot ?? null,
2086
+ processRoot: (input as any).processRoot ?? null,
2087
+ }
2088
+ : { keys: [], repoRoot: null, processRoot: null };
2089
+ const raw =
2090
+ parseString(payload.repoRoot) ?? parseString(payload.processRoot);
2091
+ const repoRoot = raw ? normalizeRepoRoot(raw) : defaultRepoRoot;
2092
+
2093
+ const projectsDir = path.join(repoRoot, "projects");
2094
+ const apiDir = path.join(repoRoot, "api");
2095
+ const agentsDir = path.join(repoRoot, "agents");
2096
+ const hasProjectsDir = isDir(projectsDir);
2097
+ const hasApiDir = isDir(apiDir);
2098
+ const hasAgentsDir = isDir(agentsDir);
2099
+
2100
+ const projects = hasProjectsDir ? projectsApi.listProjects(repoRoot) : [];
2101
+ const hint = (() => {
2102
+ if (!hasProjectsDir) {
2103
+ return [
2104
+ "Repo does not contain /projects on the server filesystem.",
2105
+ "Start the MCP server from the monorepo root (the folder that contains both /api and /projects), or pass repoRoot explicitly.",
2106
+ "repoRoot is a server-side path, not a client/LLM path.",
2107
+ ].join(" ");
2108
+ }
2109
+ if (hasProjectsDir && projects.length === 0) {
2110
+ return [
2111
+ "Found /projects, but no projects with docs/ were detected.",
2112
+ "Each project folder must contain a docs/ directory to be listed.",
2113
+ ].join(" ");
2114
+ }
2115
+ return null;
2116
+ })();
2117
+
2118
+ return {
2119
+ received,
2120
+ cwd: process.cwd(),
2121
+ configuredRepoRoot: options.repoRoot ?? null,
2122
+ defaultRepoRoot,
2123
+ repoRoot,
2124
+ hasProjectsDir,
2125
+ hasApiDir,
2126
+ hasAgentsDir,
2127
+ projects,
2128
+ hint,
2129
+ };
2130
+ },
2059
2131
  describeTool: (toolName: string) => {
2060
2132
  const normalized = typeof toolName === "string" ? toolName.trim() : "";
2061
2133
  if (!normalized) {
@@ -2099,7 +2171,7 @@ export const createExampleMcpServer = (
2099
2171
  const repoRoot = (() => {
2100
2172
  const raw =
2101
2173
  parseString(payload.repoRoot) ?? parseString(payload.processRoot);
2102
- return raw ? normalizeRepoRoot(raw) : process.cwd();
2174
+ return raw ? normalizeRepoRoot(raw) : defaultRepoRoot;
2103
2175
  })();
2104
2176
 
2105
2177
  const sectionRaw = parseString(payload.section)?.toLowerCase();
@@ -2475,6 +2547,21 @@ export const createExampleMcpServer = (
2475
2547
  ) {
2476
2548
  details.hint =
2477
2549
  "You likely passed a project root as repoRoot. repoRoot should be the repo root containing /projects. (Legacy alias: processRoot.)";
2550
+ details.suggestion = "Call mcp.workspace to see cwd/repoRoot on the server.";
2551
+ }
2552
+
2553
+ if (
2554
+ /Project folder not found:/i.test(message) &&
2555
+ !details.hint &&
2556
+ /projects[\\/]/i.test(message)
2557
+ ) {
2558
+ details.hint =
2559
+ "repoRoot might be wrong, or the server filesystem does not contain /projects for this repoRoot.";
2560
+ details.suggestion = "Call mcp.workspace to see cwd/repoRoot on the server.";
2561
+ details.example = {
2562
+ tool: prefix ? `${prefix}.mcp.workspace` : "mcp.workspace",
2563
+ arguments: {},
2564
+ };
2478
2565
  }
2479
2566
 
2480
2567
  if (/Missing search pattern\./i.test(message)) {
@@ -2538,7 +2625,11 @@ export const createExampleMcpServer = (
2538
2625
  }
2539
2626
 
2540
2627
  try {
2541
- const data = await invokeTool(toolDefinition, { args, options });
2628
+ const data = await invokeTool(
2629
+ toolDefinition,
2630
+ { args, options },
2631
+ defaultRepoRoot,
2632
+ );
2542
2633
  return { index, tool, isError: false, data };
2543
2634
  } catch (error) {
2544
2635
  const message =
@@ -2590,7 +2681,7 @@ export const createExampleMcpServer = (
2590
2681
  }
2591
2682
 
2592
2683
  try {
2593
- const data = await invokeTool(tool, request.params.arguments);
2684
+ const data = await invokeTool(tool, request.params.arguments, defaultRepoRoot);
2594
2685
  return toolOk(data);
2595
2686
  } catch (error) {
2596
2687
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foundation0/api",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Foundation 0 API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,6 +35,7 @@
35
35
  "scripts": {
36
36
  "mcp": "bun run mcp/cli.ts",
37
37
  "test": "bun test",
38
+ "test:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov",
38
39
  "deploy": "pnpm publish --access public",
39
40
  "version:patch": "pnpm version patch && git commit -am \"Bump version to %s\"",
40
41
  "version:minor": "pnpm version minor && git commit -am \"Bump version to %s\"",