@karmaniverous/jeeves-meta 0.8.0 → 0.9.0

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/README.md CHANGED
@@ -10,6 +10,7 @@ HTTP service for the Jeeves knowledge synthesis engine. Provides a Fastify API,
10
10
  - **Three-step orchestration** — architect, builder, critic with conditional re-architecture
11
11
  - **Discovery via watcher** — filesystem-based meta discovery via `/walk` endpoint (no Qdrant dependency)
12
12
  - **Ownership tree** — hierarchical scoping with child meta rollup
13
+ - **Cross-meta references** — `_crossRefs` declares relationships to other metas; referenced `_content` included as architect/builder context
13
14
  - **Archive management** — timestamped snapshots with configurable pruning
14
15
  - **Lock staging** — write to `.lock` → copy to `meta.json` → archive (crash-safe)
15
16
  - **Virtual rule registration** — registers 3 watcher inference rules at startup with retry
@@ -54,7 +55,7 @@ jeeves-meta service install --config /path/to/jeeves-meta.config.json
54
55
  | GET | `/metas/:path` | Single meta detail with optional archive |
55
56
  | GET | `/preview` | Dry-run: preview inputs for next synthesis |
56
57
  | POST | `/synthesize` | Enqueue synthesis (stalest or specific path) |
57
- | POST | `/seed` | Create `.meta/` directory + meta.json |
58
+ | POST | `/seed` | Create `.meta/` directory + meta.json (optional `crossRefs`) |
58
59
  | POST | `/unlock` | Remove `.lock` file from a meta entity |
59
60
  | GET | `/config` | Query sanitized config with optional JSONPath (`?path=$.schedule`) |
60
61
 
@@ -1230,6 +1230,22 @@ function condenseScopeFiles(files, maxIndividual = 30) {
1230
1230
  .map(([pattern, count]) => pattern + ' (' + count.toString() + ' files)')
1231
1231
  .join('\n');
1232
1232
  }
1233
+ /**
1234
+ * Read a meta.json file and extract its `_content` field.
1235
+ *
1236
+ * @param metaJsonPath - Absolute path to a meta.json file.
1237
+ * @returns The `_content` string, or null if missing/unreadable.
1238
+ */
1239
+ function readMetaContent(metaJsonPath) {
1240
+ try {
1241
+ const raw = readFileSync(metaJsonPath, 'utf8');
1242
+ const meta = JSON.parse(raw);
1243
+ return meta._content ?? null;
1244
+ }
1245
+ catch {
1246
+ return null;
1247
+ }
1248
+ }
1233
1249
  /**
1234
1250
  * Build the context package for a synthesis cycle.
1235
1251
  *
@@ -1245,15 +1261,18 @@ async function buildContextPackage(node, meta, watcher) {
1245
1261
  // Child meta outputs
1246
1262
  const childMetas = {};
1247
1263
  for (const child of node.children) {
1248
- const childMetaFile = join(child.metaPath, 'meta.json');
1249
- try {
1250
- const raw = readFileSync(childMetaFile, 'utf8');
1251
- const childMeta = JSON.parse(raw);
1252
- childMetas[child.ownerPath] = childMeta._content ?? null;
1253
- }
1254
- catch {
1255
- childMetas[child.ownerPath] = null;
1256
- }
1264
+ childMetas[child.ownerPath] = readMetaContent(join(child.metaPath, 'meta.json'));
1265
+ }
1266
+ // Cross-referenced meta outputs
1267
+ const crossRefMetas = {};
1268
+ const seen = new Set();
1269
+ for (const refPath of meta._crossRefs ?? []) {
1270
+ if (refPath === node.ownerPath || refPath === node.metaPath)
1271
+ continue;
1272
+ if (seen.has(refPath))
1273
+ continue;
1274
+ seen.add(refPath);
1275
+ crossRefMetas[refPath] = readMetaContent(join(refPath, '.meta', 'meta.json'));
1257
1276
  }
1258
1277
  // Archive paths
1259
1278
  const archives = listArchiveFiles(node.metaPath);
@@ -1262,6 +1281,7 @@ async function buildContextPackage(node, meta, watcher) {
1262
1281
  scopeFiles,
1263
1282
  deltaFiles,
1264
1283
  childMetas,
1284
+ crossRefMetas,
1265
1285
  previousContent: meta._content ?? null,
1266
1286
  previousFeedback: meta._feedback ?? null,
1267
1287
  steer: meta._steer ?? null,
@@ -1275,6 +1295,15 @@ async function buildContextPackage(node, meta, watcher) {
1275
1295
  *
1276
1296
  * @module orchestrator/buildTask
1277
1297
  */
1298
+ /** Append a keyed record of meta outputs as subsections, if non-empty. */
1299
+ function appendMetaSections(sections, heading, metas) {
1300
+ if (Object.keys(metas).length === 0)
1301
+ return;
1302
+ sections.push('', heading);
1303
+ for (const [path, content] of Object.entries(metas)) {
1304
+ sections.push(`### ${path}`, typeof content === 'string' ? content : '(not yet synthesized)');
1305
+ }
1306
+ }
1278
1307
  /** Append optional context sections shared across all step prompts. */
1279
1308
  function appendSharedSections(sections, ctx, options) {
1280
1309
  const opts = {
@@ -1283,6 +1312,7 @@ function appendSharedSections(sections, ctx, options) {
1283
1312
  includePreviousFeedback: true,
1284
1313
  feedbackHeading: '## PREVIOUS FEEDBACK',
1285
1314
  includeChildMetas: true,
1315
+ includeCrossRefs: true,
1286
1316
  ...options,
1287
1317
  };
1288
1318
  if (opts.includeSteer && ctx.steer) {
@@ -1294,11 +1324,11 @@ function appendSharedSections(sections, ctx, options) {
1294
1324
  if (opts.includePreviousFeedback && ctx.previousFeedback) {
1295
1325
  sections.push('', opts.feedbackHeading, ctx.previousFeedback);
1296
1326
  }
1297
- if (opts.includeChildMetas && Object.keys(ctx.childMetas).length > 0) {
1298
- sections.push('', '## CHILD META OUTPUTS');
1299
- for (const [childPath, content] of Object.entries(ctx.childMetas)) {
1300
- sections.push(`### ${childPath}`, typeof content === 'string' ? content : '(not yet synthesized)');
1301
- }
1327
+ if (opts.includeChildMetas) {
1328
+ appendMetaSections(sections, '## CHILD META OUTPUTS', ctx.childMetas);
1329
+ }
1330
+ if (opts.includeCrossRefs) {
1331
+ appendMetaSections(sections, '## CROSS-REFERENCED METAS', ctx.crossRefMetas);
1302
1332
  }
1303
1333
  }
1304
1334
  /**
@@ -1382,6 +1412,7 @@ function buildCriticTask(ctx, meta, config) {
1382
1412
  includePreviousContent: false,
1383
1413
  feedbackHeading: '## YOUR PREVIOUS FEEDBACK',
1384
1414
  includeChildMetas: false,
1415
+ includeCrossRefs: false,
1385
1416
  });
1386
1417
  sections.push('', '## OUTPUT FORMAT', 'Return your evaluation as Markdown text. Be specific and actionable.');
1387
1418
  return sections.join('\n');
@@ -1417,6 +1448,11 @@ const metaJsonSchema = z
1417
1448
  _id: z.uuid(),
1418
1449
  /** Human-provided steering prompt. Optional. */
1419
1450
  _steer: z.string().optional(),
1451
+ /**
1452
+ * Explicit cross-references to other meta owner paths.
1453
+ * Referenced metas' _content is included as architect/builder context.
1454
+ */
1455
+ _crossRefs: z.array(z.string()).optional(),
1420
1456
  /** Architect system prompt used this turn. Defaults from config. */
1421
1457
  _architect: z.string().optional(),
1422
1458
  /**
@@ -9917,6 +9953,27 @@ function registerMetasRoutes(app, deps) {
9917
9953
  score: Math.round(score * 100) / 100,
9918
9954
  },
9919
9955
  };
9956
+ // Cross-refs status
9957
+ const crossRefsRaw = meta._crossRefs;
9958
+ if (Array.isArray(crossRefsRaw) && crossRefsRaw.length > 0) {
9959
+ response.crossRefs = crossRefsRaw.map((refPath) => {
9960
+ const rp = String(refPath);
9961
+ const refMetaFile = join(rp, '.meta', 'meta.json');
9962
+ if (!existsSync(refMetaFile))
9963
+ return { path: rp, status: 'missing' };
9964
+ try {
9965
+ const refMeta = JSON.parse(readFileSync(refMetaFile, 'utf8'));
9966
+ return {
9967
+ path: rp,
9968
+ status: 'resolved',
9969
+ hasContent: Boolean(refMeta._content),
9970
+ };
9971
+ }
9972
+ catch {
9973
+ return { path: rp, status: 'missing' };
9974
+ }
9975
+ });
9976
+ }
9920
9977
  // Archive
9921
9978
  if (query.includeArchive) {
9922
9979
  const archiveFiles = listArchiveFiles(targetNode.metaPath);
@@ -10034,6 +10091,7 @@ function registerPreviewRoute(app, deps) {
10034
10091
  */
10035
10092
  const seedBodySchema = z.object({
10036
10093
  path: z.string().min(1),
10094
+ crossRefs: z.array(z.string()).optional(),
10037
10095
  });
10038
10096
  function registerSeedRoute(app, deps) {
10039
10097
  app.post('/seed', (request, reply) => {
@@ -10048,6 +10106,8 @@ function registerSeedRoute(app, deps) {
10048
10106
  deps.logger.info({ metaDir }, 'creating .meta directory');
10049
10107
  mkdirSync(metaDir, { recursive: true });
10050
10108
  const metaJson = { _id: randomUUID() };
10109
+ if (body.crossRefs !== undefined)
10110
+ metaJson._crossRefs = body.crossRefs;
10051
10111
  const metaJsonPath = join(metaDir, 'meta.json');
10052
10112
  deps.logger.info({ metaJsonPath }, 'writing meta.json');
10053
10113
  writeFileSync(metaJsonPath, JSON.stringify(metaJson, null, 2) + '\n');
package/dist/index.d.ts CHANGED
@@ -125,6 +125,7 @@ type MetaError = z.infer<typeof metaErrorSchema>;
125
125
  declare const metaJsonSchema: z.ZodObject<{
126
126
  _id: z.ZodUUID;
127
127
  _steer: z.ZodOptional<z.ZodString>;
128
+ _crossRefs: z.ZodOptional<z.ZodArray<z.ZodString>>;
128
129
  _architect: z.ZodOptional<z.ZodString>;
129
130
  _builder: z.ZodOptional<z.ZodString>;
130
131
  _critic: z.ZodOptional<z.ZodString>;
@@ -253,6 +254,8 @@ interface MetaContext {
253
254
  deltaFiles: string[];
254
255
  /** Child _content outputs, keyed by relative path. */
255
256
  childMetas: Record<string, unknown>;
257
+ /** Cross-referenced meta _content outputs, keyed by owner path. */
258
+ crossRefMetas: Record<string, unknown>;
256
259
  /** _content from the last cycle, or null on first run. */
257
260
  previousContent: string | null;
258
261
  /** _feedback from the last cycle, or null on first run. */
package/dist/index.js CHANGED
@@ -1222,6 +1222,22 @@ function condenseScopeFiles(files, maxIndividual = 30) {
1222
1222
  .map(([pattern, count]) => pattern + ' (' + count.toString() + ' files)')
1223
1223
  .join('\n');
1224
1224
  }
1225
+ /**
1226
+ * Read a meta.json file and extract its `_content` field.
1227
+ *
1228
+ * @param metaJsonPath - Absolute path to a meta.json file.
1229
+ * @returns The `_content` string, or null if missing/unreadable.
1230
+ */
1231
+ function readMetaContent(metaJsonPath) {
1232
+ try {
1233
+ const raw = readFileSync(metaJsonPath, 'utf8');
1234
+ const meta = JSON.parse(raw);
1235
+ return meta._content ?? null;
1236
+ }
1237
+ catch {
1238
+ return null;
1239
+ }
1240
+ }
1225
1241
  /**
1226
1242
  * Build the context package for a synthesis cycle.
1227
1243
  *
@@ -1237,15 +1253,18 @@ async function buildContextPackage(node, meta, watcher) {
1237
1253
  // Child meta outputs
1238
1254
  const childMetas = {};
1239
1255
  for (const child of node.children) {
1240
- const childMetaFile = join(child.metaPath, 'meta.json');
1241
- try {
1242
- const raw = readFileSync(childMetaFile, 'utf8');
1243
- const childMeta = JSON.parse(raw);
1244
- childMetas[child.ownerPath] = childMeta._content ?? null;
1245
- }
1246
- catch {
1247
- childMetas[child.ownerPath] = null;
1248
- }
1256
+ childMetas[child.ownerPath] = readMetaContent(join(child.metaPath, 'meta.json'));
1257
+ }
1258
+ // Cross-referenced meta outputs
1259
+ const crossRefMetas = {};
1260
+ const seen = new Set();
1261
+ for (const refPath of meta._crossRefs ?? []) {
1262
+ if (refPath === node.ownerPath || refPath === node.metaPath)
1263
+ continue;
1264
+ if (seen.has(refPath))
1265
+ continue;
1266
+ seen.add(refPath);
1267
+ crossRefMetas[refPath] = readMetaContent(join(refPath, '.meta', 'meta.json'));
1249
1268
  }
1250
1269
  // Archive paths
1251
1270
  const archives = listArchiveFiles(node.metaPath);
@@ -1254,6 +1273,7 @@ async function buildContextPackage(node, meta, watcher) {
1254
1273
  scopeFiles,
1255
1274
  deltaFiles,
1256
1275
  childMetas,
1276
+ crossRefMetas,
1257
1277
  previousContent: meta._content ?? null,
1258
1278
  previousFeedback: meta._feedback ?? null,
1259
1279
  steer: meta._steer ?? null,
@@ -1267,6 +1287,15 @@ async function buildContextPackage(node, meta, watcher) {
1267
1287
  *
1268
1288
  * @module orchestrator/buildTask
1269
1289
  */
1290
+ /** Append a keyed record of meta outputs as subsections, if non-empty. */
1291
+ function appendMetaSections(sections, heading, metas) {
1292
+ if (Object.keys(metas).length === 0)
1293
+ return;
1294
+ sections.push('', heading);
1295
+ for (const [path, content] of Object.entries(metas)) {
1296
+ sections.push(`### ${path}`, typeof content === 'string' ? content : '(not yet synthesized)');
1297
+ }
1298
+ }
1270
1299
  /** Append optional context sections shared across all step prompts. */
1271
1300
  function appendSharedSections(sections, ctx, options) {
1272
1301
  const opts = {
@@ -1275,6 +1304,7 @@ function appendSharedSections(sections, ctx, options) {
1275
1304
  includePreviousFeedback: true,
1276
1305
  feedbackHeading: '## PREVIOUS FEEDBACK',
1277
1306
  includeChildMetas: true,
1307
+ includeCrossRefs: true,
1278
1308
  ...options,
1279
1309
  };
1280
1310
  if (opts.includeSteer && ctx.steer) {
@@ -1286,11 +1316,11 @@ function appendSharedSections(sections, ctx, options) {
1286
1316
  if (opts.includePreviousFeedback && ctx.previousFeedback) {
1287
1317
  sections.push('', opts.feedbackHeading, ctx.previousFeedback);
1288
1318
  }
1289
- if (opts.includeChildMetas && Object.keys(ctx.childMetas).length > 0) {
1290
- sections.push('', '## CHILD META OUTPUTS');
1291
- for (const [childPath, content] of Object.entries(ctx.childMetas)) {
1292
- sections.push(`### ${childPath}`, typeof content === 'string' ? content : '(not yet synthesized)');
1293
- }
1319
+ if (opts.includeChildMetas) {
1320
+ appendMetaSections(sections, '## CHILD META OUTPUTS', ctx.childMetas);
1321
+ }
1322
+ if (opts.includeCrossRefs) {
1323
+ appendMetaSections(sections, '## CROSS-REFERENCED METAS', ctx.crossRefMetas);
1294
1324
  }
1295
1325
  }
1296
1326
  /**
@@ -1374,6 +1404,7 @@ function buildCriticTask(ctx, meta, config) {
1374
1404
  includePreviousContent: false,
1375
1405
  feedbackHeading: '## YOUR PREVIOUS FEEDBACK',
1376
1406
  includeChildMetas: false,
1407
+ includeCrossRefs: false,
1377
1408
  });
1378
1409
  sections.push('', '## OUTPUT FORMAT', 'Return your evaluation as Markdown text. Be specific and actionable.');
1379
1410
  return sections.join('\n');
@@ -1409,6 +1440,11 @@ const metaJsonSchema = z
1409
1440
  _id: z.uuid(),
1410
1441
  /** Human-provided steering prompt. Optional. */
1411
1442
  _steer: z.string().optional(),
1443
+ /**
1444
+ * Explicit cross-references to other meta owner paths.
1445
+ * Referenced metas' _content is included as architect/builder context.
1446
+ */
1447
+ _crossRefs: z.array(z.string()).optional(),
1412
1448
  /** Architect system prompt used this turn. Defaults from config. */
1413
1449
  _architect: z.string().optional(),
1414
1450
  /**
@@ -9913,6 +9949,27 @@ function registerMetasRoutes(app, deps) {
9913
9949
  score: Math.round(score * 100) / 100,
9914
9950
  },
9915
9951
  };
9952
+ // Cross-refs status
9953
+ const crossRefsRaw = meta._crossRefs;
9954
+ if (Array.isArray(crossRefsRaw) && crossRefsRaw.length > 0) {
9955
+ response.crossRefs = crossRefsRaw.map((refPath) => {
9956
+ const rp = String(refPath);
9957
+ const refMetaFile = join(rp, '.meta', 'meta.json');
9958
+ if (!existsSync(refMetaFile))
9959
+ return { path: rp, status: 'missing' };
9960
+ try {
9961
+ const refMeta = JSON.parse(readFileSync(refMetaFile, 'utf8'));
9962
+ return {
9963
+ path: rp,
9964
+ status: 'resolved',
9965
+ hasContent: Boolean(refMeta._content),
9966
+ };
9967
+ }
9968
+ catch {
9969
+ return { path: rp, status: 'missing' };
9970
+ }
9971
+ });
9972
+ }
9916
9973
  // Archive
9917
9974
  if (query.includeArchive) {
9918
9975
  const archiveFiles = listArchiveFiles(targetNode.metaPath);
@@ -10030,6 +10087,7 @@ function registerPreviewRoute(app, deps) {
10030
10087
  */
10031
10088
  const seedBodySchema = z.object({
10032
10089
  path: z.string().min(1),
10090
+ crossRefs: z.array(z.string()).optional(),
10033
10091
  });
10034
10092
  function registerSeedRoute(app, deps) {
10035
10093
  app.post('/seed', (request, reply) => {
@@ -10044,6 +10102,8 @@ function registerSeedRoute(app, deps) {
10044
10102
  deps.logger.info({ metaDir }, 'creating .meta directory');
10045
10103
  mkdirSync(metaDir, { recursive: true });
10046
10104
  const metaJson = { _id: randomUUID() };
10105
+ if (body.crossRefs !== undefined)
10106
+ metaJson._crossRefs = body.crossRefs;
10047
10107
  const metaJsonPath = join(metaDir, 'meta.json');
10048
10108
  deps.logger.info({ metaJsonPath }, 'writing meta.json');
10049
10109
  writeFileSync(metaJsonPath, JSON.stringify(metaJson, null, 2) + '\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Fastify HTTP service for the Jeeves Meta synthesis engine",
6
6
  "license": "BSD-3-Clause",