@karmaniverous/jeeves-watcher 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.schema.json +38 -11
- package/dist/cjs/index.js +278 -168
- package/dist/cli/jeeves-watcher/index.js +279 -169
- package/dist/index.d.ts +10 -9
- package/dist/index.iife.js +278 -169
- package/dist/index.iife.min.js +1 -1
- package/dist/mjs/index.js +279 -169
- package/package.json +1 -1
package/config.schema.json
CHANGED
|
@@ -145,11 +145,19 @@
|
|
|
145
145
|
}
|
|
146
146
|
]
|
|
147
147
|
},
|
|
148
|
+
"mapHelpers": {
|
|
149
|
+
"description": "Custom JsonMap lib function registration.",
|
|
150
|
+
"allOf": [
|
|
151
|
+
{
|
|
152
|
+
"$ref": "#/definitions/__schema55"
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
},
|
|
148
156
|
"logging": {
|
|
149
157
|
"description": "Logging configuration.",
|
|
150
158
|
"allOf": [
|
|
151
159
|
{
|
|
152
|
-
"$ref": "#/definitions/
|
|
160
|
+
"$ref": "#/definitions/__schema57"
|
|
153
161
|
}
|
|
154
162
|
]
|
|
155
163
|
},
|
|
@@ -157,7 +165,7 @@
|
|
|
157
165
|
"description": "Timeout in milliseconds for graceful shutdown.",
|
|
158
166
|
"allOf": [
|
|
159
167
|
{
|
|
160
|
-
"$ref": "#/definitions/
|
|
168
|
+
"$ref": "#/definitions/__schema60"
|
|
161
169
|
}
|
|
162
170
|
]
|
|
163
171
|
},
|
|
@@ -165,7 +173,7 @@
|
|
|
165
173
|
"description": "Maximum consecutive system-level failures before triggering fatal error. Default: Infinity.",
|
|
166
174
|
"allOf": [
|
|
167
175
|
{
|
|
168
|
-
"$ref": "#/definitions/
|
|
176
|
+
"$ref": "#/definitions/__schema61"
|
|
169
177
|
}
|
|
170
178
|
]
|
|
171
179
|
},
|
|
@@ -173,7 +181,7 @@
|
|
|
173
181
|
"description": "Maximum backoff delay in milliseconds for system errors. Default: 60000.",
|
|
174
182
|
"allOf": [
|
|
175
183
|
{
|
|
176
|
-
"$ref": "#/definitions/
|
|
184
|
+
"$ref": "#/definitions/__schema62"
|
|
177
185
|
}
|
|
178
186
|
]
|
|
179
187
|
}
|
|
@@ -636,13 +644,32 @@
|
|
|
636
644
|
}
|
|
637
645
|
},
|
|
638
646
|
"__schema55": {
|
|
647
|
+
"type": "object",
|
|
648
|
+
"properties": {
|
|
649
|
+
"paths": {
|
|
650
|
+
"description": "File paths to JS modules exporting functions to merge into the JsonMap lib.",
|
|
651
|
+
"allOf": [
|
|
652
|
+
{
|
|
653
|
+
"$ref": "#/definitions/__schema56"
|
|
654
|
+
}
|
|
655
|
+
]
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
"__schema56": {
|
|
660
|
+
"type": "array",
|
|
661
|
+
"items": {
|
|
662
|
+
"type": "string"
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
"__schema57": {
|
|
639
666
|
"type": "object",
|
|
640
667
|
"properties": {
|
|
641
668
|
"level": {
|
|
642
669
|
"description": "Logging level (trace, debug, info, warn, error, fatal).",
|
|
643
670
|
"allOf": [
|
|
644
671
|
{
|
|
645
|
-
"$ref": "#/definitions/
|
|
672
|
+
"$ref": "#/definitions/__schema58"
|
|
646
673
|
}
|
|
647
674
|
]
|
|
648
675
|
},
|
|
@@ -650,25 +677,25 @@
|
|
|
650
677
|
"description": "Path to log file (logs to stdout if omitted).",
|
|
651
678
|
"allOf": [
|
|
652
679
|
{
|
|
653
|
-
"$ref": "#/definitions/
|
|
680
|
+
"$ref": "#/definitions/__schema59"
|
|
654
681
|
}
|
|
655
682
|
]
|
|
656
683
|
}
|
|
657
684
|
}
|
|
658
685
|
},
|
|
659
|
-
"
|
|
686
|
+
"__schema58": {
|
|
660
687
|
"type": "string"
|
|
661
688
|
},
|
|
662
|
-
"
|
|
689
|
+
"__schema59": {
|
|
663
690
|
"type": "string"
|
|
664
691
|
},
|
|
665
|
-
"
|
|
692
|
+
"__schema60": {
|
|
666
693
|
"type": "number"
|
|
667
694
|
},
|
|
668
|
-
"
|
|
695
|
+
"__schema61": {
|
|
669
696
|
"type": "number"
|
|
670
697
|
},
|
|
671
|
-
"
|
|
698
|
+
"__schema62": {
|
|
672
699
|
"type": "number"
|
|
673
700
|
}
|
|
674
701
|
}
|
package/dist/cjs/index.js
CHANGED
|
@@ -8,6 +8,8 @@ var radash = require('radash');
|
|
|
8
8
|
var node_crypto = require('node:crypto');
|
|
9
9
|
var node_fs = require('node:fs');
|
|
10
10
|
var ignore = require('ignore');
|
|
11
|
+
var node_url = require('node:url');
|
|
12
|
+
var jsonmap = require('@karmaniverous/jsonmap');
|
|
11
13
|
var Handlebars = require('handlebars');
|
|
12
14
|
var dayjs = require('dayjs');
|
|
13
15
|
var hastUtilToMdast = require('hast-util-to-mdast');
|
|
@@ -18,7 +20,6 @@ var unified = require('unified');
|
|
|
18
20
|
var chokidar = require('chokidar');
|
|
19
21
|
var cosmiconfig = require('cosmiconfig');
|
|
20
22
|
var zod = require('zod');
|
|
21
|
-
var jsonmap = require('@karmaniverous/jsonmap');
|
|
22
23
|
var googleGenai = require('@langchain/google-genai');
|
|
23
24
|
var pino = require('pino');
|
|
24
25
|
var uuid = require('uuid');
|
|
@@ -657,6 +658,251 @@ class GitignoreFilter {
|
|
|
657
658
|
}
|
|
658
659
|
}
|
|
659
660
|
|
|
661
|
+
/**
|
|
662
|
+
* @module rules/templates
|
|
663
|
+
* Resolves template variables (`${path.to.value}`) in rule `set` objects against file attributes.
|
|
664
|
+
*/
|
|
665
|
+
/**
|
|
666
|
+
* Resolve `${template.vars}` in a value against the given attributes.
|
|
667
|
+
*
|
|
668
|
+
* @param value - The value to resolve.
|
|
669
|
+
* @param attributes - The file attributes for variable lookup.
|
|
670
|
+
* @returns The resolved value.
|
|
671
|
+
*/
|
|
672
|
+
function resolveTemplateVars(value, attributes) {
|
|
673
|
+
if (typeof value !== 'string')
|
|
674
|
+
return value;
|
|
675
|
+
return value.replace(/\$\{([^}]+)\}/g, (_match, varPath) => {
|
|
676
|
+
const current = radash.get(attributes, varPath);
|
|
677
|
+
if (current === null || current === undefined)
|
|
678
|
+
return '';
|
|
679
|
+
return typeof current === 'string' ? current : JSON.stringify(current);
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Resolve all template variables in a `set` object.
|
|
684
|
+
*
|
|
685
|
+
* @param setObj - The key-value pairs to resolve.
|
|
686
|
+
* @param attributes - The file attributes for variable lookup.
|
|
687
|
+
* @returns The resolved key-value pairs.
|
|
688
|
+
*/
|
|
689
|
+
function resolveSet(setObj, attributes) {
|
|
690
|
+
const result = {};
|
|
691
|
+
for (const [key, value] of Object.entries(setObj)) {
|
|
692
|
+
result[key] = resolveTemplateVars(value, attributes);
|
|
693
|
+
}
|
|
694
|
+
return result;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* @module rules/apply
|
|
699
|
+
* Applies compiled inference rules to file attributes, producing merged metadata via template resolution and JsonMap transforms.
|
|
700
|
+
*/
|
|
701
|
+
/**
|
|
702
|
+
* Create the lib object for JsonMap transformations.
|
|
703
|
+
*
|
|
704
|
+
* @param configDir - Optional config directory for resolving relative file paths in lookups.
|
|
705
|
+
* @returns The lib object.
|
|
706
|
+
*/
|
|
707
|
+
function createJsonMapLib(configDir, customLib) {
|
|
708
|
+
// Cache loaded JSON files within a single applyRules invocation.
|
|
709
|
+
const jsonCache = new Map();
|
|
710
|
+
const loadJson = (filePath) => {
|
|
711
|
+
const resolvedPath = configDir ? node_path.resolve(configDir, filePath) : filePath;
|
|
712
|
+
if (!jsonCache.has(resolvedPath)) {
|
|
713
|
+
const raw = node_fs.readFileSync(resolvedPath, 'utf-8');
|
|
714
|
+
jsonCache.set(resolvedPath, JSON.parse(raw));
|
|
715
|
+
}
|
|
716
|
+
return jsonCache.get(resolvedPath);
|
|
717
|
+
};
|
|
718
|
+
return {
|
|
719
|
+
split: (str, separator) => str.split(separator),
|
|
720
|
+
slice: (arr, start, end) => arr.slice(start, end),
|
|
721
|
+
join: (arr, separator) => arr.join(separator),
|
|
722
|
+
toLowerCase: (str) => str.toLowerCase(),
|
|
723
|
+
replace: (str, search, replacement) => str.replace(search, replacement),
|
|
724
|
+
get: (obj, path) => radash.get(obj, path),
|
|
725
|
+
/**
|
|
726
|
+
* Load a JSON file (relative to configDir) and look up a value by key,
|
|
727
|
+
* optionally drilling into a sub-path.
|
|
728
|
+
*
|
|
729
|
+
* @param filePath - Path to a JSON file (resolved relative to configDir).
|
|
730
|
+
* @param key - Top-level key to look up.
|
|
731
|
+
* @param field - Optional dot-path into the looked-up entry.
|
|
732
|
+
* @returns The resolved value, or null if not found.
|
|
733
|
+
*/
|
|
734
|
+
lookupJson: (filePath, key, field) => {
|
|
735
|
+
const data = loadJson(filePath);
|
|
736
|
+
const entry = data[key];
|
|
737
|
+
if (entry === undefined || entry === null)
|
|
738
|
+
return null;
|
|
739
|
+
if (field)
|
|
740
|
+
return radash.get(entry, field) ?? null;
|
|
741
|
+
return entry;
|
|
742
|
+
},
|
|
743
|
+
/**
|
|
744
|
+
* Map an array of keys through a JSON lookup file, collecting a specific
|
|
745
|
+
* field from each matching entry. Non-matching keys are silently skipped.
|
|
746
|
+
* Array-valued fields are flattened into the result.
|
|
747
|
+
*
|
|
748
|
+
* @param filePath - Path to a JSON file (resolved relative to configDir).
|
|
749
|
+
* @param keys - Array of top-level keys to look up.
|
|
750
|
+
* @param field - Dot-path into each looked-up entry.
|
|
751
|
+
* @returns Flat array of resolved values.
|
|
752
|
+
*/
|
|
753
|
+
mapLookup: (filePath, keys, field) => {
|
|
754
|
+
if (!Array.isArray(keys))
|
|
755
|
+
return [];
|
|
756
|
+
const data = loadJson(filePath);
|
|
757
|
+
const results = [];
|
|
758
|
+
for (const k of keys) {
|
|
759
|
+
if (typeof k !== 'string')
|
|
760
|
+
continue;
|
|
761
|
+
const entry = data[k];
|
|
762
|
+
if (entry === undefined || entry === null)
|
|
763
|
+
continue;
|
|
764
|
+
const val = radash.get(entry, field);
|
|
765
|
+
if (val === undefined || val === null)
|
|
766
|
+
continue;
|
|
767
|
+
if (Array.isArray(val)) {
|
|
768
|
+
for (const item of val) {
|
|
769
|
+
results.push(item);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
results.push(val);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return results;
|
|
777
|
+
},
|
|
778
|
+
...customLib,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Load custom JsonMap lib functions from file paths.
|
|
783
|
+
*
|
|
784
|
+
* Each module should default-export an object of functions,
|
|
785
|
+
* or use named exports. Only function-valued exports are merged.
|
|
786
|
+
*
|
|
787
|
+
* @param paths - File paths to custom lib modules.
|
|
788
|
+
* @param configDir - Directory to resolve relative paths against.
|
|
789
|
+
* @returns The merged custom lib functions.
|
|
790
|
+
*/
|
|
791
|
+
async function loadCustomMapHelpers(paths, configDir) {
|
|
792
|
+
const merged = {};
|
|
793
|
+
for (const p of paths) {
|
|
794
|
+
const resolved = node_path.resolve(configDir, p);
|
|
795
|
+
const mod = (await import(node_url.pathToFileURL(resolved).href));
|
|
796
|
+
const fns = typeof mod.default === 'object' && mod.default !== null
|
|
797
|
+
? mod.default
|
|
798
|
+
: mod;
|
|
799
|
+
for (const [key, val] of Object.entries(fns)) {
|
|
800
|
+
if (typeof val === 'function') {
|
|
801
|
+
merged[key] = val;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return merged;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Apply compiled inference rules to file attributes, returning merged metadata and optional rendered content.
|
|
809
|
+
*
|
|
810
|
+
* Rules are evaluated in order; later rules override earlier ones.
|
|
811
|
+
* If a rule has a `map`, the JsonMap transformation is applied after `set` resolution,
|
|
812
|
+
* and map output overrides set output on conflict.
|
|
813
|
+
*
|
|
814
|
+
* @param compiledRules - The compiled rules to evaluate.
|
|
815
|
+
* @param attributes - The file attributes to match against.
|
|
816
|
+
* @param namedMaps - Optional record of named JsonMap definitions.
|
|
817
|
+
* @param logger - Optional logger for warnings (falls back to console.warn).
|
|
818
|
+
* @param templateEngine - Optional template engine for rendering content templates.
|
|
819
|
+
* @param configDir - Optional config directory for resolving .json map file paths.
|
|
820
|
+
* @returns The merged metadata and optional rendered content.
|
|
821
|
+
*/
|
|
822
|
+
async function applyRules(compiledRules, attributes, namedMaps, logger, templateEngine, configDir, customMapLib) {
|
|
823
|
+
// JsonMap's type definitions expect a generic JsonMapLib shape with unary functions.
|
|
824
|
+
// Our helper functions accept multiple args, which JsonMap supports at runtime.
|
|
825
|
+
const lib = createJsonMapLib(configDir, customMapLib);
|
|
826
|
+
let merged = {};
|
|
827
|
+
let renderedContent = null;
|
|
828
|
+
const log = logger ?? console;
|
|
829
|
+
for (const [ruleIndex, { rule, validate }] of compiledRules.entries()) {
|
|
830
|
+
if (validate(attributes)) {
|
|
831
|
+
// Apply set resolution
|
|
832
|
+
const setOutput = resolveSet(rule.set, attributes);
|
|
833
|
+
merged = { ...merged, ...setOutput };
|
|
834
|
+
// Apply map transformation if present
|
|
835
|
+
if (rule.map) {
|
|
836
|
+
let mapDef;
|
|
837
|
+
// Resolve map reference
|
|
838
|
+
if (typeof rule.map === 'string') {
|
|
839
|
+
if (rule.map.endsWith('.json') && configDir) {
|
|
840
|
+
// File path: load from .json file
|
|
841
|
+
try {
|
|
842
|
+
const mapPath = node_path.resolve(configDir, rule.map);
|
|
843
|
+
const raw = node_fs.readFileSync(mapPath, 'utf-8');
|
|
844
|
+
mapDef = JSON.parse(raw);
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
log.warn(`Failed to load map file "${rule.map}": ${error instanceof Error ? error.message : String(error)}`);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
mapDef = namedMaps?.[rule.map];
|
|
853
|
+
if (!mapDef) {
|
|
854
|
+
log.warn(`Map reference "${rule.map}" not found in named maps. Skipping map transformation.`);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
mapDef = rule.map;
|
|
861
|
+
}
|
|
862
|
+
// Execute JsonMap transformation
|
|
863
|
+
try {
|
|
864
|
+
const jsonMap = new jsonmap.JsonMap(mapDef, lib);
|
|
865
|
+
const mapOutput = await jsonMap.transform(attributes);
|
|
866
|
+
if (mapOutput &&
|
|
867
|
+
typeof mapOutput === 'object' &&
|
|
868
|
+
!Array.isArray(mapOutput)) {
|
|
869
|
+
merged = { ...merged, ...mapOutput };
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
log.warn(`JsonMap transformation did not return an object; skipping merge.`);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
catch (error) {
|
|
876
|
+
log.warn(`JsonMap transformation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
// Render template if present
|
|
880
|
+
if (rule.template && templateEngine) {
|
|
881
|
+
const templateKey = `rule-${String(ruleIndex)}`;
|
|
882
|
+
// Build template context: attributes (with json spread at top) + map output
|
|
883
|
+
const context = {
|
|
884
|
+
...(attributes.json ?? {}),
|
|
885
|
+
...attributes,
|
|
886
|
+
...merged,
|
|
887
|
+
};
|
|
888
|
+
try {
|
|
889
|
+
const result = templateEngine.render(templateKey, context);
|
|
890
|
+
if (result && result.trim()) {
|
|
891
|
+
renderedContent = result;
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
log.warn(`Template for rule ${String(ruleIndex)} rendered empty output. Falling back to raw content.`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
log.warn(`Template render failed for rule ${String(ruleIndex)}: ${error instanceof Error ? error.message : String(error)}. Falling back to raw content.`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return { metadata: merged, renderedContent };
|
|
904
|
+
}
|
|
905
|
+
|
|
660
906
|
/**
|
|
661
907
|
* @module templates/helpers
|
|
662
908
|
* Registers built-in Handlebars helpers for content templates.
|
|
@@ -794,7 +1040,7 @@ function createHandlebarsInstance() {
|
|
|
794
1040
|
async function loadCustomHelpers(hbs, paths, configDir) {
|
|
795
1041
|
for (const p of paths) {
|
|
796
1042
|
const resolved = node_path.resolve(configDir, p);
|
|
797
|
-
const mod = (await import(resolved));
|
|
1043
|
+
const mod = (await import(node_url.pathToFileURL(resolved).href));
|
|
798
1044
|
if (typeof mod.default === 'function') {
|
|
799
1045
|
mod.default(hbs);
|
|
800
1046
|
}
|
|
@@ -1185,6 +1431,17 @@ const jeevesWatcherConfigSchema = zod.z.object({
|
|
|
1185
1431
|
})
|
|
1186
1432
|
.optional()
|
|
1187
1433
|
.describe('Custom Handlebars helper registration.'),
|
|
1434
|
+
/** Custom JsonMap lib function registration. */
|
|
1435
|
+
mapHelpers: zod.z
|
|
1436
|
+
.object({
|
|
1437
|
+
/** File paths to custom lib modules (each exports functions to merge into the JsonMap lib). */
|
|
1438
|
+
paths: zod.z
|
|
1439
|
+
.array(zod.z.string())
|
|
1440
|
+
.optional()
|
|
1441
|
+
.describe('File paths to JS modules exporting functions to merge into the JsonMap lib.'),
|
|
1442
|
+
})
|
|
1443
|
+
.optional()
|
|
1444
|
+
.describe('Custom JsonMap lib function registration.'),
|
|
1188
1445
|
/** Logging configuration. */
|
|
1189
1446
|
logging: loggingConfigSchema.optional().describe('Logging configuration.'),
|
|
1190
1447
|
/** Timeout in milliseconds for graceful shutdown. */
|
|
@@ -1675,160 +1932,6 @@ async function extractText(filePath, extension) {
|
|
|
1675
1932
|
return extractPlaintext(filePath);
|
|
1676
1933
|
}
|
|
1677
1934
|
|
|
1678
|
-
/**
|
|
1679
|
-
* @module rules/templates
|
|
1680
|
-
* Resolves template variables (`${path.to.value}`) in rule `set` objects against file attributes.
|
|
1681
|
-
*/
|
|
1682
|
-
/**
|
|
1683
|
-
* Resolve `${template.vars}` in a value against the given attributes.
|
|
1684
|
-
*
|
|
1685
|
-
* @param value - The value to resolve.
|
|
1686
|
-
* @param attributes - The file attributes for variable lookup.
|
|
1687
|
-
* @returns The resolved value.
|
|
1688
|
-
*/
|
|
1689
|
-
function resolveTemplateVars(value, attributes) {
|
|
1690
|
-
if (typeof value !== 'string')
|
|
1691
|
-
return value;
|
|
1692
|
-
return value.replace(/\$\{([^}]+)\}/g, (_match, varPath) => {
|
|
1693
|
-
const current = radash.get(attributes, varPath);
|
|
1694
|
-
if (current === null || current === undefined)
|
|
1695
|
-
return '';
|
|
1696
|
-
return typeof current === 'string' ? current : JSON.stringify(current);
|
|
1697
|
-
});
|
|
1698
|
-
}
|
|
1699
|
-
/**
|
|
1700
|
-
* Resolve all template variables in a `set` object.
|
|
1701
|
-
*
|
|
1702
|
-
* @param setObj - The key-value pairs to resolve.
|
|
1703
|
-
* @param attributes - The file attributes for variable lookup.
|
|
1704
|
-
* @returns The resolved key-value pairs.
|
|
1705
|
-
*/
|
|
1706
|
-
function resolveSet(setObj, attributes) {
|
|
1707
|
-
const result = {};
|
|
1708
|
-
for (const [key, value] of Object.entries(setObj)) {
|
|
1709
|
-
result[key] = resolveTemplateVars(value, attributes);
|
|
1710
|
-
}
|
|
1711
|
-
return result;
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
/**
|
|
1715
|
-
* @module rules/apply
|
|
1716
|
-
* Applies compiled inference rules to file attributes, producing merged metadata via template resolution and JsonMap transforms.
|
|
1717
|
-
*/
|
|
1718
|
-
/**
|
|
1719
|
-
* Create the lib object for JsonMap transformations.
|
|
1720
|
-
*
|
|
1721
|
-
* @returns The lib object.
|
|
1722
|
-
*/
|
|
1723
|
-
function createJsonMapLib() {
|
|
1724
|
-
return {
|
|
1725
|
-
split: (str, separator) => str.split(separator),
|
|
1726
|
-
slice: (arr, start, end) => arr.slice(start, end),
|
|
1727
|
-
join: (arr, separator) => arr.join(separator),
|
|
1728
|
-
toLowerCase: (str) => str.toLowerCase(),
|
|
1729
|
-
replace: (str, search, replacement) => str.replace(search, replacement),
|
|
1730
|
-
get: (obj, path) => radash.get(obj, path),
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
/**
|
|
1734
|
-
* Apply compiled inference rules to file attributes, returning merged metadata and optional rendered content.
|
|
1735
|
-
*
|
|
1736
|
-
* Rules are evaluated in order; later rules override earlier ones.
|
|
1737
|
-
* If a rule has a `map`, the JsonMap transformation is applied after `set` resolution,
|
|
1738
|
-
* and map output overrides set output on conflict.
|
|
1739
|
-
*
|
|
1740
|
-
* @param compiledRules - The compiled rules to evaluate.
|
|
1741
|
-
* @param attributes - The file attributes to match against.
|
|
1742
|
-
* @param namedMaps - Optional record of named JsonMap definitions.
|
|
1743
|
-
* @param logger - Optional logger for warnings (falls back to console.warn).
|
|
1744
|
-
* @param templateEngine - Optional template engine for rendering content templates.
|
|
1745
|
-
* @param configDir - Optional config directory for resolving .json map file paths.
|
|
1746
|
-
* @returns The merged metadata and optional rendered content.
|
|
1747
|
-
*/
|
|
1748
|
-
async function applyRules(compiledRules, attributes, namedMaps, logger, templateEngine, configDir) {
|
|
1749
|
-
// JsonMap's type definitions expect a generic JsonMapLib shape with unary functions.
|
|
1750
|
-
// Our helper functions accept multiple args, which JsonMap supports at runtime.
|
|
1751
|
-
const lib = createJsonMapLib();
|
|
1752
|
-
let merged = {};
|
|
1753
|
-
let renderedContent = null;
|
|
1754
|
-
const log = logger ?? console;
|
|
1755
|
-
for (const [ruleIndex, { rule, validate }] of compiledRules.entries()) {
|
|
1756
|
-
if (validate(attributes)) {
|
|
1757
|
-
// Apply set resolution
|
|
1758
|
-
const setOutput = resolveSet(rule.set, attributes);
|
|
1759
|
-
merged = { ...merged, ...setOutput };
|
|
1760
|
-
// Apply map transformation if present
|
|
1761
|
-
if (rule.map) {
|
|
1762
|
-
let mapDef;
|
|
1763
|
-
// Resolve map reference
|
|
1764
|
-
if (typeof rule.map === 'string') {
|
|
1765
|
-
if (rule.map.endsWith('.json') && configDir) {
|
|
1766
|
-
// File path: load from .json file
|
|
1767
|
-
try {
|
|
1768
|
-
const mapPath = node_path.resolve(configDir, rule.map);
|
|
1769
|
-
const raw = node_fs.readFileSync(mapPath, 'utf-8');
|
|
1770
|
-
mapDef = JSON.parse(raw);
|
|
1771
|
-
}
|
|
1772
|
-
catch (error) {
|
|
1773
|
-
log.warn(`Failed to load map file "${rule.map}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1774
|
-
continue;
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
else {
|
|
1778
|
-
mapDef = namedMaps?.[rule.map];
|
|
1779
|
-
if (!mapDef) {
|
|
1780
|
-
log.warn(`Map reference "${rule.map}" not found in named maps. Skipping map transformation.`);
|
|
1781
|
-
continue;
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
else {
|
|
1786
|
-
mapDef = rule.map;
|
|
1787
|
-
}
|
|
1788
|
-
// Execute JsonMap transformation
|
|
1789
|
-
try {
|
|
1790
|
-
const jsonMap = new jsonmap.JsonMap(mapDef, lib);
|
|
1791
|
-
const mapOutput = await jsonMap.transform(attributes);
|
|
1792
|
-
if (mapOutput &&
|
|
1793
|
-
typeof mapOutput === 'object' &&
|
|
1794
|
-
!Array.isArray(mapOutput)) {
|
|
1795
|
-
merged = { ...merged, ...mapOutput };
|
|
1796
|
-
}
|
|
1797
|
-
else {
|
|
1798
|
-
log.warn(`JsonMap transformation did not return an object; skipping merge.`);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
catch (error) {
|
|
1802
|
-
log.warn(`JsonMap transformation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
// Render template if present
|
|
1806
|
-
if (rule.template && templateEngine) {
|
|
1807
|
-
const templateKey = `rule-${String(ruleIndex)}`;
|
|
1808
|
-
// Build template context: attributes (with json spread at top) + map output
|
|
1809
|
-
const context = {
|
|
1810
|
-
...(attributes.json ?? {}),
|
|
1811
|
-
...attributes,
|
|
1812
|
-
...merged,
|
|
1813
|
-
};
|
|
1814
|
-
try {
|
|
1815
|
-
const result = templateEngine.render(templateKey, context);
|
|
1816
|
-
if (result && result.trim()) {
|
|
1817
|
-
renderedContent = result;
|
|
1818
|
-
}
|
|
1819
|
-
else {
|
|
1820
|
-
log.warn(`Template for rule ${String(ruleIndex)} rendered empty output. Falling back to raw content.`);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
catch (error) {
|
|
1824
|
-
log.warn(`Template render failed for rule ${String(ruleIndex)}: ${error instanceof Error ? error.message : String(error)}. Falling back to raw content.`);
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
return { metadata: merged, renderedContent };
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
1935
|
/**
|
|
1833
1936
|
* @module rules/attributes
|
|
1834
1937
|
* Builds file attribute objects for rule matching. Pure function: derives attributes from path, stats, and extracted data.
|
|
@@ -1919,14 +2022,14 @@ function compileRules(rules) {
|
|
|
1919
2022
|
* @param configDir - Optional config directory for resolving file paths.
|
|
1920
2023
|
* @returns The merged metadata and intermediate data.
|
|
1921
2024
|
*/
|
|
1922
|
-
async function buildMergedMetadata(filePath, compiledRules, metadataDir, maps, logger, templateEngine, configDir) {
|
|
2025
|
+
async function buildMergedMetadata(filePath, compiledRules, metadataDir, maps, logger, templateEngine, configDir, customMapLib) {
|
|
1923
2026
|
const ext = node_path.extname(filePath);
|
|
1924
2027
|
const stats = await promises.stat(filePath);
|
|
1925
2028
|
// 1. Extract text and structured data
|
|
1926
2029
|
const extracted = await extractText(filePath, ext);
|
|
1927
2030
|
// 2. Build attributes + apply rules
|
|
1928
2031
|
const attributes = buildAttributes(filePath, stats, extracted.frontmatter, extracted.json);
|
|
1929
|
-
const { metadata: inferred, renderedContent } = await applyRules(compiledRules, attributes, maps, logger, templateEngine, configDir);
|
|
2032
|
+
const { metadata: inferred, renderedContent } = await applyRules(compiledRules, attributes, maps, logger, templateEngine, configDir, customMapLib);
|
|
1930
2033
|
// 3. Read enrichment metadata (merge, enrichment wins)
|
|
1931
2034
|
const enrichment = await readMetadata(filePath, metadataDir);
|
|
1932
2035
|
const metadata = {
|
|
@@ -2039,7 +2142,7 @@ class DocumentProcessor {
|
|
|
2039
2142
|
try {
|
|
2040
2143
|
const ext = node_path.extname(filePath);
|
|
2041
2144
|
// 1. Build merged metadata + extract text
|
|
2042
|
-
const { metadata, extracted, renderedContent } = await buildMergedMetadata(filePath, this.compiledRules, this.config.metadataDir, this.config.maps, this.logger, this.templateEngine, this.config.configDir);
|
|
2145
|
+
const { metadata, extracted, renderedContent } = await buildMergedMetadata(filePath, this.compiledRules, this.config.metadataDir, this.config.maps, this.logger, this.templateEngine, this.config.configDir, this.config.customMapLib);
|
|
2043
2146
|
// Use rendered template content if available, otherwise raw extracted text
|
|
2044
2147
|
const textToEmbed = renderedContent ?? extracted.text;
|
|
2045
2148
|
if (!textToEmbed.trim()) {
|
|
@@ -2153,7 +2256,7 @@ class DocumentProcessor {
|
|
|
2153
2256
|
return null;
|
|
2154
2257
|
}
|
|
2155
2258
|
// Build merged metadata (lightweight — no embedding)
|
|
2156
|
-
const { metadata } = await buildMergedMetadata(filePath, this.compiledRules, this.config.metadataDir, this.config.maps, this.logger, this.templateEngine, this.config.configDir);
|
|
2259
|
+
const { metadata } = await buildMergedMetadata(filePath, this.compiledRules, this.config.metadataDir, this.config.maps, this.logger, this.templateEngine, this.config.configDir, this.config.customMapLib);
|
|
2157
2260
|
// Update all chunk payloads
|
|
2158
2261
|
const totalChunks = getChunkCount(existingPayload);
|
|
2159
2262
|
const ids = chunkIds(filePath, totalChunks);
|
|
@@ -2167,21 +2270,20 @@ class DocumentProcessor {
|
|
|
2167
2270
|
}
|
|
2168
2271
|
}
|
|
2169
2272
|
/**
|
|
2170
|
-
* Update compiled inference rules
|
|
2171
|
-
*
|
|
2172
|
-
* @param compiledRules - The newly compiled rules.
|
|
2173
|
-
*/
|
|
2174
|
-
/**
|
|
2175
|
-
* Update compiled inference rules and optionally the template engine.
|
|
2273
|
+
* Update compiled inference rules, template engine, and custom map lib.
|
|
2176
2274
|
*
|
|
2177
2275
|
* @param compiledRules - The newly compiled rules.
|
|
2178
2276
|
* @param templateEngine - Optional updated template engine.
|
|
2277
|
+
* @param customMapLib - Optional updated custom JsonMap lib functions.
|
|
2179
2278
|
*/
|
|
2180
|
-
updateRules(compiledRules, templateEngine) {
|
|
2279
|
+
updateRules(compiledRules, templateEngine, customMapLib) {
|
|
2181
2280
|
this.compiledRules = compiledRules;
|
|
2182
2281
|
if (templateEngine) {
|
|
2183
2282
|
this.templateEngine = templateEngine;
|
|
2184
2283
|
}
|
|
2284
|
+
if (customMapLib !== undefined) {
|
|
2285
|
+
this.config = { ...this.config, customMapLib };
|
|
2286
|
+
}
|
|
2185
2287
|
this.logger.info({ rules: compiledRules.length }, 'Inference rules updated');
|
|
2186
2288
|
}
|
|
2187
2289
|
}
|
|
@@ -3061,12 +3163,17 @@ class JeevesWatcher {
|
|
|
3061
3163
|
const compiledRules = this.factories.compileRules(this.config.inferenceRules ?? []);
|
|
3062
3164
|
const configDir = this.configPath ? node_path.dirname(this.configPath) : '.';
|
|
3063
3165
|
const templateEngine = await buildTemplateEngine(this.config.inferenceRules ?? [], this.config.templates, this.config.templateHelpers?.paths, configDir);
|
|
3166
|
+
// Load custom JsonMap lib functions
|
|
3167
|
+
const customMapLib = this.config.mapHelpers?.paths?.length && configDir
|
|
3168
|
+
? await loadCustomMapHelpers(this.config.mapHelpers.paths, configDir)
|
|
3169
|
+
: undefined;
|
|
3064
3170
|
const processor = this.factories.createDocumentProcessor({
|
|
3065
3171
|
metadataDir: this.config.metadataDir ?? '.jeeves-metadata',
|
|
3066
3172
|
chunkSize: this.config.embedding.chunkSize,
|
|
3067
3173
|
chunkOverlap: this.config.embedding.chunkOverlap,
|
|
3068
3174
|
maps: this.config.maps,
|
|
3069
3175
|
configDir,
|
|
3176
|
+
customMapLib,
|
|
3070
3177
|
}, embeddingProvider, vectorStore, compiledRules, logger, templateEngine);
|
|
3071
3178
|
this.processor = processor;
|
|
3072
3179
|
this.queue = this.factories.createEventQueue({
|
|
@@ -3185,7 +3292,10 @@ class JeevesWatcher {
|
|
|
3185
3292
|
const compiledRules = this.factories.compileRules(newConfig.inferenceRules ?? []);
|
|
3186
3293
|
const reloadConfigDir = node_path.dirname(this.configPath);
|
|
3187
3294
|
const newTemplateEngine = await buildTemplateEngine(newConfig.inferenceRules ?? [], newConfig.templates, newConfig.templateHelpers?.paths, reloadConfigDir);
|
|
3188
|
-
|
|
3295
|
+
const newCustomMapLib = newConfig.mapHelpers?.paths?.length && reloadConfigDir
|
|
3296
|
+
? await loadCustomMapHelpers(newConfig.mapHelpers.paths, reloadConfigDir)
|
|
3297
|
+
: undefined;
|
|
3298
|
+
processor.updateRules(compiledRules, newTemplateEngine, newCustomMapLib);
|
|
3189
3299
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
3190
3300
|
}
|
|
3191
3301
|
catch (error) {
|