@coderule/mcp 1.3.0 → 1.4.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 +135 -35
- package/dist/mcp-cli.cjs.map +1 -1
- package/dist/mcp-cli.js +136 -36
- package/dist/mcp-cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createHash } from 'crypto';
|
|
|
5
5
|
import envPaths from 'env-paths';
|
|
6
6
|
import pino from 'pino';
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
8
|
-
import { Qulite, enqueueFsEvent } from '@coderule/qulite';
|
|
8
|
+
import { Qulite, JobStatus, enqueueFsEvent } from '@coderule/qulite';
|
|
9
9
|
import { CoderuleClients, ASTHttpClient, SyncHttpClient } from '@coderule/clients';
|
|
10
10
|
import fs2 from 'fs';
|
|
11
11
|
import { Worker } from 'worker_threads';
|
|
@@ -29,7 +29,7 @@ var DEFAULT_HEARTBEAT_CHECK_INTERVAL_MS = 5e3;
|
|
|
29
29
|
var DEFAULT_QUEUE_POLL_INTERVAL_MS = 500;
|
|
30
30
|
var DEFAULT_HASH_BATCH_SIZE = 32;
|
|
31
31
|
var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
|
|
32
|
-
var DEFAULT_HTTP_TIMEOUT_MS =
|
|
32
|
+
var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
|
|
33
33
|
|
|
34
34
|
// src/config/Configurator.ts
|
|
35
35
|
var DEFAULT_RETRIEVAL_FORMATTER = "standard";
|
|
@@ -534,6 +534,16 @@ var Outbox = class {
|
|
|
534
534
|
if (purged > 0) {
|
|
535
535
|
this.log.warn({ purged }, "Purged legacy fs_control jobs without kind");
|
|
536
536
|
}
|
|
537
|
+
try {
|
|
538
|
+
const counts = {
|
|
539
|
+
pending: this.queue.countByStatus(JobStatus.Pending),
|
|
540
|
+
processing: this.queue.countByStatus(JobStatus.Processing),
|
|
541
|
+
done: this.queue.countByStatus(JobStatus.Done),
|
|
542
|
+
failed: this.queue.countByStatus(JobStatus.Failed)
|
|
543
|
+
};
|
|
544
|
+
this.log.debug({ counts }, "Outbox initialized");
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
537
547
|
}
|
|
538
548
|
getQueue() {
|
|
539
549
|
return this.queue;
|
|
@@ -1045,44 +1055,109 @@ function computeSnapshot(filesRepo) {
|
|
|
1045
1055
|
totalSize
|
|
1046
1056
|
};
|
|
1047
1057
|
}
|
|
1048
|
-
async function
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
for (const missingFile of missing) {
|
|
1052
|
-
const absPath = path.join(rootPath, missingFile.file_path);
|
|
1058
|
+
async function withRetries(op, logger2, context, maxAttempts) {
|
|
1059
|
+
let attempt = 0;
|
|
1060
|
+
while (true) {
|
|
1053
1061
|
try {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1062
|
+
return await op();
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
attempt += 1;
|
|
1065
|
+
if (attempt >= maxAttempts) {
|
|
1066
|
+
logger2.error(
|
|
1067
|
+
{ err, ...context, attempt },
|
|
1068
|
+
"Operation failed after retries"
|
|
1069
|
+
);
|
|
1070
|
+
throw err;
|
|
1071
|
+
}
|
|
1072
|
+
const delay = Math.min(15e3, 1e3 * 2 ** (attempt - 1));
|
|
1060
1073
|
logger2.warn(
|
|
1061
|
-
{ err
|
|
1062
|
-
"
|
|
1074
|
+
{ err, ...context, attempt, delay },
|
|
1075
|
+
"Operation failed; retrying"
|
|
1063
1076
|
);
|
|
1077
|
+
await sleep(delay);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts, chunkSize = 64) {
|
|
1082
|
+
if (!missing || missing.length === 0) return;
|
|
1083
|
+
const total = missing.length;
|
|
1084
|
+
const chunks = [];
|
|
1085
|
+
for (let i = 0; i < total; i += chunkSize) {
|
|
1086
|
+
chunks.push(missing.slice(i, i + chunkSize));
|
|
1087
|
+
}
|
|
1088
|
+
for (let idx = 0; idx < chunks.length; idx++) {
|
|
1089
|
+
const list = chunks[idx];
|
|
1090
|
+
const map = /* @__PURE__ */ new Map();
|
|
1091
|
+
for (const missingFile of list) {
|
|
1092
|
+
const absPath = path.join(rootPath, missingFile.file_path);
|
|
1093
|
+
try {
|
|
1094
|
+
const buffer = await fs4.readFile(absPath);
|
|
1095
|
+
map.set(missingFile.file_hash, {
|
|
1096
|
+
path: missingFile.file_path,
|
|
1097
|
+
content: buffer
|
|
1098
|
+
});
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
logger2.warn(
|
|
1101
|
+
{ err: error, relPath: missingFile.file_path },
|
|
1102
|
+
"Failed to read missing file content"
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1064
1105
|
}
|
|
1106
|
+
if (map.size === 0) continue;
|
|
1107
|
+
await withRetries(
|
|
1108
|
+
() => syncClient.uploadFileContent(map),
|
|
1109
|
+
logger2,
|
|
1110
|
+
{
|
|
1111
|
+
op: "uploadFileContent",
|
|
1112
|
+
chunkIndex: idx + 1,
|
|
1113
|
+
chunks: chunks.length,
|
|
1114
|
+
files: map.size
|
|
1115
|
+
},
|
|
1116
|
+
maxAttempts
|
|
1117
|
+
);
|
|
1065
1118
|
}
|
|
1066
|
-
if (map.size === 0) return;
|
|
1067
|
-
await syncClient.uploadFileContent(map);
|
|
1068
1119
|
}
|
|
1069
|
-
async function ensureSnapshotCreated(rootPath, computation, syncClient, logger2) {
|
|
1120
|
+
async function ensureSnapshotCreated(rootPath, computation, syncClient, logger2, options) {
|
|
1070
1121
|
const { snapshotHash, files } = computation;
|
|
1071
|
-
|
|
1122
|
+
const maxAttempts = options?.maxAttempts ?? 5;
|
|
1123
|
+
const uploadChunkSize = options?.uploadChunkSize ?? 64;
|
|
1124
|
+
let status = await withRetries(
|
|
1125
|
+
() => syncClient.checkSnapshotStatus(snapshotHash),
|
|
1126
|
+
logger2,
|
|
1127
|
+
{ op: "checkSnapshotStatus", snapshotHash },
|
|
1128
|
+
maxAttempts
|
|
1129
|
+
);
|
|
1072
1130
|
if (status.status === "READY") {
|
|
1073
1131
|
logger2.info({ snapshotHash }, "Snapshot already READY");
|
|
1074
1132
|
return;
|
|
1075
1133
|
}
|
|
1076
1134
|
if (status.status === "NOT_FOUND" || status.status === "MISSING_CONTENT") {
|
|
1077
|
-
status = await
|
|
1135
|
+
status = await withRetries(
|
|
1136
|
+
() => syncClient.createSnapshot(snapshotHash, files),
|
|
1137
|
+
logger2,
|
|
1138
|
+
{ op: "createSnapshot", snapshotHash },
|
|
1139
|
+
maxAttempts
|
|
1140
|
+
);
|
|
1078
1141
|
}
|
|
1079
1142
|
if (status.status === "MISSING_CONTENT" && status.missing_files?.length) {
|
|
1080
1143
|
logger2.info(
|
|
1081
1144
|
{ missing: status.missing_files.length },
|
|
1082
1145
|
"Uploading missing file content"
|
|
1083
1146
|
);
|
|
1084
|
-
await uploadMissing(
|
|
1085
|
-
|
|
1147
|
+
await uploadMissing(
|
|
1148
|
+
rootPath,
|
|
1149
|
+
status.missing_files,
|
|
1150
|
+
syncClient,
|
|
1151
|
+
logger2,
|
|
1152
|
+
maxAttempts,
|
|
1153
|
+
uploadChunkSize
|
|
1154
|
+
);
|
|
1155
|
+
status = await withRetries(
|
|
1156
|
+
() => syncClient.createSnapshot(snapshotHash, files),
|
|
1157
|
+
logger2,
|
|
1158
|
+
{ op: "createSnapshot", snapshotHash },
|
|
1159
|
+
maxAttempts
|
|
1160
|
+
);
|
|
1086
1161
|
}
|
|
1087
1162
|
let attempt = 0;
|
|
1088
1163
|
while (status.status !== "READY") {
|
|
@@ -1092,13 +1167,24 @@ async function ensureSnapshotCreated(rootPath, computation, syncClient, logger2)
|
|
|
1092
1167
|
const delay = Math.min(5e3, 1e3 * Math.max(1, 2 ** attempt));
|
|
1093
1168
|
await sleep(delay);
|
|
1094
1169
|
attempt += 1;
|
|
1095
|
-
status = await
|
|
1170
|
+
status = await withRetries(
|
|
1171
|
+
() => syncClient.checkSnapshotStatus(snapshotHash),
|
|
1172
|
+
logger2,
|
|
1173
|
+
{ op: "checkSnapshotStatus", snapshotHash },
|
|
1174
|
+
maxAttempts
|
|
1175
|
+
);
|
|
1096
1176
|
}
|
|
1097
1177
|
logger2.info({ snapshotHash }, "Snapshot READY");
|
|
1098
1178
|
}
|
|
1099
|
-
async function publishSnapshot(rootPath, filesRepo, snapshotsRepo, syncClient, logger2) {
|
|
1179
|
+
async function publishSnapshot(rootPath, filesRepo, snapshotsRepo, syncClient, logger2, options) {
|
|
1100
1180
|
const computation = computeSnapshot(filesRepo);
|
|
1101
|
-
await ensureSnapshotCreated(
|
|
1181
|
+
await ensureSnapshotCreated(
|
|
1182
|
+
rootPath,
|
|
1183
|
+
computation,
|
|
1184
|
+
syncClient,
|
|
1185
|
+
logger2,
|
|
1186
|
+
options
|
|
1187
|
+
);
|
|
1102
1188
|
const createdAt = Date.now();
|
|
1103
1189
|
snapshotsRepo.insert(
|
|
1104
1190
|
computation.snapshotHash,
|
|
@@ -1138,7 +1224,8 @@ async function runInitialSyncPipeline(runtime) {
|
|
|
1138
1224
|
runtime.filesRepo,
|
|
1139
1225
|
runtime.snapshotsRepo,
|
|
1140
1226
|
runtime.clients.sync,
|
|
1141
|
-
syncLogger
|
|
1227
|
+
syncLogger,
|
|
1228
|
+
{ maxAttempts: runtime.config.maxSnapshotAttempts }
|
|
1142
1229
|
);
|
|
1143
1230
|
return result;
|
|
1144
1231
|
}
|
|
@@ -1541,7 +1628,8 @@ var ServiceRunner = class {
|
|
|
1541
1628
|
this.runtime.filesRepo,
|
|
1542
1629
|
this.runtime.snapshotsRepo,
|
|
1543
1630
|
this.runtime.clients.sync,
|
|
1544
|
-
log
|
|
1631
|
+
log,
|
|
1632
|
+
{ maxAttempts: this.runtime.config.maxSnapshotAttempts }
|
|
1545
1633
|
);
|
|
1546
1634
|
this.runtime.outbox.ack(job.id, this.fsControlLeaseOwner);
|
|
1547
1635
|
this.state.updateSnapshotReady(result.createdAt);
|
|
@@ -1622,15 +1710,27 @@ async function runService(params) {
|
|
|
1622
1710
|
try {
|
|
1623
1711
|
runner = new ServiceRunner(runtime);
|
|
1624
1712
|
await runner.prepareWatcher(true);
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1713
|
+
let initialCreatedAt;
|
|
1714
|
+
try {
|
|
1715
|
+
const initial = await runInitialSyncPipeline(runtime);
|
|
1716
|
+
runtime.logger.info(
|
|
1717
|
+
{
|
|
1718
|
+
snapshotHash: initial.snapshotHash,
|
|
1719
|
+
filesCount: initial.filesCount
|
|
1720
|
+
},
|
|
1721
|
+
"Initial sync completed; entering continuous mode"
|
|
1722
|
+
);
|
|
1723
|
+
initialCreatedAt = initial.createdAt;
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
runtime.logger.warn(
|
|
1726
|
+
{ err: error },
|
|
1727
|
+
"Initial sync failed; enqueuing snapshot job and continuing"
|
|
1728
|
+
);
|
|
1729
|
+
runtime.outbox.enqueueSnapshot(runtime.config.rootId, 0);
|
|
1730
|
+
}
|
|
1731
|
+
if (initialCreatedAt) {
|
|
1732
|
+
runner.recordInitialSnapshot(initialCreatedAt);
|
|
1733
|
+
}
|
|
1634
1734
|
await runner.startLoops();
|
|
1635
1735
|
await runner.enableWatcherProcessing();
|
|
1636
1736
|
runtime.logger.info("Coderule scanner service is running");
|