ctxo-mcp 0.6.1 → 0.6.3

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.
@@ -517,10 +517,14 @@ var init_go_adapter = __esm({
517
517
  }
518
518
  });
519
519
 
520
+ // src/cli/cli-router.ts
521
+ import { readFileSync as readFileSync9, existsSync as existsSync15 } from "fs";
522
+ import { join as join19 } from "path";
523
+
520
524
  // src/cli/index-command.ts
521
525
  import { execFileSync as execFileSync2 } from "child_process";
522
526
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync4, statSync, readdirSync as readdirSync2 } from "fs";
523
- import { join as join5, relative, extname as extname4 } from "path";
527
+ import { join as join5, relative as relative2, extname as extname4 } from "path";
524
528
 
525
529
  // src/core/staleness/content-hasher.ts
526
530
  import { createHash } from "crypto";
@@ -1205,12 +1209,25 @@ var SchemaManager = class {
1205
1209
  }
1206
1210
  };
1207
1211
 
1212
+ // src/adapters/language/roslyn/roslyn-adapter.ts
1213
+ import { relative, dirname as dirname4, resolve as resolve2 } from "path";
1214
+
1208
1215
  // src/adapters/language/roslyn/solution-discovery.ts
1209
1216
  import { execFileSync } from "child_process";
1210
1217
  import { existsSync as existsSync3, readdirSync } from "fs";
1211
1218
  import { join as join4 } from "path";
1212
1219
  var log = createLogger("ctxo:roslyn");
1213
1220
  var IGNORE_DIRS = /* @__PURE__ */ new Set(["bin", "obj", "node_modules", ".git", ".ctxo", "packages"]);
1221
+ function findPackageRoot(startDir) {
1222
+ let dir = startDir;
1223
+ for (let i = 0; i < 10; i++) {
1224
+ if (existsSync3(join4(dir, "package.json"))) return dir;
1225
+ const parent = join4(dir, "..");
1226
+ if (parent === dir) break;
1227
+ dir = parent;
1228
+ }
1229
+ return null;
1230
+ }
1214
1231
  function detectDotnetSdk() {
1215
1232
  try {
1216
1233
  const version = execFileSync("dotnet", ["--version"], { encoding: "utf-8", timeout: 1e4 }).trim();
@@ -1260,15 +1277,16 @@ function findFiles(dir, ext, maxDepth, currentDepth = 0) {
1260
1277
  return results;
1261
1278
  }
1262
1279
  function findCtxoRoslynProject() {
1280
+ const pkgRoot = findPackageRoot(import.meta.dirname);
1263
1281
  const candidates = [
1282
+ ...pkgRoot ? [join4(pkgRoot, "tools/ctxo-roslyn")] : [],
1283
+ // package root (npx, global, local dev)
1284
+ join4(import.meta.dirname, "../tools/ctxo-roslyn"),
1285
+ // dist/ -> tools/ (tsup bundled)
1264
1286
  join4(import.meta.dirname, "../../../../tools/ctxo-roslyn"),
1265
- // local dev (src/adapters/language/roslyn -> tools/)
1266
- join4(import.meta.dirname, "../../../tools/ctxo-roslyn"),
1267
- // built dist (dist/adapters/language/roslyn -> tools/)
1287
+ // src/adapters/language/roslyn -> tools/ (dev unbundled)
1268
1288
  join4(process.cwd(), "node_modules/ctxo-mcp/tools/ctxo-roslyn"),
1269
1289
  // npm install in project
1270
- join4(process.cwd(), "node_modules/ctxo/tools/ctxo-roslyn"),
1271
- // alt package name
1272
1290
  join4(process.cwd(), "tools/ctxo-roslyn")
1273
1291
  // local repo root
1274
1292
  ];
@@ -1283,7 +1301,7 @@ function findCtxoRoslynProject() {
1283
1301
  import { spawn } from "child_process";
1284
1302
  var log2 = createLogger("ctxo:roslyn");
1285
1303
  async function runBatchIndex(projectDir, solutionPath, timeoutMs = 12e4) {
1286
- return new Promise((resolve2) => {
1304
+ return new Promise((resolve3) => {
1287
1305
  const proc = spawn("dotnet", ["run", "--project", projectDir, "--", solutionPath], {
1288
1306
  stdio: ["ignore", "pipe", "pipe"],
1289
1307
  timeout: timeoutMs
@@ -1329,14 +1347,14 @@ async function runBatchIndex(projectDir, solutionPath, timeoutMs = 12e4) {
1329
1347
  proc.on("close", (code) => {
1330
1348
  if (code !== 0) {
1331
1349
  log2.error(`ctxo-roslyn exited with code ${code}: ${stderr.trim()}`);
1332
- resolve2({ files: [], projectGraph: null, totalFiles: 0, elapsed: "" });
1350
+ resolve3({ files: [], projectGraph: null, totalFiles: 0, elapsed: "" });
1333
1351
  return;
1334
1352
  }
1335
- resolve2({ files, projectGraph, totalFiles, elapsed });
1353
+ resolve3({ files, projectGraph, totalFiles, elapsed });
1336
1354
  });
1337
1355
  proc.on("error", (err) => {
1338
1356
  log2.error(`ctxo-roslyn spawn error: ${err.message}`);
1339
- resolve2({ files: [], projectGraph: null, totalFiles: 0, elapsed: "" });
1357
+ resolve3({ files: [], projectGraph: null, totalFiles: 0, elapsed: "" });
1340
1358
  });
1341
1359
  });
1342
1360
  }
@@ -1354,7 +1372,7 @@ var RoslynKeepAlive = class {
1354
1372
  this.timeoutMs = timeoutMs;
1355
1373
  }
1356
1374
  async start() {
1357
- return new Promise((resolve2) => {
1375
+ return new Promise((resolve3) => {
1358
1376
  this.proc = spawn("dotnet", ["run", "--project", this.projectDir, "--", this.solutionPath, "--keep-alive"], {
1359
1377
  stdio: ["pipe", "pipe", "pipe"]
1360
1378
  });
@@ -1369,7 +1387,7 @@ var RoslynKeepAlive = class {
1369
1387
  const obj = JSON.parse(line);
1370
1388
  if (obj.type === "ready") {
1371
1389
  log2.info(`Roslyn keep-alive ready: ${obj.projectCount} projects, ${obj.fileCount} files`);
1372
- resolve2(true);
1390
+ resolve3(true);
1373
1391
  } else if (obj.type === "file") {
1374
1392
  const result = obj;
1375
1393
  const callback = this.pending.get(result.file);
@@ -1396,7 +1414,7 @@ var RoslynKeepAlive = class {
1396
1414
  });
1397
1415
  this.proc.on("error", (err) => {
1398
1416
  log2.error(`Roslyn keep-alive error: ${err.message}`);
1399
- resolve2(false);
1417
+ resolve3(false);
1400
1418
  });
1401
1419
  this.resetInactivityTimer();
1402
1420
  });
@@ -1406,14 +1424,14 @@ var RoslynKeepAlive = class {
1406
1424
  return null;
1407
1425
  }
1408
1426
  this.resetInactivityTimer();
1409
- return new Promise((resolve2) => {
1410
- this.pending.set(relativePath, resolve2);
1427
+ return new Promise((resolve3) => {
1428
+ this.pending.set(relativePath, resolve3);
1411
1429
  this.proc.stdin.write(JSON.stringify({ file: relativePath }) + "\n");
1412
1430
  setTimeout(() => {
1413
1431
  if (this.pending.has(relativePath)) {
1414
1432
  this.pending.delete(relativePath);
1415
1433
  log2.error(`Roslyn keep-alive timeout for ${relativePath}`);
1416
- resolve2(null);
1434
+ resolve3(null);
1417
1435
  }
1418
1436
  }, 3e4);
1419
1437
  });
@@ -1445,6 +1463,7 @@ var RoslynAdapter = class {
1445
1463
  tier = "full";
1446
1464
  roslynProjectDir = null;
1447
1465
  solutionPath = null;
1466
+ rootDir = null;
1448
1467
  cache = /* @__PURE__ */ new Map();
1449
1468
  keepAlive = null;
1450
1469
  initialized = false;
@@ -1470,6 +1489,7 @@ var RoslynAdapter = class {
1470
1489
  log3.info("Roslyn adapter unavailable: no .sln or .csproj found");
1471
1490
  return;
1472
1491
  }
1492
+ this.rootDir = rootDir;
1473
1493
  log3.info(`Roslyn adapter ready: SDK ${sdk.version}, solution ${this.solutionPath}`);
1474
1494
  this.initialized = true;
1475
1495
  }
@@ -1480,9 +1500,13 @@ var RoslynAdapter = class {
1480
1500
  async batchIndex() {
1481
1501
  if (!this.isReady()) return null;
1482
1502
  const result = await runBatchIndex(this.roslynProjectDir, this.solutionPath);
1503
+ const solutionDir = dirname4(this.solutionPath);
1483
1504
  this.cache.clear();
1484
1505
  for (const file of result.files) {
1485
- this.cache.set(file.file, file);
1506
+ const absolutePath = resolve2(solutionDir, file.file);
1507
+ const projectRelative = relative(this.rootDir, absolutePath).replace(/\\/g, "/");
1508
+ const rewritten = rewritePaths(file, file.file, projectRelative);
1509
+ this.cache.set(projectRelative, rewritten);
1486
1510
  }
1487
1511
  log3.info(`Roslyn batch index: ${result.totalFiles} files in ${result.elapsed}`);
1488
1512
  return result;
@@ -1546,6 +1570,26 @@ var RoslynAdapter = class {
1546
1570
  this.initialized = false;
1547
1571
  }
1548
1572
  };
1573
+ function rewritePaths(file, oldPrefix, newPrefix) {
1574
+ const rewrite = (s) => s.replace(oldPrefix, newPrefix);
1575
+ return {
1576
+ ...file,
1577
+ file: newPrefix,
1578
+ symbols: file.symbols.map((s) => ({
1579
+ ...s,
1580
+ symbolId: rewrite(s.symbolId)
1581
+ })),
1582
+ edges: file.edges.map((e) => ({
1583
+ ...e,
1584
+ from: rewrite(e.from),
1585
+ to: rewrite(e.to)
1586
+ })),
1587
+ complexity: file.complexity.map((c) => ({
1588
+ ...c,
1589
+ symbolId: rewrite(c.symbolId)
1590
+ }))
1591
+ };
1592
+ }
1549
1593
 
1550
1594
  // src/cli/index-command.ts
1551
1595
  var IndexCommand = class {
@@ -1598,7 +1642,7 @@ var IndexCommand = class {
1598
1642
  for (const filePath of files) {
1599
1643
  const adapter = registry.getAdapter(filePath);
1600
1644
  if (!adapter) continue;
1601
- const relativePath = relative(this.projectRoot, filePath).replace(/\\/g, "/");
1645
+ const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
1602
1646
  try {
1603
1647
  const source = readFileSync2(filePath, "utf-8");
1604
1648
  const lastModified = Math.floor(Date.now() / 1e3);
@@ -1782,7 +1826,7 @@ var IndexCommand = class {
1782
1826
  let staleCount = 0;
1783
1827
  for (const filePath of files) {
1784
1828
  if (!this.isSupportedExtension(filePath)) continue;
1785
- const relativePath = relative(this.projectRoot, filePath).replace(/\\/g, "/");
1829
+ const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
1786
1830
  const indexed = indexedMap.get(relativePath);
1787
1831
  if (!indexed) {
1788
1832
  console.error(`[ctxo] NOT INDEXED: ${relativePath}`);
@@ -1952,7 +1996,7 @@ import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync
1952
1996
 
1953
1997
  // src/cli/ai-rules.ts
1954
1998
  import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
1955
- import { join as join9, dirname as dirname4 } from "path";
1999
+ import { join as join9, dirname as dirname5 } from "path";
1956
2000
  var PLATFORMS = [
1957
2001
  { id: "claude-code", name: "Claude Code", file: "CLAUDE.md", mode: "append", detectPaths: ["CLAUDE.md", ".claude"], starred: true },
1958
2002
  { id: "cursor", name: "Cursor", file: ".cursor/rules/ctxo-mcp.mdc", mode: "create", detectPaths: [".cursor", ".cursorrules"], starred: false },
@@ -2029,7 +2073,7 @@ function installRules(projectRoot, platformId) {
2029
2073
  if (!platform) throw new Error(`Unknown platform: ${platformId}`);
2030
2074
  const filePath = join9(projectRoot, platform.file);
2031
2075
  const content = generateRules(platformId);
2032
- const dir = dirname4(filePath);
2076
+ const dir = dirname5(filePath);
2033
2077
  if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
2034
2078
  if (platform.mode === "create") {
2035
2079
  writeFileSync4(filePath, content, "utf-8");
@@ -2083,7 +2127,7 @@ version: "1.0"
2083
2127
  `;
2084
2128
  function ensureConfig(projectRoot) {
2085
2129
  const filePath = join9(projectRoot, ".ctxo", "config.yaml");
2086
- const dir = dirname4(filePath);
2130
+ const dir = dirname5(filePath);
2087
2131
  if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
2088
2132
  if (existsSync6(filePath)) {
2089
2133
  return { file: ".ctxo/config.yaml", action: "skipped" };
@@ -2115,7 +2159,7 @@ function getMcpConfigTargets(selectedPlatformIds) {
2115
2159
  }
2116
2160
  function ensureMcpConfig(projectRoot, target) {
2117
2161
  const filePath = join9(projectRoot, target.file);
2118
- const dir = dirname4(filePath);
2162
+ const dir = dirname5(filePath);
2119
2163
  if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
2120
2164
  const entry = target.extraFields ? { ...target.extraFields, ...CTXO_MCP_ENTRY } : { ...CTXO_MCP_ENTRY };
2121
2165
  if (!existsSync6(filePath)) {
@@ -2451,7 +2495,7 @@ var InitCommand = class {
2451
2495
  };
2452
2496
 
2453
2497
  // src/cli/watch-command.ts
2454
- import { join as join11, extname as extname5, relative as relative2 } from "path";
2498
+ import { join as join11, extname as extname5, relative as relative3 } from "path";
2455
2499
  import { readFileSync as readFileSync5 } from "fs";
2456
2500
 
2457
2501
  // src/adapters/watcher/chokidar-watcher-adapter.ts
@@ -2538,7 +2582,7 @@ var WatchCommand = class {
2538
2582
  const reindexFile = async (filePath) => {
2539
2583
  const adapter = registry.getAdapter(filePath);
2540
2584
  if (!adapter) return;
2541
- const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
2585
+ const relativePath = relative3(this.projectRoot, filePath).replace(/\\/g, "/");
2542
2586
  try {
2543
2587
  if (roslynAdapter?.isReady() && filePath.endsWith(".cs")) {
2544
2588
  const result = await roslynAdapter.reindexFile(relativePath);
@@ -2626,7 +2670,7 @@ var WatchCommand = class {
2626
2670
  if (event === "unlink") {
2627
2671
  const ext = extname5(filePath).toLowerCase();
2628
2672
  if (!supportedExtensions.has(ext)) return;
2629
- const relativePath = relative2(this.projectRoot, filePath).replace(/\\/g, "/");
2673
+ const relativePath = relative3(this.projectRoot, filePath).replace(/\\/g, "/");
2630
2674
  writer.delete(relativePath);
2631
2675
  storage.deleteSymbolFile(relativePath);
2632
2676
  console.error(`[ctxo] Removed from index: ${relativePath}`);
@@ -3240,6 +3284,9 @@ var DoctorCommand = class {
3240
3284
  this.ctxoRoot = join18(projectRoot, ".ctxo");
3241
3285
  }
3242
3286
  async run(options = {}) {
3287
+ const { getVersion: getVersion2 } = await import("./cli-router-ZG3ELO34.js");
3288
+ console.error(`ctxo v${getVersion2()} \u2014 health check
3289
+ `);
3243
3290
  const checks = [
3244
3291
  new NodeVersionCheck(),
3245
3292
  new GitBinaryCheck(),
@@ -3284,6 +3331,24 @@ var DoctorCommand = class {
3284
3331
  };
3285
3332
 
3286
3333
  // src/cli/cli-router.ts
3334
+ function getVersion() {
3335
+ let dir = import.meta.dirname;
3336
+ for (let i = 0; i < 10; i++) {
3337
+ const pkg = join19(dir, "package.json");
3338
+ if (existsSync15(pkg)) {
3339
+ try {
3340
+ const json = JSON.parse(readFileSync9(pkg, "utf-8"));
3341
+ return json.version ?? "unknown";
3342
+ } catch {
3343
+ break;
3344
+ }
3345
+ }
3346
+ const parent = join19(dir, "..");
3347
+ if (parent === dir) break;
3348
+ dir = parent;
3349
+ }
3350
+ return "unknown";
3351
+ }
3287
3352
  var CliRouter = class {
3288
3353
  projectRoot;
3289
3354
  constructor(projectRoot) {
@@ -3291,6 +3356,10 @@ var CliRouter = class {
3291
3356
  }
3292
3357
  async route(args) {
3293
3358
  const command = args[0];
3359
+ if (command === "--version" || command === "-v" || command === "-V") {
3360
+ console.error(`ctxo ${getVersion()}`);
3361
+ return;
3362
+ }
3294
3363
  if (!command || command === "--help" || command === "-h") {
3295
3364
  this.printHelp();
3296
3365
  return;
@@ -3359,8 +3428,9 @@ var CliRouter = class {
3359
3428
  }
3360
3429
  }
3361
3430
  printHelp() {
3431
+ const v = getVersion();
3362
3432
  console.error(`
3363
- ctxo \u2014 MCP server for dependency-aware codebase context
3433
+ ctxo v${v} \u2014 MCP server for dependency-aware codebase context
3364
3434
 
3365
3435
  Usage:
3366
3436
  ctxo Start MCP server (stdio transport)
@@ -3380,6 +3450,7 @@ Usage:
3380
3450
  }
3381
3451
  };
3382
3452
  export {
3383
- CliRouter
3453
+ CliRouter,
3454
+ getVersion
3384
3455
  };
3385
- //# sourceMappingURL=cli-router-22BBOBP7.js.map
3456
+ //# sourceMappingURL=cli-router-ZG3ELO34.js.map