@coderule/mcp 1.3.0 → 1.5.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 +135 -35
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +136 -36
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +135 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +136 -36
- package/dist/index.js.map +1 -1
- package/dist/mcp-cli.cjs +193 -45
- package/dist/mcp-cli.cjs.map +1 -1
- package/dist/mcp-cli.js +194 -46
- package/dist/mcp-cli.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp-cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createHash } from 'crypto';
|
|
|
6
6
|
import envPaths from 'env-paths';
|
|
7
7
|
import pino from 'pino';
|
|
8
8
|
import Database from 'better-sqlite3';
|
|
9
|
-
import { Qulite,
|
|
9
|
+
import { Qulite, JobStatus, enqueueFsEvent } from '@coderule/qulite';
|
|
10
10
|
import { CoderuleClients, ASTHttpClient, SyncHttpClient } from '@coderule/clients';
|
|
11
11
|
import fs2 from 'fs';
|
|
12
12
|
import { Worker } from 'worker_threads';
|
|
@@ -32,7 +32,7 @@ var DEFAULT_HEARTBEAT_CHECK_INTERVAL_MS = 5e3;
|
|
|
32
32
|
var DEFAULT_QUEUE_POLL_INTERVAL_MS = 500;
|
|
33
33
|
var DEFAULT_HASH_BATCH_SIZE = 32;
|
|
34
34
|
var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
|
|
35
|
-
var DEFAULT_HTTP_TIMEOUT_MS =
|
|
35
|
+
var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
|
|
36
36
|
|
|
37
37
|
// src/config/Configurator.ts
|
|
38
38
|
var DEFAULT_RETRIEVAL_FORMATTER = "standard";
|
|
@@ -537,6 +537,16 @@ var Outbox = class {
|
|
|
537
537
|
if (purged > 0) {
|
|
538
538
|
this.log.warn({ purged }, "Purged legacy fs_control jobs without kind");
|
|
539
539
|
}
|
|
540
|
+
try {
|
|
541
|
+
const counts = {
|
|
542
|
+
pending: this.queue.countByStatus(JobStatus.Pending),
|
|
543
|
+
processing: this.queue.countByStatus(JobStatus.Processing),
|
|
544
|
+
done: this.queue.countByStatus(JobStatus.Done),
|
|
545
|
+
failed: this.queue.countByStatus(JobStatus.Failed)
|
|
546
|
+
};
|
|
547
|
+
this.log.debug({ counts }, "Outbox initialized");
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
540
550
|
}
|
|
541
551
|
getQueue() {
|
|
542
552
|
return this.queue;
|
|
@@ -1048,44 +1058,109 @@ function computeSnapshot(filesRepo) {
|
|
|
1048
1058
|
totalSize
|
|
1049
1059
|
};
|
|
1050
1060
|
}
|
|
1051
|
-
async function
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
for (const missingFile of missing) {
|
|
1055
|
-
const absPath = path.join(rootPath, missingFile.file_path);
|
|
1061
|
+
async function withRetries(op, logger2, context, maxAttempts) {
|
|
1062
|
+
let attempt = 0;
|
|
1063
|
+
while (true) {
|
|
1056
1064
|
try {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1065
|
+
return await op();
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
attempt += 1;
|
|
1068
|
+
if (attempt >= maxAttempts) {
|
|
1069
|
+
logger2.error(
|
|
1070
|
+
{ err, ...context, attempt },
|
|
1071
|
+
"Operation failed after retries"
|
|
1072
|
+
);
|
|
1073
|
+
throw err;
|
|
1074
|
+
}
|
|
1075
|
+
const delay = Math.min(15e3, 1e3 * 2 ** (attempt - 1));
|
|
1063
1076
|
logger2.warn(
|
|
1064
|
-
{ err
|
|
1065
|
-
"
|
|
1077
|
+
{ err, ...context, attempt, delay },
|
|
1078
|
+
"Operation failed; retrying"
|
|
1066
1079
|
);
|
|
1080
|
+
await sleep(delay);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts, chunkSize = 64) {
|
|
1085
|
+
if (!missing || missing.length === 0) return;
|
|
1086
|
+
const total = missing.length;
|
|
1087
|
+
const chunks = [];
|
|
1088
|
+
for (let i = 0; i < total; i += chunkSize) {
|
|
1089
|
+
chunks.push(missing.slice(i, i + chunkSize));
|
|
1090
|
+
}
|
|
1091
|
+
for (let idx = 0; idx < chunks.length; idx++) {
|
|
1092
|
+
const list = chunks[idx];
|
|
1093
|
+
const map = /* @__PURE__ */ new Map();
|
|
1094
|
+
for (const missingFile of list) {
|
|
1095
|
+
const absPath = path.join(rootPath, missingFile.file_path);
|
|
1096
|
+
try {
|
|
1097
|
+
const buffer = await fs4.readFile(absPath);
|
|
1098
|
+
map.set(missingFile.file_hash, {
|
|
1099
|
+
path: missingFile.file_path,
|
|
1100
|
+
content: buffer
|
|
1101
|
+
});
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
logger2.warn(
|
|
1104
|
+
{ err: error, relPath: missingFile.file_path },
|
|
1105
|
+
"Failed to read missing file content"
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1067
1108
|
}
|
|
1109
|
+
if (map.size === 0) continue;
|
|
1110
|
+
await withRetries(
|
|
1111
|
+
() => syncClient.uploadFileContent(map),
|
|
1112
|
+
logger2,
|
|
1113
|
+
{
|
|
1114
|
+
op: "uploadFileContent",
|
|
1115
|
+
chunkIndex: idx + 1,
|
|
1116
|
+
chunks: chunks.length,
|
|
1117
|
+
files: map.size
|
|
1118
|
+
},
|
|
1119
|
+
maxAttempts
|
|
1120
|
+
);
|
|
1068
1121
|
}
|
|
1069
|
-
if (map.size === 0) return;
|
|
1070
|
-
await syncClient.uploadFileContent(map);
|
|
1071
1122
|
}
|
|
1072
|
-
async function ensureSnapshotCreated(rootPath, computation, syncClient, logger2) {
|
|
1123
|
+
async function ensureSnapshotCreated(rootPath, computation, syncClient, logger2, options) {
|
|
1073
1124
|
const { snapshotHash, files } = computation;
|
|
1074
|
-
|
|
1125
|
+
const maxAttempts = options?.maxAttempts ?? 5;
|
|
1126
|
+
const uploadChunkSize = options?.uploadChunkSize ?? 64;
|
|
1127
|
+
let status = await withRetries(
|
|
1128
|
+
() => syncClient.checkSnapshotStatus(snapshotHash),
|
|
1129
|
+
logger2,
|
|
1130
|
+
{ op: "checkSnapshotStatus", snapshotHash },
|
|
1131
|
+
maxAttempts
|
|
1132
|
+
);
|
|
1075
1133
|
if (status.status === "READY") {
|
|
1076
1134
|
logger2.info({ snapshotHash }, "Snapshot already READY");
|
|
1077
1135
|
return;
|
|
1078
1136
|
}
|
|
1079
1137
|
if (status.status === "NOT_FOUND" || status.status === "MISSING_CONTENT") {
|
|
1080
|
-
status = await
|
|
1138
|
+
status = await withRetries(
|
|
1139
|
+
() => syncClient.createSnapshot(snapshotHash, files),
|
|
1140
|
+
logger2,
|
|
1141
|
+
{ op: "createSnapshot", snapshotHash },
|
|
1142
|
+
maxAttempts
|
|
1143
|
+
);
|
|
1081
1144
|
}
|
|
1082
1145
|
if (status.status === "MISSING_CONTENT" && status.missing_files?.length) {
|
|
1083
1146
|
logger2.info(
|
|
1084
1147
|
{ missing: status.missing_files.length },
|
|
1085
1148
|
"Uploading missing file content"
|
|
1086
1149
|
);
|
|
1087
|
-
await uploadMissing(
|
|
1088
|
-
|
|
1150
|
+
await uploadMissing(
|
|
1151
|
+
rootPath,
|
|
1152
|
+
status.missing_files,
|
|
1153
|
+
syncClient,
|
|
1154
|
+
logger2,
|
|
1155
|
+
maxAttempts,
|
|
1156
|
+
uploadChunkSize
|
|
1157
|
+
);
|
|
1158
|
+
status = await withRetries(
|
|
1159
|
+
() => syncClient.createSnapshot(snapshotHash, files),
|
|
1160
|
+
logger2,
|
|
1161
|
+
{ op: "createSnapshot", snapshotHash },
|
|
1162
|
+
maxAttempts
|
|
1163
|
+
);
|
|
1089
1164
|
}
|
|
1090
1165
|
let attempt = 0;
|
|
1091
1166
|
while (status.status !== "READY") {
|
|
@@ -1095,13 +1170,24 @@ async function ensureSnapshotCreated(rootPath, computation, syncClient, logger2)
|
|
|
1095
1170
|
const delay = Math.min(5e3, 1e3 * Math.max(1, 2 ** attempt));
|
|
1096
1171
|
await sleep(delay);
|
|
1097
1172
|
attempt += 1;
|
|
1098
|
-
status = await
|
|
1173
|
+
status = await withRetries(
|
|
1174
|
+
() => syncClient.checkSnapshotStatus(snapshotHash),
|
|
1175
|
+
logger2,
|
|
1176
|
+
{ op: "checkSnapshotStatus", snapshotHash },
|
|
1177
|
+
maxAttempts
|
|
1178
|
+
);
|
|
1099
1179
|
}
|
|
1100
1180
|
logger2.info({ snapshotHash }, "Snapshot READY");
|
|
1101
1181
|
}
|
|
1102
|
-
async function publishSnapshot(rootPath, filesRepo, snapshotsRepo, syncClient, logger2) {
|
|
1182
|
+
async function publishSnapshot(rootPath, filesRepo, snapshotsRepo, syncClient, logger2, options) {
|
|
1103
1183
|
const computation = computeSnapshot(filesRepo);
|
|
1104
|
-
await ensureSnapshotCreated(
|
|
1184
|
+
await ensureSnapshotCreated(
|
|
1185
|
+
rootPath,
|
|
1186
|
+
computation,
|
|
1187
|
+
syncClient,
|
|
1188
|
+
logger2,
|
|
1189
|
+
options
|
|
1190
|
+
);
|
|
1105
1191
|
const createdAt = Date.now();
|
|
1106
1192
|
snapshotsRepo.insert(
|
|
1107
1193
|
computation.snapshotHash,
|
|
@@ -1141,7 +1227,8 @@ async function runInitialSyncPipeline(runtime) {
|
|
|
1141
1227
|
runtime.filesRepo,
|
|
1142
1228
|
runtime.snapshotsRepo,
|
|
1143
1229
|
runtime.clients.sync,
|
|
1144
|
-
syncLogger
|
|
1230
|
+
syncLogger,
|
|
1231
|
+
{ maxAttempts: runtime.config.maxSnapshotAttempts }
|
|
1145
1232
|
);
|
|
1146
1233
|
return result;
|
|
1147
1234
|
}
|
|
@@ -1544,7 +1631,8 @@ var ServiceRunner = class {
|
|
|
1544
1631
|
this.runtime.filesRepo,
|
|
1545
1632
|
this.runtime.snapshotsRepo,
|
|
1546
1633
|
this.runtime.clients.sync,
|
|
1547
|
-
log
|
|
1634
|
+
log,
|
|
1635
|
+
{ maxAttempts: this.runtime.config.maxSnapshotAttempts }
|
|
1548
1636
|
);
|
|
1549
1637
|
this.runtime.outbox.ack(job.id, this.fsControlLeaseOwner);
|
|
1550
1638
|
this.state.updateSnapshotReady(result.createdAt);
|
|
@@ -1602,6 +1690,63 @@ function collectIndexingStatus(runtime, runner) {
|
|
|
1602
1690
|
service: runner.getServiceStateSnapshot()
|
|
1603
1691
|
};
|
|
1604
1692
|
}
|
|
1693
|
+
function formatStatus(status) {
|
|
1694
|
+
const lines = [];
|
|
1695
|
+
lines.push("=== Coderule Indexing Status ===");
|
|
1696
|
+
lines.push("");
|
|
1697
|
+
lines.push(`Timestamp: ${new Date(status.timestamp).toISOString()}`);
|
|
1698
|
+
lines.push("");
|
|
1699
|
+
lines.push("Repository:");
|
|
1700
|
+
lines.push(` ID: ${status.root.id}`);
|
|
1701
|
+
lines.push(` Path: ${status.root.path}`);
|
|
1702
|
+
lines.push("");
|
|
1703
|
+
lines.push("Files:");
|
|
1704
|
+
lines.push(` Total: ${status.files.total}`);
|
|
1705
|
+
lines.push(" States:");
|
|
1706
|
+
lines.push(` Clean: ${status.files.byState.clean}`);
|
|
1707
|
+
lines.push(` Dirty: ${status.files.byState.dirty}`);
|
|
1708
|
+
lines.push(` Hashing: ${status.files.byState.hashing}`);
|
|
1709
|
+
lines.push(` Missing: ${status.files.byState.missing}`);
|
|
1710
|
+
lines.push("");
|
|
1711
|
+
lines.push("Queue:");
|
|
1712
|
+
lines.push(` Pending: ${status.queue.pending}`);
|
|
1713
|
+
lines.push(` Processing: ${status.queue.processing}`);
|
|
1714
|
+
lines.push(` Done: ${status.queue.done}`);
|
|
1715
|
+
lines.push(` Failed: ${status.queue.failed}`);
|
|
1716
|
+
lines.push("");
|
|
1717
|
+
if (status.latestSnapshot) {
|
|
1718
|
+
lines.push("Latest Snapshot:");
|
|
1719
|
+
lines.push(` Hash: ${status.latestSnapshot.snapshot_hash}`);
|
|
1720
|
+
lines.push(` Files: ${status.latestSnapshot.files_count}`);
|
|
1721
|
+
lines.push(` Size: ${formatBytes(status.latestSnapshot.total_size)}`);
|
|
1722
|
+
lines.push(
|
|
1723
|
+
` Created: ${new Date(status.latestSnapshot.created_at).toISOString()}`
|
|
1724
|
+
);
|
|
1725
|
+
} else {
|
|
1726
|
+
lines.push("Latest Snapshot: None");
|
|
1727
|
+
}
|
|
1728
|
+
lines.push("");
|
|
1729
|
+
lines.push("Service State:");
|
|
1730
|
+
lines.push(
|
|
1731
|
+
` Last Change: ${new Date(status.service.lastChangeAt).toISOString()}`
|
|
1732
|
+
);
|
|
1733
|
+
lines.push(
|
|
1734
|
+
` Last Snapshot Ready: ${new Date(status.service.lastSnapshotReadyAt).toISOString()}`
|
|
1735
|
+
);
|
|
1736
|
+
lines.push(
|
|
1737
|
+
` Last Heartbeat: ${status.service.lastHeartbeatEnqueuedAt > 0 ? new Date(status.service.lastHeartbeatEnqueuedAt).toISOString() : "Never"}`
|
|
1738
|
+
);
|
|
1739
|
+
lines.push(` Watcher Ready: ${status.service.watcherReady ? "Yes" : "No"}`);
|
|
1740
|
+
lines.push(` Buffering: ${status.service.buffering ? "Yes" : "No"}`);
|
|
1741
|
+
return lines.join("\n");
|
|
1742
|
+
}
|
|
1743
|
+
function formatBytes(bytes) {
|
|
1744
|
+
if (bytes === 0) return "0 B";
|
|
1745
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
1746
|
+
const k = 1024;
|
|
1747
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1748
|
+
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}`;
|
|
1749
|
+
}
|
|
1605
1750
|
|
|
1606
1751
|
// src/mcp/server.ts
|
|
1607
1752
|
var SERVER_NAME = "coderule-scanner-mcp";
|
|
@@ -1624,7 +1769,7 @@ function createMcpServer({
|
|
|
1624
1769
|
},
|
|
1625
1770
|
async () => {
|
|
1626
1771
|
const status = collectIndexingStatus(runtime, runner);
|
|
1627
|
-
const text =
|
|
1772
|
+
const text = formatStatus(status);
|
|
1628
1773
|
return {
|
|
1629
1774
|
content: [{ type: "text", text }]
|
|
1630
1775
|
};
|
|
@@ -1663,20 +1808,11 @@ function createMcpServer({
|
|
|
1663
1808
|
formatter: runtime.config.retrievalFormatter
|
|
1664
1809
|
}
|
|
1665
1810
|
);
|
|
1666
|
-
const summary = {
|
|
1667
|
-
snapshotHash: latest.snapshot_hash,
|
|
1668
|
-
budgetTokens: effectiveBudget,
|
|
1669
|
-
formatter: runtime.config.retrievalFormatter
|
|
1670
|
-
};
|
|
1671
1811
|
return {
|
|
1672
1812
|
content: [
|
|
1673
1813
|
{
|
|
1674
1814
|
type: "text",
|
|
1675
1815
|
text: result.formatted_output ?? "(no formatted output)"
|
|
1676
|
-
},
|
|
1677
|
-
{
|
|
1678
|
-
type: "text",
|
|
1679
|
-
text: JSON.stringify({ summary, result }, null, 2)
|
|
1680
1816
|
}
|
|
1681
1817
|
]
|
|
1682
1818
|
};
|
|
@@ -1872,15 +2008,27 @@ async function main() {
|
|
|
1872
2008
|
const runner = new ServiceRunner(runtime);
|
|
1873
2009
|
try {
|
|
1874
2010
|
await runner.prepareWatcher(true);
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2011
|
+
let initialCreatedAt;
|
|
2012
|
+
try {
|
|
2013
|
+
const initial = await runInitialSyncPipeline(runtime);
|
|
2014
|
+
runtime.logger.info(
|
|
2015
|
+
{
|
|
2016
|
+
snapshotHash: initial.snapshotHash,
|
|
2017
|
+
filesCount: initial.filesCount
|
|
2018
|
+
},
|
|
2019
|
+
"Initial sync completed; starting MCP server"
|
|
2020
|
+
);
|
|
2021
|
+
initialCreatedAt = initial.createdAt;
|
|
2022
|
+
} catch (error) {
|
|
2023
|
+
runtime.logger.warn(
|
|
2024
|
+
{ err: error },
|
|
2025
|
+
"Initial sync failed; enqueuing snapshot job and continuing"
|
|
2026
|
+
);
|
|
2027
|
+
runtime.outbox.enqueueSnapshot(runtime.config.rootId, 0);
|
|
2028
|
+
}
|
|
2029
|
+
if (initialCreatedAt) {
|
|
2030
|
+
runner.recordInitialSnapshot(initialCreatedAt);
|
|
2031
|
+
}
|
|
1884
2032
|
await runner.startLoops();
|
|
1885
2033
|
await runner.enableWatcherProcessing();
|
|
1886
2034
|
const server = createMcpServer({ runtime, runner });
|