@corbat-tech/coco 2.25.6 → 2.25.8

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/dist/cli/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs5 from 'fs';
3
3
  import fs5__default, { accessSync, readFileSync, constants } from 'fs';
4
- import * as path38 from 'path';
5
- import path38__default, { join, dirname, resolve, basename } from 'path';
4
+ import * as path39 from 'path';
5
+ import path39__default, { join, dirname, resolve, basename } from 'path';
6
6
  import { URL as URL$1, fileURLToPath } from 'url';
7
7
  import { z } from 'zod';
8
8
  import * as os4 from 'os';
@@ -612,7 +612,7 @@ function deepMergeConfig(base, override) {
612
612
  };
613
613
  }
614
614
  function getProjectConfigPath() {
615
- return path38__default.join(process.cwd(), ".coco", "config.json");
615
+ return path39__default.join(process.cwd(), ".coco", "config.json");
616
616
  }
617
617
  async function saveConfig(config, configPath, global = false) {
618
618
  const result = CocoConfigSchema.safeParse(config);
@@ -627,7 +627,7 @@ async function saveConfig(config, configPath, global = false) {
627
627
  });
628
628
  }
629
629
  const resolvedPath = configPath || (global ? CONFIG_PATHS.config : getProjectConfigPath());
630
- const dir = path38__default.dirname(resolvedPath);
630
+ const dir = path39__default.dirname(resolvedPath);
631
631
  await fs35__default.mkdir(dir, { recursive: true });
632
632
  const content = JSON.stringify(result.data, null, 2);
633
633
  await fs35__default.writeFile(resolvedPath, content, "utf-8");
@@ -645,7 +645,7 @@ async function findConfigPath(cwd) {
645
645
  }
646
646
  }
647
647
  const basePath = cwd || process.cwd();
648
- const projectConfigPath = path38__default.join(basePath, ".coco", "config.json");
648
+ const projectConfigPath = path39__default.join(basePath, ".coco", "config.json");
649
649
  try {
650
650
  await fs35__default.access(projectConfigPath);
651
651
  return projectConfigPath;
@@ -666,7 +666,7 @@ async function findAllConfigPaths(cwd) {
666
666
  } catch {
667
667
  }
668
668
  const basePath = cwd || process.cwd();
669
- const projectConfigPath = path38__default.join(basePath, ".coco", "config.json");
669
+ const projectConfigPath = path39__default.join(basePath, ".coco", "config.json");
670
670
  try {
671
671
  await fs35__default.access(projectConfigPath);
672
672
  result.project = projectConfigPath;
@@ -879,11 +879,11 @@ async function refreshAccessToken(provider, refreshToken) {
879
879
  }
880
880
  function getTokenStoragePath(provider) {
881
881
  const home = process.env.HOME || process.env.USERPROFILE || "";
882
- return path38.join(home, ".coco", "tokens", `${provider}.json`);
882
+ return path39.join(home, ".coco", "tokens", `${provider}.json`);
883
883
  }
884
884
  async function saveTokens(provider, tokens) {
885
885
  const filePath = getTokenStoragePath(provider);
886
- const dir = path38.dirname(filePath);
886
+ const dir = path39.dirname(filePath);
887
887
  await fs35.mkdir(dir, { recursive: true, mode: 448 });
888
888
  await fs35.writeFile(filePath, JSON.stringify(tokens, null, 2), { mode: 384 });
889
889
  }
@@ -1479,11 +1479,11 @@ function getCopilotBaseUrl(accountType) {
1479
1479
  }
1480
1480
  function getCopilotCredentialsPath() {
1481
1481
  const home = process.env.HOME || process.env.USERPROFILE || "";
1482
- return path38.join(home, ".coco", "tokens", "copilot.json");
1482
+ return path39.join(home, ".coco", "tokens", "copilot.json");
1483
1483
  }
1484
1484
  async function saveCopilotCredentials(creds) {
1485
1485
  const filePath = getCopilotCredentialsPath();
1486
- const dir = path38.dirname(filePath);
1486
+ const dir = path39.dirname(filePath);
1487
1487
  await fs35.mkdir(dir, { recursive: true, mode: 448 });
1488
1488
  await fs35.writeFile(filePath, JSON.stringify(creds, null, 2), { mode: 384 });
1489
1489
  }
@@ -2239,7 +2239,7 @@ function getADCPath() {
2239
2239
  if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
2240
2240
  return process.env.GOOGLE_APPLICATION_CREDENTIALS;
2241
2241
  }
2242
- return path38.join(home, ".config", "gcloud", "application_default_credentials.json");
2242
+ return path39.join(home, ".config", "gcloud", "application_default_credentials.json");
2243
2243
  }
2244
2244
  async function isGcloudInstalled() {
2245
2245
  try {
@@ -2395,7 +2395,7 @@ function loadGlobalCocoEnv() {
2395
2395
  try {
2396
2396
  const home = process.env.HOME || process.env.USERPROFILE || "";
2397
2397
  if (!home) return;
2398
- const globalEnvPath = path38.join(home, ".coco", ".env");
2398
+ const globalEnvPath = path39.join(home, ".coco", ".env");
2399
2399
  const content = fs5.readFileSync(globalEnvPath, "utf-8");
2400
2400
  for (const line of content.split("\n")) {
2401
2401
  const trimmed = line.trim();
@@ -2633,7 +2633,7 @@ async function updateEnvProvider(provider) {
2633
2633
  try {
2634
2634
  const home = process.env.HOME || process.env.USERPROFILE || "";
2635
2635
  if (!home) return;
2636
- const envPath = path38.join(home, ".coco", ".env");
2636
+ const envPath = path39.join(home, ".coco", ".env");
2637
2637
  let content;
2638
2638
  try {
2639
2639
  content = await fs5.promises.readFile(envPath, "utf-8");
@@ -2660,7 +2660,7 @@ async function removeEnvProvider() {
2660
2660
  try {
2661
2661
  const home = process.env.HOME || process.env.USERPROFILE || "";
2662
2662
  if (!home) return;
2663
- const envPath = path38.join(home, ".coco", ".env");
2663
+ const envPath = path39.join(home, ".coco", ".env");
2664
2664
  let content;
2665
2665
  try {
2666
2666
  content = await fs5.promises.readFile(envPath, "utf-8");
@@ -2679,7 +2679,7 @@ async function migrateOldPreferences() {
2679
2679
  try {
2680
2680
  const home = process.env.HOME || process.env.USERPROFILE || "";
2681
2681
  if (!home) return;
2682
- const oldPrefsPath = path38.join(home, ".coco", "preferences.json");
2682
+ const oldPrefsPath = path39.join(home, ".coco", "preferences.json");
2683
2683
  let oldPrefs = null;
2684
2684
  try {
2685
2685
  const content = await fs5.promises.readFile(oldPrefsPath, "utf-8");
@@ -2871,7 +2871,7 @@ function setupFileLogging(logger2, logDir, name) {
2871
2871
  if (!fs5__default.existsSync(logDir)) {
2872
2872
  fs5__default.mkdirSync(logDir, { recursive: true });
2873
2873
  }
2874
- const logFile = path38__default.join(logDir, `${name}.log`);
2874
+ const logFile = path39__default.join(logDir, `${name}.log`);
2875
2875
  logger2.attachTransport((logObj) => {
2876
2876
  const line = JSON.stringify(logObj) + "\n";
2877
2877
  fs5__default.appendFileSync(logFile, line);
@@ -2890,7 +2890,7 @@ function setLogger(logger2) {
2890
2890
  globalLogger = logger2;
2891
2891
  }
2892
2892
  function initializeLogging(projectPath, level = "info") {
2893
- const logDir = path38__default.join(projectPath, ".coco", "logs");
2893
+ const logDir = path39__default.join(projectPath, ".coco", "logs");
2894
2894
  const logger2 = createLogger({
2895
2895
  name: "coco",
2896
2896
  level,
@@ -7186,6 +7186,251 @@ var init_config = __esm({
7186
7186
  }
7187
7187
  });
7188
7188
 
7189
+ // src/mcp/config-loader.ts
7190
+ var config_loader_exports = {};
7191
+ __export(config_loader_exports, {
7192
+ loadMCPConfigFile: () => loadMCPConfigFile,
7193
+ loadMCPServersFromCOCOConfig: () => loadMCPServersFromCOCOConfig,
7194
+ loadProjectMCPFile: () => loadProjectMCPFile,
7195
+ mergeMCPConfigs: () => mergeMCPConfigs
7196
+ });
7197
+ function expandEnvVar(value) {
7198
+ return value.replace(/\$\{([^}]+)\}/g, (match, name) => process.env[name] ?? match);
7199
+ }
7200
+ function expandEnvObject(env2) {
7201
+ const result = {};
7202
+ for (const [k, v] of Object.entries(env2)) {
7203
+ result[k] = expandEnvVar(v);
7204
+ }
7205
+ return result;
7206
+ }
7207
+ function expandHeaders(headers) {
7208
+ const result = {};
7209
+ for (const [k, v] of Object.entries(headers)) {
7210
+ result[k] = expandEnvVar(v);
7211
+ }
7212
+ return result;
7213
+ }
7214
+ function convertStandardEntry(name, entry) {
7215
+ if (entry.command) {
7216
+ return {
7217
+ name,
7218
+ transport: "stdio",
7219
+ enabled: entry.enabled ?? true,
7220
+ stdio: {
7221
+ command: entry.command,
7222
+ args: entry.args,
7223
+ env: entry.env ? expandEnvObject(entry.env) : void 0
7224
+ }
7225
+ };
7226
+ }
7227
+ if (entry.url) {
7228
+ const headers = entry.headers ? expandHeaders(entry.headers) : void 0;
7229
+ const authHeader = headers?.["Authorization"] ?? headers?.["authorization"];
7230
+ let auth;
7231
+ if (authHeader) {
7232
+ if (authHeader.startsWith("Bearer ")) {
7233
+ auth = { type: "bearer", token: authHeader.slice(7) };
7234
+ } else {
7235
+ auth = { type: "apikey", token: authHeader };
7236
+ }
7237
+ }
7238
+ return {
7239
+ name,
7240
+ transport: "http",
7241
+ enabled: entry.enabled ?? true,
7242
+ http: {
7243
+ url: entry.url,
7244
+ ...headers && Object.keys(headers).length > 0 ? { headers } : {},
7245
+ ...auth ? { auth } : {}
7246
+ }
7247
+ };
7248
+ }
7249
+ throw new Error(`Server "${name}" must have either "command" (stdio) or "url" (http) defined`);
7250
+ }
7251
+ async function loadMCPConfigFile(configPath) {
7252
+ try {
7253
+ await access(configPath);
7254
+ } catch {
7255
+ throw new MCPError(-32003 /* CONNECTION_ERROR */, `Config file not found: ${configPath}`);
7256
+ }
7257
+ let content;
7258
+ try {
7259
+ content = await readFile(configPath, "utf-8");
7260
+ } catch (error) {
7261
+ throw new MCPError(
7262
+ -32003 /* CONNECTION_ERROR */,
7263
+ `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`
7264
+ );
7265
+ }
7266
+ let parsed;
7267
+ try {
7268
+ parsed = JSON.parse(content);
7269
+ } catch {
7270
+ throw new MCPError(-32700 /* PARSE_ERROR */, "Invalid JSON in config file");
7271
+ }
7272
+ const obj = parsed;
7273
+ if (obj.mcpServers && typeof obj.mcpServers === "object" && !Array.isArray(obj.mcpServers)) {
7274
+ return loadStandardFormat(obj, configPath);
7275
+ }
7276
+ if (obj.servers && Array.isArray(obj.servers)) {
7277
+ return loadCocoFormat(obj, configPath);
7278
+ }
7279
+ throw new MCPError(
7280
+ -32602 /* INVALID_PARAMS */,
7281
+ 'Config file must have either a "mcpServers" object (standard) or a "servers" array (Coco format)'
7282
+ );
7283
+ }
7284
+ function loadStandardFormat(config, configPath) {
7285
+ const validServers = [];
7286
+ const errors = [];
7287
+ for (const [name, entry] of Object.entries(config.mcpServers)) {
7288
+ if (name.startsWith("_")) continue;
7289
+ try {
7290
+ const converted = convertStandardEntry(name, entry);
7291
+ validateServerConfig(converted);
7292
+ validServers.push(converted);
7293
+ } catch (error) {
7294
+ const message = error instanceof Error ? error.message : "Unknown error";
7295
+ errors.push(`Server '${name}': ${message}`);
7296
+ }
7297
+ }
7298
+ if (errors.length > 0) {
7299
+ getLogger().warn(`[MCP] Some servers in ${configPath} failed to load: ${errors.join("; ")}`);
7300
+ }
7301
+ return validServers;
7302
+ }
7303
+ async function loadProjectMCPFile(projectPath) {
7304
+ const mcpJsonPath = path39__default.join(projectPath, ".mcp.json");
7305
+ try {
7306
+ await access(mcpJsonPath);
7307
+ } catch {
7308
+ return [];
7309
+ }
7310
+ try {
7311
+ return await loadMCPConfigFile(mcpJsonPath);
7312
+ } catch (error) {
7313
+ getLogger().warn(
7314
+ `[MCP] Failed to load .mcp.json: ${error instanceof Error ? error.message : String(error)}`
7315
+ );
7316
+ return [];
7317
+ }
7318
+ }
7319
+ function loadCocoFormat(config, configPath) {
7320
+ const validServers = [];
7321
+ const errors = [];
7322
+ for (const server of config.servers) {
7323
+ try {
7324
+ const converted = convertCocoServerEntry(server);
7325
+ validateServerConfig(converted);
7326
+ validServers.push(converted);
7327
+ } catch (error) {
7328
+ const message = error instanceof Error ? error.message : "Unknown error";
7329
+ errors.push(`Server '${server.name || "unknown"}': ${message}`);
7330
+ }
7331
+ }
7332
+ if (errors.length > 0) {
7333
+ getLogger().warn(`[MCP] Some servers in ${configPath} failed to load: ${errors.join("; ")}`);
7334
+ }
7335
+ return validServers;
7336
+ }
7337
+ function convertCocoServerEntry(server) {
7338
+ const base = {
7339
+ name: server.name,
7340
+ description: server.description,
7341
+ transport: server.transport,
7342
+ enabled: server.enabled ?? true,
7343
+ metadata: server.metadata
7344
+ };
7345
+ if (server.transport === "stdio" && server.stdio) {
7346
+ return {
7347
+ ...base,
7348
+ stdio: {
7349
+ command: server.stdio.command,
7350
+ args: server.stdio.args,
7351
+ env: server.stdio.env ? expandEnvObject(server.stdio.env) : void 0,
7352
+ cwd: server.stdio.cwd
7353
+ }
7354
+ };
7355
+ }
7356
+ if (server.transport === "http" && server.http) {
7357
+ return {
7358
+ ...base,
7359
+ http: {
7360
+ url: server.http.url,
7361
+ ...server.http.headers ? { headers: expandHeaders(server.http.headers) } : {},
7362
+ ...server.http.auth ? { auth: server.http.auth } : {},
7363
+ ...server.http.timeout !== void 0 ? { timeout: server.http.timeout } : {}
7364
+ }
7365
+ };
7366
+ }
7367
+ throw new Error(`Missing configuration for transport: ${server.transport}`);
7368
+ }
7369
+ function mergeMCPConfigs(base, ...overrides) {
7370
+ const merged = /* @__PURE__ */ new Map();
7371
+ for (const server of base) {
7372
+ merged.set(server.name, server);
7373
+ }
7374
+ for (const override of overrides) {
7375
+ for (const server of override) {
7376
+ const existing = merged.get(server.name);
7377
+ if (existing) {
7378
+ merged.set(server.name, { ...existing, ...server });
7379
+ } else {
7380
+ merged.set(server.name, server);
7381
+ }
7382
+ }
7383
+ }
7384
+ return Array.from(merged.values());
7385
+ }
7386
+ async function loadMCPServersFromCOCOConfig(configPath) {
7387
+ const { loadConfig: loadConfig3 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
7388
+ const { MCPServerConfigEntrySchema: MCPServerConfigEntrySchema2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
7389
+ const config = await loadConfig3(configPath);
7390
+ if (!config.mcp?.servers || config.mcp.servers.length === 0) {
7391
+ return [];
7392
+ }
7393
+ const servers = [];
7394
+ for (const entry of config.mcp.servers) {
7395
+ try {
7396
+ const parsed = MCPServerConfigEntrySchema2.parse(entry);
7397
+ const serverConfig = {
7398
+ name: parsed.name,
7399
+ description: parsed.description,
7400
+ transport: parsed.transport,
7401
+ enabled: parsed.enabled,
7402
+ ...parsed.transport === "stdio" && parsed.command && {
7403
+ stdio: {
7404
+ command: parsed.command,
7405
+ args: parsed.args,
7406
+ env: parsed.env ? expandEnvObject(parsed.env) : void 0
7407
+ }
7408
+ },
7409
+ ...parsed.transport === "http" && parsed.url && {
7410
+ http: {
7411
+ url: parsed.url,
7412
+ auth: parsed.auth
7413
+ }
7414
+ }
7415
+ };
7416
+ validateServerConfig(serverConfig);
7417
+ servers.push(serverConfig);
7418
+ } catch (error) {
7419
+ const message = error instanceof Error ? error.message : "Unknown error";
7420
+ getLogger().warn(`[MCP] Failed to load server '${entry.name}': ${message}`);
7421
+ }
7422
+ }
7423
+ return servers;
7424
+ }
7425
+ var init_config_loader = __esm({
7426
+ "src/mcp/config-loader.ts"() {
7427
+ init_config();
7428
+ init_types();
7429
+ init_errors2();
7430
+ init_logger();
7431
+ }
7432
+ });
7433
+
7189
7434
  // src/mcp/registry.ts
7190
7435
  var registry_exports = {};
7191
7436
  __export(registry_exports, {
@@ -7286,7 +7531,14 @@ var init_registry = __esm({
7286
7531
  try {
7287
7532
  await access(this.registryPath);
7288
7533
  const content = await readFile(this.registryPath, "utf-8");
7289
- const servers = parseRegistry(content);
7534
+ let servers = parseRegistry(content);
7535
+ if (servers.length === 0) {
7536
+ try {
7537
+ const { loadMCPConfigFile: loadMCPConfigFile2 } = await Promise.resolve().then(() => (init_config_loader(), config_loader_exports));
7538
+ servers = await loadMCPConfigFile2(this.registryPath);
7539
+ } catch {
7540
+ }
7541
+ }
7290
7542
  this.servers.clear();
7291
7543
  for (const server of servers) {
7292
7544
  try {
@@ -7378,7 +7630,7 @@ __export(markdown_loader_exports, {
7378
7630
  });
7379
7631
  async function isMarkdownSkill(skillDir) {
7380
7632
  try {
7381
- await fs35__default.access(path38__default.join(skillDir, SKILL_FILENAME));
7633
+ await fs35__default.access(path39__default.join(skillDir, SKILL_FILENAME));
7382
7634
  return true;
7383
7635
  } catch {
7384
7636
  return false;
@@ -7386,7 +7638,7 @@ async function isMarkdownSkill(skillDir) {
7386
7638
  }
7387
7639
  async function loadMarkdownMetadata(skillDir, scope) {
7388
7640
  try {
7389
- const skillPath = path38__default.join(skillDir, SKILL_FILENAME);
7641
+ const skillPath = path39__default.join(skillDir, SKILL_FILENAME);
7390
7642
  const raw = await fs35__default.readFile(skillPath, "utf-8");
7391
7643
  const { data } = matter(raw);
7392
7644
  const parsed = SkillFrontmatterSchema.safeParse(data);
@@ -7394,8 +7646,8 @@ async function loadMarkdownMetadata(skillDir, scope) {
7394
7646
  return null;
7395
7647
  }
7396
7648
  const fm = parsed.data;
7397
- const dirName = path38__default.basename(skillDir);
7398
- const parentDir = path38__default.basename(path38__default.dirname(skillDir));
7649
+ const dirName = path39__default.basename(skillDir);
7650
+ const parentDir = path39__default.basename(path39__default.dirname(skillDir));
7399
7651
  const namespace = isNamespaceDirectory(parentDir) ? parentDir : void 0;
7400
7652
  const baseId = toKebabCase(fm.name || dirName);
7401
7653
  const fullId = namespace ? `${namespace}/${baseId}` : baseId;
@@ -7435,7 +7687,7 @@ async function loadMarkdownMetadata(skillDir, scope) {
7435
7687
  }
7436
7688
  async function loadMarkdownContent(skillDir) {
7437
7689
  try {
7438
- const skillPath = path38__default.join(skillDir, SKILL_FILENAME);
7690
+ const skillPath = path39__default.join(skillDir, SKILL_FILENAME);
7439
7691
  const raw = await fs35__default.readFile(skillPath, "utf-8");
7440
7692
  const { content } = matter(raw);
7441
7693
  const references = await listSubdirectory(skillDir, "references");
@@ -7458,9 +7710,9 @@ async function loadMarkdownContent(skillDir) {
7458
7710
  }
7459
7711
  async function listSubdirectory(skillDir, subdir) {
7460
7712
  try {
7461
- const dir = path38__default.join(skillDir, subdir);
7713
+ const dir = path39__default.join(skillDir, subdir);
7462
7714
  const entries = await fs35__default.readdir(dir, { withFileTypes: true });
7463
- return entries.filter((e) => e.isFile()).map((e) => path38__default.join(dir, e.name));
7715
+ return entries.filter((e) => e.isFile()).map((e) => path39__default.join(dir, e.name));
7464
7716
  } catch {
7465
7717
  return [];
7466
7718
  }
@@ -7537,8 +7789,8 @@ async function loadSkillFromDirectory(skillDir, scope) {
7537
7789
  if (await isMarkdownSkill(skillDir)) {
7538
7790
  return loadMarkdownMetadata(skillDir, scope);
7539
7791
  }
7540
- const hasTs = await fileExists2(path38__default.join(skillDir, "index.ts"));
7541
- const hasJs = await fileExists2(path38__default.join(skillDir, "index.js"));
7792
+ const hasTs = await fileExists2(path39__default.join(skillDir, "index.ts"));
7793
+ const hasJs = await fileExists2(path39__default.join(skillDir, "index.js"));
7542
7794
  if (hasTs || hasJs) {
7543
7795
  return null;
7544
7796
  }
@@ -7581,8 +7833,8 @@ function normalizeDirectories(dirs, relativeBaseDir) {
7581
7833
  for (const dir of dirs) {
7582
7834
  const trimmed = dir.trim();
7583
7835
  if (!trimmed) continue;
7584
- const expanded = trimmed === "~" ? home : trimmed.startsWith("~/") ? path38__default.join(home, trimmed.slice(2)) : trimmed;
7585
- const resolved = path38__default.isAbsolute(expanded) ? path38__default.resolve(expanded) : path38__default.resolve(relativeBaseDir ?? process.cwd(), expanded);
7836
+ const expanded = trimmed === "~" ? home : trimmed.startsWith("~/") ? path39__default.join(home, trimmed.slice(2)) : trimmed;
7837
+ const resolved = path39__default.isAbsolute(expanded) ? path39__default.resolve(expanded) : path39__default.resolve(relativeBaseDir ?? process.cwd(), expanded);
7586
7838
  if (seen.has(resolved)) continue;
7587
7839
  seen.add(resolved);
7588
7840
  normalized.push(resolved);
@@ -7595,7 +7847,7 @@ function resolveDiscoveryDirs(projectPath, options) {
7595
7847
  opts.globalDirs && opts.globalDirs.length > 0 ? opts.globalDirs : opts.globalDir ? [opts.globalDir] : GLOBAL_SKILLS_DIRS
7596
7848
  );
7597
7849
  const projectDirs = normalizeDirectories(
7598
- opts.projectDirs && opts.projectDirs.length > 0 ? opts.projectDirs : opts.projectDir ? [opts.projectDir] : PROJECT_SKILLS_DIRNAMES.map((d) => path38__default.join(projectPath, d)),
7850
+ opts.projectDirs && opts.projectDirs.length > 0 ? opts.projectDirs : opts.projectDir ? [opts.projectDir] : PROJECT_SKILLS_DIRNAMES.map((d) => path39__default.join(projectPath, d)),
7599
7851
  projectPath
7600
7852
  );
7601
7853
  return { globalDirs, projectDirs };
@@ -7627,7 +7879,7 @@ async function scanSkillsDirectory(dir, scope) {
7627
7879
  const skillDirs = entries.filter((e) => e.isDirectory() && !e.isSymbolicLink());
7628
7880
  const results = [];
7629
7881
  for (const entry of skillDirs) {
7630
- const entryPath = path38__default.join(dir, entry.name);
7882
+ const entryPath = path39__default.join(dir, entry.name);
7631
7883
  try {
7632
7884
  const stat2 = await fs35__default.lstat(entryPath);
7633
7885
  if (stat2.isSymbolicLink()) continue;
@@ -7661,7 +7913,7 @@ async function scanNestedSkills(dir, scope, depth) {
7661
7913
  const subDirs = subEntries.filter((e) => e.isDirectory() && !e.isSymbolicLink());
7662
7914
  const results = await Promise.all(
7663
7915
  subDirs.map(async (sub) => {
7664
- const subPath = path38__default.join(dir, sub.name);
7916
+ const subPath = path39__default.join(dir, sub.name);
7665
7917
  try {
7666
7918
  const stat2 = await fs35__default.lstat(subPath);
7667
7919
  if (stat2.isSymbolicLink()) return null;
@@ -7691,17 +7943,17 @@ var init_discovery = __esm({
7691
7943
  init_paths();
7692
7944
  init_logger();
7693
7945
  GLOBAL_SKILLS_DIRS = [
7694
- path38__default.join(homedir(), ".codex", "skills"),
7946
+ path39__default.join(homedir(), ".codex", "skills"),
7695
7947
  // Codex CLI legacy compat
7696
- path38__default.join(homedir(), ".gemini", "skills"),
7948
+ path39__default.join(homedir(), ".gemini", "skills"),
7697
7949
  // Gemini CLI compat
7698
- path38__default.join(homedir(), ".opencode", "skills"),
7950
+ path39__default.join(homedir(), ".opencode", "skills"),
7699
7951
  // OpenCode compat
7700
- path38__default.join(homedir(), ".claude", "skills"),
7952
+ path39__default.join(homedir(), ".claude", "skills"),
7701
7953
  // Claude Code compat
7702
- path38__default.join(homedir(), ".agents", "skills"),
7954
+ path39__default.join(homedir(), ".agents", "skills"),
7703
7955
  // shared cross-agent standard
7704
- path38__default.join(COCO_HOME, "skills")
7956
+ path39__default.join(COCO_HOME, "skills")
7705
7957
  // Coco native global directory (authoritative for Coco)
7706
7958
  ];
7707
7959
  PROJECT_SKILLS_DIRNAMES = [
@@ -9089,7 +9341,7 @@ var init_loader3 = __esm({
9089
9341
  const rawContent = await fs35.readFile(resolvedPath, "utf-8");
9090
9342
  const { content, imports } = await this.resolveImports(
9091
9343
  rawContent,
9092
- path38.dirname(resolvedPath),
9344
+ path39.dirname(resolvedPath),
9093
9345
  0
9094
9346
  );
9095
9347
  const sections = this.parseSections(content);
@@ -9116,16 +9368,16 @@ var init_loader3 = __esm({
9116
9368
  if (this.config.includeUserLevel) {
9117
9369
  const userDir = this.resolvePath(USER_CONFIG_DIR);
9118
9370
  for (const pattern of this.config.filePatterns) {
9119
- const userPath = path38.join(userDir, pattern);
9371
+ const userPath = path39.join(userDir, pattern);
9120
9372
  if (await this.fileExists(userPath)) {
9121
9373
  result.user = userPath;
9122
9374
  break;
9123
9375
  }
9124
9376
  }
9125
9377
  }
9126
- const absoluteProjectPath = path38.resolve(projectPath);
9378
+ const absoluteProjectPath = path39.resolve(projectPath);
9127
9379
  for (const pattern of this.config.filePatterns) {
9128
- const projectFilePath = path38.join(absoluteProjectPath, pattern);
9380
+ const projectFilePath = path39.join(absoluteProjectPath, pattern);
9129
9381
  if (await this.fileExists(projectFilePath)) {
9130
9382
  result.project = projectFilePath;
9131
9383
  break;
@@ -9133,14 +9385,14 @@ var init_loader3 = __esm({
9133
9385
  }
9134
9386
  const cwd = currentDir ?? process.cwd();
9135
9387
  if (cwd.startsWith(absoluteProjectPath) && cwd !== absoluteProjectPath) {
9136
- const relativePath = path38.relative(absoluteProjectPath, cwd);
9137
- const parts = relativePath.split(path38.sep);
9388
+ const relativePath = path39.relative(absoluteProjectPath, cwd);
9389
+ const parts = relativePath.split(path39.sep);
9138
9390
  const dirFiles = [];
9139
9391
  let currentDir2 = absoluteProjectPath;
9140
9392
  for (const part of parts) {
9141
- currentDir2 = path38.join(currentDir2, part);
9393
+ currentDir2 = path39.join(currentDir2, part);
9142
9394
  for (const pattern of this.config.filePatterns) {
9143
- const dirFilePath = path38.join(currentDir2, pattern);
9395
+ const dirFilePath = path39.join(currentDir2, pattern);
9144
9396
  if (await this.fileExists(dirFilePath)) {
9145
9397
  dirFiles.push(dirFilePath);
9146
9398
  break;
@@ -9154,7 +9406,7 @@ var init_loader3 = __esm({
9154
9406
  for (const pattern of this.config.filePatterns) {
9155
9407
  const baseName = pattern.replace(/\.md$/, "");
9156
9408
  const localFileName = `${baseName}${LOCAL_SUFFIX}`;
9157
- const localPath = path38.join(absoluteProjectPath, localFileName);
9409
+ const localPath = path39.join(absoluteProjectPath, localFileName);
9158
9410
  if (await this.fileExists(localPath)) {
9159
9411
  result.local = localPath;
9160
9412
  break;
@@ -9208,7 +9460,7 @@ var init_loader3 = __esm({
9208
9460
  const importedContent = await fs35.readFile(resolvedPath, "utf-8");
9209
9461
  const nestedResult = await this.resolveImports(
9210
9462
  importedContent,
9211
- path38.dirname(resolvedPath),
9463
+ path39.dirname(resolvedPath),
9212
9464
  depth + 1
9213
9465
  );
9214
9466
  processedLines.push(`<!-- Imported from: ${importPath} -->`);
@@ -9249,7 +9501,7 @@ var init_loader3 = __esm({
9249
9501
  const parts = [];
9250
9502
  for (const file of files) {
9251
9503
  if (file.exists && file.content.trim()) {
9252
- const label = file.level === "directory" ? `directory level (${path38.dirname(file.path)}/${path38.basename(file.path)})` : `${file.level} level (${path38.basename(file.path)})`;
9504
+ const label = file.level === "directory" ? `directory level (${path39.dirname(file.path)}/${path39.basename(file.path)})` : `${file.level} level (${path39.basename(file.path)})`;
9253
9505
  parts.push(`<!-- Memory: ${label} -->`);
9254
9506
  parts.push(file.content);
9255
9507
  parts.push("");
@@ -9312,9 +9564,9 @@ var init_loader3 = __esm({
9312
9564
  */
9313
9565
  resolvePath(filePath) {
9314
9566
  if (filePath.startsWith("~")) {
9315
- return path38.join(os4.homedir(), filePath.slice(1));
9567
+ return path39.join(os4.homedir(), filePath.slice(1));
9316
9568
  }
9317
- return path38.resolve(filePath);
9569
+ return path39.resolve(filePath);
9318
9570
  }
9319
9571
  /**
9320
9572
  * Resolve an import path relative to a base directory.
@@ -9329,22 +9581,22 @@ var init_loader3 = __esm({
9329
9581
  resolveImportPath(importPath, basePath) {
9330
9582
  let resolved;
9331
9583
  if (importPath.startsWith("~")) {
9332
- resolved = path38.join(os4.homedir(), importPath.slice(1));
9584
+ resolved = path39.join(os4.homedir(), importPath.slice(1));
9333
9585
  const userConfigDir = this.resolvePath(USER_CONFIG_DIR);
9334
- if (!resolved.startsWith(userConfigDir + path38.sep) && resolved !== userConfigDir) {
9586
+ if (!resolved.startsWith(userConfigDir + path39.sep) && resolved !== userConfigDir) {
9335
9587
  throw new Error(`Import path escapes user config directory: @${importPath}`);
9336
9588
  }
9337
9589
  return resolved;
9338
9590
  }
9339
- if (path38.isAbsolute(importPath)) {
9340
- resolved = path38.resolve(importPath);
9341
- if (!resolved.startsWith(basePath + path38.sep) && resolved !== basePath) {
9591
+ if (path39.isAbsolute(importPath)) {
9592
+ resolved = path39.resolve(importPath);
9593
+ if (!resolved.startsWith(basePath + path39.sep) && resolved !== basePath) {
9342
9594
  throw new Error(`Import path escapes project directory: @${importPath}`);
9343
9595
  }
9344
9596
  return resolved;
9345
9597
  }
9346
- resolved = path38.resolve(basePath, importPath);
9347
- if (!resolved.startsWith(basePath + path38.sep) && resolved !== basePath) {
9598
+ resolved = path39.resolve(basePath, importPath);
9599
+ if (!resolved.startsWith(basePath + path39.sep) && resolved !== basePath) {
9348
9600
  throw new Error(`Import path escapes project directory: @${importPath}`);
9349
9601
  }
9350
9602
  return resolved;
@@ -9511,7 +9763,7 @@ function generateToolCatalog(registry) {
9511
9763
  const tools = registry.getAll();
9512
9764
  const byCategory = /* @__PURE__ */ new Map();
9513
9765
  for (const tool of tools) {
9514
- const cat = tool.category;
9766
+ const cat = tool.name.startsWith("mcp_") ? "mcp" : tool.category;
9515
9767
  if (!byCategory.has(cat)) byCategory.set(cat, []);
9516
9768
  byCategory.get(cat).push({ name: tool.name, description: tool.description });
9517
9769
  }
@@ -9932,9 +10184,10 @@ var init_session = __esm({
9932
10184
  init_manager();
9933
10185
  init_compactor();
9934
10186
  MAX_SKILL_INSTRUCTIONS_CHARS = 16e3;
9935
- TRUST_SETTINGS_DIR = path38__default.dirname(CONFIG_PATHS.trustedTools);
10187
+ TRUST_SETTINGS_DIR = path39__default.dirname(CONFIG_PATHS.trustedTools);
9936
10188
  TRUST_SETTINGS_FILE = CONFIG_PATHS.trustedTools;
9937
10189
  CATEGORY_LABELS = {
10190
+ mcp: "MCP Connected Services",
9938
10191
  file: "File Operations",
9939
10192
  bash: "Shell Commands",
9940
10193
  git: "Git & Version Control",
@@ -9964,6 +10217,8 @@ Rules:
9964
10217
  - NEVER show code blocks instead of writing files. NEVER describe actions instead of performing them.
9965
10218
  - NEVER ask "should I?" or "do you want me to?" \u2014 the user already told you. JUST DO IT.
9966
10219
  - If you need real-time data, CALL web_search. NEVER say "I don't have access to real-time data."
10220
+ - If an MCP tool exists for a service (tool names like \`mcp_<service>_...\`), prefer that MCP tool over generic \`web_fetch\` or \`http_fetch\`.
10221
+ - Use \`mcp_list_servers\` to inspect configured or connected MCP services. Do NOT use \`bash_exec\` to run \`coco mcp ...\` unless the user explicitly asked for that CLI command.
9967
10222
  - Before answering "I can't do that", check your full tool catalog below \u2014 you likely have a tool for it.
9968
10223
  - NEVER claim you cannot run a command because you lack credentials, access, or connectivity. bash_exec runs in the user's own shell environment and inherits their full PATH, kubeconfig, gcloud auth, AWS profiles, SSH keys, and every other tool installed on their machine. kubectl, gcloud, aws, docker, and any other CLI available to the user are available to you. ALWAYS attempt the command with bash_exec; report failure only if it actually returns a non-zero exit code.
9969
10224
 
@@ -10072,6 +10327,7 @@ Suggest 1-2 brief, actionable next steps:
10072
10327
  ## File Access
10073
10328
  File operations are restricted to the project directory by default.
10074
10329
  Use **authorize_path** to access paths outside the project \u2014 it prompts the user interactively.
10330
+ Exception: Coco's own config area under \`~/.coco/\` is first-party product state. You may read safe config files there, especially \`~/.coco/mcp.json\` and \`~/.coco/config.json\`, without using \`authorize_path\`.
10075
10331
 
10076
10332
  ## Tone and Brevity
10077
10333
 
@@ -10215,7 +10471,7 @@ var init_types4 = __esm({
10215
10471
  }
10216
10472
  });
10217
10473
  function getStatePath(projectPath) {
10218
- return path38.join(projectPath, ".coco", "state.json");
10474
+ return path39.join(projectPath, ".coco", "state.json");
10219
10475
  }
10220
10476
  function createStateManager() {
10221
10477
  async function load(projectPath) {
@@ -10240,7 +10496,7 @@ function createStateManager() {
10240
10496
  }
10241
10497
  async function save(state) {
10242
10498
  const statePath = getStatePath(state.path);
10243
- await fs35.mkdir(path38.dirname(statePath), { recursive: true });
10499
+ await fs35.mkdir(path39.dirname(statePath), { recursive: true });
10244
10500
  const file = {
10245
10501
  version: STATE_VERSION,
10246
10502
  state: {
@@ -12884,7 +13140,7 @@ var init_build_verifier = __esm({
12884
13140
  async verifyTypes() {
12885
13141
  const startTime = Date.now();
12886
13142
  try {
12887
- const hasTsConfig = await this.fileExists(path38.join(this.projectPath, "tsconfig.json"));
13143
+ const hasTsConfig = await this.fileExists(path39.join(this.projectPath, "tsconfig.json"));
12888
13144
  if (!hasTsConfig) {
12889
13145
  return {
12890
13146
  success: true,
@@ -12934,18 +13190,18 @@ var init_build_verifier = __esm({
12934
13190
  * Checks Maven, Gradle, and Node.js in that order.
12935
13191
  */
12936
13192
  async detectBuildCommand() {
12937
- if (await this.fileExists(path38.join(this.projectPath, "pom.xml"))) {
12938
- const wrapper = path38.join(this.projectPath, "mvnw");
13193
+ if (await this.fileExists(path39.join(this.projectPath, "pom.xml"))) {
13194
+ const wrapper = path39.join(this.projectPath, "mvnw");
12939
13195
  return await this.fileExists(wrapper) ? "./mvnw compile -B -q" : "mvn compile -B -q";
12940
13196
  }
12941
13197
  for (const f of ["build.gradle", "build.gradle.kts"]) {
12942
- if (await this.fileExists(path38.join(this.projectPath, f))) {
12943
- const wrapper = path38.join(this.projectPath, "gradlew");
13198
+ if (await this.fileExists(path39.join(this.projectPath, f))) {
13199
+ const wrapper = path39.join(this.projectPath, "gradlew");
12944
13200
  return await this.fileExists(wrapper) ? "./gradlew classes -q" : "gradle classes -q";
12945
13201
  }
12946
13202
  }
12947
13203
  try {
12948
- const packageJsonPath = path38.join(this.projectPath, "package.json");
13204
+ const packageJsonPath = path39.join(this.projectPath, "package.json");
12949
13205
  const content = await fs35.readFile(packageJsonPath, "utf-8");
12950
13206
  const packageJson = JSON.parse(content);
12951
13207
  if (packageJson.scripts?.build) {
@@ -14680,9 +14936,9 @@ function detectProjectLanguage(files) {
14680
14936
  return { language: dominant, confidence, evidence };
14681
14937
  }
14682
14938
  function getFileExtension(filePath) {
14683
- const base = path38.basename(filePath);
14939
+ const base = path39.basename(filePath);
14684
14940
  if (base.endsWith(".d.ts")) return ".d.ts";
14685
- return path38.extname(filePath).toLowerCase();
14941
+ return path39.extname(filePath).toLowerCase();
14686
14942
  }
14687
14943
  function buildEvidence(dominant, counts, totalSourceFiles, files) {
14688
14944
  const evidence = [];
@@ -14690,7 +14946,7 @@ function buildEvidence(dominant, counts, totalSourceFiles, files) {
14690
14946
  evidence.push(`${dominantCount} of ${totalSourceFiles} source files are ${dominant}`);
14691
14947
  const configFiles = ["tsconfig.json", "pom.xml", "build.gradle", "Cargo.toml", "go.mod"];
14692
14948
  for (const cfg of configFiles) {
14693
- if (files.some((f) => path38.basename(f) === cfg)) {
14949
+ if (files.some((f) => path39.basename(f) === cfg)) {
14694
14950
  evidence.push(`Found ${cfg}`);
14695
14951
  }
14696
14952
  }
@@ -16185,19 +16441,19 @@ var init_evaluator = __esm({
16185
16441
  });
16186
16442
  async function detectLinter2(cwd) {
16187
16443
  try {
16188
- await fs35__default.access(path38__default.join(cwd, "pom.xml"));
16444
+ await fs35__default.access(path39__default.join(cwd, "pom.xml"));
16189
16445
  return "maven-checkstyle";
16190
16446
  } catch {
16191
16447
  }
16192
16448
  for (const f of ["build.gradle", "build.gradle.kts"]) {
16193
16449
  try {
16194
- await fs35__default.access(path38__default.join(cwd, f));
16450
+ await fs35__default.access(path39__default.join(cwd, f));
16195
16451
  return "gradle-checkstyle";
16196
16452
  } catch {
16197
16453
  }
16198
16454
  }
16199
16455
  try {
16200
- const pkgPath = path38__default.join(cwd, "package.json");
16456
+ const pkgPath = path39__default.join(cwd, "package.json");
16201
16457
  const pkgContent = await fs35__default.readFile(pkgPath, "utf-8");
16202
16458
  const pkg = JSON.parse(pkgContent);
16203
16459
  const deps = {
@@ -16214,7 +16470,7 @@ async function detectLinter2(cwd) {
16214
16470
  }
16215
16471
  async function mavenExec(cwd) {
16216
16472
  try {
16217
- await fs35__default.access(path38__default.join(cwd, "mvnw"));
16473
+ await fs35__default.access(path39__default.join(cwd, "mvnw"));
16218
16474
  return "./mvnw";
16219
16475
  } catch {
16220
16476
  return "mvn";
@@ -16222,7 +16478,7 @@ async function mavenExec(cwd) {
16222
16478
  }
16223
16479
  async function gradleExec(cwd) {
16224
16480
  try {
16225
- await fs35__default.access(path38__default.join(cwd, "gradlew"));
16481
+ await fs35__default.access(path39__default.join(cwd, "gradlew"));
16226
16482
  return "./gradlew";
16227
16483
  } catch {
16228
16484
  return "gradle";
@@ -16291,14 +16547,14 @@ async function findSourceFiles(cwd) {
16291
16547
  const { glob: glob17 } = await import('glob');
16292
16548
  let isJava = false;
16293
16549
  try {
16294
- await fs35__default.access(path38__default.join(cwd, "pom.xml"));
16550
+ await fs35__default.access(path39__default.join(cwd, "pom.xml"));
16295
16551
  isJava = true;
16296
16552
  } catch {
16297
16553
  }
16298
16554
  if (!isJava) {
16299
16555
  for (const f of ["build.gradle", "build.gradle.kts"]) {
16300
16556
  try {
16301
- await fs35__default.access(path38__default.join(cwd, f));
16557
+ await fs35__default.access(path39__default.join(cwd, f));
16302
16558
  isJava = true;
16303
16559
  break;
16304
16560
  } catch {
@@ -16653,7 +16909,7 @@ async function checkTestCoverage(diff, cwd) {
16653
16909
  );
16654
16910
  if (!hasTestChange) {
16655
16911
  const ext = src.path.match(/\.(ts|tsx|js|jsx)$/)?.[0] ?? ".ts";
16656
- const testExists = await fileExists3(path38__default.join(cwd, `${baseName}.test${ext}`)) || await fileExists3(path38__default.join(cwd, `${baseName}.spec${ext}`));
16912
+ const testExists = await fileExists3(path39__default.join(cwd, `${baseName}.test${ext}`)) || await fileExists3(path39__default.join(cwd, `${baseName}.spec${ext}`));
16657
16913
  if (testExists) {
16658
16914
  if (src.additions >= TEST_COVERAGE_LARGE_CHANGE_THRESHOLD) {
16659
16915
  findings.push({
@@ -18334,7 +18590,7 @@ var init_github = __esm({
18334
18590
  });
18335
18591
  async function detectVersionFile(cwd) {
18336
18592
  for (const { file, stack, field } of VERSION_FILES) {
18337
- const fullPath = path38__default.join(cwd, file);
18593
+ const fullPath = path39__default.join(cwd, file);
18338
18594
  if (await fileExists3(fullPath)) {
18339
18595
  const version = await readVersionFromFile(fullPath, stack, field);
18340
18596
  if (version) {
@@ -18380,7 +18636,7 @@ function bumpVersion(current, bump) {
18380
18636
  }
18381
18637
  }
18382
18638
  async function writeVersion(cwd, versionFile, newVersion) {
18383
- const fullPath = path38__default.join(cwd, versionFile.path);
18639
+ const fullPath = path39__default.join(cwd, versionFile.path);
18384
18640
  const content = await readFile(fullPath, "utf-8");
18385
18641
  let updated;
18386
18642
  switch (versionFile.stack) {
@@ -18439,7 +18695,7 @@ var init_version_detector = __esm({
18439
18695
  });
18440
18696
  async function detectChangelog(cwd) {
18441
18697
  for (const name of CHANGELOG_NAMES) {
18442
- const fullPath = path38__default.join(cwd, name);
18698
+ const fullPath = path39__default.join(cwd, name);
18443
18699
  if (await fileExists3(fullPath)) {
18444
18700
  const content = await readFile(fullPath, "utf-8");
18445
18701
  const format = detectFormat(content);
@@ -18461,7 +18717,7 @@ function detectFormat(content) {
18461
18717
  return "custom";
18462
18718
  }
18463
18719
  async function insertChangelogEntry(cwd, changelog, version, entries, date) {
18464
- const fullPath = path38__default.join(cwd, changelog.path);
18720
+ const fullPath = path39__default.join(cwd, changelog.path);
18465
18721
  const content = await readFile(fullPath, "utf-8");
18466
18722
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
18467
18723
  const entry = buildEntry(changelog.format, version, entries, dateStr);
@@ -18522,11 +18778,11 @@ var init_changelog = __esm({
18522
18778
  }
18523
18779
  });
18524
18780
  async function detectStack(cwd) {
18525
- if (await fileExists3(path38__default.join(cwd, "package.json"))) return "node";
18526
- if (await fileExists3(path38__default.join(cwd, "Cargo.toml"))) return "rust";
18527
- if (await fileExists3(path38__default.join(cwd, "pyproject.toml"))) return "python";
18528
- if (await fileExists3(path38__default.join(cwd, "go.mod"))) return "go";
18529
- if (await fileExists3(path38__default.join(cwd, "pom.xml"))) return "java";
18781
+ if (await fileExists3(path39__default.join(cwd, "package.json"))) return "node";
18782
+ if (await fileExists3(path39__default.join(cwd, "Cargo.toml"))) return "rust";
18783
+ if (await fileExists3(path39__default.join(cwd, "pyproject.toml"))) return "python";
18784
+ if (await fileExists3(path39__default.join(cwd, "go.mod"))) return "go";
18785
+ if (await fileExists3(path39__default.join(cwd, "pom.xml"))) return "java";
18530
18786
  return "unknown";
18531
18787
  }
18532
18788
  async function detectPackageManager(cwd, stack) {
@@ -18534,15 +18790,15 @@ async function detectPackageManager(cwd, stack) {
18534
18790
  if (stack === "python") return "pip";
18535
18791
  if (stack === "go") return "go";
18536
18792
  if (stack === "node") {
18537
- if (await fileExists3(path38__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18538
- if (await fileExists3(path38__default.join(cwd, "yarn.lock"))) return "yarn";
18539
- if (await fileExists3(path38__default.join(cwd, "bun.lockb"))) return "bun";
18793
+ if (await fileExists3(path39__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18794
+ if (await fileExists3(path39__default.join(cwd, "yarn.lock"))) return "yarn";
18795
+ if (await fileExists3(path39__default.join(cwd, "bun.lockb"))) return "bun";
18540
18796
  return "npm";
18541
18797
  }
18542
18798
  return null;
18543
18799
  }
18544
18800
  async function detectCI(cwd) {
18545
- const ghDir = path38__default.join(cwd, ".github", "workflows");
18801
+ const ghDir = path39__default.join(cwd, ".github", "workflows");
18546
18802
  if (await fileExists3(ghDir)) {
18547
18803
  let workflowFiles = [];
18548
18804
  let hasCodeQL = false;
@@ -18566,7 +18822,7 @@ async function detectCI(cwd) {
18566
18822
  }
18567
18823
  return { type: "github-actions", workflowFiles, hasCodeQL, hasLinting };
18568
18824
  }
18569
- if (await fileExists3(path38__default.join(cwd, ".gitlab-ci.yml"))) {
18825
+ if (await fileExists3(path39__default.join(cwd, ".gitlab-ci.yml"))) {
18570
18826
  return {
18571
18827
  type: "gitlab-ci",
18572
18828
  workflowFiles: [".gitlab-ci.yml"],
@@ -18574,7 +18830,7 @@ async function detectCI(cwd) {
18574
18830
  hasLinting: false
18575
18831
  };
18576
18832
  }
18577
- if (await fileExists3(path38__default.join(cwd, ".circleci"))) {
18833
+ if (await fileExists3(path39__default.join(cwd, ".circleci"))) {
18578
18834
  return { type: "circle-ci", workflowFiles: [], hasCodeQL: false, hasLinting: false };
18579
18835
  }
18580
18836
  return { type: "none", workflowFiles: [], hasCodeQL: false, hasLinting: false };
@@ -19945,8 +20201,8 @@ function hasNullByte(str) {
19945
20201
  }
19946
20202
  function isBlockedPath(absolute) {
19947
20203
  for (const blocked of BLOCKED_PATHS) {
19948
- const normalizedBlocked = path38__default.normalize(blocked);
19949
- if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path38__default.sep)) {
20204
+ const normalizedBlocked = path39__default.normalize(blocked);
20205
+ if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path39__default.sep)) {
19950
20206
  return blocked;
19951
20207
  }
19952
20208
  }
@@ -20040,7 +20296,7 @@ Examples:
20040
20296
  throw new ToolError("Invalid file path", { tool: "open_file" });
20041
20297
  }
20042
20298
  const workDir = cwd ?? process.cwd();
20043
- const absolute = path38__default.isAbsolute(filePath) ? path38__default.normalize(filePath) : path38__default.resolve(workDir, filePath);
20299
+ const absolute = path39__default.isAbsolute(filePath) ? path39__default.normalize(filePath) : path39__default.resolve(workDir, filePath);
20044
20300
  const blockedBy = isBlockedPath(absolute);
20045
20301
  if (blockedBy) {
20046
20302
  throw new ToolError(`Access to system path '${blockedBy}' is not allowed`, {
@@ -20063,14 +20319,14 @@ Examples:
20063
20319
  };
20064
20320
  }
20065
20321
  if (isBlockedExecFile(absolute)) {
20066
- throw new ToolError(`Execution of sensitive file is blocked: ${path38__default.basename(absolute)}`, {
20322
+ throw new ToolError(`Execution of sensitive file is blocked: ${path39__default.basename(absolute)}`, {
20067
20323
  tool: "open_file"
20068
20324
  });
20069
20325
  }
20070
20326
  if (args.length > 0 && hasDangerousArgs(args)) {
20071
20327
  throw new ToolError("Arguments contain dangerous patterns", { tool: "open_file" });
20072
20328
  }
20073
- const ext = path38__default.extname(absolute);
20329
+ const ext = path39__default.extname(absolute);
20074
20330
  const interpreter = getInterpreter(ext);
20075
20331
  const executable = await isExecutable(absolute);
20076
20332
  let command;
@@ -20083,7 +20339,7 @@ Examples:
20083
20339
  cmdArgs = [...args];
20084
20340
  } else {
20085
20341
  throw new ToolError(
20086
- `Cannot execute '${path38__default.basename(absolute)}': no known interpreter for '${ext || "(no extension)"}' and file is not executable`,
20342
+ `Cannot execute '${path39__default.basename(absolute)}': no known interpreter for '${ext || "(no extension)"}' and file is not executable`,
20087
20343
  { tool: "open_file" }
20088
20344
  );
20089
20345
  }
@@ -20255,10 +20511,10 @@ function getAllowedPaths() {
20255
20511
  return [...sessionAllowedPaths];
20256
20512
  }
20257
20513
  function isWithinAllowedPath(absolutePath, operation) {
20258
- const normalizedTarget = path38__default.normalize(absolutePath);
20514
+ const normalizedTarget = path39__default.normalize(absolutePath);
20259
20515
  for (const entry of sessionAllowedPaths) {
20260
- const normalizedAllowed = path38__default.normalize(entry.path);
20261
- if (normalizedTarget === normalizedAllowed || normalizedTarget.startsWith(normalizedAllowed + path38__default.sep)) {
20516
+ const normalizedAllowed = path39__default.normalize(entry.path);
20517
+ if (normalizedTarget === normalizedAllowed || normalizedTarget.startsWith(normalizedAllowed + path39__default.sep)) {
20262
20518
  if (operation === "read") return true;
20263
20519
  if (entry.level === "write") return true;
20264
20520
  }
@@ -20266,8 +20522,8 @@ function isWithinAllowedPath(absolutePath, operation) {
20266
20522
  return false;
20267
20523
  }
20268
20524
  function addAllowedPathToSession(dirPath, level) {
20269
- const absolute = path38__default.resolve(dirPath);
20270
- if (sessionAllowedPaths.some((e) => path38__default.normalize(e.path) === path38__default.normalize(absolute))) {
20525
+ const absolute = path39__default.resolve(dirPath);
20526
+ if (sessionAllowedPaths.some((e) => path39__default.normalize(e.path) === path39__default.normalize(absolute))) {
20271
20527
  return;
20272
20528
  }
20273
20529
  sessionAllowedPaths.push({
@@ -20277,14 +20533,14 @@ function addAllowedPathToSession(dirPath, level) {
20277
20533
  });
20278
20534
  }
20279
20535
  function removeAllowedPathFromSession(dirPath) {
20280
- const absolute = path38__default.resolve(dirPath);
20281
- const normalized = path38__default.normalize(absolute);
20536
+ const absolute = path39__default.resolve(dirPath);
20537
+ const normalized = path39__default.normalize(absolute);
20282
20538
  const before = sessionAllowedPaths.length;
20283
- sessionAllowedPaths = sessionAllowedPaths.filter((e) => path38__default.normalize(e.path) !== normalized);
20539
+ sessionAllowedPaths = sessionAllowedPaths.filter((e) => path39__default.normalize(e.path) !== normalized);
20284
20540
  return sessionAllowedPaths.length < before;
20285
20541
  }
20286
20542
  async function loadAllowedPaths(projectPath) {
20287
- currentProjectPath = path38__default.resolve(projectPath);
20543
+ currentProjectPath = path39__default.resolve(projectPath);
20288
20544
  const store = await loadStore();
20289
20545
  const entries = store.projects[currentProjectPath] ?? [];
20290
20546
  for (const entry of entries) {
@@ -20293,14 +20549,14 @@ async function loadAllowedPaths(projectPath) {
20293
20549
  }
20294
20550
  async function persistAllowedPath(dirPath, level) {
20295
20551
  if (!currentProjectPath) return;
20296
- const absolute = path38__default.resolve(dirPath);
20552
+ const absolute = path39__default.resolve(dirPath);
20297
20553
  const store = await loadStore();
20298
20554
  if (!store.projects[currentProjectPath]) {
20299
20555
  store.projects[currentProjectPath] = [];
20300
20556
  }
20301
20557
  const entries = store.projects[currentProjectPath];
20302
- const normalized = path38__default.normalize(absolute);
20303
- if (entries.some((e) => path38__default.normalize(e.path) === normalized)) {
20558
+ const normalized = path39__default.normalize(absolute);
20559
+ if (entries.some((e) => path39__default.normalize(e.path) === normalized)) {
20304
20560
  return;
20305
20561
  }
20306
20562
  entries.push({
@@ -20312,13 +20568,13 @@ async function persistAllowedPath(dirPath, level) {
20312
20568
  }
20313
20569
  async function removePersistedAllowedPath(dirPath) {
20314
20570
  if (!currentProjectPath) return false;
20315
- const absolute = path38__default.resolve(dirPath);
20316
- const normalized = path38__default.normalize(absolute);
20571
+ const absolute = path39__default.resolve(dirPath);
20572
+ const normalized = path39__default.normalize(absolute);
20317
20573
  const store = await loadStore();
20318
20574
  const entries = store.projects[currentProjectPath];
20319
20575
  if (!entries) return false;
20320
20576
  const before = entries.length;
20321
- store.projects[currentProjectPath] = entries.filter((e) => path38__default.normalize(e.path) !== normalized);
20577
+ store.projects[currentProjectPath] = entries.filter((e) => path39__default.normalize(e.path) !== normalized);
20322
20578
  if (store.projects[currentProjectPath].length < before) {
20323
20579
  await saveStore(store);
20324
20580
  return true;
@@ -20335,7 +20591,7 @@ async function loadStore() {
20335
20591
  }
20336
20592
  async function saveStore(store) {
20337
20593
  try {
20338
- await fs35__default.mkdir(path38__default.dirname(STORE_FILE), { recursive: true });
20594
+ await fs35__default.mkdir(path39__default.dirname(STORE_FILE), { recursive: true });
20339
20595
  await fs35__default.writeFile(STORE_FILE, JSON.stringify(store, null, 2), "utf-8");
20340
20596
  } catch {
20341
20597
  }
@@ -20344,7 +20600,7 @@ var STORE_FILE, DEFAULT_STORE, sessionAllowedPaths, currentProjectPath;
20344
20600
  var init_allowed_paths = __esm({
20345
20601
  "src/tools/allowed-paths.ts"() {
20346
20602
  init_paths();
20347
- STORE_FILE = path38__default.join(CONFIG_PATHS.home, "allowed-paths.json");
20603
+ STORE_FILE = path39__default.join(CONFIG_PATHS.home, "allowed-paths.json");
20348
20604
  DEFAULT_STORE = {
20349
20605
  version: 1,
20350
20606
  projects: {}
@@ -20854,7 +21110,7 @@ async function loadStore2() {
20854
21110
  }
20855
21111
  }
20856
21112
  async function saveStore2(store) {
20857
- await fs35__default.mkdir(path38__default.dirname(TOKEN_STORE_PATH), { recursive: true });
21113
+ await fs35__default.mkdir(path39__default.dirname(TOKEN_STORE_PATH), { recursive: true });
20858
21114
  await fs35__default.writeFile(TOKEN_STORE_PATH, JSON.stringify(store, null, 2), {
20859
21115
  encoding: "utf-8",
20860
21116
  mode: 384
@@ -21110,7 +21366,7 @@ async function authenticateMcpOAuth(params) {
21110
21366
  const resource = canonicalizeResourceUrl(params.resourceUrl);
21111
21367
  const store = await loadStore2();
21112
21368
  const stored = store.tokens[getResourceKey(resource)];
21113
- if (stored && !isTokenExpired2(stored)) {
21369
+ if (stored && !params.forceRefresh && !isTokenExpired2(stored)) {
21114
21370
  return stored.accessToken;
21115
21371
  }
21116
21372
  if (!process.stdout.isTTY) {
@@ -21135,7 +21391,7 @@ async function authenticateMcpOAuth(params) {
21135
21391
  authorizationMetadata = await discoverAuthorizationServerMetadata(resource);
21136
21392
  }
21137
21393
  authorizationServer = authorizationServer ?? authorizationMetadata.issuer ?? new URL(resource).origin;
21138
- if (stored && isTokenExpired2(stored) && stored.refreshToken && stored.clientId) {
21394
+ if (stored && stored.refreshToken && stored.clientId && (params.forceRefresh || isTokenExpired2(stored))) {
21139
21395
  try {
21140
21396
  const refreshed = await refreshAccessToken2({
21141
21397
  tokenEndpoint: authorizationMetadata.token_endpoint,
@@ -21201,7 +21457,7 @@ var init_oauth2 = __esm({
21201
21457
  init_paths();
21202
21458
  init_logger();
21203
21459
  execFileAsync2 = promisify(execFile);
21204
- TOKEN_STORE_PATH = path38__default.join(CONFIG_PATHS.tokens, "mcp-oauth.json");
21460
+ TOKEN_STORE_PATH = path39__default.join(CONFIG_PATHS.tokens, "mcp-oauth.json");
21205
21461
  OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
21206
21462
  logger = getLogger();
21207
21463
  }
@@ -21280,18 +21536,22 @@ var init_http = __esm({
21280
21536
  }
21281
21537
  return true;
21282
21538
  }
21283
- async ensureOAuthToken(wwwAuthenticateHeader) {
21284
- if (this.oauthToken) {
21539
+ async ensureOAuthToken(wwwAuthenticateHeader, options) {
21540
+ if (this.oauthToken && !options?.forceRefresh) {
21285
21541
  return this.oauthToken;
21286
21542
  }
21287
21543
  if (this.oauthInFlight) {
21288
21544
  return this.oauthInFlight;
21289
21545
  }
21290
21546
  const serverName = this.config.name ?? this.config.url;
21547
+ if (options?.forceRefresh) {
21548
+ this.oauthToken = void 0;
21549
+ }
21291
21550
  this.oauthInFlight = authenticateMcpOAuth({
21292
21551
  serverName,
21293
21552
  resourceUrl: this.config.url,
21294
- wwwAuthenticateHeader
21553
+ wwwAuthenticateHeader,
21554
+ forceRefresh: options?.forceRefresh
21295
21555
  }).then((token) => {
21296
21556
  this.oauthToken = token;
21297
21557
  return token;
@@ -21315,14 +21575,14 @@ var init_http = __esm({
21315
21575
  }
21316
21576
  return response;
21317
21577
  }
21318
- await this.ensureOAuthToken(response.headers.get("www-authenticate"));
21578
+ await this.ensureOAuthToken(response.headers.get("www-authenticate"), { forceRefresh: true });
21319
21579
  response = await doFetch();
21320
21580
  return response;
21321
21581
  }
21322
21582
  looksLikeAuthErrorMessage(message) {
21323
21583
  if (!message) return false;
21324
21584
  const msg = message.toLowerCase();
21325
- const hasStrongAuthSignal = msg.includes("unauthorized") || msg.includes("unauthorised") || msg.includes("authentication") || msg.includes("oauth") || msg.includes("access token") || msg.includes("bearer") || msg.includes("not authenticated") || msg.includes("not logged") || msg.includes("login") || msg.includes("generate") && msg.includes("token");
21585
+ const hasStrongAuthSignal = msg.includes("unauthorized") || msg.includes("unauthorised") || msg.includes("authentication") || msg.includes("oauth") || msg.includes("access token") || msg.includes("invalid_token") || msg.includes("invalid token") || msg.includes("token expired") || msg.includes("bearer") || msg.includes("not authenticated") || msg.includes("not logged") || msg.includes("login") || msg.includes("generate") && msg.includes("token");
21326
21586
  const hasVendorHint = msg.includes("gemini cli") || msg.includes("jira") || msg.includes("confluence") || msg.includes("atlassian");
21327
21587
  const hasWeakAuthSignal = msg.includes("authenticate") || msg.includes("token") || msg.includes("authorization");
21328
21588
  return hasStrongAuthSignal || // Vendor-specific hints alone are not enough; require an auth-related token too.
@@ -21396,7 +21656,9 @@ var init_http = __esm({
21396
21656
  }
21397
21657
  const data = await response.json();
21398
21658
  if (this.shouldAttemptOAuth() && this.isJsonRpcAuthError(data)) {
21399
- await this.ensureOAuthToken(response.headers.get("www-authenticate"));
21659
+ await this.ensureOAuthToken(response.headers.get("www-authenticate"), {
21660
+ forceRefresh: true
21661
+ });
21400
21662
  const retryResponse = await this.sendRequestWithOAuthRetry(
21401
21663
  "POST",
21402
21664
  JSON.stringify(message),
@@ -22069,7 +22331,7 @@ __export(allow_path_prompt_exports, {
22069
22331
  promptAllowPath: () => promptAllowPath
22070
22332
  });
22071
22333
  async function promptAllowPath(dirPath) {
22072
- const absolute = path38__default.resolve(dirPath);
22334
+ const absolute = path39__default.resolve(dirPath);
22073
22335
  console.log();
22074
22336
  console.log(chalk.yellow(" \u26A0 Access denied \u2014 path is outside the project directory"));
22075
22337
  console.log(chalk.dim(` \u{1F4C1} ${absolute}`));
@@ -22311,13 +22573,13 @@ __export(stack_detector_exports, {
22311
22573
  detectProjectStack: () => detectProjectStack
22312
22574
  });
22313
22575
  async function detectStack2(cwd) {
22314
- if (await fileExists3(path38__default.join(cwd, "package.json"))) return "node";
22315
- if (await fileExists3(path38__default.join(cwd, "Cargo.toml"))) return "rust";
22316
- if (await fileExists3(path38__default.join(cwd, "pyproject.toml"))) return "python";
22317
- if (await fileExists3(path38__default.join(cwd, "go.mod"))) return "go";
22318
- if (await fileExists3(path38__default.join(cwd, "pom.xml"))) return "java";
22319
- if (await fileExists3(path38__default.join(cwd, "build.gradle"))) return "java";
22320
- if (await fileExists3(path38__default.join(cwd, "build.gradle.kts"))) return "java";
22576
+ if (await fileExists3(path39__default.join(cwd, "package.json"))) return "node";
22577
+ if (await fileExists3(path39__default.join(cwd, "Cargo.toml"))) return "rust";
22578
+ if (await fileExists3(path39__default.join(cwd, "pyproject.toml"))) return "python";
22579
+ if (await fileExists3(path39__default.join(cwd, "go.mod"))) return "go";
22580
+ if (await fileExists3(path39__default.join(cwd, "pom.xml"))) return "java";
22581
+ if (await fileExists3(path39__default.join(cwd, "build.gradle"))) return "java";
22582
+ if (await fileExists3(path39__default.join(cwd, "build.gradle.kts"))) return "java";
22321
22583
  return "unknown";
22322
22584
  }
22323
22585
  async function detectPackageManager3(cwd, stack) {
@@ -22325,23 +22587,23 @@ async function detectPackageManager3(cwd, stack) {
22325
22587
  if (stack === "python") return "pip";
22326
22588
  if (stack === "go") return "go";
22327
22589
  if (stack === "java") {
22328
- if (await fileExists3(path38__default.join(cwd, "build.gradle")) || await fileExists3(path38__default.join(cwd, "build.gradle.kts"))) {
22590
+ if (await fileExists3(path39__default.join(cwd, "build.gradle")) || await fileExists3(path39__default.join(cwd, "build.gradle.kts"))) {
22329
22591
  return "gradle";
22330
22592
  }
22331
- if (await fileExists3(path38__default.join(cwd, "pom.xml"))) {
22593
+ if (await fileExists3(path39__default.join(cwd, "pom.xml"))) {
22332
22594
  return "maven";
22333
22595
  }
22334
22596
  }
22335
22597
  if (stack === "node") {
22336
- if (await fileExists3(path38__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
22337
- if (await fileExists3(path38__default.join(cwd, "yarn.lock"))) return "yarn";
22338
- if (await fileExists3(path38__default.join(cwd, "bun.lockb"))) return "bun";
22598
+ if (await fileExists3(path39__default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
22599
+ if (await fileExists3(path39__default.join(cwd, "yarn.lock"))) return "yarn";
22600
+ if (await fileExists3(path39__default.join(cwd, "bun.lockb"))) return "bun";
22339
22601
  return "npm";
22340
22602
  }
22341
22603
  return null;
22342
22604
  }
22343
22605
  async function parsePackageJson(cwd) {
22344
- const packageJsonPath = path38__default.join(cwd, "package.json");
22606
+ const packageJsonPath = path39__default.join(cwd, "package.json");
22345
22607
  try {
22346
22608
  const content = await fs35__default.readFile(packageJsonPath, "utf-8");
22347
22609
  const pkg = JSON.parse(content);
@@ -22373,7 +22635,7 @@ async function parsePackageJson(cwd) {
22373
22635
  if (allDeps["@playwright/test"]) testingFrameworks.push("playwright");
22374
22636
  if (allDeps.cypress) testingFrameworks.push("cypress");
22375
22637
  const languages = ["JavaScript"];
22376
- if (allDeps.typescript || await fileExists3(path38__default.join(cwd, "tsconfig.json"))) {
22638
+ if (allDeps.typescript || await fileExists3(path39__default.join(cwd, "tsconfig.json"))) {
22377
22639
  languages.push("TypeScript");
22378
22640
  }
22379
22641
  return {
@@ -22394,7 +22656,7 @@ async function parsePackageJson(cwd) {
22394
22656
  }
22395
22657
  }
22396
22658
  async function parsePomXml(cwd) {
22397
- const pomPath = path38__default.join(cwd, "pom.xml");
22659
+ const pomPath = path39__default.join(cwd, "pom.xml");
22398
22660
  try {
22399
22661
  const content = await fs35__default.readFile(pomPath, "utf-8");
22400
22662
  const dependencies = {};
@@ -22431,7 +22693,7 @@ async function parsePomXml(cwd) {
22431
22693
  }
22432
22694
  }
22433
22695
  async function parsePyprojectToml(cwd) {
22434
- const pyprojectPath = path38__default.join(cwd, "pyproject.toml");
22696
+ const pyprojectPath = path39__default.join(cwd, "pyproject.toml");
22435
22697
  try {
22436
22698
  const content = await fs35__default.readFile(pyprojectPath, "utf-8");
22437
22699
  const dependencies = {};
@@ -22474,7 +22736,7 @@ async function detectProjectStack(cwd) {
22474
22736
  testingFrameworks = parsed.testingFrameworks;
22475
22737
  languages = parsed.languages;
22476
22738
  } else if (stack === "java") {
22477
- const isGradle = await fileExists3(path38__default.join(cwd, "build.gradle")) || await fileExists3(path38__default.join(cwd, "build.gradle.kts"));
22739
+ const isGradle = await fileExists3(path39__default.join(cwd, "build.gradle")) || await fileExists3(path39__default.join(cwd, "build.gradle.kts"));
22478
22740
  const parsed = isGradle ? { dependencies: {}, frameworks: [], buildTools: ["gradle"], testingFrameworks: ["JUnit"] } : await parsePomXml(cwd);
22479
22741
  dependencies = parsed.dependencies;
22480
22742
  frameworks = parsed.frameworks;
@@ -22522,6 +22784,14 @@ __export(tools_exports, {
22522
22784
  wrapMCPTool: () => wrapMCPTool,
22523
22785
  wrapMCPTools: () => wrapMCPTools
22524
22786
  });
22787
+ function buildMcpToolDescription(serverName, tool) {
22788
+ const base = tool.description || `Tool '${tool.name}' exposed by MCP server '${serverName}'`;
22789
+ const lowerServer = serverName.toLowerCase();
22790
+ if (lowerServer.includes("atlassian") || lowerServer.includes("jira") || lowerServer.includes("confluence")) {
22791
+ return `${base}. Use this MCP tool for Atlassian/Jira/Confluence data. Prefer it over direct web_fetch or http_fetch for Atlassian content.`;
22792
+ }
22793
+ return `${base}. Exposed by MCP server '${serverName}'. Prefer this MCP tool over generic web/http fetch when accessing data from that connected service.`;
22794
+ }
22525
22795
  function jsonSchemaToZod(schema) {
22526
22796
  if (schema.enum && Array.isArray(schema.enum)) {
22527
22797
  const values = schema.enum;
@@ -22674,7 +22944,7 @@ function wrapMCPTool(tool, serverName, client, options = {}) {
22674
22944
  const parametersSchema = createToolParametersSchema(tool);
22675
22945
  const cocoTool = {
22676
22946
  name: wrappedName,
22677
- description: tool.description || `MCP tool: ${tool.name}`,
22947
+ description: buildMcpToolDescription(serverName, tool),
22678
22948
  category: opts.category,
22679
22949
  parameters: parametersSchema,
22680
22950
  execute: async (params) => {
@@ -22760,251 +23030,6 @@ var init_tools = __esm({
22760
23030
  }
22761
23031
  });
22762
23032
 
22763
- // src/mcp/config-loader.ts
22764
- var config_loader_exports = {};
22765
- __export(config_loader_exports, {
22766
- loadMCPConfigFile: () => loadMCPConfigFile,
22767
- loadMCPServersFromCOCOConfig: () => loadMCPServersFromCOCOConfig,
22768
- loadProjectMCPFile: () => loadProjectMCPFile,
22769
- mergeMCPConfigs: () => mergeMCPConfigs
22770
- });
22771
- function expandEnvVar(value) {
22772
- return value.replace(/\$\{([^}]+)\}/g, (match, name) => process.env[name] ?? match);
22773
- }
22774
- function expandEnvObject(env2) {
22775
- const result = {};
22776
- for (const [k, v] of Object.entries(env2)) {
22777
- result[k] = expandEnvVar(v);
22778
- }
22779
- return result;
22780
- }
22781
- function expandHeaders(headers) {
22782
- const result = {};
22783
- for (const [k, v] of Object.entries(headers)) {
22784
- result[k] = expandEnvVar(v);
22785
- }
22786
- return result;
22787
- }
22788
- function convertStandardEntry(name, entry) {
22789
- if (entry.command) {
22790
- return {
22791
- name,
22792
- transport: "stdio",
22793
- enabled: entry.enabled ?? true,
22794
- stdio: {
22795
- command: entry.command,
22796
- args: entry.args,
22797
- env: entry.env ? expandEnvObject(entry.env) : void 0
22798
- }
22799
- };
22800
- }
22801
- if (entry.url) {
22802
- const headers = entry.headers ? expandHeaders(entry.headers) : void 0;
22803
- const authHeader = headers?.["Authorization"] ?? headers?.["authorization"];
22804
- let auth;
22805
- if (authHeader) {
22806
- if (authHeader.startsWith("Bearer ")) {
22807
- auth = { type: "bearer", token: authHeader.slice(7) };
22808
- } else {
22809
- auth = { type: "apikey", token: authHeader };
22810
- }
22811
- }
22812
- return {
22813
- name,
22814
- transport: "http",
22815
- enabled: entry.enabled ?? true,
22816
- http: {
22817
- url: entry.url,
22818
- ...headers && Object.keys(headers).length > 0 ? { headers } : {},
22819
- ...auth ? { auth } : {}
22820
- }
22821
- };
22822
- }
22823
- throw new Error(`Server "${name}" must have either "command" (stdio) or "url" (http) defined`);
22824
- }
22825
- async function loadMCPConfigFile(configPath) {
22826
- try {
22827
- await access(configPath);
22828
- } catch {
22829
- throw new MCPError(-32003 /* CONNECTION_ERROR */, `Config file not found: ${configPath}`);
22830
- }
22831
- let content;
22832
- try {
22833
- content = await readFile(configPath, "utf-8");
22834
- } catch (error) {
22835
- throw new MCPError(
22836
- -32003 /* CONNECTION_ERROR */,
22837
- `Failed to read config file: ${error instanceof Error ? error.message : "Unknown error"}`
22838
- );
22839
- }
22840
- let parsed;
22841
- try {
22842
- parsed = JSON.parse(content);
22843
- } catch {
22844
- throw new MCPError(-32700 /* PARSE_ERROR */, "Invalid JSON in config file");
22845
- }
22846
- const obj = parsed;
22847
- if (obj.mcpServers && typeof obj.mcpServers === "object" && !Array.isArray(obj.mcpServers)) {
22848
- return loadStandardFormat(obj, configPath);
22849
- }
22850
- if (obj.servers && Array.isArray(obj.servers)) {
22851
- return loadCocoFormat(obj, configPath);
22852
- }
22853
- throw new MCPError(
22854
- -32602 /* INVALID_PARAMS */,
22855
- 'Config file must have either a "mcpServers" object (standard) or a "servers" array (Coco format)'
22856
- );
22857
- }
22858
- function loadStandardFormat(config, configPath) {
22859
- const validServers = [];
22860
- const errors = [];
22861
- for (const [name, entry] of Object.entries(config.mcpServers)) {
22862
- if (name.startsWith("_")) continue;
22863
- try {
22864
- const converted = convertStandardEntry(name, entry);
22865
- validateServerConfig(converted);
22866
- validServers.push(converted);
22867
- } catch (error) {
22868
- const message = error instanceof Error ? error.message : "Unknown error";
22869
- errors.push(`Server '${name}': ${message}`);
22870
- }
22871
- }
22872
- if (errors.length > 0) {
22873
- getLogger().warn(`[MCP] Some servers in ${configPath} failed to load: ${errors.join("; ")}`);
22874
- }
22875
- return validServers;
22876
- }
22877
- async function loadProjectMCPFile(projectPath) {
22878
- const mcpJsonPath = path38__default.join(projectPath, ".mcp.json");
22879
- try {
22880
- await access(mcpJsonPath);
22881
- } catch {
22882
- return [];
22883
- }
22884
- try {
22885
- return await loadMCPConfigFile(mcpJsonPath);
22886
- } catch (error) {
22887
- getLogger().warn(
22888
- `[MCP] Failed to load .mcp.json: ${error instanceof Error ? error.message : String(error)}`
22889
- );
22890
- return [];
22891
- }
22892
- }
22893
- function loadCocoFormat(config, configPath) {
22894
- const validServers = [];
22895
- const errors = [];
22896
- for (const server of config.servers) {
22897
- try {
22898
- const converted = convertCocoServerEntry(server);
22899
- validateServerConfig(converted);
22900
- validServers.push(converted);
22901
- } catch (error) {
22902
- const message = error instanceof Error ? error.message : "Unknown error";
22903
- errors.push(`Server '${server.name || "unknown"}': ${message}`);
22904
- }
22905
- }
22906
- if (errors.length > 0) {
22907
- getLogger().warn(`[MCP] Some servers in ${configPath} failed to load: ${errors.join("; ")}`);
22908
- }
22909
- return validServers;
22910
- }
22911
- function convertCocoServerEntry(server) {
22912
- const base = {
22913
- name: server.name,
22914
- description: server.description,
22915
- transport: server.transport,
22916
- enabled: server.enabled ?? true,
22917
- metadata: server.metadata
22918
- };
22919
- if (server.transport === "stdio" && server.stdio) {
22920
- return {
22921
- ...base,
22922
- stdio: {
22923
- command: server.stdio.command,
22924
- args: server.stdio.args,
22925
- env: server.stdio.env ? expandEnvObject(server.stdio.env) : void 0,
22926
- cwd: server.stdio.cwd
22927
- }
22928
- };
22929
- }
22930
- if (server.transport === "http" && server.http) {
22931
- return {
22932
- ...base,
22933
- http: {
22934
- url: server.http.url,
22935
- ...server.http.headers ? { headers: expandHeaders(server.http.headers) } : {},
22936
- ...server.http.auth ? { auth: server.http.auth } : {},
22937
- ...server.http.timeout !== void 0 ? { timeout: server.http.timeout } : {}
22938
- }
22939
- };
22940
- }
22941
- throw new Error(`Missing configuration for transport: ${server.transport}`);
22942
- }
22943
- function mergeMCPConfigs(base, ...overrides) {
22944
- const merged = /* @__PURE__ */ new Map();
22945
- for (const server of base) {
22946
- merged.set(server.name, server);
22947
- }
22948
- for (const override of overrides) {
22949
- for (const server of override) {
22950
- const existing = merged.get(server.name);
22951
- if (existing) {
22952
- merged.set(server.name, { ...existing, ...server });
22953
- } else {
22954
- merged.set(server.name, server);
22955
- }
22956
- }
22957
- }
22958
- return Array.from(merged.values());
22959
- }
22960
- async function loadMCPServersFromCOCOConfig(configPath) {
22961
- const { loadConfig: loadConfig3 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
22962
- const { MCPServerConfigEntrySchema: MCPServerConfigEntrySchema2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
22963
- const config = await loadConfig3(configPath);
22964
- if (!config.mcp?.servers || config.mcp.servers.length === 0) {
22965
- return [];
22966
- }
22967
- const servers = [];
22968
- for (const entry of config.mcp.servers) {
22969
- try {
22970
- const parsed = MCPServerConfigEntrySchema2.parse(entry);
22971
- const serverConfig = {
22972
- name: parsed.name,
22973
- description: parsed.description,
22974
- transport: parsed.transport,
22975
- enabled: parsed.enabled,
22976
- ...parsed.transport === "stdio" && parsed.command && {
22977
- stdio: {
22978
- command: parsed.command,
22979
- args: parsed.args,
22980
- env: parsed.env ? expandEnvObject(parsed.env) : void 0
22981
- }
22982
- },
22983
- ...parsed.transport === "http" && parsed.url && {
22984
- http: {
22985
- url: parsed.url,
22986
- auth: parsed.auth
22987
- }
22988
- }
22989
- };
22990
- validateServerConfig(serverConfig);
22991
- servers.push(serverConfig);
22992
- } catch (error) {
22993
- const message = error instanceof Error ? error.message : "Unknown error";
22994
- getLogger().warn(`[MCP] Failed to load server '${entry.name}': ${message}`);
22995
- }
22996
- }
22997
- return servers;
22998
- }
22999
- var init_config_loader = __esm({
23000
- "src/mcp/config-loader.ts"() {
23001
- init_config();
23002
- init_types();
23003
- init_errors2();
23004
- init_logger();
23005
- }
23006
- });
23007
-
23008
23033
  // src/cli/repl/hooks/types.ts
23009
23034
  function isHookEvent(value) {
23010
23035
  return typeof value === "string" && HOOK_EVENTS.includes(value);
@@ -24187,23 +24212,23 @@ init_version();
24187
24212
  // src/orchestrator/project.ts
24188
24213
  init_env();
24189
24214
  async function createProjectStructure(projectPath, info) {
24190
- const cocoPath = path38__default.join(projectPath, ".coco");
24215
+ const cocoPath = path39__default.join(projectPath, ".coco");
24191
24216
  const directories = [
24192
24217
  cocoPath,
24193
- path38__default.join(cocoPath, "state"),
24194
- path38__default.join(cocoPath, "checkpoints"),
24195
- path38__default.join(cocoPath, "logs"),
24196
- path38__default.join(cocoPath, "discovery"),
24197
- path38__default.join(cocoPath, "spec"),
24198
- path38__default.join(cocoPath, "architecture"),
24199
- path38__default.join(cocoPath, "architecture", "adrs"),
24200
- path38__default.join(cocoPath, "architecture", "diagrams"),
24201
- path38__default.join(cocoPath, "planning"),
24202
- path38__default.join(cocoPath, "planning", "epics"),
24203
- path38__default.join(cocoPath, "execution"),
24204
- path38__default.join(cocoPath, "versions"),
24205
- path38__default.join(cocoPath, "reviews"),
24206
- path38__default.join(cocoPath, "delivery")
24218
+ path39__default.join(cocoPath, "state"),
24219
+ path39__default.join(cocoPath, "checkpoints"),
24220
+ path39__default.join(cocoPath, "logs"),
24221
+ path39__default.join(cocoPath, "discovery"),
24222
+ path39__default.join(cocoPath, "spec"),
24223
+ path39__default.join(cocoPath, "architecture"),
24224
+ path39__default.join(cocoPath, "architecture", "adrs"),
24225
+ path39__default.join(cocoPath, "architecture", "diagrams"),
24226
+ path39__default.join(cocoPath, "planning"),
24227
+ path39__default.join(cocoPath, "planning", "epics"),
24228
+ path39__default.join(cocoPath, "execution"),
24229
+ path39__default.join(cocoPath, "versions"),
24230
+ path39__default.join(cocoPath, "reviews"),
24231
+ path39__default.join(cocoPath, "delivery")
24207
24232
  ];
24208
24233
  for (const dir of directories) {
24209
24234
  await fs35__default.mkdir(dir, { recursive: true });
@@ -24241,7 +24266,7 @@ async function createInitialConfig(cocoPath, info) {
24241
24266
  maxCheckpoints: 50
24242
24267
  }
24243
24268
  };
24244
- await fs35__default.writeFile(path38__default.join(cocoPath, "config.json"), JSON.stringify(config, null, 2), "utf-8");
24269
+ await fs35__default.writeFile(path39__default.join(cocoPath, "config.json"), JSON.stringify(config, null, 2), "utf-8");
24245
24270
  }
24246
24271
  async function createProjectState(cocoPath, info) {
24247
24272
  const state = {
@@ -24259,7 +24284,7 @@ async function createProjectState(cocoPath, info) {
24259
24284
  lastCheckpoint: null
24260
24285
  };
24261
24286
  await fs35__default.writeFile(
24262
- path38__default.join(cocoPath, "state", "project.json"),
24287
+ path39__default.join(cocoPath, "state", "project.json"),
24263
24288
  JSON.stringify(state, null, 2),
24264
24289
  "utf-8"
24265
24290
  );
@@ -24280,7 +24305,7 @@ checkpoints/
24280
24305
  state/session.json
24281
24306
  state/lock.json
24282
24307
  `;
24283
- await fs35__default.writeFile(path38__default.join(cocoPath, ".gitignore"), content, "utf-8");
24308
+ await fs35__default.writeFile(path39__default.join(cocoPath, ".gitignore"), content, "utf-8");
24284
24309
  }
24285
24310
  async function createReadme(cocoPath, info) {
24286
24311
  const content = `# Corbat-Coco Project: ${info.name}
@@ -24327,7 +24352,7 @@ Edit \`config.json\` to customize:
24327
24352
  ---
24328
24353
  Generated by Corbat-Coco v0.1.0
24329
24354
  `;
24330
- await fs35__default.writeFile(path38__default.join(cocoPath, "README.md"), content, "utf-8");
24355
+ await fs35__default.writeFile(path39__default.join(cocoPath, "README.md"), content, "utf-8");
24331
24356
  }
24332
24357
 
24333
24358
  // src/cli/commands/init.ts
@@ -25631,13 +25656,13 @@ function createSpecificationGenerator(llm, config) {
25631
25656
  // src/phases/converge/persistence.ts
25632
25657
  init_errors();
25633
25658
  function getPersistencePaths(projectPath) {
25634
- const baseDir = path38__default.join(projectPath, ".coco", "spec");
25659
+ const baseDir = path39__default.join(projectPath, ".coco", "spec");
25635
25660
  return {
25636
25661
  baseDir,
25637
- sessionFile: path38__default.join(baseDir, "discovery-session.json"),
25638
- specFile: path38__default.join(baseDir, "spec.md"),
25639
- conversationLog: path38__default.join(baseDir, "conversation.jsonl"),
25640
- checkpointFile: path38__default.join(baseDir, "checkpoint.json")
25662
+ sessionFile: path39__default.join(baseDir, "discovery-session.json"),
25663
+ specFile: path39__default.join(baseDir, "spec.md"),
25664
+ conversationLog: path39__default.join(baseDir, "conversation.jsonl"),
25665
+ checkpointFile: path39__default.join(baseDir, "checkpoint.json")
25641
25666
  };
25642
25667
  }
25643
25668
  var SessionPersistence = class {
@@ -27741,7 +27766,7 @@ var OrchestrateExecutor = class {
27741
27766
  }
27742
27767
  async loadSpecification(projectPath) {
27743
27768
  try {
27744
- const jsonPath = path38__default.join(projectPath, ".coco", "spec", "spec.json");
27769
+ const jsonPath = path39__default.join(projectPath, ".coco", "spec", "spec.json");
27745
27770
  const jsonContent = await fs35__default.readFile(jsonPath, "utf-8");
27746
27771
  return JSON.parse(jsonContent);
27747
27772
  } catch {
@@ -27753,7 +27778,7 @@ var OrchestrateExecutor = class {
27753
27778
  version: "1.0.0",
27754
27779
  generatedAt: /* @__PURE__ */ new Date(),
27755
27780
  overview: {
27756
- name: path38__default.basename(projectPath),
27781
+ name: path39__default.basename(projectPath),
27757
27782
  description: "Project specification",
27758
27783
  goals: [],
27759
27784
  targetUsers: ["developers"],
@@ -27780,52 +27805,52 @@ var OrchestrateExecutor = class {
27780
27805
  };
27781
27806
  }
27782
27807
  async saveArchitecture(projectPath, architecture) {
27783
- const dir = path38__default.join(projectPath, ".coco", "architecture");
27808
+ const dir = path39__default.join(projectPath, ".coco", "architecture");
27784
27809
  await fs35__default.mkdir(dir, { recursive: true });
27785
- const mdPath = path38__default.join(dir, "ARCHITECTURE.md");
27810
+ const mdPath = path39__default.join(dir, "ARCHITECTURE.md");
27786
27811
  await fs35__default.writeFile(mdPath, generateArchitectureMarkdown(architecture), "utf-8");
27787
- const jsonPath = path38__default.join(dir, "architecture.json");
27812
+ const jsonPath = path39__default.join(dir, "architecture.json");
27788
27813
  await fs35__default.writeFile(jsonPath, JSON.stringify(architecture, null, 2), "utf-8");
27789
27814
  return mdPath;
27790
27815
  }
27791
27816
  async saveADRs(projectPath, adrs) {
27792
- const dir = path38__default.join(projectPath, ".coco", "architecture", "adrs");
27817
+ const dir = path39__default.join(projectPath, ".coco", "architecture", "adrs");
27793
27818
  await fs35__default.mkdir(dir, { recursive: true });
27794
27819
  const paths = [];
27795
- const indexPath = path38__default.join(dir, "README.md");
27820
+ const indexPath = path39__default.join(dir, "README.md");
27796
27821
  await fs35__default.writeFile(indexPath, generateADRIndexMarkdown(adrs), "utf-8");
27797
27822
  paths.push(indexPath);
27798
27823
  for (const adr of adrs) {
27799
27824
  const filename = getADRFilename(adr);
27800
- const adrPath = path38__default.join(dir, filename);
27825
+ const adrPath = path39__default.join(dir, filename);
27801
27826
  await fs35__default.writeFile(adrPath, generateADRMarkdown(adr), "utf-8");
27802
27827
  paths.push(adrPath);
27803
27828
  }
27804
27829
  return paths;
27805
27830
  }
27806
27831
  async saveBacklog(projectPath, backlogResult) {
27807
- const dir = path38__default.join(projectPath, ".coco", "planning");
27832
+ const dir = path39__default.join(projectPath, ".coco", "planning");
27808
27833
  await fs35__default.mkdir(dir, { recursive: true });
27809
- const mdPath = path38__default.join(dir, "BACKLOG.md");
27834
+ const mdPath = path39__default.join(dir, "BACKLOG.md");
27810
27835
  await fs35__default.writeFile(mdPath, generateBacklogMarkdown(backlogResult.backlog), "utf-8");
27811
- const jsonPath = path38__default.join(dir, "backlog.json");
27836
+ const jsonPath = path39__default.join(dir, "backlog.json");
27812
27837
  await fs35__default.writeFile(jsonPath, JSON.stringify(backlogResult, null, 2), "utf-8");
27813
27838
  return mdPath;
27814
27839
  }
27815
27840
  async saveSprint(projectPath, sprint, backlogResult) {
27816
- const dir = path38__default.join(projectPath, ".coco", "planning", "sprints");
27841
+ const dir = path39__default.join(projectPath, ".coco", "planning", "sprints");
27817
27842
  await fs35__default.mkdir(dir, { recursive: true });
27818
27843
  const filename = `${sprint.id}.md`;
27819
- const sprintPath = path38__default.join(dir, filename);
27844
+ const sprintPath = path39__default.join(dir, filename);
27820
27845
  await fs35__default.writeFile(sprintPath, generateSprintMarkdown(sprint, backlogResult.backlog), "utf-8");
27821
- const jsonPath = path38__default.join(dir, `${sprint.id}.json`);
27846
+ const jsonPath = path39__default.join(dir, `${sprint.id}.json`);
27822
27847
  await fs35__default.writeFile(jsonPath, JSON.stringify(sprint, null, 2), "utf-8");
27823
27848
  return sprintPath;
27824
27849
  }
27825
27850
  async saveDiagram(projectPath, id, mermaid) {
27826
- const dir = path38__default.join(projectPath, ".coco", "architecture", "diagrams");
27851
+ const dir = path39__default.join(projectPath, ".coco", "architecture", "diagrams");
27827
27852
  await fs35__default.mkdir(dir, { recursive: true });
27828
- const diagramPath = path38__default.join(dir, `${id}.mmd`);
27853
+ const diagramPath = path39__default.join(dir, `${id}.mmd`);
27829
27854
  await fs35__default.writeFile(diagramPath, mermaid, "utf-8");
27830
27855
  return diagramPath;
27831
27856
  }
@@ -30076,10 +30101,10 @@ async function runAdd(source, options) {
30076
30101
  const isGithubShorthand = source.includes("/") && !isGitUrl;
30077
30102
  const isLocalPath = source.startsWith(".") || source.startsWith("/");
30078
30103
  if (isLocalPath) {
30079
- const targetDir = options.global ? CONFIG_PATHS.skills : path38__default.join(process.cwd(), ".agents", "skills");
30080
- const sourcePath = path38__default.resolve(source);
30081
- const skillName = path38__default.basename(sourcePath);
30082
- const destPath = path38__default.join(targetDir, skillName);
30104
+ const targetDir = options.global ? CONFIG_PATHS.skills : path39__default.join(process.cwd(), ".agents", "skills");
30105
+ const sourcePath = path39__default.resolve(source);
30106
+ const skillName = path39__default.basename(sourcePath);
30107
+ const destPath = path39__default.join(targetDir, skillName);
30083
30108
  try {
30084
30109
  await fs35__default.mkdir(targetDir, { recursive: true });
30085
30110
  await fs35__default.cp(sourcePath, destPath, { recursive: true });
@@ -30113,10 +30138,10 @@ async function runAdd(source, options) {
30113
30138
  p26.log.info("Try installing manually: git clone the repo into .agents/skills/");
30114
30139
  }
30115
30140
  } else if (isGitUrl) {
30116
- const targetDir = options.global ? CONFIG_PATHS.skills : path38__default.join(process.cwd(), ".agents", "skills");
30141
+ const targetDir = options.global ? CONFIG_PATHS.skills : path39__default.join(process.cwd(), ".agents", "skills");
30117
30142
  await fs35__default.mkdir(targetDir, { recursive: true });
30118
30143
  const skillName = source.split("/").pop()?.replace(".git", "") ?? "skill";
30119
- const skillDir = path38__default.join(targetDir, skillName);
30144
+ const skillDir = path39__default.join(targetDir, skillName);
30120
30145
  const spinner18 = p26.spinner();
30121
30146
  spinner18.start(`Cloning ${source}...`);
30122
30147
  try {
@@ -30142,9 +30167,9 @@ async function runAdd(source, options) {
30142
30167
  }
30143
30168
  async function runRemove(name, options) {
30144
30169
  p26.intro(chalk.magenta("Remove Skill"));
30145
- const targetDir = options.global ? CONFIG_PATHS.skills : path38__default.join(process.cwd(), ".agents", "skills");
30146
- const skillPath = path38__default.resolve(targetDir, name);
30147
- if (!skillPath.startsWith(path38__default.resolve(targetDir) + path38__default.sep)) {
30170
+ const targetDir = options.global ? CONFIG_PATHS.skills : path39__default.join(process.cwd(), ".agents", "skills");
30171
+ const skillPath = path39__default.resolve(targetDir, name);
30172
+ if (!skillPath.startsWith(path39__default.resolve(targetDir) + path39__default.sep)) {
30148
30173
  p26.log.error(`Invalid skill name: "${name}"`);
30149
30174
  p26.outro("");
30150
30175
  return;
@@ -30228,8 +30253,8 @@ async function runInfo(name) {
30228
30253
  }
30229
30254
  async function runCreate(name, options) {
30230
30255
  p26.intro(chalk.magenta("Create Skill"));
30231
- const targetDir = options.global ? CONFIG_PATHS.skills : path38__default.join(process.cwd(), ".agents", "skills");
30232
- const skillDir = path38__default.join(targetDir, name);
30256
+ const targetDir = options.global ? CONFIG_PATHS.skills : path39__default.join(process.cwd(), ".agents", "skills");
30257
+ const skillDir = path39__default.join(targetDir, name);
30233
30258
  try {
30234
30259
  await fs35__default.access(skillDir);
30235
30260
  p26.log.error(`Skill "${name}" already exists at ${skillDir}`);
@@ -30282,10 +30307,10 @@ when this skill is activated (automatically via matching or manually via /${name
30282
30307
  2. Include examples when helpful
30283
30308
  3. Keep instructions under 500 lines
30284
30309
  `;
30285
- await fs35__default.writeFile(path38__default.join(skillDir, "SKILL.md"), skillMd, "utf-8");
30286
- await fs35__default.mkdir(path38__default.join(skillDir, "references"), { recursive: true });
30310
+ await fs35__default.writeFile(path39__default.join(skillDir, "SKILL.md"), skillMd, "utf-8");
30311
+ await fs35__default.mkdir(path39__default.join(skillDir, "references"), { recursive: true });
30287
30312
  p26.log.success(`Created skill at ${skillDir}`);
30288
- p26.log.info(`Edit ${path38__default.join(skillDir, "SKILL.md")} to add instructions.`);
30313
+ p26.log.info(`Edit ${path39__default.join(skillDir, "SKILL.md")} to add instructions.`);
30289
30314
  p26.outro("");
30290
30315
  }
30291
30316
  function registerWinner(winners, candidate, candidateScanOrder, scanOrderById) {
@@ -33982,7 +34007,7 @@ async function saveConfiguration(result) {
33982
34007
  }
33983
34008
  async function saveEnvVars(filePath, vars, createDir = false) {
33984
34009
  if (createDir) {
33985
- const dir = path38.dirname(filePath);
34010
+ const dir = path39.dirname(filePath);
33986
34011
  try {
33987
34012
  await fs35.mkdir(dir, { recursive: true, mode: 448 });
33988
34013
  } catch {
@@ -35553,7 +35578,7 @@ var buildCommand = {
35553
35578
  };
35554
35579
  async function loadBacklog(projectPath) {
35555
35580
  try {
35556
- const backlogPath = path38.join(projectPath, ".coco", "planning", "backlog.json");
35581
+ const backlogPath = path39.join(projectPath, ".coco", "planning", "backlog.json");
35557
35582
  const content = await fs35.readFile(backlogPath, "utf-8");
35558
35583
  const data = JSON.parse(content);
35559
35584
  return data.backlog;
@@ -35563,13 +35588,13 @@ async function loadBacklog(projectPath) {
35563
35588
  }
35564
35589
  async function loadSprint(projectPath, sprintId) {
35565
35590
  try {
35566
- const sprintsDir = path38.join(projectPath, ".coco", "planning", "sprints");
35591
+ const sprintsDir = path39.join(projectPath, ".coco", "planning", "sprints");
35567
35592
  const files = await fs35.readdir(sprintsDir);
35568
35593
  const jsonFiles = files.filter((f) => f.endsWith(".json"));
35569
35594
  if (jsonFiles.length === 0) return null;
35570
35595
  const targetFile = sprintId ? jsonFiles.find((f) => f.includes(sprintId)) : jsonFiles[0];
35571
35596
  if (!targetFile) return null;
35572
- const sprintPath = path38.join(sprintsDir, targetFile);
35597
+ const sprintPath = path39.join(sprintsDir, targetFile);
35573
35598
  const content = await fs35.readFile(sprintPath, "utf-8");
35574
35599
  return JSON.parse(content);
35575
35600
  } catch {
@@ -35577,7 +35602,7 @@ async function loadSprint(projectPath, sprintId) {
35577
35602
  }
35578
35603
  }
35579
35604
  async function saveBacklog(projectPath, backlog) {
35580
- const backlogPath = path38.join(projectPath, ".coco", "planning", "backlog.json");
35605
+ const backlogPath = path39.join(projectPath, ".coco", "planning", "backlog.json");
35581
35606
  const content = await fs35.readFile(backlogPath, "utf-8");
35582
35607
  const data = JSON.parse(content);
35583
35608
  data.backlog = backlog;
@@ -36287,7 +36312,7 @@ function getLevelName(level) {
36287
36312
  function displayMemoryFile(file) {
36288
36313
  const { emoji, color } = getLevelStyle(file.level);
36289
36314
  const levelName = getLevelName(file.level);
36290
- const fileName = path38__default.basename(file.path);
36315
+ const fileName = path39__default.basename(file.path);
36291
36316
  console.log();
36292
36317
  console.log(
36293
36318
  `${emoji} ${color.bold(levelName)} ${chalk.dim(`(${fileName})`)} ${chalk.dim(file.path)}`
@@ -36382,7 +36407,7 @@ var memoryCommand = {
36382
36407
  )
36383
36408
  );
36384
36409
  for (const f of reloaded) {
36385
- const fileName = path38__default.basename(f.path);
36410
+ const fileName = path39__default.basename(f.path);
36386
36411
  console.log(chalk.dim(` ${fileName} (${formatSize(f.content?.length ?? 0)})`));
36387
36412
  }
36388
36413
  } else {
@@ -37253,8 +37278,8 @@ var NPM_REGISTRY_URL = "https://registry.npmjs.org/@corbat-tech/coco/latest";
37253
37278
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
37254
37279
  var FETCH_TIMEOUT_MS = 5e3;
37255
37280
  var STARTUP_TIMEOUT_MS = 5500;
37256
- var CACHE_DIR = path38__default.join(os4__default.homedir(), ".coco");
37257
- var CACHE_FILE = path38__default.join(CACHE_DIR, "version-check-cache.json");
37281
+ var CACHE_DIR = path39__default.join(os4__default.homedir(), ".coco");
37282
+ var CACHE_FILE = path39__default.join(CACHE_DIR, "version-check-cache.json");
37258
37283
  function compareVersions(a, b) {
37259
37284
  const partsA = a.replace(/^v/, "").split(".").map((p45) => Number(p45.replace(/-.*$/, "")));
37260
37285
  const partsB = b.replace(/^v/, "").split(".").map((p45) => Number(p45.replace(/-.*$/, "")));
@@ -37596,7 +37621,7 @@ async function readClipboardImage() {
37596
37621
  return null;
37597
37622
  }
37598
37623
  async function readClipboardImageMacOS() {
37599
- const tmpFile = path38.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
37624
+ const tmpFile = path39.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
37600
37625
  try {
37601
37626
  const script = `
37602
37627
  set theFile to POSIX file "${tmpFile}"
@@ -37658,7 +37683,7 @@ async function readClipboardImageLinux() {
37658
37683
  }
37659
37684
  }
37660
37685
  async function readClipboardImageWindows() {
37661
- const tmpFile = path38.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
37686
+ const tmpFile = path39.join(os4.tmpdir(), `coco-clipboard-${Date.now()}.png`);
37662
37687
  try {
37663
37688
  const escapedPath = tmpFile.replace(/'/g, "''");
37664
37689
  const script = `
@@ -37783,7 +37808,7 @@ var allowPathCommand = {
37783
37808
  }
37784
37809
  };
37785
37810
  async function addPath(dirPath, session) {
37786
- const absolute = path38__default.resolve(dirPath);
37811
+ const absolute = path39__default.resolve(dirPath);
37787
37812
  try {
37788
37813
  const stat2 = await fs35__default.stat(absolute);
37789
37814
  if (!stat2.isDirectory()) {
@@ -37795,19 +37820,19 @@ async function addPath(dirPath, session) {
37795
37820
  return;
37796
37821
  }
37797
37822
  for (const blocked of BLOCKED_SYSTEM_PATHS) {
37798
- const normalizedBlocked = path38__default.normalize(blocked);
37799
- if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path38__default.sep)) {
37823
+ const normalizedBlocked = path39__default.normalize(blocked);
37824
+ if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path39__default.sep)) {
37800
37825
  p26.log.error(`System path '${blocked}' cannot be allowed`);
37801
37826
  return;
37802
37827
  }
37803
37828
  }
37804
- const normalizedCwd = path38__default.normalize(session.projectPath);
37805
- if (absolute === normalizedCwd || absolute.startsWith(normalizedCwd + path38__default.sep)) {
37829
+ const normalizedCwd = path39__default.normalize(session.projectPath);
37830
+ if (absolute === normalizedCwd || absolute.startsWith(normalizedCwd + path39__default.sep)) {
37806
37831
  p26.log.info("That path is already within the project directory");
37807
37832
  return;
37808
37833
  }
37809
37834
  const existing = getAllowedPaths();
37810
- if (existing.some((e) => path38__default.normalize(e.path) === path38__default.normalize(absolute))) {
37835
+ if (existing.some((e) => path39__default.normalize(e.path) === path39__default.normalize(absolute))) {
37811
37836
  p26.log.info(`Already allowed: ${absolute}`);
37812
37837
  return;
37813
37838
  }
@@ -37878,7 +37903,7 @@ async function revokePath(dirPath, _session) {
37878
37903
  }
37879
37904
  dirPath = selected;
37880
37905
  }
37881
- const absolute = path38__default.resolve(dirPath);
37906
+ const absolute = path39__default.resolve(dirPath);
37882
37907
  const removed = removeAllowedPathFromSession(absolute);
37883
37908
  await removePersistedAllowedPath(absolute);
37884
37909
  if (removed) {
@@ -38251,7 +38276,7 @@ async function savePermissionPreference(key, value) {
38251
38276
  } catch {
38252
38277
  }
38253
38278
  config[key] = value;
38254
- await fs35__default.mkdir(path38__default.dirname(CONFIG_PATHS.config), { recursive: true });
38279
+ await fs35__default.mkdir(path39__default.dirname(CONFIG_PATHS.config), { recursive: true });
38255
38280
  await fs35__default.writeFile(CONFIG_PATHS.config, JSON.stringify(config, null, 2), "utf-8");
38256
38281
  } catch {
38257
38282
  }
@@ -38968,15 +38993,20 @@ var tutorialCommand = {
38968
38993
  // src/cli/repl/commands/mcp.ts
38969
38994
  init_lifecycle();
38970
38995
  init_registry();
38996
+ init_config_loader();
38971
38997
  function formatLatency(ms) {
38972
38998
  if (ms < 1) return "<1ms";
38973
38999
  if (ms < 1e3) return `${Math.round(ms)}ms`;
38974
39000
  return `${(ms / 1e3).toFixed(1)}s`;
38975
39001
  }
38976
- async function listServers() {
39002
+ async function listServers(session) {
38977
39003
  const registry = new MCPRegistryImpl();
38978
39004
  await registry.load();
38979
- const servers = registry.listServers();
39005
+ const servers = mergeMCPConfigs(
39006
+ registry.listServers(),
39007
+ await loadMCPServersFromCOCOConfig(),
39008
+ await loadProjectMCPFile(session.projectPath)
39009
+ );
38980
39010
  const manager = getMCPServerManager();
38981
39011
  p26.intro("MCP Servers");
38982
39012
  if (servers.length === 0) {
@@ -39078,12 +39108,12 @@ var mcpCommand = {
39078
39108
  aliases: [],
39079
39109
  description: "Manage MCP servers (list, status, health, restart)",
39080
39110
  usage: "/mcp [list|status|health [name]|restart <name>]",
39081
- execute: async (args, _session) => {
39111
+ execute: async (args, session) => {
39082
39112
  const subcommand = args[0]?.toLowerCase() ?? "list";
39083
39113
  try {
39084
39114
  switch (subcommand) {
39085
39115
  case "list":
39086
- await listServers();
39116
+ await listServers(session);
39087
39117
  break;
39088
39118
  case "status":
39089
39119
  showStatus2();
@@ -39442,9 +39472,9 @@ Response format (JSON only, no prose):
39442
39472
  cancel5("Build cancelled.");
39443
39473
  }
39444
39474
  }
39445
- const cocoDir = path38__default.join(outputPath, ".coco");
39475
+ const cocoDir = path39__default.join(outputPath, ".coco");
39446
39476
  await fs35__default.mkdir(cocoDir, { recursive: true });
39447
- await fs35__default.writeFile(path38__default.join(cocoDir, "backlog.json"), JSON.stringify(spec, null, 2), "utf-8");
39477
+ await fs35__default.writeFile(path39__default.join(cocoDir, "backlog.json"), JSON.stringify(spec, null, 2), "utf-8");
39448
39478
  p26.outro(" Spec saved \u2014 starting sprints");
39449
39479
  return spec;
39450
39480
  }
@@ -39994,19 +40024,19 @@ init_errors();
39994
40024
  init_subprocess_registry();
39995
40025
  async function detectTestFramework2(cwd) {
39996
40026
  try {
39997
- await fs35__default.access(path38__default.join(cwd, "pom.xml"));
40027
+ await fs35__default.access(path39__default.join(cwd, "pom.xml"));
39998
40028
  return "maven";
39999
40029
  } catch {
40000
40030
  }
40001
40031
  for (const gradleFile of ["build.gradle", "build.gradle.kts"]) {
40002
40032
  try {
40003
- await fs35__default.access(path38__default.join(cwd, gradleFile));
40033
+ await fs35__default.access(path39__default.join(cwd, gradleFile));
40004
40034
  return "gradle";
40005
40035
  } catch {
40006
40036
  }
40007
40037
  }
40008
40038
  try {
40009
- const pkgPath = path38__default.join(cwd, "package.json");
40039
+ const pkgPath = path39__default.join(cwd, "package.json");
40010
40040
  const pkgContent = await fs35__default.readFile(pkgPath, "utf-8");
40011
40041
  const pkg = JSON.parse(pkgContent);
40012
40042
  const deps = {
@@ -40023,16 +40053,16 @@ async function detectTestFramework2(cwd) {
40023
40053
  }
40024
40054
  }
40025
40055
  function toMavenTestFilter(pattern) {
40026
- const base = path38__default.basename(pattern).replace(/\.java$/, "");
40056
+ const base = path39__default.basename(pattern).replace(/\.java$/, "");
40027
40057
  return base;
40028
40058
  }
40029
40059
  function toGradleTestFilter(pattern) {
40030
- const base = path38__default.basename(pattern).replace(/\.java$/, "");
40060
+ const base = path39__default.basename(pattern).replace(/\.java$/, "");
40031
40061
  return `*${base}`;
40032
40062
  }
40033
40063
  async function mavenExecutable(cwd) {
40034
40064
  try {
40035
- await fs35__default.access(path38__default.join(cwd, "mvnw"));
40065
+ await fs35__default.access(path39__default.join(cwd, "mvnw"));
40036
40066
  return "./mvnw";
40037
40067
  } catch {
40038
40068
  return "mvn";
@@ -40040,7 +40070,7 @@ async function mavenExecutable(cwd) {
40040
40070
  }
40041
40071
  async function gradleExecutable(cwd) {
40042
40072
  try {
40043
- await fs35__default.access(path38__default.join(cwd, "gradlew"));
40073
+ await fs35__default.access(path39__default.join(cwd, "gradlew"));
40044
40074
  return "./gradlew";
40045
40075
  } catch {
40046
40076
  return "gradle";
@@ -40281,14 +40311,14 @@ Examples:
40281
40311
  const projectDir = cwd ?? process.cwd();
40282
40312
  try {
40283
40313
  const coverageLocations = [
40284
- path38__default.join(projectDir, "coverage", "coverage-summary.json"),
40285
- path38__default.join(projectDir, "coverage", "coverage-final.json"),
40286
- path38__default.join(projectDir, ".nyc_output", "coverage-summary.json"),
40314
+ path39__default.join(projectDir, "coverage", "coverage-summary.json"),
40315
+ path39__default.join(projectDir, "coverage", "coverage-final.json"),
40316
+ path39__default.join(projectDir, ".nyc_output", "coverage-summary.json"),
40287
40317
  // Maven JaCoCo
40288
- path38__default.join(projectDir, "target", "site", "jacoco", "jacoco.csv"),
40289
- path38__default.join(projectDir, "target", "site", "jacoco-ut", "jacoco.csv"),
40318
+ path39__default.join(projectDir, "target", "site", "jacoco", "jacoco.csv"),
40319
+ path39__default.join(projectDir, "target", "site", "jacoco-ut", "jacoco.csv"),
40290
40320
  // Gradle JaCoCo
40291
- path38__default.join(projectDir, "build", "reports", "jacoco", "test", "jacocoTestReport.csv")
40321
+ path39__default.join(projectDir, "build", "reports", "jacoco", "test", "jacocoTestReport.csv")
40292
40322
  ];
40293
40323
  for (const location of coverageLocations) {
40294
40324
  try {
@@ -40401,7 +40431,7 @@ async function findFileRecursive(rootDir, target, options = {}) {
40401
40431
  const results = [];
40402
40432
  const startTime = Date.now();
40403
40433
  const isTimedOut = () => Date.now() - startTime > opts.timeoutMs;
40404
- const queue = [[path38__default.resolve(rootDir), 0]];
40434
+ const queue = [[path39__default.resolve(rootDir), 0]];
40405
40435
  const visited = /* @__PURE__ */ new Set();
40406
40436
  while (queue.length > 0 && results.length < opts.maxResults) {
40407
40437
  if (isTimedOut()) break;
@@ -40414,7 +40444,7 @@ async function findFileRecursive(rootDir, target, options = {}) {
40414
40444
  for (const entry of entries) {
40415
40445
  if (isTimedOut()) break;
40416
40446
  const entryName = entry.name;
40417
- const entryPath = path38__default.join(currentDir, entryName);
40447
+ const entryPath = path39__default.join(currentDir, entryName);
40418
40448
  if (!opts.includeHidden && entryName.startsWith(".")) continue;
40419
40449
  if (entry.isDirectory() && opts.excludeDirs.has(entryName)) continue;
40420
40450
  const isMatch = opts.type === "file" && entry.isFile() || opts.type === "directory" && entry.isDirectory() || opts.type === "both";
@@ -40448,19 +40478,19 @@ async function suggestSimilarFilesDeep(missingPath, rootDir = process.cwd(), opt
40448
40478
  if (fastResults.length > 0) {
40449
40479
  return fastResults;
40450
40480
  }
40451
- const absPath = path38__default.resolve(missingPath);
40452
- const target = path38__default.basename(absPath);
40481
+ const absPath = path39__default.resolve(missingPath);
40482
+ const target = path39__default.basename(absPath);
40453
40483
  return findFileRecursive(rootDir, target, options);
40454
40484
  }
40455
40485
  async function suggestSimilarDirsDeep(missingPath, rootDir = process.cwd(), options) {
40456
- const absPath = path38__default.resolve(missingPath);
40457
- const target = path38__default.basename(absPath);
40458
- const parentDir = path38__default.dirname(absPath);
40486
+ const absPath = path39__default.resolve(missingPath);
40487
+ const target = path39__default.basename(absPath);
40488
+ const parentDir = path39__default.dirname(absPath);
40459
40489
  try {
40460
40490
  const entries = await fs35__default.readdir(parentDir, { withFileTypes: true });
40461
40491
  const dirs = entries.filter((e) => e.isDirectory());
40462
40492
  const scored = dirs.map((d) => ({
40463
- path: path38__default.join(parentDir, d.name),
40493
+ path: path39__default.join(parentDir, d.name),
40464
40494
  distance: levenshtein(target.toLowerCase(), d.name.toLowerCase())
40465
40495
  })).filter((s) => s.distance <= Math.max(target.length * 0.6, 3)).sort((a, b) => a.distance - b.distance).slice(0, options?.maxResults ?? MAX_SUGGESTIONS);
40466
40496
  if (scored.length > 0) {
@@ -40471,15 +40501,15 @@ async function suggestSimilarDirsDeep(missingPath, rootDir = process.cwd(), opti
40471
40501
  return findFileRecursive(rootDir, target, { ...options, type: "directory" });
40472
40502
  }
40473
40503
  async function suggestSimilarFiles(missingPath, options) {
40474
- const absPath = path38__default.resolve(missingPath);
40475
- const dir = path38__default.dirname(absPath);
40476
- const target = path38__default.basename(absPath);
40504
+ const absPath = path39__default.resolve(missingPath);
40505
+ const dir = path39__default.dirname(absPath);
40506
+ const target = path39__default.basename(absPath);
40477
40507
  const maxResults = options?.maxResults;
40478
40508
  try {
40479
40509
  const entries = await fs35__default.readdir(dir);
40480
40510
  const limited = entries.slice(0, MAX_DIR_ENTRIES);
40481
40511
  const scored = limited.map((name) => ({
40482
- path: path38__default.join(dir, name),
40512
+ path: path39__default.join(dir, name),
40483
40513
  distance: levenshtein(target.toLowerCase(), name.toLowerCase())
40484
40514
  })).filter((s) => s.distance <= Math.max(target.length * 0.6, 3)).sort((a, b) => a.distance - b.distance);
40485
40515
  return scored.slice(0, maxResults);
@@ -40491,7 +40521,7 @@ function formatSuggestions(suggestions, baseDir) {
40491
40521
  if (suggestions.length === 0) return "";
40492
40522
  const base = baseDir ?? process.cwd();
40493
40523
  const lines = suggestions.map((s) => {
40494
- const rel = path38__default.relative(base, s.path);
40524
+ const rel = path39__default.relative(base, s.path);
40495
40525
  return ` - ${rel}`;
40496
40526
  });
40497
40527
  return `
@@ -40520,44 +40550,85 @@ var SENSITIVE_PATTERNS = [
40520
40550
  // PyPI auth
40521
40551
  ];
40522
40552
  var BLOCKED_PATHS2 = ["/etc", "/var", "/usr", "/root", "/sys", "/proc", "/boot"];
40553
+ var SAFE_COCO_HOME_READ_FILES = /* @__PURE__ */ new Set([
40554
+ "mcp.json",
40555
+ "config.json",
40556
+ "COCO.md",
40557
+ "AGENTS.md",
40558
+ "CLAUDE.md",
40559
+ "projects.json",
40560
+ "trusted-tools.json",
40561
+ "allowed-paths.json"
40562
+ ]);
40563
+ var SAFE_COCO_HOME_READ_DIR_PREFIXES = ["skills", "memories", "logs", "checkpoints", "sessions"];
40523
40564
  function hasNullByte2(str) {
40524
40565
  return str.includes("\0");
40525
40566
  }
40526
40567
  function normalizePath2(filePath) {
40527
40568
  let normalized = filePath.replace(/\0/g, "");
40528
- normalized = path38__default.normalize(normalized);
40569
+ normalized = path39__default.normalize(normalized);
40529
40570
  return normalized;
40530
40571
  }
40572
+ function isWithinDirectory(targetPath, baseDir) {
40573
+ const normalizedTarget = path39__default.normalize(targetPath);
40574
+ const normalizedBase = path39__default.normalize(baseDir);
40575
+ return normalizedTarget === normalizedBase || normalizedTarget.startsWith(normalizedBase + path39__default.sep);
40576
+ }
40577
+ function isSafeCocoHomeReadPath(absolutePath, homeDir) {
40578
+ const cocoHome = path39__default.join(homeDir, ".coco");
40579
+ if (!isWithinDirectory(absolutePath, cocoHome)) {
40580
+ return false;
40581
+ }
40582
+ const relativePath = path39__default.relative(cocoHome, absolutePath);
40583
+ if (!relativePath || relativePath.startsWith("..")) {
40584
+ return false;
40585
+ }
40586
+ const segments = relativePath.split(path39__default.sep).filter(Boolean);
40587
+ const firstSegment = segments[0];
40588
+ if (!firstSegment) {
40589
+ return false;
40590
+ }
40591
+ if (firstSegment === "tokens" || firstSegment === ".env") {
40592
+ return false;
40593
+ }
40594
+ if (segments.length === 1 && SAFE_COCO_HOME_READ_FILES.has(firstSegment)) {
40595
+ return true;
40596
+ }
40597
+ return SAFE_COCO_HOME_READ_DIR_PREFIXES.includes(firstSegment);
40598
+ }
40531
40599
  function isPathAllowed(filePath, operation) {
40532
40600
  if (hasNullByte2(filePath)) {
40533
40601
  return { allowed: false, reason: "Path contains invalid characters" };
40534
40602
  }
40535
40603
  const normalized = normalizePath2(filePath);
40536
- const absolute = path38__default.resolve(normalized);
40604
+ const absolute = path39__default.resolve(normalized);
40537
40605
  const cwd = process.cwd();
40538
40606
  for (const blocked of BLOCKED_PATHS2) {
40539
- const normalizedBlocked = path38__default.normalize(blocked);
40540
- if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path38__default.sep)) {
40607
+ const normalizedBlocked = path39__default.normalize(blocked);
40608
+ if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path39__default.sep)) {
40541
40609
  return { allowed: false, reason: `Access to system path '${blocked}' is not allowed` };
40542
40610
  }
40543
40611
  }
40544
40612
  const home = process.env.HOME;
40545
40613
  if (home) {
40546
- const normalizedHome = path38__default.normalize(home);
40547
- const normalizedCwd = path38__default.normalize(cwd);
40614
+ const normalizedHome = path39__default.normalize(home);
40615
+ const normalizedCwd = path39__default.normalize(cwd);
40548
40616
  if (absolute.startsWith(normalizedHome) && !absolute.startsWith(normalizedCwd)) {
40549
40617
  if (isWithinAllowedPath(absolute, operation)) ; else if (operation === "read") {
40618
+ if (isSafeCocoHomeReadPath(absolute, normalizedHome)) {
40619
+ return { allowed: true };
40620
+ }
40550
40621
  const allowedHomeReads = [".gitconfig", ".zshrc", ".bashrc"];
40551
- const basename4 = path38__default.basename(absolute);
40622
+ const basename4 = path39__default.basename(absolute);
40552
40623
  if (!allowedHomeReads.includes(basename4)) {
40553
- const targetDir = path38__default.dirname(absolute);
40624
+ const targetDir = path39__default.dirname(absolute);
40554
40625
  return {
40555
40626
  allowed: false,
40556
40627
  reason: `Reading files outside project directory is not allowed. Use /allow-path ${targetDir} to grant access.`
40557
40628
  };
40558
40629
  }
40559
40630
  } else {
40560
- const targetDir = path38__default.dirname(absolute);
40631
+ const targetDir = path39__default.dirname(absolute);
40561
40632
  return {
40562
40633
  allowed: false,
40563
40634
  reason: `${operation} operations outside project directory are not allowed. Use /allow-path ${targetDir} to grant access.`
@@ -40566,7 +40637,7 @@ function isPathAllowed(filePath, operation) {
40566
40637
  }
40567
40638
  }
40568
40639
  if (operation === "write" || operation === "delete") {
40569
- const basename4 = path38__default.basename(absolute);
40640
+ const basename4 = path39__default.basename(absolute);
40570
40641
  for (const pattern of SENSITIVE_PATTERNS) {
40571
40642
  if (pattern.test(basename4)) {
40572
40643
  return {
@@ -40589,17 +40660,17 @@ function isENOENT(error) {
40589
40660
  return error.code === "ENOENT";
40590
40661
  }
40591
40662
  async function enrichENOENT(filePath, operation) {
40592
- const absPath = path38__default.resolve(filePath);
40663
+ const absPath = path39__default.resolve(filePath);
40593
40664
  const suggestions = await suggestSimilarFilesDeep(absPath, process.cwd());
40594
- const hint = formatSuggestions(suggestions, path38__default.dirname(absPath));
40665
+ const hint = formatSuggestions(suggestions, path39__default.dirname(absPath));
40595
40666
  const action = operation === "read" ? "Use glob or list_dir to find the correct path." : "Check that the parent directory exists.";
40596
40667
  return `File not found: ${filePath}${hint}
40597
40668
  ${action}`;
40598
40669
  }
40599
40670
  async function enrichDirENOENT(dirPath) {
40600
- const absPath = path38__default.resolve(dirPath);
40671
+ const absPath = path39__default.resolve(dirPath);
40601
40672
  const suggestions = await suggestSimilarDirsDeep(absPath, process.cwd());
40602
- const hint = formatSuggestions(suggestions, path38__default.dirname(absPath));
40673
+ const hint = formatSuggestions(suggestions, path39__default.dirname(absPath));
40603
40674
  return `Directory not found: ${dirPath}${hint}
40604
40675
  Use list_dir or glob to find the correct path.`;
40605
40676
  }
@@ -40620,7 +40691,7 @@ Examples:
40620
40691
  async execute({ path: filePath, encoding, maxSize }) {
40621
40692
  validatePath(filePath, "read");
40622
40693
  try {
40623
- const absolutePath = path38__default.resolve(filePath);
40694
+ const absolutePath = path39__default.resolve(filePath);
40624
40695
  const stats = await fs35__default.stat(absolutePath);
40625
40696
  const maxBytes = maxSize ?? DEFAULT_MAX_FILE_SIZE;
40626
40697
  let truncated = false;
@@ -40679,7 +40750,7 @@ Examples:
40679
40750
  async execute({ path: filePath, content, createDirs, dryRun }) {
40680
40751
  validatePath(filePath, "write");
40681
40752
  try {
40682
- const absolutePath = path38__default.resolve(filePath);
40753
+ const absolutePath = path39__default.resolve(filePath);
40683
40754
  let wouldCreate = false;
40684
40755
  try {
40685
40756
  await fs35__default.access(absolutePath);
@@ -40695,7 +40766,7 @@ Examples:
40695
40766
  };
40696
40767
  }
40697
40768
  if (createDirs) {
40698
- await fs35__default.mkdir(path38__default.dirname(absolutePath), { recursive: true });
40769
+ await fs35__default.mkdir(path39__default.dirname(absolutePath), { recursive: true });
40699
40770
  }
40700
40771
  await fs35__default.writeFile(absolutePath, content, "utf-8");
40701
40772
  const stats = await fs35__default.stat(absolutePath);
@@ -40741,7 +40812,7 @@ Examples:
40741
40812
  async execute({ path: filePath, oldText, newText, all, dryRun }) {
40742
40813
  validatePath(filePath, "write");
40743
40814
  try {
40744
- const absolutePath = path38__default.resolve(filePath);
40815
+ const absolutePath = path39__default.resolve(filePath);
40745
40816
  let content = await fs35__default.readFile(absolutePath, "utf-8");
40746
40817
  let replacements = 0;
40747
40818
  if (all) {
@@ -40862,7 +40933,7 @@ Examples:
40862
40933
  }),
40863
40934
  async execute({ path: filePath }) {
40864
40935
  try {
40865
- const absolutePath = path38__default.resolve(filePath);
40936
+ const absolutePath = path39__default.resolve(filePath);
40866
40937
  const stats = await fs35__default.stat(absolutePath);
40867
40938
  return {
40868
40939
  exists: true,
@@ -40893,12 +40964,12 @@ Examples:
40893
40964
  }),
40894
40965
  async execute({ path: dirPath, recursive }) {
40895
40966
  try {
40896
- const absolutePath = path38__default.resolve(dirPath);
40967
+ const absolutePath = path39__default.resolve(dirPath);
40897
40968
  const entries = [];
40898
40969
  async function listDir(dir, prefix = "") {
40899
40970
  const items = await fs35__default.readdir(dir, { withFileTypes: true });
40900
40971
  for (const item of items) {
40901
- const fullPath = path38__default.join(dir, item.name);
40972
+ const fullPath = path39__default.join(dir, item.name);
40902
40973
  const relativePath = prefix ? `${prefix}/${item.name}` : item.name;
40903
40974
  if (item.isDirectory()) {
40904
40975
  entries.push({ name: relativePath, type: "directory" });
@@ -40953,7 +41024,7 @@ Examples:
40953
41024
  }
40954
41025
  validatePath(filePath, "delete");
40955
41026
  try {
40956
- const absolutePath = path38__default.resolve(filePath);
41027
+ const absolutePath = path39__default.resolve(filePath);
40957
41028
  const stats = await fs35__default.stat(absolutePath);
40958
41029
  if (stats.isDirectory()) {
40959
41030
  if (!recursive) {
@@ -40969,7 +41040,7 @@ Examples:
40969
41040
  } catch (error) {
40970
41041
  if (error instanceof ToolError) throw error;
40971
41042
  if (error.code === "ENOENT") {
40972
- return { deleted: false, path: path38__default.resolve(filePath) };
41043
+ return { deleted: false, path: path39__default.resolve(filePath) };
40973
41044
  }
40974
41045
  throw new FileSystemError(`Failed to delete: ${filePath}`, {
40975
41046
  path: filePath,
@@ -40997,8 +41068,8 @@ Examples:
40997
41068
  validatePath(source, "read");
40998
41069
  validatePath(destination, "write");
40999
41070
  try {
41000
- const srcPath = path38__default.resolve(source);
41001
- const destPath = path38__default.resolve(destination);
41071
+ const srcPath = path39__default.resolve(source);
41072
+ const destPath = path39__default.resolve(destination);
41002
41073
  if (!overwrite) {
41003
41074
  try {
41004
41075
  await fs35__default.access(destPath);
@@ -41014,7 +41085,7 @@ Examples:
41014
41085
  }
41015
41086
  }
41016
41087
  }
41017
- await fs35__default.mkdir(path38__default.dirname(destPath), { recursive: true });
41088
+ await fs35__default.mkdir(path39__default.dirname(destPath), { recursive: true });
41018
41089
  await fs35__default.copyFile(srcPath, destPath);
41019
41090
  const stats = await fs35__default.stat(destPath);
41020
41091
  return {
@@ -41058,8 +41129,8 @@ Examples:
41058
41129
  validatePath(source, "delete");
41059
41130
  validatePath(destination, "write");
41060
41131
  try {
41061
- const srcPath = path38__default.resolve(source);
41062
- const destPath = path38__default.resolve(destination);
41132
+ const srcPath = path39__default.resolve(source);
41133
+ const destPath = path39__default.resolve(destination);
41063
41134
  if (!overwrite) {
41064
41135
  try {
41065
41136
  await fs35__default.access(destPath);
@@ -41075,7 +41146,7 @@ Examples:
41075
41146
  }
41076
41147
  }
41077
41148
  }
41078
- await fs35__default.mkdir(path38__default.dirname(destPath), { recursive: true });
41149
+ await fs35__default.mkdir(path39__default.dirname(destPath), { recursive: true });
41079
41150
  await fs35__default.rename(srcPath, destPath);
41080
41151
  return {
41081
41152
  source: srcPath,
@@ -41145,10 +41216,10 @@ Examples:
41145
41216
  }),
41146
41217
  async execute({ path: dirPath, depth, showHidden, dirsOnly }) {
41147
41218
  try {
41148
- const absolutePath = path38__default.resolve(dirPath ?? ".");
41219
+ const absolutePath = path39__default.resolve(dirPath ?? ".");
41149
41220
  let totalFiles = 0;
41150
41221
  let totalDirs = 0;
41151
- const lines = [path38__default.basename(absolutePath) + "/"];
41222
+ const lines = [path39__default.basename(absolutePath) + "/"];
41152
41223
  let truncated = false;
41153
41224
  async function buildTree(dir, prefix, currentDepth) {
41154
41225
  if (currentDepth > (depth ?? 4)) return;
@@ -41178,7 +41249,7 @@ Examples:
41178
41249
  if (item.isDirectory()) {
41179
41250
  totalDirs++;
41180
41251
  lines.push(`${prefix}${connector}${item.name}/`);
41181
- await buildTree(path38__default.join(dir, item.name), prefix + childPrefix, currentDepth + 1);
41252
+ await buildTree(path39__default.join(dir, item.name), prefix + childPrefix, currentDepth + 1);
41182
41253
  } else {
41183
41254
  totalFiles++;
41184
41255
  lines.push(`${prefix}${connector}${item.name}`);
@@ -42446,7 +42517,7 @@ Examples:
42446
42517
  caseSensitive,
42447
42518
  wholeWord
42448
42519
  }) {
42449
- const targetPath = searchPath ? path38__default.resolve(searchPath) : process.cwd();
42520
+ const targetPath = searchPath ? path39__default.resolve(searchPath) : process.cwd();
42450
42521
  const matches = [];
42451
42522
  let filesSearched = 0;
42452
42523
  const filesWithMatches = /* @__PURE__ */ new Set();
@@ -42513,7 +42584,7 @@ Examples:
42513
42584
  contextAfter.push(lines[j] ?? "");
42514
42585
  }
42515
42586
  matches.push({
42516
- file: path38__default.relative(process.cwd(), file),
42587
+ file: path39__default.relative(process.cwd(), file),
42517
42588
  line: i + 1,
42518
42589
  column: match.index + 1,
42519
42590
  content: line,
@@ -42564,7 +42635,7 @@ Examples:
42564
42635
  }),
42565
42636
  async execute({ file, pattern, caseSensitive }) {
42566
42637
  try {
42567
- const absolutePath = path38__default.resolve(file);
42638
+ const absolutePath = path39__default.resolve(file);
42568
42639
  const content = await fs35__default.readFile(absolutePath, "utf-8");
42569
42640
  const lines = content.split("\n");
42570
42641
  const matches = [];
@@ -42583,7 +42654,7 @@ Examples:
42583
42654
  } catch (error) {
42584
42655
  if (error.code === "ENOENT") {
42585
42656
  const suggestions = await suggestSimilarFilesDeep(file, process.cwd());
42586
- const hint = formatSuggestions(suggestions, path38__default.dirname(file));
42657
+ const hint = formatSuggestions(suggestions, path39__default.dirname(file));
42587
42658
  throw new ToolError(`File not found: ${file}${hint}
42588
42659
  Use glob to find the correct path.`, {
42589
42660
  tool: "find_in_file"
@@ -42774,7 +42845,7 @@ async function detectPackageManager2(cwd) {
42774
42845
  ];
42775
42846
  for (const { file, pm } of lockfiles) {
42776
42847
  try {
42777
- await fs35__default.access(path38__default.join(cwd, file));
42848
+ await fs35__default.access(path39__default.join(cwd, file));
42778
42849
  return pm;
42779
42850
  } catch {
42780
42851
  }
@@ -43047,7 +43118,7 @@ ${message}
43047
43118
  });
43048
43119
  try {
43049
43120
  try {
43050
- await fs35__default.access(path38__default.join(projectDir, "Makefile"));
43121
+ await fs35__default.access(path39__default.join(projectDir, "Makefile"));
43051
43122
  } catch {
43052
43123
  throw new ToolError("No Makefile found in directory", { tool: "make" });
43053
43124
  }
@@ -43217,7 +43288,7 @@ ${message}
43217
43288
  });
43218
43289
  async function resolveMaven(cwd) {
43219
43290
  try {
43220
- await fs35__default.access(path38__default.join(cwd, "mvnw"));
43291
+ await fs35__default.access(path39__default.join(cwd, "mvnw"));
43221
43292
  return "./mvnw";
43222
43293
  } catch {
43223
43294
  return "mvn";
@@ -43225,7 +43296,7 @@ async function resolveMaven(cwd) {
43225
43296
  }
43226
43297
  async function resolveGradle(cwd) {
43227
43298
  try {
43228
- await fs35__default.access(path38__default.join(cwd, "gradlew"));
43299
+ await fs35__default.access(path39__default.join(cwd, "gradlew"));
43229
43300
  return "./gradlew";
43230
43301
  } catch {
43231
43302
  return "gradle";
@@ -44093,7 +44164,7 @@ init_review();
44093
44164
  init_registry4();
44094
44165
  init_errors();
44095
44166
  var fs38 = await import('fs/promises');
44096
- var path41 = await import('path');
44167
+ var path42 = await import('path');
44097
44168
  var { glob: glob14 } = await import('glob');
44098
44169
  var DEFAULT_MAX_FILES = 200;
44099
44170
  var LANGUAGE_EXTENSIONS = {
@@ -44119,7 +44190,7 @@ var DEFAULT_EXCLUDES = [
44119
44190
  "**/*.d.ts"
44120
44191
  ];
44121
44192
  function detectLanguage3(filePath) {
44122
- const ext = path41.extname(filePath).toLowerCase();
44193
+ const ext = path42.extname(filePath).toLowerCase();
44123
44194
  for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
44124
44195
  if (extensions.includes(ext)) return lang;
44125
44196
  }
@@ -44528,7 +44599,7 @@ Examples:
44528
44599
  }),
44529
44600
  async execute({ path: rootPath, include, exclude, languages, maxFiles, depth }) {
44530
44601
  const startTime = performance.now();
44531
- const absPath = path41.resolve(rootPath);
44602
+ const absPath = path42.resolve(rootPath);
44532
44603
  try {
44533
44604
  const stat2 = await fs38.stat(absPath);
44534
44605
  if (!stat2.isDirectory()) {
@@ -44567,7 +44638,7 @@ Examples:
44567
44638
  let totalDefinitions = 0;
44568
44639
  let exportedSymbols = 0;
44569
44640
  for (const file of limitedFiles) {
44570
- const fullPath = path41.join(absPath, file);
44641
+ const fullPath = path42.join(absPath, file);
44571
44642
  const language = detectLanguage3(file);
44572
44643
  if (!language) continue;
44573
44644
  if (languages && !languages.includes(language)) {
@@ -44612,9 +44683,9 @@ init_registry4();
44612
44683
  init_errors();
44613
44684
  init_paths();
44614
44685
  var fs39 = await import('fs/promises');
44615
- var path42 = await import('path');
44686
+ var path43 = await import('path');
44616
44687
  var crypto2 = await import('crypto');
44617
- var GLOBAL_MEMORIES_DIR = path42.join(COCO_HOME, "memories");
44688
+ var GLOBAL_MEMORIES_DIR = path43.join(COCO_HOME, "memories");
44618
44689
  var PROJECT_MEMORIES_DIR = ".coco/memories";
44619
44690
  var DEFAULT_MAX_MEMORIES = 1e3;
44620
44691
  async function ensureDir2(dirPath) {
@@ -44625,7 +44696,7 @@ function getMemoriesDir(scope) {
44625
44696
  }
44626
44697
  async function loadIndex(scope) {
44627
44698
  const dir = getMemoriesDir(scope);
44628
- const indexPath = path42.join(dir, "index.json");
44699
+ const indexPath = path43.join(dir, "index.json");
44629
44700
  try {
44630
44701
  const content = await fs39.readFile(indexPath, "utf-8");
44631
44702
  return JSON.parse(content);
@@ -44636,12 +44707,12 @@ async function loadIndex(scope) {
44636
44707
  async function saveIndex(scope, index) {
44637
44708
  const dir = getMemoriesDir(scope);
44638
44709
  await ensureDir2(dir);
44639
- const indexPath = path42.join(dir, "index.json");
44710
+ const indexPath = path43.join(dir, "index.json");
44640
44711
  await fs39.writeFile(indexPath, JSON.stringify(index, null, 2), "utf-8");
44641
44712
  }
44642
44713
  async function loadMemory(scope, id) {
44643
44714
  const dir = getMemoriesDir(scope);
44644
- const memPath = path42.join(dir, `${id}.json`);
44715
+ const memPath = path43.join(dir, `${id}.json`);
44645
44716
  try {
44646
44717
  const content = await fs39.readFile(memPath, "utf-8");
44647
44718
  return JSON.parse(content);
@@ -44652,7 +44723,7 @@ async function loadMemory(scope, id) {
44652
44723
  async function saveMemory(scope, memory) {
44653
44724
  const dir = getMemoriesDir(scope);
44654
44725
  await ensureDir2(dir);
44655
- const memPath = path42.join(dir, `${memory.id}.json`);
44726
+ const memPath = path43.join(dir, `${memory.id}.json`);
44656
44727
  await fs39.writeFile(memPath, JSON.stringify(memory, null, 2), "utf-8");
44657
44728
  }
44658
44729
  var createMemoryTool = defineTool({
@@ -44993,7 +45064,7 @@ var checkpointTools = [createCheckpointTool, restoreCheckpointTool, listCheckpoi
44993
45064
  // src/tools/semantic-search.ts
44994
45065
  init_registry4();
44995
45066
  var fs41 = await import('fs/promises');
44996
- var path43 = await import('path');
45067
+ var path44 = await import('path');
44997
45068
  var { glob: glob15 } = await import('glob');
44998
45069
  var INDEX_DIR = ".coco/search-index";
44999
45070
  var DEFAULT_CHUNK_SIZE = 20;
@@ -45121,7 +45192,7 @@ async function getEmbedding(text13) {
45121
45192
  }
45122
45193
  async function loadIndex2(indexDir) {
45123
45194
  try {
45124
- const indexPath = path43.join(indexDir, "index.json");
45195
+ const indexPath = path44.join(indexDir, "index.json");
45125
45196
  const content = await fs41.readFile(indexPath, "utf-8");
45126
45197
  return JSON.parse(content);
45127
45198
  } catch {
@@ -45130,11 +45201,11 @@ async function loadIndex2(indexDir) {
45130
45201
  }
45131
45202
  async function saveIndex2(indexDir, index) {
45132
45203
  await fs41.mkdir(indexDir, { recursive: true });
45133
- const indexPath = path43.join(indexDir, "index.json");
45204
+ const indexPath = path44.join(indexDir, "index.json");
45134
45205
  await fs41.writeFile(indexPath, JSON.stringify(index), "utf-8");
45135
45206
  }
45136
45207
  function isBinary(filePath) {
45137
- return BINARY_EXTENSIONS.has(path43.extname(filePath).toLowerCase());
45208
+ return BINARY_EXTENSIONS.has(path44.extname(filePath).toLowerCase());
45138
45209
  }
45139
45210
  var semanticSearchTool = defineTool({
45140
45211
  name: "semantic_search",
@@ -45159,8 +45230,8 @@ Examples:
45159
45230
  const effectivePath = rootPath ?? ".";
45160
45231
  const effectiveMaxResults = maxResults ?? 10;
45161
45232
  const effectiveThreshold = threshold ?? 0.3;
45162
- const absPath = path43.resolve(effectivePath);
45163
- const indexDir = path43.join(absPath, INDEX_DIR);
45233
+ const absPath = path44.resolve(effectivePath);
45234
+ const indexDir = path44.join(absPath, INDEX_DIR);
45164
45235
  let index = reindex ? null : await loadIndex2(indexDir);
45165
45236
  let warnings = [];
45166
45237
  if (!index) {
@@ -45176,7 +45247,7 @@ Examples:
45176
45247
  let indexSaveWarning = "";
45177
45248
  for (const file of files) {
45178
45249
  if (isBinary(file)) continue;
45179
- const fullPath = path43.join(absPath, file);
45250
+ const fullPath = path44.join(absPath, file);
45180
45251
  try {
45181
45252
  const stat2 = await fs41.stat(fullPath);
45182
45253
  const content = await fs41.readFile(fullPath, "utf-8");
@@ -45260,7 +45331,7 @@ var semanticSearchTools = [semanticSearchTool];
45260
45331
  init_registry4();
45261
45332
  init_errors();
45262
45333
  var fs42 = await import('fs/promises');
45263
- var path44 = await import('path');
45334
+ var path45 = await import('path');
45264
45335
  var { glob: glob16 } = await import('glob');
45265
45336
  async function parseClassRelationships(rootPath, include) {
45266
45337
  const pattern = include ?? "**/*.{ts,tsx,js,jsx}";
@@ -45273,7 +45344,7 @@ async function parseClassRelationships(rootPath, include) {
45273
45344
  const interfaces = [];
45274
45345
  for (const file of files.slice(0, 100)) {
45275
45346
  try {
45276
- const content = await fs42.readFile(path44.join(rootPath, file), "utf-8");
45347
+ const content = await fs42.readFile(path45.join(rootPath, file), "utf-8");
45277
45348
  const lines = content.split("\n");
45278
45349
  for (let i = 0; i < lines.length; i++) {
45279
45350
  const line = lines[i];
@@ -45399,7 +45470,7 @@ async function generateArchitectureDiagram(rootPath) {
45399
45470
  const lines = ["graph TD"];
45400
45471
  let nodeCount = 0;
45401
45472
  let edgeCount = 0;
45402
- const rootName = path44.basename(rootPath);
45473
+ const rootName = path45.basename(rootPath);
45403
45474
  lines.push(` ROOT["${rootName}"]`);
45404
45475
  nodeCount++;
45405
45476
  for (const dir of dirs) {
@@ -45409,7 +45480,7 @@ async function generateArchitectureDiagram(rootPath) {
45409
45480
  nodeCount++;
45410
45481
  edgeCount++;
45411
45482
  try {
45412
- const subEntries = await fs42.readdir(path44.join(rootPath, dir.name), {
45483
+ const subEntries = await fs42.readdir(path45.join(rootPath, dir.name), {
45413
45484
  withFileTypes: true
45414
45485
  });
45415
45486
  const subDirs = subEntries.filter(
@@ -45532,7 +45603,7 @@ Examples:
45532
45603
  tool: "generate_diagram"
45533
45604
  });
45534
45605
  }
45535
- const absPath = rootPath ? path44.resolve(rootPath) : process.cwd();
45606
+ const absPath = rootPath ? path45.resolve(rootPath) : process.cwd();
45536
45607
  switch (type) {
45537
45608
  case "class":
45538
45609
  return generateClassDiagram(absPath, include);
@@ -45598,7 +45669,7 @@ var diagramTools = [generateDiagramTool];
45598
45669
  init_registry4();
45599
45670
  init_errors();
45600
45671
  var fs43 = await import('fs/promises');
45601
- var path45 = await import('path');
45672
+ var path46 = await import('path');
45602
45673
  var DEFAULT_MAX_PAGES = 20;
45603
45674
  var MAX_FILE_SIZE = 50 * 1024 * 1024;
45604
45675
  function parsePageRange(rangeStr, totalPages) {
@@ -45633,7 +45704,7 @@ Examples:
45633
45704
  }),
45634
45705
  async execute({ path: filePath, pages, maxPages }) {
45635
45706
  const startTime = performance.now();
45636
- const absPath = path45.resolve(filePath);
45707
+ const absPath = path46.resolve(filePath);
45637
45708
  try {
45638
45709
  const stat2 = await fs43.stat(absPath);
45639
45710
  if (!stat2.isFile()) {
@@ -45718,7 +45789,7 @@ var pdfTools = [readPdfTool];
45718
45789
  init_registry4();
45719
45790
  init_errors();
45720
45791
  var fs44 = await import('fs/promises');
45721
- var path46 = await import('path');
45792
+ var path47 = await import('path');
45722
45793
  var SUPPORTED_FORMATS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
45723
45794
  var MAX_IMAGE_SIZE = 20 * 1024 * 1024;
45724
45795
  var MIME_TYPES = {
@@ -45746,15 +45817,15 @@ Examples:
45746
45817
  async execute({ path: filePath, prompt, provider }) {
45747
45818
  const startTime = performance.now();
45748
45819
  const effectivePrompt = prompt ?? "Describe this image in detail. If it's code or a UI, identify the key elements.";
45749
- const absPath = path46.resolve(filePath);
45820
+ const absPath = path47.resolve(filePath);
45750
45821
  const cwd = process.cwd();
45751
- if (!absPath.startsWith(cwd + path46.sep) && absPath !== cwd) {
45822
+ if (!absPath.startsWith(cwd + path47.sep) && absPath !== cwd) {
45752
45823
  throw new ToolError(
45753
45824
  `Path traversal denied: '${filePath}' resolves outside the project directory`,
45754
45825
  { tool: "read_image" }
45755
45826
  );
45756
45827
  }
45757
- const ext = path46.extname(absPath).toLowerCase();
45828
+ const ext = path47.extname(absPath).toLowerCase();
45758
45829
  if (!SUPPORTED_FORMATS.has(ext)) {
45759
45830
  throw new ToolError(
45760
45831
  `Unsupported image format '${ext}'. Supported: ${Array.from(SUPPORTED_FORMATS).join(", ")}`,
@@ -45905,7 +45976,7 @@ var imageTools = [readImageTool];
45905
45976
  // src/tools/database.ts
45906
45977
  init_registry4();
45907
45978
  init_errors();
45908
- var path47 = await import('path');
45979
+ var path48 = await import('path');
45909
45980
  var DANGEROUS_PATTERNS = [
45910
45981
  /\bDROP\s+(?:TABLE|DATABASE|INDEX|VIEW)\b/i,
45911
45982
  /\bTRUNCATE\b/i,
@@ -45936,7 +46007,7 @@ Examples:
45936
46007
  async execute({ database, query, params, readonly: isReadonlyParam }) {
45937
46008
  const isReadonly = isReadonlyParam ?? true;
45938
46009
  const startTime = performance.now();
45939
- const absPath = path47.resolve(database);
46010
+ const absPath = path48.resolve(database);
45940
46011
  if (isReadonly && isDangerousSql(query)) {
45941
46012
  throw new ToolError(
45942
46013
  "Write operations (INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE) are blocked in readonly mode. Set readonly: false to allow writes.",
@@ -46019,7 +46090,7 @@ Examples:
46019
46090
  }),
46020
46091
  async execute({ database, table }) {
46021
46092
  const startTime = performance.now();
46022
- const absPath = path47.resolve(database);
46093
+ const absPath = path48.resolve(database);
46023
46094
  try {
46024
46095
  const { default: Database } = await import('better-sqlite3');
46025
46096
  const db = new Database(absPath, { readonly: true, fileMustExist: true });
@@ -46203,7 +46274,7 @@ var astValidatorTools = [validateCodeTool, findMissingImportsTool];
46203
46274
  // src/tools/code-analyzer.ts
46204
46275
  init_registry4();
46205
46276
  var fs45 = await import('fs/promises');
46206
- var path48 = await import('path');
46277
+ var path49 = await import('path');
46207
46278
  var AnalyzeFileSchema = z.object({
46208
46279
  filePath: z.string().describe("Path to file to analyze"),
46209
46280
  includeAst: z.boolean().default(false).describe("Include AST in result")
@@ -46313,10 +46384,10 @@ async function analyzeDirectory(dirPath) {
46313
46384
  try {
46314
46385
  const analysis = await analyzeFile(file, false);
46315
46386
  totalLines += analysis.lines;
46316
- const ext = path48.extname(file);
46387
+ const ext = path49.extname(file);
46317
46388
  filesByType[ext] = (filesByType[ext] || 0) + 1;
46318
46389
  fileStats.push({
46319
- file: path48.relative(dirPath, file),
46390
+ file: path49.relative(dirPath, file),
46320
46391
  lines: analysis.lines,
46321
46392
  complexity: analysis.complexity.cyclomatic
46322
46393
  });
@@ -46880,7 +46951,7 @@ var smartSuggestionsTools = [suggestImprovementsTool, calculateCodeScoreTool];
46880
46951
  // src/tools/context-enhancer.ts
46881
46952
  init_registry4();
46882
46953
  var fs47 = await import('fs/promises');
46883
- var path49 = await import('path');
46954
+ var path50 = await import('path');
46884
46955
  var ContextMemoryStore = class {
46885
46956
  items = /* @__PURE__ */ new Map();
46886
46957
  learnings = /* @__PURE__ */ new Map();
@@ -46900,7 +46971,7 @@ var ContextMemoryStore = class {
46900
46971
  }
46901
46972
  }
46902
46973
  async save() {
46903
- const dir = path49.dirname(this.storePath);
46974
+ const dir = path50.dirname(this.storePath);
46904
46975
  await fs47.mkdir(dir, { recursive: true });
46905
46976
  const data = {
46906
46977
  sessionId: this.sessionId,
@@ -47077,7 +47148,7 @@ var contextEnhancerTools = [
47077
47148
  // src/tools/skill-enhancer.ts
47078
47149
  init_registry4();
47079
47150
  var fs48 = await import('fs/promises');
47080
- var path50 = await import('path');
47151
+ var path51 = await import('path');
47081
47152
  async function discoverSkills(skillsDir) {
47082
47153
  try {
47083
47154
  const files = await fs48.readdir(skillsDir);
@@ -47093,7 +47164,7 @@ async function loadSkillMetadata(skillPath) {
47093
47164
  const descMatch = content.match(/@description\s+(.+)/);
47094
47165
  const versionMatch = content.match(/@version\s+(\S+)/);
47095
47166
  return {
47096
- name: nameMatch?.[1] || path50.basename(skillPath, path50.extname(skillPath)),
47167
+ name: nameMatch?.[1] || path51.basename(skillPath, path51.extname(skillPath)),
47097
47168
  description: descMatch?.[1] || "No description",
47098
47169
  version: versionMatch?.[1] || "1.0.0",
47099
47170
  dependencies: []
@@ -47137,7 +47208,7 @@ var discoverSkillsTool = defineTool({
47137
47208
  const { skillsDir } = input;
47138
47209
  const skills = await discoverSkills(skillsDir);
47139
47210
  const metadata = await Promise.all(
47140
- skills.map((s) => loadSkillMetadata(path50.join(skillsDir, s)))
47211
+ skills.map((s) => loadSkillMetadata(path51.join(skillsDir, s)))
47141
47212
  );
47142
47213
  return {
47143
47214
  skillsDir,
@@ -47265,6 +47336,66 @@ var skillEnhancerTools = [discoverSkillsTool, validateSkillTool, createCustomToo
47265
47336
  init_git_enhanced();
47266
47337
  init_github();
47267
47338
  init_open();
47339
+
47340
+ // src/tools/mcp.ts
47341
+ init_registry4();
47342
+ init_registry();
47343
+ init_config_loader();
47344
+ init_lifecycle();
47345
+ var mcpListServersTool = defineTool({
47346
+ name: "mcp_list_servers",
47347
+ description: `Inspect Coco's MCP configuration and current runtime connections.
47348
+
47349
+ Use this instead of bash_exec with "coco mcp ..." and instead of manually reading ~/.coco/mcp.json
47350
+ when you need to know which MCP servers are configured, connected, healthy, or which tools they expose.`,
47351
+ category: "config",
47352
+ parameters: z.object({
47353
+ includeDisabled: z.boolean().optional().default(false).describe("Include disabled MCP servers in the result"),
47354
+ includeTools: z.boolean().optional().default(false).describe("Include the list of exposed tool names for connected servers"),
47355
+ projectPath: z.string().optional().describe("Project path whose .mcp.json should be merged. Defaults to process.cwd()")
47356
+ }),
47357
+ async execute({ includeDisabled, includeTools, projectPath }) {
47358
+ const registry = new MCPRegistryImpl();
47359
+ await registry.load();
47360
+ const resolvedProjectPath = projectPath || process.cwd();
47361
+ const configuredServers = mergeMCPConfigs(
47362
+ registry.listServers(),
47363
+ await loadMCPServersFromCOCOConfig(),
47364
+ await loadProjectMCPFile(resolvedProjectPath)
47365
+ ).filter((server) => includeDisabled || server.enabled !== false);
47366
+ const manager = getMCPServerManager();
47367
+ const servers = [];
47368
+ for (const server of configuredServers) {
47369
+ const connection = manager.getConnection(server.name);
47370
+ let tools;
47371
+ if (includeTools && connection) {
47372
+ try {
47373
+ const listed = await connection.client.listTools();
47374
+ tools = listed.tools.map((tool) => tool.name);
47375
+ } catch {
47376
+ tools = [];
47377
+ }
47378
+ }
47379
+ servers.push({
47380
+ name: server.name,
47381
+ transport: server.transport,
47382
+ enabled: server.enabled !== false,
47383
+ connected: connection !== void 0,
47384
+ healthy: connection?.healthy ?? false,
47385
+ toolCount: connection?.toolCount ?? 0,
47386
+ ...includeTools ? { tools: tools ?? [] } : {}
47387
+ });
47388
+ }
47389
+ return {
47390
+ configuredCount: servers.length,
47391
+ connectedCount: servers.filter((server) => server.connected).length,
47392
+ servers
47393
+ };
47394
+ }
47395
+ });
47396
+ var mcpTools = [mcpListServersTool];
47397
+
47398
+ // src/tools/index.ts
47268
47399
  init_registry4();
47269
47400
  init_bash();
47270
47401
  init_git();
@@ -47308,7 +47439,7 @@ Examples:
47308
47439
  reason: z.string().optional().describe("Why access is needed (shown to user for context)")
47309
47440
  }),
47310
47441
  async execute({ path: dirPath, reason }) {
47311
- const absolute = path38__default.resolve(dirPath);
47442
+ const absolute = path39__default.resolve(dirPath);
47312
47443
  if (isWithinAllowedPath(absolute, "read")) {
47313
47444
  return {
47314
47445
  authorized: true,
@@ -47317,8 +47448,8 @@ Examples:
47317
47448
  };
47318
47449
  }
47319
47450
  for (const blocked of BLOCKED_SYSTEM_PATHS2) {
47320
- const normalizedBlocked = path38__default.normalize(blocked);
47321
- if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path38__default.sep)) {
47451
+ const normalizedBlocked = path39__default.normalize(blocked);
47452
+ if (absolute === normalizedBlocked || absolute.startsWith(normalizedBlocked + path39__default.sep)) {
47322
47453
  return {
47323
47454
  authorized: false,
47324
47455
  path: absolute,
@@ -47327,7 +47458,7 @@ Examples:
47327
47458
  }
47328
47459
  }
47329
47460
  const cwd = process.cwd();
47330
- if (absolute === path38__default.normalize(cwd) || absolute.startsWith(path38__default.normalize(cwd) + path38__default.sep)) {
47461
+ if (absolute === path39__default.normalize(cwd) || absolute.startsWith(path39__default.normalize(cwd) + path39__default.sep)) {
47331
47462
  return {
47332
47463
  authorized: true,
47333
47464
  path: absolute,
@@ -47351,7 +47482,7 @@ Examples:
47351
47482
  };
47352
47483
  }
47353
47484
  const existing = getAllowedPaths();
47354
- if (existing.some((e) => path38__default.normalize(e.path) === path38__default.normalize(absolute))) {
47485
+ if (existing.some((e) => path39__default.normalize(e.path) === path39__default.normalize(absolute))) {
47355
47486
  return {
47356
47487
  authorized: true,
47357
47488
  path: absolute,
@@ -47410,6 +47541,7 @@ function registerAllTools(registry) {
47410
47541
  ...gitEnhancedTools,
47411
47542
  ...githubTools,
47412
47543
  ...openTools,
47544
+ ...mcpTools,
47413
47545
  ...authorizePathTools
47414
47546
  ];
47415
47547
  for (const tool of allTools) {
@@ -47437,7 +47569,7 @@ async function runSprints(options) {
47437
47569
  );
47438
47570
  const coordinator = createAgentCoordinator(executor, agentDefsMap);
47439
47571
  await fs35__default.mkdir(spec.outputPath, { recursive: true });
47440
- const sprintsDir = path38__default.join(spec.outputPath, ".coco", "sprints");
47572
+ const sprintsDir = path39__default.join(spec.outputPath, ".coco", "sprints");
47441
47573
  await fs35__default.mkdir(sprintsDir, { recursive: true });
47442
47574
  for (const sprint of spec.sprints) {
47443
47575
  onProgress(`Starting ${sprint.id}: ${sprint.name}`);
@@ -47691,7 +47823,7 @@ Assess: overall architecture, consistency, error handling, and production readin
47691
47823
  };
47692
47824
  }
47693
47825
  async function saveSprintResult(sprintsDir, result) {
47694
- const filePath = path38__default.join(sprintsDir, `${result.sprintId}.json`);
47826
+ const filePath = path39__default.join(sprintsDir, `${result.sprintId}.json`);
47695
47827
  await fs35__default.writeFile(filePath, JSON.stringify(result, null, 2), "utf-8");
47696
47828
  }
47697
47829
 
@@ -47717,9 +47849,9 @@ function parseArgs6(args) {
47717
47849
  return { description, specFile, outputDir, skipConfirmation };
47718
47850
  }
47719
47851
  function isWithinRoot(resolvedPath, rootDir) {
47720
- const normalRoot = path38__default.normalize(rootDir) + path38__default.sep;
47721
- const normalPath = path38__default.normalize(resolvedPath);
47722
- return normalPath === path38__default.normalize(rootDir) || normalPath.startsWith(normalRoot);
47852
+ const normalRoot = path39__default.normalize(rootDir) + path39__default.sep;
47853
+ const normalPath = path39__default.normalize(resolvedPath);
47854
+ return normalPath === path39__default.normalize(rootDir) || normalPath.startsWith(normalRoot);
47723
47855
  }
47724
47856
  var buildAppCommand = {
47725
47857
  name: "build-app",
@@ -47742,7 +47874,7 @@ var buildAppCommand = {
47742
47874
  }
47743
47875
  let initialDescription = parsed.description;
47744
47876
  if (parsed.specFile) {
47745
- const specPath = path38__default.resolve(session.projectPath, parsed.specFile);
47877
+ const specPath = path39__default.resolve(session.projectPath, parsed.specFile);
47746
47878
  if (!isWithinRoot(specPath, session.projectPath)) {
47747
47879
  p26.log.error(`--spec path must be within the project directory: ${specPath}`);
47748
47880
  return false;
@@ -47755,7 +47887,7 @@ var buildAppCommand = {
47755
47887
  return false;
47756
47888
  }
47757
47889
  }
47758
- const outputPath = parsed.outputDir ? path38__default.resolve(session.projectPath, parsed.outputDir) : path38__default.join(session.projectPath, "build-app-output");
47890
+ const outputPath = parsed.outputDir ? path39__default.resolve(session.projectPath, parsed.outputDir) : path39__default.join(session.projectPath, "build-app-output");
47759
47891
  if (parsed.outputDir && !isWithinRoot(outputPath, session.projectPath)) {
47760
47892
  p26.log.error(`--output path must be within the project directory: ${outputPath}`);
47761
47893
  return false;
@@ -47939,7 +48071,7 @@ var WorktreeManager = class {
47939
48071
  const id = randomUUID();
47940
48072
  const branchPrefix = options.branchPrefix ?? "coco-agent";
47941
48073
  const branchName = `${branchPrefix}/${name}-${id.slice(0, 8)}`;
47942
- const worktreePath = path38__default.join(this.projectRoot, WORKTREES_DIR, `${name}-${id.slice(0, 8)}`);
48074
+ const worktreePath = path39__default.join(this.projectRoot, WORKTREES_DIR, `${name}-${id.slice(0, 8)}`);
47943
48075
  const worktree = {
47944
48076
  id,
47945
48077
  name,
@@ -47950,7 +48082,7 @@ var WorktreeManager = class {
47950
48082
  };
47951
48083
  this.worktrees.set(id, worktree);
47952
48084
  try {
47953
- await fs35__default.mkdir(path38__default.join(this.projectRoot, WORKTREES_DIR), { recursive: true });
48085
+ await fs35__default.mkdir(path39__default.join(this.projectRoot, WORKTREES_DIR), { recursive: true });
47954
48086
  const baseBranch = options.baseBranch ?? "HEAD";
47955
48087
  await this.git(["worktree", "add", "-b", branchName, worktreePath, baseBranch]);
47956
48088
  worktree.status = "active";
@@ -49549,7 +49681,7 @@ function getAllCommands() {
49549
49681
  }
49550
49682
 
49551
49683
  // src/cli/repl/input/handler.ts
49552
- var HISTORY_FILE = path38.join(os4.homedir(), ".coco", "history");
49684
+ var HISTORY_FILE = path39.join(os4.homedir(), ".coco", "history");
49553
49685
  async function handleOptionC(copyFn = copyToClipboard, getLastBlockFn = getLastBlock) {
49554
49686
  const block = getLastBlockFn();
49555
49687
  if (!block) return null;
@@ -49570,7 +49702,7 @@ function loadHistory() {
49570
49702
  }
49571
49703
  function saveHistory(history) {
49572
49704
  try {
49573
- const dir = path38.dirname(HISTORY_FILE);
49705
+ const dir = path39.dirname(HISTORY_FILE);
49574
49706
  if (!fs5.existsSync(dir)) {
49575
49707
  fs5.mkdirSync(dir, { recursive: true });
49576
49708
  }
@@ -51590,6 +51722,28 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
51590
51722
  };
51591
51723
  const allTools = toolRegistry.getToolDefinitionsForLLM();
51592
51724
  const tools = session.planMode ? filterReadOnlyTools(allTools) : allTools;
51725
+ const availableMcpToolNames = allTools.map((t) => t.name).filter((name) => name.startsWith("mcp_"));
51726
+ function extractPlainText(content) {
51727
+ if (typeof content === "string") return content;
51728
+ return content.filter((block) => block.type === "text").map((block) => block.text).join(" ");
51729
+ }
51730
+ const normalizedUserRequest = extractPlainText(userMessage).toLowerCase();
51731
+ const userExplicitlyRequestedMcp = /\bmcp\b/.test(normalizedUserRequest) || /\b(use|using|usa|usar|utiliza|utilizar)\b.{0,24}\bmcp\b/.test(normalizedUserRequest);
51732
+ const userExplicitlyRequestedCocoMcpCli = /\bcoco\s+mcp\b/.test(normalizedUserRequest) || /\b(run|ejecuta|ejecutar|lanza|lanzar)\b.{0,24}\bcoco\s+mcp\b/.test(normalizedUserRequest);
51733
+ const genericNetworkToolNames = /* @__PURE__ */ new Set(["http_fetch", "http_json", "web_fetch", "web_search"]);
51734
+ function shouldForceMcpForTool(toolCall) {
51735
+ if (!userExplicitlyRequestedMcp) return false;
51736
+ if (availableMcpToolNames.length === 0) return false;
51737
+ if (!genericNetworkToolNames.has(toolCall.name)) return false;
51738
+ return true;
51739
+ }
51740
+ function shouldBlockShellMcpInspection(toolCall) {
51741
+ if (toolCall.name !== "bash_exec") return false;
51742
+ if (!userExplicitlyRequestedMcp) return false;
51743
+ if (userExplicitlyRequestedCocoMcpCli) return false;
51744
+ const command = String(toolCall.input.command ?? "").trim().toLowerCase();
51745
+ return /^coco\s+mcp(?:\s|$)/.test(command);
51746
+ }
51593
51747
  let iteration = 0;
51594
51748
  let maxIterations = session.config.agent.maxToolIterations;
51595
51749
  const HARD_MAX_ITERATIONS = 100;
@@ -51829,6 +51983,22 @@ ${tail}`;
51829
51983
  if (options.signal?.aborted || turnAborted) {
51830
51984
  break;
51831
51985
  }
51986
+ if (shouldForceMcpForTool(toolCall)) {
51987
+ declinedTools.set(
51988
+ toolCall.id,
51989
+ `User explicitly requested MCP, but the model selected '${toolCall.name}' instead. Use an MCP tool (${availableMcpToolNames.join(", ")}) for this service access.`
51990
+ );
51991
+ options.onToolSkipped?.(toolCall, "Use MCP tool instead of generic fetch");
51992
+ continue;
51993
+ }
51994
+ if (shouldBlockShellMcpInspection(toolCall)) {
51995
+ declinedTools.set(
51996
+ toolCall.id,
51997
+ "Use the native mcp_list_servers tool to inspect configured and connected MCP services in this session. Do not shell out to `coco mcp ...` for runtime MCP diagnosis unless the user explicitly asked for the CLI command."
51998
+ );
51999
+ options.onToolSkipped?.(toolCall, "Use mcp_list_servers instead of coco mcp CLI");
52000
+ continue;
52001
+ }
51832
52002
  const trustPattern = getTrustPattern(toolCall.name, toolCall.input);
51833
52003
  const needsConfirmation = !options.skipConfirmation && !session.trustedTools.has(trustPattern) && requiresConfirmation(toolCall.name, toolCall.input);
51834
52004
  if (needsConfirmation) {
@@ -52721,7 +52891,7 @@ function formatContextUsage(percent) {
52721
52891
  }
52722
52892
  function formatStatusBar(projectPath, config, gitCtx, contextUsagePercent) {
52723
52893
  const parts = [];
52724
- const projectName = path38__default.basename(projectPath);
52894
+ const projectName = path39__default.basename(projectPath);
52725
52895
  parts.push(chalk.dim("\u{1F4C1} ") + chalk.magenta(projectName));
52726
52896
  const providerName = config.provider.type;
52727
52897
  const modelName = config.provider.model || "default";