@karmaniverous/jeeves-meta-openclaw 0.1.0 → 0.1.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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { HttpWatcherClient, paginatedScan, isLocked, normalizePath, globMetas, buildOwnershipTree, findNode, ensureMetaJson, actualStaleness, computeEffectiveStaleness, selectCandidate, filterInScope, computeStructureHash, readLatestArchive, hasSteerChanged, isArchitectTriggered, loadSynthConfig } from '@karmaniverous/jeeves-meta';
1
+ import { HttpWatcherClient, paginatedScan, buildMetaFilter, isLocked, normalizePath, discoverMetas, buildOwnershipTree, findNode, actualStaleness, computeEffectiveStaleness, selectCandidate, filterInScope, computeStructureHash, readLatestArchive, hasSteerChanged, isArchitectTriggered, loadSynthConfig } from '@karmaniverous/jeeves-meta';
2
2
  import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { resolve } from 'node:path';
4
4
 
@@ -55,168 +55,175 @@ function fail(error) {
55
55
  * @module rules
56
56
  */
57
57
  const SOURCE = 'jeeves-meta';
58
- /** Virtual rule definitions per spec Section 15. */
59
- const SYNTH_RULES = [
60
- {
61
- name: 'synth-meta-live',
62
- description: 'Live jeeves-meta .meta/meta.json files',
63
- match: {
64
- properties: {
65
- file: {
66
- properties: {
67
- path: { type: 'string', glob: '**/.meta/meta.json' },
68
- },
69
- },
70
- },
71
- },
72
- schema: [
73
- 'base',
74
- {
58
+ /**
59
+ * Build virtual rule definitions using configured domain tags.
60
+ *
61
+ * @param config - Synth config with metaProperty/metaArchiveProperty.
62
+ * @returns Array of inference rule specs.
63
+ */
64
+ function buildSynthRules(config) {
65
+ return [
66
+ {
67
+ name: 'synth-meta-live',
68
+ description: 'Live jeeves-meta .meta/meta.json files',
69
+ match: {
75
70
  properties: {
76
- domains: { set: ['synth-meta'] },
77
- synth_id: { type: 'string', set: '{{json._id}}' },
78
- synth_steer: { type: 'string', set: '{{json._steer}}' },
79
- synth_depth: { type: 'number', set: '{{json._depth}}' },
80
- synth_emphasis: { type: 'number', set: '{{json._emphasis}}' },
81
- synth_synthesis_count: {
82
- type: 'integer',
83
- set: '{{json._synthesisCount}}',
84
- },
85
- synth_structure_hash: {
86
- type: 'string',
87
- set: '{{json._structureHash}}',
88
- },
89
- synth_architect_tokens: {
90
- type: 'integer',
91
- set: '{{json._architectTokens}}',
92
- },
93
- synth_builder_tokens: {
94
- type: 'integer',
95
- set: '{{json._builderTokens}}',
96
- },
97
- synth_critic_tokens: {
98
- type: 'integer',
99
- set: '{{json._criticTokens}}',
100
- },
101
- synth_error_step: {
102
- type: 'string',
103
- set: '{{json._error.step}}',
104
- },
105
- generated_at_unix: {
106
- type: 'integer',
107
- set: '{{toUnix json._generatedAt}}',
108
- description: 'Synthesis timestamp as Unix seconds for range queries',
109
- },
110
- has_error: {
111
- type: 'boolean',
112
- set: '{{#if json._error}}true{{else}}false{{/if}}',
71
+ file: {
72
+ properties: {
73
+ path: { type: 'string', glob: '**/.meta/meta.json' },
74
+ },
113
75
  },
114
76
  },
115
77
  },
116
- ],
117
- render: {
118
- frontmatter: [
119
- 'synth_id',
120
- 'synth_steer',
121
- 'generated_at_unix',
122
- 'synth_depth',
123
- 'synth_emphasis',
124
- 'synth_architect_tokens',
125
- 'synth_builder_tokens',
126
- 'synth_critic_tokens',
127
- ],
128
- body: [
78
+ schema: [
79
+ 'base',
129
80
  {
130
- path: 'json._content',
131
- heading: 1,
132
- label: 'Synthesis',
133
- },
134
- ],
135
- },
136
- renderAs: 'md',
137
- },
138
- {
139
- name: 'synth-meta-archive',
140
- description: 'Archived jeeves-meta .meta/archive snapshots',
141
- match: {
142
- properties: {
143
- file: {
144
81
  properties: {
145
- path: { type: 'string', glob: '**/.meta/archive/*.json' },
82
+ domains: { set: config.metaProperty.domains },
83
+ synth_id: { type: 'string', set: '{{json._id}}' },
84
+ synth_steer: { type: 'string', set: '{{json._steer}}' },
85
+ synth_depth: { type: 'number', set: '{{json._depth}}' },
86
+ synth_emphasis: { type: 'number', set: '{{json._emphasis}}' },
87
+ synth_synthesis_count: {
88
+ type: 'integer',
89
+ set: '{{json._synthesisCount}}',
90
+ },
91
+ synth_structure_hash: {
92
+ type: 'string',
93
+ set: '{{json._structureHash}}',
94
+ },
95
+ synth_architect_tokens: {
96
+ type: 'integer',
97
+ set: '{{json._architectTokens}}',
98
+ },
99
+ synth_builder_tokens: {
100
+ type: 'integer',
101
+ set: '{{json._builderTokens}}',
102
+ },
103
+ synth_critic_tokens: {
104
+ type: 'integer',
105
+ set: '{{json._criticTokens}}',
106
+ },
107
+ synth_error_step: {
108
+ type: 'string',
109
+ set: '{{json._error.step}}',
110
+ },
111
+ generated_at_unix: {
112
+ type: 'integer',
113
+ set: '{{toUnix json._generatedAt}}',
114
+ description: 'Synthesis timestamp as Unix seconds for range queries',
115
+ },
116
+ has_error: {
117
+ type: 'boolean',
118
+ set: '{{#if json._error}}true{{else}}false{{/if}}',
119
+ },
146
120
  },
147
121
  },
122
+ ],
123
+ render: {
124
+ frontmatter: [
125
+ 'synth_id',
126
+ 'synth_steer',
127
+ 'generated_at_unix',
128
+ 'synth_depth',
129
+ 'synth_emphasis',
130
+ 'synth_architect_tokens',
131
+ 'synth_builder_tokens',
132
+ 'synth_critic_tokens',
133
+ ],
134
+ body: [
135
+ {
136
+ path: 'json._content',
137
+ heading: 1,
138
+ label: 'Synthesis',
139
+ },
140
+ ],
148
141
  },
142
+ renderAs: 'md',
149
143
  },
150
- schema: [
151
- 'base',
152
- {
144
+ {
145
+ name: 'synth-meta-archive',
146
+ description: 'Archived jeeves-meta .meta/archive snapshots',
147
+ match: {
153
148
  properties: {
154
- domains: { set: ['synth-archive'] },
155
- synth_id: { type: 'string', set: '{{json._id}}' },
156
- archived: { type: 'boolean', set: 'true' },
157
- archived_at: { type: 'string', set: '{{json._archivedAt}}' },
149
+ file: {
150
+ properties: {
151
+ path: { type: 'string', glob: '**/.meta/archive/*.json' },
152
+ },
153
+ },
158
154
  },
159
155
  },
160
- ],
161
- render: {
162
- frontmatter: ['synth_id', 'archived', 'archived_at'],
163
- body: [
156
+ schema: [
157
+ 'base',
164
158
  {
165
- path: 'json._content',
166
- heading: 1,
167
- label: 'Synthesis (archived)',
168
- },
169
- ],
170
- },
171
- renderAs: 'md',
172
- },
173
- {
174
- name: 'synth-config',
175
- description: 'jeeves-meta configuration file',
176
- match: {
177
- properties: {
178
- file: {
179
159
  properties: {
180
- path: { type: 'string', glob: '**/jeeves-meta.config.json' },
160
+ domains: { set: config.metaArchiveProperty.domains },
161
+ synth_id: { type: 'string', set: '{{json._id}}' },
162
+ archived: { type: 'boolean', set: 'true' },
163
+ archived_at: { type: 'string', set: '{{json._archivedAt}}' },
181
164
  },
182
165
  },
166
+ ],
167
+ render: {
168
+ frontmatter: ['synth_id', 'archived', 'archived_at'],
169
+ body: [
170
+ {
171
+ path: 'json._content',
172
+ heading: 1,
173
+ label: 'Synthesis (archived)',
174
+ },
175
+ ],
183
176
  },
177
+ renderAs: 'md',
184
178
  },
185
- schema: [
186
- 'base',
187
- {
179
+ {
180
+ name: 'synth-config',
181
+ description: 'jeeves-meta configuration file',
182
+ match: {
188
183
  properties: {
189
- domains: { set: ['synth-config'] },
184
+ file: {
185
+ properties: {
186
+ path: { type: 'string', glob: '**/jeeves-meta.config.json' },
187
+ },
188
+ },
190
189
  },
191
190
  },
192
- ],
193
- render: {
194
- frontmatter: [
195
- 'watchPaths',
196
- 'watcherUrl',
197
- 'gatewayUrl',
198
- 'architectEvery',
199
- 'depthWeight',
200
- 'maxArchive',
201
- 'maxLines',
202
- 'batchSize',
203
- ],
204
- body: [
191
+ schema: [
192
+ 'base',
205
193
  {
206
- path: 'json.defaultArchitect',
207
- heading: 2,
208
- label: 'Default Architect Prompt',
209
- },
210
- {
211
- path: 'json.defaultCritic',
212
- heading: 2,
213
- label: 'Default Critic Prompt',
194
+ properties: {
195
+ domains: { set: ['synth-config'] },
196
+ },
214
197
  },
215
198
  ],
199
+ render: {
200
+ frontmatter: [
201
+ 'watchPaths',
202
+ 'watcherUrl',
203
+ 'gatewayUrl',
204
+ 'architectEvery',
205
+ 'depthWeight',
206
+ 'maxArchive',
207
+ 'maxLines',
208
+ 'batchSize',
209
+ ],
210
+ body: [
211
+ {
212
+ path: 'json.defaultArchitect',
213
+ heading: 2,
214
+ label: 'Default Architect Prompt',
215
+ },
216
+ {
217
+ path: 'json.defaultCritic',
218
+ heading: 2,
219
+ label: 'Default Critic Prompt',
220
+ },
221
+ ],
222
+ },
223
+ renderAs: 'md',
216
224
  },
217
- renderAs: 'md',
218
- },
219
- ];
225
+ ];
226
+ }
220
227
  /**
221
228
  * Register jeeves-meta virtual rules with the watcher.
222
229
  *
@@ -225,9 +232,9 @@ const SYNTH_RULES = [
225
232
  *
226
233
  * @param watcherUrl - Base URL for the watcher service.
227
234
  */
228
- async function registerSynthRules(watcherUrl) {
235
+ async function registerSynthRules(watcherUrl, config) {
229
236
  const client = new HttpWatcherClient({ baseUrl: watcherUrl });
230
- await client.registerRules(SOURCE, SYNTH_RULES);
237
+ await client.registerRules(SOURCE, buildSynthRules(config));
231
238
  }
232
239
 
233
240
  /**
@@ -248,8 +255,6 @@ function registerSynthTools(api) {
248
255
  };
249
256
  /** Derive watcherUrl from loaded config. */
250
257
  const getWatcherUrl = () => getConfig().watcherUrl;
251
- /** Derive watchPaths from loaded config. */
252
- const getWatchPaths = () => getConfig().watchPaths;
253
258
  // ─── synth_list ──────────────────────────────────────────────
254
259
  api.registerTool({
255
260
  name: 'synth_list',
@@ -282,12 +287,11 @@ function registerSynthTools(api) {
282
287
  try {
283
288
  const pathPrefix = params.pathPrefix;
284
289
  const watcher = new HttpWatcherClient({ baseUrl: getWatcherUrl() });
285
- // Query watcher for synth-meta domain points
290
+ const config = getConfig();
291
+ // Query watcher for synth entity points using configured filter
286
292
  const scanFiles = await paginatedScan(watcher, {
287
293
  ...(pathPrefix ? { pathPrefix } : {}),
288
- filter: {
289
- must: [{ key: 'domains', match: { value: 'synth-meta' } }],
290
- },
294
+ filter: buildMetaFilter(config),
291
295
  fields: [
292
296
  'file_path',
293
297
  'synth_depth',
@@ -300,6 +304,15 @@ function registerSynthTools(api) {
300
304
  'synth_error_step',
301
305
  ],
302
306
  });
307
+ // Deduplicate by file_path (watcher chunks files into multiple points)
308
+ const seenPaths = new Set();
309
+ const dedupedFiles = scanFiles.filter((sf) => {
310
+ const fp = sf.file_path;
311
+ if (seenPaths.has(fp))
312
+ return false;
313
+ seenPaths.add(fp);
314
+ return true;
315
+ });
303
316
  const entities = [];
304
317
  let staleCount = 0;
305
318
  let errorCount = 0;
@@ -312,8 +325,7 @@ function registerSynthTools(api) {
312
325
  let lastSynthAt = null;
313
326
  let stalestPath = null;
314
327
  let stalestEffective = -1;
315
- const config = getConfig();
316
- for (const sf of scanFiles) {
328
+ for (const sf of dedupedFiles) {
317
329
  const filePath = sf.file_path;
318
330
  const depth = typeof sf['synth_depth'] === 'number' ? sf['synth_depth'] : 0;
319
331
  const emphasis = typeof sf['synth_emphasis'] === 'number' ? sf['synth_emphasis'] : 1;
@@ -475,13 +487,16 @@ function registerSynthTools(api) {
475
487
  '_feedback',
476
488
  ]);
477
489
  const fields = params.fields;
478
- const metaPaths = globMetas(getWatchPaths());
490
+ const watcher = new HttpWatcherClient({ baseUrl: getWatcherUrl() });
491
+ const metaPaths = await discoverMetas(getConfig(), watcher);
479
492
  const tree = buildOwnershipTree(metaPaths);
480
493
  const targetNode = findNode(tree, targetPath);
481
494
  if (!targetNode) {
482
495
  return fail('Meta path not found: ' + targetPath);
483
496
  }
484
- const meta = ensureMetaJson(targetNode.metaPath);
497
+ const { readFileSync } = await import('node:fs');
498
+ const { join } = await import('node:path');
499
+ const meta = JSON.parse(readFileSync(join(targetNode.metaPath, 'meta.json'), 'utf8'));
485
500
  // Apply field projection
486
501
  const projectMeta = (m) => {
487
502
  if (fields) {
@@ -542,7 +557,8 @@ function registerSynthTools(api) {
542
557
  execute: async (_id, params) => {
543
558
  try {
544
559
  const targetPath = params.path;
545
- const metaPaths = globMetas(getWatchPaths());
560
+ const watcher = new HttpWatcherClient({ baseUrl: getWatcherUrl() });
561
+ const metaPaths = await discoverMetas(getConfig(), watcher);
546
562
  const tree = buildOwnershipTree(metaPaths);
547
563
  let targetNode;
548
564
  if (targetPath) {
@@ -554,12 +570,19 @@ function registerSynthTools(api) {
554
570
  }
555
571
  else {
556
572
  // Select stalest
573
+ const { readFileSync: readFs } = await import('node:fs');
574
+ const { join: joinPath } = await import('node:path');
557
575
  const candidates = [];
558
576
  for (const node of tree.nodes.values()) {
559
- const meta = ensureMetaJson(node.metaPath);
560
- const staleness = actualStaleness(meta);
561
- if (staleness > 0) {
562
- candidates.push({ node, meta, actualStaleness: staleness });
577
+ try {
578
+ const meta = JSON.parse(readFs(joinPath(node.metaPath, 'meta.json'), 'utf8'));
579
+ const staleness = actualStaleness(meta);
580
+ if (staleness > 0) {
581
+ candidates.push({ node, meta, actualStaleness: staleness });
582
+ }
583
+ }
584
+ catch {
585
+ // skip unreadable
563
586
  }
564
587
  }
565
588
  const weighted = computeEffectiveStaleness(candidates, getConfig().depthWeight);
@@ -571,8 +594,9 @@ function registerSynthTools(api) {
571
594
  }
572
595
  targetNode = winner.node;
573
596
  }
574
- const meta = ensureMetaJson(targetNode.metaPath);
575
- const watcher = new HttpWatcherClient({ baseUrl: getWatcherUrl() });
597
+ const { readFileSync: readMeta } = await import('node:fs');
598
+ const { join: joinMeta } = await import('node:path');
599
+ const meta = JSON.parse(readMeta(joinMeta(targetNode.metaPath, 'meta.json'), 'utf8'));
576
600
  // Scope files (paginated for completeness)
577
601
  const allScanFiles = await paginatedScan(watcher, {
578
602
  pathPrefix: targetNode.ownerPath,
@@ -661,15 +685,8 @@ function registerSynthTools(api) {
661
685
  apiKey: config.gatewayApiKey,
662
686
  });
663
687
  const watcher = new HttpWatcherClient({ baseUrl: getWatcherUrl() });
664
- // If path specified, temporarily override watchPaths to target it
665
688
  const targetPath = params.path;
666
- const effectiveConfig = targetPath
667
- ? {
668
- ...config,
669
- watchPaths: [targetPath.replace(/[/\\]\.meta[/\\]?$/, '')],
670
- }
671
- : config;
672
- const results = await orchestrate(effectiveConfig, executor, watcher);
689
+ const results = await orchestrate(config, executor, watcher, targetPath);
673
690
  const synthesized = results.filter((r) => r.synthesized);
674
691
  if (synthesized.length === 0) {
675
692
  return ok({
@@ -715,19 +732,12 @@ function registerSynthTools(api) {
715
732
  * @param watcherUrl - Watcher API base URL.
716
733
  * @returns Markdown string for the Meta section.
717
734
  */
718
- async function generateMetaMenu(watcherUrl) {
735
+ async function generateMetaMenu(watcherUrl, metaFilter) {
719
736
  let entities = [];
720
737
  try {
721
738
  const watcher = new HttpWatcherClient({ baseUrl: watcherUrl });
722
739
  entities = await paginatedScan(watcher, {
723
- filter: {
724
- must: [
725
- {
726
- key: 'domains',
727
- match: { value: 'synth-meta' },
728
- },
729
- ],
730
- },
740
+ filter: metaFilter,
731
741
  fields: [
732
742
  'synth_depth',
733
743
  'synth_emphasis',
@@ -751,6 +761,15 @@ async function generateMetaMenu(watcherUrl) {
751
761
  '> for setup instructions. Do not attempt synthesis until watcher is available.',
752
762
  ].join('\n');
753
763
  }
764
+ // Deduplicate by file_path (watcher chunks files into multiple points)
765
+ const seenPaths = new Set();
766
+ entities = entities.filter((e) => {
767
+ const fp = e.file_path;
768
+ if (seenPaths.has(fp))
769
+ return false;
770
+ seenPaths.add(fp);
771
+ return true;
772
+ });
754
773
  if (entities.length === 0) {
755
774
  return [
756
775
  '> **ACTION REQUIRED: No synthesis entities found.**',
@@ -926,8 +945,8 @@ function upsertMetaSection(existing, metaMenu) {
926
945
  * @param watcherUrl - Watcher API base URL.
927
946
  * @returns True if the file was updated.
928
947
  */
929
- async function refreshToolsMd(api, watcherUrl) {
930
- const menu = await generateMetaMenu(watcherUrl);
948
+ async function refreshToolsMd(api, config) {
949
+ const menu = await generateMetaMenu(config.watcherUrl, buildMetaFilter(config));
931
950
  if (menu === lastWrittenMenu) {
932
951
  return false;
933
952
  }
@@ -955,10 +974,10 @@ async function refreshToolsMd(api, watcherUrl) {
955
974
  * @param api - Plugin API.
956
975
  * @param watcherUrl - Watcher API base URL.
957
976
  */
958
- function startToolsWriter(api, watcherUrl) {
977
+ function startToolsWriter(api, config) {
959
978
  // Deferred initial write
960
979
  setTimeout(() => {
961
- refreshToolsMd(api, watcherUrl).catch((err) => {
980
+ refreshToolsMd(api, config).catch((err) => {
962
981
  console.error('[jeeves-meta] Failed to write TOOLS.md:', err);
963
982
  });
964
983
  }, INITIAL_DELAY_MS);
@@ -967,7 +986,7 @@ function startToolsWriter(api, watcherUrl) {
967
986
  clearInterval(intervalHandle);
968
987
  }
969
988
  intervalHandle = setInterval(() => {
970
- refreshToolsMd(api, watcherUrl).catch((err) => {
989
+ refreshToolsMd(api, config).catch((err) => {
971
990
  console.error('[jeeves-meta] Failed to refresh TOOLS.md:', err);
972
991
  });
973
992
  }, REFRESH_INTERVAL_MS);
@@ -990,12 +1009,12 @@ function register(api) {
990
1009
  // Load config for rule registration and tools writer
991
1010
  const config = loadSynthConfig(getConfigPath(api));
992
1011
  // Register virtual rules with watcher (fire-and-forget at startup)
993
- registerSynthRules(config.watcherUrl).catch((err) => {
1012
+ registerSynthRules(config.watcherUrl, config).catch((err) => {
994
1013
  const message = err instanceof Error ? err.message : String(err);
995
1014
  console.error('[jeeves-meta] Failed to register virtual rules:', message);
996
1015
  });
997
1016
  // Start periodic TOOLS.md writer
998
- startToolsWriter(api, config.watcherUrl);
1017
+ startToolsWriter(api, config);
999
1018
  }
1000
1019
 
1001
1020
  export { register as default };
@@ -136,11 +136,38 @@ Before the synthesis engine can operate:
136
136
 
137
137
  ### Installation
138
138
 
139
+ 1. Install the core library globally (provides the CLI and the dependency for the plugin):
140
+
141
+ ```bash
142
+ npm install -g @karmaniverous/jeeves-meta
143
+ ```
144
+
145
+ 2. Install the OpenClaw plugin:
146
+
139
147
  ```bash
140
148
  npx @karmaniverous/jeeves-meta-openclaw install
141
149
  ```
142
150
 
143
- Then restart the OpenClaw gateway to load the plugin.
151
+ 3. Configure the plugin in `openclaw.json`:
152
+
153
+ ```json
154
+ {
155
+ "plugins": {
156
+ "entries": {
157
+ "jeeves-meta-openclaw": {
158
+ "enabled": true,
159
+ "config": {
160
+ "configPath": "/path/to/jeeves-meta.config.json"
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
168
+ Alternatively, set the `JEEVES_META_CONFIG` environment variable instead of using plugin config.
169
+
170
+ 4. Restart the OpenClaw gateway to load the plugin.
144
171
 
145
172
  ### First Synthesis
146
173
 
@@ -17,4 +17,4 @@
17
17
  * @param watcherUrl - Watcher API base URL.
18
18
  * @returns Markdown string for the Meta section.
19
19
  */
20
- export declare function generateMetaMenu(watcherUrl: string): Promise<string>;
20
+ export declare function generateMetaMenu(watcherUrl: string, metaFilter: Record<string, unknown>): Promise<string>;
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * @module rules
10
10
  */
11
+ import type { SynthConfig } from '@karmaniverous/jeeves-meta';
11
12
  /**
12
13
  * Register jeeves-meta virtual rules with the watcher.
13
14
  *
@@ -16,4 +17,4 @@
16
17
  *
17
18
  * @param watcherUrl - Base URL for the watcher service.
18
19
  */
19
- export declare function registerSynthRules(watcherUrl: string): Promise<void>;
20
+ export declare function registerSynthRules(watcherUrl: string, config: SynthConfig): Promise<void>;
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * @module toolsWriter
8
8
  */
9
+ import { type SynthConfig } from '@karmaniverous/jeeves-meta';
9
10
  import type { PluginApi } from './helpers.js';
10
11
  /**
11
12
  * Upsert the Meta section in TOOLS.md content.
@@ -23,4 +24,4 @@ export declare function upsertMetaSection(existing: string, metaMenu: string): s
23
24
  * @param api - Plugin API.
24
25
  * @param watcherUrl - Watcher API base URL.
25
26
  */
26
- export declare function startToolsWriter(api: PluginApi, watcherUrl: string): void;
27
+ export declare function startToolsWriter(api: PluginApi, config: SynthConfig): void;
@@ -2,7 +2,7 @@
2
2
  "id": "jeeves-meta-openclaw",
3
3
  "name": "Jeeves Meta",
4
4
  "description": "Knowledge synthesis tools — trigger synthesis, view status, manage entities.",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "skills": [
7
7
  "dist/skills/jeeves-meta"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta-openclaw",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "OpenClaw plugin for jeeves-meta — synthesis tools and virtual rule registration",
6
6
  "license": "BSD-3-Clause",