@coderule/mcp 1.8.0 → 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/index.cjs CHANGED
@@ -2,24 +2,26 @@
2
2
 
3
3
  var pino = require('pino');
4
4
  var crypto = require('crypto');
5
- var fs4 = require('fs/promises');
6
- var path = require('path');
5
+ var fs5 = require('fs/promises');
6
+ var path2 = require('path');
7
7
  var envPaths = require('env-paths');
8
+ var os = require('os');
8
9
  var Database = require('better-sqlite3');
9
10
  var qulite = require('@coderule/qulite');
10
11
  var clients = require('@coderule/clients');
11
- var fs2 = require('fs');
12
+ var fs3 = require('fs');
12
13
  var worker_threads = require('worker_threads');
13
14
  var chokidar = require('chokidar');
14
15
 
15
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
17
 
17
18
  var pino__default = /*#__PURE__*/_interopDefault(pino);
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);
22
+ var os__default = /*#__PURE__*/_interopDefault(os);
21
23
  var Database__default = /*#__PURE__*/_interopDefault(Database);
22
- var fs2__default = /*#__PURE__*/_interopDefault(fs2);
24
+ var fs3__default = /*#__PURE__*/_interopDefault(fs3);
23
25
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
24
26
 
25
27
  // node_modules/tsup/assets/cjs_shims.js
@@ -46,6 +48,166 @@ var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
46
48
  var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
47
49
  var DEFAULT_UPLOAD_CHUNK_SIZE = 1;
48
50
  var DEFAULT_MAX_QUERY_WAIT_MS = 5e4;
51
+ function collectCandidateSources(options) {
52
+ const sources = [];
53
+ if (options?.cliRoot) {
54
+ sources.push({
55
+ value: options.cliRoot,
56
+ source: "cli-arg",
57
+ baseConfidence: 1
58
+ });
59
+ }
60
+ if (process.env.CODERULE_ROOT) {
61
+ sources.push({
62
+ value: process.env.CODERULE_ROOT,
63
+ source: "env-coderule-root",
64
+ baseConfidence: 0.9
65
+ });
66
+ }
67
+ if (process.env.WORKSPACE_FOLDER_PATHS) {
68
+ sources.push({
69
+ value: process.env.WORKSPACE_FOLDER_PATHS,
70
+ source: "env-workspace",
71
+ baseConfidence: 0.8
72
+ });
73
+ }
74
+ sources.push({
75
+ value: process.cwd(),
76
+ source: "process-cwd",
77
+ baseConfidence: 0.6
78
+ });
79
+ return sources;
80
+ }
81
+ async function pathExists(targetPath) {
82
+ try {
83
+ await fs5__default.default.access(targetPath);
84
+ return true;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ async function isDirectory(targetPath) {
90
+ try {
91
+ const stat = await fs5__default.default.stat(targetPath);
92
+ return stat.isDirectory();
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+ async function hasGitDirectory(rootPath) {
98
+ const gitPath = path2__default.default.join(rootPath, ".git");
99
+ return pathExists(gitPath);
100
+ }
101
+ function isShallowPath(absolutePath) {
102
+ const normalized = absolutePath.replace(/\\/g, "/");
103
+ const separatorCount = (normalized.match(/\//g) || []).length;
104
+ return separatorCount <= 1;
105
+ }
106
+ function isHomeDirectory(candidatePath) {
107
+ const home = os__default.default.homedir();
108
+ const normalizedCandidate = path2__default.default.normalize(candidatePath);
109
+ const normalizedHome = path2__default.default.normalize(home);
110
+ return normalizedCandidate === normalizedHome;
111
+ }
112
+ async function applyModifiers(candidate) {
113
+ const modifiers = [];
114
+ if (await hasGitDirectory(candidate.path)) {
115
+ modifiers.push({ reason: "has .git directory", delta: 0.1 });
116
+ }
117
+ if (isShallowPath(candidate.path)) {
118
+ modifiers.push({ reason: "shallow path (likely root)", delta: -0.2 });
119
+ }
120
+ if (isHomeDirectory(candidate.path)) {
121
+ modifiers.push({ reason: "is home directory", delta: -0.3 });
122
+ }
123
+ const totalModifier = modifiers.reduce((sum, m) => sum + m.delta, 0);
124
+ const finalConfidence = candidate.baseConfidence + totalModifier;
125
+ return {
126
+ ...candidate,
127
+ modifiers,
128
+ finalConfidence
129
+ };
130
+ }
131
+ async function createCandidate(source) {
132
+ const absolutePath = path2__default.default.resolve(source.value);
133
+ const exists = await pathExists(absolutePath);
134
+ const isDir = exists ? await isDirectory(absolutePath) : false;
135
+ const candidate = {
136
+ path: absolutePath,
137
+ source: source.source,
138
+ baseConfidence: source.baseConfidence,
139
+ modifiers: [],
140
+ finalConfidence: source.baseConfidence,
141
+ exists,
142
+ isDirectory: isDir
143
+ };
144
+ if (exists && isDir) {
145
+ return applyModifiers(candidate);
146
+ }
147
+ return candidate;
148
+ }
149
+ async function resolveRootPath(options) {
150
+ const logger2 = options?.logger;
151
+ const sources = collectCandidateSources(options);
152
+ const candidates = await Promise.all(sources.map(createCandidate));
153
+ logger2?.debug(
154
+ {
155
+ candidates: candidates.map((c) => ({
156
+ path: c.path,
157
+ source: c.source,
158
+ baseConfidence: c.baseConfidence,
159
+ finalConfidence: c.finalConfidence,
160
+ exists: c.exists,
161
+ isDirectory: c.isDirectory,
162
+ modifiers: c.modifiers
163
+ }))
164
+ },
165
+ "Collected root path candidates"
166
+ );
167
+ const validCandidates = candidates.filter((c) => c.exists && c.isDirectory);
168
+ if (validCandidates.length === 0) {
169
+ const attempted = candidates.map((c) => c.path).join(", ");
170
+ throw new Error(
171
+ `No valid root directory found. Attempted: ${attempted}. Ensure the directory exists and is accessible.`
172
+ );
173
+ }
174
+ const sourcePriority = {
175
+ "cli-arg": 1,
176
+ "env-coderule-root": 2,
177
+ "env-workspace": 3,
178
+ "process-cwd": 4
179
+ };
180
+ validCandidates.sort((a, b) => {
181
+ const confidenceDiff = b.finalConfidence - a.finalConfidence;
182
+ if (confidenceDiff !== 0) {
183
+ return confidenceDiff;
184
+ }
185
+ return sourcePriority[a.source] - sourcePriority[b.source];
186
+ });
187
+ const best = validCandidates[0];
188
+ if (best.finalConfidence < 0.7) {
189
+ logger2?.warn(
190
+ {
191
+ path: best.path,
192
+ source: best.source,
193
+ confidence: best.finalConfidence,
194
+ modifiers: best.modifiers
195
+ },
196
+ "Selected root path has low confidence"
197
+ );
198
+ } else {
199
+ logger2?.info(
200
+ {
201
+ path: best.path,
202
+ source: best.source,
203
+ confidence: best.finalConfidence,
204
+ modifiers: best.modifiers
205
+ },
206
+ "Resolved root path"
207
+ );
208
+ }
209
+ return best;
210
+ }
49
211
 
50
212
  // src/config/Configurator.ts
51
213
  var DEFAULT_RETRIEVAL_FORMATTER = "standard";
@@ -58,9 +220,9 @@ var DEFAULTS = {
58
220
  maxSnapshotAttempts: DEFAULT_MAX_SNAPSHOT_ATTEMPTS
59
221
  };
60
222
  function normalizeRoot(root) {
61
- const resolved = path__default.default.resolve(root);
62
- const normalized = path__default.default.normalize(resolved);
63
- return normalized.split(path__default.default.sep).join("/");
223
+ const resolved = path2__default.default.resolve(root);
224
+ const normalized = path2__default.default.normalize(resolved);
225
+ return normalized.split(path2__default.default.sep).join("/");
64
226
  }
65
227
  function sha256(input) {
66
228
  return crypto.createHash("sha256").update(input).digest("hex");
@@ -92,7 +254,8 @@ function parseFormatter(value) {
92
254
  );
93
255
  }
94
256
  async function resolveConfig({
95
- token
257
+ token,
258
+ rootPath: cliRoot
96
259
  }) {
97
260
  const resolvedToken = token ?? process.env.CODERULE_TOKEN;
98
261
  if (!resolvedToken) {
@@ -100,14 +263,17 @@ async function resolveConfig({
100
263
  "Missing token: provide params.token or CODERULE_TOKEN env"
101
264
  );
102
265
  }
103
- const rootCandidate = process.env.CODERULE_ROOT || process.cwd();
104
- const rootPath = path__default.default.resolve(rootCandidate);
266
+ const rootCandidate = await resolveRootPath({
267
+ cliRoot,
268
+ logger: logger.child({ scope: "root-resolver" })
269
+ });
270
+ const rootPath = rootCandidate.path;
105
271
  const normalized = normalizeRoot(rootPath);
106
272
  const rootId = sha256(normalized);
107
273
  const dataDir = process.env.CODERULE_DATA_DIR || envPaths__default.default("coderule").data;
108
- const watchDir = path__default.default.join(dataDir, "watch");
109
- await fs4__default.default.mkdir(watchDir, { recursive: true });
110
- const dbPath = path__default.default.join(watchDir, `${rootId}.sqlite`);
274
+ const watchDir = path2__default.default.join(dataDir, "watch");
275
+ await fs5__default.default.mkdir(watchDir, { recursive: true });
276
+ const dbPath = path2__default.default.join(watchDir, `${rootId}.sqlite`);
111
277
  const baseConfig = {
112
278
  token: resolvedToken,
113
279
  rootPath,
@@ -184,6 +350,9 @@ async function resolveConfig({
184
350
  logger.debug(
185
351
  {
186
352
  rootPath,
353
+ rootSource: rootCandidate.source,
354
+ rootConfidence: rootCandidate.finalConfidence,
355
+ rootModifiers: rootCandidate.modifiers,
187
356
  dbPath,
188
357
  dataDir,
189
358
  authBaseUrl: baseConfig.authBaseUrl,
@@ -684,7 +853,7 @@ async function fetchVisitorRules(clients, logger2) {
684
853
  return rules;
685
854
  }
686
855
  function toPosix(input) {
687
- return input.split(path__default.default.sep).join("/");
856
+ return input.split(path2__default.default.sep).join("/");
688
857
  }
689
858
  function getLowerBasename(input) {
690
859
  const base = input.split("/").pop();
@@ -703,7 +872,7 @@ function compileRulesBundle(rules) {
703
872
  if (!info) {
704
873
  logger.debug({ path: fullPath }, "Predicate fallback lstat");
705
874
  try {
706
- info = fs2__default.default.lstatSync(fullPath);
875
+ info = fs3__default.default.lstatSync(fullPath);
707
876
  } catch (error) {
708
877
  logger.warn(
709
878
  { err: error, path: fullPath },
@@ -852,14 +1021,14 @@ var Hasher = class {
852
1021
  await new Promise((resolve) => setImmediate(resolve));
853
1022
  }
854
1023
  resolveAbsolutePath(record) {
855
- if (path__default.default.isAbsolute(record.display_path)) {
1024
+ if (path2__default.default.isAbsolute(record.display_path)) {
856
1025
  return record.display_path;
857
1026
  }
858
- return path__default.default.join(this.options.rootPath, record.rel_path);
1027
+ return path2__default.default.join(this.options.rootPath, record.rel_path);
859
1028
  }
860
1029
  async ensureExists(absPath, record) {
861
1030
  try {
862
- await fs4__default.default.access(absPath);
1031
+ await fs5__default.default.access(absPath);
863
1032
  return true;
864
1033
  } catch (error) {
865
1034
  this.log.warn(
@@ -882,7 +1051,7 @@ var Hasher = class {
882
1051
  const service = crypto.createHash("sha256");
883
1052
  service.update(relPath);
884
1053
  service.update("\n");
885
- const stream = fs2__default.default.createReadStream(absPath);
1054
+ const stream = fs3__default.default.createReadStream(absPath);
886
1055
  stream.on("data", (chunk) => {
887
1056
  content.update(chunk);
888
1057
  service.update(chunk);
@@ -997,13 +1166,13 @@ async function bootstrap(params) {
997
1166
  return runtime;
998
1167
  }
999
1168
  function toPosixRelative(root, target) {
1000
- const rel = path__default.default.relative(root, target);
1169
+ const rel = path2__default.default.relative(root, target);
1001
1170
  if (!rel || rel === "") return "";
1002
- return rel.split(path__default.default.sep).join("/");
1171
+ return rel.split(path2__default.default.sep).join("/");
1003
1172
  }
1004
1173
  function isInsideRoot(root, target) {
1005
- const rel = path__default.default.relative(root, target);
1006
- return rel === "" || !rel.startsWith("..") && !path__default.default.isAbsolute(rel);
1174
+ const rel = path2__default.default.relative(root, target);
1175
+ return rel === "" || !rel.startsWith("..") && !path2__default.default.isAbsolute(rel);
1007
1176
  }
1008
1177
 
1009
1178
  // src/fs/Walker.ts
@@ -1018,7 +1187,7 @@ function cloneStats(stats) {
1018
1187
  }
1019
1188
  async function readSymlinkTarget(absPath, log) {
1020
1189
  try {
1021
- return await fs4__default.default.readlink(absPath);
1190
+ return await fs5__default.default.readlink(absPath);
1022
1191
  } catch (error) {
1023
1192
  log.warn({ err: error, path: absPath }, "Failed to read symlink target");
1024
1193
  return null;
@@ -1028,13 +1197,13 @@ async function walkDirectory(current, opts, stats) {
1028
1197
  const dirLogger = opts.logger;
1029
1198
  let dirents;
1030
1199
  try {
1031
- dirents = await fs4__default.default.readdir(current, { withFileTypes: true });
1200
+ dirents = await fs5__default.default.readdir(current, { withFileTypes: true });
1032
1201
  } catch (error) {
1033
1202
  dirLogger.warn({ err: error, path: current }, "Failed to read directory");
1034
1203
  return;
1035
1204
  }
1036
1205
  for (const dirent of dirents) {
1037
- const absPath = path__default.default.join(current, dirent.name);
1206
+ const absPath = path2__default.default.join(current, dirent.name);
1038
1207
  const relPath = toPosixRelative(opts.rootPath, absPath);
1039
1208
  if (dirent.isDirectory()) {
1040
1209
  if (shouldPruneDirectory(relPath, opts.bundle)) {
@@ -1047,7 +1216,7 @@ async function walkDirectory(current, opts, stats) {
1047
1216
  if (dirent.isSymbolicLink() || dirent.isFile()) {
1048
1217
  let stat;
1049
1218
  try {
1050
- stat = await fs4__default.default.lstat(absPath);
1219
+ stat = await fs5__default.default.lstat(absPath);
1051
1220
  } catch (error) {
1052
1221
  dirLogger.warn({ err: error, path: absPath }, "Failed to stat file");
1053
1222
  continue;
@@ -1139,9 +1308,9 @@ async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts
1139
1308
  const list = chunks[idx];
1140
1309
  const map = /* @__PURE__ */ new Map();
1141
1310
  for (const missingFile of list) {
1142
- const absPath = path__default.default.join(rootPath, missingFile.file_path);
1311
+ const absPath = path2__default.default.join(rootPath, missingFile.file_path);
1143
1312
  try {
1144
- const buffer = await fs4__default.default.readFile(absPath);
1313
+ const buffer = await fs5__default.default.readFile(absPath);
1145
1314
  map.set(missingFile.file_hash, {
1146
1315
  path: missingFile.file_path,
1147
1316
  content: buffer
@@ -1421,7 +1590,7 @@ function computeBackoff(attempts) {
1421
1590
  }
1422
1591
  async function readSymlinkTarget2(absPath) {
1423
1592
  try {
1424
- return await fs4__default.default.readlink(absPath);
1593
+ return await fs5__default.default.readlink(absPath);
1425
1594
  } catch {
1426
1595
  return null;
1427
1596
  }
@@ -1562,7 +1731,7 @@ var ServiceRunner = class {
1562
1731
  async handleEvent(event, absPath, stats) {
1563
1732
  if (!this.running) return;
1564
1733
  const root = this.runtime.config.rootPath;
1565
- const absolute = path__default.default.isAbsolute(absPath) ? absPath : path__default.default.join(root, absPath);
1734
+ const absolute = path2__default.default.isAbsolute(absPath) ? absPath : path2__default.default.join(root, absPath);
1566
1735
  if (!isInsideRoot(root, absolute)) {
1567
1736
  return;
1568
1737
  }
@@ -1582,7 +1751,7 @@ var ServiceRunner = class {
1582
1751
  async handleAddChange(absPath, _stats) {
1583
1752
  let fileStats;
1584
1753
  try {
1585
- fileStats = await fs4__default.default.lstat(absPath);
1754
+ fileStats = await fs5__default.default.lstat(absPath);
1586
1755
  } catch (error) {
1587
1756
  this.runtime.logger.warn(
1588
1757
  { err: error, path: absPath },