@karmaniverous/jeeves-meta 0.13.0 → 0.13.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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import fs, { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, statSync, readdirSync, watchFile } from 'node:fs';
3
- import path, { join, dirname, relative, posix, resolve } from 'node:path';
3
+ import path, { join, dirname, basename, relative, posix, resolve } from 'node:path';
4
+ import { randomUUID, createHash } from 'node:crypto';
4
5
  import require$$0$4 from 'path';
5
6
  import require$$0$3 from 'fs';
6
7
  import require$$0$1 from 'constants';
@@ -14,13 +15,12 @@ import * as commander from 'commander';
14
15
  import { homedir, tmpdir } from 'node:os';
15
16
  import { execSync } from 'node:child_process';
16
17
  import { fileURLToPath } from 'node:url';
17
- import { randomUUID, createHash } from 'node:crypto';
18
18
  import { readFile, unlink, mkdir, writeFile, copyFile } from 'node:fs/promises';
19
19
  import pino from 'pino';
20
20
  import Handlebars from 'handlebars';
21
+ import process$1 from 'node:process';
21
22
  import { Cron } from 'croner';
22
23
  import Fastify from 'fastify';
23
- import process$1 from 'node:process';
24
24
 
25
25
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
26
26
 
@@ -7088,30 +7088,64 @@ function getComponentConfigDir(componentName) {
7088
7088
  throw new Error('jeeves-core: init() must be called first');
7089
7089
  return join(state.configRoot, `${COMPONENT_CONFIG_PREFIX}${componentName}`);
7090
7090
  }
7091
+ /** Maximum rename retry attempts on EPERM. */
7092
+ const ATOMIC_WRITE_MAX_RETRIES = 3;
7093
+ /** Delay between EPERM retries in milliseconds. */
7094
+ const ATOMIC_WRITE_RETRY_DELAY_MS = 100;
7091
7095
  /**
7092
7096
  * Write content to a file atomically via a temp file + rename.
7093
7097
  *
7098
+ * @remarks
7099
+ * Retries the rename up to three times on EPERM (Windows file-handle
7100
+ * contention) with a 100 ms synchronous delay between attempts.
7101
+ *
7094
7102
  * @param filePath - Absolute path to the target file.
7095
7103
  * @param content - Content to write.
7096
7104
  */
7097
7105
  function atomicWrite(filePath, content) {
7098
7106
  const dir = dirname(filePath);
7099
- const tempPath = join(dir, `.${String(Date.now())}.tmp`);
7107
+ const base = basename(filePath, '.md');
7108
+ const tempPath = join(dir, `.${base}.${String(Date.now())}.${randomUUID().slice(0, 8)}.tmp`);
7100
7109
  writeFileSync(tempPath, content, 'utf-8');
7101
- try {
7102
- renameSync(tempPath, filePath);
7103
- }
7104
- catch (err) {
7110
+ for (let attempt = 0; attempt < ATOMIC_WRITE_MAX_RETRIES; attempt++) {
7105
7111
  try {
7106
- unlinkSync(tempPath);
7112
+ renameSync(tempPath, filePath);
7113
+ return;
7107
7114
  }
7108
- catch {
7109
- /* best-effort cleanup */
7115
+ catch (err) {
7116
+ const isEperm = err instanceof Error &&
7117
+ 'code' in err &&
7118
+ err.code === 'EPERM';
7119
+ if (!isEperm || attempt === ATOMIC_WRITE_MAX_RETRIES - 1) {
7120
+ try {
7121
+ unlinkSync(tempPath);
7122
+ }
7123
+ catch {
7124
+ /* best-effort cleanup */
7125
+ }
7126
+ throw err;
7127
+ }
7128
+ // Synchronous sleep before retry (acceptable in atomic write context)
7129
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ATOMIC_WRITE_RETRY_DELAY_MS);
7110
7130
  }
7111
- throw err;
7112
7131
  }
7113
7132
  }
7114
7133
 
7134
+ /**
7135
+ * Shared internal utility functions.
7136
+ *
7137
+ * @packageDocumentation
7138
+ */
7139
+ /**
7140
+ * Extract a human-readable message from an unknown caught value.
7141
+ *
7142
+ * @param err - The caught value (typically `unknown`).
7143
+ * @returns The error message string.
7144
+ */
7145
+ function getErrorMessage(err) {
7146
+ return err instanceof Error ? err.message : String(err);
7147
+ }
7148
+
7115
7149
  /**
7116
7150
  * Factory for a framework-agnostic config apply HTTP handler.
7117
7151
  *
@@ -7164,8 +7198,7 @@ function readConfigFile(filePath) {
7164
7198
  return JSON.parse(raw);
7165
7199
  }
7166
7200
  catch (err) {
7167
- const msg = err instanceof Error ? err.message : String(err);
7168
- console.warn(`jeeves-core: Could not read config file ${filePath}: ${msg}`);
7201
+ console.warn(`jeeves-core: Could not read config file ${filePath}: ${getErrorMessage(err)}`);
7169
7202
  return {};
7170
7203
  }
7171
7204
  }
@@ -7214,10 +7247,9 @@ function createConfigApplyHandler(descriptor) {
7214
7247
  atomicWrite(configPath, json);
7215
7248
  }
7216
7249
  catch (err) {
7217
- const message = err instanceof Error ? err.message : String(err);
7218
7250
  return {
7219
7251
  status: 500,
7220
- body: { error: `Failed to write config: ${message}` },
7252
+ body: { error: `Failed to write config: ${getErrorMessage(err)}` },
7221
7253
  };
7222
7254
  }
7223
7255
  // Call onConfigApply callback if defined
@@ -7226,12 +7258,11 @@ function createConfigApplyHandler(descriptor) {
7226
7258
  await descriptor.onConfigApply(validatedConfig);
7227
7259
  }
7228
7260
  catch (err) {
7229
- const message = err instanceof Error ? err.message : String(err);
7230
7261
  return {
7231
7262
  status: 200,
7232
7263
  body: {
7233
7264
  applied: true,
7234
- warning: `Config written but callback failed: ${message}`,
7265
+ warning: `Config written but callback failed: ${getErrorMessage(err)}`,
7235
7266
  config: validatedConfig,
7236
7267
  },
7237
7268
  };
@@ -7314,8 +7345,7 @@ function createStatusHandler(options) {
7314
7345
  health = await options.getHealth();
7315
7346
  }
7316
7347
  catch (err) {
7317
- const message = err instanceof Error ? err.message : String(err);
7318
- health = { error: message };
7348
+ health = { error: getErrorMessage(err) };
7319
7349
  overallStatus = 'degraded';
7320
7350
  }
7321
7351
  }
@@ -7372,7 +7402,13 @@ z.object({
7372
7402
  /** Memory hygiene shared defaults. */
7373
7403
  memory: workspaceMemoryConfigSchema.optional(),
7374
7404
  });
7375
- /** Built-in workspace config defaults. */
7405
+ /**
7406
+ * Built-in workspace config defaults.
7407
+ *
7408
+ * @remarks
7409
+ * These defaults are used as the lowest-priority tier in config resolution
7410
+ * (below CLI flags, env vars, and `jeeves.config.json` values).
7411
+ */
7376
7412
  const WORKSPACE_CONFIG_DEFAULTS = {
7377
7413
  core: {
7378
7414
  workspace: '.',
@@ -8028,6 +8064,10 @@ function createServiceManager(descriptor) {
8028
8064
  * a component descriptor. Components add domain-specific commands
8029
8065
  * via `descriptor.customCliCommands`.
8030
8066
  */
8067
+ function handleCommandError(action, err) {
8068
+ console.error(`${action} failed: ${getErrorMessage(err)}`);
8069
+ process.exitCode = 1;
8070
+ }
8031
8071
  /**
8032
8072
  * Create a standard service CLI program from a component descriptor.
8033
8073
  *
@@ -8080,8 +8120,7 @@ function createServiceCli(descriptor) {
8080
8120
  console.log(JSON.stringify(result, null, 2));
8081
8121
  }
8082
8122
  catch (err) {
8083
- const msg = err instanceof Error ? err.message : String(err);
8084
- console.error(`Service unreachable: ${msg}`);
8123
+ console.error(`Service unreachable: ${getErrorMessage(err)}`);
8085
8124
  process.exitCode = 1;
8086
8125
  }
8087
8126
  });
@@ -8099,8 +8138,7 @@ function createServiceCli(descriptor) {
8099
8138
  console.log(JSON.stringify(result, null, 2));
8100
8139
  }
8101
8140
  catch (err) {
8102
- const msg = err instanceof Error ? err.message : String(err);
8103
- console.error(`Config query failed: ${msg}`);
8141
+ console.error(`Config query failed: ${getErrorMessage(err)}`);
8104
8142
  process.exitCode = 1;
8105
8143
  }
8106
8144
  });
@@ -8116,8 +8154,7 @@ function createServiceCli(descriptor) {
8116
8154
  console.log('Config is valid.');
8117
8155
  }
8118
8156
  catch (err) {
8119
- const msg = err instanceof Error ? err.message : String(err);
8120
- console.error(`Validation failed: ${msg}`);
8157
+ console.error(`Validation failed: ${getErrorMessage(err)}`);
8121
8158
  process.exitCode = 1;
8122
8159
  }
8123
8160
  });
@@ -8154,8 +8191,7 @@ function createServiceCli(descriptor) {
8154
8191
  console.log(JSON.stringify(result, null, 2));
8155
8192
  }
8156
8193
  catch (err) {
8157
- const msg = err instanceof Error ? err.message : String(err);
8158
- console.error(`Config apply failed: ${msg}`);
8194
+ console.error(`Config apply failed: ${getErrorMessage(err)}`);
8159
8195
  process.exitCode = 1;
8160
8196
  }
8161
8197
  });
@@ -8192,9 +8228,7 @@ function createServiceCli(descriptor) {
8192
8228
  console.log(`Service "${opts.name}" installed.`);
8193
8229
  }
8194
8230
  catch (err) {
8195
- const msg = err instanceof Error ? err.message : String(err);
8196
- console.error(`Install failed: ${msg}`);
8197
- process.exitCode = 1;
8231
+ handleCommandError('Install', err);
8198
8232
  }
8199
8233
  });
8200
8234
  serviceCmd
@@ -8207,9 +8241,7 @@ function createServiceCli(descriptor) {
8207
8241
  console.log(`Service "${opts.name}" uninstalled.`);
8208
8242
  }
8209
8243
  catch (err) {
8210
- const msg = err instanceof Error ? err.message : String(err);
8211
- console.error(`Uninstall failed: ${msg}`);
8212
- process.exitCode = 1;
8244
+ handleCommandError('Uninstall', err);
8213
8245
  }
8214
8246
  });
8215
8247
  serviceCmd
@@ -8222,9 +8254,7 @@ function createServiceCli(descriptor) {
8222
8254
  console.log(`Service "${opts.name}" started.`);
8223
8255
  }
8224
8256
  catch (err) {
8225
- const msg = err instanceof Error ? err.message : String(err);
8226
- console.error(`Start failed: ${msg}`);
8227
- process.exitCode = 1;
8257
+ handleCommandError('Start', err);
8228
8258
  }
8229
8259
  });
8230
8260
  serviceCmd
@@ -8237,9 +8267,7 @@ function createServiceCli(descriptor) {
8237
8267
  console.log(`Service "${opts.name}" stopped.`);
8238
8268
  }
8239
8269
  catch (err) {
8240
- const msg = err instanceof Error ? err.message : String(err);
8241
- console.error(`Stop failed: ${msg}`);
8242
- process.exitCode = 1;
8270
+ handleCommandError('Stop', err);
8243
8271
  }
8244
8272
  });
8245
8273
  serviceCmd
@@ -8252,9 +8280,7 @@ function createServiceCli(descriptor) {
8252
8280
  console.log(`Service "${opts.name}" restarted.`);
8253
8281
  }
8254
8282
  catch (err) {
8255
- const msg = err instanceof Error ? err.message : String(err);
8256
- console.error(`Restart failed: ${msg}`);
8257
- process.exitCode = 1;
8283
+ handleCommandError('Restart', err);
8258
8284
  }
8259
8285
  });
8260
8286
  serviceCmd
@@ -8267,9 +8293,7 @@ function createServiceCli(descriptor) {
8267
8293
  console.log(`Service "${opts.name}": ${state}`);
8268
8294
  }
8269
8295
  catch (err) {
8270
- const msg = err instanceof Error ? err.message : String(err);
8271
- console.error(`Status failed: ${msg}`);
8272
- process.exitCode = 1;
8296
+ handleCommandError('Status', err);
8273
8297
  }
8274
8298
  });
8275
8299
  // Apply custom CLI commands if provided
@@ -9566,7 +9590,10 @@ function createLogger(config) {
9566
9590
  *
9567
9591
  * @module prompts
9568
9592
  */
9569
- const promptDir = dirname(fileURLToPath(import.meta.url));
9593
+ const packageRoot = packageDirectorySync({
9594
+ cwd: fileURLToPath(import.meta.url),
9595
+ });
9596
+ const promptDir = join(packageRoot, 'dist', 'prompts');
9570
9597
  /** Built-in default architect prompt. */
9571
9598
  const DEFAULT_ARCHITECT_PROMPT = readFileSync(join(promptDir, 'architect.md'), 'utf8');
9572
9599
  /** Built-in default critic prompt. */
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import fs, { readdirSync, readFileSync, existsSync, writeFileSync, renameSync, unlinkSync, mkdirSync, copyFileSync, statSync, watchFile } from 'node:fs';
2
- import path, { join, dirname, resolve, relative, posix } from 'node:path';
2
+ import path, { join, dirname, resolve, basename, relative, posix } from 'node:path';
3
3
  import { unlink, readFile, mkdir, writeFile, copyFile } from 'node:fs/promises';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import process$1 from 'node:process';
6
+ import { randomUUID, createHash } from 'node:crypto';
6
7
  import require$$0$4 from 'path';
7
8
  import require$$0$3 from 'fs';
8
9
  import require$$0$1 from 'constants';
@@ -15,7 +16,6 @@ import { z } from 'zod';
15
16
  import * as commander from 'commander';
16
17
  import { tmpdir } from 'node:os';
17
18
  import 'node:child_process';
18
- import { randomUUID, createHash } from 'node:crypto';
19
19
  import pino from 'pino';
20
20
  import Handlebars from 'handlebars';
21
21
  import { Cron } from 'croner';
@@ -7287,30 +7287,64 @@ function getCoreConfigDir() {
7287
7287
  function getComponentConfigDir(componentName) {
7288
7288
  throw new Error('jeeves-core: init() must be called first');
7289
7289
  }
7290
+ /** Maximum rename retry attempts on EPERM. */
7291
+ const ATOMIC_WRITE_MAX_RETRIES = 3;
7292
+ /** Delay between EPERM retries in milliseconds. */
7293
+ const ATOMIC_WRITE_RETRY_DELAY_MS = 100;
7290
7294
  /**
7291
7295
  * Write content to a file atomically via a temp file + rename.
7292
7296
  *
7297
+ * @remarks
7298
+ * Retries the rename up to three times on EPERM (Windows file-handle
7299
+ * contention) with a 100 ms synchronous delay between attempts.
7300
+ *
7293
7301
  * @param filePath - Absolute path to the target file.
7294
7302
  * @param content - Content to write.
7295
7303
  */
7296
7304
  function atomicWrite(filePath, content) {
7297
7305
  const dir = dirname(filePath);
7298
- const tempPath = join(dir, `.${String(Date.now())}.tmp`);
7306
+ const base = basename(filePath, '.md');
7307
+ const tempPath = join(dir, `.${base}.${String(Date.now())}.${randomUUID().slice(0, 8)}.tmp`);
7299
7308
  writeFileSync(tempPath, content, 'utf-8');
7300
- try {
7301
- renameSync(tempPath, filePath);
7302
- }
7303
- catch (err) {
7309
+ for (let attempt = 0; attempt < ATOMIC_WRITE_MAX_RETRIES; attempt++) {
7304
7310
  try {
7305
- unlinkSync(tempPath);
7311
+ renameSync(tempPath, filePath);
7312
+ return;
7306
7313
  }
7307
- catch {
7308
- /* best-effort cleanup */
7314
+ catch (err) {
7315
+ const isEperm = err instanceof Error &&
7316
+ 'code' in err &&
7317
+ err.code === 'EPERM';
7318
+ if (!isEperm || attempt === ATOMIC_WRITE_MAX_RETRIES - 1) {
7319
+ try {
7320
+ unlinkSync(tempPath);
7321
+ }
7322
+ catch {
7323
+ /* best-effort cleanup */
7324
+ }
7325
+ throw err;
7326
+ }
7327
+ // Synchronous sleep before retry (acceptable in atomic write context)
7328
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ATOMIC_WRITE_RETRY_DELAY_MS);
7309
7329
  }
7310
- throw err;
7311
7330
  }
7312
7331
  }
7313
7332
 
7333
+ /**
7334
+ * Shared internal utility functions.
7335
+ *
7336
+ * @packageDocumentation
7337
+ */
7338
+ /**
7339
+ * Extract a human-readable message from an unknown caught value.
7340
+ *
7341
+ * @param err - The caught value (typically `unknown`).
7342
+ * @returns The error message string.
7343
+ */
7344
+ function getErrorMessage(err) {
7345
+ return err instanceof Error ? err.message : String(err);
7346
+ }
7347
+
7314
7348
  /**
7315
7349
  * Factory for a framework-agnostic config apply HTTP handler.
7316
7350
  *
@@ -7363,8 +7397,7 @@ function readConfigFile(filePath) {
7363
7397
  return JSON.parse(raw);
7364
7398
  }
7365
7399
  catch (err) {
7366
- const msg = err instanceof Error ? err.message : String(err);
7367
- console.warn(`jeeves-core: Could not read config file ${filePath}: ${msg}`);
7400
+ console.warn(`jeeves-core: Could not read config file ${filePath}: ${getErrorMessage(err)}`);
7368
7401
  return {};
7369
7402
  }
7370
7403
  }
@@ -7413,10 +7446,9 @@ function createConfigApplyHandler(descriptor) {
7413
7446
  atomicWrite(configPath, json);
7414
7447
  }
7415
7448
  catch (err) {
7416
- const message = err instanceof Error ? err.message : String(err);
7417
7449
  return {
7418
7450
  status: 500,
7419
- body: { error: `Failed to write config: ${message}` },
7451
+ body: { error: `Failed to write config: ${getErrorMessage(err)}` },
7420
7452
  };
7421
7453
  }
7422
7454
  // Call onConfigApply callback if defined
@@ -7425,12 +7457,11 @@ function createConfigApplyHandler(descriptor) {
7425
7457
  await descriptor.onConfigApply(validatedConfig);
7426
7458
  }
7427
7459
  catch (err) {
7428
- const message = err instanceof Error ? err.message : String(err);
7429
7460
  return {
7430
7461
  status: 200,
7431
7462
  body: {
7432
7463
  applied: true,
7433
- warning: `Config written but callback failed: ${message}`,
7464
+ warning: `Config written but callback failed: ${getErrorMessage(err)}`,
7434
7465
  config: validatedConfig,
7435
7466
  },
7436
7467
  };
@@ -7513,8 +7544,7 @@ function createStatusHandler(options) {
7513
7544
  health = await options.getHealth();
7514
7545
  }
7515
7546
  catch (err) {
7516
- const message = err instanceof Error ? err.message : String(err);
7517
- health = { error: message };
7547
+ health = { error: getErrorMessage(err) };
7518
7548
  overallStatus = 'degraded';
7519
7549
  }
7520
7550
  }
@@ -9279,7 +9309,10 @@ function createLogger(config) {
9279
9309
  *
9280
9310
  * @module prompts
9281
9311
  */
9282
- const promptDir = dirname(fileURLToPath(import.meta.url));
9312
+ const packageRoot = packageDirectorySync({
9313
+ cwd: fileURLToPath(import.meta.url),
9314
+ });
9315
+ const promptDir = join(packageRoot, 'dist', 'prompts');
9283
9316
  /** Built-in default architect prompt. */
9284
9317
  const DEFAULT_ARCHITECT_PROMPT = readFileSync(join(promptDir, 'architect.md'), 'utf8');
9285
9318
  /** Built-in default critic prompt. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karmaniverous/jeeves-meta",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "author": "Jason Williscroft",
5
5
  "description": "Fastify HTTP service for the Jeeves Meta synthesis engine",
6
6
  "license": "BSD-3-Clause",
@@ -41,33 +41,33 @@
41
41
  "node": ">=22"
42
42
  },
43
43
  "dependencies": {
44
- "@karmaniverous/jeeves": "^0.5.1",
44
+ "@karmaniverous/jeeves": "^0.5.3",
45
45
  "commander": "^14",
46
46
  "croner": "^10",
47
47
  "fastify": "^5.8",
48
- "handlebars": "^4.7.8",
48
+ "handlebars": "^4.7.9",
49
49
  "package-directory": "^8.2.0",
50
50
  "pino": "^10",
51
51
  "zod": "^4.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@commander-js/extra-typings": "^14.0.0",
55
- "@dotenvx/dotenvx": "^1.55.1",
55
+ "@dotenvx/dotenvx": "^1.59.1",
56
56
  "@rollup/plugin-commonjs": "^29.0.2",
57
57
  "@rollup/plugin-json": "^6.1.0",
58
58
  "@rollup/plugin-node-resolve": "^16.0.3",
59
59
  "@rollup/plugin-typescript": "^12.3.0",
60
- "@types/node": "^25.5.0",
61
- "@vitest/coverage-v8": "^4.1.0",
60
+ "@types/node": "^25.5.2",
61
+ "@vitest/coverage-v8": "^4.1.2",
62
62
  "auto-changelog": "^2.5.0",
63
63
  "cross-env": "^10.1.0",
64
64
  "knip": "^5.87.0",
65
65
  "release-it": "^19.2.4",
66
- "rollup": "^4.59.0",
66
+ "rollup": "^4.60.1",
67
67
  "rollup-plugin-copy": "^3.5.0",
68
- "rollup-plugin-dts": "^6.4.0",
68
+ "rollup-plugin-dts": "^6.4.1",
69
69
  "tslib": "^2.8.1",
70
- "vitest": "^4.1.0"
70
+ "vitest": "^4.1.2"
71
71
  },
72
72
  "scripts": {
73
73
  "build": "rimraf dist && cross-env NO_COLOR=1 rollup --config rollup.config.mjs",