@coderule/mcp 1.7.2 → 2.0.0

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/mcp-cli.cjs CHANGED
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- var fs4 = require('fs/promises');
5
- var path = require('path');
4
+ var fs5 = require('fs/promises');
5
+ var path2 = require('path');
6
6
  var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
7
7
  var crypto = require('crypto');
8
8
  var envPaths = require('env-paths');
9
9
  var pino = require('pino');
10
+ var os = require('os');
10
11
  var Database = require('better-sqlite3');
11
12
  var qulite = require('@coderule/qulite');
12
13
  var clients = require('@coderule/clients');
13
- var fs2 = require('fs');
14
+ var fs3 = require('fs');
14
15
  var worker_threads = require('worker_threads');
15
16
  var chokidar = require('chokidar');
16
17
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
@@ -18,12 +19,13 @@ var zod = require('zod');
18
19
 
19
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
21
 
21
- var fs4__default = /*#__PURE__*/_interopDefault(fs4);
22
- var path__default = /*#__PURE__*/_interopDefault(path);
22
+ var fs5__default = /*#__PURE__*/_interopDefault(fs5);
23
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
23
24
  var envPaths__default = /*#__PURE__*/_interopDefault(envPaths);
24
25
  var pino__default = /*#__PURE__*/_interopDefault(pino);
26
+ var os__default = /*#__PURE__*/_interopDefault(os);
25
27
  var Database__default = /*#__PURE__*/_interopDefault(Database);
26
- var fs2__default = /*#__PURE__*/_interopDefault(fs2);
28
+ var fs3__default = /*#__PURE__*/_interopDefault(fs3);
27
29
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
28
30
 
29
31
  // node_modules/tsup/assets/cjs_shims.js
@@ -50,6 +52,166 @@ var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
50
52
  var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
51
53
  var DEFAULT_UPLOAD_CHUNK_SIZE = 1;
52
54
  var DEFAULT_MAX_QUERY_WAIT_MS = 5e4;
55
+ function collectCandidateSources(options) {
56
+ const sources = [];
57
+ if (options?.cliRoot) {
58
+ sources.push({
59
+ value: options.cliRoot,
60
+ source: "cli-arg",
61
+ baseConfidence: 1
62
+ });
63
+ }
64
+ if (process.env.CODERULE_ROOT) {
65
+ sources.push({
66
+ value: process.env.CODERULE_ROOT,
67
+ source: "env-coderule-root",
68
+ baseConfidence: 0.9
69
+ });
70
+ }
71
+ if (process.env.WORKSPACE_FOLDER_PATHS) {
72
+ sources.push({
73
+ value: process.env.WORKSPACE_FOLDER_PATHS,
74
+ source: "env-workspace",
75
+ baseConfidence: 0.8
76
+ });
77
+ }
78
+ sources.push({
79
+ value: process.cwd(),
80
+ source: "process-cwd",
81
+ baseConfidence: 0.6
82
+ });
83
+ return sources;
84
+ }
85
+ async function pathExists(targetPath) {
86
+ try {
87
+ await fs5__default.default.access(targetPath);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ async function isDirectory(targetPath) {
94
+ try {
95
+ const stat = await fs5__default.default.stat(targetPath);
96
+ return stat.isDirectory();
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ async function hasGitDirectory(rootPath) {
102
+ const gitPath = path2__default.default.join(rootPath, ".git");
103
+ return pathExists(gitPath);
104
+ }
105
+ function isShallowPath(absolutePath) {
106
+ const normalized = absolutePath.replace(/\\/g, "/");
107
+ const separatorCount = (normalized.match(/\//g) || []).length;
108
+ return separatorCount <= 1;
109
+ }
110
+ function isHomeDirectory(candidatePath) {
111
+ const home = os__default.default.homedir();
112
+ const normalizedCandidate = path2__default.default.normalize(candidatePath);
113
+ const normalizedHome = path2__default.default.normalize(home);
114
+ return normalizedCandidate === normalizedHome;
115
+ }
116
+ async function applyModifiers(candidate) {
117
+ const modifiers = [];
118
+ if (await hasGitDirectory(candidate.path)) {
119
+ modifiers.push({ reason: "has .git directory", delta: 0.1 });
120
+ }
121
+ if (isShallowPath(candidate.path)) {
122
+ modifiers.push({ reason: "shallow path (likely root)", delta: -0.2 });
123
+ }
124
+ if (isHomeDirectory(candidate.path)) {
125
+ modifiers.push({ reason: "is home directory", delta: -0.3 });
126
+ }
127
+ const totalModifier = modifiers.reduce((sum, m) => sum + m.delta, 0);
128
+ const finalConfidence = candidate.baseConfidence + totalModifier;
129
+ return {
130
+ ...candidate,
131
+ modifiers,
132
+ finalConfidence
133
+ };
134
+ }
135
+ async function createCandidate(source) {
136
+ const absolutePath = path2__default.default.resolve(source.value);
137
+ const exists = await pathExists(absolutePath);
138
+ const isDir = exists ? await isDirectory(absolutePath) : false;
139
+ const candidate = {
140
+ path: absolutePath,
141
+ source: source.source,
142
+ baseConfidence: source.baseConfidence,
143
+ modifiers: [],
144
+ finalConfidence: source.baseConfidence,
145
+ exists,
146
+ isDirectory: isDir
147
+ };
148
+ if (exists && isDir) {
149
+ return applyModifiers(candidate);
150
+ }
151
+ return candidate;
152
+ }
153
+ async function resolveRootPath(options) {
154
+ const logger2 = options?.logger;
155
+ const sources = collectCandidateSources(options);
156
+ const candidates = await Promise.all(sources.map(createCandidate));
157
+ logger2?.debug(
158
+ {
159
+ candidates: candidates.map((c) => ({
160
+ path: c.path,
161
+ source: c.source,
162
+ baseConfidence: c.baseConfidence,
163
+ finalConfidence: c.finalConfidence,
164
+ exists: c.exists,
165
+ isDirectory: c.isDirectory,
166
+ modifiers: c.modifiers
167
+ }))
168
+ },
169
+ "Collected root path candidates"
170
+ );
171
+ const validCandidates = candidates.filter((c) => c.exists && c.isDirectory);
172
+ if (validCandidates.length === 0) {
173
+ const attempted = candidates.map((c) => c.path).join(", ");
174
+ throw new Error(
175
+ `No valid root directory found. Attempted: ${attempted}. Ensure the directory exists and is accessible.`
176
+ );
177
+ }
178
+ const sourcePriority = {
179
+ "cli-arg": 1,
180
+ "env-coderule-root": 2,
181
+ "env-workspace": 3,
182
+ "process-cwd": 4
183
+ };
184
+ validCandidates.sort((a, b) => {
185
+ const confidenceDiff = b.finalConfidence - a.finalConfidence;
186
+ if (confidenceDiff !== 0) {
187
+ return confidenceDiff;
188
+ }
189
+ return sourcePriority[a.source] - sourcePriority[b.source];
190
+ });
191
+ const best = validCandidates[0];
192
+ if (best.finalConfidence < 0.7) {
193
+ logger2?.warn(
194
+ {
195
+ path: best.path,
196
+ source: best.source,
197
+ confidence: best.finalConfidence,
198
+ modifiers: best.modifiers
199
+ },
200
+ "Selected root path has low confidence"
201
+ );
202
+ } else {
203
+ logger2?.info(
204
+ {
205
+ path: best.path,
206
+ source: best.source,
207
+ confidence: best.finalConfidence,
208
+ modifiers: best.modifiers
209
+ },
210
+ "Resolved root path"
211
+ );
212
+ }
213
+ return best;
214
+ }
53
215
 
54
216
  // src/config/Configurator.ts
55
217
  var DEFAULT_RETRIEVAL_FORMATTER = "standard";
@@ -62,9 +224,9 @@ var DEFAULTS = {
62
224
  maxSnapshotAttempts: DEFAULT_MAX_SNAPSHOT_ATTEMPTS
63
225
  };
64
226
  function normalizeRoot(root) {
65
- const resolved = path__default.default.resolve(root);
66
- const normalized = path__default.default.normalize(resolved);
67
- return normalized.split(path__default.default.sep).join("/");
227
+ const resolved = path2__default.default.resolve(root);
228
+ const normalized = path2__default.default.normalize(resolved);
229
+ return normalized.split(path2__default.default.sep).join("/");
68
230
  }
69
231
  function sha256(input) {
70
232
  return crypto.createHash("sha256").update(input).digest("hex");
@@ -96,7 +258,8 @@ function parseFormatter(value) {
96
258
  );
97
259
  }
98
260
  async function resolveConfig({
99
- token
261
+ token,
262
+ rootPath: cliRoot
100
263
  }) {
101
264
  const resolvedToken = token ?? process.env.CODERULE_TOKEN;
102
265
  if (!resolvedToken) {
@@ -104,14 +267,17 @@ async function resolveConfig({
104
267
  "Missing token: provide params.token or CODERULE_TOKEN env"
105
268
  );
106
269
  }
107
- const rootCandidate = process.env.CODERULE_ROOT || process.cwd();
108
- const rootPath = path__default.default.resolve(rootCandidate);
270
+ const rootCandidate = await resolveRootPath({
271
+ cliRoot,
272
+ logger: logger.child({ scope: "root-resolver" })
273
+ });
274
+ const rootPath = rootCandidate.path;
109
275
  const normalized = normalizeRoot(rootPath);
110
276
  const rootId = sha256(normalized);
111
277
  const dataDir = process.env.CODERULE_DATA_DIR || envPaths__default.default("coderule").data;
112
- const watchDir = path__default.default.join(dataDir, "watch");
113
- await fs4__default.default.mkdir(watchDir, { recursive: true });
114
- const dbPath = path__default.default.join(watchDir, `${rootId}.sqlite`);
278
+ const watchDir = path2__default.default.join(dataDir, "watch");
279
+ await fs5__default.default.mkdir(watchDir, { recursive: true });
280
+ const dbPath = path2__default.default.join(watchDir, `${rootId}.sqlite`);
115
281
  const baseConfig = {
116
282
  token: resolvedToken,
117
283
  rootPath,
@@ -188,6 +354,9 @@ async function resolveConfig({
188
354
  logger.debug(
189
355
  {
190
356
  rootPath,
357
+ rootSource: rootCandidate.source,
358
+ rootConfidence: rootCandidate.finalConfidence,
359
+ rootModifiers: rootCandidate.modifiers,
191
360
  dbPath,
192
361
  dataDir,
193
362
  authBaseUrl: baseConfig.authBaseUrl,
@@ -688,7 +857,7 @@ async function fetchVisitorRules(clients, logger2) {
688
857
  return rules;
689
858
  }
690
859
  function toPosix(input) {
691
- return input.split(path__default.default.sep).join("/");
860
+ return input.split(path2__default.default.sep).join("/");
692
861
  }
693
862
  function getLowerBasename(input) {
694
863
  const base = input.split("/").pop();
@@ -707,7 +876,7 @@ function compileRulesBundle(rules) {
707
876
  if (!info) {
708
877
  logger.debug({ path: fullPath }, "Predicate fallback lstat");
709
878
  try {
710
- info = fs2__default.default.lstatSync(fullPath);
879
+ info = fs3__default.default.lstatSync(fullPath);
711
880
  } catch (error) {
712
881
  logger.warn(
713
882
  { err: error, path: fullPath },
@@ -856,14 +1025,14 @@ var Hasher = class {
856
1025
  await new Promise((resolve) => setImmediate(resolve));
857
1026
  }
858
1027
  resolveAbsolutePath(record) {
859
- if (path__default.default.isAbsolute(record.display_path)) {
1028
+ if (path2__default.default.isAbsolute(record.display_path)) {
860
1029
  return record.display_path;
861
1030
  }
862
- return path__default.default.join(this.options.rootPath, record.rel_path);
1031
+ return path2__default.default.join(this.options.rootPath, record.rel_path);
863
1032
  }
864
1033
  async ensureExists(absPath, record) {
865
1034
  try {
866
- await fs4__default.default.access(absPath);
1035
+ await fs5__default.default.access(absPath);
867
1036
  return true;
868
1037
  } catch (error) {
869
1038
  this.log.warn(
@@ -886,7 +1055,7 @@ var Hasher = class {
886
1055
  const service = crypto.createHash("sha256");
887
1056
  service.update(relPath);
888
1057
  service.update("\n");
889
- const stream = fs2__default.default.createReadStream(absPath);
1058
+ const stream = fs3__default.default.createReadStream(absPath);
890
1059
  stream.on("data", (chunk) => {
891
1060
  content.update(chunk);
892
1061
  service.update(chunk);
@@ -1001,13 +1170,13 @@ async function bootstrap(params) {
1001
1170
  return runtime;
1002
1171
  }
1003
1172
  function toPosixRelative(root, target) {
1004
- const rel = path__default.default.relative(root, target);
1173
+ const rel = path2__default.default.relative(root, target);
1005
1174
  if (!rel || rel === "") return "";
1006
- return rel.split(path__default.default.sep).join("/");
1175
+ return rel.split(path2__default.default.sep).join("/");
1007
1176
  }
1008
1177
  function isInsideRoot(root, target) {
1009
- const rel = path__default.default.relative(root, target);
1010
- return rel === "" || !rel.startsWith("..") && !path__default.default.isAbsolute(rel);
1178
+ const rel = path2__default.default.relative(root, target);
1179
+ return rel === "" || !rel.startsWith("..") && !path2__default.default.isAbsolute(rel);
1011
1180
  }
1012
1181
 
1013
1182
  // src/fs/Walker.ts
@@ -1022,7 +1191,7 @@ function cloneStats(stats) {
1022
1191
  }
1023
1192
  async function readSymlinkTarget(absPath, log) {
1024
1193
  try {
1025
- return await fs4__default.default.readlink(absPath);
1194
+ return await fs5__default.default.readlink(absPath);
1026
1195
  } catch (error) {
1027
1196
  log.warn({ err: error, path: absPath }, "Failed to read symlink target");
1028
1197
  return null;
@@ -1032,13 +1201,13 @@ async function walkDirectory(current, opts, stats) {
1032
1201
  const dirLogger = opts.logger;
1033
1202
  let dirents;
1034
1203
  try {
1035
- dirents = await fs4__default.default.readdir(current, { withFileTypes: true });
1204
+ dirents = await fs5__default.default.readdir(current, { withFileTypes: true });
1036
1205
  } catch (error) {
1037
1206
  dirLogger.warn({ err: error, path: current }, "Failed to read directory");
1038
1207
  return;
1039
1208
  }
1040
1209
  for (const dirent of dirents) {
1041
- const absPath = path__default.default.join(current, dirent.name);
1210
+ const absPath = path2__default.default.join(current, dirent.name);
1042
1211
  const relPath = toPosixRelative(opts.rootPath, absPath);
1043
1212
  if (dirent.isDirectory()) {
1044
1213
  if (shouldPruneDirectory(relPath, opts.bundle)) {
@@ -1051,7 +1220,7 @@ async function walkDirectory(current, opts, stats) {
1051
1220
  if (dirent.isSymbolicLink() || dirent.isFile()) {
1052
1221
  let stat;
1053
1222
  try {
1054
- stat = await fs4__default.default.lstat(absPath);
1223
+ stat = await fs5__default.default.lstat(absPath);
1055
1224
  } catch (error) {
1056
1225
  dirLogger.warn({ err: error, path: absPath }, "Failed to stat file");
1057
1226
  continue;
@@ -1143,9 +1312,9 @@ async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts
1143
1312
  const list = chunks[idx];
1144
1313
  const map = /* @__PURE__ */ new Map();
1145
1314
  for (const missingFile of list) {
1146
- const absPath = path__default.default.join(rootPath, missingFile.file_path);
1315
+ const absPath = path2__default.default.join(rootPath, missingFile.file_path);
1147
1316
  try {
1148
- const buffer = await fs4__default.default.readFile(absPath);
1317
+ const buffer = await fs5__default.default.readFile(absPath);
1149
1318
  map.set(missingFile.file_hash, {
1150
1319
  path: missingFile.file_path,
1151
1320
  content: buffer
@@ -1410,7 +1579,7 @@ function computeBackoff(attempts) {
1410
1579
  }
1411
1580
  async function readSymlinkTarget2(absPath) {
1412
1581
  try {
1413
- return await fs4__default.default.readlink(absPath);
1582
+ return await fs5__default.default.readlink(absPath);
1414
1583
  } catch {
1415
1584
  return null;
1416
1585
  }
@@ -1551,7 +1720,7 @@ var ServiceRunner = class {
1551
1720
  async handleEvent(event, absPath, stats) {
1552
1721
  if (!this.running) return;
1553
1722
  const root = this.runtime.config.rootPath;
1554
- const absolute = path__default.default.isAbsolute(absPath) ? absPath : path__default.default.join(root, absPath);
1723
+ const absolute = path2__default.default.isAbsolute(absPath) ? absPath : path2__default.default.join(root, absPath);
1555
1724
  if (!isInsideRoot(root, absolute)) {
1556
1725
  return;
1557
1726
  }
@@ -1571,7 +1740,7 @@ var ServiceRunner = class {
1571
1740
  async handleAddChange(absPath, _stats) {
1572
1741
  let fileStats;
1573
1742
  try {
1574
- fileStats = await fs4__default.default.lstat(absPath);
1743
+ fileStats = await fs5__default.default.lstat(absPath);
1575
1744
  } catch (error) {
1576
1745
  this.runtime.logger.warn(
1577
1746
  { err: error, path: absPath },
@@ -1823,9 +1992,19 @@ function formatStatus(status) {
1823
1992
  ` Pass 2 (Cross-Reference): ${formatDuration(timing.pass2)}`
1824
1993
  );
1825
1994
  }
1826
- if (timing.pass2db !== void 0) {
1995
+ if (timing.pass2_hdf5_compile !== void 0) {
1996
+ lines.push(
1997
+ ` HDF5 Compilation: ${formatDuration(timing.pass2_hdf5_compile)}`
1998
+ );
1999
+ }
2000
+ if (timing.pass2_hdf5_dedup !== void 0) {
1827
2001
  lines.push(
1828
- ` Pass 2 DB Operations: ${formatDuration(timing.pass2db)}`
2002
+ ` HDF5 Deduplication: ${formatDuration(timing.pass2_hdf5_dedup)}`
2003
+ );
2004
+ }
2005
+ if (timing.pass2_hdf5_write !== void 0) {
2006
+ lines.push(
2007
+ ` HDF5 Write I/O: ${formatDuration(timing.pass2_hdf5_write)}`
1829
2008
  );
1830
2009
  }
1831
2010
  const totalTime = (timing.pass1 ?? 0) + (timing.gap ?? 0) + (timing.pass2 ?? 0);
@@ -2062,7 +2241,6 @@ ${statusText}`;
2062
2241
 
2063
2242
  // src/mcp-cli.ts
2064
2243
  var ENV_FLAG_MAP = {
2065
- root: "CODERULE_ROOT",
2066
2244
  "data-dir": "CODERULE_DATA_DIR",
2067
2245
  "auth-url": "CODERULE_AUTH_URL",
2068
2246
  "sync-url": "CODERULE_SYNC_URL",
@@ -2133,6 +2311,7 @@ function printUsage() {
2133
2311
  }
2134
2312
  function parseArgs(argv) {
2135
2313
  let token = process.env.CODERULE_TOKEN;
2314
+ let rootPath;
2136
2315
  let clean = false;
2137
2316
  let inlineHasher = false;
2138
2317
  const env = {};
@@ -2163,6 +2342,14 @@ function parseArgs(argv) {
2163
2342
  token = arg.slice("--token=".length);
2164
2343
  continue;
2165
2344
  }
2345
+ if (arg === "--root") {
2346
+ const value = args.shift();
2347
+ if (!value) {
2348
+ throw new Error("Missing value for --root");
2349
+ }
2350
+ rootPath = value;
2351
+ continue;
2352
+ }
2166
2353
  if (arg.startsWith("--")) {
2167
2354
  const flag = arg.slice(2);
2168
2355
  const envKey = ENV_FLAG_MAP[flag];
@@ -2195,22 +2382,22 @@ function parseArgs(argv) {
2195
2382
  "Missing token. Provide via argument or CODERULE_TOKEN environment variable."
2196
2383
  );
2197
2384
  }
2198
- return { token, clean, inlineHasher, env };
2385
+ return { token, rootPath, clean, inlineHasher, env };
2199
2386
  }
2200
- async function ensureClean(configToken) {
2201
- const config = await resolveConfig({ token: configToken });
2387
+ async function ensureClean(configToken, rootPath) {
2388
+ const config = await resolveConfig({ token: configToken, rootPath });
2202
2389
  const targets = [
2203
2390
  config.dbPath,
2204
2391
  `${config.dbPath}-shm`,
2205
2392
  `${config.dbPath}-wal`
2206
2393
  ];
2207
- await Promise.all(targets.map((target) => fs4__default.default.rm(target, { force: true })));
2208
- await fs4__default.default.rm(path__default.default.join(config.dataDir, "watch", `${config.rootId}.sqlite-shm`), {
2394
+ await Promise.all(targets.map((target) => fs5__default.default.rm(target, { force: true })));
2395
+ await fs5__default.default.rm(path2__default.default.join(config.dataDir, "watch", `${config.rootId}.sqlite-shm`), {
2209
2396
  force: true
2210
2397
  }).catch(() => {
2211
2398
  });
2212
- const dir = path__default.default.dirname(config.dbPath);
2213
- await fs4__default.default.mkdir(dir, { recursive: true });
2399
+ const dir = path2__default.default.dirname(config.dbPath);
2400
+ await fs5__default.default.mkdir(dir, { recursive: true });
2214
2401
  console.log(`Removed scanner database at ${config.dbPath}`);
2215
2402
  }
2216
2403
  function awaitShutdownSignals() {
@@ -2241,9 +2428,12 @@ async function main() {
2241
2428
  process.env[key] = value;
2242
2429
  }
2243
2430
  if (options.clean) {
2244
- await ensureClean(options.token);
2431
+ await ensureClean(options.token, options.rootPath);
2245
2432
  }
2246
- const runtime = await bootstrap({ token: options.token });
2433
+ const runtime = await bootstrap({
2434
+ token: options.token,
2435
+ rootPath: options.rootPath
2436
+ });
2247
2437
  const runner = new ServiceRunner(runtime);
2248
2438
  try {
2249
2439
  await runner.prepareWatcher(true);