@karmaniverous/jeeves-watcher 0.6.4 → 0.6.5

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.
@@ -456,7 +456,7 @@ z.object({
456
456
  * @returns The configured AJV instance.
457
457
  */
458
458
  function createRuleAjv() {
459
- const ajv = new Ajv({ allErrors: true });
459
+ const ajv = new Ajv({ allErrors: true, strict: false });
460
460
  addFormats(ajv);
461
461
  ajv.addKeyword({
462
462
  keyword: 'glob',
@@ -2786,6 +2786,53 @@ function createReindexHandler(deps) {
2786
2786
  }, deps.logger, 'Reindex');
2787
2787
  }
2788
2788
 
2789
+ /**
2790
+ * @module api/handlers/rulesReapply
2791
+ * Fastify route handler for POST /rules/reapply.
2792
+ * Re-applies current inference rules to already-indexed files matching given globs.
2793
+ */
2794
+ /**
2795
+ * Create handler for POST /rules/reapply.
2796
+ *
2797
+ * Scrolls through all indexed points, finds files matching the given globs,
2798
+ * and re-applies current inference rules without re-embedding.
2799
+ */
2800
+ function createRulesReapplyHandler(deps) {
2801
+ return wrapHandler(async (request) => {
2802
+ await Promise.resolve();
2803
+ const { globs } = request.body;
2804
+ if (!Array.isArray(globs) || globs.length === 0) {
2805
+ throw new Error('Missing required field: globs (non-empty string array)');
2806
+ }
2807
+ const normalizedGlobs = globs.map((g) => normalizeSlashes(g));
2808
+ const isMatch = picomatch(normalizedGlobs, { dot: true, nocase: true });
2809
+ // Collect unique file paths matching the globs
2810
+ const matchingFiles = new Set();
2811
+ for await (const point of deps.vectorStore.scroll()) {
2812
+ const filePath = point.payload['file_path'];
2813
+ if (typeof filePath === 'string' && isMatch(filePath)) {
2814
+ matchingFiles.add(filePath);
2815
+ }
2816
+ }
2817
+ deps.logger.info({ globs: normalizedGlobs, matchCount: matchingFiles.size }, 'Re-applying rules to matching files');
2818
+ let updated = 0;
2819
+ for (const filePath of matchingFiles) {
2820
+ try {
2821
+ const result = await deps.processor.processRulesUpdate(filePath);
2822
+ if (result !== null)
2823
+ updated++;
2824
+ }
2825
+ catch (error) {
2826
+ deps.logger.warn({ filePath, err: error }, 'Failed to re-apply rules to file');
2827
+ }
2828
+ }
2829
+ return {
2830
+ matched: matchingFiles.size,
2831
+ updated,
2832
+ };
2833
+ }, deps.logger, 'RulesReapply');
2834
+ }
2835
+
2789
2836
  /**
2790
2837
  * @module api/handlers/rulesRegister
2791
2838
  * Fastify route handler for POST /rules/register.
@@ -3028,6 +3075,7 @@ function createApiServer(options) {
3028
3075
  onRulesChanged,
3029
3076
  }));
3030
3077
  app.post('/points/delete', createPointsDeleteHandler({ vectorStore, logger }));
3078
+ app.post('/rules/reapply', createRulesReapplyHandler({ processor, vectorStore, logger }));
3031
3079
  }
3032
3080
  return app;
3033
3081
  }
@@ -3949,7 +3997,10 @@ class DocumentProcessor {
3949
3997
  if (customMapLib !== undefined) {
3950
3998
  this.config = { ...this.config, customMapLib };
3951
3999
  }
3952
- this.logger.info({ rules: compiledRules.length }, 'Inference rules updated');
4000
+ this.logger.info({
4001
+ rules: compiledRules.length,
4002
+ ruleNames: compiledRules.map((r) => r.rule.name),
4003
+ }, 'Inference rules updated');
3953
4004
  }
3954
4005
  }
3955
4006
 
package/dist/index.js CHANGED
@@ -905,7 +905,7 @@ function buildAttributes(filePath, stats, extractedFrontmatter, extractedJson) {
905
905
  * @returns The configured AJV instance.
906
906
  */
907
907
  function createRuleAjv() {
908
- const ajv = new Ajv({ allErrors: true });
908
+ const ajv = new Ajv({ allErrors: true, strict: false });
909
909
  addFormats(ajv);
910
910
  ajv.addKeyword({
911
911
  keyword: 'glob',
@@ -2478,6 +2478,53 @@ function createReindexHandler(deps) {
2478
2478
  }, deps.logger, 'Reindex');
2479
2479
  }
2480
2480
 
2481
+ /**
2482
+ * @module api/handlers/rulesReapply
2483
+ * Fastify route handler for POST /rules/reapply.
2484
+ * Re-applies current inference rules to already-indexed files matching given globs.
2485
+ */
2486
+ /**
2487
+ * Create handler for POST /rules/reapply.
2488
+ *
2489
+ * Scrolls through all indexed points, finds files matching the given globs,
2490
+ * and re-applies current inference rules without re-embedding.
2491
+ */
2492
+ function createRulesReapplyHandler(deps) {
2493
+ return wrapHandler(async (request) => {
2494
+ await Promise.resolve();
2495
+ const { globs } = request.body;
2496
+ if (!Array.isArray(globs) || globs.length === 0) {
2497
+ throw new Error('Missing required field: globs (non-empty string array)');
2498
+ }
2499
+ const normalizedGlobs = globs.map((g) => normalizeSlashes(g));
2500
+ const isMatch = picomatch(normalizedGlobs, { dot: true, nocase: true });
2501
+ // Collect unique file paths matching the globs
2502
+ const matchingFiles = new Set();
2503
+ for await (const point of deps.vectorStore.scroll()) {
2504
+ const filePath = point.payload['file_path'];
2505
+ if (typeof filePath === 'string' && isMatch(filePath)) {
2506
+ matchingFiles.add(filePath);
2507
+ }
2508
+ }
2509
+ deps.logger.info({ globs: normalizedGlobs, matchCount: matchingFiles.size }, 'Re-applying rules to matching files');
2510
+ let updated = 0;
2511
+ for (const filePath of matchingFiles) {
2512
+ try {
2513
+ const result = await deps.processor.processRulesUpdate(filePath);
2514
+ if (result !== null)
2515
+ updated++;
2516
+ }
2517
+ catch (error) {
2518
+ deps.logger.warn({ filePath, err: error }, 'Failed to re-apply rules to file');
2519
+ }
2520
+ }
2521
+ return {
2522
+ matched: matchingFiles.size,
2523
+ updated,
2524
+ };
2525
+ }, deps.logger, 'RulesReapply');
2526
+ }
2527
+
2481
2528
  /**
2482
2529
  * @module api/handlers/rulesRegister
2483
2530
  * Fastify route handler for POST /rules/register.
@@ -2720,6 +2767,7 @@ function createApiServer(options) {
2720
2767
  onRulesChanged,
2721
2768
  }));
2722
2769
  app.post('/points/delete', createPointsDeleteHandler({ vectorStore, logger }));
2770
+ app.post('/rules/reapply', createRulesReapplyHandler({ processor, vectorStore, logger }));
2723
2771
  }
2724
2772
  return app;
2725
2773
  }
@@ -3931,7 +3979,10 @@ class DocumentProcessor {
3931
3979
  if (customMapLib !== undefined) {
3932
3980
  this.config = { ...this.config, customMapLib };
3933
3981
  }
3934
- this.logger.info({ rules: compiledRules.length }, 'Inference rules updated');
3982
+ this.logger.info({
3983
+ rules: compiledRules.length,
3984
+ ruleNames: compiledRules.map((r) => r.rule.name),
3985
+ }, 'Inference rules updated');
3935
3986
  }
3936
3987
  }
3937
3988
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-watcher",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Filesystem watcher that keeps a Qdrant vector store in sync with document changes",
6
6
  "license": "BSD-3-Clause",