@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/dist/mjs/index.js
CHANGED
|
@@ -2,10 +2,12 @@ import Fastify from 'fastify';
|
|
|
2
2
|
import { readdir, stat, rm, readFile, mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { resolve, dirname, join, relative, extname, basename } from 'node:path';
|
|
4
4
|
import picomatch from 'picomatch';
|
|
5
|
-
import { omit, capitalize, title, camel, snake, dash, isEqual
|
|
5
|
+
import { omit, get, capitalize, title, camel, snake, dash, isEqual } from 'radash';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { existsSync, statSync, readdirSync, readFileSync } from 'node:fs';
|
|
8
8
|
import ignore from 'ignore';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { JsonMap, jsonMapMapSchema } from '@karmaniverous/jsonmap';
|
|
9
11
|
import Handlebars from 'handlebars';
|
|
10
12
|
import dayjs from 'dayjs';
|
|
11
13
|
import { toMdast } from 'hast-util-to-mdast';
|
|
@@ -16,7 +18,6 @@ import { unified } from 'unified';
|
|
|
16
18
|
import chokidar from 'chokidar';
|
|
17
19
|
import { cosmiconfig } from 'cosmiconfig';
|
|
18
20
|
import { z, ZodError } from 'zod';
|
|
19
|
-
import { jsonMapMapSchema, JsonMap } from '@karmaniverous/jsonmap';
|
|
20
21
|
import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';
|
|
21
22
|
import pino from 'pino';
|
|
22
23
|
import { v5 } from 'uuid';
|
|
@@ -636,6 +637,251 @@ class GitignoreFilter {
|
|
|
636
637
|
}
|
|
637
638
|
}
|
|
638
639
|
|
|
640
|
+
/**
|
|
641
|
+
* @module rules/templates
|
|
642
|
+
* Resolves template variables (`${path.to.value}`) in rule `set` objects against file attributes.
|
|
643
|
+
*/
|
|
644
|
+
/**
|
|
645
|
+
* Resolve `${template.vars}` in a value against the given attributes.
|
|
646
|
+
*
|
|
647
|
+
* @param value - The value to resolve.
|
|
648
|
+
* @param attributes - The file attributes for variable lookup.
|
|
649
|
+
* @returns The resolved value.
|
|
650
|
+
*/
|
|
651
|
+
function resolveTemplateVars(value, attributes) {
|
|
652
|
+
if (typeof value !== 'string')
|
|
653
|
+
return value;
|
|
654
|
+
return value.replace(/\$\{([^}]+)\}/g, (_match, varPath) => {
|
|
655
|
+
const current = get(attributes, varPath);
|
|
656
|
+
if (current === null || current === undefined)
|
|
657
|
+
return '';
|
|
658
|
+
return typeof current === 'string' ? current : JSON.stringify(current);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Resolve all template variables in a `set` object.
|
|
663
|
+
*
|
|
664
|
+
* @param setObj - The key-value pairs to resolve.
|
|
665
|
+
* @param attributes - The file attributes for variable lookup.
|
|
666
|
+
* @returns The resolved key-value pairs.
|
|
667
|
+
*/
|
|
668
|
+
function resolveSet(setObj, attributes) {
|
|
669
|
+
const result = {};
|
|
670
|
+
for (const [key, value] of Object.entries(setObj)) {
|
|
671
|
+
result[key] = resolveTemplateVars(value, attributes);
|
|
672
|
+
}
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* @module rules/apply
|
|
678
|
+
* Applies compiled inference rules to file attributes, producing merged metadata via template resolution and JsonMap transforms.
|
|
679
|
+
*/
|
|
680
|
+
/**
|
|
681
|
+
* Create the lib object for JsonMap transformations.
|
|
682
|
+
*
|
|
683
|
+
* @param configDir - Optional config directory for resolving relative file paths in lookups.
|
|
684
|
+
* @returns The lib object.
|
|
685
|
+
*/
|
|
686
|
+
function createJsonMapLib(configDir, customLib) {
|
|
687
|
+
// Cache loaded JSON files within a single applyRules invocation.
|
|
688
|
+
const jsonCache = new Map();
|
|
689
|
+
const loadJson = (filePath) => {
|
|
690
|
+
const resolvedPath = configDir ? resolve(configDir, filePath) : filePath;
|
|
691
|
+
if (!jsonCache.has(resolvedPath)) {
|
|
692
|
+
const raw = readFileSync(resolvedPath, 'utf-8');
|
|
693
|
+
jsonCache.set(resolvedPath, JSON.parse(raw));
|
|
694
|
+
}
|
|
695
|
+
return jsonCache.get(resolvedPath);
|
|
696
|
+
};
|
|
697
|
+
return {
|
|
698
|
+
split: (str, separator) => str.split(separator),
|
|
699
|
+
slice: (arr, start, end) => arr.slice(start, end),
|
|
700
|
+
join: (arr, separator) => arr.join(separator),
|
|
701
|
+
toLowerCase: (str) => str.toLowerCase(),
|
|
702
|
+
replace: (str, search, replacement) => str.replace(search, replacement),
|
|
703
|
+
get: (obj, path) => get(obj, path),
|
|
704
|
+
/**
|
|
705
|
+
* Load a JSON file (relative to configDir) and look up a value by key,
|
|
706
|
+
* optionally drilling into a sub-path.
|
|
707
|
+
*
|
|
708
|
+
* @param filePath - Path to a JSON file (resolved relative to configDir).
|
|
709
|
+
* @param key - Top-level key to look up.
|
|
710
|
+
* @param field - Optional dot-path into the looked-up entry.
|
|
711
|
+
* @returns The resolved value, or null if not found.
|
|
712
|
+
*/
|
|
713
|
+
lookupJson: (filePath, key, field) => {
|
|
714
|
+
const data = loadJson(filePath);
|
|
715
|
+
const entry = data[key];
|
|
716
|
+
if (entry === undefined || entry === null)
|
|
717
|
+
return null;
|
|
718
|
+
if (field)
|
|
719
|
+
return get(entry, field) ?? null;
|
|
720
|
+
return entry;
|
|
721
|
+
},
|
|
722
|
+
/**
|
|
723
|
+
* Map an array of keys through a JSON lookup file, collecting a specific
|
|
724
|
+
* field from each matching entry. Non-matching keys are silently skipped.
|
|
725
|
+
* Array-valued fields are flattened into the result.
|
|
726
|
+
*
|
|
727
|
+
* @param filePath - Path to a JSON file (resolved relative to configDir).
|
|
728
|
+
* @param keys - Array of top-level keys to look up.
|
|
729
|
+
* @param field - Dot-path into each looked-up entry.
|
|
730
|
+
* @returns Flat array of resolved values.
|
|
731
|
+
*/
|
|
732
|
+
mapLookup: (filePath, keys, field) => {
|
|
733
|
+
if (!Array.isArray(keys))
|
|
734
|
+
return [];
|
|
735
|
+
const data = loadJson(filePath);
|
|
736
|
+
const results = [];
|
|
737
|
+
for (const k of keys) {
|
|
738
|
+
if (typeof k !== 'string')
|
|
739
|
+
continue;
|
|
740
|
+
const entry = data[k];
|
|
741
|
+
if (entry === undefined || entry === null)
|
|
742
|
+
continue;
|
|
743
|
+
const val = get(entry, field);
|
|
744
|
+
if (val === undefined || val === null)
|
|
745
|
+
continue;
|
|
746
|
+
if (Array.isArray(val)) {
|
|
747
|
+
for (const item of val) {
|
|
748
|
+
results.push(item);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
results.push(val);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return results;
|
|
756
|
+
},
|
|
757
|
+
...customLib,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Load custom JsonMap lib functions from file paths.
|
|
762
|
+
*
|
|
763
|
+
* Each module should default-export an object of functions,
|
|
764
|
+
* or use named exports. Only function-valued exports are merged.
|
|
765
|
+
*
|
|
766
|
+
* @param paths - File paths to custom lib modules.
|
|
767
|
+
* @param configDir - Directory to resolve relative paths against.
|
|
768
|
+
* @returns The merged custom lib functions.
|
|
769
|
+
*/
|
|
770
|
+
async function loadCustomMapHelpers(paths, configDir) {
|
|
771
|
+
const merged = {};
|
|
772
|
+
for (const p of paths) {
|
|
773
|
+
const resolved = resolve(configDir, p);
|
|
774
|
+
const mod = (await import(pathToFileURL(resolved).href));
|
|
775
|
+
const fns = typeof mod.default === 'object' && mod.default !== null
|
|
776
|
+
? mod.default
|
|
777
|
+
: mod;
|
|
778
|
+
for (const [key, val] of Object.entries(fns)) {
|
|
779
|
+
if (typeof val === 'function') {
|
|
780
|
+
merged[key] = val;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return merged;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Apply compiled inference rules to file attributes, returning merged metadata and optional rendered content.
|
|
788
|
+
*
|
|
789
|
+
* Rules are evaluated in order; later rules override earlier ones.
|
|
790
|
+
* If a rule has a `map`, the JsonMap transformation is applied after `set` resolution,
|
|
791
|
+
* and map output overrides set output on conflict.
|
|
792
|
+
*
|
|
793
|
+
* @param compiledRules - The compiled rules to evaluate.
|
|
794
|
+
* @param attributes - The file attributes to match against.
|
|
795
|
+
* @param namedMaps - Optional record of named JsonMap definitions.
|
|
796
|
+
* @param logger - Optional logger for warnings (falls back to console.warn).
|
|
797
|
+
* @param templateEngine - Optional template engine for rendering content templates.
|
|
798
|
+
* @param configDir - Optional config directory for resolving .json map file paths.
|
|
799
|
+
* @returns The merged metadata and optional rendered content.
|
|
800
|
+
*/
|
|
801
|
+
async function applyRules(compiledRules, attributes, namedMaps, logger, templateEngine, configDir, customMapLib) {
|
|
802
|
+
// JsonMap's type definitions expect a generic JsonMapLib shape with unary functions.
|
|
803
|
+
// Our helper functions accept multiple args, which JsonMap supports at runtime.
|
|
804
|
+
const lib = createJsonMapLib(configDir, customMapLib);
|
|
805
|
+
let merged = {};
|
|
806
|
+
let renderedContent = null;
|
|
807
|
+
const log = logger ?? console;
|
|
808
|
+
for (const [ruleIndex, { rule, validate }] of compiledRules.entries()) {
|
|
809
|
+
if (validate(attributes)) {
|
|
810
|
+
// Apply set resolution
|
|
811
|
+
const setOutput = resolveSet(rule.set, attributes);
|
|
812
|
+
merged = { ...merged, ...setOutput };
|
|
813
|
+
// Apply map transformation if present
|
|
814
|
+
if (rule.map) {
|
|
815
|
+
let mapDef;
|
|
816
|
+
// Resolve map reference
|
|
817
|
+
if (typeof rule.map === 'string') {
|
|
818
|
+
if (rule.map.endsWith('.json') && configDir) {
|
|
819
|
+
// File path: load from .json file
|
|
820
|
+
try {
|
|
821
|
+
const mapPath = resolve(configDir, rule.map);
|
|
822
|
+
const raw = readFileSync(mapPath, 'utf-8');
|
|
823
|
+
mapDef = JSON.parse(raw);
|
|
824
|
+
}
|
|
825
|
+
catch (error) {
|
|
826
|
+
log.warn(`Failed to load map file "${rule.map}": ${error instanceof Error ? error.message : String(error)}`);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
mapDef = namedMaps?.[rule.map];
|
|
832
|
+
if (!mapDef) {
|
|
833
|
+
log.warn(`Map reference "${rule.map}" not found in named maps. Skipping map transformation.`);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
mapDef = rule.map;
|
|
840
|
+
}
|
|
841
|
+
// Execute JsonMap transformation
|
|
842
|
+
try {
|
|
843
|
+
const jsonMap = new JsonMap(mapDef, lib);
|
|
844
|
+
const mapOutput = await jsonMap.transform(attributes);
|
|
845
|
+
if (mapOutput &&
|
|
846
|
+
typeof mapOutput === 'object' &&
|
|
847
|
+
!Array.isArray(mapOutput)) {
|
|
848
|
+
merged = { ...merged, ...mapOutput };
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
log.warn(`JsonMap transformation did not return an object; skipping merge.`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
log.warn(`JsonMap transformation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
// Render template if present
|
|
859
|
+
if (rule.template && templateEngine) {
|
|
860
|
+
const templateKey = `rule-${String(ruleIndex)}`;
|
|
861
|
+
// Build template context: attributes (with json spread at top) + map output
|
|
862
|
+
const context = {
|
|
863
|
+
...(attributes.json ?? {}),
|
|
864
|
+
...attributes,
|
|
865
|
+
...merged,
|
|
866
|
+
};
|
|
867
|
+
try {
|
|
868
|
+
const result = templateEngine.render(templateKey, context);
|
|
869
|
+
if (result && result.trim()) {
|
|
870
|
+
renderedContent = result;
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
log.warn(`Template for rule ${String(ruleIndex)} rendered empty output. Falling back to raw content.`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
catch (error) {
|
|
877
|
+
log.warn(`Template render failed for rule ${String(ruleIndex)}: ${error instanceof Error ? error.message : String(error)}. Falling back to raw content.`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return { metadata: merged, renderedContent };
|
|
883
|
+
}
|
|
884
|
+
|
|
639
885
|
/**
|
|
640
886
|
* @module templates/helpers
|
|
641
887
|
* Registers built-in Handlebars helpers for content templates.
|
|
@@ -773,7 +1019,7 @@ function createHandlebarsInstance() {
|
|
|
773
1019
|
async function loadCustomHelpers(hbs, paths, configDir) {
|
|
774
1020
|
for (const p of paths) {
|
|
775
1021
|
const resolved = resolve(configDir, p);
|
|
776
|
-
const mod = (await import(resolved));
|
|
1022
|
+
const mod = (await import(pathToFileURL(resolved).href));
|
|
777
1023
|
if (typeof mod.default === 'function') {
|
|
778
1024
|
mod.default(hbs);
|
|
779
1025
|
}
|
|
@@ -1164,6 +1410,17 @@ const jeevesWatcherConfigSchema = z.object({
|
|
|
1164
1410
|
})
|
|
1165
1411
|
.optional()
|
|
1166
1412
|
.describe('Custom Handlebars helper registration.'),
|
|
1413
|
+
/** Custom JsonMap lib function registration. */
|
|
1414
|
+
mapHelpers: z
|
|
1415
|
+
.object({
|
|
1416
|
+
/** File paths to custom lib modules (each exports functions to merge into the JsonMap lib). */
|
|
1417
|
+
paths: z
|
|
1418
|
+
.array(z.string())
|
|
1419
|
+
.optional()
|
|
1420
|
+
.describe('File paths to JS modules exporting functions to merge into the JsonMap lib.'),
|
|
1421
|
+
})
|
|
1422
|
+
.optional()
|
|
1423
|
+
.describe('Custom JsonMap lib function registration.'),
|
|
1167
1424
|
/** Logging configuration. */
|
|
1168
1425
|
logging: loggingConfigSchema.optional().describe('Logging configuration.'),
|
|
1169
1426
|
/** Timeout in milliseconds for graceful shutdown. */
|
|
@@ -1654,160 +1911,6 @@ async function extractText(filePath, extension) {
|
|
|
1654
1911
|
return extractPlaintext(filePath);
|
|
1655
1912
|
}
|
|
1656
1913
|
|
|
1657
|
-
/**
|
|
1658
|
-
* @module rules/templates
|
|
1659
|
-
* Resolves template variables (`${path.to.value}`) in rule `set` objects against file attributes.
|
|
1660
|
-
*/
|
|
1661
|
-
/**
|
|
1662
|
-
* Resolve `${template.vars}` in a value against the given attributes.
|
|
1663
|
-
*
|
|
1664
|
-
* @param value - The value to resolve.
|
|
1665
|
-
* @param attributes - The file attributes for variable lookup.
|
|
1666
|
-
* @returns The resolved value.
|
|
1667
|
-
*/
|
|
1668
|
-
function resolveTemplateVars(value, attributes) {
|
|
1669
|
-
if (typeof value !== 'string')
|
|
1670
|
-
return value;
|
|
1671
|
-
return value.replace(/\$\{([^}]+)\}/g, (_match, varPath) => {
|
|
1672
|
-
const current = get(attributes, varPath);
|
|
1673
|
-
if (current === null || current === undefined)
|
|
1674
|
-
return '';
|
|
1675
|
-
return typeof current === 'string' ? current : JSON.stringify(current);
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
/**
|
|
1679
|
-
* Resolve all template variables in a `set` object.
|
|
1680
|
-
*
|
|
1681
|
-
* @param setObj - The key-value pairs to resolve.
|
|
1682
|
-
* @param attributes - The file attributes for variable lookup.
|
|
1683
|
-
* @returns The resolved key-value pairs.
|
|
1684
|
-
*/
|
|
1685
|
-
function resolveSet(setObj, attributes) {
|
|
1686
|
-
const result = {};
|
|
1687
|
-
for (const [key, value] of Object.entries(setObj)) {
|
|
1688
|
-
result[key] = resolveTemplateVars(value, attributes);
|
|
1689
|
-
}
|
|
1690
|
-
return result;
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
/**
|
|
1694
|
-
* @module rules/apply
|
|
1695
|
-
* Applies compiled inference rules to file attributes, producing merged metadata via template resolution and JsonMap transforms.
|
|
1696
|
-
*/
|
|
1697
|
-
/**
|
|
1698
|
-
* Create the lib object for JsonMap transformations.
|
|
1699
|
-
*
|
|
1700
|
-
* @returns The lib object.
|
|
1701
|
-
*/
|
|
1702
|
-
function createJsonMapLib() {
|
|
1703
|
-
return {
|
|
1704
|
-
split: (str, separator) => str.split(separator),
|
|
1705
|
-
slice: (arr, start, end) => arr.slice(start, end),
|
|
1706
|
-
join: (arr, separator) => arr.join(separator),
|
|
1707
|
-
toLowerCase: (str) => str.toLowerCase(),
|
|
1708
|
-
replace: (str, search, replacement) => str.replace(search, replacement),
|
|
1709
|
-
get: (obj, path) => get(obj, path),
|
|
1710
|
-
};
|
|
1711
|
-
}
|
|
1712
|
-
/**
|
|
1713
|
-
* Apply compiled inference rules to file attributes, returning merged metadata and optional rendered content.
|
|
1714
|
-
*
|
|
1715
|
-
* Rules are evaluated in order; later rules override earlier ones.
|
|
1716
|
-
* If a rule has a `map`, the JsonMap transformation is applied after `set` resolution,
|
|
1717
|
-
* and map output overrides set output on conflict.
|
|
1718
|
-
*
|
|
1719
|
-
* @param compiledRules - The compiled rules to evaluate.
|
|
1720
|
-
* @param attributes - The file attributes to match against.
|
|
1721
|
-
* @param namedMaps - Optional record of named JsonMap definitions.
|
|
1722
|
-
* @param logger - Optional logger for warnings (falls back to console.warn).
|
|
1723
|
-
* @param templateEngine - Optional template engine for rendering content templates.
|
|
1724
|
-
* @param configDir - Optional config directory for resolving .json map file paths.
|
|
1725
|
-
* @returns The merged metadata and optional rendered content.
|
|
1726
|
-
*/
|
|
1727
|
-
async function applyRules(compiledRules, attributes, namedMaps, logger, templateEngine, configDir) {
|
|
1728
|
-
// JsonMap's type definitions expect a generic JsonMapLib shape with unary functions.
|
|
1729
|
-
// Our helper functions accept multiple args, which JsonMap supports at runtime.
|
|
1730
|
-
const lib = createJsonMapLib();
|
|
1731
|
-
let merged = {};
|
|
1732
|
-
let renderedContent = null;
|
|
1733
|
-
const log = logger ?? console;
|
|
1734
|
-
for (const [ruleIndex, { rule, validate }] of compiledRules.entries()) {
|
|
1735
|
-
if (validate(attributes)) {
|
|
1736
|
-
// Apply set resolution
|
|
1737
|
-
const setOutput = resolveSet(rule.set, attributes);
|
|
1738
|
-
merged = { ...merged, ...setOutput };
|
|
1739
|
-
// Apply map transformation if present
|
|
1740
|
-
if (rule.map) {
|
|
1741
|
-
let mapDef;
|
|
1742
|
-
// Resolve map reference
|
|
1743
|
-
if (typeof rule.map === 'string') {
|
|
1744
|
-
if (rule.map.endsWith('.json') && configDir) {
|
|
1745
|
-
// File path: load from .json file
|
|
1746
|
-
try {
|
|
1747
|
-
const mapPath = resolve(configDir, rule.map);
|
|
1748
|
-
const raw = readFileSync(mapPath, 'utf-8');
|
|
1749
|
-
mapDef = JSON.parse(raw);
|
|
1750
|
-
}
|
|
1751
|
-
catch (error) {
|
|
1752
|
-
log.warn(`Failed to load map file "${rule.map}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1753
|
-
continue;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
else {
|
|
1757
|
-
mapDef = namedMaps?.[rule.map];
|
|
1758
|
-
if (!mapDef) {
|
|
1759
|
-
log.warn(`Map reference "${rule.map}" not found in named maps. Skipping map transformation.`);
|
|
1760
|
-
continue;
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
else {
|
|
1765
|
-
mapDef = rule.map;
|
|
1766
|
-
}
|
|
1767
|
-
// Execute JsonMap transformation
|
|
1768
|
-
try {
|
|
1769
|
-
const jsonMap = new JsonMap(mapDef, lib);
|
|
1770
|
-
const mapOutput = await jsonMap.transform(attributes);
|
|
1771
|
-
if (mapOutput &&
|
|
1772
|
-
typeof mapOutput === 'object' &&
|
|
1773
|
-
!Array.isArray(mapOutput)) {
|
|
1774
|
-
merged = { ...merged, ...mapOutput };
|
|
1775
|
-
}
|
|
1776
|
-
else {
|
|
1777
|
-
log.warn(`JsonMap transformation did not return an object; skipping merge.`);
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
catch (error) {
|
|
1781
|
-
log.warn(`JsonMap transformation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
// Render template if present
|
|
1785
|
-
if (rule.template && templateEngine) {
|
|
1786
|
-
const templateKey = `rule-${String(ruleIndex)}`;
|
|
1787
|
-
// Build template context: attributes (with json spread at top) + map output
|
|
1788
|
-
const context = {
|
|
1789
|
-
...(attributes.json ?? {}),
|
|
1790
|
-
...attributes,
|
|
1791
|
-
...merged,
|
|
1792
|
-
};
|
|
1793
|
-
try {
|
|
1794
|
-
const result = templateEngine.render(templateKey, context);
|
|
1795
|
-
if (result && result.trim()) {
|
|
1796
|
-
renderedContent = result;
|
|
1797
|
-
}
|
|
1798
|
-
else {
|
|
1799
|
-
log.warn(`Template for rule ${String(ruleIndex)} rendered empty output. Falling back to raw content.`);
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
catch (error) {
|
|
1803
|
-
log.warn(`Template render failed for rule ${String(ruleIndex)}: ${error instanceof Error ? error.message : String(error)}. Falling back to raw content.`);
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
return { metadata: merged, renderedContent };
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
1914
|
/**
|
|
1812
1915
|
* @module rules/attributes
|
|
1813
1916
|
* Builds file attribute objects for rule matching. Pure function: derives attributes from path, stats, and extracted data.
|
|
@@ -1898,14 +2001,14 @@ function compileRules(rules) {
|
|
|
1898
2001
|
* @param configDir - Optional config directory for resolving file paths.
|
|
1899
2002
|
* @returns The merged metadata and intermediate data.
|
|
1900
2003
|
*/
|
|
1901
|
-
async function buildMergedMetadata(filePath, compiledRules, metadataDir, maps, logger, templateEngine, configDir) {
|
|
2004
|
+
async function buildMergedMetadata(filePath, compiledRules, metadataDir, maps, logger, templateEngine, configDir, customMapLib) {
|
|
1902
2005
|
const ext = extname(filePath);
|
|
1903
2006
|
const stats = await stat(filePath);
|
|
1904
2007
|
// 1. Extract text and structured data
|
|
1905
2008
|
const extracted = await extractText(filePath, ext);
|
|
1906
2009
|
// 2. Build attributes + apply rules
|
|
1907
2010
|
const attributes = buildAttributes(filePath, stats, extracted.frontmatter, extracted.json);
|
|
1908
|
-
const { metadata: inferred, renderedContent } = await applyRules(compiledRules, attributes, maps, logger, templateEngine, configDir);
|
|
2011
|
+
const { metadata: inferred, renderedContent } = await applyRules(compiledRules, attributes, maps, logger, templateEngine, configDir, customMapLib);
|
|
1909
2012
|
// 3. Read enrichment metadata (merge, enrichment wins)
|
|
1910
2013
|
const enrichment = await readMetadata(filePath, metadataDir);
|
|
1911
2014
|
const metadata = {
|
|
@@ -2018,7 +2121,7 @@ class DocumentProcessor {
|
|
|
2018
2121
|
try {
|
|
2019
2122
|
const ext = extname(filePath);
|
|
2020
2123
|
// 1. Build merged metadata + extract text
|
|
2021
|
-
const { metadata, extracted, renderedContent } = await buildMergedMetadata(filePath, this.compiledRules, this.config.metadataDir, this.config.maps, this.logger, this.templateEngine, this.config.configDir);
|
|
2124
|
+
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);
|
|
2022
2125
|
// Use rendered template content if available, otherwise raw extracted text
|
|
2023
2126
|
const textToEmbed = renderedContent ?? extracted.text;
|
|
2024
2127
|
if (!textToEmbed.trim()) {
|
|
@@ -2132,7 +2235,7 @@ class DocumentProcessor {
|
|
|
2132
2235
|
return null;
|
|
2133
2236
|
}
|
|
2134
2237
|
// Build merged metadata (lightweight — no embedding)
|
|
2135
|
-
const { metadata } = await buildMergedMetadata(filePath, this.compiledRules, this.config.metadataDir, this.config.maps, this.logger, this.templateEngine, this.config.configDir);
|
|
2238
|
+
const { metadata } = await buildMergedMetadata(filePath, this.compiledRules, this.config.metadataDir, this.config.maps, this.logger, this.templateEngine, this.config.configDir, this.config.customMapLib);
|
|
2136
2239
|
// Update all chunk payloads
|
|
2137
2240
|
const totalChunks = getChunkCount(existingPayload);
|
|
2138
2241
|
const ids = chunkIds(filePath, totalChunks);
|
|
@@ -2146,21 +2249,20 @@ class DocumentProcessor {
|
|
|
2146
2249
|
}
|
|
2147
2250
|
}
|
|
2148
2251
|
/**
|
|
2149
|
-
* Update compiled inference rules
|
|
2150
|
-
*
|
|
2151
|
-
* @param compiledRules - The newly compiled rules.
|
|
2152
|
-
*/
|
|
2153
|
-
/**
|
|
2154
|
-
* Update compiled inference rules and optionally the template engine.
|
|
2252
|
+
* Update compiled inference rules, template engine, and custom map lib.
|
|
2155
2253
|
*
|
|
2156
2254
|
* @param compiledRules - The newly compiled rules.
|
|
2157
2255
|
* @param templateEngine - Optional updated template engine.
|
|
2256
|
+
* @param customMapLib - Optional updated custom JsonMap lib functions.
|
|
2158
2257
|
*/
|
|
2159
|
-
updateRules(compiledRules, templateEngine) {
|
|
2258
|
+
updateRules(compiledRules, templateEngine, customMapLib) {
|
|
2160
2259
|
this.compiledRules = compiledRules;
|
|
2161
2260
|
if (templateEngine) {
|
|
2162
2261
|
this.templateEngine = templateEngine;
|
|
2163
2262
|
}
|
|
2263
|
+
if (customMapLib !== undefined) {
|
|
2264
|
+
this.config = { ...this.config, customMapLib };
|
|
2265
|
+
}
|
|
2164
2266
|
this.logger.info({ rules: compiledRules.length }, 'Inference rules updated');
|
|
2165
2267
|
}
|
|
2166
2268
|
}
|
|
@@ -3040,12 +3142,17 @@ class JeevesWatcher {
|
|
|
3040
3142
|
const compiledRules = this.factories.compileRules(this.config.inferenceRules ?? []);
|
|
3041
3143
|
const configDir = this.configPath ? dirname(this.configPath) : '.';
|
|
3042
3144
|
const templateEngine = await buildTemplateEngine(this.config.inferenceRules ?? [], this.config.templates, this.config.templateHelpers?.paths, configDir);
|
|
3145
|
+
// Load custom JsonMap lib functions
|
|
3146
|
+
const customMapLib = this.config.mapHelpers?.paths?.length && configDir
|
|
3147
|
+
? await loadCustomMapHelpers(this.config.mapHelpers.paths, configDir)
|
|
3148
|
+
: undefined;
|
|
3043
3149
|
const processor = this.factories.createDocumentProcessor({
|
|
3044
3150
|
metadataDir: this.config.metadataDir ?? '.jeeves-metadata',
|
|
3045
3151
|
chunkSize: this.config.embedding.chunkSize,
|
|
3046
3152
|
chunkOverlap: this.config.embedding.chunkOverlap,
|
|
3047
3153
|
maps: this.config.maps,
|
|
3048
3154
|
configDir,
|
|
3155
|
+
customMapLib,
|
|
3049
3156
|
}, embeddingProvider, vectorStore, compiledRules, logger, templateEngine);
|
|
3050
3157
|
this.processor = processor;
|
|
3051
3158
|
this.queue = this.factories.createEventQueue({
|
|
@@ -3164,7 +3271,10 @@ class JeevesWatcher {
|
|
|
3164
3271
|
const compiledRules = this.factories.compileRules(newConfig.inferenceRules ?? []);
|
|
3165
3272
|
const reloadConfigDir = dirname(this.configPath);
|
|
3166
3273
|
const newTemplateEngine = await buildTemplateEngine(newConfig.inferenceRules ?? [], newConfig.templates, newConfig.templateHelpers?.paths, reloadConfigDir);
|
|
3167
|
-
|
|
3274
|
+
const newCustomMapLib = newConfig.mapHelpers?.paths?.length && reloadConfigDir
|
|
3275
|
+
? await loadCustomMapHelpers(newConfig.mapHelpers.paths, reloadConfigDir)
|
|
3276
|
+
: undefined;
|
|
3277
|
+
processor.updateRules(compiledRules, newTemplateEngine, newCustomMapLib);
|
|
3168
3278
|
logger.info({ configPath: this.configPath, rules: compiledRules.length }, 'Config reloaded');
|
|
3169
3279
|
}
|
|
3170
3280
|
catch (error) {
|
package/package.json
CHANGED