@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/mcp-cli.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs5 from 'fs/promises';
|
|
3
|
+
import path2 from 'path';
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
5
|
import { createHash } from 'crypto';
|
|
6
6
|
import envPaths from 'env-paths';
|
|
7
7
|
import pino from 'pino';
|
|
8
|
+
import os from 'os';
|
|
8
9
|
import Database from 'better-sqlite3';
|
|
9
10
|
import { Qulite, JobStatus, enqueueFsEvent } from '@coderule/qulite';
|
|
10
11
|
import { CoderuleClients, ASTHttpClient, SyncHttpClient } from '@coderule/clients';
|
|
11
|
-
import
|
|
12
|
+
import fs3 from 'fs';
|
|
12
13
|
import { Worker } from 'worker_threads';
|
|
13
14
|
import chokidar from 'chokidar';
|
|
14
15
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -35,6 +36,166 @@ var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
|
|
|
35
36
|
var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
|
|
36
37
|
var DEFAULT_UPLOAD_CHUNK_SIZE = 1;
|
|
37
38
|
var DEFAULT_MAX_QUERY_WAIT_MS = 5e4;
|
|
39
|
+
function collectCandidateSources(options) {
|
|
40
|
+
const sources = [];
|
|
41
|
+
if (options?.cliRoot) {
|
|
42
|
+
sources.push({
|
|
43
|
+
value: options.cliRoot,
|
|
44
|
+
source: "cli-arg",
|
|
45
|
+
baseConfidence: 1
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (process.env.CODERULE_ROOT) {
|
|
49
|
+
sources.push({
|
|
50
|
+
value: process.env.CODERULE_ROOT,
|
|
51
|
+
source: "env-coderule-root",
|
|
52
|
+
baseConfidence: 0.9
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (process.env.WORKSPACE_FOLDER_PATHS) {
|
|
56
|
+
sources.push({
|
|
57
|
+
value: process.env.WORKSPACE_FOLDER_PATHS,
|
|
58
|
+
source: "env-workspace",
|
|
59
|
+
baseConfidence: 0.8
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
sources.push({
|
|
63
|
+
value: process.cwd(),
|
|
64
|
+
source: "process-cwd",
|
|
65
|
+
baseConfidence: 0.6
|
|
66
|
+
});
|
|
67
|
+
return sources;
|
|
68
|
+
}
|
|
69
|
+
async function pathExists(targetPath) {
|
|
70
|
+
try {
|
|
71
|
+
await fs5.access(targetPath);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function isDirectory(targetPath) {
|
|
78
|
+
try {
|
|
79
|
+
const stat = await fs5.stat(targetPath);
|
|
80
|
+
return stat.isDirectory();
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function hasGitDirectory(rootPath) {
|
|
86
|
+
const gitPath = path2.join(rootPath, ".git");
|
|
87
|
+
return pathExists(gitPath);
|
|
88
|
+
}
|
|
89
|
+
function isShallowPath(absolutePath) {
|
|
90
|
+
const normalized = absolutePath.replace(/\\/g, "/");
|
|
91
|
+
const separatorCount = (normalized.match(/\//g) || []).length;
|
|
92
|
+
return separatorCount <= 1;
|
|
93
|
+
}
|
|
94
|
+
function isHomeDirectory(candidatePath) {
|
|
95
|
+
const home = os.homedir();
|
|
96
|
+
const normalizedCandidate = path2.normalize(candidatePath);
|
|
97
|
+
const normalizedHome = path2.normalize(home);
|
|
98
|
+
return normalizedCandidate === normalizedHome;
|
|
99
|
+
}
|
|
100
|
+
async function applyModifiers(candidate) {
|
|
101
|
+
const modifiers = [];
|
|
102
|
+
if (await hasGitDirectory(candidate.path)) {
|
|
103
|
+
modifiers.push({ reason: "has .git directory", delta: 0.1 });
|
|
104
|
+
}
|
|
105
|
+
if (isShallowPath(candidate.path)) {
|
|
106
|
+
modifiers.push({ reason: "shallow path (likely root)", delta: -0.2 });
|
|
107
|
+
}
|
|
108
|
+
if (isHomeDirectory(candidate.path)) {
|
|
109
|
+
modifiers.push({ reason: "is home directory", delta: -0.3 });
|
|
110
|
+
}
|
|
111
|
+
const totalModifier = modifiers.reduce((sum, m) => sum + m.delta, 0);
|
|
112
|
+
const finalConfidence = candidate.baseConfidence + totalModifier;
|
|
113
|
+
return {
|
|
114
|
+
...candidate,
|
|
115
|
+
modifiers,
|
|
116
|
+
finalConfidence
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function createCandidate(source) {
|
|
120
|
+
const absolutePath = path2.resolve(source.value);
|
|
121
|
+
const exists = await pathExists(absolutePath);
|
|
122
|
+
const isDir = exists ? await isDirectory(absolutePath) : false;
|
|
123
|
+
const candidate = {
|
|
124
|
+
path: absolutePath,
|
|
125
|
+
source: source.source,
|
|
126
|
+
baseConfidence: source.baseConfidence,
|
|
127
|
+
modifiers: [],
|
|
128
|
+
finalConfidence: source.baseConfidence,
|
|
129
|
+
exists,
|
|
130
|
+
isDirectory: isDir
|
|
131
|
+
};
|
|
132
|
+
if (exists && isDir) {
|
|
133
|
+
return applyModifiers(candidate);
|
|
134
|
+
}
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
async function resolveRootPath(options) {
|
|
138
|
+
const logger2 = options?.logger;
|
|
139
|
+
const sources = collectCandidateSources(options);
|
|
140
|
+
const candidates = await Promise.all(sources.map(createCandidate));
|
|
141
|
+
logger2?.debug(
|
|
142
|
+
{
|
|
143
|
+
candidates: candidates.map((c) => ({
|
|
144
|
+
path: c.path,
|
|
145
|
+
source: c.source,
|
|
146
|
+
baseConfidence: c.baseConfidence,
|
|
147
|
+
finalConfidence: c.finalConfidence,
|
|
148
|
+
exists: c.exists,
|
|
149
|
+
isDirectory: c.isDirectory,
|
|
150
|
+
modifiers: c.modifiers
|
|
151
|
+
}))
|
|
152
|
+
},
|
|
153
|
+
"Collected root path candidates"
|
|
154
|
+
);
|
|
155
|
+
const validCandidates = candidates.filter((c) => c.exists && c.isDirectory);
|
|
156
|
+
if (validCandidates.length === 0) {
|
|
157
|
+
const attempted = candidates.map((c) => c.path).join(", ");
|
|
158
|
+
throw new Error(
|
|
159
|
+
`No valid root directory found. Attempted: ${attempted}. Ensure the directory exists and is accessible.`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const sourcePriority = {
|
|
163
|
+
"cli-arg": 1,
|
|
164
|
+
"env-coderule-root": 2,
|
|
165
|
+
"env-workspace": 3,
|
|
166
|
+
"process-cwd": 4
|
|
167
|
+
};
|
|
168
|
+
validCandidates.sort((a, b) => {
|
|
169
|
+
const confidenceDiff = b.finalConfidence - a.finalConfidence;
|
|
170
|
+
if (confidenceDiff !== 0) {
|
|
171
|
+
return confidenceDiff;
|
|
172
|
+
}
|
|
173
|
+
return sourcePriority[a.source] - sourcePriority[b.source];
|
|
174
|
+
});
|
|
175
|
+
const best = validCandidates[0];
|
|
176
|
+
if (best.finalConfidence < 0.7) {
|
|
177
|
+
logger2?.warn(
|
|
178
|
+
{
|
|
179
|
+
path: best.path,
|
|
180
|
+
source: best.source,
|
|
181
|
+
confidence: best.finalConfidence,
|
|
182
|
+
modifiers: best.modifiers
|
|
183
|
+
},
|
|
184
|
+
"Selected root path has low confidence"
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
logger2?.info(
|
|
188
|
+
{
|
|
189
|
+
path: best.path,
|
|
190
|
+
source: best.source,
|
|
191
|
+
confidence: best.finalConfidence,
|
|
192
|
+
modifiers: best.modifiers
|
|
193
|
+
},
|
|
194
|
+
"Resolved root path"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return best;
|
|
198
|
+
}
|
|
38
199
|
|
|
39
200
|
// src/config/Configurator.ts
|
|
40
201
|
var DEFAULT_RETRIEVAL_FORMATTER = "standard";
|
|
@@ -47,9 +208,9 @@ var DEFAULTS = {
|
|
|
47
208
|
maxSnapshotAttempts: DEFAULT_MAX_SNAPSHOT_ATTEMPTS
|
|
48
209
|
};
|
|
49
210
|
function normalizeRoot(root) {
|
|
50
|
-
const resolved =
|
|
51
|
-
const normalized =
|
|
52
|
-
return normalized.split(
|
|
211
|
+
const resolved = path2.resolve(root);
|
|
212
|
+
const normalized = path2.normalize(resolved);
|
|
213
|
+
return normalized.split(path2.sep).join("/");
|
|
53
214
|
}
|
|
54
215
|
function sha256(input) {
|
|
55
216
|
return createHash("sha256").update(input).digest("hex");
|
|
@@ -81,7 +242,8 @@ function parseFormatter(value) {
|
|
|
81
242
|
);
|
|
82
243
|
}
|
|
83
244
|
async function resolveConfig({
|
|
84
|
-
token
|
|
245
|
+
token,
|
|
246
|
+
rootPath: cliRoot
|
|
85
247
|
}) {
|
|
86
248
|
const resolvedToken = token ?? process.env.CODERULE_TOKEN;
|
|
87
249
|
if (!resolvedToken) {
|
|
@@ -89,14 +251,17 @@ async function resolveConfig({
|
|
|
89
251
|
"Missing token: provide params.token or CODERULE_TOKEN env"
|
|
90
252
|
);
|
|
91
253
|
}
|
|
92
|
-
const rootCandidate =
|
|
93
|
-
|
|
254
|
+
const rootCandidate = await resolveRootPath({
|
|
255
|
+
cliRoot,
|
|
256
|
+
logger: logger.child({ scope: "root-resolver" })
|
|
257
|
+
});
|
|
258
|
+
const rootPath = rootCandidate.path;
|
|
94
259
|
const normalized = normalizeRoot(rootPath);
|
|
95
260
|
const rootId = sha256(normalized);
|
|
96
261
|
const dataDir = process.env.CODERULE_DATA_DIR || envPaths("coderule").data;
|
|
97
|
-
const watchDir =
|
|
98
|
-
await
|
|
99
|
-
const dbPath =
|
|
262
|
+
const watchDir = path2.join(dataDir, "watch");
|
|
263
|
+
await fs5.mkdir(watchDir, { recursive: true });
|
|
264
|
+
const dbPath = path2.join(watchDir, `${rootId}.sqlite`);
|
|
100
265
|
const baseConfig = {
|
|
101
266
|
token: resolvedToken,
|
|
102
267
|
rootPath,
|
|
@@ -173,6 +338,9 @@ async function resolveConfig({
|
|
|
173
338
|
logger.debug(
|
|
174
339
|
{
|
|
175
340
|
rootPath,
|
|
341
|
+
rootSource: rootCandidate.source,
|
|
342
|
+
rootConfidence: rootCandidate.finalConfidence,
|
|
343
|
+
rootModifiers: rootCandidate.modifiers,
|
|
176
344
|
dbPath,
|
|
177
345
|
dataDir,
|
|
178
346
|
authBaseUrl: baseConfig.authBaseUrl,
|
|
@@ -673,7 +841,7 @@ async function fetchVisitorRules(clients, logger2) {
|
|
|
673
841
|
return rules;
|
|
674
842
|
}
|
|
675
843
|
function toPosix(input) {
|
|
676
|
-
return input.split(
|
|
844
|
+
return input.split(path2.sep).join("/");
|
|
677
845
|
}
|
|
678
846
|
function getLowerBasename(input) {
|
|
679
847
|
const base = input.split("/").pop();
|
|
@@ -692,7 +860,7 @@ function compileRulesBundle(rules) {
|
|
|
692
860
|
if (!info) {
|
|
693
861
|
logger.debug({ path: fullPath }, "Predicate fallback lstat");
|
|
694
862
|
try {
|
|
695
|
-
info =
|
|
863
|
+
info = fs3.lstatSync(fullPath);
|
|
696
864
|
} catch (error) {
|
|
697
865
|
logger.warn(
|
|
698
866
|
{ err: error, path: fullPath },
|
|
@@ -841,14 +1009,14 @@ var Hasher = class {
|
|
|
841
1009
|
await new Promise((resolve) => setImmediate(resolve));
|
|
842
1010
|
}
|
|
843
1011
|
resolveAbsolutePath(record) {
|
|
844
|
-
if (
|
|
1012
|
+
if (path2.isAbsolute(record.display_path)) {
|
|
845
1013
|
return record.display_path;
|
|
846
1014
|
}
|
|
847
|
-
return
|
|
1015
|
+
return path2.join(this.options.rootPath, record.rel_path);
|
|
848
1016
|
}
|
|
849
1017
|
async ensureExists(absPath, record) {
|
|
850
1018
|
try {
|
|
851
|
-
await
|
|
1019
|
+
await fs5.access(absPath);
|
|
852
1020
|
return true;
|
|
853
1021
|
} catch (error) {
|
|
854
1022
|
this.log.warn(
|
|
@@ -871,7 +1039,7 @@ var Hasher = class {
|
|
|
871
1039
|
const service = createHash("sha256");
|
|
872
1040
|
service.update(relPath);
|
|
873
1041
|
service.update("\n");
|
|
874
|
-
const stream =
|
|
1042
|
+
const stream = fs3.createReadStream(absPath);
|
|
875
1043
|
stream.on("data", (chunk) => {
|
|
876
1044
|
content.update(chunk);
|
|
877
1045
|
service.update(chunk);
|
|
@@ -986,13 +1154,13 @@ async function bootstrap(params) {
|
|
|
986
1154
|
return runtime;
|
|
987
1155
|
}
|
|
988
1156
|
function toPosixRelative(root, target) {
|
|
989
|
-
const rel =
|
|
1157
|
+
const rel = path2.relative(root, target);
|
|
990
1158
|
if (!rel || rel === "") return "";
|
|
991
|
-
return rel.split(
|
|
1159
|
+
return rel.split(path2.sep).join("/");
|
|
992
1160
|
}
|
|
993
1161
|
function isInsideRoot(root, target) {
|
|
994
|
-
const rel =
|
|
995
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
1162
|
+
const rel = path2.relative(root, target);
|
|
1163
|
+
return rel === "" || !rel.startsWith("..") && !path2.isAbsolute(rel);
|
|
996
1164
|
}
|
|
997
1165
|
|
|
998
1166
|
// src/fs/Walker.ts
|
|
@@ -1007,7 +1175,7 @@ function cloneStats(stats) {
|
|
|
1007
1175
|
}
|
|
1008
1176
|
async function readSymlinkTarget(absPath, log) {
|
|
1009
1177
|
try {
|
|
1010
|
-
return await
|
|
1178
|
+
return await fs5.readlink(absPath);
|
|
1011
1179
|
} catch (error) {
|
|
1012
1180
|
log.warn({ err: error, path: absPath }, "Failed to read symlink target");
|
|
1013
1181
|
return null;
|
|
@@ -1017,13 +1185,13 @@ async function walkDirectory(current, opts, stats) {
|
|
|
1017
1185
|
const dirLogger = opts.logger;
|
|
1018
1186
|
let dirents;
|
|
1019
1187
|
try {
|
|
1020
|
-
dirents = await
|
|
1188
|
+
dirents = await fs5.readdir(current, { withFileTypes: true });
|
|
1021
1189
|
} catch (error) {
|
|
1022
1190
|
dirLogger.warn({ err: error, path: current }, "Failed to read directory");
|
|
1023
1191
|
return;
|
|
1024
1192
|
}
|
|
1025
1193
|
for (const dirent of dirents) {
|
|
1026
|
-
const absPath =
|
|
1194
|
+
const absPath = path2.join(current, dirent.name);
|
|
1027
1195
|
const relPath = toPosixRelative(opts.rootPath, absPath);
|
|
1028
1196
|
if (dirent.isDirectory()) {
|
|
1029
1197
|
if (shouldPruneDirectory(relPath, opts.bundle)) {
|
|
@@ -1036,7 +1204,7 @@ async function walkDirectory(current, opts, stats) {
|
|
|
1036
1204
|
if (dirent.isSymbolicLink() || dirent.isFile()) {
|
|
1037
1205
|
let stat;
|
|
1038
1206
|
try {
|
|
1039
|
-
stat = await
|
|
1207
|
+
stat = await fs5.lstat(absPath);
|
|
1040
1208
|
} catch (error) {
|
|
1041
1209
|
dirLogger.warn({ err: error, path: absPath }, "Failed to stat file");
|
|
1042
1210
|
continue;
|
|
@@ -1128,9 +1296,9 @@ async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts
|
|
|
1128
1296
|
const list = chunks[idx];
|
|
1129
1297
|
const map = /* @__PURE__ */ new Map();
|
|
1130
1298
|
for (const missingFile of list) {
|
|
1131
|
-
const absPath =
|
|
1299
|
+
const absPath = path2.join(rootPath, missingFile.file_path);
|
|
1132
1300
|
try {
|
|
1133
|
-
const buffer = await
|
|
1301
|
+
const buffer = await fs5.readFile(absPath);
|
|
1134
1302
|
map.set(missingFile.file_hash, {
|
|
1135
1303
|
path: missingFile.file_path,
|
|
1136
1304
|
content: buffer
|
|
@@ -1257,6 +1425,21 @@ async function runInitialSyncPipeline(runtime, options) {
|
|
|
1257
1425
|
hashLogger.debug("Hasher processed batch");
|
|
1258
1426
|
}
|
|
1259
1427
|
}
|
|
1428
|
+
if (options?.blockUntilReady !== false) {
|
|
1429
|
+
const syncLogger = runtime.logger.child({ scope: "snapshot" });
|
|
1430
|
+
const result = await publishSnapshot(
|
|
1431
|
+
runtime.config.rootPath,
|
|
1432
|
+
runtime.filesRepo,
|
|
1433
|
+
runtime.snapshotsRepo,
|
|
1434
|
+
runtime.clients.sync,
|
|
1435
|
+
syncLogger,
|
|
1436
|
+
{
|
|
1437
|
+
maxAttempts: runtime.config.maxSnapshotAttempts,
|
|
1438
|
+
uploadChunkSize: runtime.config.uploadChunkSize
|
|
1439
|
+
}
|
|
1440
|
+
);
|
|
1441
|
+
return result;
|
|
1442
|
+
}
|
|
1260
1443
|
const computation = computeSnapshot(runtime.filesRepo);
|
|
1261
1444
|
const createdAt = Date.now();
|
|
1262
1445
|
runtime.snapshotsRepo.insert(
|
|
@@ -1395,7 +1578,7 @@ function computeBackoff(attempts) {
|
|
|
1395
1578
|
}
|
|
1396
1579
|
async function readSymlinkTarget2(absPath) {
|
|
1397
1580
|
try {
|
|
1398
|
-
return await
|
|
1581
|
+
return await fs5.readlink(absPath);
|
|
1399
1582
|
} catch {
|
|
1400
1583
|
return null;
|
|
1401
1584
|
}
|
|
@@ -1536,7 +1719,7 @@ var ServiceRunner = class {
|
|
|
1536
1719
|
async handleEvent(event, absPath, stats) {
|
|
1537
1720
|
if (!this.running) return;
|
|
1538
1721
|
const root = this.runtime.config.rootPath;
|
|
1539
|
-
const absolute =
|
|
1722
|
+
const absolute = path2.isAbsolute(absPath) ? absPath : path2.join(root, absPath);
|
|
1540
1723
|
if (!isInsideRoot(root, absolute)) {
|
|
1541
1724
|
return;
|
|
1542
1725
|
}
|
|
@@ -1556,7 +1739,7 @@ var ServiceRunner = class {
|
|
|
1556
1739
|
async handleAddChange(absPath, _stats) {
|
|
1557
1740
|
let fileStats;
|
|
1558
1741
|
try {
|
|
1559
|
-
fileStats = await
|
|
1742
|
+
fileStats = await fs5.lstat(absPath);
|
|
1560
1743
|
} catch (error) {
|
|
1561
1744
|
this.runtime.logger.warn(
|
|
1562
1745
|
{ err: error, path: absPath },
|
|
@@ -2057,7 +2240,6 @@ ${statusText}`;
|
|
|
2057
2240
|
|
|
2058
2241
|
// src/mcp-cli.ts
|
|
2059
2242
|
var ENV_FLAG_MAP = {
|
|
2060
|
-
root: "CODERULE_ROOT",
|
|
2061
2243
|
"data-dir": "CODERULE_DATA_DIR",
|
|
2062
2244
|
"auth-url": "CODERULE_AUTH_URL",
|
|
2063
2245
|
"sync-url": "CODERULE_SYNC_URL",
|
|
@@ -2083,6 +2265,9 @@ function printUsage() {
|
|
|
2083
2265
|
console.log(
|
|
2084
2266
|
" --clean, --reindex Remove existing local state before running"
|
|
2085
2267
|
);
|
|
2268
|
+
console.log(
|
|
2269
|
+
" --exit Exit after reindex (use with --reindex)"
|
|
2270
|
+
);
|
|
2086
2271
|
console.log(
|
|
2087
2272
|
" --inline-hasher Force inline hashing (debug only)"
|
|
2088
2273
|
);
|
|
@@ -2128,7 +2313,9 @@ function printUsage() {
|
|
|
2128
2313
|
}
|
|
2129
2314
|
function parseArgs(argv) {
|
|
2130
2315
|
let token = process.env.CODERULE_TOKEN;
|
|
2316
|
+
let rootPath;
|
|
2131
2317
|
let clean = false;
|
|
2318
|
+
let exit = false;
|
|
2132
2319
|
let inlineHasher = false;
|
|
2133
2320
|
const env = {};
|
|
2134
2321
|
const args = [...argv];
|
|
@@ -2142,6 +2329,10 @@ function parseArgs(argv) {
|
|
|
2142
2329
|
clean = true;
|
|
2143
2330
|
continue;
|
|
2144
2331
|
}
|
|
2332
|
+
if (arg === "--exit") {
|
|
2333
|
+
exit = true;
|
|
2334
|
+
continue;
|
|
2335
|
+
}
|
|
2145
2336
|
if (arg === "--inline-hasher") {
|
|
2146
2337
|
inlineHasher = true;
|
|
2147
2338
|
continue;
|
|
@@ -2158,6 +2349,14 @@ function parseArgs(argv) {
|
|
|
2158
2349
|
token = arg.slice("--token=".length);
|
|
2159
2350
|
continue;
|
|
2160
2351
|
}
|
|
2352
|
+
if (arg === "--root") {
|
|
2353
|
+
const value = args.shift();
|
|
2354
|
+
if (!value) {
|
|
2355
|
+
throw new Error("Missing value for --root");
|
|
2356
|
+
}
|
|
2357
|
+
rootPath = value;
|
|
2358
|
+
continue;
|
|
2359
|
+
}
|
|
2161
2360
|
if (arg.startsWith("--")) {
|
|
2162
2361
|
const flag = arg.slice(2);
|
|
2163
2362
|
const envKey = ENV_FLAG_MAP[flag];
|
|
@@ -2190,22 +2389,22 @@ function parseArgs(argv) {
|
|
|
2190
2389
|
"Missing token. Provide via argument or CODERULE_TOKEN environment variable."
|
|
2191
2390
|
);
|
|
2192
2391
|
}
|
|
2193
|
-
return { token, clean, inlineHasher, env };
|
|
2392
|
+
return { token, rootPath, clean, exit, inlineHasher, env };
|
|
2194
2393
|
}
|
|
2195
|
-
async function ensureClean(configToken) {
|
|
2196
|
-
const config = await resolveConfig({ token: configToken });
|
|
2394
|
+
async function ensureClean(configToken, rootPath) {
|
|
2395
|
+
const config = await resolveConfig({ token: configToken, rootPath });
|
|
2197
2396
|
const targets = [
|
|
2198
2397
|
config.dbPath,
|
|
2199
2398
|
`${config.dbPath}-shm`,
|
|
2200
2399
|
`${config.dbPath}-wal`
|
|
2201
2400
|
];
|
|
2202
|
-
await Promise.all(targets.map((target) =>
|
|
2203
|
-
await
|
|
2401
|
+
await Promise.all(targets.map((target) => fs5.rm(target, { force: true })));
|
|
2402
|
+
await fs5.rm(path2.join(config.dataDir, "watch", `${config.rootId}.sqlite-shm`), {
|
|
2204
2403
|
force: true
|
|
2205
2404
|
}).catch(() => {
|
|
2206
2405
|
});
|
|
2207
|
-
const dir =
|
|
2208
|
-
await
|
|
2406
|
+
const dir = path2.dirname(config.dbPath);
|
|
2407
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
2209
2408
|
console.log(`Removed scanner database at ${config.dbPath}`);
|
|
2210
2409
|
}
|
|
2211
2410
|
function awaitShutdownSignals() {
|
|
@@ -2236,10 +2435,30 @@ async function main() {
|
|
|
2236
2435
|
process.env[key] = value;
|
|
2237
2436
|
}
|
|
2238
2437
|
if (options.clean) {
|
|
2239
|
-
await ensureClean(options.token);
|
|
2438
|
+
await ensureClean(options.token, options.rootPath);
|
|
2240
2439
|
}
|
|
2241
|
-
const runtime = await bootstrap({
|
|
2440
|
+
const runtime = await bootstrap({
|
|
2441
|
+
token: options.token,
|
|
2442
|
+
rootPath: options.rootPath
|
|
2443
|
+
});
|
|
2242
2444
|
const runner = new ServiceRunner(runtime);
|
|
2445
|
+
if (options.exit && options.clean) {
|
|
2446
|
+
try {
|
|
2447
|
+
const initial = await runInitialSyncPipeline(runtime, {
|
|
2448
|
+
blockUntilReady: true
|
|
2449
|
+
});
|
|
2450
|
+
runtime.logger.info(
|
|
2451
|
+
{
|
|
2452
|
+
snapshotHash: initial.snapshotHash,
|
|
2453
|
+
filesCount: initial.filesCount
|
|
2454
|
+
},
|
|
2455
|
+
"Reindex completed; exiting"
|
|
2456
|
+
);
|
|
2457
|
+
} finally {
|
|
2458
|
+
await runner.stop();
|
|
2459
|
+
}
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2243
2462
|
try {
|
|
2244
2463
|
await runner.prepareWatcher(true);
|
|
2245
2464
|
const server = createMcpServer({ runtime, runner });
|