@karmaniverous/jeeves-meta 0.10.1 → 0.11.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.
@@ -46,11 +46,11 @@ const metaConfigSchema = z.object({
46
46
  /** Maximum lines of context to include in subprocess prompts. */
47
47
  maxLines: z.number().int().min(50).default(500),
48
48
  /** Architect subprocess timeout in seconds. */
49
- architectTimeout: z.number().int().min(30).default(120),
49
+ architectTimeout: z.number().int().min(30).default(180),
50
50
  /** Builder subprocess timeout in seconds. */
51
- builderTimeout: z.number().int().min(60).default(600),
51
+ builderTimeout: z.number().int().min(60).default(360),
52
52
  /** Critic subprocess timeout in seconds. */
53
- criticTimeout: z.number().int().min(30).default(300),
53
+ criticTimeout: z.number().int().min(30).default(240),
54
54
  /** Thinking level for spawned synthesis sessions. */
55
55
  thinking: z.string().default('low'),
56
56
  /** Resolved architect system prompt text. Falls back to built-in default. */
@@ -2151,6 +2151,17 @@ async function synthesizeNode(node, currentMeta, config, executor, watcher, onPr
2151
2151
  const steerChanged = hasSteerChanged(currentMeta._steer, latestArchive?._steer, Boolean(latestArchive));
2152
2152
  // Step 7: Compute context (includes scope files and delta files)
2153
2153
  const ctx = await buildContextPackage(node, currentMeta, watcher, logger);
2154
+ // Skip empty-scope entities that have no prior content.
2155
+ // Without scope files, child metas, or cross-refs there is nothing for
2156
+ // the architect/builder to work with and the cycle will either time out
2157
+ // or produce empty output.
2158
+ const hasScope = ctx.scopeFiles.length > 0 ||
2159
+ Object.keys(ctx.childMetas).length > 0 ||
2160
+ Object.keys(ctx.crossRefMetas).length > 0;
2161
+ if (!hasScope && !currentMeta._content) {
2162
+ logger?.debug({ path: node.ownerPath }, 'Skipping empty-scope entity');
2163
+ return { synthesized: false };
2164
+ }
2154
2165
  // Step 5 (deferred): Structure hash from context scope files
2155
2166
  const newStructureHash = computeStructureHash(ctx.scopeFiles);
2156
2167
  const structureChanged = newStructureHash !== currentMeta._structureHash;
@@ -2447,21 +2458,41 @@ function formatSeconds(durationMs) {
2447
2458
  function titleCasePhase(phase) {
2448
2459
  return phase.charAt(0).toUpperCase() + phase.slice(1);
2449
2460
  }
2450
- /** Build a link to the entity's meta.json output file. */
2451
- function buildEntityLink(path, serverBaseUrl) {
2452
- // Normalize backslashes, then convert drive letter to URL path segment
2461
+ /**
2462
+ * URL-encode each path segment individually so that spaces and special
2463
+ * characters are safe while preserving the `/` separators.
2464
+ */
2465
+ function encodePathSegments(p) {
2466
+ return p
2467
+ .split('/')
2468
+ .map((seg) => encodeURIComponent(seg))
2469
+ .join('/');
2470
+ }
2471
+ /** Build a link (or plain path) to the owner directory. */
2472
+ function buildDirectoryLink(path, serverBaseUrl) {
2473
+ const normalized = normalizePath(path).replace(/^([A-Za-z]):/, '/$1');
2474
+ const encoded = encodePathSegments(normalized);
2475
+ if (!serverBaseUrl)
2476
+ return normalized;
2477
+ const base = serverBaseUrl.replace(/\/+$/, '');
2478
+ return `${base}/path${encoded}`;
2479
+ }
2480
+ /** Build a link (or plain path) to the entity's meta.json output file. */
2481
+ function buildMetaJsonLink(path, serverBaseUrl) {
2453
2482
  const normalized = normalizePath(path).replace(/^([A-Za-z]):/, '/$1');
2454
2483
  const metaJsonPath = `${normalized}/.meta/meta.json`;
2484
+ const encoded = encodePathSegments(metaJsonPath);
2455
2485
  if (!serverBaseUrl)
2456
2486
  return metaJsonPath;
2457
2487
  const base = serverBaseUrl.replace(/\/+$/, '');
2458
- return `${base}/path${metaJsonPath}`;
2488
+ return `${base}/path${encoded}`;
2459
2489
  }
2460
2490
  function formatProgressEvent(event, serverBaseUrl) {
2461
- const pathDisplay = buildEntityLink(event.path, serverBaseUrl);
2462
2491
  switch (event.type) {
2463
- case 'synthesis_start':
2464
- return `🔬 Started meta synthesis: ${pathDisplay}`;
2492
+ case 'synthesis_start': {
2493
+ const dirLink = buildDirectoryLink(event.path, serverBaseUrl);
2494
+ return `🔬 Started meta synthesis: ${dirLink}`;
2495
+ }
2465
2496
  case 'phase_start': {
2466
2497
  if (!event.phase) {
2467
2498
  return ' ⚙️ Phase started';
@@ -2475,16 +2506,18 @@ function formatProgressEvent(event, serverBaseUrl) {
2475
2506
  return ` ✅ ${phase} complete (${formatNumber(tokens)} tokens / ${duration})`;
2476
2507
  }
2477
2508
  case 'synthesis_complete': {
2509
+ const metaLink = buildMetaJsonLink(event.path, serverBaseUrl);
2478
2510
  const tokens = event.tokens ?? 0;
2479
2511
  const duration = event.durationMs !== undefined
2480
2512
  ? formatSeconds(event.durationMs)
2481
2513
  : '0.0s';
2482
- return `✅ Completed: ${pathDisplay} (${formatNumber(tokens)} tokens / ${duration})`;
2514
+ return `✅ Completed: ${metaLink} (${formatNumber(tokens)} tokens / ${duration})`;
2483
2515
  }
2484
2516
  case 'error': {
2517
+ const dirLink = buildDirectoryLink(event.path, serverBaseUrl);
2485
2518
  const phase = event.phase ? `${titleCasePhase(event.phase)} ` : '';
2486
2519
  const error = event.error ?? 'Unknown error';
2487
- return `❌ Synthesis failed at ${phase}phase: ${pathDisplay}\n Error: ${error}`;
2520
+ return `❌ Synthesis failed at ${phase}phase: ${dirLink}\n Error: ${error}`;
2488
2521
  }
2489
2522
  default: {
2490
2523
  return 'Unknown progress event';
@@ -11087,6 +11120,7 @@ async function startService(config, configPath) {
11087
11120
  await progress.report({
11088
11121
  type: 'error',
11089
11122
  path: ownerPath,
11123
+ phase: result.error.step,
11090
11124
  error: result.error.message,
11091
11125
  });
11092
11126
  }
package/dist/index.js CHANGED
@@ -254,11 +254,11 @@ const metaConfigSchema = z.object({
254
254
  /** Maximum lines of context to include in subprocess prompts. */
255
255
  maxLines: z.number().int().min(50).default(500),
256
256
  /** Architect subprocess timeout in seconds. */
257
- architectTimeout: z.number().int().min(30).default(120),
257
+ architectTimeout: z.number().int().min(30).default(180),
258
258
  /** Builder subprocess timeout in seconds. */
259
- builderTimeout: z.number().int().min(60).default(600),
259
+ builderTimeout: z.number().int().min(60).default(360),
260
260
  /** Critic subprocess timeout in seconds. */
261
- criticTimeout: z.number().int().min(30).default(300),
261
+ criticTimeout: z.number().int().min(30).default(240),
262
262
  /** Thinking level for spawned synthesis sessions. */
263
263
  thinking: z.string().default('low'),
264
264
  /** Resolved architect system prompt text. Falls back to built-in default. */
@@ -2143,6 +2143,17 @@ async function synthesizeNode(node, currentMeta, config, executor, watcher, onPr
2143
2143
  const steerChanged = hasSteerChanged(currentMeta._steer, latestArchive?._steer, Boolean(latestArchive));
2144
2144
  // Step 7: Compute context (includes scope files and delta files)
2145
2145
  const ctx = await buildContextPackage(node, currentMeta, watcher, logger);
2146
+ // Skip empty-scope entities that have no prior content.
2147
+ // Without scope files, child metas, or cross-refs there is nothing for
2148
+ // the architect/builder to work with and the cycle will either time out
2149
+ // or produce empty output.
2150
+ const hasScope = ctx.scopeFiles.length > 0 ||
2151
+ Object.keys(ctx.childMetas).length > 0 ||
2152
+ Object.keys(ctx.crossRefMetas).length > 0;
2153
+ if (!hasScope && !currentMeta._content) {
2154
+ logger?.debug({ path: node.ownerPath }, 'Skipping empty-scope entity');
2155
+ return { synthesized: false };
2156
+ }
2146
2157
  // Step 5 (deferred): Structure hash from context scope files
2147
2158
  const newStructureHash = computeStructureHash(ctx.scopeFiles);
2148
2159
  const structureChanged = newStructureHash !== currentMeta._structureHash;
@@ -2439,21 +2450,41 @@ function formatSeconds(durationMs) {
2439
2450
  function titleCasePhase(phase) {
2440
2451
  return phase.charAt(0).toUpperCase() + phase.slice(1);
2441
2452
  }
2442
- /** Build a link to the entity's meta.json output file. */
2443
- function buildEntityLink(path, serverBaseUrl) {
2444
- // Normalize backslashes, then convert drive letter to URL path segment
2453
+ /**
2454
+ * URL-encode each path segment individually so that spaces and special
2455
+ * characters are safe while preserving the `/` separators.
2456
+ */
2457
+ function encodePathSegments(p) {
2458
+ return p
2459
+ .split('/')
2460
+ .map((seg) => encodeURIComponent(seg))
2461
+ .join('/');
2462
+ }
2463
+ /** Build a link (or plain path) to the owner directory. */
2464
+ function buildDirectoryLink(path, serverBaseUrl) {
2465
+ const normalized = normalizePath(path).replace(/^([A-Za-z]):/, '/$1');
2466
+ const encoded = encodePathSegments(normalized);
2467
+ if (!serverBaseUrl)
2468
+ return normalized;
2469
+ const base = serverBaseUrl.replace(/\/+$/, '');
2470
+ return `${base}/path${encoded}`;
2471
+ }
2472
+ /** Build a link (or plain path) to the entity's meta.json output file. */
2473
+ function buildMetaJsonLink(path, serverBaseUrl) {
2445
2474
  const normalized = normalizePath(path).replace(/^([A-Za-z]):/, '/$1');
2446
2475
  const metaJsonPath = `${normalized}/.meta/meta.json`;
2476
+ const encoded = encodePathSegments(metaJsonPath);
2447
2477
  if (!serverBaseUrl)
2448
2478
  return metaJsonPath;
2449
2479
  const base = serverBaseUrl.replace(/\/+$/, '');
2450
- return `${base}/path${metaJsonPath}`;
2480
+ return `${base}/path${encoded}`;
2451
2481
  }
2452
2482
  function formatProgressEvent(event, serverBaseUrl) {
2453
- const pathDisplay = buildEntityLink(event.path, serverBaseUrl);
2454
2483
  switch (event.type) {
2455
- case 'synthesis_start':
2456
- return `🔬 Started meta synthesis: ${pathDisplay}`;
2484
+ case 'synthesis_start': {
2485
+ const dirLink = buildDirectoryLink(event.path, serverBaseUrl);
2486
+ return `🔬 Started meta synthesis: ${dirLink}`;
2487
+ }
2457
2488
  case 'phase_start': {
2458
2489
  if (!event.phase) {
2459
2490
  return ' ⚙️ Phase started';
@@ -2467,16 +2498,18 @@ function formatProgressEvent(event, serverBaseUrl) {
2467
2498
  return ` ✅ ${phase} complete (${formatNumber(tokens)} tokens / ${duration})`;
2468
2499
  }
2469
2500
  case 'synthesis_complete': {
2501
+ const metaLink = buildMetaJsonLink(event.path, serverBaseUrl);
2470
2502
  const tokens = event.tokens ?? 0;
2471
2503
  const duration = event.durationMs !== undefined
2472
2504
  ? formatSeconds(event.durationMs)
2473
2505
  : '0.0s';
2474
- return `✅ Completed: ${pathDisplay} (${formatNumber(tokens)} tokens / ${duration})`;
2506
+ return `✅ Completed: ${metaLink} (${formatNumber(tokens)} tokens / ${duration})`;
2475
2507
  }
2476
2508
  case 'error': {
2509
+ const dirLink = buildDirectoryLink(event.path, serverBaseUrl);
2477
2510
  const phase = event.phase ? `${titleCasePhase(event.phase)} ` : '';
2478
2511
  const error = event.error ?? 'Unknown error';
2479
- return `❌ Synthesis failed at ${phase}phase: ${pathDisplay}\n Error: ${error}`;
2512
+ return `❌ Synthesis failed at ${phase}phase: ${dirLink}\n Error: ${error}`;
2480
2513
  }
2481
2514
  default: {
2482
2515
  return 'Unknown progress event';
@@ -11079,6 +11112,7 @@ async function startService(config, configPath) {
11079
11112
  await progress.report({
11080
11113
  type: 'error',
11081
11114
  path: ownerPath,
11115
+ phase: result.error.step,
11082
11116
  error: result.error.message,
11083
11117
  });
11084
11118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Fastify HTTP service for the Jeeves Meta synthesis engine",
6
6
  "license": "BSD-3-Clause",