@coderule/mcp 1.8.0 → 2.0.1

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.cjs CHANGED
@@ -1,26 +1,28 @@
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 crypto = require('crypto');
7
7
  var envPaths = require('env-paths');
8
8
  var pino = require('pino');
9
+ var os = require('os');
9
10
  var Database = require('better-sqlite3');
10
11
  var qulite = require('@coderule/qulite');
11
12
  var clients = require('@coderule/clients');
12
- var fs2 = require('fs');
13
+ var fs3 = require('fs');
13
14
  var worker_threads = require('worker_threads');
14
15
  var chokidar = require('chokidar');
15
16
 
16
17
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
18
 
18
- var fs4__default = /*#__PURE__*/_interopDefault(fs4);
19
- var path__default = /*#__PURE__*/_interopDefault(path);
19
+ var fs5__default = /*#__PURE__*/_interopDefault(fs5);
20
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
20
21
  var envPaths__default = /*#__PURE__*/_interopDefault(envPaths);
21
22
  var pino__default = /*#__PURE__*/_interopDefault(pino);
23
+ var os__default = /*#__PURE__*/_interopDefault(os);
22
24
  var Database__default = /*#__PURE__*/_interopDefault(Database);
23
- var fs2__default = /*#__PURE__*/_interopDefault(fs2);
25
+ var fs3__default = /*#__PURE__*/_interopDefault(fs3);
24
26
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
25
27
 
26
28
  // node_modules/tsup/assets/cjs_shims.js
@@ -47,6 +49,166 @@ var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
47
49
  var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
48
50
  var DEFAULT_UPLOAD_CHUNK_SIZE = 1;
49
51
  var DEFAULT_MAX_QUERY_WAIT_MS = 5e4;
52
+ function collectCandidateSources(options) {
53
+ const sources = [];
54
+ if (options?.cliRoot) {
55
+ sources.push({
56
+ value: options.cliRoot,
57
+ source: "cli-arg",
58
+ baseConfidence: 1
59
+ });
60
+ }
61
+ if (process.env.CODERULE_ROOT) {
62
+ sources.push({
63
+ value: process.env.CODERULE_ROOT,
64
+ source: "env-coderule-root",
65
+ baseConfidence: 0.9
66
+ });
67
+ }
68
+ if (process.env.WORKSPACE_FOLDER_PATHS) {
69
+ sources.push({
70
+ value: process.env.WORKSPACE_FOLDER_PATHS,
71
+ source: "env-workspace",
72
+ baseConfidence: 0.8
73
+ });
74
+ }
75
+ sources.push({
76
+ value: process.cwd(),
77
+ source: "process-cwd",
78
+ baseConfidence: 0.6
79
+ });
80
+ return sources;
81
+ }
82
+ async function pathExists(targetPath) {
83
+ try {
84
+ await fs5__default.default.access(targetPath);
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+ async function isDirectory(targetPath) {
91
+ try {
92
+ const stat = await fs5__default.default.stat(targetPath);
93
+ return stat.isDirectory();
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ async function hasGitDirectory(rootPath) {
99
+ const gitPath = path2__default.default.join(rootPath, ".git");
100
+ return pathExists(gitPath);
101
+ }
102
+ function isShallowPath(absolutePath) {
103
+ const normalized = absolutePath.replace(/\\/g, "/");
104
+ const separatorCount = (normalized.match(/\//g) || []).length;
105
+ return separatorCount <= 1;
106
+ }
107
+ function isHomeDirectory(candidatePath) {
108
+ const home = os__default.default.homedir();
109
+ const normalizedCandidate = path2__default.default.normalize(candidatePath);
110
+ const normalizedHome = path2__default.default.normalize(home);
111
+ return normalizedCandidate === normalizedHome;
112
+ }
113
+ async function applyModifiers(candidate) {
114
+ const modifiers = [];
115
+ if (await hasGitDirectory(candidate.path)) {
116
+ modifiers.push({ reason: "has .git directory", delta: 0.1 });
117
+ }
118
+ if (isShallowPath(candidate.path)) {
119
+ modifiers.push({ reason: "shallow path (likely root)", delta: -0.2 });
120
+ }
121
+ if (isHomeDirectory(candidate.path)) {
122
+ modifiers.push({ reason: "is home directory", delta: -0.3 });
123
+ }
124
+ const totalModifier = modifiers.reduce((sum, m) => sum + m.delta, 0);
125
+ const finalConfidence = candidate.baseConfidence + totalModifier;
126
+ return {
127
+ ...candidate,
128
+ modifiers,
129
+ finalConfidence
130
+ };
131
+ }
132
+ async function createCandidate(source) {
133
+ const absolutePath = path2__default.default.resolve(source.value);
134
+ const exists = await pathExists(absolutePath);
135
+ const isDir = exists ? await isDirectory(absolutePath) : false;
136
+ const candidate = {
137
+ path: absolutePath,
138
+ source: source.source,
139
+ baseConfidence: source.baseConfidence,
140
+ modifiers: [],
141
+ finalConfidence: source.baseConfidence,
142
+ exists,
143
+ isDirectory: isDir
144
+ };
145
+ if (exists && isDir) {
146
+ return applyModifiers(candidate);
147
+ }
148
+ return candidate;
149
+ }
150
+ async function resolveRootPath(options) {
151
+ const logger2 = options?.logger;
152
+ const sources = collectCandidateSources(options);
153
+ const candidates = await Promise.all(sources.map(createCandidate));
154
+ logger2?.debug(
155
+ {
156
+ candidates: candidates.map((c) => ({
157
+ path: c.path,
158
+ source: c.source,
159
+ baseConfidence: c.baseConfidence,
160
+ finalConfidence: c.finalConfidence,
161
+ exists: c.exists,
162
+ isDirectory: c.isDirectory,
163
+ modifiers: c.modifiers
164
+ }))
165
+ },
166
+ "Collected root path candidates"
167
+ );
168
+ const validCandidates = candidates.filter((c) => c.exists && c.isDirectory);
169
+ if (validCandidates.length === 0) {
170
+ const attempted = candidates.map((c) => c.path).join(", ");
171
+ throw new Error(
172
+ `No valid root directory found. Attempted: ${attempted}. Ensure the directory exists and is accessible.`
173
+ );
174
+ }
175
+ const sourcePriority = {
176
+ "cli-arg": 1,
177
+ "env-coderule-root": 2,
178
+ "env-workspace": 3,
179
+ "process-cwd": 4
180
+ };
181
+ validCandidates.sort((a, b) => {
182
+ const confidenceDiff = b.finalConfidence - a.finalConfidence;
183
+ if (confidenceDiff !== 0) {
184
+ return confidenceDiff;
185
+ }
186
+ return sourcePriority[a.source] - sourcePriority[b.source];
187
+ });
188
+ const best = validCandidates[0];
189
+ if (best.finalConfidence < 0.7) {
190
+ logger2?.warn(
191
+ {
192
+ path: best.path,
193
+ source: best.source,
194
+ confidence: best.finalConfidence,
195
+ modifiers: best.modifiers
196
+ },
197
+ "Selected root path has low confidence"
198
+ );
199
+ } else {
200
+ logger2?.info(
201
+ {
202
+ path: best.path,
203
+ source: best.source,
204
+ confidence: best.finalConfidence,
205
+ modifiers: best.modifiers
206
+ },
207
+ "Resolved root path"
208
+ );
209
+ }
210
+ return best;
211
+ }
50
212
 
51
213
  // src/config/Configurator.ts
52
214
  var DEFAULT_RETRIEVAL_FORMATTER = "standard";
@@ -59,9 +221,9 @@ var DEFAULTS = {
59
221
  maxSnapshotAttempts: DEFAULT_MAX_SNAPSHOT_ATTEMPTS
60
222
  };
61
223
  function normalizeRoot(root) {
62
- const resolved = path__default.default.resolve(root);
63
- const normalized = path__default.default.normalize(resolved);
64
- return normalized.split(path__default.default.sep).join("/");
224
+ const resolved = path2__default.default.resolve(root);
225
+ const normalized = path2__default.default.normalize(resolved);
226
+ return normalized.split(path2__default.default.sep).join("/");
65
227
  }
66
228
  function sha256(input) {
67
229
  return crypto.createHash("sha256").update(input).digest("hex");
@@ -93,7 +255,8 @@ function parseFormatter(value) {
93
255
  );
94
256
  }
95
257
  async function resolveConfig({
96
- token
258
+ token,
259
+ rootPath: cliRoot
97
260
  }) {
98
261
  const resolvedToken = token ?? process.env.CODERULE_TOKEN;
99
262
  if (!resolvedToken) {
@@ -101,14 +264,17 @@ async function resolveConfig({
101
264
  "Missing token: provide params.token or CODERULE_TOKEN env"
102
265
  );
103
266
  }
104
- const rootCandidate = process.env.CODERULE_ROOT || process.cwd();
105
- const rootPath = path__default.default.resolve(rootCandidate);
267
+ const rootCandidate = await resolveRootPath({
268
+ cliRoot,
269
+ logger: logger.child({ scope: "root-resolver" })
270
+ });
271
+ const rootPath = rootCandidate.path;
106
272
  const normalized = normalizeRoot(rootPath);
107
273
  const rootId = sha256(normalized);
108
274
  const dataDir = process.env.CODERULE_DATA_DIR || envPaths__default.default("coderule").data;
109
- const watchDir = path__default.default.join(dataDir, "watch");
110
- await fs4__default.default.mkdir(watchDir, { recursive: true });
111
- const dbPath = path__default.default.join(watchDir, `${rootId}.sqlite`);
275
+ const watchDir = path2__default.default.join(dataDir, "watch");
276
+ await fs5__default.default.mkdir(watchDir, { recursive: true });
277
+ const dbPath = path2__default.default.join(watchDir, `${rootId}.sqlite`);
112
278
  const baseConfig = {
113
279
  token: resolvedToken,
114
280
  rootPath,
@@ -185,6 +351,9 @@ async function resolveConfig({
185
351
  logger.debug(
186
352
  {
187
353
  rootPath,
354
+ rootSource: rootCandidate.source,
355
+ rootConfidence: rootCandidate.finalConfidence,
356
+ rootModifiers: rootCandidate.modifiers,
188
357
  dbPath,
189
358
  dataDir,
190
359
  authBaseUrl: baseConfig.authBaseUrl,
@@ -685,7 +854,7 @@ async function fetchVisitorRules(clients, logger2) {
685
854
  return rules;
686
855
  }
687
856
  function toPosix(input) {
688
- return input.split(path__default.default.sep).join("/");
857
+ return input.split(path2__default.default.sep).join("/");
689
858
  }
690
859
  function getLowerBasename(input) {
691
860
  const base = input.split("/").pop();
@@ -704,7 +873,7 @@ function compileRulesBundle(rules) {
704
873
  if (!info) {
705
874
  logger.debug({ path: fullPath }, "Predicate fallback lstat");
706
875
  try {
707
- info = fs2__default.default.lstatSync(fullPath);
876
+ info = fs3__default.default.lstatSync(fullPath);
708
877
  } catch (error) {
709
878
  logger.warn(
710
879
  { err: error, path: fullPath },
@@ -853,14 +1022,14 @@ var Hasher = class {
853
1022
  await new Promise((resolve) => setImmediate(resolve));
854
1023
  }
855
1024
  resolveAbsolutePath(record) {
856
- if (path__default.default.isAbsolute(record.display_path)) {
1025
+ if (path2__default.default.isAbsolute(record.display_path)) {
857
1026
  return record.display_path;
858
1027
  }
859
- return path__default.default.join(this.options.rootPath, record.rel_path);
1028
+ return path2__default.default.join(this.options.rootPath, record.rel_path);
860
1029
  }
861
1030
  async ensureExists(absPath, record) {
862
1031
  try {
863
- await fs4__default.default.access(absPath);
1032
+ await fs5__default.default.access(absPath);
864
1033
  return true;
865
1034
  } catch (error) {
866
1035
  this.log.warn(
@@ -883,7 +1052,7 @@ var Hasher = class {
883
1052
  const service = crypto.createHash("sha256");
884
1053
  service.update(relPath);
885
1054
  service.update("\n");
886
- const stream = fs2__default.default.createReadStream(absPath);
1055
+ const stream = fs3__default.default.createReadStream(absPath);
887
1056
  stream.on("data", (chunk) => {
888
1057
  content.update(chunk);
889
1058
  service.update(chunk);
@@ -998,13 +1167,13 @@ async function bootstrap(params) {
998
1167
  return runtime;
999
1168
  }
1000
1169
  function toPosixRelative(root, target) {
1001
- const rel = path__default.default.relative(root, target);
1170
+ const rel = path2__default.default.relative(root, target);
1002
1171
  if (!rel || rel === "") return "";
1003
- return rel.split(path__default.default.sep).join("/");
1172
+ return rel.split(path2__default.default.sep).join("/");
1004
1173
  }
1005
1174
  function isInsideRoot(root, target) {
1006
- const rel = path__default.default.relative(root, target);
1007
- return rel === "" || !rel.startsWith("..") && !path__default.default.isAbsolute(rel);
1175
+ const rel = path2__default.default.relative(root, target);
1176
+ return rel === "" || !rel.startsWith("..") && !path2__default.default.isAbsolute(rel);
1008
1177
  }
1009
1178
 
1010
1179
  // src/fs/Walker.ts
@@ -1019,7 +1188,7 @@ function cloneStats(stats) {
1019
1188
  }
1020
1189
  async function readSymlinkTarget(absPath, log) {
1021
1190
  try {
1022
- return await fs4__default.default.readlink(absPath);
1191
+ return await fs5__default.default.readlink(absPath);
1023
1192
  } catch (error) {
1024
1193
  log.warn({ err: error, path: absPath }, "Failed to read symlink target");
1025
1194
  return null;
@@ -1029,13 +1198,13 @@ async function walkDirectory(current, opts, stats) {
1029
1198
  const dirLogger = opts.logger;
1030
1199
  let dirents;
1031
1200
  try {
1032
- dirents = await fs4__default.default.readdir(current, { withFileTypes: true });
1201
+ dirents = await fs5__default.default.readdir(current, { withFileTypes: true });
1033
1202
  } catch (error) {
1034
1203
  dirLogger.warn({ err: error, path: current }, "Failed to read directory");
1035
1204
  return;
1036
1205
  }
1037
1206
  for (const dirent of dirents) {
1038
- const absPath = path__default.default.join(current, dirent.name);
1207
+ const absPath = path2__default.default.join(current, dirent.name);
1039
1208
  const relPath = toPosixRelative(opts.rootPath, absPath);
1040
1209
  if (dirent.isDirectory()) {
1041
1210
  if (shouldPruneDirectory(relPath, opts.bundle)) {
@@ -1048,7 +1217,7 @@ async function walkDirectory(current, opts, stats) {
1048
1217
  if (dirent.isSymbolicLink() || dirent.isFile()) {
1049
1218
  let stat;
1050
1219
  try {
1051
- stat = await fs4__default.default.lstat(absPath);
1220
+ stat = await fs5__default.default.lstat(absPath);
1052
1221
  } catch (error) {
1053
1222
  dirLogger.warn({ err: error, path: absPath }, "Failed to stat file");
1054
1223
  continue;
@@ -1140,9 +1309,9 @@ async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts
1140
1309
  const list = chunks[idx];
1141
1310
  const map = /* @__PURE__ */ new Map();
1142
1311
  for (const missingFile of list) {
1143
- const absPath = path__default.default.join(rootPath, missingFile.file_path);
1312
+ const absPath = path2__default.default.join(rootPath, missingFile.file_path);
1144
1313
  try {
1145
- const buffer = await fs4__default.default.readFile(absPath);
1314
+ const buffer = await fs5__default.default.readFile(absPath);
1146
1315
  map.set(missingFile.file_hash, {
1147
1316
  path: missingFile.file_path,
1148
1317
  content: buffer
@@ -1422,7 +1591,7 @@ function computeBackoff(attempts) {
1422
1591
  }
1423
1592
  async function readSymlinkTarget2(absPath) {
1424
1593
  try {
1425
- return await fs4__default.default.readlink(absPath);
1594
+ return await fs5__default.default.readlink(absPath);
1426
1595
  } catch {
1427
1596
  return null;
1428
1597
  }
@@ -1563,7 +1732,7 @@ var ServiceRunner = class {
1563
1732
  async handleEvent(event, absPath, stats) {
1564
1733
  if (!this.running) return;
1565
1734
  const root = this.runtime.config.rootPath;
1566
- const absolute = path__default.default.isAbsolute(absPath) ? absPath : path__default.default.join(root, absPath);
1735
+ const absolute = path2__default.default.isAbsolute(absPath) ? absPath : path2__default.default.join(root, absPath);
1567
1736
  if (!isInsideRoot(root, absolute)) {
1568
1737
  return;
1569
1738
  }
@@ -1583,7 +1752,7 @@ var ServiceRunner = class {
1583
1752
  async handleAddChange(absPath, _stats) {
1584
1753
  let fileStats;
1585
1754
  try {
1586
- fileStats = await fs4__default.default.lstat(absPath);
1755
+ fileStats = await fs5__default.default.lstat(absPath);
1587
1756
  } catch (error) {
1588
1757
  this.runtime.logger.warn(
1589
1758
  { err: error, path: absPath },
@@ -1829,7 +1998,6 @@ async function runService(params) {
1829
1998
 
1830
1999
  // src/cli.ts
1831
2000
  var ENV_FLAG_MAP = {
1832
- root: "CODERULE_ROOT",
1833
2001
  "data-dir": "CODERULE_DATA_DIR",
1834
2002
  "auth-url": "CODERULE_AUTH_URL",
1835
2003
  "sync-url": "CODERULE_SYNC_URL",
@@ -1876,6 +2044,7 @@ Options:
1876
2044
  }
1877
2045
  function parseArgs(argv) {
1878
2046
  let token = process.env.CODERULE_TOKEN;
2047
+ let rootPath;
1879
2048
  let mode = "service";
1880
2049
  let clean = false;
1881
2050
  let inlineHasher = false;
@@ -1903,6 +2072,13 @@ function parseArgs(argv) {
1903
2072
  inlineHasher = true;
1904
2073
  continue;
1905
2074
  }
2075
+ if (arg === "--root") {
2076
+ if (args.length === 0) {
2077
+ throw new Error("Option --root requires a value");
2078
+ }
2079
+ rootPath = args.shift();
2080
+ continue;
2081
+ }
1906
2082
  if (arg.startsWith("--")) {
1907
2083
  const flag = arg.slice(2);
1908
2084
  const envKey = ENV_FLAG_MAP[flag];
@@ -1935,22 +2111,22 @@ function parseArgs(argv) {
1935
2111
  "Missing token. Provide via argument or CODERULE_TOKEN env."
1936
2112
  );
1937
2113
  }
1938
- return { token, mode, clean, inlineHasher, env };
2114
+ return { token, rootPath, mode, clean, inlineHasher, env };
1939
2115
  }
1940
- async function ensureClean(configToken) {
1941
- const config = await resolveConfig({ token: configToken });
2116
+ async function ensureClean(configToken, rootPath) {
2117
+ const config = await resolveConfig({ token: configToken, rootPath });
1942
2118
  const targets = [
1943
2119
  config.dbPath,
1944
2120
  `${config.dbPath}-shm`,
1945
2121
  `${config.dbPath}-wal`
1946
2122
  ];
1947
- await Promise.all(targets.map((target) => fs4__default.default.rm(target, { force: true })));
1948
- await fs4__default.default.rm(path__default.default.join(config.dataDir, "watch", `${config.rootId}.sqlite-shm`), {
2123
+ await Promise.all(targets.map((target) => fs5__default.default.rm(target, { force: true })));
2124
+ await fs5__default.default.rm(path2__default.default.join(config.dataDir, "watch", `${config.rootId}.sqlite-shm`), {
1949
2125
  force: true
1950
2126
  }).catch(() => {
1951
2127
  });
1952
- const dir = path__default.default.dirname(config.dbPath);
1953
- await fs4__default.default.mkdir(dir, { recursive: true });
2128
+ const dir = path2__default.default.dirname(config.dbPath);
2129
+ await fs5__default.default.mkdir(dir, { recursive: true });
1954
2130
  console.log(`Removed scanner database at ${config.dbPath}`);
1955
2131
  }
1956
2132
  async function main() {
@@ -1967,13 +2143,16 @@ async function main() {
1967
2143
  process.env[key] = value;
1968
2144
  }
1969
2145
  if (options.clean) {
1970
- await ensureClean(options.token);
2146
+ await ensureClean(options.token, options.rootPath);
1971
2147
  }
1972
2148
  if (options.mode === "initial") {
1973
- const result = await runInitialSync({ token: options.token });
2149
+ const result = await runInitialSync({
2150
+ token: options.token,
2151
+ rootPath: options.rootPath
2152
+ });
1974
2153
  console.log("Initial sync result:", JSON.stringify(result, null, 2));
1975
2154
  } else {
1976
- await runService({ token: options.token });
2155
+ await runService({ token: options.token, rootPath: options.rootPath });
1977
2156
  }
1978
2157
  } catch (error) {
1979
2158
  console.error("Scanner CLI failed:", error);