@karmaniverous/jeeves-watcher 0.4.0 → 0.4.2

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.
@@ -122,7 +122,7 @@
122
122
  ]
123
123
  },
124
124
  "maps": {
125
- "description": "Reusable named JsonMap transformations.",
125
+ "description": "Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).",
126
126
  "allOf": [
127
127
  {
128
128
  "$ref": "#/definitions/__schema51"
@@ -597,7 +597,14 @@
597
597
  "type": "string"
598
598
  },
599
599
  "additionalProperties": {
600
- "$ref": "#/definitions/__schema48"
600
+ "anyOf": [
601
+ {
602
+ "$ref": "#/definitions/__schema48"
603
+ },
604
+ {
605
+ "type": "string"
606
+ }
607
+ ]
601
608
  }
602
609
  },
603
610
  "__schema52": {
package/dist/cjs/index.js CHANGED
@@ -1164,11 +1164,11 @@ const jeevesWatcherConfigSchema = zod.z.object({
1164
1164
  .array(inferenceRuleSchema)
1165
1165
  .optional()
1166
1166
  .describe('Rules for inferring metadata from file attributes.'),
1167
- /** Reusable named JsonMap transformations. */
1167
+ /** Reusable named JsonMap transformations (inline objects or .json file paths). */
1168
1168
  maps: zod.z
1169
- .record(zod.z.string(), jsonmap.jsonMapMapSchema)
1169
+ .record(zod.z.string(), jsonmap.jsonMapMapSchema.or(zod.z.string()))
1170
1170
  .optional()
1171
- .describe('Reusable named JsonMap transformations.'),
1171
+ .describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
1172
1172
  /** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
1173
1173
  templates: zod.z
1174
1174
  .record(zod.z.string(), zod.z.string())
@@ -1282,6 +1282,18 @@ async function loadConfig(configPath) {
1282
1282
  }
1283
1283
  try {
1284
1284
  const validated = jeevesWatcherConfigSchema.parse(result.config);
1285
+ // Resolve file-path map references relative to config directory.
1286
+ // After this block, all map values are inline JsonMapMap objects.
1287
+ if (validated.maps) {
1288
+ const configDir = node_path.dirname(result.filepath);
1289
+ for (const [name, value] of Object.entries(validated.maps)) {
1290
+ if (typeof value === 'string') {
1291
+ const mapPath = node_path.resolve(configDir, value);
1292
+ const raw = node_fs.readFileSync(mapPath, 'utf-8');
1293
+ validated.maps[name] = JSON.parse(raw);
1294
+ }
1295
+ }
1296
+ }
1285
1297
  const withDefaults = applyDefaults(validated);
1286
1298
  return substituteEnvVars(withDefaults);
1287
1299
  }
@@ -2767,6 +2779,34 @@ function resolveWatchPaths(globs) {
2767
2779
  const matches = buildGlobMatcher(globs);
2768
2780
  return { roots, matches };
2769
2781
  }
2782
+ /**
2783
+ * Convert ignored glob patterns to picomatch matcher functions.
2784
+ *
2785
+ * Chokidar v5 replaced the external `anymatch` dependency with an inline
2786
+ * implementation that does **exact string equality** for string matchers,
2787
+ * breaking glob-based `ignored` patterns. This function converts glob strings
2788
+ * to picomatch functions that chokidar's `createPattern` passes through
2789
+ * unchanged (`typeof matcher === 'function'`).
2790
+ *
2791
+ * Non-string entries (functions, RegExps) are passed through as-is.
2792
+ *
2793
+ * @param ignored - Array of ignored patterns (globs, functions, RegExps).
2794
+ * @returns Array with glob strings replaced by picomatch matcher functions.
2795
+ */
2796
+ function resolveIgnored(ignored) {
2797
+ return ignored.map((entry) => {
2798
+ if (typeof entry !== 'string')
2799
+ return entry;
2800
+ // If the string contains glob characters, convert to a picomatch function.
2801
+ // Literal strings (exact paths) are also converted for consistent matching.
2802
+ const normalizedPattern = entry.replace(/\\/g, '/');
2803
+ const matcher = picomatch(normalizedPattern, { dot: true, nocase: true });
2804
+ return (filePath) => {
2805
+ const normalized = filePath.replace(/\\/g, '/');
2806
+ return matcher(normalized);
2807
+ };
2808
+ });
2809
+ }
2770
2810
 
2771
2811
  /**
2772
2812
  * @module watcher
@@ -2818,8 +2858,14 @@ class FileSystemWatcher {
2818
2858
  // filter emitted events against the original globs via picomatch.
2819
2859
  const { roots, matches } = resolveWatchPaths(this.config.paths);
2820
2860
  this.globMatches = matches;
2861
+ // Chokidar v5's inline anymatch does exact string equality for string
2862
+ // matchers, breaking glob-based ignored patterns. Convert to picomatch
2863
+ // functions that chokidar passes through as-is.
2864
+ const ignored = this.config.ignored
2865
+ ? resolveIgnored(this.config.ignored)
2866
+ : undefined;
2821
2867
  this.watcher = chokidar.watch(roots, {
2822
- ignored: this.config.ignored,
2868
+ ignored,
2823
2869
  usePolling: this.config.usePolling,
2824
2870
  interval: this.config.pollIntervalMs,
2825
2871
  awaitWriteFinish: this.config.stabilityThresholdMs
@@ -1166,11 +1166,11 @@ const jeevesWatcherConfigSchema = z.object({
1166
1166
  .array(inferenceRuleSchema)
1167
1167
  .optional()
1168
1168
  .describe('Rules for inferring metadata from file attributes.'),
1169
- /** Reusable named JsonMap transformations. */
1169
+ /** Reusable named JsonMap transformations (inline objects or .json file paths). */
1170
1170
  maps: z
1171
- .record(z.string(), jsonMapMapSchema)
1171
+ .record(z.string(), jsonMapMapSchema.or(z.string()))
1172
1172
  .optional()
1173
- .describe('Reusable named JsonMap transformations.'),
1173
+ .describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
1174
1174
  /** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
1175
1175
  templates: z
1176
1176
  .record(z.string(), z.string())
@@ -1284,6 +1284,18 @@ async function loadConfig(configPath) {
1284
1284
  }
1285
1285
  try {
1286
1286
  const validated = jeevesWatcherConfigSchema.parse(result.config);
1287
+ // Resolve file-path map references relative to config directory.
1288
+ // After this block, all map values are inline JsonMapMap objects.
1289
+ if (validated.maps) {
1290
+ const configDir = dirname(result.filepath);
1291
+ for (const [name, value] of Object.entries(validated.maps)) {
1292
+ if (typeof value === 'string') {
1293
+ const mapPath = resolve(configDir, value);
1294
+ const raw = readFileSync(mapPath, 'utf-8');
1295
+ validated.maps[name] = JSON.parse(raw);
1296
+ }
1297
+ }
1298
+ }
1287
1299
  const withDefaults = applyDefaults(validated);
1288
1300
  return substituteEnvVars(withDefaults);
1289
1301
  }
@@ -2769,6 +2781,34 @@ function resolveWatchPaths(globs) {
2769
2781
  const matches = buildGlobMatcher(globs);
2770
2782
  return { roots, matches };
2771
2783
  }
2784
+ /**
2785
+ * Convert ignored glob patterns to picomatch matcher functions.
2786
+ *
2787
+ * Chokidar v5 replaced the external `anymatch` dependency with an inline
2788
+ * implementation that does **exact string equality** for string matchers,
2789
+ * breaking glob-based `ignored` patterns. This function converts glob strings
2790
+ * to picomatch functions that chokidar's `createPattern` passes through
2791
+ * unchanged (`typeof matcher === 'function'`).
2792
+ *
2793
+ * Non-string entries (functions, RegExps) are passed through as-is.
2794
+ *
2795
+ * @param ignored - Array of ignored patterns (globs, functions, RegExps).
2796
+ * @returns Array with glob strings replaced by picomatch matcher functions.
2797
+ */
2798
+ function resolveIgnored(ignored) {
2799
+ return ignored.map((entry) => {
2800
+ if (typeof entry !== 'string')
2801
+ return entry;
2802
+ // If the string contains glob characters, convert to a picomatch function.
2803
+ // Literal strings (exact paths) are also converted for consistent matching.
2804
+ const normalizedPattern = entry.replace(/\\/g, '/');
2805
+ const matcher = picomatch(normalizedPattern, { dot: true, nocase: true });
2806
+ return (filePath) => {
2807
+ const normalized = filePath.replace(/\\/g, '/');
2808
+ return matcher(normalized);
2809
+ };
2810
+ });
2811
+ }
2772
2812
 
2773
2813
  /**
2774
2814
  * @module watcher
@@ -2820,8 +2860,14 @@ class FileSystemWatcher {
2820
2860
  // filter emitted events against the original globs via picomatch.
2821
2861
  const { roots, matches } = resolveWatchPaths(this.config.paths);
2822
2862
  this.globMatches = matches;
2863
+ // Chokidar v5's inline anymatch does exact string equality for string
2864
+ // matchers, breaking glob-based ignored patterns. Convert to picomatch
2865
+ // functions that chokidar passes through as-is.
2866
+ const ignored = this.config.ignored
2867
+ ? resolveIgnored(this.config.ignored)
2868
+ : undefined;
2823
2869
  this.watcher = chokidar.watch(roots, {
2824
- ignored: this.config.ignored,
2870
+ ignored,
2825
2871
  usePolling: this.config.usePolling,
2826
2872
  interval: this.config.pollIntervalMs,
2827
2873
  awaitWriteFinish: this.config.stabilityThresholdMs
package/dist/index.d.ts CHANGED
@@ -127,7 +127,7 @@ declare const jeevesWatcherConfigSchema: z.ZodObject<{
127
127
  map: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<_karmaniverous_jsonmap.JsonMapMap, unknown, z.core.$ZodTypeInternals<_karmaniverous_jsonmap.JsonMapMap, unknown>>, z.ZodString]>>;
128
128
  template: z.ZodOptional<z.ZodString>;
129
129
  }, z.core.$strip>>>;
130
- maps: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<_karmaniverous_jsonmap.JsonMapMap, unknown, z.core.$ZodTypeInternals<_karmaniverous_jsonmap.JsonMapMap, unknown>>>>;
130
+ maps: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodType<_karmaniverous_jsonmap.JsonMapMap, unknown, z.core.$ZodTypeInternals<_karmaniverous_jsonmap.JsonMapMap, unknown>>, z.ZodString]>>>;
131
131
  templates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
132
132
  templateHelpers: z.ZodOptional<z.ZodObject<{
133
133
  paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1135,11 +1135,11 @@
1135
1135
  .array(inferenceRuleSchema)
1136
1136
  .optional()
1137
1137
  .describe('Rules for inferring metadata from file attributes.'),
1138
- /** Reusable named JsonMap transformations. */
1138
+ /** Reusable named JsonMap transformations (inline objects or .json file paths). */
1139
1139
  maps: zod.z
1140
- .record(zod.z.string(), jsonmap.jsonMapMapSchema)
1140
+ .record(zod.z.string(), jsonmap.jsonMapMapSchema.or(zod.z.string()))
1141
1141
  .optional()
1142
- .describe('Reusable named JsonMap transformations.'),
1142
+ .describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
1143
1143
  /** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
1144
1144
  templates: zod.z
1145
1145
  .record(zod.z.string(), zod.z.string())
@@ -1253,6 +1253,18 @@
1253
1253
  }
1254
1254
  try {
1255
1255
  const validated = jeevesWatcherConfigSchema.parse(result.config);
1256
+ // Resolve file-path map references relative to config directory.
1257
+ // After this block, all map values are inline JsonMapMap objects.
1258
+ if (validated.maps) {
1259
+ const configDir = node_path.dirname(result.filepath);
1260
+ for (const [name, value] of Object.entries(validated.maps)) {
1261
+ if (typeof value === 'string') {
1262
+ const mapPath = node_path.resolve(configDir, value);
1263
+ const raw = node_fs.readFileSync(mapPath, 'utf-8');
1264
+ validated.maps[name] = JSON.parse(raw);
1265
+ }
1266
+ }
1267
+ }
1256
1268
  const withDefaults = applyDefaults(validated);
1257
1269
  return substituteEnvVars(withDefaults);
1258
1270
  }
@@ -2738,6 +2750,34 @@
2738
2750
  const matches = buildGlobMatcher(globs);
2739
2751
  return { roots, matches };
2740
2752
  }
2753
+ /**
2754
+ * Convert ignored glob patterns to picomatch matcher functions.
2755
+ *
2756
+ * Chokidar v5 replaced the external `anymatch` dependency with an inline
2757
+ * implementation that does **exact string equality** for string matchers,
2758
+ * breaking glob-based `ignored` patterns. This function converts glob strings
2759
+ * to picomatch functions that chokidar's `createPattern` passes through
2760
+ * unchanged (`typeof matcher === 'function'`).
2761
+ *
2762
+ * Non-string entries (functions, RegExps) are passed through as-is.
2763
+ *
2764
+ * @param ignored - Array of ignored patterns (globs, functions, RegExps).
2765
+ * @returns Array with glob strings replaced by picomatch matcher functions.
2766
+ */
2767
+ function resolveIgnored(ignored) {
2768
+ return ignored.map((entry) => {
2769
+ if (typeof entry !== 'string')
2770
+ return entry;
2771
+ // If the string contains glob characters, convert to a picomatch function.
2772
+ // Literal strings (exact paths) are also converted for consistent matching.
2773
+ const normalizedPattern = entry.replace(/\\/g, '/');
2774
+ const matcher = picomatch(normalizedPattern, { dot: true, nocase: true });
2775
+ return (filePath) => {
2776
+ const normalized = filePath.replace(/\\/g, '/');
2777
+ return matcher(normalized);
2778
+ };
2779
+ });
2780
+ }
2741
2781
 
2742
2782
  /**
2743
2783
  * @module watcher
@@ -2789,8 +2829,14 @@
2789
2829
  // filter emitted events against the original globs via picomatch.
2790
2830
  const { roots, matches } = resolveWatchPaths(this.config.paths);
2791
2831
  this.globMatches = matches;
2832
+ // Chokidar v5's inline anymatch does exact string equality for string
2833
+ // matchers, breaking glob-based ignored patterns. Convert to picomatch
2834
+ // functions that chokidar passes through as-is.
2835
+ const ignored = this.config.ignored
2836
+ ? resolveIgnored(this.config.ignored)
2837
+ : undefined;
2792
2838
  this.watcher = chokidar.watch(roots, {
2793
- ignored: this.config.ignored,
2839
+ ignored,
2794
2840
  usePolling: this.config.usePolling,
2795
2841
  interval: this.config.pollIntervalMs,
2796
2842
  awaitWriteFinish: this.config.stabilityThresholdMs
@@ -1 +1 @@
1
- !function(e,t,r,i,n,o,s,a,c,l,h,d,u,g,f,p,m,y,w,b,v,M,S,F,x,k,P,z,j,C){"use strict";function E(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 R=E(F);function T(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 D(e){const t=e.replace(/\\/g,"/"),r=t.search(/[*?\[]/);if(-1===r)return i.resolve(e);const n=t.slice(0,r),o=n.endsWith("/")?n.slice(0,-1):i.dirname(n);return i.resolve(o)}async function*I(e){let t;try{t=(await r.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const n of t){const t=i.resolve(e,n.name);if(n.isDirectory)yield*I(t);else try{(await r.stat(t)).isFile()&&(yield t)}catch{}}}async function A(e,t,r,i){const o=await async function(e,t=[]){const r=e.map((e=>e.replace(/\\/g,"/"))),i=t.map((e=>e.replace(/\\/g,"/"))),o=n(r,{dot:!0}),s=i.length?n(i,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(D))),c=new Set;for(const e of a)for await(const t of I(e)){const e=t.replace(/\\/g,"/");s(e)||o(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of o)await r[i](e);return o.length}function W(e,t=!1){let r=e.replace(/\\/g,"/").toLowerCase();return t&&(r=r.replace(/^([a-z]):/,((e,t)=>t))),r}function N(e,t){const r=W(e,!0),n=s.createHash("sha256").update(r,"utf8").digest("hex");return i.join(t,`${n}.meta.json`)}async function _(e,t){try{const i=await r.readFile(N(e,t),"utf8");return JSON.parse(i)}catch{return null}}async function H(e,t,n){const o=N(e,t);await r.mkdir(i.dirname(o),{recursive:!0}),await r.writeFile(o,JSON.stringify(n,null,2),"utf8")}async function O(e,t){try{await r.rm(N(e,t))}catch{}}const $=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function q(e){const{processor:r,vectorStore:i,embeddingProvider:n,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(l={vectorStore:i,config:a},async()=>{const e=await l.vectorStore.getCollectionInfo();return{status:"ok",uptime:process.uptime(),collection:{name:l.config.vectorStore.collectionName,pointCount:e.pointCount,dimensions:e.dimensions},payloadFields:e.payloadFields}})),c.post("/metadata",function(e){return async(t,r)=>{try{const{path:r,metadata:i}=t.body;return await e.processor.processMetadataUpdate(r,i),{ok:!0}}catch(t){return e.logger.error({err:T(t)},"Metadata update failed"),r.status(500).send({error:"Internal server error"})}}}({processor:r,logger:s})),c.post("/search",function(e){return async(t,r)=>{try{const{query:r,limit:i=10,filter:n}=t.body,o=await e.embeddingProvider.embed([r]);return await e.vectorStore.search(o[0],i,n)}catch(t){return e.logger.error({err:T(t)},"Search failed"),r.status(500).send({error:"Internal server error"})}}}({embeddingProvider:n,vectorStore:i,logger:s})),c.post("/reindex",function(e){return async(t,r)=>{try{const t=await A(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:T(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=[...$];for await(const r of e.vectorStore.scroll()){const e=r.payload,n=e.file_path;if("string"!=typeof n||0===n.length)continue;const s=o.omit(e,i);await H(n,t,s)}return await r.status(200).send({ok:!0})}catch(t){return e.logger.error({err:T(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 A(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 A(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:T(t),scope:i},"Config reindex failed")}})(),await r.status(200).send({status:"started",scope:i})}catch(t){return e.logger.error({err:T(t)},"Config reindex request failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c}function L(e){let t=i.resolve(e);const r=i.resolve("/");for(;t!==r;){if(a.existsSync(i.join(t,".git"))&&a.statSync(i.join(t,".git")).isDirectory())return t;const e=i.dirname(t);if(e===t)break;t=e}}function Q(e){const t=i.resolve(e);try{return a.statSync(t).isDirectory()?t:i.dirname(t)}catch{}const r=/[*?[{]/.exec(e);if(!r)return;const n=e.slice(0,r.index).trim(),o=0===n.length?".":n.endsWith("/")||n.endsWith("\\")?n:i.dirname(n),s=i.resolve(o);return a.existsSync(s)?s:void 0}function G(e){const t=[],r=i.join(e,".gitignore");let n;a.existsSync(r)&&t.push(r);try{n=a.readdirSync(e)}catch{return t}for(const r of n){if(".git"===r||"node_modules"===r)continue;const n=i.join(e,r);try{a.statSync(n).isDirectory()&&t.push(...G(n))}catch{}}return t}function B(e){const t=a.readFileSync(e,"utf8");return c().add(t)}class J{repos=new Map;constructor(e){this.scan(e)}scan(e){this.repos.clear();const t=new Set;for(const r of e){const e=Q(r);if(!e)continue;if(t.has(e))continue;t.add(e);const n=L(e);if(!n)continue;if(this.repos.has(n))continue;const o=G(n).map((e=>({dir:i.dirname(e),ig:B(e)})));o.sort(((e,t)=>t.dir.length-e.dir.length)),this.repos.set(n,{root:n,entries:o})}}isIgnored(e){const t=i.resolve(e);for(const[,e]of this.repos){const r=i.relative(e.root,t);if(!(r.startsWith("..")||r.startsWith(i.resolve("/"))||/^[a-zA-Z]:/.test(r)))for(const r of e.entries){const e=i.relative(r.dir,t);if(e.startsWith("..")||/^[a-zA-Z]:/.test(e))continue;const n=e.replace(/\\/g,"/");if(r.ig.ignores(n))return!0}}return!1}invalidate(e){const t=i.resolve(e),r=i.dirname(t);for(const[,e]of this.repos){if(!i.relative(e.root,r).startsWith(".."))return e.entries=e.entries.filter((e=>e.dir!==r)),void(a.existsSync(t)&&(e.entries.push({dir:r,ig:B(t)}),e.entries.sort(((e,t)=>t.dir.length-e.dir.length))))}const n=L(r);if(n&&a.existsSync(t)){const e=[{dir:r,ig:B(t)}];if(this.repos.has(n)){const t=this.repos.get(n);t.entries.push(e[0]),t.entries.sort(((e,t)=>t.dir.length-e.dir.length))}else this.repos.set(n,{root:n,entries:e})}}}const K=p.unified().use(f,{fragment:!0});function U(e){e.registerHelper("adfToMarkdown",(function(t){if(!t||"object"!=typeof t)return"";try{const r=u.fromADF(t);return new e.SafeString(g.toMarkdown(r).trim())}catch{return"\x3c!-- ADF conversion failed --\x3e"}})),e.registerHelper("markdownify",(function(t){if("string"!=typeof t||!t.trim())return"";try{const r=K.parse(t),i=d.toMdast(r);return new e.SafeString(g.toMarkdown(i).trim())}catch{return"\x3c!-- HTML conversion failed --\x3e"}})),e.registerHelper("dateFormat",(function(e,t){if(null==e)return"";const r="string"==typeof t?t:"YYYY-MM-DD";return h(e).format(r)})),e.registerHelper("join",(function(e,t){if(!Array.isArray(e))return"";const r="string"==typeof t?t:", ";return e.join(r)})),e.registerHelper("pluck",(function(e,t){return Array.isArray(e)&&"string"==typeof t?e.map((e=>e&&"object"==typeof e?e[t]:void 0)):[]})),e.registerHelper("lowercase",(e=>"string"==typeof e?e.toLowerCase():"")),e.registerHelper("uppercase",(e=>"string"==typeof e?e.toUpperCase():"")),e.registerHelper("capitalize",(e=>"string"==typeof e?o.capitalize(e):"")),e.registerHelper("title",(e=>"string"==typeof e?o.title(e):"")),e.registerHelper("camel",(e=>"string"==typeof e?o.camel(e):"")),e.registerHelper("snake",(e=>"string"==typeof e?o.snake(e):"")),e.registerHelper("dash",(e=>"string"==typeof e?o.dash(e):"")),e.registerHelper("default",(function(e,t){return e??t??""})),e.registerHelper("eq",(function(e,t){return o.isEqual(e,t)})),e.registerHelper("json",(function(t){return new e.SafeString(JSON.stringify(t,null,2))}))}function V(e,t,r,n=new Set){if(e.endsWith(".hbs")||e.endsWith(".handlebars"))return a.readFileSync(i.resolve(r,e),"utf-8");if(void 0!==t?.[e]){if(n.has(e))throw new Error(`Circular template reference detected: ${e}`);return n.add(e),V(t[e],t,r,n)}return e}function Y(){const e=l.create();return U(e),e}async function Z(e,t,r){for(const n of t){const t=i.resolve(r,n),o=await import(t);"function"==typeof o.default&&o.default(e)}}class X{hbs;compiled=new Map;constructor(e){this.hbs=e}compile(e,t){const r=this.hbs.compile(t);return this.compiled.set(e,r),r}get(e){return this.compiled.get(e)}render(e,t){const r=this.compiled.get(e);return r?r(t):null}}async function ee(e,t,r,i){if(0===e.filter((e=>e.template)).length)return;const n=Y();r?.length&&i&&await Z(n,r,i);const o=new X(n);for(const[r,n]of e.entries()){if(!n.template)continue;const e=V(n.template,t,i??".");o.compile(`rule-${String(r)}`,e)}return o}class te{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=m.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:T(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={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},ie={enabled:!0,debounceMs:1e3},ne={host:"127.0.0.1",port:3456},oe={level:"info"},se={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3,respectGitignore:!0},ae={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},ce=w.z.object({paths:w.z.array(w.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:w.z.array(w.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:w.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:w.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:w.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing."),respectGitignore:w.z.boolean().optional().describe("Skip files ignored by .gitignore in git repositories. Only applies to repos with a .git directory. Default: true.")}),le=w.z.object({enabled:w.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),he=w.z.object({provider:w.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:w.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:w.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:w.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:w.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:w.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:w.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:w.z.number().optional().describe("Maximum concurrent embedding requests.")}),de=w.z.object({url:w.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:w.z.string().describe("Qdrant collection name for vector storage."),apiKey:w.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),ue=w.z.object({host:w.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:w.z.number().optional().describe("Port for API server (e.g., 3456).")}),ge=w.z.object({level:w.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:w.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),fe=w.z.object({match:w.z.record(w.z.string(),w.z.unknown()).describe("JSON Schema object to match against file attributes."),set:w.z.record(w.z.string(),w.z.unknown()).describe("Metadata fields to set when match succeeds."),map:w.z.union([b.jsonMapMapSchema,w.z.string()]).optional().describe("JsonMap transformation (inline definition, named map reference, or .json file path)."),template:w.z.string().optional().describe("Handlebars content template (inline string, named ref, or .hbs/.handlebars file path).")}),pe=w.z.object({watch:ce.describe("File system watch configuration."),configWatch:le.optional().describe("Configuration file watch settings."),embedding:he.describe("Embedding model configuration."),vectorStore:de.describe("Qdrant vector store configuration."),metadataDir:w.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:ue.optional().describe("API server configuration."),extractors:w.z.record(w.z.string(),w.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:w.z.array(fe).optional().describe("Rules for inferring metadata from file attributes."),maps:w.z.record(w.z.string(),b.jsonMapMapSchema).optional().describe("Reusable named JsonMap transformations."),templates:w.z.record(w.z.string(),w.z.string()).optional().describe("Named reusable Handlebars templates (inline strings or .hbs/.handlebars file paths)."),templateHelpers:w.z.object({paths:w.z.array(w.z.string()).optional().describe("File paths to custom helper modules.")}).optional().describe("Custom Handlebars helper registration."),logging:ge.optional().describe("Logging configuration."),shutdownTimeoutMs:w.z.number().optional().describe("Timeout in milliseconds for graceful shutdown."),maxRetries:w.z.number().optional().describe("Maximum consecutive system-level failures before triggering fatal error. Default: Infinity."),maxBackoffMs:w.z.number().optional().describe("Maximum backoff delay in milliseconds for system errors. Default: 60000.")}),me=/\$\{([^}]+)\}/g;function ye(e){if("string"==typeof e)return function(e){return e.replace(me,((e,t)=>{const r=process.env[t];return void 0===r?e:r}))}(e);if(Array.isArray(e))return e.map((e=>ye(e)));if(null!==e&&"object"==typeof e){const t={};for(const[r,i]of Object.entries(e))t[r]=ye(i);return t}return e}const we="jeeves-watcher";async function be(e){const t=y.cosmiconfig(we),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=pe.parse(r.config);return ye((i=e,{...re,...i,watch:{...se,...i.watch},configWatch:{...ie,...i.configWatch},embedding:{...ae,...i.embedding},api:{...ne,...i.api},logging:{...oe,...i.logging}}))}catch(e){if(e instanceof w.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 ve(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function Me(e,t){return e<=0?Promise.resolve():new Promise(((r,i)=>{const n=setTimeout((()=>{s(),r()}),e),o=()=>{s(),i(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(n),t&&t.removeEventListener("abort",o)};if(t){if(t.aborted)return void o();t.addEventListener("abort",o,{once:!0})}}))}function Se(e,t,r,i=0){const n=Math.max(0,e-1),o=Math.min(r,t*2**n),s=i>0?1+Math.random()*i:1;return Math.round(o*s)}async function Fe(e,t){const r=Math.max(1,t.attempts);let i;for(let n=1;n<=r;n++)try{return await e(n)}catch(e){i=e;if(n>=r)break;const o=Se(n,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:n,attempts:r,delayMs:o,error:e}),await Me(o,t.signal)}throw i}const xe=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=ve(t),n=new v.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:r,async embed(t){const o=await Fe((async r=>(r>1&&i.warn({attempt:r,provider:"gemini",model:e.model},"Retrying embedding request"),n.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:r,error:n})=>{i.warn({attempt:t,delayMs:r,provider:"gemini",model:e.model,err:T(n)},"Embedding call failed; will retry")}});for(const e of o)if(e.length!==r)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(r)}, got ${String(e.length)}`);return o}}}]]);function ke(e,t){const r=xe.get(e.provider);if(!r)throw new Error(`Unsupported embedding provider: ${e.provider}`);return r(e,t)}function Pe(e){const t=e?.level??"info";if(e?.file){const r=M.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return M({level:t},r)}return M({level:t})}function ze(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const je="6a6f686e-6761-4c74-ad6a-656576657321";function Ce(e,t){const r=void 0!==t?`${W(e)}#${String(t)}`:W(e);return S.v5(r,je)}const Ee=["content","body","text","snippet","subject","description","summary","transcript"];function Re(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of Ee){const r=t[e];if("string"==typeof r&&r.trim())return r}return JSON.stringify(e)}async function Te(e){const t=await r.readFile(e,"utf8"),{frontmatter:i,body:n}=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,n]=r,o=x.load(i);return{frontmatter:o&&"object"==typeof o&&!Array.isArray(o)?o:void 0,body:n}}(t);return{text:n,frontmatter:i}}async function De(e){return{text:(await r.readFile(e,"utf8")).replace(/^\uFEFF/,"")}}async function Ie(e){const t=await r.readFile(e,"utf8"),i=R.load(t.replace(/^\uFEFF/,""));i("script, style").remove();return{text:i("body").text().trim()||i.text().trim()}}const Ae=new Map([[".md",Te],[".markdown",Te],[".txt",De],[".text",De],[".json",async function(e){const t=await r.readFile(e,"utf8"),i=JSON.parse(t.replace(/^\uFEFF/,"")),n=i&&"object"==typeof i&&!Array.isArray(i)?i:void 0;return{text:Re(i),json:n}}],[".pdf",async function(e){const t=await r.readFile(e),i=new Uint8Array(t),{extractText:n}=await import("unpdf"),{text:o}=await n(i);return{text:Array.isArray(o)?o.join("\n\n"):o}}],[".docx",async function(e){const t=await r.readFile(e);return{text:(await k.extractRawText({buffer:t})).value}}],[".html",Ie],[".htm",Ie]]);async function We(e,t){const r=Ae.get(t.toLowerCase());return r?r(e):De(e)}function Ne(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,r)=>{const i=o.get(t,r);return null==i?"":"string"==typeof i?i:JSON.stringify(i)}))}function _e(e,t){const r={};for(const[i,n]of Object.entries(e))r[i]=Ne(n,t);return r}async function He(e,t,r,n,s,c){const l={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)=>o.get(e,t)};let h={},d=null;const u=n??console;for(const[n,{rule:o,validate:g}]of e.entries())if(g(t)){const e=_e(o.set,t);if(h={...h,...e},o.map){let e;if("string"==typeof o.map){if(o.map.endsWith(".json")&&c)try{const t=i.resolve(c,o.map),r=a.readFileSync(t,"utf-8");e=JSON.parse(r)}catch(e){u.warn(`Failed to load map file "${o.map}": ${e instanceof Error?e.message:String(e)}`);continue}else if(e=r?.[o.map],!e){u.warn(`Map reference "${o.map}" not found in named maps. Skipping map transformation.`);continue}}else e=o.map;try{const r=new b.JsonMap(e,l),i=await r.transform(t);i&&"object"==typeof i&&!Array.isArray(i)?h={...h,...i}:u.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){u.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}if(o.template&&s){const e=`rule-${String(n)}`,r={...t.json??{},...t,...h};try{const t=s.render(e,r);t&&t.trim()?d=t:u.warn(`Template for rule ${String(n)} rendered empty output. Falling back to raw content.`)}catch(e){u.warn(`Template render failed for rule ${String(n)}: ${e instanceof Error?e.message:String(e)}. Falling back to raw content.`)}}}return{metadata:h,renderedContent:d}}function Oe(e,t,r,n){const o=e.replace(/\\/g,"/"),s={file:{path:o,directory:i.dirname(o).replace(/\\/g,"/"),filename:i.basename(o),extension:i.extname(o),sizeBytes:t.size,modified:t.mtime.toISOString()}};return r&&(s.frontmatter=r),n&&(s.json=n),s}function $e(e){const t=function(){const e=new P({allErrors:!0});return z(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>n.isMatch(t,e)}),e}();return e.map(((e,r)=>({rule:e,validate:t.compile({$id:`rule-${String(r)}`,...e.match})})))}async function qe(e,t,n,o,s,a,c){const l=i.extname(e),h=await r.stat(e),d=await We(e,l),u=Oe(e,h,d.frontmatter,d.json),{metadata:g,renderedContent:f}=await He(t,u,o,s,a,c),p=await _(e,n);return{inferred:g,enrichment:p,metadata:{...g,...p??{}},attributes:u,extracted:d,renderedContent:f}}function Le(e,t){const r=[];for(let i=0;i<t;i++)r.push(Ce(e,i));return r}function Qe(e,t=1){if(!e)return t;const r=e.total_chunks;return"number"==typeof r?r:t}class Ge{config;embeddingProvider;vectorStore;compiledRules;logger;templateEngine;constructor(e,t,r,i,n,o){this.config=e,this.embeddingProvider=t,this.vectorStore=r,this.compiledRules=i,this.logger=n,this.templateEngine=o}async processFile(e){try{const t=i.extname(e),{metadata:r,extracted:n,renderedContent:o}=await qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),s=o??n.text;if(!s.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const a=ze(s),c=Ce(e,0),l=await this.vectorStore.getPayload(c);if(l&&l.content_hash===a)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const h=Qe(l),d=this.config.chunkSize??1e3,u=function(e,t,r){const i=e.toLowerCase();return".md"===i||".markdown"===i?new j.MarkdownTextSplitter({chunkSize:t,chunkOverlap:r}):new j.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:r})}(t,d,this.config.chunkOverlap??200),g=await u.splitText(s),f=await this.embeddingProvider.embed(g),p=g.map(((t,i)=>({id:Ce(e,i),vector:f[i],payload:{...r,file_path:e.replace(/\\/g,"/"),chunk_index:i,total_chunks:g.length,content_hash:a,chunk_text:t}})));if(await this.vectorStore.upsert(p),h>g.length){const t=Le(e,h).slice(g.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:g.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to process file")}}async deleteFile(e){try{const t=Ce(e,0),r=await this.vectorStore.getPayload(t),i=Le(e,Qe(r));await this.vectorStore.delete(i),await O(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const r={...await _(e,this.config.metadataDir)??{},...t};await H(e,this.config.metadataDir,r);const i=Ce(e,0),n=await this.vectorStore.getPayload(i);if(!n)return null;const o=Qe(n),s=Le(e,o);return await this.vectorStore.setPayload(s,r),this.logger.info({filePath:e,chunks:o},"Metadata updated"),r}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=Ce(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 qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),n=Qe(r),o=Le(e,n);return await this.vectorStore.setPayload(o,i),this.logger.info({filePath:e,chunks:n},"Rules re-applied"),i}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to re-apply rules"),null}}updateRules(e,t){this.compiledRules=e,t&&(this.templateEngine=t),this.logger.info({rules:e.length},"Inference rules updated")}}class Be{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 n=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,n)}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()}}function Je(e){return null==e?"keyword":"number"==typeof e?Number.isInteger(e)?"integer":"float":"boolean"==typeof e?"bool":Array.isArray(e)?"keyword[]":"string"==typeof e&&e.length>256?"text":"keyword"}class Ke{client;collectionName;dims;log;constructor(e,t,r){this.client=new C.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=ve(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 Fe((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:T(r)},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await Fe((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:T(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 getCollectionInfo(){const e=await this.client.getCollection(this.collectionName),t=e.points_count??0,r=e.config.params.vectors,i=void 0!==r&&"size"in r?r.size:0,n={},o=Object.entries(e.payload_schema);if(o.length>0)for(const[e,t]of o)n[e]={type:t.data_type??"unknown"};else t>0&&await this.discoverPayloadFields(n);return{pointCount:t,dimensions:i,payloadFields:n}}async discoverPayloadFields(e,t=100){const r=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1});for(const t of r.points){const r=t.payload;if(r)for(const[t,i]of Object.entries(r))t in e||(e[t]={type:Je(i)})}}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 n=i.next_page_offset;if(null==n)break;if("string"!=typeof n&&"number"!=typeof n)break;r=n}}}class Ue{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:T(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 n=setTimeout((()=>{s(),r()}),t),o=()=>{s(),i(new Error("Backoff aborted"))},s=()=>{clearTimeout(n),e&&e.removeEventListener("abort",o)};if(e){if(e.aborted)return void o();e.addEventListener("abort",o,{once:!0})}})))}get failures(){return this.consecutiveFailures}}function Ve(e){const t=e.replace(/\\/g,"/").split("/"),r=[];for(const e of t){if(/[*?{[\]]/.test(e))break;r.push(e)}return r.join("/")||"."}function Ye(e){const t=function(e){const t=e.map((e=>e.replace(/\\/g,"/").toLowerCase()));return[...new Set(t)].sort().filter(((e,t,r)=>{const i=e.endsWith("/")?e:e+"/";return!r.some((t=>t!==e&&i.startsWith(t+"/")))}))}(e.map(Ve)),r=function(e){const t=e.map((e=>e.replace(/\\/g,"/"))),r=n(t,{dot:!0,nocase:!0});return e=>{const t=e.replace(/\\/g,"/");return r(t)}}(e);return{roots:t,matches:r}}class Ze{config;queue;processor;logger;health;gitignoreFilter;globMatches;watcher;constructor(e,t,r,i,n={}){this.config=e,this.queue=t,this.processor=r,this.logger=i,this.gitignoreFilter=n.gitignoreFilter,this.globMatches=()=>!0;const o={maxRetries:n.maxRetries,maxBackoffMs:n.maxBackoffMs,onFatalError:n.onFatalError,logger:i};this.health=new Ue(o)}start(){const{roots:e,matches:t}=Ye(this.config.paths);this.globMatches=t,this.watcher=m.watch(e,{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.globMatches(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.globMatches(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.globMatches(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:T(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()}}}const Xe={loadConfig:be,createLogger:Pe,createEmbeddingProvider:ke,createVectorStoreClient:(e,t,r)=>new Ke(e,t,r),compileRules:$e,createDocumentProcessor:(e,t,r,i,n,o)=>new Ge(e,t,r,i,n,o),createEventQueue:e=>new Be(e),createFileSystemWatcher:(e,t,r,i,n)=>new Ze(e,t,r,i,n),createApiServer:q};class et{config;configPath;factories;runtimeOptions;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,r={},i={}){this.config=e,this.configPath=t,this.factories={...Xe,...r},this.runtimeOptions=i}async start(){const e=this.factories.createLogger(this.config.logging);this.logger=e;const{embeddingProvider:t,vectorStore:r}=await this.initEmbeddingAndStore(e),n=this.factories.compileRules(this.config.inferenceRules??[]),o=this.configPath?i.dirname(this.configPath):".",s=await ee(this.config.inferenceRules??[],this.config.templates,this.config.templateHelpers?.paths,o),a=this.factories.createDocumentProcessor({metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps,configDir:o},t,r,n,e,s);this.processor=a,this.queue=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute}),this.watcher=this.createWatcher(this.queue,a,e),this.server=await this.startApiServer(a,r,t,e),this.watcher.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")}async initEmbeddingAndStore(e){let t;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({err:T(t)},"Failed to create embedding provider"),t}const r=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);return await r.ensureCollection(),{embeddingProvider:t,vectorStore:r}}createWatcher(e,t,r){const i=this.config.watch.respectGitignore??!0?new J(this.config.watch.paths):void 0;return this.factories.createFileSystemWatcher(this.config.watch,e,t,r,{maxRetries:this.config.maxRetries,maxBackoffMs:this.config.maxBackoffMs,onFatalError:this.runtimeOptions.onFatalError,gitignoreFilter:i})}async startApiServer(e,t,r,i){const n=this.factories.createApiServer({processor:e,vectorStore:t,embeddingProvider:r,queue:this.queue,config:this.config,logger:i});return await n.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),n}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;t&&this.configPath?(this.configWatcher=new te({configPath:this.configPath,enabled:t,debounceMs:this.config.configWatch?.debounceMs??1e4,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()):this.configPath||e.debug("Config watch enabled, but no config path was provided")}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 n=this.factories.compileRules(r.inferenceRules??[]),o=i.dirname(this.configPath),s=await ee(r.inferenceRules??[],r.templates,r.templateHelpers?.paths,o);t.updateRules(n,s),e.info({configPath:this.configPath,rules:n.length},"Config reloaded")}catch(t){e.error({err:T(t)},"Failed to reload config")}}}}e.DocumentProcessor=Ge,e.EventQueue=Be,e.FileSystemWatcher=Ze,e.GitignoreFilter=J,e.JeevesWatcher=et,e.SystemHealth=Ue,e.TemplateEngine=X,e.VectorStoreClient=Ke,e.apiConfigSchema=ue,e.applyRules=He,e.buildAttributes=Oe,e.buildTemplateEngine=ee,e.compileRules=$e,e.configWatchConfigSchema=le,e.contentHash=ze,e.createApiServer=q,e.createEmbeddingProvider=ke,e.createHandlebarsInstance=Y,e.createLogger=Pe,e.deleteMetadata=O,e.embeddingConfigSchema=he,e.extractText=We,e.inferenceRuleSchema=fe,e.jeevesWatcherConfigSchema=pe,e.loadConfig=be,e.loadCustomHelpers=Z,e.loggingConfigSchema=ge,e.metadataPath=N,e.pointId=Ce,e.readMetadata=_,e.registerBuiltinHelpers=U,e.resolveTemplateSource=V,e.startFromConfig=async function(e){const t=await be(e),r=new et(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=de,e.watchConfigSchema=ce,e.writeMetadata=H}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,node_fs,ignore,Handlebars,dayjs,hastUtilToMdast,mdastUtilFromAdf,mdastUtilToMarkdown,rehypeParse,unified,chokidar,cosmiconfig,zod,jsonmap,googleGenai,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest);
1
+ !function(e,t,r,i,n,o,s,a,c,l,h,d,u,g,f,p,m,y,w,b,v,M,S,F,x,k,P,z,j,C){"use strict";function E(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 R=E(F);function T(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 D(e){const t=e.replace(/\\/g,"/"),r=t.search(/[*?\[]/);if(-1===r)return i.resolve(e);const n=t.slice(0,r),o=n.endsWith("/")?n.slice(0,-1):i.dirname(n);return i.resolve(o)}async function*I(e){let t;try{t=(await r.readdir(e,{withFileTypes:!0})).map((e=>({name:e.name,isDirectory:e.isDirectory()})))}catch{return}for(const n of t){const t=i.resolve(e,n.name);if(n.isDirectory)yield*I(t);else try{(await r.stat(t)).isFile()&&(yield t)}catch{}}}async function A(e,t,r,i){const o=await async function(e,t=[]){const r=e.map((e=>e.replace(/\\/g,"/"))),i=t.map((e=>e.replace(/\\/g,"/"))),o=n(r,{dot:!0}),s=i.length?n(i,{dot:!0}):()=>!1,a=Array.from(new Set(e.map(D))),c=new Set;for(const e of a)for await(const t of I(e)){const e=t.replace(/\\/g,"/");s(e)||o(e)&&c.add(t)}return Array.from(c)}(e,t);for(const e of o)await r[i](e);return o.length}function W(e,t=!1){let r=e.replace(/\\/g,"/").toLowerCase();return t&&(r=r.replace(/^([a-z]):/,((e,t)=>t))),r}function N(e,t){const r=W(e,!0),n=s.createHash("sha256").update(r,"utf8").digest("hex");return i.join(t,`${n}.meta.json`)}async function O(e,t){try{const i=await r.readFile(N(e,t),"utf8");return JSON.parse(i)}catch{return null}}async function _(e,t,n){const o=N(e,t);await r.mkdir(i.dirname(o),{recursive:!0}),await r.writeFile(o,JSON.stringify(n,null,2),"utf8")}async function H(e,t){try{await r.rm(N(e,t))}catch{}}const $=["file_path","chunk_index","total_chunks","content_hash","chunk_text"];function q(e){const{processor:r,vectorStore:i,embeddingProvider:n,logger:s,config:a}=e,c=t({logger:!1});var l;return c.get("/status",(l={vectorStore:i,config:a},async()=>{const e=await l.vectorStore.getCollectionInfo();return{status:"ok",uptime:process.uptime(),collection:{name:l.config.vectorStore.collectionName,pointCount:e.pointCount,dimensions:e.dimensions},payloadFields:e.payloadFields}})),c.post("/metadata",function(e){return async(t,r)=>{try{const{path:r,metadata:i}=t.body;return await e.processor.processMetadataUpdate(r,i),{ok:!0}}catch(t){return e.logger.error({err:T(t)},"Metadata update failed"),r.status(500).send({error:"Internal server error"})}}}({processor:r,logger:s})),c.post("/search",function(e){return async(t,r)=>{try{const{query:r,limit:i=10,filter:n}=t.body,o=await e.embeddingProvider.embed([r]);return await e.vectorStore.search(o[0],i,n)}catch(t){return e.logger.error({err:T(t)},"Search failed"),r.status(500).send({error:"Internal server error"})}}}({embeddingProvider:n,vectorStore:i,logger:s})),c.post("/reindex",function(e){return async(t,r)=>{try{const t=await A(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:T(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=[...$];for await(const r of e.vectorStore.scroll()){const e=r.payload,n=e.file_path;if("string"!=typeof n||0===n.length)continue;const s=o.omit(e,i);await _(n,t,s)}return await r.status(200).send({ok:!0})}catch(t){return e.logger.error({err:T(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 A(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 A(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:T(t),scope:i},"Config reindex failed")}})(),await r.status(200).send({status:"started",scope:i})}catch(t){return e.logger.error({err:T(t)},"Config reindex request failed"),await r.status(500).send({error:"Internal server error"})}}}({config:a,processor:r,logger:s})),c}function L(e){let t=i.resolve(e);const r=i.resolve("/");for(;t!==r;){if(a.existsSync(i.join(t,".git"))&&a.statSync(i.join(t,".git")).isDirectory())return t;const e=i.dirname(t);if(e===t)break;t=e}}function Q(e){const t=i.resolve(e);try{return a.statSync(t).isDirectory()?t:i.dirname(t)}catch{}const r=/[*?[{]/.exec(e);if(!r)return;const n=e.slice(0,r.index).trim(),o=0===n.length?".":n.endsWith("/")||n.endsWith("\\")?n:i.dirname(n),s=i.resolve(o);return a.existsSync(s)?s:void 0}function G(e){const t=[],r=i.join(e,".gitignore");let n;a.existsSync(r)&&t.push(r);try{n=a.readdirSync(e)}catch{return t}for(const r of n){if(".git"===r||"node_modules"===r)continue;const n=i.join(e,r);try{a.statSync(n).isDirectory()&&t.push(...G(n))}catch{}}return t}function B(e){const t=a.readFileSync(e,"utf8");return c().add(t)}class J{repos=new Map;constructor(e){this.scan(e)}scan(e){this.repos.clear();const t=new Set;for(const r of e){const e=Q(r);if(!e)continue;if(t.has(e))continue;t.add(e);const n=L(e);if(!n)continue;if(this.repos.has(n))continue;const o=G(n).map((e=>({dir:i.dirname(e),ig:B(e)})));o.sort(((e,t)=>t.dir.length-e.dir.length)),this.repos.set(n,{root:n,entries:o})}}isIgnored(e){const t=i.resolve(e);for(const[,e]of this.repos){const r=i.relative(e.root,t);if(!(r.startsWith("..")||r.startsWith(i.resolve("/"))||/^[a-zA-Z]:/.test(r)))for(const r of e.entries){const e=i.relative(r.dir,t);if(e.startsWith("..")||/^[a-zA-Z]:/.test(e))continue;const n=e.replace(/\\/g,"/");if(r.ig.ignores(n))return!0}}return!1}invalidate(e){const t=i.resolve(e),r=i.dirname(t);for(const[,e]of this.repos){if(!i.relative(e.root,r).startsWith(".."))return e.entries=e.entries.filter((e=>e.dir!==r)),void(a.existsSync(t)&&(e.entries.push({dir:r,ig:B(t)}),e.entries.sort(((e,t)=>t.dir.length-e.dir.length))))}const n=L(r);if(n&&a.existsSync(t)){const e=[{dir:r,ig:B(t)}];if(this.repos.has(n)){const t=this.repos.get(n);t.entries.push(e[0]),t.entries.sort(((e,t)=>t.dir.length-e.dir.length))}else this.repos.set(n,{root:n,entries:e})}}}const K=p.unified().use(f,{fragment:!0});function U(e){e.registerHelper("adfToMarkdown",(function(t){if(!t||"object"!=typeof t)return"";try{const r=u.fromADF(t);return new e.SafeString(g.toMarkdown(r).trim())}catch{return"\x3c!-- ADF conversion failed --\x3e"}})),e.registerHelper("markdownify",(function(t){if("string"!=typeof t||!t.trim())return"";try{const r=K.parse(t),i=d.toMdast(r);return new e.SafeString(g.toMarkdown(i).trim())}catch{return"\x3c!-- HTML conversion failed --\x3e"}})),e.registerHelper("dateFormat",(function(e,t){if(null==e)return"";const r="string"==typeof t?t:"YYYY-MM-DD";return h(e).format(r)})),e.registerHelper("join",(function(e,t){if(!Array.isArray(e))return"";const r="string"==typeof t?t:", ";return e.join(r)})),e.registerHelper("pluck",(function(e,t){return Array.isArray(e)&&"string"==typeof t?e.map((e=>e&&"object"==typeof e?e[t]:void 0)):[]})),e.registerHelper("lowercase",(e=>"string"==typeof e?e.toLowerCase():"")),e.registerHelper("uppercase",(e=>"string"==typeof e?e.toUpperCase():"")),e.registerHelper("capitalize",(e=>"string"==typeof e?o.capitalize(e):"")),e.registerHelper("title",(e=>"string"==typeof e?o.title(e):"")),e.registerHelper("camel",(e=>"string"==typeof e?o.camel(e):"")),e.registerHelper("snake",(e=>"string"==typeof e?o.snake(e):"")),e.registerHelper("dash",(e=>"string"==typeof e?o.dash(e):"")),e.registerHelper("default",(function(e,t){return e??t??""})),e.registerHelper("eq",(function(e,t){return o.isEqual(e,t)})),e.registerHelper("json",(function(t){return new e.SafeString(JSON.stringify(t,null,2))}))}function V(e,t,r,n=new Set){if(e.endsWith(".hbs")||e.endsWith(".handlebars"))return a.readFileSync(i.resolve(r,e),"utf-8");if(void 0!==t?.[e]){if(n.has(e))throw new Error(`Circular template reference detected: ${e}`);return n.add(e),V(t[e],t,r,n)}return e}function Y(){const e=l.create();return U(e),e}async function Z(e,t,r){for(const n of t){const t=i.resolve(r,n),o=await import(t);"function"==typeof o.default&&o.default(e)}}class X{hbs;compiled=new Map;constructor(e){this.hbs=e}compile(e,t){const r=this.hbs.compile(t);return this.compiled.set(e,r),r}get(e){return this.compiled.get(e)}render(e,t){const r=this.compiled.get(e);return r?r(t):null}}async function ee(e,t,r,i){if(0===e.filter((e=>e.template)).length)return;const n=Y();r?.length&&i&&await Z(n,r,i);const o=new X(n);for(const[r,n]of e.entries()){if(!n.template)continue;const e=V(n.template,t,i??".");o.compile(`rule-${String(r)}`,e)}return o}class te{options;watcher;debounce;constructor(e){this.options=e}start(){this.options.enabled&&(this.watcher=m.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:T(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={metadataDir:".jeeves-watcher",shutdownTimeoutMs:1e4},ie={enabled:!0,debounceMs:1e3},ne={host:"127.0.0.1",port:3456},oe={level:"info"},se={debounceMs:300,stabilityThresholdMs:500,usePolling:!1,pollIntervalMs:1e3,respectGitignore:!0},ae={chunkSize:1e3,chunkOverlap:200,dimensions:3072,rateLimitPerMinute:300,concurrency:5},ce=w.z.object({paths:w.z.array(w.z.string()).min(1).describe('Glob patterns for files to watch (e.g., "**/*.md"). At least one required.'),ignored:w.z.array(w.z.string()).optional().describe('Glob patterns to exclude from watching (e.g., "**/node_modules/**").'),pollIntervalMs:w.z.number().optional().describe("Polling interval in milliseconds when usePolling is enabled."),usePolling:w.z.boolean().optional().describe("Use polling instead of native file system events (for network drives)."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for file change events."),stabilityThresholdMs:w.z.number().optional().describe("Time in milliseconds a file must remain unchanged before processing."),respectGitignore:w.z.boolean().optional().describe("Skip files ignored by .gitignore in git repositories. Only applies to repos with a .git directory. Default: true.")}),le=w.z.object({enabled:w.z.boolean().optional().describe("Enable automatic reloading when config file changes."),debounceMs:w.z.number().optional().describe("Debounce delay in milliseconds for config file change detection.")}),he=w.z.object({provider:w.z.string().default("gemini").describe('Embedding provider name (e.g., "gemini", "openai").'),model:w.z.string().default("gemini-embedding-001").describe('Embedding model identifier (e.g., "gemini-embedding-001", "text-embedding-3-small").'),chunkSize:w.z.number().optional().describe("Maximum chunk size in characters for text splitting."),chunkOverlap:w.z.number().optional().describe("Character overlap between consecutive chunks."),dimensions:w.z.number().optional().describe("Embedding vector dimensions (must match model output)."),apiKey:w.z.string().optional().describe("API key for embedding provider (supports ${ENV_VAR} substitution)."),rateLimitPerMinute:w.z.number().optional().describe("Maximum embedding API requests per minute (rate limiting)."),concurrency:w.z.number().optional().describe("Maximum concurrent embedding requests.")}),de=w.z.object({url:w.z.string().describe('Qdrant server URL (e.g., "http://localhost:6333").'),collectionName:w.z.string().describe("Qdrant collection name for vector storage."),apiKey:w.z.string().optional().describe("Qdrant API key for authentication (supports ${ENV_VAR} substitution).")}),ue=w.z.object({host:w.z.string().optional().describe('Host address for API server (e.g., "127.0.0.1", "0.0.0.0").'),port:w.z.number().optional().describe("Port for API server (e.g., 3456).")}),ge=w.z.object({level:w.z.string().optional().describe("Logging level (trace, debug, info, warn, error, fatal)."),file:w.z.string().optional().describe("Path to log file (logs to stdout if omitted).")}),fe=w.z.object({match:w.z.record(w.z.string(),w.z.unknown()).describe("JSON Schema object to match against file attributes."),set:w.z.record(w.z.string(),w.z.unknown()).describe("Metadata fields to set when match succeeds."),map:w.z.union([b.jsonMapMapSchema,w.z.string()]).optional().describe("JsonMap transformation (inline definition, named map reference, or .json file path)."),template:w.z.string().optional().describe("Handlebars content template (inline string, named ref, or .hbs/.handlebars file path).")}),pe=w.z.object({watch:ce.describe("File system watch configuration."),configWatch:le.optional().describe("Configuration file watch settings."),embedding:he.describe("Embedding model configuration."),vectorStore:de.describe("Qdrant vector store configuration."),metadataDir:w.z.string().optional().describe("Directory for persisted metadata sidecar files."),api:ue.optional().describe("API server configuration."),extractors:w.z.record(w.z.string(),w.z.unknown()).optional().describe("Extractor configurations keyed by name."),inferenceRules:w.z.array(fe).optional().describe("Rules for inferring metadata from file attributes."),maps:w.z.record(w.z.string(),b.jsonMapMapSchema.or(w.z.string())).optional().describe("Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory)."),templates:w.z.record(w.z.string(),w.z.string()).optional().describe("Named reusable Handlebars templates (inline strings or .hbs/.handlebars file paths)."),templateHelpers:w.z.object({paths:w.z.array(w.z.string()).optional().describe("File paths to custom helper modules.")}).optional().describe("Custom Handlebars helper registration."),logging:ge.optional().describe("Logging configuration."),shutdownTimeoutMs:w.z.number().optional().describe("Timeout in milliseconds for graceful shutdown."),maxRetries:w.z.number().optional().describe("Maximum consecutive system-level failures before triggering fatal error. Default: Infinity."),maxBackoffMs:w.z.number().optional().describe("Maximum backoff delay in milliseconds for system errors. Default: 60000.")}),me=/\$\{([^}]+)\}/g;function ye(e){if("string"==typeof e)return function(e){return e.replace(me,((e,t)=>{const r=process.env[t];return void 0===r?e:r}))}(e);if(Array.isArray(e))return e.map((e=>ye(e)));if(null!==e&&"object"==typeof e){const t={};for(const[r,i]of Object.entries(e))t[r]=ye(i);return t}return e}const we="jeeves-watcher";async function be(e){const t=y.cosmiconfig(we),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=pe.parse(r.config);if(e.maps){const t=i.dirname(r.filepath);for(const[r,n]of Object.entries(e.maps))if("string"==typeof n){const o=i.resolve(t,n),s=a.readFileSync(o,"utf-8");e.maps[r]=JSON.parse(s)}}return ye((n=e,{...re,...n,watch:{...se,...n.watch},configWatch:{...ie,...n.configWatch},embedding:{...ae,...n.embedding},api:{...ne,...n.api},logging:{...oe,...n.logging}}))}catch(e){if(e instanceof w.ZodError){const t=e.issues.map((e=>`${e.path.join(".")}: ${e.message}`)).join("; ");throw new Error(`Invalid jeeves-watcher configuration: ${t}`)}throw e}var n}function ve(e){return e||{warn(e,t){t?console.warn(e,t):console.warn(e)}}}function Me(e,t){return e<=0?Promise.resolve():new Promise(((r,i)=>{const n=setTimeout((()=>{s(),r()}),e),o=()=>{s(),i(new Error("Retry sleep aborted"))},s=()=>{clearTimeout(n),t&&t.removeEventListener("abort",o)};if(t){if(t.aborted)return void o();t.addEventListener("abort",o,{once:!0})}}))}function Se(e,t,r,i=0){const n=Math.max(0,e-1),o=Math.min(r,t*2**n),s=i>0?1+Math.random()*i:1;return Math.round(o*s)}async function Fe(e,t){const r=Math.max(1,t.attempts);let i;for(let n=1;n<=r;n++)try{return await e(n)}catch(e){i=e;if(n>=r)break;const o=Se(n,t.baseDelayMs,t.maxDelayMs,t.jitter);t.onRetry?.({attempt:n,attempts:r,delayMs:o,error:e}),await Me(o,t.signal)}throw i}const xe=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=ve(t),n=new v.GoogleGenerativeAIEmbeddings({apiKey:e.apiKey,model:e.model});return{dimensions:r,async embed(t){const o=await Fe((async r=>(r>1&&i.warn({attempt:r,provider:"gemini",model:e.model},"Retrying embedding request"),n.embedDocuments(t))),{attempts:5,baseDelayMs:500,maxDelayMs:1e4,jitter:.2,onRetry:({attempt:t,delayMs:r,error:n})=>{i.warn({attempt:t,delayMs:r,provider:"gemini",model:e.model,err:T(n)},"Embedding call failed; will retry")}});for(const e of o)if(e.length!==r)throw new Error(`Gemini embedding returned invalid dimensions: expected ${String(r)}, got ${String(e.length)}`);return o}}}]]);function ke(e,t){const r=xe.get(e.provider);if(!r)throw new Error(`Unsupported embedding provider: ${e.provider}`);return r(e,t)}function Pe(e){const t=e?.level??"info";if(e?.file){const r=M.transport({target:"pino/file",options:{destination:e.file,mkdir:!0}});return M({level:t},r)}return M({level:t})}function ze(e){return s.createHash("sha256").update(e,"utf8").digest("hex")}const je="6a6f686e-6761-4c74-ad6a-656576657321";function Ce(e,t){const r=void 0!==t?`${W(e)}#${String(t)}`:W(e);return S.v5(r,je)}const Ee=["content","body","text","snippet","subject","description","summary","transcript"];function Re(e){if(!e||"object"!=typeof e)return JSON.stringify(e);const t=e;for(const e of Ee){const r=t[e];if("string"==typeof r&&r.trim())return r}return JSON.stringify(e)}async function Te(e){const t=await r.readFile(e,"utf8"),{frontmatter:i,body:n}=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,n]=r,o=x.load(i);return{frontmatter:o&&"object"==typeof o&&!Array.isArray(o)?o:void 0,body:n}}(t);return{text:n,frontmatter:i}}async function De(e){return{text:(await r.readFile(e,"utf8")).replace(/^\uFEFF/,"")}}async function Ie(e){const t=await r.readFile(e,"utf8"),i=R.load(t.replace(/^\uFEFF/,""));i("script, style").remove();return{text:i("body").text().trim()||i.text().trim()}}const Ae=new Map([[".md",Te],[".markdown",Te],[".txt",De],[".text",De],[".json",async function(e){const t=await r.readFile(e,"utf8"),i=JSON.parse(t.replace(/^\uFEFF/,"")),n=i&&"object"==typeof i&&!Array.isArray(i)?i:void 0;return{text:Re(i),json:n}}],[".pdf",async function(e){const t=await r.readFile(e),i=new Uint8Array(t),{extractText:n}=await import("unpdf"),{text:o}=await n(i);return{text:Array.isArray(o)?o.join("\n\n"):o}}],[".docx",async function(e){const t=await r.readFile(e);return{text:(await k.extractRawText({buffer:t})).value}}],[".html",Ie],[".htm",Ie]]);async function We(e,t){const r=Ae.get(t.toLowerCase());return r?r(e):De(e)}function Ne(e,t){return"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,((e,r)=>{const i=o.get(t,r);return null==i?"":"string"==typeof i?i:JSON.stringify(i)}))}function Oe(e,t){const r={};for(const[i,n]of Object.entries(e))r[i]=Ne(n,t);return r}async function _e(e,t,r,n,s,c){const l={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)=>o.get(e,t)};let h={},d=null;const u=n??console;for(const[n,{rule:o,validate:g}]of e.entries())if(g(t)){const e=Oe(o.set,t);if(h={...h,...e},o.map){let e;if("string"==typeof o.map){if(o.map.endsWith(".json")&&c)try{const t=i.resolve(c,o.map),r=a.readFileSync(t,"utf-8");e=JSON.parse(r)}catch(e){u.warn(`Failed to load map file "${o.map}": ${e instanceof Error?e.message:String(e)}`);continue}else if(e=r?.[o.map],!e){u.warn(`Map reference "${o.map}" not found in named maps. Skipping map transformation.`);continue}}else e=o.map;try{const r=new b.JsonMap(e,l),i=await r.transform(t);i&&"object"==typeof i&&!Array.isArray(i)?h={...h,...i}:u.warn("JsonMap transformation did not return an object; skipping merge.")}catch(e){u.warn(`JsonMap transformation failed: ${e instanceof Error?e.message:String(e)}`)}}if(o.template&&s){const e=`rule-${String(n)}`,r={...t.json??{},...t,...h};try{const t=s.render(e,r);t&&t.trim()?d=t:u.warn(`Template for rule ${String(n)} rendered empty output. Falling back to raw content.`)}catch(e){u.warn(`Template render failed for rule ${String(n)}: ${e instanceof Error?e.message:String(e)}. Falling back to raw content.`)}}}return{metadata:h,renderedContent:d}}function He(e,t,r,n){const o=e.replace(/\\/g,"/"),s={file:{path:o,directory:i.dirname(o).replace(/\\/g,"/"),filename:i.basename(o),extension:i.extname(o),sizeBytes:t.size,modified:t.mtime.toISOString()}};return r&&(s.frontmatter=r),n&&(s.json=n),s}function $e(e){const t=function(){const e=new P({allErrors:!0});return z(e),e.addKeyword({keyword:"glob",type:"string",schemaType:"string",validate:(e,t)=>n.isMatch(t,e)}),e}();return e.map(((e,r)=>({rule:e,validate:t.compile({$id:`rule-${String(r)}`,...e.match})})))}async function qe(e,t,n,o,s,a,c){const l=i.extname(e),h=await r.stat(e),d=await We(e,l),u=He(e,h,d.frontmatter,d.json),{metadata:g,renderedContent:f}=await _e(t,u,o,s,a,c),p=await O(e,n);return{inferred:g,enrichment:p,metadata:{...g,...p??{}},attributes:u,extracted:d,renderedContent:f}}function Le(e,t){const r=[];for(let i=0;i<t;i++)r.push(Ce(e,i));return r}function Qe(e,t=1){if(!e)return t;const r=e.total_chunks;return"number"==typeof r?r:t}class Ge{config;embeddingProvider;vectorStore;compiledRules;logger;templateEngine;constructor(e,t,r,i,n,o){this.config=e,this.embeddingProvider=t,this.vectorStore=r,this.compiledRules=i,this.logger=n,this.templateEngine=o}async processFile(e){try{const t=i.extname(e),{metadata:r,extracted:n,renderedContent:o}=await qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),s=o??n.text;if(!s.trim())return void this.logger.debug({filePath:e},"Skipping empty file");const a=ze(s),c=Ce(e,0),l=await this.vectorStore.getPayload(c);if(l&&l.content_hash===a)return void this.logger.debug({filePath:e},"Content unchanged, skipping");const h=Qe(l),d=this.config.chunkSize??1e3,u=function(e,t,r){const i=e.toLowerCase();return".md"===i||".markdown"===i?new j.MarkdownTextSplitter({chunkSize:t,chunkOverlap:r}):new j.RecursiveCharacterTextSplitter({chunkSize:t,chunkOverlap:r})}(t,d,this.config.chunkOverlap??200),g=await u.splitText(s),f=await this.embeddingProvider.embed(g),p=g.map(((t,i)=>({id:Ce(e,i),vector:f[i],payload:{...r,file_path:e.replace(/\\/g,"/"),chunk_index:i,total_chunks:g.length,content_hash:a,chunk_text:t}})));if(await this.vectorStore.upsert(p),h>g.length){const t=Le(e,h).slice(g.length);await this.vectorStore.delete(t)}this.logger.info({filePath:e,chunks:g.length},"File processed successfully")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to process file")}}async deleteFile(e){try{const t=Ce(e,0),r=await this.vectorStore.getPayload(t),i=Le(e,Qe(r));await this.vectorStore.delete(i),await H(e,this.config.metadataDir),this.logger.info({filePath:e},"File deleted from index")}catch(t){this.logger.error({filePath:e,err:T(t)},"Failed to delete file")}}async processMetadataUpdate(e,t){try{const r={...await O(e,this.config.metadataDir)??{},...t};await _(e,this.config.metadataDir,r);const i=Ce(e,0),n=await this.vectorStore.getPayload(i);if(!n)return null;const o=Qe(n),s=Le(e,o);return await this.vectorStore.setPayload(s,r),this.logger.info({filePath:e,chunks:o},"Metadata updated"),r}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to update metadata"),null}}async processRulesUpdate(e){try{const t=Ce(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 qe(e,this.compiledRules,this.config.metadataDir,this.config.maps,this.logger,this.templateEngine,this.config.configDir),n=Qe(r),o=Le(e,n);return await this.vectorStore.setPayload(o,i),this.logger.info({filePath:e,chunks:n},"Rules re-applied"),i}catch(t){return this.logger.error({filePath:e,err:T(t)},"Failed to re-apply rules"),null}}updateRules(e,t){this.compiledRules=e,t&&(this.templateEngine=t),this.logger.info({rules:e.length},"Inference rules updated")}}class Be{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 n=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,n)}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()}}function Je(e){return null==e?"keyword":"number"==typeof e?Number.isInteger(e)?"integer":"float":"boolean"==typeof e?"bool":Array.isArray(e)?"keyword[]":"string"==typeof e&&e.length>256?"text":"keyword"}class Ke{client;collectionName;dims;log;constructor(e,t,r){this.client=new C.QdrantClient({url:e.url,apiKey:e.apiKey,checkCompatibility:!1}),this.collectionName=e.collectionName,this.dims=t,this.log=ve(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 Fe((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:T(r)},"Qdrant upsert failed; will retry")}})}async delete(e){0!==e.length&&await Fe((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:T(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 getCollectionInfo(){const e=await this.client.getCollection(this.collectionName),t=e.points_count??0,r=e.config.params.vectors,i=void 0!==r&&"size"in r?r.size:0,n={},o=Object.entries(e.payload_schema);if(o.length>0)for(const[e,t]of o)n[e]={type:t.data_type??"unknown"};else t>0&&await this.discoverPayloadFields(n);return{pointCount:t,dimensions:i,payloadFields:n}}async discoverPayloadFields(e,t=100){const r=await this.client.scroll(this.collectionName,{limit:t,with_payload:!0,with_vector:!1});for(const t of r.points){const r=t.payload;if(r)for(const[t,i]of Object.entries(r))t in e||(e[t]={type:Je(i)})}}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 n=i.next_page_offset;if(null==n)break;if("string"!=typeof n&&"number"!=typeof n)break;r=n}}}class Ue{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:T(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 n=setTimeout((()=>{s(),r()}),t),o=()=>{s(),i(new Error("Backoff aborted"))},s=()=>{clearTimeout(n),e&&e.removeEventListener("abort",o)};if(e){if(e.aborted)return void o();e.addEventListener("abort",o,{once:!0})}})))}get failures(){return this.consecutiveFailures}}function Ve(e){const t=e.replace(/\\/g,"/").split("/"),r=[];for(const e of t){if(/[*?{[\]]/.test(e))break;r.push(e)}return r.join("/")||"."}function Ye(e){const t=function(e){const t=e.map((e=>e.replace(/\\/g,"/").toLowerCase()));return[...new Set(t)].sort().filter(((e,t,r)=>{const i=e.endsWith("/")?e:e+"/";return!r.some((t=>t!==e&&i.startsWith(t+"/")))}))}(e.map(Ve)),r=function(e){const t=e.map((e=>e.replace(/\\/g,"/"))),r=n(t,{dot:!0,nocase:!0});return e=>{const t=e.replace(/\\/g,"/");return r(t)}}(e);return{roots:t,matches:r}}class Ze{config;queue;processor;logger;health;gitignoreFilter;globMatches;watcher;constructor(e,t,r,i,n={}){this.config=e,this.queue=t,this.processor=r,this.logger=i,this.gitignoreFilter=n.gitignoreFilter,this.globMatches=()=>!0;const o={maxRetries:n.maxRetries,maxBackoffMs:n.maxBackoffMs,onFatalError:n.onFatalError,logger:i};this.health=new Ue(o)}start(){const{roots:e,matches:t}=Ye(this.config.paths);this.globMatches=t;const r=this.config.ignored?function(e){return e.map((e=>{if("string"!=typeof e)return e;const t=e.replace(/\\/g,"/"),r=n(t,{dot:!0,nocase:!0});return e=>{const t=e.replace(/\\/g,"/");return r(t)}}))}(this.config.ignored):void 0;this.watcher=m.watch(e,{ignored:r,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.globMatches(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.globMatches(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.globMatches(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:T(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()}}}const Xe={loadConfig:be,createLogger:Pe,createEmbeddingProvider:ke,createVectorStoreClient:(e,t,r)=>new Ke(e,t,r),compileRules:$e,createDocumentProcessor:(e,t,r,i,n,o)=>new Ge(e,t,r,i,n,o),createEventQueue:e=>new Be(e),createFileSystemWatcher:(e,t,r,i,n)=>new Ze(e,t,r,i,n),createApiServer:q};class et{config;configPath;factories;runtimeOptions;logger;watcher;queue;server;processor;configWatcher;constructor(e,t,r={},i={}){this.config=e,this.configPath=t,this.factories={...Xe,...r},this.runtimeOptions=i}async start(){const e=this.factories.createLogger(this.config.logging);this.logger=e;const{embeddingProvider:t,vectorStore:r}=await this.initEmbeddingAndStore(e),n=this.factories.compileRules(this.config.inferenceRules??[]),o=this.configPath?i.dirname(this.configPath):".",s=await ee(this.config.inferenceRules??[],this.config.templates,this.config.templateHelpers?.paths,o),a=this.factories.createDocumentProcessor({metadataDir:this.config.metadataDir??".jeeves-metadata",chunkSize:this.config.embedding.chunkSize,chunkOverlap:this.config.embedding.chunkOverlap,maps:this.config.maps,configDir:o},t,r,n,e,s);this.processor=a,this.queue=this.factories.createEventQueue({debounceMs:this.config.watch.debounceMs??2e3,concurrency:this.config.embedding.concurrency??5,rateLimitPerMinute:this.config.embedding.rateLimitPerMinute}),this.watcher=this.createWatcher(this.queue,a,e),this.server=await this.startApiServer(a,r,t,e),this.watcher.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")}async initEmbeddingAndStore(e){let t;try{t=this.factories.createEmbeddingProvider(this.config.embedding,e)}catch(t){throw e.fatal({err:T(t)},"Failed to create embedding provider"),t}const r=this.factories.createVectorStoreClient(this.config.vectorStore,t.dimensions,e);return await r.ensureCollection(),{embeddingProvider:t,vectorStore:r}}createWatcher(e,t,r){const i=this.config.watch.respectGitignore??!0?new J(this.config.watch.paths):void 0;return this.factories.createFileSystemWatcher(this.config.watch,e,t,r,{maxRetries:this.config.maxRetries,maxBackoffMs:this.config.maxBackoffMs,onFatalError:this.runtimeOptions.onFatalError,gitignoreFilter:i})}async startApiServer(e,t,r,i){const n=this.factories.createApiServer({processor:e,vectorStore:t,embeddingProvider:r,queue:this.queue,config:this.config,logger:i});return await n.listen({host:this.config.api?.host??"127.0.0.1",port:this.config.api?.port??3456}),n}startConfigWatch(){const e=this.logger;if(!e)return;const t=this.config.configWatch?.enabled??!0;t&&this.configPath?(this.configWatcher=new te({configPath:this.configPath,enabled:t,debounceMs:this.config.configWatch?.debounceMs??1e4,logger:e,onChange:async()=>this.reloadConfig()}),this.configWatcher.start()):this.configPath||e.debug("Config watch enabled, but no config path was provided")}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 n=this.factories.compileRules(r.inferenceRules??[]),o=i.dirname(this.configPath),s=await ee(r.inferenceRules??[],r.templates,r.templateHelpers?.paths,o);t.updateRules(n,s),e.info({configPath:this.configPath,rules:n.length},"Config reloaded")}catch(t){e.error({err:T(t)},"Failed to reload config")}}}}e.DocumentProcessor=Ge,e.EventQueue=Be,e.FileSystemWatcher=Ze,e.GitignoreFilter=J,e.JeevesWatcher=et,e.SystemHealth=Ue,e.TemplateEngine=X,e.VectorStoreClient=Ke,e.apiConfigSchema=ue,e.applyRules=_e,e.buildAttributes=He,e.buildTemplateEngine=ee,e.compileRules=$e,e.configWatchConfigSchema=le,e.contentHash=ze,e.createApiServer=q,e.createEmbeddingProvider=ke,e.createHandlebarsInstance=Y,e.createLogger=Pe,e.deleteMetadata=H,e.embeddingConfigSchema=he,e.extractText=We,e.inferenceRuleSchema=fe,e.jeevesWatcherConfigSchema=pe,e.loadConfig=be,e.loadCustomHelpers=Z,e.loggingConfigSchema=ge,e.metadataPath=N,e.pointId=Ce,e.readMetadata=O,e.registerBuiltinHelpers=U,e.resolveTemplateSource=V,e.startFromConfig=async function(e){const t=await be(e),r=new et(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=de,e.watchConfigSchema=ce,e.writeMetadata=_}(this["jeeves-watcher"]=this["jeeves-watcher"]||{},Fastify,promises,node_path,picomatch,radash,node_crypto,node_fs,ignore,Handlebars,dayjs,hastUtilToMdast,mdastUtilFromAdf,mdastUtilToMarkdown,rehypeParse,unified,chokidar,cosmiconfig,zod,jsonmap,googleGenai,pino,uuid,cheerio,yaml,mammoth,Ajv,addFormats,textsplitters,jsClientRest);
package/dist/mjs/index.js CHANGED
@@ -1143,11 +1143,11 @@ const jeevesWatcherConfigSchema = z.object({
1143
1143
  .array(inferenceRuleSchema)
1144
1144
  .optional()
1145
1145
  .describe('Rules for inferring metadata from file attributes.'),
1146
- /** Reusable named JsonMap transformations. */
1146
+ /** Reusable named JsonMap transformations (inline objects or .json file paths). */
1147
1147
  maps: z
1148
- .record(z.string(), jsonMapMapSchema)
1148
+ .record(z.string(), jsonMapMapSchema.or(z.string()))
1149
1149
  .optional()
1150
- .describe('Reusable named JsonMap transformations.'),
1150
+ .describe('Reusable named JsonMap transformations (inline definition or .json file path resolved relative to config directory).'),
1151
1151
  /** Reusable named Handlebars templates (inline strings or .hbs/.handlebars file paths). */
1152
1152
  templates: z
1153
1153
  .record(z.string(), z.string())
@@ -1261,6 +1261,18 @@ async function loadConfig(configPath) {
1261
1261
  }
1262
1262
  try {
1263
1263
  const validated = jeevesWatcherConfigSchema.parse(result.config);
1264
+ // Resolve file-path map references relative to config directory.
1265
+ // After this block, all map values are inline JsonMapMap objects.
1266
+ if (validated.maps) {
1267
+ const configDir = dirname(result.filepath);
1268
+ for (const [name, value] of Object.entries(validated.maps)) {
1269
+ if (typeof value === 'string') {
1270
+ const mapPath = resolve(configDir, value);
1271
+ const raw = readFileSync(mapPath, 'utf-8');
1272
+ validated.maps[name] = JSON.parse(raw);
1273
+ }
1274
+ }
1275
+ }
1264
1276
  const withDefaults = applyDefaults(validated);
1265
1277
  return substituteEnvVars(withDefaults);
1266
1278
  }
@@ -2746,6 +2758,34 @@ function resolveWatchPaths(globs) {
2746
2758
  const matches = buildGlobMatcher(globs);
2747
2759
  return { roots, matches };
2748
2760
  }
2761
+ /**
2762
+ * Convert ignored glob patterns to picomatch matcher functions.
2763
+ *
2764
+ * Chokidar v5 replaced the external `anymatch` dependency with an inline
2765
+ * implementation that does **exact string equality** for string matchers,
2766
+ * breaking glob-based `ignored` patterns. This function converts glob strings
2767
+ * to picomatch functions that chokidar's `createPattern` passes through
2768
+ * unchanged (`typeof matcher === 'function'`).
2769
+ *
2770
+ * Non-string entries (functions, RegExps) are passed through as-is.
2771
+ *
2772
+ * @param ignored - Array of ignored patterns (globs, functions, RegExps).
2773
+ * @returns Array with glob strings replaced by picomatch matcher functions.
2774
+ */
2775
+ function resolveIgnored(ignored) {
2776
+ return ignored.map((entry) => {
2777
+ if (typeof entry !== 'string')
2778
+ return entry;
2779
+ // If the string contains glob characters, convert to a picomatch function.
2780
+ // Literal strings (exact paths) are also converted for consistent matching.
2781
+ const normalizedPattern = entry.replace(/\\/g, '/');
2782
+ const matcher = picomatch(normalizedPattern, { dot: true, nocase: true });
2783
+ return (filePath) => {
2784
+ const normalized = filePath.replace(/\\/g, '/');
2785
+ return matcher(normalized);
2786
+ };
2787
+ });
2788
+ }
2749
2789
 
2750
2790
  /**
2751
2791
  * @module watcher
@@ -2797,8 +2837,14 @@ class FileSystemWatcher {
2797
2837
  // filter emitted events against the original globs via picomatch.
2798
2838
  const { roots, matches } = resolveWatchPaths(this.config.paths);
2799
2839
  this.globMatches = matches;
2840
+ // Chokidar v5's inline anymatch does exact string equality for string
2841
+ // matchers, breaking glob-based ignored patterns. Convert to picomatch
2842
+ // functions that chokidar passes through as-is.
2843
+ const ignored = this.config.ignored
2844
+ ? resolveIgnored(this.config.ignored)
2845
+ : undefined;
2800
2846
  this.watcher = chokidar.watch(roots, {
2801
- ignored: this.config.ignored,
2847
+ ignored,
2802
2848
  usePolling: this.config.usePolling,
2803
2849
  interval: this.config.pollIntervalMs,
2804
2850
  awaitWriteFinish: this.config.stabilityThresholdMs
package/package.json CHANGED
@@ -185,5 +185,5 @@
185
185
  },
186
186
  "type": "module",
187
187
  "types": "dist/index.d.ts",
188
- "version": "0.4.0"
188
+ "version": "0.4.2"
189
189
  }