@karmaniverous/jeeves-meta 0.12.4 → 0.13.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 +4 -4
- package/dist/cli/jeeves-meta/index.js +322 -218
- package/dist/index.d.ts +5 -6
- package/dist/index.js +286 -186
- package/package.json +3 -3
|
@@ -9,13 +9,13 @@ import require$$4 from 'util';
|
|
|
9
9
|
import require$$5 from 'assert';
|
|
10
10
|
import require$$2 from 'events';
|
|
11
11
|
import vm from 'vm';
|
|
12
|
+
import { z } from 'zod';
|
|
12
13
|
import * as commander from 'commander';
|
|
13
14
|
import { homedir, tmpdir } from 'node:os';
|
|
14
|
-
import { z } from 'zod';
|
|
15
15
|
import { execSync } from 'node:child_process';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import { readFile, unlink, mkdir, writeFile, copyFile } from 'node:fs/promises';
|
|
18
17
|
import { randomUUID, createHash } from 'node:crypto';
|
|
18
|
+
import { readFile, unlink, mkdir, writeFile, copyFile } from 'node:fs/promises';
|
|
19
19
|
import pino from 'pino';
|
|
20
20
|
import Handlebars from 'handlebars';
|
|
21
21
|
import { Cron } from 'croner';
|
|
@@ -7017,6 +7017,32 @@ const COMPONENT_CONFIG_PREFIX = 'jeeves-';
|
|
|
7017
7017
|
/** Core config file name. */
|
|
7018
7018
|
const CONFIG_FILE = 'config.json';
|
|
7019
7019
|
|
|
7020
|
+
/**
|
|
7021
|
+
* Default port assignments for Jeeves platform services.
|
|
7022
|
+
*
|
|
7023
|
+
* @remarks
|
|
7024
|
+
* Each port number is a historical reference:
|
|
7025
|
+
* - 1934: Wodehouse's *Thank You, Jeeves*; Popper's *Logic of Scientific Discovery*
|
|
7026
|
+
* - 1936: Turing's "On Computable Numbers"; Church's lambda calculus
|
|
7027
|
+
* - 1937: Turing's paper in *Proceedings of the London Mathematical Society*
|
|
7028
|
+
* - 1938: Wodehouse's *The Code of the Woosters*; Shannon's relay/switching paper
|
|
7029
|
+
*/
|
|
7030
|
+
/** Default port for jeeves-server. */
|
|
7031
|
+
const SERVER_PORT = 1934;
|
|
7032
|
+
/** Default port for jeeves-watcher. */
|
|
7033
|
+
const WATCHER_PORT = 1936;
|
|
7034
|
+
/** Default port for jeeves-runner. */
|
|
7035
|
+
const RUNNER_PORT = 1937;
|
|
7036
|
+
/** Default port for jeeves-meta. */
|
|
7037
|
+
const META_PORT = 1938;
|
|
7038
|
+
/** Map of service names to their default ports. */
|
|
7039
|
+
const DEFAULT_PORTS = {
|
|
7040
|
+
server: SERVER_PORT,
|
|
7041
|
+
watcher: WATCHER_PORT,
|
|
7042
|
+
runner: RUNNER_PORT,
|
|
7043
|
+
meta: META_PORT,
|
|
7044
|
+
};
|
|
7045
|
+
|
|
7020
7046
|
/**
|
|
7021
7047
|
* Workspace and config root initialization.
|
|
7022
7048
|
*
|
|
@@ -7305,6 +7331,64 @@ function createStatusHandler(options) {
|
|
|
7305
7331
|
};
|
|
7306
7332
|
};
|
|
7307
7333
|
}
|
|
7334
|
+
/** Core shared config section. */
|
|
7335
|
+
const workspaceCoreConfigSchema = z
|
|
7336
|
+
.object({
|
|
7337
|
+
/** Workspace root path. */
|
|
7338
|
+
workspace: z.string().optional().describe('Workspace root path'),
|
|
7339
|
+
/** Platform config root path. */
|
|
7340
|
+
configRoot: z.string().optional().describe('Platform config root path'),
|
|
7341
|
+
/** OpenClaw gateway URL. */
|
|
7342
|
+
gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
|
|
7343
|
+
})
|
|
7344
|
+
.partial();
|
|
7345
|
+
/** Memory shared config section. */
|
|
7346
|
+
const workspaceMemoryConfigSchema = z
|
|
7347
|
+
.object({
|
|
7348
|
+
/** MEMORY.md character budget. */
|
|
7349
|
+
budget: z.number().int().positive().optional().describe('Memory budget'),
|
|
7350
|
+
/** Warning threshold as a fraction of budget. */
|
|
7351
|
+
warningThreshold: z
|
|
7352
|
+
.number()
|
|
7353
|
+
.min(0)
|
|
7354
|
+
.max(1)
|
|
7355
|
+
.optional()
|
|
7356
|
+
.describe('Memory warning threshold'),
|
|
7357
|
+
/** Staleness threshold in days. */
|
|
7358
|
+
staleDays: z
|
|
7359
|
+
.number()
|
|
7360
|
+
.int()
|
|
7361
|
+
.positive()
|
|
7362
|
+
.optional()
|
|
7363
|
+
.describe('Memory staleness threshold in days'),
|
|
7364
|
+
})
|
|
7365
|
+
.partial();
|
|
7366
|
+
/** Workspace config Zod schema. */
|
|
7367
|
+
z.object({
|
|
7368
|
+
/** JSON Schema pointer for IDE autocomplete. */
|
|
7369
|
+
$schema: z.string().optional().describe('JSON Schema pointer'),
|
|
7370
|
+
/** Core shared defaults. */
|
|
7371
|
+
core: workspaceCoreConfigSchema.optional(),
|
|
7372
|
+
/** Memory hygiene shared defaults. */
|
|
7373
|
+
memory: workspaceMemoryConfigSchema.optional(),
|
|
7374
|
+
});
|
|
7375
|
+
/** Built-in workspace config defaults. */
|
|
7376
|
+
const WORKSPACE_CONFIG_DEFAULTS = {
|
|
7377
|
+
core: {
|
|
7378
|
+
workspace: '.',
|
|
7379
|
+
configRoot: './config'}};
|
|
7380
|
+
|
|
7381
|
+
/**
|
|
7382
|
+
* Shared CLI defaults and resolution for Jeeves CLI commands.
|
|
7383
|
+
*
|
|
7384
|
+
* @remarks
|
|
7385
|
+
* All root CLI commands share workspace/config-root resolution. Values follow
|
|
7386
|
+
* the shared precedence model: flags → env → jeeves.config.json → defaults.
|
|
7387
|
+
*/
|
|
7388
|
+
/** Default workspace path. */
|
|
7389
|
+
const DEFAULT_WORKSPACE = WORKSPACE_CONFIG_DEFAULTS.core.workspace;
|
|
7390
|
+
/** Default config root path. */
|
|
7391
|
+
const DEFAULT_CONFIG_ROOT = WORKSPACE_CONFIG_DEFAULTS.core.configRoot;
|
|
7308
7392
|
|
|
7309
7393
|
function getDefaultExportFromCjs (x) {
|
|
7310
7394
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -7690,7 +7774,7 @@ function isExecError(err) {
|
|
|
7690
7774
|
* (Windows), systemd (Linux), or launchd (macOS) based on platform.
|
|
7691
7775
|
*/
|
|
7692
7776
|
/** Exec helper that returns stdout. */
|
|
7693
|
-
function run(cmd) {
|
|
7777
|
+
function run$1(cmd) {
|
|
7694
7778
|
return execSync(cmd, {
|
|
7695
7779
|
encoding: 'utf-8',
|
|
7696
7780
|
timeout: 30_000,
|
|
@@ -7743,31 +7827,31 @@ function createWindowsManager(descriptor) {
|
|
|
7743
7827
|
const cmdArgs = descriptor.startCommand(cfgPath);
|
|
7744
7828
|
const appPath = cmdArgs[0];
|
|
7745
7829
|
const appArgs = cmdArgs.slice(1).join(' ');
|
|
7746
|
-
run(`nssm install ${svcName} ${appPath}`);
|
|
7830
|
+
run$1(`nssm install ${svcName} ${appPath}`);
|
|
7747
7831
|
if (appArgs) {
|
|
7748
|
-
run(`nssm set ${svcName} AppParameters ${appArgs}`);
|
|
7832
|
+
run$1(`nssm set ${svcName} AppParameters ${appArgs}`);
|
|
7749
7833
|
}
|
|
7750
|
-
run(`nssm set ${svcName} AppStdout ${join(homedir(), `${svcName}.log`)}`);
|
|
7751
|
-
run(`nssm set ${svcName} AppStderr ${join(homedir(), `${svcName}.log`)}`);
|
|
7752
|
-
run(`nssm set ${svcName} AppRotateFiles 1`);
|
|
7753
|
-
run(`nssm set ${svcName} AppRotateBytes 1048576`);
|
|
7834
|
+
run$1(`nssm set ${svcName} AppStdout ${join(homedir(), `${svcName}.log`)}`);
|
|
7835
|
+
run$1(`nssm set ${svcName} AppStderr ${join(homedir(), `${svcName}.log`)}`);
|
|
7836
|
+
run$1(`nssm set ${svcName} AppRotateFiles 1`);
|
|
7837
|
+
run$1(`nssm set ${svcName} AppRotateBytes 1048576`);
|
|
7754
7838
|
},
|
|
7755
7839
|
uninstall(options) {
|
|
7756
7840
|
const svcName = resolveServiceName(descriptor, options);
|
|
7757
7841
|
runQuiet(`nssm stop ${svcName}`);
|
|
7758
|
-
run(`nssm remove ${svcName} confirm`);
|
|
7842
|
+
run$1(`nssm remove ${svcName} confirm`);
|
|
7759
7843
|
},
|
|
7760
7844
|
start(options) {
|
|
7761
7845
|
const svcName = resolveServiceName(descriptor, options);
|
|
7762
|
-
run(`nssm start ${svcName}`);
|
|
7846
|
+
run$1(`nssm start ${svcName}`);
|
|
7763
7847
|
},
|
|
7764
7848
|
stop(options) {
|
|
7765
7849
|
const svcName = resolveServiceName(descriptor, options);
|
|
7766
|
-
run(`nssm stop ${svcName}`);
|
|
7850
|
+
run$1(`nssm stop ${svcName}`);
|
|
7767
7851
|
},
|
|
7768
7852
|
restart(options) {
|
|
7769
7853
|
const svcName = resolveServiceName(descriptor, options);
|
|
7770
|
-
run(`nssm restart ${svcName}`);
|
|
7854
|
+
run$1(`nssm restart ${svcName}`);
|
|
7771
7855
|
},
|
|
7772
7856
|
status(options) {
|
|
7773
7857
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7812,8 +7896,8 @@ function createLinuxManager(descriptor) {
|
|
|
7812
7896
|
const cmdArgs = descriptor.startCommand(cfgPath);
|
|
7813
7897
|
mkdirSync(unitDir, { recursive: true });
|
|
7814
7898
|
writeFileSync(unitPath(svcName), buildSystemdUnit(svcName, cmdArgs));
|
|
7815
|
-
run('systemctl --user daemon-reload');
|
|
7816
|
-
run(`systemctl --user enable ${svcName}.service`);
|
|
7899
|
+
run$1('systemctl --user daemon-reload');
|
|
7900
|
+
run$1(`systemctl --user enable ${svcName}.service`);
|
|
7817
7901
|
},
|
|
7818
7902
|
uninstall(options) {
|
|
7819
7903
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7826,15 +7910,15 @@ function createLinuxManager(descriptor) {
|
|
|
7826
7910
|
},
|
|
7827
7911
|
start(options) {
|
|
7828
7912
|
const svcName = resolveServiceName(descriptor, options);
|
|
7829
|
-
run(`systemctl --user start ${svcName}.service`);
|
|
7913
|
+
run$1(`systemctl --user start ${svcName}.service`);
|
|
7830
7914
|
},
|
|
7831
7915
|
stop(options) {
|
|
7832
7916
|
const svcName = resolveServiceName(descriptor, options);
|
|
7833
|
-
run(`systemctl --user stop ${svcName}.service`);
|
|
7917
|
+
run$1(`systemctl --user stop ${svcName}.service`);
|
|
7834
7918
|
},
|
|
7835
7919
|
restart(options) {
|
|
7836
7920
|
const svcName = resolveServiceName(descriptor, options);
|
|
7837
|
-
run(`systemctl --user restart ${svcName}.service`);
|
|
7921
|
+
run$1(`systemctl --user restart ${svcName}.service`);
|
|
7838
7922
|
},
|
|
7839
7923
|
status(options) {
|
|
7840
7924
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7898,16 +7982,16 @@ function createMacOSManager(descriptor) {
|
|
|
7898
7982
|
},
|
|
7899
7983
|
start(options) {
|
|
7900
7984
|
const svcName = resolveServiceName(descriptor, options);
|
|
7901
|
-
run(`launchctl load ${plistPath(svcName)}`);
|
|
7985
|
+
run$1(`launchctl load ${plistPath(svcName)}`);
|
|
7902
7986
|
},
|
|
7903
7987
|
stop(options) {
|
|
7904
7988
|
const svcName = resolveServiceName(descriptor, options);
|
|
7905
|
-
run(`launchctl unload ${plistPath(svcName)}`);
|
|
7989
|
+
run$1(`launchctl unload ${plistPath(svcName)}`);
|
|
7906
7990
|
},
|
|
7907
7991
|
restart(options) {
|
|
7908
7992
|
const svcName = resolveServiceName(descriptor, options);
|
|
7909
7993
|
runQuiet(`launchctl unload ${plistPath(svcName)}`);
|
|
7910
|
-
run(`launchctl load ${plistPath(svcName)}`);
|
|
7994
|
+
run$1(`launchctl load ${plistPath(svcName)}`);
|
|
7911
7995
|
},
|
|
7912
7996
|
status(options) {
|
|
7913
7997
|
const svcName = resolveServiceName(descriptor, options);
|
|
@@ -7936,19 +8020,6 @@ function createServiceManager(descriptor) {
|
|
|
7936
8020
|
}
|
|
7937
8021
|
}
|
|
7938
8022
|
|
|
7939
|
-
/**
|
|
7940
|
-
* Shared CLI defaults and option registration for Jeeves CLI commands.
|
|
7941
|
-
*
|
|
7942
|
-
* @remarks
|
|
7943
|
-
* All three CLI commands (install, uninstall, status) share the same
|
|
7944
|
-
* `--workspace` and `--config-root` options with the same defaults.
|
|
7945
|
-
* This module centralizes them to eliminate duplication.
|
|
7946
|
-
*/
|
|
7947
|
-
/** Default workspace path (current directory). */
|
|
7948
|
-
const DEFAULT_WORKSPACE = '.';
|
|
7949
|
-
/** Default config root path. */
|
|
7950
|
-
const DEFAULT_CONFIG_ROOT = './config';
|
|
7951
|
-
|
|
7952
8023
|
/**
|
|
7953
8024
|
* Factory for the standard Jeeves service CLI.
|
|
7954
8025
|
*
|
|
@@ -8283,6 +8354,38 @@ function loadConfig(configDir) {
|
|
|
8283
8354
|
}
|
|
8284
8355
|
}
|
|
8285
8356
|
|
|
8357
|
+
/**
|
|
8358
|
+
* Service URL resolution.
|
|
8359
|
+
*
|
|
8360
|
+
* @remarks
|
|
8361
|
+
* Resolves the URL for a named Jeeves service using the following
|
|
8362
|
+
* resolution order:
|
|
8363
|
+
* 1. Consumer's own component config
|
|
8364
|
+
* 2. Core config (`{configRoot}/jeeves-core/config.json`)
|
|
8365
|
+
* 3. Default port constants
|
|
8366
|
+
*/
|
|
8367
|
+
/**
|
|
8368
|
+
* Resolve the URL for a named Jeeves service.
|
|
8369
|
+
*
|
|
8370
|
+
* @param serviceName - The service name (e.g., 'watcher', 'runner').
|
|
8371
|
+
* @param consumerName - Optional consumer component name for config override.
|
|
8372
|
+
* @returns The resolved service URL.
|
|
8373
|
+
* @throws Error if `init()` has not been called or the service is unknown.
|
|
8374
|
+
*/
|
|
8375
|
+
function getServiceUrl(serviceName, consumerName) {
|
|
8376
|
+
// 2. Check core config
|
|
8377
|
+
const coreDir = getCoreConfigDir();
|
|
8378
|
+
const coreConfig = loadConfig(coreDir);
|
|
8379
|
+
const coreUrl = coreConfig?.services[serviceName]?.url;
|
|
8380
|
+
if (coreUrl)
|
|
8381
|
+
return coreUrl;
|
|
8382
|
+
// 3. Fall back to port constants
|
|
8383
|
+
const port = DEFAULT_PORTS[serviceName];
|
|
8384
|
+
{
|
|
8385
|
+
return `http://127.0.0.1:${String(port)}`;
|
|
8386
|
+
}
|
|
8387
|
+
}
|
|
8388
|
+
|
|
8286
8389
|
/**
|
|
8287
8390
|
* Resolve the bind address for a Jeeves service.
|
|
8288
8391
|
*
|
|
@@ -8320,6 +8423,10 @@ function getBindAddress(componentName) {
|
|
|
8320
8423
|
// Tier 4: Default
|
|
8321
8424
|
return DEFAULT_BIND_ADDRESS;
|
|
8322
8425
|
}
|
|
8426
|
+
/** Async sleep via setTimeout. */
|
|
8427
|
+
function sleepAsync(ms) {
|
|
8428
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
8429
|
+
}
|
|
8323
8430
|
|
|
8324
8431
|
/**
|
|
8325
8432
|
* Shared live config hot-reload support.
|
|
@@ -8337,7 +8444,6 @@ function getBindAddress(componentName) {
|
|
|
8337
8444
|
*/
|
|
8338
8445
|
const RESTART_REQUIRED_FIELDS = [
|
|
8339
8446
|
'port',
|
|
8340
|
-
'host',
|
|
8341
8447
|
'watcherUrl',
|
|
8342
8448
|
'gatewayUrl',
|
|
8343
8449
|
'gatewayApiKey',
|
|
@@ -8449,8 +8555,6 @@ const autoSeedRuleSchema = z.object({
|
|
|
8449
8555
|
const serviceConfigSchema = metaConfigSchema.extend({
|
|
8450
8556
|
/** HTTP port for the service (default: 1938). */
|
|
8451
8557
|
port: z.number().int().min(1).max(65535).default(1938),
|
|
8452
|
-
/** Bind address for the HTTP server (default: 127.0.0.1). */
|
|
8453
|
-
host: z.string().default('127.0.0.1'),
|
|
8454
8558
|
/** Cron schedule for synthesis cycles (default: every 30 min). */
|
|
8455
8559
|
schedule: z.string().default('*/30 * * * *'),
|
|
8456
8560
|
/** Optional channel identifier for reporting. */
|
|
@@ -8810,6 +8914,174 @@ async function readMetaJson(metaPath) {
|
|
|
8810
8914
|
return JSON.parse(raw);
|
|
8811
8915
|
}
|
|
8812
8916
|
|
|
8917
|
+
/**
|
|
8918
|
+
* Escape special glob characters in a path so it can be used as a literal
|
|
8919
|
+
* prefix in glob patterns.
|
|
8920
|
+
*
|
|
8921
|
+
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
8922
|
+
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
8923
|
+
* special characters are matched literally by the watcher's walk endpoint.
|
|
8924
|
+
*
|
|
8925
|
+
* @module escapeGlob
|
|
8926
|
+
*/
|
|
8927
|
+
/**
|
|
8928
|
+
* Escape glob metacharacters in a string using character-class wrapping.
|
|
8929
|
+
*
|
|
8930
|
+
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
8931
|
+
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
8932
|
+
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
8933
|
+
*
|
|
8934
|
+
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
8935
|
+
*
|
|
8936
|
+
* @param s - Raw path string.
|
|
8937
|
+
* @returns String with glob metacharacters wrapped in character classes.
|
|
8938
|
+
*/
|
|
8939
|
+
function escapeGlob(s) {
|
|
8940
|
+
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
8941
|
+
}
|
|
8942
|
+
|
|
8943
|
+
/**
|
|
8944
|
+
* Filter file paths by modification time.
|
|
8945
|
+
*
|
|
8946
|
+
* Shared utility for staleness detection and delta file enumeration.
|
|
8947
|
+
* Uses `fs.statSync` for fast local mtime checks on known paths.
|
|
8948
|
+
*
|
|
8949
|
+
* @module mtimeFilter
|
|
8950
|
+
*/
|
|
8951
|
+
/**
|
|
8952
|
+
* Check if any file in the list was modified after the given timestamp.
|
|
8953
|
+
*
|
|
8954
|
+
* Short-circuits on first match for efficiency (staleness checks).
|
|
8955
|
+
*
|
|
8956
|
+
* @param files - Array of file paths to check.
|
|
8957
|
+
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` match.
|
|
8958
|
+
* @returns True if any file was modified after the timestamp.
|
|
8959
|
+
*/
|
|
8960
|
+
function hasModifiedAfter(files, afterMs) {
|
|
8961
|
+
for (const filePath of files) {
|
|
8962
|
+
try {
|
|
8963
|
+
if (statSync(filePath).mtimeMs > afterMs)
|
|
8964
|
+
return true;
|
|
8965
|
+
}
|
|
8966
|
+
catch {
|
|
8967
|
+
// Unreadable file — skip
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
return false;
|
|
8971
|
+
}
|
|
8972
|
+
/**
|
|
8973
|
+
* Filter files to only those modified after the given timestamp.
|
|
8974
|
+
*
|
|
8975
|
+
* @param files - Array of file paths to filter.
|
|
8976
|
+
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` are included.
|
|
8977
|
+
* @returns Filtered array of file paths.
|
|
8978
|
+
*/
|
|
8979
|
+
function filterModifiedAfter(files, afterMs) {
|
|
8980
|
+
return files.filter((filePath) => {
|
|
8981
|
+
try {
|
|
8982
|
+
return statSync(filePath).mtimeMs > afterMs;
|
|
8983
|
+
}
|
|
8984
|
+
catch {
|
|
8985
|
+
return false;
|
|
8986
|
+
}
|
|
8987
|
+
});
|
|
8988
|
+
}
|
|
8989
|
+
|
|
8990
|
+
/**
|
|
8991
|
+
* Staleness detection via watcher walk.
|
|
8992
|
+
*
|
|
8993
|
+
* A meta is stale when any watched file in its scope was modified after
|
|
8994
|
+
* `_generatedAt`.
|
|
8995
|
+
*
|
|
8996
|
+
* @module scheduling/staleness
|
|
8997
|
+
*/
|
|
8998
|
+
/**
|
|
8999
|
+
* Check if a meta is stale.
|
|
9000
|
+
*
|
|
9001
|
+
* Uses watcher `/walk` to enumerate watched files under the scope prefix,
|
|
9002
|
+
* then applies a local mtime check (fast) to detect any modifications since
|
|
9003
|
+
* `_generatedAt`. Short-circuits on first match.
|
|
9004
|
+
*
|
|
9005
|
+
* @param scopePrefix - Path prefix for this meta's scope.
|
|
9006
|
+
* @param meta - Current meta.json content.
|
|
9007
|
+
* @param watcher - WatcherClient instance.
|
|
9008
|
+
* @returns True if any file in scope was modified after `_generatedAt`.
|
|
9009
|
+
*/
|
|
9010
|
+
async function isStale(scopePrefix, meta, watcher) {
|
|
9011
|
+
if (!meta._generatedAt)
|
|
9012
|
+
return true; // Never synthesized = stale
|
|
9013
|
+
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
9014
|
+
// Exclude .meta/ subtree — synthesis outputs must not trigger staleness.
|
|
9015
|
+
// Handle both forward and back slashes for cross-platform compatibility.
|
|
9016
|
+
const metaSep = /[/\\]\.meta(?:[/\\]|$)/;
|
|
9017
|
+
const filtered = files.filter((f) => !metaSep.test(f));
|
|
9018
|
+
return hasModifiedAfter(filtered, new Date(meta._generatedAt).getTime());
|
|
9019
|
+
}
|
|
9020
|
+
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
9021
|
+
const MAX_STALENESS_SECONDS = 365 * 86_400;
|
|
9022
|
+
/**
|
|
9023
|
+
* Compute actual staleness in seconds (now minus _generatedAt).
|
|
9024
|
+
*
|
|
9025
|
+
* Never-synthesized metas are capped at {@link MAX_STALENESS_SECONDS}
|
|
9026
|
+
* (1 year) so that depth weighting can differentiate them. Without
|
|
9027
|
+
* bounding, `Infinity * depthFactor` = `Infinity` for all depths.
|
|
9028
|
+
*
|
|
9029
|
+
* @param meta - Current meta.json content.
|
|
9030
|
+
* @returns Staleness in seconds, capped at 1 year for never-synthesized metas.
|
|
9031
|
+
*/
|
|
9032
|
+
function actualStaleness(meta) {
|
|
9033
|
+
if (!meta._generatedAt)
|
|
9034
|
+
return MAX_STALENESS_SECONDS;
|
|
9035
|
+
const generatedMs = new Date(meta._generatedAt).getTime();
|
|
9036
|
+
return Math.min((Date.now() - generatedMs) / 1000, MAX_STALENESS_SECONDS);
|
|
9037
|
+
}
|
|
9038
|
+
/**
|
|
9039
|
+
* Check whether the architect step should be triggered.
|
|
9040
|
+
*
|
|
9041
|
+
* @param meta - Current meta.json.
|
|
9042
|
+
* @param structureChanged - Whether the structure hash changed.
|
|
9043
|
+
* @param steerChanged - Whether the steer directive changed.
|
|
9044
|
+
* @param architectEvery - Config: run architect every N cycles.
|
|
9045
|
+
* @returns True if the architect step should run.
|
|
9046
|
+
*/
|
|
9047
|
+
function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
|
|
9048
|
+
return (!meta._builder ||
|
|
9049
|
+
structureChanged ||
|
|
9050
|
+
steerChanged ||
|
|
9051
|
+
(meta._synthesisCount ?? 0) >= architectEvery);
|
|
9052
|
+
}
|
|
9053
|
+
/**
|
|
9054
|
+
* Detect whether the steer directive changed since the last archive.
|
|
9055
|
+
*
|
|
9056
|
+
* @param currentSteer - Current _steer value (or undefined).
|
|
9057
|
+
* @param archiveSteer - Archive _steer value (or undefined).
|
|
9058
|
+
* @param hasArchive - Whether an archive snapshot exists.
|
|
9059
|
+
* @returns True if steer changed.
|
|
9060
|
+
*/
|
|
9061
|
+
function hasSteerChanged(currentSteer, archiveSteer, hasArchive) {
|
|
9062
|
+
if (!hasArchive)
|
|
9063
|
+
return Boolean(currentSteer);
|
|
9064
|
+
return currentSteer !== archiveSteer;
|
|
9065
|
+
}
|
|
9066
|
+
/**
|
|
9067
|
+
* Compute a normalized staleness score (0–1) for display purposes.
|
|
9068
|
+
*
|
|
9069
|
+
* Uses the same depth/emphasis weighting as candidate selection,
|
|
9070
|
+
* normalized to a 30-day window.
|
|
9071
|
+
*
|
|
9072
|
+
* @param stalenessSeconds - Raw staleness in seconds (null = never synthesized).
|
|
9073
|
+
* @param depth - Meta tree depth.
|
|
9074
|
+
* @param emphasis - Scheduling emphasis multiplier.
|
|
9075
|
+
* @param depthWeight - Depth weighting exponent from config.
|
|
9076
|
+
* @returns Normalized score between 0 and 1.
|
|
9077
|
+
*/
|
|
9078
|
+
function computeStalenessScore(stalenessSeconds, depth, emphasis, depthWeight) {
|
|
9079
|
+
if (stalenessSeconds === null)
|
|
9080
|
+
return 1;
|
|
9081
|
+
const depthFactor = Math.pow(1 + depthWeight, depth);
|
|
9082
|
+
return Math.min(1, (stalenessSeconds * depthFactor * emphasis) / (30 * 86400));
|
|
9083
|
+
}
|
|
9084
|
+
|
|
8813
9085
|
/**
|
|
8814
9086
|
* Build the ownership tree from discovered .meta/ paths.
|
|
8815
9087
|
*
|
|
@@ -8892,8 +9164,6 @@ function findNode(tree, targetPath) {
|
|
|
8892
9164
|
*
|
|
8893
9165
|
* @module discovery/listMetas
|
|
8894
9166
|
*/
|
|
8895
|
-
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
8896
|
-
const MAX_STALENESS_SECONDS$1 = 365 * 86_400;
|
|
8897
9167
|
/**
|
|
8898
9168
|
* Discover, deduplicate, and enrich all metas.
|
|
8899
9169
|
*
|
|
@@ -8929,7 +9199,7 @@ async function listMetas(config, watcher) {
|
|
|
8929
9199
|
// Compute staleness
|
|
8930
9200
|
let stalenessSeconds;
|
|
8931
9201
|
if (neverSynth) {
|
|
8932
|
-
stalenessSeconds = MAX_STALENESS_SECONDS
|
|
9202
|
+
stalenessSeconds = MAX_STALENESS_SECONDS;
|
|
8933
9203
|
}
|
|
8934
9204
|
else {
|
|
8935
9205
|
const genAt = new Date(meta._generatedAt).getTime();
|
|
@@ -8962,79 +9232,6 @@ async function listMetas(config, watcher) {
|
|
|
8962
9232
|
};
|
|
8963
9233
|
}
|
|
8964
9234
|
|
|
8965
|
-
/**
|
|
8966
|
-
* Escape special glob characters in a path so it can be used as a literal
|
|
8967
|
-
* prefix in glob patterns.
|
|
8968
|
-
*
|
|
8969
|
-
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
8970
|
-
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
8971
|
-
* special characters are matched literally by the watcher's walk endpoint.
|
|
8972
|
-
*
|
|
8973
|
-
* @module escapeGlob
|
|
8974
|
-
*/
|
|
8975
|
-
/**
|
|
8976
|
-
* Escape glob metacharacters in a string using character-class wrapping.
|
|
8977
|
-
*
|
|
8978
|
-
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
8979
|
-
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
8980
|
-
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
8981
|
-
*
|
|
8982
|
-
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
8983
|
-
*
|
|
8984
|
-
* @param s - Raw path string.
|
|
8985
|
-
* @returns String with glob metacharacters wrapped in character classes.
|
|
8986
|
-
*/
|
|
8987
|
-
function escapeGlob(s) {
|
|
8988
|
-
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
8989
|
-
}
|
|
8990
|
-
|
|
8991
|
-
/**
|
|
8992
|
-
* Filter file paths by modification time.
|
|
8993
|
-
*
|
|
8994
|
-
* Shared utility for staleness detection and delta file enumeration.
|
|
8995
|
-
* Uses `fs.statSync` for fast local mtime checks on known paths.
|
|
8996
|
-
*
|
|
8997
|
-
* @module mtimeFilter
|
|
8998
|
-
*/
|
|
8999
|
-
/**
|
|
9000
|
-
* Check if any file in the list was modified after the given timestamp.
|
|
9001
|
-
*
|
|
9002
|
-
* Short-circuits on first match for efficiency (staleness checks).
|
|
9003
|
-
*
|
|
9004
|
-
* @param files - Array of file paths to check.
|
|
9005
|
-
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` match.
|
|
9006
|
-
* @returns True if any file was modified after the timestamp.
|
|
9007
|
-
*/
|
|
9008
|
-
function hasModifiedAfter(files, afterMs) {
|
|
9009
|
-
for (const filePath of files) {
|
|
9010
|
-
try {
|
|
9011
|
-
if (statSync(filePath).mtimeMs > afterMs)
|
|
9012
|
-
return true;
|
|
9013
|
-
}
|
|
9014
|
-
catch {
|
|
9015
|
-
// Unreadable file — skip
|
|
9016
|
-
}
|
|
9017
|
-
}
|
|
9018
|
-
return false;
|
|
9019
|
-
}
|
|
9020
|
-
/**
|
|
9021
|
-
* Filter files to only those modified after the given timestamp.
|
|
9022
|
-
*
|
|
9023
|
-
* @param files - Array of file paths to filter.
|
|
9024
|
-
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` are included.
|
|
9025
|
-
* @returns Filtered array of file paths.
|
|
9026
|
-
*/
|
|
9027
|
-
function filterModifiedAfter(files, afterMs) {
|
|
9028
|
-
return files.filter((filePath) => {
|
|
9029
|
-
try {
|
|
9030
|
-
return statSync(filePath).mtimeMs > afterMs;
|
|
9031
|
-
}
|
|
9032
|
-
catch {
|
|
9033
|
-
return false;
|
|
9034
|
-
}
|
|
9035
|
-
});
|
|
9036
|
-
}
|
|
9037
|
-
|
|
9038
9235
|
/**
|
|
9039
9236
|
* Compute the file scope owned by a meta node.
|
|
9040
9237
|
*
|
|
@@ -9111,11 +9308,6 @@ function getDeltaFiles(generatedAt, scopeFiles) {
|
|
|
9111
9308
|
return filterModifiedAfter(scopeFiles, new Date(generatedAt).getTime());
|
|
9112
9309
|
}
|
|
9113
9310
|
|
|
9114
|
-
/** Sleep for a given number of milliseconds. */
|
|
9115
|
-
function sleep(ms) {
|
|
9116
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9117
|
-
}
|
|
9118
|
-
|
|
9119
9311
|
/**
|
|
9120
9312
|
* Error thrown when a spawned subprocess is aborted via AbortController.
|
|
9121
9313
|
*
|
|
@@ -9270,7 +9462,7 @@ class GatewayExecutor {
|
|
|
9270
9462
|
JSON.stringify(spawnResult));
|
|
9271
9463
|
}
|
|
9272
9464
|
// Step 2: Poll for completion via sessions_history
|
|
9273
|
-
await
|
|
9465
|
+
await sleepAsync(3000);
|
|
9274
9466
|
while (Date.now() < deadline) {
|
|
9275
9467
|
// Check for abort before each poll iteration
|
|
9276
9468
|
if (this.controller.signal.aborted) {
|
|
@@ -9334,7 +9526,7 @@ class GatewayExecutor {
|
|
|
9334
9526
|
catch {
|
|
9335
9527
|
// Transient poll failure — keep trying
|
|
9336
9528
|
}
|
|
9337
|
-
await
|
|
9529
|
+
await sleepAsync(this.pollIntervalMs);
|
|
9338
9530
|
}
|
|
9339
9531
|
throw new SpawnTimeoutError('Synthesis subprocess timed out after ' + timeoutMs.toString() + 'ms', outputPath);
|
|
9340
9532
|
}
|
|
@@ -10195,101 +10387,6 @@ function discoverStalestPath(candidates, depthWeight) {
|
|
|
10195
10387
|
return winner?.node.metaPath ?? null;
|
|
10196
10388
|
}
|
|
10197
10389
|
|
|
10198
|
-
/**
|
|
10199
|
-
* Staleness detection via watcher walk.
|
|
10200
|
-
*
|
|
10201
|
-
* A meta is stale when any watched file in its scope was modified after
|
|
10202
|
-
* `_generatedAt`.
|
|
10203
|
-
*
|
|
10204
|
-
* @module scheduling/staleness
|
|
10205
|
-
*/
|
|
10206
|
-
/**
|
|
10207
|
-
* Check if a meta is stale.
|
|
10208
|
-
*
|
|
10209
|
-
* Uses watcher `/walk` to enumerate watched files under the scope prefix,
|
|
10210
|
-
* then applies a local mtime check (fast) to detect any modifications since
|
|
10211
|
-
* `_generatedAt`. Short-circuits on first match.
|
|
10212
|
-
*
|
|
10213
|
-
* @param scopePrefix - Path prefix for this meta's scope.
|
|
10214
|
-
* @param meta - Current meta.json content.
|
|
10215
|
-
* @param watcher - WatcherClient instance.
|
|
10216
|
-
* @returns True if any file in scope was modified after `_generatedAt`.
|
|
10217
|
-
*/
|
|
10218
|
-
async function isStale(scopePrefix, meta, watcher) {
|
|
10219
|
-
if (!meta._generatedAt)
|
|
10220
|
-
return true; // Never synthesized = stale
|
|
10221
|
-
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
10222
|
-
// Exclude .meta/ subtree — synthesis outputs must not trigger staleness.
|
|
10223
|
-
// Handle both forward and back slashes for cross-platform compatibility.
|
|
10224
|
-
const metaSep = /[/\\]\.meta(?:[/\\]|$)/;
|
|
10225
|
-
const filtered = files.filter((f) => !metaSep.test(f));
|
|
10226
|
-
return hasModifiedAfter(filtered, new Date(meta._generatedAt).getTime());
|
|
10227
|
-
}
|
|
10228
|
-
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
10229
|
-
const MAX_STALENESS_SECONDS = 365 * 86_400;
|
|
10230
|
-
/**
|
|
10231
|
-
* Compute actual staleness in seconds (now minus _generatedAt).
|
|
10232
|
-
*
|
|
10233
|
-
* Never-synthesized metas are capped at {@link MAX_STALENESS_SECONDS}
|
|
10234
|
-
* (1 year) so that depth weighting can differentiate them. Without
|
|
10235
|
-
* bounding, `Infinity * depthFactor` = `Infinity` for all depths.
|
|
10236
|
-
*
|
|
10237
|
-
* @param meta - Current meta.json content.
|
|
10238
|
-
* @returns Staleness in seconds, capped at 1 year for never-synthesized metas.
|
|
10239
|
-
*/
|
|
10240
|
-
function actualStaleness(meta) {
|
|
10241
|
-
if (!meta._generatedAt)
|
|
10242
|
-
return MAX_STALENESS_SECONDS;
|
|
10243
|
-
const generatedMs = new Date(meta._generatedAt).getTime();
|
|
10244
|
-
return Math.min((Date.now() - generatedMs) / 1000, MAX_STALENESS_SECONDS);
|
|
10245
|
-
}
|
|
10246
|
-
/**
|
|
10247
|
-
* Check whether the architect step should be triggered.
|
|
10248
|
-
*
|
|
10249
|
-
* @param meta - Current meta.json.
|
|
10250
|
-
* @param structureChanged - Whether the structure hash changed.
|
|
10251
|
-
* @param steerChanged - Whether the steer directive changed.
|
|
10252
|
-
* @param architectEvery - Config: run architect every N cycles.
|
|
10253
|
-
* @returns True if the architect step should run.
|
|
10254
|
-
*/
|
|
10255
|
-
function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
|
|
10256
|
-
return (!meta._builder ||
|
|
10257
|
-
structureChanged ||
|
|
10258
|
-
steerChanged ||
|
|
10259
|
-
(meta._synthesisCount ?? 0) >= architectEvery);
|
|
10260
|
-
}
|
|
10261
|
-
/**
|
|
10262
|
-
* Detect whether the steer directive changed since the last archive.
|
|
10263
|
-
*
|
|
10264
|
-
* @param currentSteer - Current _steer value (or undefined).
|
|
10265
|
-
* @param archiveSteer - Archive _steer value (or undefined).
|
|
10266
|
-
* @param hasArchive - Whether an archive snapshot exists.
|
|
10267
|
-
* @returns True if steer changed.
|
|
10268
|
-
*/
|
|
10269
|
-
function hasSteerChanged(currentSteer, archiveSteer, hasArchive) {
|
|
10270
|
-
if (!hasArchive)
|
|
10271
|
-
return Boolean(currentSteer);
|
|
10272
|
-
return currentSteer !== archiveSteer;
|
|
10273
|
-
}
|
|
10274
|
-
/**
|
|
10275
|
-
* Compute a normalized staleness score (0–1) for display purposes.
|
|
10276
|
-
*
|
|
10277
|
-
* Uses the same depth/emphasis weighting as candidate selection,
|
|
10278
|
-
* normalized to a 30-day window.
|
|
10279
|
-
*
|
|
10280
|
-
* @param stalenessSeconds - Raw staleness in seconds (null = never synthesized).
|
|
10281
|
-
* @param depth - Meta tree depth.
|
|
10282
|
-
* @param emphasis - Scheduling emphasis multiplier.
|
|
10283
|
-
* @param depthWeight - Depth weighting exponent from config.
|
|
10284
|
-
* @returns Normalized score between 0 and 1.
|
|
10285
|
-
*/
|
|
10286
|
-
function computeStalenessScore(stalenessSeconds, depth, emphasis, depthWeight) {
|
|
10287
|
-
if (stalenessSeconds === null)
|
|
10288
|
-
return 1;
|
|
10289
|
-
const depthFactor = Math.pow(1 + depthWeight, depth);
|
|
10290
|
-
return Math.min(1, (stalenessSeconds * depthFactor * emphasis) / (30 * 86400));
|
|
10291
|
-
}
|
|
10292
|
-
|
|
10293
10390
|
/**
|
|
10294
10391
|
* Shared error utilities.
|
|
10295
10392
|
*
|
|
@@ -11350,7 +11447,10 @@ function buildMetaRules(config) {
|
|
|
11350
11447
|
properties: {
|
|
11351
11448
|
file: {
|
|
11352
11449
|
properties: {
|
|
11353
|
-
path: {
|
|
11450
|
+
path: {
|
|
11451
|
+
type: 'string',
|
|
11452
|
+
glob: '**/jeeves-meta{.config.json,/config.json}',
|
|
11453
|
+
},
|
|
11354
11454
|
},
|
|
11355
11455
|
},
|
|
11356
11456
|
},
|
|
@@ -12511,7 +12611,7 @@ class HttpWatcherClient {
|
|
|
12511
12611
|
}
|
|
12512
12612
|
// Exponential backoff
|
|
12513
12613
|
const delayMs = this.backoffBaseMs * Math.pow(this.backoffFactor, attempt);
|
|
12514
|
-
await
|
|
12614
|
+
await sleepAsync(delayMs);
|
|
12515
12615
|
}
|
|
12516
12616
|
// Unreachable, but TypeScript needs it
|
|
12517
12617
|
throw new Error('Retry exhausted');
|
|
@@ -12730,7 +12830,11 @@ async function startService(config, configPath) {
|
|
|
12730
12830
|
*/
|
|
12731
12831
|
/** Build the full API URL for a given port string and path. */
|
|
12732
12832
|
function apiUrl(port, apiPath) {
|
|
12733
|
-
|
|
12833
|
+
const url = new URL(apiPath, getServiceUrl('meta'));
|
|
12834
|
+
if (port !== DEFAULT_PORT_STR) {
|
|
12835
|
+
url.port = port;
|
|
12836
|
+
}
|
|
12837
|
+
return url.toString();
|
|
12734
12838
|
}
|
|
12735
12839
|
/** Wrap an async CLI action with consistent error handling. */
|
|
12736
12840
|
function withErrorHandling(fn, label) {
|