@karmaniverous/jeeves-watcher 0.2.4 → 0.2.6
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 +1 -0
- package/config.schema.json +99 -85
- package/dist/cjs/index.js +254 -0
- package/dist/cli/jeeves-watcher/index.js +254 -3
- package/dist/index.d.ts +50 -1
- package/dist/index.iife.js +254 -2
- package/dist/index.iife.min.js +1 -1
- package/dist/mjs/index.js +255 -2
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ declare const watchConfigSchema: z.ZodObject<{
|
|
|
15
15
|
usePolling: z.ZodOptional<z.ZodBoolean>;
|
|
16
16
|
debounceMs: z.ZodOptional<z.ZodNumber>;
|
|
17
17
|
stabilityThresholdMs: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
respectGitignore: z.ZodOptional<z.ZodBoolean>;
|
|
18
19
|
}, z.core.$strip>;
|
|
19
20
|
/** Watch configuration for file system monitoring paths, ignore patterns, and debounce/stability settings. */
|
|
20
21
|
type WatchConfig = z.infer<typeof watchConfigSchema>;
|
|
@@ -91,6 +92,7 @@ declare const jeevesWatcherConfigSchema: z.ZodObject<{
|
|
|
91
92
|
usePolling: z.ZodOptional<z.ZodBoolean>;
|
|
92
93
|
debounceMs: z.ZodOptional<z.ZodNumber>;
|
|
93
94
|
stabilityThresholdMs: z.ZodOptional<z.ZodNumber>;
|
|
95
|
+
respectGitignore: z.ZodOptional<z.ZodBoolean>;
|
|
94
96
|
}, z.core.$strip>;
|
|
95
97
|
configWatch: z.ZodOptional<z.ZodObject<{
|
|
96
98
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -543,6 +545,42 @@ declare function createApiServer(options: ApiServerOptions): FastifyInstance;
|
|
|
543
545
|
*/
|
|
544
546
|
declare function createLogger(config?: LoggingConfig): pino.Logger;
|
|
545
547
|
|
|
548
|
+
/**
|
|
549
|
+
* @module gitignore
|
|
550
|
+
* Processor-level gitignore filtering. Scans watched paths for `.gitignore` files in git repos, caches parsed patterns, and exposes `isIgnored()` for path checking.
|
|
551
|
+
*/
|
|
552
|
+
/**
|
|
553
|
+
* Processor-level gitignore filter. Checks file paths against the nearest
|
|
554
|
+
* `.gitignore` chain in git repositories.
|
|
555
|
+
*/
|
|
556
|
+
declare class GitignoreFilter {
|
|
557
|
+
private repos;
|
|
558
|
+
/**
|
|
559
|
+
* Create a GitignoreFilter by scanning watched paths for `.gitignore` files.
|
|
560
|
+
*
|
|
561
|
+
* @param watchPaths - Absolute paths being watched (directories or globs resolved to roots).
|
|
562
|
+
*/
|
|
563
|
+
constructor(watchPaths: string[]);
|
|
564
|
+
/**
|
|
565
|
+
* Scan paths for git repos and their `.gitignore` files.
|
|
566
|
+
*/
|
|
567
|
+
private scan;
|
|
568
|
+
/**
|
|
569
|
+
* Check whether a file path is ignored by any applicable `.gitignore`.
|
|
570
|
+
*
|
|
571
|
+
* @param filePath - Absolute file path to check.
|
|
572
|
+
* @returns `true` if the file should be ignored.
|
|
573
|
+
*/
|
|
574
|
+
isIgnored(filePath: string): boolean;
|
|
575
|
+
/**
|
|
576
|
+
* Invalidate and re-parse a specific `.gitignore` file.
|
|
577
|
+
* Call when a `.gitignore` file is added, changed, or removed.
|
|
578
|
+
*
|
|
579
|
+
* @param gitignorePath - Absolute path to the `.gitignore` file that changed.
|
|
580
|
+
*/
|
|
581
|
+
invalidate(gitignorePath: string): void;
|
|
582
|
+
}
|
|
583
|
+
|
|
546
584
|
/**
|
|
547
585
|
* @module health
|
|
548
586
|
* Tracks consecutive system-level failures and applies exponential backoff.
|
|
@@ -612,6 +650,8 @@ interface FileSystemWatcherOptions {
|
|
|
612
650
|
maxBackoffMs?: number;
|
|
613
651
|
/** Callback invoked on unrecoverable system error. If not set, throws. */
|
|
614
652
|
onFatalError?: (error: unknown) => void;
|
|
653
|
+
/** Optional gitignore filter for processor-level filtering. */
|
|
654
|
+
gitignoreFilter?: GitignoreFilter;
|
|
615
655
|
}
|
|
616
656
|
/**
|
|
617
657
|
* Filesystem watcher that maps chokidar events to the processing queue.
|
|
@@ -622,6 +662,7 @@ declare class FileSystemWatcher {
|
|
|
622
662
|
private readonly processor;
|
|
623
663
|
private readonly logger;
|
|
624
664
|
private readonly health;
|
|
665
|
+
private readonly gitignoreFilter?;
|
|
625
666
|
private watcher;
|
|
626
667
|
/**
|
|
627
668
|
* Create a new FileSystemWatcher.
|
|
@@ -645,6 +686,14 @@ declare class FileSystemWatcher {
|
|
|
645
686
|
* Get the system health tracker.
|
|
646
687
|
*/
|
|
647
688
|
get systemHealth(): SystemHealth;
|
|
689
|
+
/**
|
|
690
|
+
* Check if a path is gitignored and should be skipped.
|
|
691
|
+
*/
|
|
692
|
+
private isGitignored;
|
|
693
|
+
/**
|
|
694
|
+
* If the changed file is a `.gitignore`, invalidate the filter cache.
|
|
695
|
+
*/
|
|
696
|
+
private handleGitignoreChange;
|
|
648
697
|
/**
|
|
649
698
|
* Wrap a processing operation with health tracking.
|
|
650
699
|
* On success, resets the failure counter.
|
|
@@ -812,5 +861,5 @@ declare function deleteMetadata(filePath: string, metadataDir: string): Promise<
|
|
|
812
861
|
*/
|
|
813
862
|
declare function pointId(filePath: string, chunkIndex?: number): string;
|
|
814
863
|
|
|
815
|
-
export { DocumentProcessor, EventQueue, FileSystemWatcher, JeevesWatcher, SystemHealth, VectorStoreClient, apiConfigSchema, applyRules, buildAttributes, compileRules, configWatchConfigSchema, contentHash, createApiServer, createEmbeddingProvider, createLogger, deleteMetadata, embeddingConfigSchema, extractText, inferenceRuleSchema, jeevesWatcherConfigSchema, loadConfig, loggingConfigSchema, metadataPath, pointId, readMetadata, startFromConfig, vectorStoreConfigSchema, watchConfigSchema, writeMetadata };
|
|
864
|
+
export { DocumentProcessor, EventQueue, FileSystemWatcher, GitignoreFilter, JeevesWatcher, SystemHealth, VectorStoreClient, apiConfigSchema, applyRules, buildAttributes, compileRules, configWatchConfigSchema, contentHash, createApiServer, createEmbeddingProvider, createLogger, deleteMetadata, embeddingConfigSchema, extractText, inferenceRuleSchema, jeevesWatcherConfigSchema, loadConfig, loggingConfigSchema, metadataPath, pointId, readMetadata, startFromConfig, vectorStoreConfigSchema, watchConfigSchema, writeMetadata };
|
|
816
865
|
export type { ApiConfig, ApiServerOptions, CompiledRule, ConfigWatchConfig, EmbeddingConfig, EmbeddingProvider, EventQueueOptions, ExtractedText, FileAttributes, FileSystemWatcherOptions, InferenceRule, JeevesWatcherConfig, JeevesWatcherFactories, JeevesWatcherRuntimeOptions, LoggingConfig, ProcessFn, ProcessorConfig, RuleLogger, ScrolledPoint, SearchResult, SystemHealthOptions, VectorPoint, VectorStoreConfig, WatchConfig, WatchEvent };
|
package/dist/index.iife.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function (exports, Fastify, promises, node_path, picomatch, radash, node_crypto, cosmiconfig, zod, jsonmap, googleGenai, pino, uuid, cheerio, yaml, mammoth, Ajv, addFormats, textsplitters, jsClientRest, chokidar) {
|
|
1
|
+
(function (exports, Fastify, promises, node_path, picomatch, radash, node_crypto, cosmiconfig, zod, jsonmap, googleGenai, node_fs, ignore, pino, uuid, cheerio, yaml, mammoth, Ajv, addFormats, textsplitters, jsClientRest, chokidar) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
function _interopNamespaceDefault(e) {
|
|
@@ -434,6 +434,7 @@
|
|
|
434
434
|
stabilityThresholdMs: 500,
|
|
435
435
|
usePolling: false,
|
|
436
436
|
pollIntervalMs: 1000,
|
|
437
|
+
respectGitignore: true,
|
|
437
438
|
};
|
|
438
439
|
/** Default embedding configuration. */
|
|
439
440
|
const EMBEDDING_DEFAULTS = {
|
|
@@ -478,6 +479,11 @@
|
|
|
478
479
|
.number()
|
|
479
480
|
.optional()
|
|
480
481
|
.describe('Time in milliseconds a file must remain unchanged before processing.'),
|
|
482
|
+
/** Whether to respect .gitignore files when processing. */
|
|
483
|
+
respectGitignore: zod.z
|
|
484
|
+
.boolean()
|
|
485
|
+
.optional()
|
|
486
|
+
.describe('Skip files ignored by .gitignore in git repositories. Only applies to repos with a .git directory. Default: true.'),
|
|
481
487
|
});
|
|
482
488
|
/**
|
|
483
489
|
* Configuration watch settings.
|
|
@@ -943,6 +949,212 @@
|
|
|
943
949
|
return factory(config, logger);
|
|
944
950
|
}
|
|
945
951
|
|
|
952
|
+
/**
|
|
953
|
+
* @module gitignore
|
|
954
|
+
* Processor-level gitignore filtering. Scans watched paths for `.gitignore` files in git repos, caches parsed patterns, and exposes `isIgnored()` for path checking.
|
|
955
|
+
*/
|
|
956
|
+
/**
|
|
957
|
+
* Find the git repo root by walking up from `startDir` looking for `.git/`.
|
|
958
|
+
* Returns `undefined` if no repo is found.
|
|
959
|
+
*/
|
|
960
|
+
function findRepoRoot(startDir) {
|
|
961
|
+
let dir = node_path.resolve(startDir);
|
|
962
|
+
const root = node_path.resolve('/');
|
|
963
|
+
while (dir !== root) {
|
|
964
|
+
if (node_fs.existsSync(node_path.join(dir, '.git')) &&
|
|
965
|
+
node_fs.statSync(node_path.join(dir, '.git')).isDirectory()) {
|
|
966
|
+
return dir;
|
|
967
|
+
}
|
|
968
|
+
const parent = node_path.dirname(dir);
|
|
969
|
+
if (parent === dir)
|
|
970
|
+
break;
|
|
971
|
+
dir = parent;
|
|
972
|
+
}
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Convert a watch path (directory, file path, or glob) to a concrete directory
|
|
977
|
+
* that can be scanned for a repo root.
|
|
978
|
+
*/
|
|
979
|
+
function watchPathToScanDir(watchPath) {
|
|
980
|
+
const absPath = node_path.resolve(watchPath);
|
|
981
|
+
try {
|
|
982
|
+
return node_fs.statSync(absPath).isDirectory() ? absPath : node_path.dirname(absPath);
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
// ignore
|
|
986
|
+
}
|
|
987
|
+
// If this is a glob, fall back to the non-glob prefix.
|
|
988
|
+
const globMatch = /[*?[{]/.exec(watchPath);
|
|
989
|
+
if (!globMatch)
|
|
990
|
+
return undefined;
|
|
991
|
+
const prefix = watchPath.slice(0, globMatch.index);
|
|
992
|
+
const trimmed = prefix.trim();
|
|
993
|
+
const baseDir = trimmed.length === 0
|
|
994
|
+
? '.'
|
|
995
|
+
: trimmed.endsWith('/') || trimmed.endsWith('\\')
|
|
996
|
+
? trimmed
|
|
997
|
+
: node_path.dirname(trimmed);
|
|
998
|
+
const resolved = node_path.resolve(baseDir);
|
|
999
|
+
if (!node_fs.existsSync(resolved))
|
|
1000
|
+
return undefined;
|
|
1001
|
+
return resolved;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Recursively find all `.gitignore` files under `dir`.
|
|
1005
|
+
* Skips `.git` and `node_modules` directories for performance.
|
|
1006
|
+
*/
|
|
1007
|
+
function findGitignoreFiles(dir) {
|
|
1008
|
+
const results = [];
|
|
1009
|
+
const gitignorePath = node_path.join(dir, '.gitignore');
|
|
1010
|
+
if (node_fs.existsSync(gitignorePath)) {
|
|
1011
|
+
results.push(gitignorePath);
|
|
1012
|
+
}
|
|
1013
|
+
let entries;
|
|
1014
|
+
try {
|
|
1015
|
+
entries = node_fs.readdirSync(dir);
|
|
1016
|
+
}
|
|
1017
|
+
catch {
|
|
1018
|
+
return results;
|
|
1019
|
+
}
|
|
1020
|
+
for (const entry of entries) {
|
|
1021
|
+
if (entry === '.git' || entry === 'node_modules')
|
|
1022
|
+
continue;
|
|
1023
|
+
const fullPath = node_path.join(dir, entry);
|
|
1024
|
+
try {
|
|
1025
|
+
if (node_fs.statSync(fullPath).isDirectory()) {
|
|
1026
|
+
results.push(...findGitignoreFiles(fullPath));
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
catch {
|
|
1030
|
+
// Skip inaccessible entries
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return results;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Parse a `.gitignore` file into an `ignore` instance.
|
|
1037
|
+
*/
|
|
1038
|
+
function parseGitignore(gitignorePath) {
|
|
1039
|
+
const content = node_fs.readFileSync(gitignorePath, 'utf8');
|
|
1040
|
+
return ignore().add(content);
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Normalize a path to use forward slashes (required by `ignore` package).
|
|
1044
|
+
*/
|
|
1045
|
+
function toForwardSlash(p) {
|
|
1046
|
+
return p.replace(/\\/g, '/');
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Processor-level gitignore filter. Checks file paths against the nearest
|
|
1050
|
+
* `.gitignore` chain in git repositories.
|
|
1051
|
+
*/
|
|
1052
|
+
class GitignoreFilter {
|
|
1053
|
+
repos = new Map();
|
|
1054
|
+
/**
|
|
1055
|
+
* Create a GitignoreFilter by scanning watched paths for `.gitignore` files.
|
|
1056
|
+
*
|
|
1057
|
+
* @param watchPaths - Absolute paths being watched (directories or globs resolved to roots).
|
|
1058
|
+
*/
|
|
1059
|
+
constructor(watchPaths) {
|
|
1060
|
+
this.scan(watchPaths);
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Scan paths for git repos and their `.gitignore` files.
|
|
1064
|
+
*/
|
|
1065
|
+
scan(watchPaths) {
|
|
1066
|
+
this.repos.clear();
|
|
1067
|
+
const scannedDirs = new Set();
|
|
1068
|
+
for (const watchPath of watchPaths) {
|
|
1069
|
+
const scanDir = watchPathToScanDir(watchPath);
|
|
1070
|
+
if (!scanDir)
|
|
1071
|
+
continue;
|
|
1072
|
+
if (scannedDirs.has(scanDir))
|
|
1073
|
+
continue;
|
|
1074
|
+
scannedDirs.add(scanDir);
|
|
1075
|
+
const repoRoot = findRepoRoot(scanDir);
|
|
1076
|
+
if (!repoRoot)
|
|
1077
|
+
continue;
|
|
1078
|
+
if (this.repos.has(repoRoot))
|
|
1079
|
+
continue;
|
|
1080
|
+
const gitignoreFiles = findGitignoreFiles(repoRoot);
|
|
1081
|
+
const entries = gitignoreFiles.map((gf) => ({
|
|
1082
|
+
dir: node_path.dirname(gf),
|
|
1083
|
+
ig: parseGitignore(gf),
|
|
1084
|
+
}));
|
|
1085
|
+
// Sort deepest-first so nested `.gitignore` files are checked first
|
|
1086
|
+
entries.sort((a, b) => b.dir.length - a.dir.length);
|
|
1087
|
+
this.repos.set(repoRoot, { root: repoRoot, entries });
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Check whether a file path is ignored by any applicable `.gitignore`.
|
|
1092
|
+
*
|
|
1093
|
+
* @param filePath - Absolute file path to check.
|
|
1094
|
+
* @returns `true` if the file should be ignored.
|
|
1095
|
+
*/
|
|
1096
|
+
isIgnored(filePath) {
|
|
1097
|
+
const absPath = node_path.resolve(filePath);
|
|
1098
|
+
for (const [, repo] of this.repos) {
|
|
1099
|
+
// Check if file is within this repo
|
|
1100
|
+
const relToRepo = node_path.relative(repo.root, absPath);
|
|
1101
|
+
if (relToRepo.startsWith('..') || relToRepo.startsWith(node_path.resolve('/'))) {
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
// Check each `.gitignore` entry (deepest-first)
|
|
1105
|
+
for (const entry of repo.entries) {
|
|
1106
|
+
const relToEntry = node_path.relative(entry.dir, absPath);
|
|
1107
|
+
if (relToEntry.startsWith('..'))
|
|
1108
|
+
continue;
|
|
1109
|
+
const normalized = toForwardSlash(relToEntry);
|
|
1110
|
+
if (entry.ig.ignores(normalized)) {
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Invalidate and re-parse a specific `.gitignore` file.
|
|
1119
|
+
* Call when a `.gitignore` file is added, changed, or removed.
|
|
1120
|
+
*
|
|
1121
|
+
* @param gitignorePath - Absolute path to the `.gitignore` file that changed.
|
|
1122
|
+
*/
|
|
1123
|
+
invalidate(gitignorePath) {
|
|
1124
|
+
const absPath = node_path.resolve(gitignorePath);
|
|
1125
|
+
const gitignoreDir = node_path.dirname(absPath);
|
|
1126
|
+
for (const [, repo] of this.repos) {
|
|
1127
|
+
const relToRepo = node_path.relative(repo.root, gitignoreDir);
|
|
1128
|
+
if (relToRepo.startsWith('..'))
|
|
1129
|
+
continue;
|
|
1130
|
+
// Remove old entry for this directory
|
|
1131
|
+
repo.entries = repo.entries.filter((e) => e.dir !== gitignoreDir);
|
|
1132
|
+
// Re-parse if file still exists
|
|
1133
|
+
if (node_fs.existsSync(absPath)) {
|
|
1134
|
+
repo.entries.push({ dir: gitignoreDir, ig: parseGitignore(absPath) });
|
|
1135
|
+
// Re-sort deepest-first
|
|
1136
|
+
repo.entries.sort((a, b) => b.dir.length - a.dir.length);
|
|
1137
|
+
}
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
// If not in any known repo, check if it's in a repo we haven't scanned
|
|
1141
|
+
const repoRoot = findRepoRoot(gitignoreDir);
|
|
1142
|
+
if (repoRoot && node_fs.existsSync(absPath)) {
|
|
1143
|
+
const entries = [
|
|
1144
|
+
{ dir: gitignoreDir, ig: parseGitignore(absPath) },
|
|
1145
|
+
];
|
|
1146
|
+
if (this.repos.has(repoRoot)) {
|
|
1147
|
+
const repo = this.repos.get(repoRoot);
|
|
1148
|
+
repo.entries.push(entries[0]);
|
|
1149
|
+
repo.entries.sort((a, b) => b.dir.length - a.dir.length);
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
this.repos.set(repoRoot, { root: repoRoot, entries });
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
946
1158
|
/**
|
|
947
1159
|
* @module logger
|
|
948
1160
|
* Creates pino logger instances. I/O: optionally writes logs to file via pino/file transport. Defaults to stdout at info level.
|
|
@@ -2034,6 +2246,7 @@
|
|
|
2034
2246
|
processor;
|
|
2035
2247
|
logger;
|
|
2036
2248
|
health;
|
|
2249
|
+
gitignoreFilter;
|
|
2037
2250
|
watcher;
|
|
2038
2251
|
/**
|
|
2039
2252
|
* Create a new FileSystemWatcher.
|
|
@@ -2049,6 +2262,7 @@
|
|
|
2049
2262
|
this.queue = queue;
|
|
2050
2263
|
this.processor = processor;
|
|
2051
2264
|
this.logger = logger;
|
|
2265
|
+
this.gitignoreFilter = options.gitignoreFilter;
|
|
2052
2266
|
const healthOptions = {
|
|
2053
2267
|
maxRetries: options.maxRetries,
|
|
2054
2268
|
maxBackoffMs: options.maxBackoffMs,
|
|
@@ -2071,14 +2285,23 @@
|
|
|
2071
2285
|
ignoreInitial: false,
|
|
2072
2286
|
});
|
|
2073
2287
|
this.watcher.on('add', (path) => {
|
|
2288
|
+
this.handleGitignoreChange(path);
|
|
2289
|
+
if (this.isGitignored(path))
|
|
2290
|
+
return;
|
|
2074
2291
|
this.logger.debug({ path }, 'File added');
|
|
2075
2292
|
this.queue.enqueue({ type: 'create', path, priority: 'normal' }, () => this.wrapProcessing(() => this.processor.processFile(path)));
|
|
2076
2293
|
});
|
|
2077
2294
|
this.watcher.on('change', (path) => {
|
|
2295
|
+
this.handleGitignoreChange(path);
|
|
2296
|
+
if (this.isGitignored(path))
|
|
2297
|
+
return;
|
|
2078
2298
|
this.logger.debug({ path }, 'File changed');
|
|
2079
2299
|
this.queue.enqueue({ type: 'modify', path, priority: 'normal' }, () => this.wrapProcessing(() => this.processor.processFile(path)));
|
|
2080
2300
|
});
|
|
2081
2301
|
this.watcher.on('unlink', (path) => {
|
|
2302
|
+
this.handleGitignoreChange(path);
|
|
2303
|
+
if (this.isGitignored(path))
|
|
2304
|
+
return;
|
|
2082
2305
|
this.logger.debug({ path }, 'File removed');
|
|
2083
2306
|
this.queue.enqueue({ type: 'delete', path, priority: 'normal' }, () => this.wrapProcessing(() => this.processor.deleteFile(path)));
|
|
2084
2307
|
});
|
|
@@ -2105,6 +2328,29 @@
|
|
|
2105
2328
|
get systemHealth() {
|
|
2106
2329
|
return this.health;
|
|
2107
2330
|
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Check if a path is gitignored and should be skipped.
|
|
2333
|
+
*/
|
|
2334
|
+
isGitignored(path) {
|
|
2335
|
+
if (!this.gitignoreFilter)
|
|
2336
|
+
return false;
|
|
2337
|
+
const ignored = this.gitignoreFilter.isIgnored(path);
|
|
2338
|
+
if (ignored) {
|
|
2339
|
+
this.logger.debug({ path }, 'Skipping gitignored file');
|
|
2340
|
+
}
|
|
2341
|
+
return ignored;
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* If the changed file is a `.gitignore`, invalidate the filter cache.
|
|
2345
|
+
*/
|
|
2346
|
+
handleGitignoreChange(path) {
|
|
2347
|
+
if (!this.gitignoreFilter)
|
|
2348
|
+
return;
|
|
2349
|
+
if (path.endsWith('.gitignore')) {
|
|
2350
|
+
this.logger.info({ path }, 'Gitignore file changed, refreshing filter');
|
|
2351
|
+
this.gitignoreFilter.invalidate(path);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2108
2354
|
/**
|
|
2109
2355
|
* Wrap a processing operation with health tracking.
|
|
2110
2356
|
* On success, resets the failure counter.
|
|
@@ -2260,10 +2506,15 @@
|
|
|
2260
2506
|
rateLimitPerMinute: this.config.embedding.rateLimitPerMinute,
|
|
2261
2507
|
});
|
|
2262
2508
|
this.queue = queue;
|
|
2509
|
+
const respectGitignore = this.config.watch.respectGitignore ?? true;
|
|
2510
|
+
const gitignoreFilter = respectGitignore
|
|
2511
|
+
? new GitignoreFilter(this.config.watch.paths)
|
|
2512
|
+
: undefined;
|
|
2263
2513
|
const watcher = this.factories.createFileSystemWatcher(this.config.watch, queue, processor, logger, {
|
|
2264
2514
|
maxRetries: this.config.maxRetries,
|
|
2265
2515
|
maxBackoffMs: this.config.maxBackoffMs,
|
|
2266
2516
|
onFatalError: this.runtimeOptions.onFatalError,
|
|
2517
|
+
gitignoreFilter,
|
|
2267
2518
|
});
|
|
2268
2519
|
this.watcher = watcher;
|
|
2269
2520
|
const server = this.factories.createApiServer({
|
|
@@ -2372,6 +2623,7 @@
|
|
|
2372
2623
|
exports.DocumentProcessor = DocumentProcessor;
|
|
2373
2624
|
exports.EventQueue = EventQueue;
|
|
2374
2625
|
exports.FileSystemWatcher = FileSystemWatcher;
|
|
2626
|
+
exports.GitignoreFilter = GitignoreFilter;
|
|
2375
2627
|
exports.JeevesWatcher = JeevesWatcher;
|
|
2376
2628
|
exports.SystemHealth = SystemHealth;
|
|
2377
2629
|
exports.VectorStoreClient = VectorStoreClient;
|
|
@@ -2399,4 +2651,4 @@
|
|
|
2399
2651
|
exports.watchConfigSchema = watchConfigSchema;
|
|
2400
2652
|
exports.writeMetadata = writeMetadata;
|
|
2401
2653
|
|
|
2402
|
-
})(this["jeeves-watcher"] = this["jeeves-watcher"] || {}, Fastify, promises, node_path, picomatch, radash, node_crypto, cosmiconfig, zod, jsonmap, googleGenai, pino, uuid, cheerio, yaml, mammoth, Ajv, addFormats, textsplitters, jsClientRest, chokidar);
|
|
2654
|
+
})(this["jeeves-watcher"] = this["jeeves-watcher"] || {}, Fastify, promises, node_path, picomatch, radash, node_crypto, cosmiconfig, zod, jsonmap, googleGenai, node_fs, ignore, pino, uuid, cheerio, yaml, mammoth, Ajv, addFormats, textsplitters, jsClientRest, chokidar);
|
package/dist/index.iife.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t,r,i,o,n,s,a,c,l,h,u,d,f,g,m,p,y,w,b,v){"use strict";function M(e){var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var i=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,i.get?i:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}var x=M(f);function P(e){if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);const t=String("object"==typeof e&&null!==e&&"message"in e?e.message:e),r=new Error(t);return r.cause=e,r}function k(e){const t=e.replace(/\\/g,"/"),r=t.search(/[*?\[]/);if(-1===r)return i.resolve(e);const o=t.slice(0,r),n=o.endsWith("/")?o.slice(0,-1):i.dirname(o);return i.resolve(n)}async function*S(e){let t;try{t=(await r.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const o of t){const t=i.resolve(e,o.name);if(o.isDirectory)yield*S(t);else try{(await r.stat(t)).isFile()&&(yield t)}catch{}}}async function F(e,t,r,i){const n=await async function(e,t=[]){const r=e.map((e=>e.replace(/\\/g,"/"))),i=t.map((e=>e.replace(/\\/g,"/"))),n=o(r,{dot:!0}),s=i.length?o(i,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(k))),c=new Set;for(const e of a)for await(const t of S(e)){const e=t.replace(/\\/g,"/");s(e)||n(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of n)await r[i](e);return n.length}function z(e,t=!1){let r=e.replace(/\\/g,"/").toLowerCase();return t&&(r=r.replace(/^([a-z]):/,((e,t)=>t))),r}function j(e,t){const r=z(e,!0),o=s.createHash("sha256").update(r,"utf8").digest("hex");return i.join(t,`${o}.meta.json`)}async function R(e,t){try{const i=await r.readFile(j(e,t),"utf8");return JSON.parse(i)}catch{return null}}async function E(e,t,o){const n=j(e,t);await r.mkdir(i.dirname(n),{recursive:!0}),await r.writeFile(n,JSON.stringify(o,null,2),"utf8")}async function C(e,t){try{await r.rm(j(e,t))}catch{}}const T=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function I(e){const{processor:r,vectorStore:i,embeddingProvider:o,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(()=>({status:"ok",uptime:process.uptime()}))),c.post("/metadata",(l={processor:r,logger:s},async(e,t)=>{try{const{path:t,metadata:r}=e.body;return await l.processor.processMetadataUpdate(t,r),{ok:!0}}catch(e){return l.logger.error({err:P(e)},"Metadata update failed"),t.status(500).send({error:"Internal server error"})}})),c.post("/search",function(e){return async(t,r)=>{try{const{query:r,limit:i=10}=t.body,o=await e.embeddingProvider.embed([r]);return await e.vectorStore.search(o[0],i)}catch(t){return e.logger.error({err:P(t)},"Search failed"),r.status(500).send({error:"Internal server error"})}}}({embeddingProvider:o,vectorStore:i,logger:s})),c.post("/reindex",function(e){return async(t,r)=>{try{const t=await F(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");return await r.status(200).send({ok:!0,filesIndexed:t})}catch(t){return e.logger.error({err:P(t)},"Reindex failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c.post("/rebuild-metadata",function(e){return async(t,r)=>{try{const t=e.config.metadataDir??".jeeves-metadata",i=[...T];for await(const r of e.vectorStore.scroll()){const e=r.payload,o=e.file_path;if("string"!=typeof o||0===o.length)continue;const s=n.omit(e,i);await E(o,t,s)}return await r.status(200).send({ok:!0})}catch(t){return e.logger.error({err:P(t)},"Rebuild metadata failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,vectorStore:i,logger:s})),c.post("/config-reindex",function(e){return async(t,r)=>{try{const i=t.body.scope??"rules";return(async()=>{try{if("rules"===i){const t=await F(e.config.watch.paths,e.config.watch.ignored,e.processor,"processRulesUpdate");e.logger.info({scope:i,filesProcessed:t},"Config reindex (rules) completed")}else{const t=await F(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");e.logger.info({scope:i,filesProcessed:t},"Config reindex (full) completed")}}catch(t){e.logger.error({err:P(t),scope:i},"Config reindex failed")}})(),await r.status(200).send({status:"started",scope:i})}catch(t){return e.logger.error({err:P(t)},"Config reindex request failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c}const D={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},N={enabled:!0,debounceMs:1e3},A={host:"127.0.0.1",port:3456},O={level:"info"},W={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3},_={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},q=c.z.object({paths:c.z.array(c.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:c.z.array(c.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:c.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:c.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:c.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing.")}),L=c.z.object({enabled:c.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),Q=c.z.object({provider:c.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:c.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:c.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:c.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:c.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:c.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:c.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:c.z.number().optional().describe("Maximum concurrent embedding requests.")}),$=c.z.object({url:c.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:c.z.string().describe("Qdrant collection name for vector storage."),apiKey:c.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),B=c.z.object({host:c.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:c.z.number().optional().describe("Port for API server (e.g., 3456).")}),K=c.z.object({level:c.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:c.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),J=c.z.object({match:c.z.record(c.z.string(),c.z.unknown()).describe("JSON Schema object to match against file attributes."),set:c.z.record(c.z.string(),c.z.unknown()).describe("Metadata fields to set when match succeeds."),map:c.z.union([l.jsonMapMapSchema,c.z.string()]).optional().describe("JsonMap transformation (inline definition or named map reference).")}),G=c.z.object({watch:q.describe("File system watch configuration."),configWatch:L.optional().describe("Configuration file watch settings."),embedding:Q.describe("Embedding model configuration."),vectorStore:$.describe("Qdrant vector store configuration."),metadataDir:c.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:B.optional().describe("API server configuration."),extractors:c.z.record(c.z.string(),c.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:c.z.array(J).optional().describe("Rules for inferring metadata from file attributes."),maps:c.z.record(c.z.string(),l.jsonMapMapSchema).optional().describe("Reusable named JsonMap transformations."),logging:K.optional().describe("Logging configuration."),shutdownTimeoutMs:c.z.number().optional().describe("Timeout in milliseconds for graceful shutdown."),maxRetries:c.z.number().optional().describe("Maximum consecutive system-level failures before triggering fatal error. Default: Infinity."),maxBackoffMs:c.z.number().optional().describe("Maximum backoff delay in milliseconds for system errors. Default: 60000.")}),V=/\$\{([^}]+)\}/g;function U(e){if("string"==typeof e)return function(e){return e.replace(V,((e,t)=>{const r=process.env[t];return void 0===r?e:r}))}(e);if(Array.isArray(e))return e.map((e=>U(e)));if(null!==e&&"object"==typeof e){const t={};for(const[r,i]of Object.entries(e))t[r]=U(i);return t}return e}const H="jeeves-watcher";async function Y(e){const t=a.cosmiconfig(H),r=e?await t.load(e):await t.search();if(!r||r.isEmpty)throw new Error("No jeeves-watcher configuration found. Create a .jeeves-watcherrc or jeeves-watcher.config.{js,ts,json,yaml} file.");try{const e=G.parse(r.config);return U((i=e,{...D,...i,watch:{...W,...i.watch},configWatch:{...N,...i.configWatch},embedding:{..._,...i.embedding},api:{...A,...i.api},logging:{...O,...i.logging}}))}catch(e){if(e instanceof c.ZodError){const t=e.issues.map((e=>`${e.path.join(".")}: ${e.message}`)).join("; ");throw new Error(`Invalid jeeves-watcher configuration: ${t}`)}throw e}var i}function Z(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function X(e,t){return e<=0?Promise.resolve():new Promise(((r,i)=>{const o=setTimeout((()=>{s(),r()}),e),n=()=>{s(),i(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(o),t&&t.removeEventListener("abort",n)};if(t){if(t.aborted)return void n();t.addEventListener("abort",n,{once:!0})}}))}function ee(e,t,r,i=0){const o=Math.max(0,e-1),n=Math.min(r,t*2**o),s=i>0?1+Math.random()*i:1;return Math.round(n*s)}async function te(e,t){const r=Math.max(1,t.attempts);let i;for(let o=1;o<=r;o++)try{return await e(o)}catch(e){i=e;if(o>=r)break;const n=ee(o,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:o,attempts:r,delayMs:n,error:e}),await X(n,t.signal)}throw i}const re=new Map([["mock",function(e){return function(e){return{dimensions:e,embed:t=>Promise.resolve(t.map((t=>{const r=s.createHash("sha256").update(t,"utf8").digest(),i=[];for(let t=0;t<e;t++){const e=r[t%r.length];i.push(e/127.5-1)}return i})))}}(e.dimensions??768)}],["gemini",function(e,t){if(!e.apiKey)throw new Error("Gemini embedding provider requires config.embedding.apiKey");const r=e.dimensions??3072,i=Z(t),o=new h.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:r,async embed(t){const n=await te((async r=>(r>1&&i.warn({attempt:r,provider:"gemini",model:e.model},"Retrying embedding request"),o.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:r,error:o})=>{i.warn({attempt:t,delayMs:r,provider:"gemini",model:e.model,err:P(o)},"Embedding call failed; will retry")}});for(const e of n)if(e.length!==r)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(r)}, got ${String(e.length)}`);return n}}}]]);function ie(e,t){const r=re.get(e.provider);if(!r)throw new Error(`Unsupported embedding provider: ${e.provider}`);return r(e,t)}function oe(e){const t=e?.level??"info";if(e?.file){const r=u.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return u({level:t},r)}return u({level:t})}function ne(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const se="6a6f686e-6761-4c74-ad6a-656576657321";function ae(e,t){const r=void 0!==t?`${z(e)}#${String(t)}`:z(e);return d.v5(r,se)}const ce=["content","body","text","snippet","subject","description","summary","transcript"];function le(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of ce){const r=t[e];if("string"==typeof r&&r.trim())return r}return JSON.stringify(e)}async function he(e){const t=await r.readFile(e,"utf8"),{frontmatter:i,body:o}=function(e){const t=e.replace(/^\uFEFF/,"");if(!/^\s*---/.test(t))return{body:e};const r=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(t);if(!r)return{body:e};const[,i,o]=r,n=g.load(i);return{frontmatter:n&&"object"==typeof n&&!Array.isArray(n)?n:void 0,body:o}}(t);return{text:o,frontmatter:i}}async function ue(e){return{text:(await r.readFile(e,"utf8")).replace(/^\uFEFF/,"")}}async function de(e){const t=await r.readFile(e,"utf8"),i=x.load(t.replace(/^\uFEFF/,""));i("script, style").remove();return{text:i("body").text().trim()||i.text().trim()}}const fe=new Map([[".md",he],[".markdown",he],[".txt",ue],[".text",ue],[".json",async function(e){const t=await r.readFile(e,"utf8"),i=JSON.parse(t.replace(/^\uFEFF/,"")),o=i&&"object"==typeof i&&!Array.isArray(i)?i:void 0;return{text:le(i),json:o}}],[".pdf",async function(e){const t=await r.readFile(e),i=new Uint8Array(t),{extractText:o}=await import("unpdf"),{text:n}=await o(i);return{text:Array.isArray(n)?n.join("\n\n"):n}}],[".docx",async function(e){const t=await r.readFile(e);return{text:(await m.extractRawText({buffer:t})).value}}],[".html",de],[".htm",de]]);async function ge(e,t){const r=fe.get(t.toLowerCase());return r?r(e):ue(e)}function me(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,r)=>{const i=n.get(t,r);return null==i?"":"string"==typeof i?i:JSON.stringify(i)}))}function pe(e,t){const r={};for(const[i,o]of Object.entries(e))r[i]=me(o,t);return r}async function ye(e,t,r,i){const o={split:(e,t)=>e.split(t),slice:(e,t,r)=>e.slice(t,r),join:(e,t)=>e.join(t),toLowerCase:e=>e.toLowerCase(),replace:(e,t,r)=>e.replace(t,r),get:(e,t)=>n.get(e,t)};let s={};const a=i??console;for(const{rule:i,validate:n}of e)if(n(t)){const e=pe(i.set,t);if(s={...s,...e},i.map){let e;if("string"==typeof i.map){if(e=r?.[i.map],!e){a.warn(`Map reference "${i.map}" not found in named maps. Skipping map transformation.`);continue}}else e=i.map;try{const r=new l.JsonMap(e,o),i=await r.transform(t);i&&"object"==typeof i&&!Array.isArray(i)?s={...s,...i}:a.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){a.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}}return s}function we(e,t,r,o){const n=e.replace(/\\/g,"/"),s={file:{path:n,directory:i.dirname(n).replace(/\\/g,"/"),filename:i.basename(n),extension:i.extname(n),sizeBytes:t.size,modified:t.mtime.toISOString()}};return r&&(s.frontmatter=r),o&&(s.json=o),s}function be(e){const t=function(){const e=new p({allErrors:!0});return y(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>o.isMatch(t,e)}),e}();return e.map(((e,r)=>({rule:e,validate:t.compile({$id:`rule-${String(r)}`,...e.match})})))}async function ve(e,t,o,n,s){const a=i.extname(e),c=await r.stat(e),l=await ge(e,a),h=we(e,c,l.frontmatter,l.json),u=await ye(t,h,n,s),d=await R(e,o);return{inferred:u,enrichment:d,metadata:{...u,...d??{}},attributes:h,extracted:l}}function Me(e,t){const r=[];for(let i=0;i<t;i++)r.push(ae(e,i));return r}function xe(e,t=1){if(!e)return t;const r=e.total_chunks;return"number"==typeof r?r:t}class Pe{config;embeddingProvider;vectorStore;compiledRules;logger;constructor(e,t,r,i,o){this.config=e,this.embeddingProvider=t,this.vectorStore=r,this.compiledRules=i,this.logger=o}async processFile(e){try{const t=i.extname(e),{metadata:r,extracted:o}=await ve(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger);if(!o.text.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const n=ne(o.text),s=ae(e,0),a=await this.vectorStore.getPayload(s);if(a&&a.content_hash===n)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const c=xe(a),l=this.config.chunkSize??1e3,h=function(e,t,r){const i=e.toLowerCase();return".md"===i||".markdown"===i?new w.MarkdownTextSplitter({chunkSize:t,chunkOverlap:r}):new w.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:r})}(t,l,this.config.chunkOverlap??200),u=await h.splitText(o.text),d=await this.embeddingProvider.embed(u),f=u.map(((t,i)=>({id:ae(e,i),vector:d[i],payload:{...r,file_path:e.replace(/\\/g,"/"),chunk_index:i,total_chunks:u.length,content_hash:n,chunk_text:t}})));if(await this.vectorStore.upsert(f),c>u.length){const t=Me(e,c).slice(u.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:u.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,err:P(t)},"Failed to process file")}}async deleteFile(e){try{const t=ae(e,0),r=await this.vectorStore.getPayload(t),i=Me(e,xe(r));await this.vectorStore.delete(i),await C(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,err:P(t)},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const r={...await R(e,this.config.metadataDir)??{},...t};await E(e,this.config.metadataDir,r);const i=ae(e,0),o=await this.vectorStore.getPayload(i);if(!o)return null;const n=xe(o),s=Me(e,n);return await this.vectorStore.setPayload(s,r),this.logger.info({filePath:e,chunks:n},"Metadata updated"),r}catch(t){return this.logger.error({filePath:e,err:P(t)},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=ae(e,0),r=await this.vectorStore.getPayload(t);if(!r)return this.logger.debug({filePath:e},"File not indexed, skipping"),null;const{metadata:i}=await ve(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger),o=xe(r),n=Me(e,o);return await this.vectorStore.setPayload(n,i),this.logger.info({filePath:e,chunks:o},"Rules re-applied"),i}catch(t){return this.logger.error({filePath:e,err:P(t)},"Failed to re-apply rules"),null}}updateRules(e){this.compiledRules=e,this.logger.info({rules:e.length},"Inference rules updated")}}class ke{debounceMs;concurrency;rateLimitPerMinute;started=!1;active=0;debounceTimers=new Map;latestByKey=new Map;normalQueue=[];lowQueue=[];tokens;lastRefillMs=Date.now();drainWaiters=[];constructor(e){this.debounceMs=e.debounceMs,this.concurrency=e.concurrency,this.rateLimitPerMinute=e.rateLimitPerMinute,this.tokens=this.rateLimitPerMinute??Number.POSITIVE_INFINITY}enqueue(e,t){const r=`${e.priority}:${e.path}`;this.latestByKey.set(r,{event:e,fn:t});const i=this.debounceTimers.get(r);i&&clearTimeout(i);const o=setTimeout((()=>{this.debounceTimers.delete(r);const e=this.latestByKey.get(r);e&&(this.latestByKey.delete(r),this.push(e),this.pump())}),this.debounceMs);this.debounceTimers.set(r,o)}process(){this.started=!0,this.pump()}async drain(){this.isIdle()||await new Promise((e=>{this.drainWaiters.push(e)}))}push(e){"low"===e.event.priority?this.lowQueue.push(e):this.normalQueue.push(e)}refillTokens(e){if(void 0===this.rateLimitPerMinute)return;const t=Math.max(0,e-this.lastRefillMs)*(this.rateLimitPerMinute/6e4);this.tokens=Math.min(this.rateLimitPerMinute,this.tokens+t),this.lastRefillMs=e}takeToken(){const e=Date.now();return this.refillTokens(e),!(this.tokens<1)&&(this.tokens-=1,!0)}nextItem(){return this.normalQueue.shift()??this.lowQueue.shift()}pump(){if(this.started){for(;this.active<this.concurrency;){const e=this.nextItem();if(!e)break;if(!this.takeToken()){"low"===e.event.priority?this.lowQueue.unshift(e):this.normalQueue.unshift(e),setTimeout((()=>{this.pump()}),250);break}this.active+=1,Promise.resolve().then((()=>e.fn(e.event))).finally((()=>{this.active-=1,this.pump(),this.maybeResolveDrain()}))}this.maybeResolveDrain()}}isIdle(){return 0===this.active&&0===this.normalQueue.length&&0===this.lowQueue.length&&0===this.debounceTimers.size&&0===this.latestByKey.size}maybeResolveDrain(){if(!this.isIdle())return;const e=this.drainWaiters;this.drainWaiters=[];for(const t of e)t()}}class Se{client;collectionName;dims;log;constructor(e,t,r){this.client=new b.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=Z(r)}async ensureCollection(){try{const e=await this.client.getCollections();e.collections.some((e=>e.name===this.collectionName))||await this.client.createCollection(this.collectionName,{vectors:{size:this.dims,distance:"Cosine"}})}catch(e){throw new Error(`Failed to ensure collection "${this.collectionName}": ${String(e)}`)}}async upsert(e){0!==e.length&&await te((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.upsert",points:e.length},"Retrying Qdrant upsert"),await this.client.upsert(this.collectionName,{wait:!0,points:e.map((e=>({id:e.id,vector:e.vector,payload:e.payload})))})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:r})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.upsert",err:P(r)},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await te((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.delete",ids:e.length},"Retrying Qdrant delete"),await this.client.delete(this.collectionName,{wait:!0,points:e})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:r})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.delete",err:P(r)},"Qdrant delete failed; will retry")}})}async setPayload(e,t){0!==e.length&&await this.client.setPayload(this.collectionName,{wait:!0,points:e,payload:t})}async getPayload(e){try{const t=await this.client.retrieve(this.collectionName,{ids:[e],with_payload:!0,with_vector:!1});return 0===t.length?null:t[0].payload}catch{return null}}async search(e,t,r){return(await this.client.search(this.collectionName,{vector:e,limit:t,with_payload:!0,...r?{filter:r}:{}})).map((e=>({id:String(e.id),score:e.score,payload:e.payload})))}async*scroll(e,t=100){let r;for(;;){const i=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1,...e?{filter:e}:{},...void 0!==r?{offset:r}:{}});for(const e of i.points)yield{id:String(e.id),payload:e.payload};const o=i.next_page_offset;if(null==o)break;if("string"!=typeof o&&"number"!=typeof o)break;r=o}}}class Fe{consecutiveFailures=0;maxRetries;maxBackoffMs;baseDelayMs;onFatalError;logger;constructor(e){this.maxRetries=e.maxRetries??Number.POSITIVE_INFINITY,this.maxBackoffMs=e.maxBackoffMs??6e4,this.baseDelayMs=e.baseDelayMs??1e3,this.onFatalError=e.onFatalError,this.logger=e.logger}recordSuccess(){this.consecutiveFailures>0&&this.logger.info({previousFailures:this.consecutiveFailures},"System health recovered"),this.consecutiveFailures=0}recordFailure(e){if(this.consecutiveFailures+=1,this.logger.error({consecutiveFailures:this.consecutiveFailures,maxRetries:this.maxRetries,err:P(e)},"System-level failure recorded"),this.consecutiveFailures>=this.maxRetries){if(this.logger.fatal({consecutiveFailures:this.consecutiveFailures},"Maximum retries exceeded, triggering fatal error"),this.onFatalError)return this.onFatalError(e),!1;throw e instanceof Error?e:new Error(`Fatal system error: ${String(e)}`)}return!0}get currentBackoffMs(){if(0===this.consecutiveFailures)return 0;const e=Math.max(0,this.consecutiveFailures-1);return Math.min(this.maxBackoffMs,this.baseDelayMs*2**e)}async backoff(e){const t=this.currentBackoffMs;t<=0||(this.logger.warn({delayMs:t,consecutiveFailures:this.consecutiveFailures},"Backing off before next attempt"),await new Promise(((r,i)=>{const o=setTimeout((()=>{s(),r()}),t),n=()=>{s(),i(new Error("Backoff aborted"))},s=()=>{clearTimeout(o),e&&e.removeEventListener("abort",n)};if(e){if(e.aborted)return void n();e.addEventListener("abort",n,{once:!0})}})))}get failures(){return this.consecutiveFailures}}class ze{config;queue;processor;logger;health;watcher;constructor(e,t,r,i,o={}){this.config=e,this.queue=t,this.processor=r,this.logger=i;const n={maxRetries:o.maxRetries,maxBackoffMs:o.maxBackoffMs,onFatalError:o.onFatalError,logger:i};this.health=new Fe(n)}start(){this.watcher=v.watch(this.config.paths,{ignored:this.config.ignored,usePolling:this.config.usePolling,interval:this.config.pollIntervalMs,awaitWriteFinish:!!this.config.stabilityThresholdMs&&{stabilityThreshold:this.config.stabilityThresholdMs},ignoreInitial:!1}),this.watcher.on("add",(e=>{this.logger.debug({path:e},"File added"),this.queue.enqueue({type:"create",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e)))))})),this.watcher.on("change",(e=>{this.logger.debug({path:e},"File changed"),this.queue.enqueue({type:"modify",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e)))))})),this.watcher.on("unlink",(e=>{this.logger.debug({path:e},"File removed"),this.queue.enqueue({type:"delete",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.deleteFile(e)))))})),this.watcher.on("error",(e=>{this.logger.error({err:P(e)},"Watcher error"),this.health.recordFailure(e)})),this.queue.process(),this.logger.info({paths:this.config.paths},"Filesystem watcher started")}async stop(){this.watcher&&(await this.watcher.close(),this.watcher=void 0,this.logger.info("Filesystem watcher stopped"))}get systemHealth(){return this.health}async wrapProcessing(e){try{await this.health.backoff(),await e(),this.health.recordSuccess()}catch(e){this.health.recordFailure(e)||await this.stop()}}}class je{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=v.watch(this.options.configPath,{ignoreInitial:!0}),this.watcher.on("change",(()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout((()=>{this.options.onChange()}),this.options.debounceMs)})),this.watcher.on("error",(e=>{this.options.logger.error({err:P(e)},"Config watcher error")})),this.options.logger.info({configPath:this.options.configPath,debounceMs:this.options.debounceMs},"Config watcher started"))}async stop(){this.debounce&&(clearTimeout(this.debounce),this.debounce=void 0),this.watcher&&(await this.watcher.close(),this.watcher=void 0)}}const Re={loadConfig:Y,createLogger:oe,createEmbeddingProvider:ie,createVectorStoreClient:(e,t,r)=>new Se(e,t,r),compileRules:be,createDocumentProcessor:(e,t,r,i,o)=>new Pe(e,t,r,i,o),createEventQueue:e=>new ke(e),createFileSystemWatcher:(e,t,r,i,o)=>new ze(e,t,r,i,o),createApiServer:I};class Ee{config;configPath;factories;runtimeOptions;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,r={},i={}){this.config=e,this.configPath=t,this.factories={...Re,...r},this.runtimeOptions=i}async start(){const e=this.factories.createLogger(this.config.logging);let t;this.logger=e;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({err:P(t)},"Failed to create embedding provider"),t}const r=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);await r.ensureCollection();const i=this.factories.compileRules(this.config.inferenceRules??[]),o={metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps},n=this.factories.createDocumentProcessor(o,t,r,i,e);this.processor=n;const s=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute});this.queue=s;const a=this.factories.createFileSystemWatcher(this.config.watch,s,n,e,{maxRetries:this.config.maxRetries,maxBackoffMs:this.config.maxBackoffMs,onFatalError:this.runtimeOptions.onFatalError});this.watcher=a;const c=this.factories.createApiServer({processor:n,vectorStore:r,embeddingProvider:t,queue:s,config:this.config,logger:e});this.server=c,await c.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),a.start(),this.startConfigWatch(),e.info("jeeves-watcher started")}async stop(){if(await this.stopConfigWatch(),this.watcher&&await this.watcher.stop(),this.queue){const e=this.config.shutdownTimeoutMs??1e4;await Promise.race([this.queue.drain().then((()=>!0)),new Promise((t=>{setTimeout((()=>{t(!1)}),e)}))])||this.logger?.warn({timeoutMs:e},"Queue drain timeout hit, forcing shutdown")}this.server&&await this.server.close(),this.logger?.info("jeeves-watcher stopped")}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;if(!t)return;if(!this.configPath)return void e.debug("Config watch enabled, but no config path was provided");const r=this.config.configWatch?.debounceMs??1e4;this.configWatcher=new je({configPath:this.configPath,enabled:t,debounceMs:r,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()}async stopConfigWatch(){this.configWatcher&&(await this.configWatcher.stop(),this.configWatcher=void 0)}async reloadConfig(){const e=this.logger,t=this.processor;if(e&&t&&this.configPath){e.info({configPath:this.configPath},"Config change detected, reloading...");try{const r=await this.factories.loadConfig(this.configPath);this.config=r;const i=this.factories.compileRules(r.inferenceRules??[]);t.updateRules(i),e.info({configPath:this.configPath,rules:i.length},"Config reloaded")}catch(t){e.error({err:P(t)},"Failed to reload config")}}}}e.DocumentProcessor=Pe,e.EventQueue=ke,e.FileSystemWatcher=ze,e.JeevesWatcher=Ee,e.SystemHealth=Fe,e.VectorStoreClient=Se,e.apiConfigSchema=B,e.applyRules=ye,e.buildAttributes=we,e.compileRules=be,e.configWatchConfigSchema=L,e.contentHash=ne,e.createApiServer=I,e.createEmbeddingProvider=ie,e.createLogger=oe,e.deleteMetadata=C,e.embeddingConfigSchema=Q,e.extractText=ge,e.inferenceRuleSchema=J,e.jeevesWatcherConfigSchema=G,e.loadConfig=Y,e.loggingConfigSchema=K,e.metadataPath=j,e.pointId=ae,e.readMetadata=R,e.startFromConfig=async function(e){const t=await Y(e),r=new Ee(t,e);return function(e){const t=async()=>{await e(),process.exit(0)};process.on("SIGTERM",(()=>{t()})),process.on("SIGINT",(()=>{t()}))}((()=>r.stop())),await r.start(),r},e.vectorStoreConfigSchema=$,e.watchConfigSchema=q,e.writeMetadata=E}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,cosmiconfig,zod,jsonmap,googleGenai,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest,chokidar);
|
|
1
|
+
!function(e,t,i,r,o,n,s,a,c,l,h,d,u,g,f,p,m,y,w,b,v,M,x){"use strict";function S(e){var t=Object.create(null);return e&&Object.keys(e).forEach((function(i){if("default"!==i){var r=Object.getOwnPropertyDescriptor(e,i);Object.defineProperty(t,i,r.get?r:{enumerable:!0,get:function(){return e[i]}})}})),t.default=e,Object.freeze(t)}var F=S(p);function P(e){if(e instanceof Error)return e;if("string"==typeof e)return new Error(e);const t=String("object"==typeof e&&null!==e&&"message"in e?e.message:e),i=new Error(t);return i.cause=e,i}function k(e){const t=e.replace(/\\/g,"/"),i=t.search(/[*?\[]/);if(-1===i)return r.resolve(e);const o=t.slice(0,i),n=o.endsWith("/")?o.slice(0,-1):r.dirname(o);return r.resolve(n)}async function*z(e){let t;try{t=(await i.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const o of t){const t=r.resolve(e,o.name);if(o.isDirectory)yield*z(t);else try{(await i.stat(t)).isFile()&&(yield t)}catch{}}}async function j(e,t,i,r){const n=await async function(e,t=[]){const i=e.map((e=>e.replace(/\\/g,"/"))),r=t.map((e=>e.replace(/\\/g,"/"))),n=o(i,{dot:!0}),s=r.length?o(r,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(k))),c=new Set;for(const e of a)for await(const t of z(e)){const e=t.replace(/\\/g,"/");s(e)||n(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of n)await i[r](e);return n.length}function R(e,t=!1){let i=e.replace(/\\/g,"/").toLowerCase();return t&&(i=i.replace(/^([a-z]):/,((e,t)=>t))),i}function E(e,t){const i=R(e,!0),o=s.createHash("sha256").update(i,"utf8").digest("hex");return r.join(t,`${o}.meta.json`)}async function C(e,t){try{const r=await i.readFile(E(e,t),"utf8");return JSON.parse(r)}catch{return null}}async function D(e,t,o){const n=E(e,t);await i.mkdir(r.dirname(n),{recursive:!0}),await i.writeFile(n,JSON.stringify(o,null,2),"utf8")}async function T(e,t){try{await i.rm(E(e,t))}catch{}}const I=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function W(e){const{processor:i,vectorStore:r,embeddingProvider:o,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(()=>({status:"ok",uptime:process.uptime()}))),c.post("/metadata",(l={processor:i,logger:s},async(e,t)=>{try{const{path:t,metadata:i}=e.body;return await l.processor.processMetadataUpdate(t,i),{ok:!0}}catch(e){return l.logger.error({err:P(e)},"Metadata update failed"),t.status(500).send({error:"Internal server error"})}})),c.post("/search",function(e){return async(t,i)=>{try{const{query:i,limit:r=10}=t.body,o=await e.embeddingProvider.embed([i]);return await e.vectorStore.search(o[0],r)}catch(t){return e.logger.error({err:P(t)},"Search failed"),i.status(500).send({error:"Internal server error"})}}}({embeddingProvider:o,vectorStore:r,logger:s})),c.post("/reindex",function(e){return async(t,i)=>{try{const t=await j(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");return await i.status(200).send({ok:!0,filesIndexed:t})}catch(t){return e.logger.error({err:P(t)},"Reindex failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,processor:i,logger:s})),c.post("/rebuild-metadata",function(e){return async(t,i)=>{try{const t=e.config.metadataDir??".jeeves-metadata",r=[...I];for await(const i of e.vectorStore.scroll()){const e=i.payload,o=e.file_path;if("string"!=typeof o||0===o.length)continue;const s=n.omit(e,r);await D(o,t,s)}return await i.status(200).send({ok:!0})}catch(t){return e.logger.error({err:P(t)},"Rebuild metadata failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,vectorStore:r,logger:s})),c.post("/config-reindex",function(e){return async(t,i)=>{try{const r=t.body.scope??"rules";return(async()=>{try{if("rules"===r){const t=await j(e.config.watch.paths,e.config.watch.ignored,e.processor,"processRulesUpdate");e.logger.info({scope:r,filesProcessed:t},"Config reindex (rules) completed")}else{const t=await j(e.config.watch.paths,e.config.watch.ignored,e.processor,"processFile");e.logger.info({scope:r,filesProcessed:t},"Config reindex (full) completed")}}catch(t){e.logger.error({err:P(t),scope:r},"Config reindex failed")}})(),await i.status(200).send({status:"started",scope:r})}catch(t){return e.logger.error({err:P(t)},"Config reindex request failed"),await i.status(500).send({error:"Internal server error"})}}}({config:a,processor:i,logger:s})),c}const N={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},O={enabled:!0,debounceMs:1e3},_={host:"127.0.0.1",port:3456},A={level:"info"},q={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3,respectGitignore:!0},L={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},Q=c.z.object({paths:c.z.array(c.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:c.z.array(c.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:c.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:c.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:c.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing."),respectGitignore:c.z.boolean().optional().describe("Skip files ignored by .gitignore in git repositories. Only applies to repos with a .git directory. Default: true.")}),$=c.z.object({enabled:c.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:c.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),G=c.z.object({provider:c.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:c.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:c.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:c.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:c.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:c.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:c.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:c.z.number().optional().describe("Maximum concurrent embedding requests.")}),B=c.z.object({url:c.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:c.z.string().describe("Qdrant collection name for vector storage."),apiKey:c.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),K=c.z.object({host:c.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:c.z.number().optional().describe("Port for API server (e.g., 3456).")}),J=c.z.object({level:c.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:c.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),V=c.z.object({match:c.z.record(c.z.string(),c.z.unknown()).describe("JSON Schema object to match against file attributes."),set:c.z.record(c.z.string(),c.z.unknown()).describe("Metadata fields to set when match succeeds."),map:c.z.union([l.jsonMapMapSchema,c.z.string()]).optional().describe("JsonMap transformation (inline definition or named map reference).")}),U=c.z.object({watch:Q.describe("File system watch configuration."),configWatch:$.optional().describe("Configuration file watch settings."),embedding:G.describe("Embedding model configuration."),vectorStore:B.describe("Qdrant vector store configuration."),metadataDir:c.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:K.optional().describe("API server configuration."),extractors:c.z.record(c.z.string(),c.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:c.z.array(V).optional().describe("Rules for inferring metadata from file attributes."),maps:c.z.record(c.z.string(),l.jsonMapMapSchema).optional().describe("Reusable named JsonMap transformations."),logging:J.optional().describe("Logging configuration."),shutdownTimeoutMs:c.z.number().optional().describe("Timeout in milliseconds for graceful shutdown."),maxRetries:c.z.number().optional().describe("Maximum consecutive system-level failures before triggering fatal error. Default: Infinity."),maxBackoffMs:c.z.number().optional().describe("Maximum backoff delay in milliseconds for system errors. Default: 60000.")}),H=/\$\{([^}]+)\}/g;function Y(e){if("string"==typeof e)return function(e){return e.replace(H,((e,t)=>{const i=process.env[t];return void 0===i?e:i}))}(e);if(Array.isArray(e))return e.map((e=>Y(e)));if(null!==e&&"object"==typeof e){const t={};for(const[i,r]of Object.entries(e))t[i]=Y(r);return t}return e}const Z="jeeves-watcher";async function X(e){const t=a.cosmiconfig(Z),i=e?await t.load(e):await t.search();if(!i||i.isEmpty)throw new Error("No jeeves-watcher configuration found. Create a .jeeves-watcherrc or jeeves-watcher.config.{js,ts,json,yaml} file.");try{const e=U.parse(i.config);return Y((r=e,{...N,...r,watch:{...q,...r.watch},configWatch:{...O,...r.configWatch},embedding:{...L,...r.embedding},api:{..._,...r.api},logging:{...A,...r.logging}}))}catch(e){if(e instanceof c.ZodError){const t=e.issues.map((e=>`${e.path.join(".")}: ${e.message}`)).join("; ");throw new Error(`Invalid jeeves-watcher configuration: ${t}`)}throw e}var r}function ee(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function te(e,t){return e<=0?Promise.resolve():new Promise(((i,r)=>{const o=setTimeout((()=>{s(),i()}),e),n=()=>{s(),r(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(o),t&&t.removeEventListener("abort",n)};if(t){if(t.aborted)return void n();t.addEventListener("abort",n,{once:!0})}}))}function ie(e,t,i,r=0){const o=Math.max(0,e-1),n=Math.min(i,t*2**o),s=r>0?1+Math.random()*r:1;return Math.round(n*s)}async function re(e,t){const i=Math.max(1,t.attempts);let r;for(let o=1;o<=i;o++)try{return await e(o)}catch(e){r=e;if(o>=i)break;const n=ie(o,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:o,attempts:i,delayMs:n,error:e}),await te(n,t.signal)}throw r}const oe=new Map([["mock",function(e){return function(e){return{dimensions:e,embed:t=>Promise.resolve(t.map((t=>{const i=s.createHash("sha256").update(t,"utf8").digest(),r=[];for(let t=0;t<e;t++){const e=i[t%i.length];r.push(e/127.5-1)}return r})))}}(e.dimensions??768)}],["gemini",function(e,t){if(!e.apiKey)throw new Error("Gemini embedding provider requires config.embedding.apiKey");const i=e.dimensions??3072,r=ee(t),o=new h.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:i,async embed(t){const n=await re((async i=>(i>1&&r.warn({attempt:i,provider:"gemini",model:e.model},"Retrying embedding request"),o.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:i,error:o})=>{r.warn({attempt:t,delayMs:i,provider:"gemini",model:e.model,err:P(o)},"Embedding call failed; will retry")}});for(const e of n)if(e.length!==i)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(i)}, got ${String(e.length)}`);return n}}}]]);function ne(e,t){const i=oe.get(e.provider);if(!i)throw new Error(`Unsupported embedding provider: ${e.provider}`);return i(e,t)}function se(e){let t=r.resolve(e);const i=r.resolve("/");for(;t!==i;){if(d.existsSync(r.join(t,".git"))&&d.statSync(r.join(t,".git")).isDirectory())return t;const e=r.dirname(t);if(e===t)break;t=e}}function ae(e){const t=r.resolve(e);try{return d.statSync(t).isDirectory()?t:r.dirname(t)}catch{}const i=/[*?[{]/.exec(e);if(!i)return;const o=e.slice(0,i.index).trim(),n=0===o.length?".":o.endsWith("/")||o.endsWith("\\")?o:r.dirname(o),s=r.resolve(n);return d.existsSync(s)?s:void 0}function ce(e){const t=[],i=r.join(e,".gitignore");let o;d.existsSync(i)&&t.push(i);try{o=d.readdirSync(e)}catch{return t}for(const i of o){if(".git"===i||"node_modules"===i)continue;const o=r.join(e,i);try{d.statSync(o).isDirectory()&&t.push(...ce(o))}catch{}}return t}function le(e){const t=d.readFileSync(e,"utf8");return u().add(t)}class he{repos=new Map;constructor(e){this.scan(e)}scan(e){this.repos.clear();const t=new Set;for(const i of e){const e=ae(i);if(!e)continue;if(t.has(e))continue;t.add(e);const o=se(e);if(!o)continue;if(this.repos.has(o))continue;const n=ce(o).map((e=>({dir:r.dirname(e),ig:le(e)})));n.sort(((e,t)=>t.dir.length-e.dir.length)),this.repos.set(o,{root:o,entries:n})}}isIgnored(e){const t=r.resolve(e);for(const[,e]of this.repos){const i=r.relative(e.root,t);if(!i.startsWith("..")&&!i.startsWith(r.resolve("/")))for(const i of e.entries){const e=r.relative(i.dir,t);if(e.startsWith(".."))continue;const o=e.replace(/\\/g,"/");if(i.ig.ignores(o))return!0}}return!1}invalidate(e){const t=r.resolve(e),i=r.dirname(t);for(const[,e]of this.repos){if(!r.relative(e.root,i).startsWith(".."))return e.entries=e.entries.filter((e=>e.dir!==i)),void(d.existsSync(t)&&(e.entries.push({dir:i,ig:le(t)}),e.entries.sort(((e,t)=>t.dir.length-e.dir.length))))}const o=se(i);if(o&&d.existsSync(t)){const e=[{dir:i,ig:le(t)}];if(this.repos.has(o)){const t=this.repos.get(o);t.entries.push(e[0]),t.entries.sort(((e,t)=>t.dir.length-e.dir.length))}else this.repos.set(o,{root:o,entries:e})}}}function de(e){const t=e?.level??"info";if(e?.file){const i=g.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return g({level:t},i)}return g({level:t})}function ue(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const ge="6a6f686e-6761-4c74-ad6a-656576657321";function fe(e,t){const i=void 0!==t?`${R(e)}#${String(t)}`:R(e);return f.v5(i,ge)}const pe=["content","body","text","snippet","subject","description","summary","transcript"];function me(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of pe){const i=t[e];if("string"==typeof i&&i.trim())return i}return JSON.stringify(e)}async function ye(e){const t=await i.readFile(e,"utf8"),{frontmatter:r,body:o}=function(e){const t=e.replace(/^\uFEFF/,"");if(!/^\s*---/.test(t))return{body:e};const i=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/m.exec(t);if(!i)return{body:e};const[,r,o]=i,n=m.load(r);return{frontmatter:n&&"object"==typeof n&&!Array.isArray(n)?n:void 0,body:o}}(t);return{text:o,frontmatter:r}}async function we(e){return{text:(await i.readFile(e,"utf8")).replace(/^\uFEFF/,"")}}async function be(e){const t=await i.readFile(e,"utf8"),r=F.load(t.replace(/^\uFEFF/,""));r("script, style").remove();return{text:r("body").text().trim()||r.text().trim()}}const ve=new Map([[".md",ye],[".markdown",ye],[".txt",we],[".text",we],[".json",async function(e){const t=await i.readFile(e,"utf8"),r=JSON.parse(t.replace(/^\uFEFF/,"")),o=r&&"object"==typeof r&&!Array.isArray(r)?r:void 0;return{text:me(r),json:o}}],[".pdf",async function(e){const t=await i.readFile(e),r=new Uint8Array(t),{extractText:o}=await import("unpdf"),{text:n}=await o(r);return{text:Array.isArray(n)?n.join("\n\n"):n}}],[".docx",async function(e){const t=await i.readFile(e);return{text:(await y.extractRawText({buffer:t})).value}}],[".html",be],[".htm",be]]);async function Me(e,t){const i=ve.get(t.toLowerCase());return i?i(e):we(e)}function xe(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,i)=>{const r=n.get(t,i);return null==r?"":"string"==typeof r?r:JSON.stringify(r)}))}function Se(e,t){const i={};for(const[r,o]of Object.entries(e))i[r]=xe(o,t);return i}async function Fe(e,t,i,r){const o={split:(e,t)=>e.split(t),slice:(e,t,i)=>e.slice(t,i),join:(e,t)=>e.join(t),toLowerCase:e=>e.toLowerCase(),replace:(e,t,i)=>e.replace(t,i),get:(e,t)=>n.get(e,t)};let s={};const a=r??console;for(const{rule:r,validate:n}of e)if(n(t)){const e=Se(r.set,t);if(s={...s,...e},r.map){let e;if("string"==typeof r.map){if(e=i?.[r.map],!e){a.warn(`Map reference "${r.map}" not found in named maps. Skipping map transformation.`);continue}}else e=r.map;try{const i=new l.JsonMap(e,o),r=await i.transform(t);r&&"object"==typeof r&&!Array.isArray(r)?s={...s,...r}:a.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){a.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}}return s}function Pe(e,t,i,o){const n=e.replace(/\\/g,"/"),s={file:{path:n,directory:r.dirname(n).replace(/\\/g,"/"),filename:r.basename(n),extension:r.extname(n),sizeBytes:t.size,modified:t.mtime.toISOString()}};return i&&(s.frontmatter=i),o&&(s.json=o),s}function ke(e){const t=function(){const e=new w({allErrors:!0});return b(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>o.isMatch(t,e)}),e}();return e.map(((e,i)=>({rule:e,validate:t.compile({$id:`rule-${String(i)}`,...e.match})})))}async function ze(e,t,o,n,s){const a=r.extname(e),c=await i.stat(e),l=await Me(e,a),h=Pe(e,c,l.frontmatter,l.json),d=await Fe(t,h,n,s),u=await C(e,o);return{inferred:d,enrichment:u,metadata:{...d,...u??{}},attributes:h,extracted:l}}function je(e,t){const i=[];for(let r=0;r<t;r++)i.push(fe(e,r));return i}function Re(e,t=1){if(!e)return t;const i=e.total_chunks;return"number"==typeof i?i:t}class Ee{config;embeddingProvider;vectorStore;compiledRules;logger;constructor(e,t,i,r,o){this.config=e,this.embeddingProvider=t,this.vectorStore=i,this.compiledRules=r,this.logger=o}async processFile(e){try{const t=r.extname(e),{metadata:i,extracted:o}=await ze(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger);if(!o.text.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const n=ue(o.text),s=fe(e,0),a=await this.vectorStore.getPayload(s);if(a&&a.content_hash===n)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const c=Re(a),l=this.config.chunkSize??1e3,h=function(e,t,i){const r=e.toLowerCase();return".md"===r||".markdown"===r?new v.MarkdownTextSplitter({chunkSize:t,chunkOverlap:i}):new v.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:i})}(t,l,this.config.chunkOverlap??200),d=await h.splitText(o.text),u=await this.embeddingProvider.embed(d),g=d.map(((t,r)=>({id:fe(e,r),vector:u[r],payload:{...i,file_path:e.replace(/\\/g,"/"),chunk_index:r,total_chunks:d.length,content_hash:n,chunk_text:t}})));if(await this.vectorStore.upsert(g),c>d.length){const t=je(e,c).slice(d.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:d.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,err:P(t)},"Failed to process file")}}async deleteFile(e){try{const t=fe(e,0),i=await this.vectorStore.getPayload(t),r=je(e,Re(i));await this.vectorStore.delete(r),await T(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,err:P(t)},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const i={...await C(e,this.config.metadataDir)??{},...t};await D(e,this.config.metadataDir,i);const r=fe(e,0),o=await this.vectorStore.getPayload(r);if(!o)return null;const n=Re(o),s=je(e,n);return await this.vectorStore.setPayload(s,i),this.logger.info({filePath:e,chunks:n},"Metadata updated"),i}catch(t){return this.logger.error({filePath:e,err:P(t)},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=fe(e,0),i=await this.vectorStore.getPayload(t);if(!i)return this.logger.debug({filePath:e},"File not indexed, skipping"),null;const{metadata:r}=await ze(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger),o=Re(i),n=je(e,o);return await this.vectorStore.setPayload(n,r),this.logger.info({filePath:e,chunks:o},"Rules re-applied"),r}catch(t){return this.logger.error({filePath:e,err:P(t)},"Failed to re-apply rules"),null}}updateRules(e){this.compiledRules=e,this.logger.info({rules:e.length},"Inference rules updated")}}class Ce{debounceMs;concurrency;rateLimitPerMinute;started=!1;active=0;debounceTimers=new Map;latestByKey=new Map;normalQueue=[];lowQueue=[];tokens;lastRefillMs=Date.now();drainWaiters=[];constructor(e){this.debounceMs=e.debounceMs,this.concurrency=e.concurrency,this.rateLimitPerMinute=e.rateLimitPerMinute,this.tokens=this.rateLimitPerMinute??Number.POSITIVE_INFINITY}enqueue(e,t){const i=`${e.priority}:${e.path}`;this.latestByKey.set(i,{event:e,fn:t});const r=this.debounceTimers.get(i);r&&clearTimeout(r);const o=setTimeout((()=>{this.debounceTimers.delete(i);const e=this.latestByKey.get(i);e&&(this.latestByKey.delete(i),this.push(e),this.pump())}),this.debounceMs);this.debounceTimers.set(i,o)}process(){this.started=!0,this.pump()}async drain(){this.isIdle()||await new Promise((e=>{this.drainWaiters.push(e)}))}push(e){"low"===e.event.priority?this.lowQueue.push(e):this.normalQueue.push(e)}refillTokens(e){if(void 0===this.rateLimitPerMinute)return;const t=Math.max(0,e-this.lastRefillMs)*(this.rateLimitPerMinute/6e4);this.tokens=Math.min(this.rateLimitPerMinute,this.tokens+t),this.lastRefillMs=e}takeToken(){const e=Date.now();return this.refillTokens(e),!(this.tokens<1)&&(this.tokens-=1,!0)}nextItem(){return this.normalQueue.shift()??this.lowQueue.shift()}pump(){if(this.started){for(;this.active<this.concurrency;){const e=this.nextItem();if(!e)break;if(!this.takeToken()){"low"===e.event.priority?this.lowQueue.unshift(e):this.normalQueue.unshift(e),setTimeout((()=>{this.pump()}),250);break}this.active+=1,Promise.resolve().then((()=>e.fn(e.event))).finally((()=>{this.active-=1,this.pump(),this.maybeResolveDrain()}))}this.maybeResolveDrain()}}isIdle(){return 0===this.active&&0===this.normalQueue.length&&0===this.lowQueue.length&&0===this.debounceTimers.size&&0===this.latestByKey.size}maybeResolveDrain(){if(!this.isIdle())return;const e=this.drainWaiters;this.drainWaiters=[];for(const t of e)t()}}class De{client;collectionName;dims;log;constructor(e,t,i){this.client=new M.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=ee(i)}async ensureCollection(){try{const e=await this.client.getCollections();e.collections.some((e=>e.name===this.collectionName))||await this.client.createCollection(this.collectionName,{vectors:{size:this.dims,distance:"Cosine"}})}catch(e){throw new Error(`Failed to ensure collection "${this.collectionName}": ${String(e)}`)}}async upsert(e){0!==e.length&&await re((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.upsert",points:e.length},"Retrying Qdrant upsert"),await this.client.upsert(this.collectionName,{wait:!0,points:e.map((e=>({id:e.id,vector:e.vector,payload:e.payload})))})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:i})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.upsert",err:P(i)},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await re((async t=>{t>1&&this.log.warn({attempt:t,operation:"qdrant.delete",ids:e.length},"Retrying Qdrant delete"),await this.client.delete(this.collectionName,{wait:!0,points:e})}),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:e,delayMs:t,error:i})=>{this.log.warn({attempt:e,delayMs:t,operation:"qdrant.delete",err:P(i)},"Qdrant delete failed; will retry")}})}async setPayload(e,t){0!==e.length&&await this.client.setPayload(this.collectionName,{wait:!0,points:e,payload:t})}async getPayload(e){try{const t=await this.client.retrieve(this.collectionName,{ids:[e],with_payload:!0,with_vector:!1});return 0===t.length?null:t[0].payload}catch{return null}}async search(e,t,i){return(await this.client.search(this.collectionName,{vector:e,limit:t,with_payload:!0,...i?{filter:i}:{}})).map((e=>({id:String(e.id),score:e.score,payload:e.payload})))}async*scroll(e,t=100){let i;for(;;){const r=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1,...e?{filter:e}:{},...void 0!==i?{offset:i}:{}});for(const e of r.points)yield{id:String(e.id),payload:e.payload};const o=r.next_page_offset;if(null==o)break;if("string"!=typeof o&&"number"!=typeof o)break;i=o}}}class Te{consecutiveFailures=0;maxRetries;maxBackoffMs;baseDelayMs;onFatalError;logger;constructor(e){this.maxRetries=e.maxRetries??Number.POSITIVE_INFINITY,this.maxBackoffMs=e.maxBackoffMs??6e4,this.baseDelayMs=e.baseDelayMs??1e3,this.onFatalError=e.onFatalError,this.logger=e.logger}recordSuccess(){this.consecutiveFailures>0&&this.logger.info({previousFailures:this.consecutiveFailures},"System health recovered"),this.consecutiveFailures=0}recordFailure(e){if(this.consecutiveFailures+=1,this.logger.error({consecutiveFailures:this.consecutiveFailures,maxRetries:this.maxRetries,err:P(e)},"System-level failure recorded"),this.consecutiveFailures>=this.maxRetries){if(this.logger.fatal({consecutiveFailures:this.consecutiveFailures},"Maximum retries exceeded, triggering fatal error"),this.onFatalError)return this.onFatalError(e),!1;throw e instanceof Error?e:new Error(`Fatal system error: ${String(e)}`)}return!0}get currentBackoffMs(){if(0===this.consecutiveFailures)return 0;const e=Math.max(0,this.consecutiveFailures-1);return Math.min(this.maxBackoffMs,this.baseDelayMs*2**e)}async backoff(e){const t=this.currentBackoffMs;t<=0||(this.logger.warn({delayMs:t,consecutiveFailures:this.consecutiveFailures},"Backing off before next attempt"),await new Promise(((i,r)=>{const o=setTimeout((()=>{s(),i()}),t),n=()=>{s(),r(new Error("Backoff aborted"))},s=()=>{clearTimeout(o),e&&e.removeEventListener("abort",n)};if(e){if(e.aborted)return void n();e.addEventListener("abort",n,{once:!0})}})))}get failures(){return this.consecutiveFailures}}class Ie{config;queue;processor;logger;health;gitignoreFilter;watcher;constructor(e,t,i,r,o={}){this.config=e,this.queue=t,this.processor=i,this.logger=r,this.gitignoreFilter=o.gitignoreFilter;const n={maxRetries:o.maxRetries,maxBackoffMs:o.maxBackoffMs,onFatalError:o.onFatalError,logger:r};this.health=new Te(n)}start(){this.watcher=x.watch(this.config.paths,{ignored:this.config.ignored,usePolling:this.config.usePolling,interval:this.config.pollIntervalMs,awaitWriteFinish:!!this.config.stabilityThresholdMs&&{stabilityThreshold:this.config.stabilityThresholdMs},ignoreInitial:!1}),this.watcher.on("add",(e=>{this.handleGitignoreChange(e),this.isGitignored(e)||(this.logger.debug({path:e},"File added"),this.queue.enqueue({type:"create",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e))))))})),this.watcher.on("change",(e=>{this.handleGitignoreChange(e),this.isGitignored(e)||(this.logger.debug({path:e},"File changed"),this.queue.enqueue({type:"modify",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.processFile(e))))))})),this.watcher.on("unlink",(e=>{this.handleGitignoreChange(e),this.isGitignored(e)||(this.logger.debug({path:e},"File removed"),this.queue.enqueue({type:"delete",path:e,priority:"normal"},(()=>this.wrapProcessing((()=>this.processor.deleteFile(e))))))})),this.watcher.on("error",(e=>{this.logger.error({err:P(e)},"Watcher error"),this.health.recordFailure(e)})),this.queue.process(),this.logger.info({paths:this.config.paths},"Filesystem watcher started")}async stop(){this.watcher&&(await this.watcher.close(),this.watcher=void 0,this.logger.info("Filesystem watcher stopped"))}get systemHealth(){return this.health}isGitignored(e){if(!this.gitignoreFilter)return!1;const t=this.gitignoreFilter.isIgnored(e);return t&&this.logger.debug({path:e},"Skipping gitignored file"),t}handleGitignoreChange(e){this.gitignoreFilter&&e.endsWith(".gitignore")&&(this.logger.info({path:e},"Gitignore file changed, refreshing filter"),this.gitignoreFilter.invalidate(e))}async wrapProcessing(e){try{await this.health.backoff(),await e(),this.health.recordSuccess()}catch(e){this.health.recordFailure(e)||await this.stop()}}}class We{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=x.watch(this.options.configPath,{ignoreInitial:!0}),this.watcher.on("change",(()=>{this.debounce&&clearTimeout(this.debounce),this.debounce=setTimeout((()=>{this.options.onChange()}),this.options.debounceMs)})),this.watcher.on("error",(e=>{this.options.logger.error({err:P(e)},"Config watcher error")})),this.options.logger.info({configPath:this.options.configPath,debounceMs:this.options.debounceMs},"Config watcher started"))}async stop(){this.debounce&&(clearTimeout(this.debounce),this.debounce=void 0),this.watcher&&(await this.watcher.close(),this.watcher=void 0)}}const Ne={loadConfig:X,createLogger:de,createEmbeddingProvider:ne,createVectorStoreClient:(e,t,i)=>new De(e,t,i),compileRules:ke,createDocumentProcessor:(e,t,i,r,o)=>new Ee(e,t,i,r,o),createEventQueue:e=>new Ce(e),createFileSystemWatcher:(e,t,i,r,o)=>new Ie(e,t,i,r,o),createApiServer:W};class Oe{config;configPath;factories;runtimeOptions;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,i={},r={}){this.config=e,this.configPath=t,this.factories={...Ne,...i},this.runtimeOptions=r}async start(){const e=this.factories.createLogger(this.config.logging);let t;this.logger=e;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({err:P(t)},"Failed to create embedding provider"),t}const i=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);await i.ensureCollection();const r=this.factories.compileRules(this.config.inferenceRules??[]),o={metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps},n=this.factories.createDocumentProcessor(o,t,i,r,e);this.processor=n;const s=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute});this.queue=s;const a=this.config.watch.respectGitignore??!0?new he(this.config.watch.paths):void 0,c=this.factories.createFileSystemWatcher(this.config.watch,s,n,e,{maxRetries:this.config.maxRetries,maxBackoffMs:this.config.maxBackoffMs,onFatalError:this.runtimeOptions.onFatalError,gitignoreFilter:a});this.watcher=c;const l=this.factories.createApiServer({processor:n,vectorStore:i,embeddingProvider:t,queue:s,config:this.config,logger:e});this.server=l,await l.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),c.start(),this.startConfigWatch(),e.info("jeeves-watcher started")}async stop(){if(await this.stopConfigWatch(),this.watcher&&await this.watcher.stop(),this.queue){const e=this.config.shutdownTimeoutMs??1e4;await Promise.race([this.queue.drain().then((()=>!0)),new Promise((t=>{setTimeout((()=>{t(!1)}),e)}))])||this.logger?.warn({timeoutMs:e},"Queue drain timeout hit, forcing shutdown")}this.server&&await this.server.close(),this.logger?.info("jeeves-watcher stopped")}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;if(!t)return;if(!this.configPath)return void e.debug("Config watch enabled, but no config path was provided");const i=this.config.configWatch?.debounceMs??1e4;this.configWatcher=new We({configPath:this.configPath,enabled:t,debounceMs:i,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()}async stopConfigWatch(){this.configWatcher&&(await this.configWatcher.stop(),this.configWatcher=void 0)}async reloadConfig(){const e=this.logger,t=this.processor;if(e&&t&&this.configPath){e.info({configPath:this.configPath},"Config change detected, reloading...");try{const i=await this.factories.loadConfig(this.configPath);this.config=i;const r=this.factories.compileRules(i.inferenceRules??[]);t.updateRules(r),e.info({configPath:this.configPath,rules:r.length},"Config reloaded")}catch(t){e.error({err:P(t)},"Failed to reload config")}}}}e.DocumentProcessor=Ee,e.EventQueue=Ce,e.FileSystemWatcher=Ie,e.GitignoreFilter=he,e.JeevesWatcher=Oe,e.SystemHealth=Te,e.VectorStoreClient=De,e.apiConfigSchema=K,e.applyRules=Fe,e.buildAttributes=Pe,e.compileRules=ke,e.configWatchConfigSchema=$,e.contentHash=ue,e.createApiServer=W,e.createEmbeddingProvider=ne,e.createLogger=de,e.deleteMetadata=T,e.embeddingConfigSchema=G,e.extractText=Me,e.inferenceRuleSchema=V,e.jeevesWatcherConfigSchema=U,e.loadConfig=X,e.loggingConfigSchema=J,e.metadataPath=E,e.pointId=fe,e.readMetadata=C,e.startFromConfig=async function(e){const t=await X(e),i=new Oe(t,e);return function(e){const t=async()=>{await e(),process.exit(0)};process.on("SIGTERM",(()=>{t()})),process.on("SIGINT",(()=>{t()}))}((()=>i.stop())),await i.start(),i},e.vectorStoreConfigSchema=B,e.watchConfigSchema=Q,e.writeMetadata=D}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,cosmiconfig,zod,jsonmap,googleGenai,node_fs,ignore,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest,chokidar);
|