@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 +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 +264 -44
- package/dist/mcp-cli.cjs.map +1 -1
- package/dist/mcp-cli.js +260 -41
- package/dist/mcp-cli.js.map +1 -1
- package/package.json +6 -4
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
|
|
4
|
-
import
|
|
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
|
|
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 =
|
|
48
|
-
const normalized =
|
|
49
|
-
return normalized.split(
|
|
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 =
|
|
90
|
-
|
|
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 =
|
|
95
|
-
await
|
|
96
|
-
const dbPath =
|
|
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(
|
|
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 =
|
|
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 (
|
|
1009
|
+
if (path2.isAbsolute(record.display_path)) {
|
|
842
1010
|
return record.display_path;
|
|
843
1011
|
}
|
|
844
|
-
return
|
|
1012
|
+
return path2.join(this.options.rootPath, record.rel_path);
|
|
845
1013
|
}
|
|
846
1014
|
async ensureExists(absPath, record) {
|
|
847
1015
|
try {
|
|
848
|
-
await
|
|
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 =
|
|
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 =
|
|
1154
|
+
const rel = path2.relative(root, target);
|
|
987
1155
|
if (!rel || rel === "") return "";
|
|
988
|
-
return rel.split(
|
|
1156
|
+
return rel.split(path2.sep).join("/");
|
|
989
1157
|
}
|
|
990
1158
|
function isInsideRoot(root, target) {
|
|
991
|
-
const rel =
|
|
992
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
1296
|
+
const absPath = path2.join(rootPath, missingFile.file_path);
|
|
1129
1297
|
try {
|
|
1130
|
-
const buffer = await
|
|
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
|
|
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 =
|
|
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
|
|
1739
|
+
fileStats = await fs5.lstat(absPath);
|
|
1572
1740
|
} catch (error) {
|
|
1573
1741
|
this.runtime.logger.warn(
|
|
1574
1742
|
{ err: error, path: absPath },
|