@karmaniverous/jeeves-meta 0.3.1 → 0.3.3

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/dist/cli.js CHANGED
@@ -48,20 +48,20 @@ const synthConfigSchema = z.object({
48
48
  /** Number of metas to synthesize per invocation. */
49
49
  batchSize: z.number().int().min(1).default(1),
50
50
  /**
51
- * Watcher metadata properties for live .meta/meta.json files.
52
- * Virtual rules use these to tag live metas; scan queries derive
53
- * their filter from the first domain value.
51
+ * Watcher metadata properties applied to live .meta/meta.json files.
52
+ * Virtual rules set these on every indexed live meta point.
53
+ * Discovery filter is derived from these properties.
54
+ * Default: `\{ _meta: "current" \}`
54
55
  */
55
- metaProperty: z
56
- .object({ domains: z.array(z.string()).min(1) })
57
- .default({ domains: ['meta'] }),
56
+ metaProperty: z.record(z.string(), z.unknown()).default({ _meta: 'current' }),
58
57
  /**
59
- * Watcher metadata properties for .meta/archive/** snapshots.
60
- * Virtual rules use these to tag archive files.
58
+ * Watcher metadata properties applied to .meta/archive/** snapshots.
59
+ * Virtual rules set these on every indexed archive point.
60
+ * Default: `\{ _meta: "archive" \}`
61
61
  */
62
62
  metaArchiveProperty: z
63
- .object({ domains: z.array(z.string()).min(1) })
64
- .default({ domains: ['meta-archive'] }),
63
+ .record(z.string(), z.unknown())
64
+ .default({ _meta: 'archive' }),
65
65
  });
66
66
 
67
67
  /**
@@ -373,21 +373,50 @@ async function paginatedScan(watcher, params) {
373
373
  *
374
374
  * @module discovery/discoverMetas
375
375
  */
376
+ /**
377
+ * Build a single Qdrant filter clause from a key-value pair.
378
+ *
379
+ * Arrays use `match.value` on the first element (Qdrant array membership).
380
+ * Scalars (string, number, boolean) use `match.value` directly.
381
+ * Objects and other non-filterable types are skipped with a warning.
382
+ */
383
+ function buildMatchClause(key, value) {
384
+ if (Array.isArray(value)) {
385
+ if (value.length === 0)
386
+ return null;
387
+ return { key, match: { value: value[0] } };
388
+ }
389
+ if (typeof value === 'string' ||
390
+ typeof value === 'number' ||
391
+ typeof value === 'boolean') {
392
+ return { key, match: { value } };
393
+ }
394
+ // Non-filterable value (object, null, etc.) — valid for tagging but
395
+ // cannot be expressed as a Qdrant match clause.
396
+ return null;
397
+ }
376
398
  /**
377
399
  * Build a Qdrant filter from config metaProperty.
378
400
  *
401
+ * Iterates all key-value pairs in `metaProperty` (a generic record)
402
+ * to construct `must` clauses. Always appends `file_path: meta.json`
403
+ * for deduplication.
404
+ *
379
405
  * @param config - Synth config with metaProperty.
380
406
  * @returns Qdrant filter object for scanning live metas.
381
407
  */
382
408
  function buildMetaFilter(config) {
383
- return {
384
- must: [
385
- {
386
- key: 'domains',
387
- match: { value: config.metaProperty.domains[0] },
388
- },
389
- ],
390
- };
409
+ const must = [];
410
+ for (const [key, value] of Object.entries(config.metaProperty)) {
411
+ const clause = buildMatchClause(key, value);
412
+ if (clause)
413
+ must.push(clause);
414
+ }
415
+ must.push({
416
+ key: 'file_path',
417
+ match: { text: 'meta.json' },
418
+ });
419
+ return { must };
391
420
  }
392
421
  /**
393
422
  * Discover all .meta/ directories via watcher scan.
@@ -405,16 +434,16 @@ async function discoverMetas(config, watcher) {
405
434
  filter,
406
435
  fields: ['file_path'],
407
436
  });
408
- // Deduplicate by file_path (multi-chunk files)
437
+ // Deduplicate by .meta/ directory path (handles multi-chunk files)
409
438
  const seen = new Set();
410
439
  const metaPaths = [];
411
440
  for (const sf of scanFiles) {
412
441
  const fp = normalizePath$1(sf.file_path);
413
- if (seen.has(fp))
414
- continue;
415
- seen.add(fp);
416
442
  // Derive .meta/ directory from file_path (strip /meta.json)
417
443
  const metaPath = fp.replace(/\/meta\.json$/, '');
444
+ if (seen.has(metaPath))
445
+ continue;
446
+ seen.add(metaPath);
418
447
  metaPaths.push(metaPath);
419
448
  }
420
449
  return metaPaths;
@@ -1471,6 +1500,8 @@ async function orchestrateOnce(config, executor, watcher, targetPath) {
1471
1500
  const candidates = [];
1472
1501
  for (const node of tree.nodes.values()) {
1473
1502
  const meta = metas.get(node.metaPath);
1503
+ if (!meta)
1504
+ continue; // Node not in metas map (e.g. unreadable meta.json)
1474
1505
  const staleness = actualStaleness(meta);
1475
1506
  if (staleness > 0) {
1476
1507
  candidates.push({ node, meta, actualStaleness: staleness });
package/dist/index.d.ts CHANGED
@@ -53,12 +53,8 @@ declare const synthConfigSchema: z.ZodObject<{
53
53
  defaultCritic: z.ZodString;
54
54
  skipUnchanged: z.ZodDefault<z.ZodBoolean>;
55
55
  batchSize: z.ZodDefault<z.ZodNumber>;
56
- metaProperty: z.ZodDefault<z.ZodObject<{
57
- domains: z.ZodArray<z.ZodString>;
58
- }, z.core.$strip>>;
59
- metaArchiveProperty: z.ZodDefault<z.ZodObject<{
60
- domains: z.ZodArray<z.ZodString>;
61
- }, z.core.$strip>>;
56
+ metaProperty: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
57
+ metaArchiveProperty: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
62
58
  }, z.core.$strip>;
63
59
  /** Inferred type for jeeves-meta configuration. */
64
60
  type SynthConfig = z.infer<typeof synthConfigSchema>;
@@ -347,6 +343,10 @@ interface WatcherClient {
347
343
  /**
348
344
  * Build a Qdrant filter from config metaProperty.
349
345
  *
346
+ * Iterates all key-value pairs in `metaProperty` (a generic record)
347
+ * to construct `must` clauses. Always appends `file_path: meta.json`
348
+ * for deduplication.
349
+ *
350
350
  * @param config - Synth config with metaProperty.
351
351
  * @returns Qdrant filter object for scanning live metas.
352
352
  */
package/dist/index.js CHANGED
@@ -143,20 +143,20 @@ const synthConfigSchema = z.object({
143
143
  /** Number of metas to synthesize per invocation. */
144
144
  batchSize: z.number().int().min(1).default(1),
145
145
  /**
146
- * Watcher metadata properties for live .meta/meta.json files.
147
- * Virtual rules use these to tag live metas; scan queries derive
148
- * their filter from the first domain value.
146
+ * Watcher metadata properties applied to live .meta/meta.json files.
147
+ * Virtual rules set these on every indexed live meta point.
148
+ * Discovery filter is derived from these properties.
149
+ * Default: `\{ _meta: "current" \}`
149
150
  */
150
- metaProperty: z
151
- .object({ domains: z.array(z.string()).min(1) })
152
- .default({ domains: ['meta'] }),
151
+ metaProperty: z.record(z.string(), z.unknown()).default({ _meta: 'current' }),
153
152
  /**
154
- * Watcher metadata properties for .meta/archive/** snapshots.
155
- * Virtual rules use these to tag archive files.
153
+ * Watcher metadata properties applied to .meta/archive/** snapshots.
154
+ * Virtual rules set these on every indexed archive point.
155
+ * Default: `\{ _meta: "archive" \}`
156
156
  */
157
157
  metaArchiveProperty: z
158
- .object({ domains: z.array(z.string()).min(1) })
159
- .default({ domains: ['meta-archive'] }),
158
+ .record(z.string(), z.unknown())
159
+ .default({ _meta: 'archive' }),
160
160
  });
161
161
 
162
162
  /**
@@ -358,21 +358,50 @@ async function paginatedScan(watcher, params) {
358
358
  *
359
359
  * @module discovery/discoverMetas
360
360
  */
361
+ /**
362
+ * Build a single Qdrant filter clause from a key-value pair.
363
+ *
364
+ * Arrays use `match.value` on the first element (Qdrant array membership).
365
+ * Scalars (string, number, boolean) use `match.value` directly.
366
+ * Objects and other non-filterable types are skipped with a warning.
367
+ */
368
+ function buildMatchClause(key, value) {
369
+ if (Array.isArray(value)) {
370
+ if (value.length === 0)
371
+ return null;
372
+ return { key, match: { value: value[0] } };
373
+ }
374
+ if (typeof value === 'string' ||
375
+ typeof value === 'number' ||
376
+ typeof value === 'boolean') {
377
+ return { key, match: { value } };
378
+ }
379
+ // Non-filterable value (object, null, etc.) — valid for tagging but
380
+ // cannot be expressed as a Qdrant match clause.
381
+ return null;
382
+ }
361
383
  /**
362
384
  * Build a Qdrant filter from config metaProperty.
363
385
  *
386
+ * Iterates all key-value pairs in `metaProperty` (a generic record)
387
+ * to construct `must` clauses. Always appends `file_path: meta.json`
388
+ * for deduplication.
389
+ *
364
390
  * @param config - Synth config with metaProperty.
365
391
  * @returns Qdrant filter object for scanning live metas.
366
392
  */
367
393
  function buildMetaFilter(config) {
368
- return {
369
- must: [
370
- {
371
- key: 'domains',
372
- match: { value: config.metaProperty.domains[0] },
373
- },
374
- ],
375
- };
394
+ const must = [];
395
+ for (const [key, value] of Object.entries(config.metaProperty)) {
396
+ const clause = buildMatchClause(key, value);
397
+ if (clause)
398
+ must.push(clause);
399
+ }
400
+ must.push({
401
+ key: 'file_path',
402
+ match: { text: 'meta.json' },
403
+ });
404
+ return { must };
376
405
  }
377
406
  /**
378
407
  * Discover all .meta/ directories via watcher scan.
@@ -390,16 +419,16 @@ async function discoverMetas(config, watcher) {
390
419
  filter,
391
420
  fields: ['file_path'],
392
421
  });
393
- // Deduplicate by file_path (multi-chunk files)
422
+ // Deduplicate by .meta/ directory path (handles multi-chunk files)
394
423
  const seen = new Set();
395
424
  const metaPaths = [];
396
425
  for (const sf of scanFiles) {
397
426
  const fp = normalizePath$1(sf.file_path);
398
- if (seen.has(fp))
399
- continue;
400
- seen.add(fp);
401
427
  // Derive .meta/ directory from file_path (strip /meta.json)
402
428
  const metaPath = fp.replace(/\/meta\.json$/, '');
429
+ if (seen.has(metaPath))
430
+ continue;
431
+ seen.add(metaPath);
403
432
  metaPaths.push(metaPath);
404
433
  }
405
434
  return metaPaths;
@@ -1456,6 +1485,8 @@ async function orchestrateOnce(config, executor, watcher, targetPath) {
1456
1485
  const candidates = [];
1457
1486
  for (const node of tree.nodes.values()) {
1458
1487
  const meta = metas.get(node.metaPath);
1488
+ if (!meta)
1489
+ continue; // Node not in metas map (e.g. unreadable meta.json)
1459
1490
  const staleness = actualStaleness(meta);
1460
1491
  if (staleness > 0) {
1461
1492
  candidates.push({ node, meta, actualStaleness: staleness });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Knowledge synthesis engine for the Jeeves platform",
6
6
  "license": "BSD-3-Clause",