@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/index.d.cts CHANGED
@@ -8,9 +8,11 @@ interface InitialSyncResult {
8
8
 
9
9
  declare function runInitialSync(params: {
10
10
  token?: string;
11
+ rootPath?: string;
11
12
  }): Promise<InitialSyncResult>;
12
13
  declare function runService(params: {
13
14
  token?: string;
15
+ rootPath?: string;
14
16
  }): Promise<void>;
15
17
 
16
18
  export { type InitialSyncResult, runInitialSync, runService };
package/dist/index.d.ts CHANGED
@@ -8,9 +8,11 @@ interface InitialSyncResult {
8
8
 
9
9
  declare function runInitialSync(params: {
10
10
  token?: string;
11
+ rootPath?: string;
11
12
  }): Promise<InitialSyncResult>;
12
13
  declare function runService(params: {
13
14
  token?: string;
15
+ rootPath?: string;
14
16
  }): Promise<void>;
15
17
 
16
18
  export { type InitialSyncResult, runInitialSync, runService };
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import pino from 'pino';
2
2
  import { createHash } from 'crypto';
3
- import fs4 from 'fs/promises';
4
- import path from 'path';
3
+ import fs5 from 'fs/promises';
4
+ import path2 from 'path';
5
5
  import envPaths from 'env-paths';
6
+ import os from 'os';
6
7
  import Database from 'better-sqlite3';
7
8
  import { Qulite, JobStatus, enqueueFsEvent } from '@coderule/qulite';
8
9
  import { CoderuleClients, ASTHttpClient, SyncHttpClient } from '@coderule/clients';
9
- import fs2 from 'fs';
10
+ import fs3 from 'fs';
10
11
  import { Worker } from 'worker_threads';
11
12
  import chokidar from 'chokidar';
12
13
 
@@ -32,6 +33,166 @@ var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
32
33
  var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
33
34
  var DEFAULT_UPLOAD_CHUNK_SIZE = 1;
34
35
  var DEFAULT_MAX_QUERY_WAIT_MS = 5e4;
36
+ function collectCandidateSources(options) {
37
+ const sources = [];
38
+ if (options?.cliRoot) {
39
+ sources.push({
40
+ value: options.cliRoot,
41
+ source: "cli-arg",
42
+ baseConfidence: 1
43
+ });
44
+ }
45
+ if (process.env.CODERULE_ROOT) {
46
+ sources.push({
47
+ value: process.env.CODERULE_ROOT,
48
+ source: "env-coderule-root",
49
+ baseConfidence: 0.9
50
+ });
51
+ }
52
+ if (process.env.WORKSPACE_FOLDER_PATHS) {
53
+ sources.push({
54
+ value: process.env.WORKSPACE_FOLDER_PATHS,
55
+ source: "env-workspace",
56
+ baseConfidence: 0.8
57
+ });
58
+ }
59
+ sources.push({
60
+ value: process.cwd(),
61
+ source: "process-cwd",
62
+ baseConfidence: 0.6
63
+ });
64
+ return sources;
65
+ }
66
+ async function pathExists(targetPath) {
67
+ try {
68
+ await fs5.access(targetPath);
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+ async function isDirectory(targetPath) {
75
+ try {
76
+ const stat = await fs5.stat(targetPath);
77
+ return stat.isDirectory();
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+ async function hasGitDirectory(rootPath) {
83
+ const gitPath = path2.join(rootPath, ".git");
84
+ return pathExists(gitPath);
85
+ }
86
+ function isShallowPath(absolutePath) {
87
+ const normalized = absolutePath.replace(/\\/g, "/");
88
+ const separatorCount = (normalized.match(/\//g) || []).length;
89
+ return separatorCount <= 1;
90
+ }
91
+ function isHomeDirectory(candidatePath) {
92
+ const home = os.homedir();
93
+ const normalizedCandidate = path2.normalize(candidatePath);
94
+ const normalizedHome = path2.normalize(home);
95
+ return normalizedCandidate === normalizedHome;
96
+ }
97
+ async function applyModifiers(candidate) {
98
+ const modifiers = [];
99
+ if (await hasGitDirectory(candidate.path)) {
100
+ modifiers.push({ reason: "has .git directory", delta: 0.1 });
101
+ }
102
+ if (isShallowPath(candidate.path)) {
103
+ modifiers.push({ reason: "shallow path (likely root)", delta: -0.2 });
104
+ }
105
+ if (isHomeDirectory(candidate.path)) {
106
+ modifiers.push({ reason: "is home directory", delta: -0.3 });
107
+ }
108
+ const totalModifier = modifiers.reduce((sum, m) => sum + m.delta, 0);
109
+ const finalConfidence = candidate.baseConfidence + totalModifier;
110
+ return {
111
+ ...candidate,
112
+ modifiers,
113
+ finalConfidence
114
+ };
115
+ }
116
+ async function createCandidate(source) {
117
+ const absolutePath = path2.resolve(source.value);
118
+ const exists = await pathExists(absolutePath);
119
+ const isDir = exists ? await isDirectory(absolutePath) : false;
120
+ const candidate = {
121
+ path: absolutePath,
122
+ source: source.source,
123
+ baseConfidence: source.baseConfidence,
124
+ modifiers: [],
125
+ finalConfidence: source.baseConfidence,
126
+ exists,
127
+ isDirectory: isDir
128
+ };
129
+ if (exists && isDir) {
130
+ return applyModifiers(candidate);
131
+ }
132
+ return candidate;
133
+ }
134
+ async function resolveRootPath(options) {
135
+ const logger2 = options?.logger;
136
+ const sources = collectCandidateSources(options);
137
+ const candidates = await Promise.all(sources.map(createCandidate));
138
+ logger2?.debug(
139
+ {
140
+ candidates: candidates.map((c) => ({
141
+ path: c.path,
142
+ source: c.source,
143
+ baseConfidence: c.baseConfidence,
144
+ finalConfidence: c.finalConfidence,
145
+ exists: c.exists,
146
+ isDirectory: c.isDirectory,
147
+ modifiers: c.modifiers
148
+ }))
149
+ },
150
+ "Collected root path candidates"
151
+ );
152
+ const validCandidates = candidates.filter((c) => c.exists && c.isDirectory);
153
+ if (validCandidates.length === 0) {
154
+ const attempted = candidates.map((c) => c.path).join(", ");
155
+ throw new Error(
156
+ `No valid root directory found. Attempted: ${attempted}. Ensure the directory exists and is accessible.`
157
+ );
158
+ }
159
+ const sourcePriority = {
160
+ "cli-arg": 1,
161
+ "env-coderule-root": 2,
162
+ "env-workspace": 3,
163
+ "process-cwd": 4
164
+ };
165
+ validCandidates.sort((a, b) => {
166
+ const confidenceDiff = b.finalConfidence - a.finalConfidence;
167
+ if (confidenceDiff !== 0) {
168
+ return confidenceDiff;
169
+ }
170
+ return sourcePriority[a.source] - sourcePriority[b.source];
171
+ });
172
+ const best = validCandidates[0];
173
+ if (best.finalConfidence < 0.7) {
174
+ logger2?.warn(
175
+ {
176
+ path: best.path,
177
+ source: best.source,
178
+ confidence: best.finalConfidence,
179
+ modifiers: best.modifiers
180
+ },
181
+ "Selected root path has low confidence"
182
+ );
183
+ } else {
184
+ logger2?.info(
185
+ {
186
+ path: best.path,
187
+ source: best.source,
188
+ confidence: best.finalConfidence,
189
+ modifiers: best.modifiers
190
+ },
191
+ "Resolved root path"
192
+ );
193
+ }
194
+ return best;
195
+ }
35
196
 
36
197
  // src/config/Configurator.ts
37
198
  var DEFAULT_RETRIEVAL_FORMATTER = "standard";
@@ -44,9 +205,9 @@ var DEFAULTS = {
44
205
  maxSnapshotAttempts: DEFAULT_MAX_SNAPSHOT_ATTEMPTS
45
206
  };
46
207
  function normalizeRoot(root) {
47
- const resolved = path.resolve(root);
48
- const normalized = path.normalize(resolved);
49
- return normalized.split(path.sep).join("/");
208
+ const resolved = path2.resolve(root);
209
+ const normalized = path2.normalize(resolved);
210
+ return normalized.split(path2.sep).join("/");
50
211
  }
51
212
  function sha256(input) {
52
213
  return createHash("sha256").update(input).digest("hex");
@@ -78,7 +239,8 @@ function parseFormatter(value) {
78
239
  );
79
240
  }
80
241
  async function resolveConfig({
81
- token
242
+ token,
243
+ rootPath: cliRoot
82
244
  }) {
83
245
  const resolvedToken = token ?? process.env.CODERULE_TOKEN;
84
246
  if (!resolvedToken) {
@@ -86,14 +248,17 @@ async function resolveConfig({
86
248
  "Missing token: provide params.token or CODERULE_TOKEN env"
87
249
  );
88
250
  }
89
- const rootCandidate = process.env.CODERULE_ROOT || process.cwd();
90
- const rootPath = path.resolve(rootCandidate);
251
+ const rootCandidate = await resolveRootPath({
252
+ cliRoot,
253
+ logger: logger.child({ scope: "root-resolver" })
254
+ });
255
+ const rootPath = rootCandidate.path;
91
256
  const normalized = normalizeRoot(rootPath);
92
257
  const rootId = sha256(normalized);
93
258
  const dataDir = process.env.CODERULE_DATA_DIR || envPaths("coderule").data;
94
- const watchDir = path.join(dataDir, "watch");
95
- await fs4.mkdir(watchDir, { recursive: true });
96
- const dbPath = path.join(watchDir, `${rootId}.sqlite`);
259
+ const watchDir = path2.join(dataDir, "watch");
260
+ await fs5.mkdir(watchDir, { recursive: true });
261
+ const dbPath = path2.join(watchDir, `${rootId}.sqlite`);
97
262
  const baseConfig = {
98
263
  token: resolvedToken,
99
264
  rootPath,
@@ -170,6 +335,9 @@ async function resolveConfig({
170
335
  logger.debug(
171
336
  {
172
337
  rootPath,
338
+ rootSource: rootCandidate.source,
339
+ rootConfidence: rootCandidate.finalConfidence,
340
+ rootModifiers: rootCandidate.modifiers,
173
341
  dbPath,
174
342
  dataDir,
175
343
  authBaseUrl: baseConfig.authBaseUrl,
@@ -670,7 +838,7 @@ async function fetchVisitorRules(clients, logger2) {
670
838
  return rules;
671
839
  }
672
840
  function toPosix(input) {
673
- return input.split(path.sep).join("/");
841
+ return input.split(path2.sep).join("/");
674
842
  }
675
843
  function getLowerBasename(input) {
676
844
  const base = input.split("/").pop();
@@ -689,7 +857,7 @@ function compileRulesBundle(rules) {
689
857
  if (!info) {
690
858
  logger.debug({ path: fullPath }, "Predicate fallback lstat");
691
859
  try {
692
- info = fs2.lstatSync(fullPath);
860
+ info = fs3.lstatSync(fullPath);
693
861
  } catch (error) {
694
862
  logger.warn(
695
863
  { err: error, path: fullPath },
@@ -838,14 +1006,14 @@ var Hasher = class {
838
1006
  await new Promise((resolve) => setImmediate(resolve));
839
1007
  }
840
1008
  resolveAbsolutePath(record) {
841
- if (path.isAbsolute(record.display_path)) {
1009
+ if (path2.isAbsolute(record.display_path)) {
842
1010
  return record.display_path;
843
1011
  }
844
- return path.join(this.options.rootPath, record.rel_path);
1012
+ return path2.join(this.options.rootPath, record.rel_path);
845
1013
  }
846
1014
  async ensureExists(absPath, record) {
847
1015
  try {
848
- await fs4.access(absPath);
1016
+ await fs5.access(absPath);
849
1017
  return true;
850
1018
  } catch (error) {
851
1019
  this.log.warn(
@@ -868,7 +1036,7 @@ var Hasher = class {
868
1036
  const service = createHash("sha256");
869
1037
  service.update(relPath);
870
1038
  service.update("\n");
871
- const stream = fs2.createReadStream(absPath);
1039
+ const stream = fs3.createReadStream(absPath);
872
1040
  stream.on("data", (chunk) => {
873
1041
  content.update(chunk);
874
1042
  service.update(chunk);
@@ -983,13 +1151,13 @@ async function bootstrap(params) {
983
1151
  return runtime;
984
1152
  }
985
1153
  function toPosixRelative(root, target) {
986
- const rel = path.relative(root, target);
1154
+ const rel = path2.relative(root, target);
987
1155
  if (!rel || rel === "") return "";
988
- return rel.split(path.sep).join("/");
1156
+ return rel.split(path2.sep).join("/");
989
1157
  }
990
1158
  function isInsideRoot(root, target) {
991
- const rel = path.relative(root, target);
992
- return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
1159
+ const rel = path2.relative(root, target);
1160
+ return rel === "" || !rel.startsWith("..") && !path2.isAbsolute(rel);
993
1161
  }
994
1162
 
995
1163
  // src/fs/Walker.ts
@@ -1004,7 +1172,7 @@ function cloneStats(stats) {
1004
1172
  }
1005
1173
  async function readSymlinkTarget(absPath, log) {
1006
1174
  try {
1007
- return await fs4.readlink(absPath);
1175
+ return await fs5.readlink(absPath);
1008
1176
  } catch (error) {
1009
1177
  log.warn({ err: error, path: absPath }, "Failed to read symlink target");
1010
1178
  return null;
@@ -1014,13 +1182,13 @@ async function walkDirectory(current, opts, stats) {
1014
1182
  const dirLogger = opts.logger;
1015
1183
  let dirents;
1016
1184
  try {
1017
- dirents = await fs4.readdir(current, { withFileTypes: true });
1185
+ dirents = await fs5.readdir(current, { withFileTypes: true });
1018
1186
  } catch (error) {
1019
1187
  dirLogger.warn({ err: error, path: current }, "Failed to read directory");
1020
1188
  return;
1021
1189
  }
1022
1190
  for (const dirent of dirents) {
1023
- const absPath = path.join(current, dirent.name);
1191
+ const absPath = path2.join(current, dirent.name);
1024
1192
  const relPath = toPosixRelative(opts.rootPath, absPath);
1025
1193
  if (dirent.isDirectory()) {
1026
1194
  if (shouldPruneDirectory(relPath, opts.bundle)) {
@@ -1033,7 +1201,7 @@ async function walkDirectory(current, opts, stats) {
1033
1201
  if (dirent.isSymbolicLink() || dirent.isFile()) {
1034
1202
  let stat;
1035
1203
  try {
1036
- stat = await fs4.lstat(absPath);
1204
+ stat = await fs5.lstat(absPath);
1037
1205
  } catch (error) {
1038
1206
  dirLogger.warn({ err: error, path: absPath }, "Failed to stat file");
1039
1207
  continue;
@@ -1125,9 +1293,9 @@ async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts
1125
1293
  const list = chunks[idx];
1126
1294
  const map = /* @__PURE__ */ new Map();
1127
1295
  for (const missingFile of list) {
1128
- const absPath = path.join(rootPath, missingFile.file_path);
1296
+ const absPath = path2.join(rootPath, missingFile.file_path);
1129
1297
  try {
1130
- const buffer = await fs4.readFile(absPath);
1298
+ const buffer = await fs5.readFile(absPath);
1131
1299
  map.set(missingFile.file_hash, {
1132
1300
  path: missingFile.file_path,
1133
1301
  content: buffer
@@ -1407,7 +1575,7 @@ function computeBackoff(attempts) {
1407
1575
  }
1408
1576
  async function readSymlinkTarget2(absPath) {
1409
1577
  try {
1410
- return await fs4.readlink(absPath);
1578
+ return await fs5.readlink(absPath);
1411
1579
  } catch {
1412
1580
  return null;
1413
1581
  }
@@ -1548,7 +1716,7 @@ var ServiceRunner = class {
1548
1716
  async handleEvent(event, absPath, stats) {
1549
1717
  if (!this.running) return;
1550
1718
  const root = this.runtime.config.rootPath;
1551
- const absolute = path.isAbsolute(absPath) ? absPath : path.join(root, absPath);
1719
+ const absolute = path2.isAbsolute(absPath) ? absPath : path2.join(root, absPath);
1552
1720
  if (!isInsideRoot(root, absolute)) {
1553
1721
  return;
1554
1722
  }
@@ -1568,7 +1736,7 @@ var ServiceRunner = class {
1568
1736
  async handleAddChange(absPath, _stats) {
1569
1737
  let fileStats;
1570
1738
  try {
1571
- fileStats = await fs4.lstat(absPath);
1739
+ fileStats = await fs5.lstat(absPath);
1572
1740
  } catch (error) {
1573
1741
  this.runtime.logger.warn(
1574
1742
  { err: error, path: absPath },