raggrep 0.1.0 → 0.1.3
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/README.md +138 -6
- package/dist/cli/main.js +539 -36
- package/dist/cli/main.js.map +8 -7
- package/dist/domain/entities/fileSummary.d.ts +18 -0
- package/dist/domain/services/keywords.d.ts +45 -0
- package/dist/index.js +141 -7
- package/dist/index.js.map +7 -6
- package/dist/indexer/index.d.ts +25 -0
- package/dist/indexer/watcher.d.ts +33 -0
- package/package.json +4 -3
package/dist/cli/main.js
CHANGED
|
@@ -548,10 +548,92 @@ function extractKeywords(content, name, maxKeywords = 50) {
|
|
|
548
548
|
}
|
|
549
549
|
return Array.from(keywords).slice(0, maxKeywords);
|
|
550
550
|
}
|
|
551
|
+
function splitIdentifier(str) {
|
|
552
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/).map((s) => s.toLowerCase()).filter((s) => s.length > 1);
|
|
553
|
+
}
|
|
551
554
|
function extractPathKeywords(filepath) {
|
|
552
|
-
|
|
555
|
+
const keywords = new Set;
|
|
556
|
+
const pathWithoutExt = filepath.replace(/\.[^.]+$/, "");
|
|
557
|
+
const segments = pathWithoutExt.split(/[/\\]/);
|
|
558
|
+
for (const segment of segments) {
|
|
559
|
+
if (segment.length < 2)
|
|
560
|
+
continue;
|
|
561
|
+
const lower = segment.toLowerCase();
|
|
562
|
+
if (!COMMON_KEYWORDS.has(lower) && lower.length > 2) {
|
|
563
|
+
keywords.add(lower);
|
|
564
|
+
}
|
|
565
|
+
const parts = splitIdentifier(segment);
|
|
566
|
+
for (const part of parts) {
|
|
567
|
+
if (!COMMON_KEYWORDS.has(part) && part.length > 2) {
|
|
568
|
+
keywords.add(part);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return Array.from(keywords);
|
|
573
|
+
}
|
|
574
|
+
function parsePathContext(filepath) {
|
|
575
|
+
const pathWithoutExt = filepath.replace(/\.[^.]+$/, "");
|
|
576
|
+
const allSegments = pathWithoutExt.split(/[/\\]/);
|
|
577
|
+
const filename = allSegments[allSegments.length - 1];
|
|
578
|
+
const dirSegments = allSegments.slice(0, -1);
|
|
579
|
+
const keywords = extractPathKeywords(filepath);
|
|
580
|
+
let layer;
|
|
581
|
+
const allLower = [...dirSegments, filename].map((s) => s.toLowerCase()).join(" ");
|
|
582
|
+
const filenameLower = filename.toLowerCase();
|
|
583
|
+
for (const [layerName, patterns] of Object.entries(LAYER_PATTERNS)) {
|
|
584
|
+
for (const pattern of patterns) {
|
|
585
|
+
if (filenameLower.includes(pattern)) {
|
|
586
|
+
layer = layerName;
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
if (dirSegments.some((s) => s.toLowerCase() === pattern)) {
|
|
590
|
+
layer = layerName;
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (layer)
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
let domain;
|
|
598
|
+
const layerPatternSet = new Set(Object.values(LAYER_PATTERNS).flat());
|
|
599
|
+
const reversedSegments = [...dirSegments].reverse();
|
|
600
|
+
for (const segment of reversedSegments) {
|
|
601
|
+
const lower = segment.toLowerCase();
|
|
602
|
+
if (["src", "lib", "app", "packages", "modules"].includes(lower))
|
|
603
|
+
continue;
|
|
604
|
+
if (layerPatternSet.has(lower))
|
|
605
|
+
continue;
|
|
606
|
+
if (lower.length > 2) {
|
|
607
|
+
domain = lower;
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
segments: dirSegments,
|
|
613
|
+
layer,
|
|
614
|
+
domain,
|
|
615
|
+
depth: dirSegments.length,
|
|
616
|
+
keywords
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function formatPathContextForEmbedding(pathContext) {
|
|
620
|
+
const parts = [];
|
|
621
|
+
if (pathContext.domain) {
|
|
622
|
+
parts.push(pathContext.domain);
|
|
623
|
+
}
|
|
624
|
+
if (pathContext.layer) {
|
|
625
|
+
parts.push(pathContext.layer);
|
|
626
|
+
}
|
|
627
|
+
const significantSegments = pathContext.segments.slice(-3).filter((s) => s.length > 2 && !["src", "lib", "app"].includes(s.toLowerCase()));
|
|
628
|
+
if (significantSegments.length > 0) {
|
|
629
|
+
parts.push(...significantSegments.map((s) => s.toLowerCase()));
|
|
630
|
+
}
|
|
631
|
+
if (parts.length === 0)
|
|
632
|
+
return "";
|
|
633
|
+
const unique = [...new Set(parts)];
|
|
634
|
+
return `[${unique.join(" ")}]`;
|
|
553
635
|
}
|
|
554
|
-
var COMMON_KEYWORDS;
|
|
636
|
+
var COMMON_KEYWORDS, LAYER_PATTERNS;
|
|
555
637
|
var init_keywords = __esm(() => {
|
|
556
638
|
COMMON_KEYWORDS = new Set([
|
|
557
639
|
"const",
|
|
@@ -621,6 +703,19 @@ var init_keywords = __esm(() => {
|
|
|
621
703
|
"has",
|
|
622
704
|
"have"
|
|
623
705
|
]);
|
|
706
|
+
LAYER_PATTERNS = {
|
|
707
|
+
controller: ["controller", "controllers", "handler", "handlers", "route", "routes", "api"],
|
|
708
|
+
service: ["service", "services", "usecase", "usecases", "application"],
|
|
709
|
+
repository: ["repository", "repositories", "repo", "repos", "dao", "store", "storage"],
|
|
710
|
+
model: ["model", "models", "entity", "entities", "schema", "schemas"],
|
|
711
|
+
util: ["util", "utils", "utility", "utilities", "helper", "helpers", "common", "shared"],
|
|
712
|
+
config: ["config", "configs", "configuration", "settings"],
|
|
713
|
+
middleware: ["middleware", "middlewares", "interceptor", "interceptors"],
|
|
714
|
+
domain: ["domain", "core", "business"],
|
|
715
|
+
infrastructure: ["infrastructure", "infra", "external", "adapters"],
|
|
716
|
+
presentation: ["presentation", "view", "views", "component", "components", "ui"],
|
|
717
|
+
test: ["test", "tests", "spec", "specs", "__tests__", "__test__"]
|
|
718
|
+
};
|
|
624
719
|
});
|
|
625
720
|
|
|
626
721
|
// src/utils/tieredIndex.ts
|
|
@@ -799,7 +894,12 @@ class SemanticModule {
|
|
|
799
894
|
if (parsedChunks.length === 0) {
|
|
800
895
|
return null;
|
|
801
896
|
}
|
|
802
|
-
const
|
|
897
|
+
const pathContext = parsePathContext(filepath);
|
|
898
|
+
const pathPrefix = formatPathContextForEmbedding(pathContext);
|
|
899
|
+
const chunkContents = parsedChunks.map((c) => {
|
|
900
|
+
const namePrefix = c.name ? `${c.name}: ` : "";
|
|
901
|
+
return `${pathPrefix} ${namePrefix}${c.content}`;
|
|
902
|
+
});
|
|
803
903
|
const embeddings = await getEmbeddings(chunkContents);
|
|
804
904
|
const chunks = parsedChunks.map((pc) => ({
|
|
805
905
|
id: generateChunkId(filepath, pc.startLine, pc.endLine),
|
|
@@ -825,13 +925,20 @@ class SemanticModule {
|
|
|
825
925
|
const keywords = extractKeywords(pc.content, pc.name);
|
|
826
926
|
keywords.forEach((k) => allKeywords.add(k));
|
|
827
927
|
}
|
|
928
|
+
pathContext.keywords.forEach((k) => allKeywords.add(k));
|
|
828
929
|
const fileSummary = {
|
|
829
930
|
filepath,
|
|
830
931
|
chunkCount: chunks.length,
|
|
831
932
|
chunkTypes,
|
|
832
933
|
keywords: Array.from(allKeywords),
|
|
833
934
|
exports,
|
|
834
|
-
lastModified: stats.lastModified
|
|
935
|
+
lastModified: stats.lastModified,
|
|
936
|
+
pathContext: {
|
|
937
|
+
segments: pathContext.segments,
|
|
938
|
+
layer: pathContext.layer,
|
|
939
|
+
domain: pathContext.domain,
|
|
940
|
+
depth: pathContext.depth
|
|
941
|
+
}
|
|
835
942
|
};
|
|
836
943
|
this.pendingSummaries.set(filepath, fileSummary);
|
|
837
944
|
return {
|
|
@@ -908,11 +1015,32 @@ class SemanticModule {
|
|
|
908
1015
|
for (const result of bm25Results) {
|
|
909
1016
|
bm25Scores.set(result.id, normalizeScore(result.score, 3));
|
|
910
1017
|
}
|
|
1018
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
|
|
1019
|
+
const pathBoosts = new Map;
|
|
1020
|
+
for (const filepath of candidateFiles) {
|
|
1021
|
+
const summary = symbolicIndex.getFileSummary(filepath);
|
|
1022
|
+
if (summary?.pathContext) {
|
|
1023
|
+
let boost = 0;
|
|
1024
|
+
const ctx2 = summary.pathContext;
|
|
1025
|
+
if (ctx2.domain && queryTerms.some((t) => ctx2.domain.includes(t) || t.includes(ctx2.domain))) {
|
|
1026
|
+
boost += 0.1;
|
|
1027
|
+
}
|
|
1028
|
+
if (ctx2.layer && queryTerms.some((t) => ctx2.layer.includes(t) || t.includes(ctx2.layer))) {
|
|
1029
|
+
boost += 0.05;
|
|
1030
|
+
}
|
|
1031
|
+
const segmentMatch = ctx2.segments.some((seg) => queryTerms.some((t) => seg.toLowerCase().includes(t) || t.includes(seg.toLowerCase())));
|
|
1032
|
+
if (segmentMatch) {
|
|
1033
|
+
boost += 0.05;
|
|
1034
|
+
}
|
|
1035
|
+
pathBoosts.set(filepath, boost);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
911
1038
|
const results = [];
|
|
912
1039
|
for (const { filepath, chunk, embedding } of allChunksData) {
|
|
913
1040
|
const semanticScore = cosineSimilarity(queryEmbedding, embedding);
|
|
914
1041
|
const bm25Score = bm25Scores.get(chunk.id) || 0;
|
|
915
|
-
const
|
|
1042
|
+
const pathBoost = pathBoosts.get(filepath) || 0;
|
|
1043
|
+
const hybridScore = SEMANTIC_WEIGHT * semanticScore + BM25_WEIGHT * bm25Score + pathBoost;
|
|
916
1044
|
if (hybridScore >= minScore || bm25Score > 0.3) {
|
|
917
1045
|
results.push({
|
|
918
1046
|
filepath,
|
|
@@ -921,7 +1049,8 @@ class SemanticModule {
|
|
|
921
1049
|
moduleId: this.id,
|
|
922
1050
|
context: {
|
|
923
1051
|
semanticScore,
|
|
924
|
-
bm25Score
|
|
1052
|
+
bm25Score,
|
|
1053
|
+
pathBoost
|
|
925
1054
|
}
|
|
926
1055
|
});
|
|
927
1056
|
}
|
|
@@ -960,6 +1089,7 @@ var init_semantic = __esm(() => {
|
|
|
960
1089
|
init_config2();
|
|
961
1090
|
init_parseCode();
|
|
962
1091
|
init_tieredIndex();
|
|
1092
|
+
init_keywords();
|
|
963
1093
|
});
|
|
964
1094
|
|
|
965
1095
|
// src/modules/registry.ts
|
|
@@ -991,18 +1121,183 @@ var init_registry = __esm(() => {
|
|
|
991
1121
|
registry = new ModuleRegistryImpl;
|
|
992
1122
|
});
|
|
993
1123
|
|
|
1124
|
+
// src/indexer/watcher.ts
|
|
1125
|
+
import { watch } from "chokidar";
|
|
1126
|
+
import * as path5 from "path";
|
|
1127
|
+
async function watchDirectory(rootDir, options = {}) {
|
|
1128
|
+
const {
|
|
1129
|
+
debounceMs = DEFAULT_DEBOUNCE_MS,
|
|
1130
|
+
verbose = false,
|
|
1131
|
+
model,
|
|
1132
|
+
onIndexStart,
|
|
1133
|
+
onIndexComplete,
|
|
1134
|
+
onFileChange,
|
|
1135
|
+
onError
|
|
1136
|
+
} = options;
|
|
1137
|
+
rootDir = path5.resolve(rootDir);
|
|
1138
|
+
const config = await loadConfig(rootDir);
|
|
1139
|
+
const watchPatterns = config.extensions.map((ext) => `**/*${ext}`);
|
|
1140
|
+
const ignorePatterns = [
|
|
1141
|
+
...config.ignorePaths.map((p) => `**/${p}/**`),
|
|
1142
|
+
`**/${config.indexDir}/**`
|
|
1143
|
+
];
|
|
1144
|
+
let isRunning = true;
|
|
1145
|
+
let isIndexing = false;
|
|
1146
|
+
let pendingChanges = new Map;
|
|
1147
|
+
let debounceTimer = null;
|
|
1148
|
+
let watcher = null;
|
|
1149
|
+
async function processPendingChanges() {
|
|
1150
|
+
if (!isRunning || isIndexing || pendingChanges.size === 0) {
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
isIndexing = true;
|
|
1154
|
+
const changes = new Map(pendingChanges);
|
|
1155
|
+
pendingChanges.clear();
|
|
1156
|
+
try {
|
|
1157
|
+
const filesToIndex = [];
|
|
1158
|
+
const filesToDelete = [];
|
|
1159
|
+
for (const [filepath, event] of changes) {
|
|
1160
|
+
if (event === "unlink") {
|
|
1161
|
+
filesToDelete.push(filepath);
|
|
1162
|
+
} else {
|
|
1163
|
+
filesToIndex.push(filepath);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (filesToDelete.length > 0) {
|
|
1167
|
+
if (verbose) {
|
|
1168
|
+
console.log(`
|
|
1169
|
+
[Watch] Cleaning up ${filesToDelete.length} deleted file(s)...`);
|
|
1170
|
+
}
|
|
1171
|
+
await cleanupIndex(rootDir, { verbose: false });
|
|
1172
|
+
}
|
|
1173
|
+
if (filesToIndex.length > 0) {
|
|
1174
|
+
if (onIndexStart) {
|
|
1175
|
+
onIndexStart(filesToIndex);
|
|
1176
|
+
}
|
|
1177
|
+
if (verbose) {
|
|
1178
|
+
console.log(`
|
|
1179
|
+
[Watch] Indexing ${filesToIndex.length} changed file(s)...`);
|
|
1180
|
+
}
|
|
1181
|
+
const results = await indexDirectory(rootDir, {
|
|
1182
|
+
model,
|
|
1183
|
+
verbose: false
|
|
1184
|
+
});
|
|
1185
|
+
if (onIndexComplete) {
|
|
1186
|
+
onIndexComplete(results);
|
|
1187
|
+
}
|
|
1188
|
+
for (const result of results) {
|
|
1189
|
+
if (result.indexed > 0 || result.errors > 0) {
|
|
1190
|
+
console.log(`[Watch] ${result.moduleId}: ${result.indexed} indexed, ${result.errors} errors`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1196
|
+
console.error("[Watch] Error during indexing:", err.message);
|
|
1197
|
+
if (onError) {
|
|
1198
|
+
onError(err);
|
|
1199
|
+
}
|
|
1200
|
+
} finally {
|
|
1201
|
+
isIndexing = false;
|
|
1202
|
+
if (pendingChanges.size > 0) {
|
|
1203
|
+
scheduleProcessing();
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function scheduleProcessing() {
|
|
1208
|
+
if (debounceTimer) {
|
|
1209
|
+
clearTimeout(debounceTimer);
|
|
1210
|
+
}
|
|
1211
|
+
if (pendingChanges.size >= MAX_BATCH_SIZE) {
|
|
1212
|
+
processPendingChanges();
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
debounceTimer = setTimeout(() => {
|
|
1216
|
+
debounceTimer = null;
|
|
1217
|
+
processPendingChanges();
|
|
1218
|
+
}, debounceMs);
|
|
1219
|
+
}
|
|
1220
|
+
function handleFileEvent(event, filepath) {
|
|
1221
|
+
if (!isRunning)
|
|
1222
|
+
return;
|
|
1223
|
+
const relativePath = path5.relative(rootDir, filepath);
|
|
1224
|
+
for (const ignorePath of config.ignorePaths) {
|
|
1225
|
+
if (relativePath.startsWith(ignorePath) || relativePath.includes(`/${ignorePath}/`)) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (onFileChange) {
|
|
1230
|
+
onFileChange(event, relativePath);
|
|
1231
|
+
}
|
|
1232
|
+
if (verbose) {
|
|
1233
|
+
const symbol = event === "add" ? "+" : event === "unlink" ? "-" : "~";
|
|
1234
|
+
console.log(`[Watch] ${symbol} ${relativePath}`);
|
|
1235
|
+
}
|
|
1236
|
+
pendingChanges.set(relativePath, event);
|
|
1237
|
+
scheduleProcessing();
|
|
1238
|
+
}
|
|
1239
|
+
watcher = watch(watchPatterns, {
|
|
1240
|
+
cwd: rootDir,
|
|
1241
|
+
ignored: ignorePatterns,
|
|
1242
|
+
persistent: true,
|
|
1243
|
+
ignoreInitial: true,
|
|
1244
|
+
awaitWriteFinish: {
|
|
1245
|
+
stabilityThreshold: 100,
|
|
1246
|
+
pollInterval: 50
|
|
1247
|
+
},
|
|
1248
|
+
usePolling: false,
|
|
1249
|
+
atomic: true
|
|
1250
|
+
});
|
|
1251
|
+
watcher.on("add", (filepath) => handleFileEvent("add", path5.join(rootDir, filepath)));
|
|
1252
|
+
watcher.on("change", (filepath) => handleFileEvent("change", path5.join(rootDir, filepath)));
|
|
1253
|
+
watcher.on("unlink", (filepath) => handleFileEvent("unlink", path5.join(rootDir, filepath)));
|
|
1254
|
+
watcher.on("error", (error) => {
|
|
1255
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1256
|
+
console.error("[Watch] Watcher error:", err);
|
|
1257
|
+
if (onError) {
|
|
1258
|
+
onError(err);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
await new Promise((resolve2) => {
|
|
1262
|
+
watcher.on("ready", () => {
|
|
1263
|
+
resolve2();
|
|
1264
|
+
});
|
|
1265
|
+
});
|
|
1266
|
+
return {
|
|
1267
|
+
stop: async () => {
|
|
1268
|
+
isRunning = false;
|
|
1269
|
+
if (debounceTimer) {
|
|
1270
|
+
clearTimeout(debounceTimer);
|
|
1271
|
+
debounceTimer = null;
|
|
1272
|
+
}
|
|
1273
|
+
if (watcher) {
|
|
1274
|
+
await watcher.close();
|
|
1275
|
+
watcher = null;
|
|
1276
|
+
}
|
|
1277
|
+
},
|
|
1278
|
+
isRunning: () => isRunning
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
var DEFAULT_DEBOUNCE_MS = 300, MAX_BATCH_SIZE = 100;
|
|
1282
|
+
var init_watcher = __esm(() => {
|
|
1283
|
+
init_config2();
|
|
1284
|
+
init_indexer();
|
|
1285
|
+
});
|
|
1286
|
+
|
|
994
1287
|
// src/indexer/index.ts
|
|
995
1288
|
var exports_indexer = {};
|
|
996
1289
|
__export(exports_indexer, {
|
|
1290
|
+
watchDirectory: () => watchDirectory,
|
|
997
1291
|
indexDirectory: () => indexDirectory,
|
|
1292
|
+
getIndexStatus: () => getIndexStatus,
|
|
998
1293
|
cleanupIndex: () => cleanupIndex
|
|
999
1294
|
});
|
|
1000
1295
|
import { glob } from "glob";
|
|
1001
1296
|
import * as fs3 from "fs/promises";
|
|
1002
|
-
import * as
|
|
1297
|
+
import * as path6 from "path";
|
|
1003
1298
|
async function indexDirectory(rootDir, options = {}) {
|
|
1004
1299
|
const verbose = options.verbose ?? false;
|
|
1005
|
-
rootDir =
|
|
1300
|
+
rootDir = path6.resolve(rootDir);
|
|
1006
1301
|
console.log(`Indexing directory: ${rootDir}`);
|
|
1007
1302
|
const config = await loadConfig(rootDir);
|
|
1008
1303
|
await registerBuiltInModules();
|
|
@@ -1037,11 +1332,11 @@ async function indexDirectory(rootDir, options = {}) {
|
|
|
1037
1332
|
rootDir,
|
|
1038
1333
|
config,
|
|
1039
1334
|
readFile: async (filepath) => {
|
|
1040
|
-
const fullPath =
|
|
1335
|
+
const fullPath = path6.isAbsolute(filepath) ? filepath : path6.join(rootDir, filepath);
|
|
1041
1336
|
return fs3.readFile(fullPath, "utf-8");
|
|
1042
1337
|
},
|
|
1043
1338
|
getFileStats: async (filepath) => {
|
|
1044
|
-
const fullPath =
|
|
1339
|
+
const fullPath = path6.isAbsolute(filepath) ? filepath : path6.join(rootDir, filepath);
|
|
1045
1340
|
const stats = await fs3.stat(fullPath);
|
|
1046
1341
|
return { lastModified: stats.mtime.toISOString() };
|
|
1047
1342
|
}
|
|
@@ -1065,17 +1360,17 @@ async function indexWithModule(rootDir, files, module, config, verbose) {
|
|
|
1065
1360
|
rootDir,
|
|
1066
1361
|
config,
|
|
1067
1362
|
readFile: async (filepath) => {
|
|
1068
|
-
const fullPath =
|
|
1363
|
+
const fullPath = path6.isAbsolute(filepath) ? filepath : path6.join(rootDir, filepath);
|
|
1069
1364
|
return fs3.readFile(fullPath, "utf-8");
|
|
1070
1365
|
},
|
|
1071
1366
|
getFileStats: async (filepath) => {
|
|
1072
|
-
const fullPath =
|
|
1367
|
+
const fullPath = path6.isAbsolute(filepath) ? filepath : path6.join(rootDir, filepath);
|
|
1073
1368
|
const stats = await fs3.stat(fullPath);
|
|
1074
1369
|
return { lastModified: stats.mtime.toISOString() };
|
|
1075
1370
|
}
|
|
1076
1371
|
};
|
|
1077
1372
|
for (const filepath of files) {
|
|
1078
|
-
const relativePath =
|
|
1373
|
+
const relativePath = path6.relative(rootDir, filepath);
|
|
1079
1374
|
try {
|
|
1080
1375
|
const stats = await fs3.stat(filepath);
|
|
1081
1376
|
const lastModified = stats.mtime.toISOString();
|
|
@@ -1144,13 +1439,13 @@ async function loadModuleManifest(rootDir, moduleId, config) {
|
|
|
1144
1439
|
}
|
|
1145
1440
|
async function writeModuleManifest(rootDir, moduleId, manifest, config) {
|
|
1146
1441
|
const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
|
|
1147
|
-
await fs3.mkdir(
|
|
1442
|
+
await fs3.mkdir(path6.dirname(manifestPath), { recursive: true });
|
|
1148
1443
|
await fs3.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1149
1444
|
}
|
|
1150
1445
|
async function writeFileIndex(rootDir, moduleId, filepath, fileIndex, config) {
|
|
1151
1446
|
const indexPath = getModuleIndexPath(rootDir, moduleId, config);
|
|
1152
|
-
const indexFilePath =
|
|
1153
|
-
await fs3.mkdir(
|
|
1447
|
+
const indexFilePath = path6.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
|
|
1448
|
+
await fs3.mkdir(path6.dirname(indexFilePath), { recursive: true });
|
|
1154
1449
|
await fs3.writeFile(indexFilePath, JSON.stringify(fileIndex, null, 2));
|
|
1155
1450
|
}
|
|
1156
1451
|
async function updateGlobalManifest(rootDir, modules, config) {
|
|
@@ -1160,12 +1455,12 @@ async function updateGlobalManifest(rootDir, modules, config) {
|
|
|
1160
1455
|
lastUpdated: new Date().toISOString(),
|
|
1161
1456
|
modules: modules.map((m) => m.id)
|
|
1162
1457
|
};
|
|
1163
|
-
await fs3.mkdir(
|
|
1458
|
+
await fs3.mkdir(path6.dirname(manifestPath), { recursive: true });
|
|
1164
1459
|
await fs3.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1165
1460
|
}
|
|
1166
1461
|
async function cleanupIndex(rootDir, options = {}) {
|
|
1167
1462
|
const verbose = options.verbose ?? false;
|
|
1168
|
-
rootDir =
|
|
1463
|
+
rootDir = path6.resolve(rootDir);
|
|
1169
1464
|
console.log(`Cleaning up index in: ${rootDir}`);
|
|
1170
1465
|
const config = await loadConfig(rootDir);
|
|
1171
1466
|
await registerBuiltInModules();
|
|
@@ -1195,7 +1490,7 @@ async function cleanupModuleIndex(rootDir, moduleId, config, verbose) {
|
|
|
1195
1490
|
const filesToRemove = [];
|
|
1196
1491
|
const updatedFiles = {};
|
|
1197
1492
|
for (const [filepath, entry] of Object.entries(manifest.files)) {
|
|
1198
|
-
const fullPath =
|
|
1493
|
+
const fullPath = path6.join(rootDir, filepath);
|
|
1199
1494
|
try {
|
|
1200
1495
|
await fs3.access(fullPath);
|
|
1201
1496
|
updatedFiles[filepath] = entry;
|
|
@@ -1209,7 +1504,7 @@ async function cleanupModuleIndex(rootDir, moduleId, config, verbose) {
|
|
|
1209
1504
|
}
|
|
1210
1505
|
}
|
|
1211
1506
|
for (const filepath of filesToRemove) {
|
|
1212
|
-
const indexFilePath =
|
|
1507
|
+
const indexFilePath = path6.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
|
|
1213
1508
|
try {
|
|
1214
1509
|
await fs3.unlink(indexFilePath);
|
|
1215
1510
|
} catch {}
|
|
@@ -1225,7 +1520,7 @@ async function cleanupEmptyDirectories(dir) {
|
|
|
1225
1520
|
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
1226
1521
|
for (const entry of entries) {
|
|
1227
1522
|
if (entry.isDirectory()) {
|
|
1228
|
-
const subDir =
|
|
1523
|
+
const subDir = path6.join(dir, entry.name);
|
|
1229
1524
|
await cleanupEmptyDirectories(subDir);
|
|
1230
1525
|
}
|
|
1231
1526
|
}
|
|
@@ -1239,9 +1534,66 @@ async function cleanupEmptyDirectories(dir) {
|
|
|
1239
1534
|
return false;
|
|
1240
1535
|
}
|
|
1241
1536
|
}
|
|
1537
|
+
async function getIndexStatus(rootDir) {
|
|
1538
|
+
rootDir = path6.resolve(rootDir);
|
|
1539
|
+
const config = await loadConfig(rootDir);
|
|
1540
|
+
const indexDir = path6.join(rootDir, config.indexDir);
|
|
1541
|
+
const status = {
|
|
1542
|
+
exists: false,
|
|
1543
|
+
rootDir,
|
|
1544
|
+
indexDir,
|
|
1545
|
+
modules: [],
|
|
1546
|
+
totalFiles: 0
|
|
1547
|
+
};
|
|
1548
|
+
try {
|
|
1549
|
+
await fs3.access(indexDir);
|
|
1550
|
+
} catch {
|
|
1551
|
+
return status;
|
|
1552
|
+
}
|
|
1553
|
+
try {
|
|
1554
|
+
const globalManifestPath = getGlobalManifestPath(rootDir, config);
|
|
1555
|
+
const content = await fs3.readFile(globalManifestPath, "utf-8");
|
|
1556
|
+
const globalManifest = JSON.parse(content);
|
|
1557
|
+
status.exists = true;
|
|
1558
|
+
status.lastUpdated = globalManifest.lastUpdated;
|
|
1559
|
+
for (const moduleId of globalManifest.modules) {
|
|
1560
|
+
try {
|
|
1561
|
+
const manifest = await loadModuleManifest(rootDir, moduleId, config);
|
|
1562
|
+
const fileCount = Object.keys(manifest.files).length;
|
|
1563
|
+
status.modules.push({
|
|
1564
|
+
id: moduleId,
|
|
1565
|
+
fileCount,
|
|
1566
|
+
lastUpdated: manifest.lastUpdated
|
|
1567
|
+
});
|
|
1568
|
+
status.totalFiles += fileCount;
|
|
1569
|
+
} catch {}
|
|
1570
|
+
}
|
|
1571
|
+
} catch {
|
|
1572
|
+
try {
|
|
1573
|
+
const entries = await fs3.readdir(path6.join(indexDir, "index"));
|
|
1574
|
+
if (entries.length > 0) {
|
|
1575
|
+
status.exists = true;
|
|
1576
|
+
for (const entry of entries) {
|
|
1577
|
+
try {
|
|
1578
|
+
const manifest = await loadModuleManifest(rootDir, entry, config);
|
|
1579
|
+
const fileCount = Object.keys(manifest.files).length;
|
|
1580
|
+
status.modules.push({
|
|
1581
|
+
id: entry,
|
|
1582
|
+
fileCount,
|
|
1583
|
+
lastUpdated: manifest.lastUpdated
|
|
1584
|
+
});
|
|
1585
|
+
status.totalFiles += fileCount;
|
|
1586
|
+
} catch {}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
} catch {}
|
|
1590
|
+
}
|
|
1591
|
+
return status;
|
|
1592
|
+
}
|
|
1242
1593
|
var init_indexer = __esm(() => {
|
|
1243
1594
|
init_config2();
|
|
1244
1595
|
init_registry();
|
|
1596
|
+
init_watcher();
|
|
1245
1597
|
});
|
|
1246
1598
|
|
|
1247
1599
|
// src/search/index.ts
|
|
@@ -1251,9 +1603,9 @@ __export(exports_search, {
|
|
|
1251
1603
|
formatSearchResults: () => formatSearchResults
|
|
1252
1604
|
});
|
|
1253
1605
|
import * as fs4 from "fs/promises";
|
|
1254
|
-
import * as
|
|
1606
|
+
import * as path7 from "path";
|
|
1255
1607
|
async function search(rootDir, query, options = {}) {
|
|
1256
|
-
rootDir =
|
|
1608
|
+
rootDir = path7.resolve(rootDir);
|
|
1257
1609
|
console.log(`Searching for: "${query}"`);
|
|
1258
1610
|
const config = await loadConfig(rootDir);
|
|
1259
1611
|
await registerBuiltInModules();
|
|
@@ -1294,7 +1646,7 @@ function createSearchContext(rootDir, moduleId, config) {
|
|
|
1294
1646
|
config,
|
|
1295
1647
|
loadFileIndex: async (filepath) => {
|
|
1296
1648
|
const hasExtension = /\.[^./]+$/.test(filepath);
|
|
1297
|
-
const indexFilePath = hasExtension ?
|
|
1649
|
+
const indexFilePath = hasExtension ? path7.join(indexPath, filepath.replace(/\.[^.]+$/, ".json")) : path7.join(indexPath, filepath + ".json");
|
|
1298
1650
|
try {
|
|
1299
1651
|
const content = await fs4.readFile(indexFilePath, "utf-8");
|
|
1300
1652
|
return JSON.parse(content);
|
|
@@ -1306,8 +1658,8 @@ function createSearchContext(rootDir, moduleId, config) {
|
|
|
1306
1658
|
const files = [];
|
|
1307
1659
|
await traverseDirectory(indexPath, files, indexPath);
|
|
1308
1660
|
return files.filter((f) => f.endsWith(".json") && !f.endsWith("manifest.json")).map((f) => {
|
|
1309
|
-
const
|
|
1310
|
-
return
|
|
1661
|
+
const relative4 = path7.relative(indexPath, f);
|
|
1662
|
+
return relative4.replace(/\.json$/, "");
|
|
1311
1663
|
});
|
|
1312
1664
|
}
|
|
1313
1665
|
};
|
|
@@ -1316,7 +1668,7 @@ async function traverseDirectory(dir, files, basePath) {
|
|
|
1316
1668
|
try {
|
|
1317
1669
|
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
1318
1670
|
for (const entry of entries) {
|
|
1319
|
-
const fullPath =
|
|
1671
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1320
1672
|
if (entry.isDirectory()) {
|
|
1321
1673
|
await traverseDirectory(fullPath, files, basePath);
|
|
1322
1674
|
} else if (entry.isFile()) {
|
|
@@ -1373,12 +1725,38 @@ var init_search = __esm(() => {
|
|
|
1373
1725
|
|
|
1374
1726
|
// src/cli/main.ts
|
|
1375
1727
|
init_embeddings();
|
|
1728
|
+
import { createRequire } from "module";
|
|
1729
|
+
var require2 = createRequire(import.meta.url);
|
|
1730
|
+
var pkg = require2("../../package.json");
|
|
1731
|
+
var VERSION = pkg.version;
|
|
1376
1732
|
var args = process.argv.slice(2);
|
|
1377
1733
|
var command = args[0];
|
|
1734
|
+
if (command === "--version" || command === "-v") {
|
|
1735
|
+
console.log(`raggrep v${VERSION}`);
|
|
1736
|
+
process.exit(0);
|
|
1737
|
+
}
|
|
1738
|
+
function formatTimeAgo(date) {
|
|
1739
|
+
const now = new Date;
|
|
1740
|
+
const diffMs = now.getTime() - date.getTime();
|
|
1741
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
1742
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
1743
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
1744
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
1745
|
+
if (diffSecs < 60)
|
|
1746
|
+
return "just now";
|
|
1747
|
+
if (diffMins < 60)
|
|
1748
|
+
return `${diffMins}m ago`;
|
|
1749
|
+
if (diffHours < 24)
|
|
1750
|
+
return `${diffHours}h ago`;
|
|
1751
|
+
if (diffDays < 7)
|
|
1752
|
+
return `${diffDays}d ago`;
|
|
1753
|
+
return date.toLocaleDateString();
|
|
1754
|
+
}
|
|
1378
1755
|
function parseFlags(args2) {
|
|
1379
1756
|
const flags = {
|
|
1380
1757
|
help: false,
|
|
1381
1758
|
verbose: false,
|
|
1759
|
+
watch: false,
|
|
1382
1760
|
remaining: []
|
|
1383
1761
|
};
|
|
1384
1762
|
for (let i = 0;i < args2.length; i++) {
|
|
@@ -1387,6 +1765,8 @@ function parseFlags(args2) {
|
|
|
1387
1765
|
flags.help = true;
|
|
1388
1766
|
} else if (arg === "--verbose" || arg === "-v") {
|
|
1389
1767
|
flags.verbose = true;
|
|
1768
|
+
} else if (arg === "--watch" || arg === "-w") {
|
|
1769
|
+
flags.watch = true;
|
|
1390
1770
|
} else if (arg === "--model" || arg === "-m") {
|
|
1391
1771
|
const modelName = args2[++i];
|
|
1392
1772
|
if (modelName && modelName in EMBEDDING_MODELS) {
|
|
@@ -1436,6 +1816,7 @@ Usage:
|
|
|
1436
1816
|
raggrep index [options]
|
|
1437
1817
|
|
|
1438
1818
|
Options:
|
|
1819
|
+
-w, --watch Watch for file changes and re-index automatically
|
|
1439
1820
|
-m, --model <name> Embedding model to use (default: all-MiniLM-L6-v2)
|
|
1440
1821
|
-v, --verbose Show detailed progress
|
|
1441
1822
|
-h, --help Show this help message
|
|
@@ -1447,12 +1828,13 @@ Model Cache: ${getCacheDir()}
|
|
|
1447
1828
|
|
|
1448
1829
|
Examples:
|
|
1449
1830
|
raggrep index
|
|
1831
|
+
raggrep index --watch
|
|
1450
1832
|
raggrep index --model bge-small-en-v1.5
|
|
1451
1833
|
raggrep index --verbose
|
|
1452
1834
|
`);
|
|
1453
1835
|
process.exit(0);
|
|
1454
1836
|
}
|
|
1455
|
-
const { indexDirectory: indexDirectory2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
|
|
1837
|
+
const { indexDirectory: indexDirectory2, watchDirectory: watchDirectory2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
|
|
1456
1838
|
console.log("RAGgrep Indexer");
|
|
1457
1839
|
console.log(`================
|
|
1458
1840
|
`);
|
|
@@ -1471,6 +1853,39 @@ Examples:
|
|
|
1471
1853
|
console.error("Error during indexing:", error);
|
|
1472
1854
|
process.exit(1);
|
|
1473
1855
|
}
|
|
1856
|
+
if (flags.watch) {
|
|
1857
|
+
console.log(`
|
|
1858
|
+
┌─────────────────────────────────────────┐`);
|
|
1859
|
+
console.log("│ Watching for changes... (Ctrl+C to stop) │");
|
|
1860
|
+
console.log(`└─────────────────────────────────────────┘
|
|
1861
|
+
`);
|
|
1862
|
+
try {
|
|
1863
|
+
const watcher = await watchDirectory2(process.cwd(), {
|
|
1864
|
+
model: flags.model,
|
|
1865
|
+
verbose: flags.verbose,
|
|
1866
|
+
onFileChange: (event, filepath) => {
|
|
1867
|
+
if (flags.verbose) {
|
|
1868
|
+
const symbol = event === "add" ? "+" : event === "unlink" ? "-" : "~";
|
|
1869
|
+
console.log(` ${symbol} ${filepath}`);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
const shutdown = async () => {
|
|
1874
|
+
console.log(`
|
|
1875
|
+
|
|
1876
|
+
Stopping watcher...`);
|
|
1877
|
+
await watcher.stop();
|
|
1878
|
+
console.log("Done.");
|
|
1879
|
+
process.exit(0);
|
|
1880
|
+
};
|
|
1881
|
+
process.on("SIGINT", shutdown);
|
|
1882
|
+
process.on("SIGTERM", shutdown);
|
|
1883
|
+
await new Promise(() => {});
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
console.error("Error starting watcher:", error);
|
|
1886
|
+
process.exit(1);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1474
1889
|
break;
|
|
1475
1890
|
}
|
|
1476
1891
|
case "query": {
|
|
@@ -1487,6 +1902,10 @@ Options:
|
|
|
1487
1902
|
-t, --type <ext> Filter by file extension (e.g., ts, tsx, js)
|
|
1488
1903
|
-h, --help Show this help message
|
|
1489
1904
|
|
|
1905
|
+
Note:
|
|
1906
|
+
If the current directory has not been indexed, raggrep will
|
|
1907
|
+
automatically index it before searching.
|
|
1908
|
+
|
|
1490
1909
|
Examples:
|
|
1491
1910
|
raggrep query "user authentication"
|
|
1492
1911
|
raggrep query "handle errors" --top 5
|
|
@@ -1496,16 +1915,36 @@ Examples:
|
|
|
1496
1915
|
process.exit(0);
|
|
1497
1916
|
}
|
|
1498
1917
|
const { search: search2, formatSearchResults: formatSearchResults2 } = await Promise.resolve().then(() => (init_search(), exports_search));
|
|
1918
|
+
const { getIndexStatus: getIndexStatus2, indexDirectory: indexDirectory2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
|
|
1499
1919
|
const query = flags.remaining[0];
|
|
1500
1920
|
if (!query) {
|
|
1501
1921
|
console.error("Usage: raggrep query <search query>");
|
|
1502
1922
|
console.error('Run "raggrep query --help" for more information.');
|
|
1503
1923
|
process.exit(1);
|
|
1504
1924
|
}
|
|
1505
|
-
console.log("RAGgrep Search");
|
|
1506
|
-
console.log(`==============
|
|
1507
|
-
`);
|
|
1508
1925
|
try {
|
|
1926
|
+
const status = await getIndexStatus2(process.cwd());
|
|
1927
|
+
if (!status.exists) {
|
|
1928
|
+
console.log(`No index found. Indexing directory first...
|
|
1929
|
+
`);
|
|
1930
|
+
console.log("RAGgrep Indexer");
|
|
1931
|
+
console.log(`================
|
|
1932
|
+
`);
|
|
1933
|
+
const indexResults = await indexDirectory2(process.cwd(), {
|
|
1934
|
+
model: flags.model,
|
|
1935
|
+
verbose: false
|
|
1936
|
+
});
|
|
1937
|
+
console.log(`
|
|
1938
|
+
================`);
|
|
1939
|
+
console.log("Summary:");
|
|
1940
|
+
for (const result of indexResults) {
|
|
1941
|
+
console.log(` ${result.moduleId}: ${result.indexed} indexed, ${result.skipped} skipped, ${result.errors} errors`);
|
|
1942
|
+
}
|
|
1943
|
+
console.log("");
|
|
1944
|
+
}
|
|
1945
|
+
console.log("RAGgrep Search");
|
|
1946
|
+
console.log(`==============
|
|
1947
|
+
`);
|
|
1509
1948
|
const filePatterns = flags.fileType ? [`*.${flags.fileType}`] : undefined;
|
|
1510
1949
|
const results = await search2(process.cwd(), query, {
|
|
1511
1950
|
topK: flags.topK ?? 10,
|
|
@@ -1561,9 +2000,72 @@ Examples:
|
|
|
1561
2000
|
}
|
|
1562
2001
|
break;
|
|
1563
2002
|
}
|
|
2003
|
+
case "status": {
|
|
2004
|
+
if (flags.help) {
|
|
2005
|
+
console.log(`
|
|
2006
|
+
raggrep status - Show the current state of the index
|
|
2007
|
+
|
|
2008
|
+
Usage:
|
|
2009
|
+
raggrep status [options]
|
|
2010
|
+
|
|
2011
|
+
Options:
|
|
2012
|
+
-h, --help Show this help message
|
|
2013
|
+
|
|
2014
|
+
Description:
|
|
2015
|
+
Displays information about the index in the current directory,
|
|
2016
|
+
including whether it exists, how many files are indexed, and
|
|
2017
|
+
when it was last updated.
|
|
2018
|
+
|
|
2019
|
+
Examples:
|
|
2020
|
+
raggrep status
|
|
2021
|
+
`);
|
|
2022
|
+
process.exit(0);
|
|
2023
|
+
}
|
|
2024
|
+
const { getIndexStatus: getIndexStatus2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
|
|
2025
|
+
try {
|
|
2026
|
+
const status = await getIndexStatus2(process.cwd());
|
|
2027
|
+
if (!status.exists) {
|
|
2028
|
+
console.log(`
|
|
2029
|
+
┌─────────────────────────────────────────┐
|
|
2030
|
+
│ RAGgrep Status │
|
|
2031
|
+
├─────────────────────────────────────────┤
|
|
2032
|
+
│ ○ Not indexed │
|
|
2033
|
+
└─────────────────────────────────────────┘
|
|
2034
|
+
|
|
2035
|
+
Directory: ${status.rootDir}
|
|
2036
|
+
|
|
2037
|
+
Run "raggrep index" to create an index.
|
|
2038
|
+
`);
|
|
2039
|
+
} else {
|
|
2040
|
+
const date = status.lastUpdated ? new Date(status.lastUpdated) : null;
|
|
2041
|
+
const timeAgo = date ? formatTimeAgo(date) : "unknown";
|
|
2042
|
+
console.log(`
|
|
2043
|
+
┌─────────────────────────────────────────┐
|
|
2044
|
+
│ RAGgrep Status │
|
|
2045
|
+
├─────────────────────────────────────────┤
|
|
2046
|
+
│ ● Indexed │
|
|
2047
|
+
└─────────────────────────────────────────┘
|
|
2048
|
+
|
|
2049
|
+
Files: ${status.totalFiles.toString().padEnd(10)} Updated: ${timeAgo}
|
|
2050
|
+
Location: ${status.indexDir}
|
|
2051
|
+
`);
|
|
2052
|
+
if (status.modules.length > 0) {
|
|
2053
|
+
console.log(" Modules:");
|
|
2054
|
+
for (const mod of status.modules) {
|
|
2055
|
+
console.log(` └─ ${mod.id} (${mod.fileCount} files)`);
|
|
2056
|
+
}
|
|
2057
|
+
console.log("");
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
} catch (error) {
|
|
2061
|
+
console.error("Error getting status:", error);
|
|
2062
|
+
process.exit(1);
|
|
2063
|
+
}
|
|
2064
|
+
break;
|
|
2065
|
+
}
|
|
1564
2066
|
default:
|
|
1565
2067
|
console.log(`
|
|
1566
|
-
raggrep - Local filesystem-based RAG system for codebases
|
|
2068
|
+
raggrep v${VERSION} - Local filesystem-based RAG system for codebases
|
|
1567
2069
|
|
|
1568
2070
|
Usage:
|
|
1569
2071
|
raggrep <command> [options]
|
|
@@ -1571,16 +2073,17 @@ Usage:
|
|
|
1571
2073
|
Commands:
|
|
1572
2074
|
index Index the current directory
|
|
1573
2075
|
query Search the indexed codebase
|
|
2076
|
+
status Show the current state of the index
|
|
1574
2077
|
cleanup Remove stale index entries for deleted files
|
|
1575
2078
|
|
|
1576
2079
|
Options:
|
|
1577
|
-
-h, --help
|
|
2080
|
+
-h, --help Show help for a command
|
|
2081
|
+
-v, --version Show version number
|
|
1578
2082
|
|
|
1579
2083
|
Examples:
|
|
1580
2084
|
raggrep index
|
|
1581
|
-
raggrep index --model bge-small-en-v1.5
|
|
1582
2085
|
raggrep query "user login"
|
|
1583
|
-
raggrep
|
|
2086
|
+
raggrep status
|
|
1584
2087
|
raggrep cleanup
|
|
1585
2088
|
|
|
1586
2089
|
Run 'raggrep <command> --help' for more information.
|
|
@@ -1593,4 +2096,4 @@ Run 'raggrep <command> --help' for more information.
|
|
|
1593
2096
|
}
|
|
1594
2097
|
main();
|
|
1595
2098
|
|
|
1596
|
-
//# debugId=
|
|
2099
|
+
//# debugId=0D3D8495D3A140B664756E2164756E21
|