@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/cli.cjs +224 -45
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +220 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +203 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +199 -31
- package/dist/index.js.map +1 -1
- package/dist/mcp-cli.cjs +224 -44
- package/dist/mcp-cli.cjs.map +1 -1
- package/dist/mcp-cli.js +220 -41
- package/dist/mcp-cli.js.map +1 -1
- package/package.json +6 -4
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
|
|
6
|
-
var
|
|
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
|
|
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
|
|
19
|
-
var
|
|
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
|
|
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 =
|
|
62
|
-
const normalized =
|
|
63
|
-
return normalized.split(
|
|
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 =
|
|
104
|
-
|
|
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 =
|
|
109
|
-
await
|
|
110
|
-
const dbPath =
|
|
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(
|
|
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 =
|
|
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 (
|
|
1024
|
+
if (path2__default.default.isAbsolute(record.display_path)) {
|
|
856
1025
|
return record.display_path;
|
|
857
1026
|
}
|
|
858
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
1169
|
+
const rel = path2__default.default.relative(root, target);
|
|
1001
1170
|
if (!rel || rel === "") return "";
|
|
1002
|
-
return rel.split(
|
|
1171
|
+
return rel.split(path2__default.default.sep).join("/");
|
|
1003
1172
|
}
|
|
1004
1173
|
function isInsideRoot(root, target) {
|
|
1005
|
-
const rel =
|
|
1006
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
1311
|
+
const absPath = path2__default.default.join(rootPath, missingFile.file_path);
|
|
1143
1312
|
try {
|
|
1144
|
-
const buffer = await
|
|
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
|
|
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 =
|
|
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
|
|
1754
|
+
fileStats = await fs5__default.default.lstat(absPath);
|
|
1586
1755
|
} catch (error) {
|
|
1587
1756
|
this.runtime.logger.warn(
|
|
1588
1757
|
{ err: error, path: absPath },
|