@karmaniverous/jeeves-meta 0.12.3 → 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 -214
- package/dist/index.d.ts +5 -6
- package/dist/index.js +286 -182
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { JeevesComponentDescriptor } from '@karmaniverous/jeeves';
|
|
4
|
+
export { sleepAsync as sleep } from '@karmaniverous/jeeves';
|
|
4
5
|
import pino, { Logger } from 'pino';
|
|
5
6
|
import * as fastify from 'fastify';
|
|
6
7
|
import { FastifyInstance, FastifyBaseLogger } from 'fastify';
|
|
@@ -236,7 +237,6 @@ declare const serviceConfigSchema: z.ZodObject<{
|
|
|
236
237
|
metaProperty: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
237
238
|
metaArchiveProperty: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
238
239
|
port: z.ZodDefault<z.ZodNumber>;
|
|
239
|
-
host: z.ZodDefault<z.ZodString>;
|
|
240
240
|
schedule: z.ZodDefault<z.ZodString>;
|
|
241
241
|
reportChannel: z.ZodOptional<z.ZodString>;
|
|
242
242
|
serverBaseUrl: z.ZodOptional<z.ZodString>;
|
|
@@ -636,7 +636,7 @@ declare class Scheduler {
|
|
|
636
636
|
* Shared between the descriptor's `onConfigApply` and the file-watcher
|
|
637
637
|
* hot-reload in `bootstrap.ts`.
|
|
638
638
|
*/
|
|
639
|
-
declare const RESTART_REQUIRED_FIELDS: readonly ["port", "
|
|
639
|
+
declare const RESTART_REQUIRED_FIELDS: readonly ["port", "watcherUrl", "gatewayUrl", "gatewayApiKey", "defaultArchitect", "defaultCritic"];
|
|
640
640
|
|
|
641
641
|
/**
|
|
642
642
|
* Load and resolve jeeves-meta service config.
|
|
@@ -1368,6 +1368,8 @@ declare function selectCandidate(candidates: StalenessCandidate[]): StalenessCan
|
|
|
1368
1368
|
* @returns True if any file in scope was modified after `_generatedAt`.
|
|
1369
1369
|
*/
|
|
1370
1370
|
declare function isStale(scopePrefix: string, meta: MetaJson, watcher: WatcherClient): Promise<boolean>;
|
|
1371
|
+
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
1372
|
+
declare const MAX_STALENESS_SECONDS: number;
|
|
1371
1373
|
/**
|
|
1372
1374
|
* Compute actual staleness in seconds (now minus _generatedAt).
|
|
1373
1375
|
*
|
|
@@ -1453,9 +1455,6 @@ declare function registerRoutes(app: FastifyInstance, deps: RouteDeps): void;
|
|
|
1453
1455
|
*/
|
|
1454
1456
|
declare function verifyRuleApplication(watcher: WatcherClient, logger: MinimalLogger): Promise<number>;
|
|
1455
1457
|
|
|
1456
|
-
/** Sleep for a given number of milliseconds. */
|
|
1457
|
-
declare function sleep(ms: number): Promise<void>;
|
|
1458
|
-
|
|
1459
1458
|
/** Options for creating the server. */
|
|
1460
1459
|
interface ServerOptions {
|
|
1461
1460
|
/** Pino logger instance. */
|
|
@@ -1519,5 +1518,5 @@ declare function registerShutdownHandlers(deps: ShutdownDeps): void;
|
|
|
1519
1518
|
*/
|
|
1520
1519
|
declare function startService(config: ServiceConfig, configPath?: string): Promise<void>;
|
|
1521
1520
|
|
|
1522
|
-
export { DEFAULT_PORT, DEFAULT_PORT_STR, GatewayExecutor, HttpWatcherClient, ProgressReporter, RESTART_REQUIRED_FIELDS, RuleRegistrar, SERVICE_NAME, SERVICE_VERSION, Scheduler, SynthesisQueue, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildOwnershipTree, cleanupStaleLocks, computeEffectiveStaleness, computeEma, computeStructureHash, createLogger, createServer, createSnapshot, discoverMetas, filterInScope, findNode, formatProgressEvent, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, listMetas, loadServiceConfig, mergeAndWrite, metaConfigSchema, metaDescriptor, metaErrorSchema, metaJsonSchema, migrateConfigPath, normalizePath, orchestrate, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, readLockState, registerCustomCliCommands, registerRoutes, registerShutdownHandlers, releaseLock, resolveConfigPath, resolveMetaDir, selectCandidate, serviceConfigSchema,
|
|
1521
|
+
export { DEFAULT_PORT, DEFAULT_PORT_STR, GatewayExecutor, HttpWatcherClient, MAX_STALENESS_SECONDS, ProgressReporter, RESTART_REQUIRED_FIELDS, RuleRegistrar, SERVICE_NAME, SERVICE_VERSION, Scheduler, SynthesisQueue, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildOwnershipTree, cleanupStaleLocks, computeEffectiveStaleness, computeEma, computeStructureHash, createLogger, createServer, createSnapshot, discoverMetas, filterInScope, findNode, formatProgressEvent, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, listMetas, loadServiceConfig, mergeAndWrite, metaConfigSchema, metaDescriptor, metaErrorSchema, metaJsonSchema, migrateConfigPath, normalizePath, orchestrate, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, readLockState, registerCustomCliCommands, registerRoutes, registerShutdownHandlers, releaseLock, resolveConfigPath, resolveMetaDir, selectCandidate, serviceConfigSchema, startService, toMetaError, verifyRuleApplication };
|
|
1523
1522
|
export type { BuilderOutput, EnqueueResult, GatewayExecutorOptions, HttpWatcherClientOptions, InferenceRuleSpec, LockState, LoggerConfig, MergeOptions, MetaConfig, MetaContext, MetaEntry, MetaError, MetaExecutor, MetaJson, MetaListResult, MetaListSummary, MetaNode, MetaSpawnOptions, MetaSpawnResult, MinimalLogger, OrchestrateResult, OwnershipTree, ProgressCallback, ProgressEvent, ProgressPhase, ProgressReporterConfig, QueueItem, QueueState, RouteDeps, ServerOptions, ServiceConfig, ServiceStats, StalenessCandidate, WatcherClient, WatcherScanPoint, WatcherScanRequest, WatcherScanResult };
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,9 @@ import require$$4 from 'util';
|
|
|
11
11
|
import require$$5 from 'assert';
|
|
12
12
|
import require$$2 from 'events';
|
|
13
13
|
import vm from 'vm';
|
|
14
|
+
import { z } from 'zod';
|
|
14
15
|
import * as commander from 'commander';
|
|
15
16
|
import { tmpdir } from 'node:os';
|
|
16
|
-
import { z } from 'zod';
|
|
17
17
|
import 'node:child_process';
|
|
18
18
|
import { randomUUID, createHash } from 'node:crypto';
|
|
19
19
|
import pino from 'pino';
|
|
@@ -7243,6 +7243,32 @@ requireSemver();
|
|
|
7243
7243
|
|
|
7244
7244
|
/** Core config file name. */
|
|
7245
7245
|
const CONFIG_FILE = 'config.json';
|
|
7246
|
+
|
|
7247
|
+
/**
|
|
7248
|
+
* Default port assignments for Jeeves platform services.
|
|
7249
|
+
*
|
|
7250
|
+
* @remarks
|
|
7251
|
+
* Each port number is a historical reference:
|
|
7252
|
+
* - 1934: Wodehouse's *Thank You, Jeeves*; Popper's *Logic of Scientific Discovery*
|
|
7253
|
+
* - 1936: Turing's "On Computable Numbers"; Church's lambda calculus
|
|
7254
|
+
* - 1937: Turing's paper in *Proceedings of the London Mathematical Society*
|
|
7255
|
+
* - 1938: Wodehouse's *The Code of the Woosters*; Shannon's relay/switching paper
|
|
7256
|
+
*/
|
|
7257
|
+
/** Default port for jeeves-server. */
|
|
7258
|
+
const SERVER_PORT = 1934;
|
|
7259
|
+
/** Default port for jeeves-watcher. */
|
|
7260
|
+
const WATCHER_PORT = 1936;
|
|
7261
|
+
/** Default port for jeeves-runner. */
|
|
7262
|
+
const RUNNER_PORT = 1937;
|
|
7263
|
+
/** Default port for jeeves-meta. */
|
|
7264
|
+
const META_PORT = 1938;
|
|
7265
|
+
/** Map of service names to their default ports. */
|
|
7266
|
+
const DEFAULT_PORTS = {
|
|
7267
|
+
server: SERVER_PORT,
|
|
7268
|
+
watcher: WATCHER_PORT,
|
|
7269
|
+
runner: RUNNER_PORT,
|
|
7270
|
+
meta: META_PORT,
|
|
7271
|
+
};
|
|
7246
7272
|
/**
|
|
7247
7273
|
* Get the core config directory path.
|
|
7248
7274
|
*
|
|
@@ -7504,6 +7530,47 @@ function createStatusHandler(options) {
|
|
|
7504
7530
|
};
|
|
7505
7531
|
};
|
|
7506
7532
|
}
|
|
7533
|
+
/** Core shared config section. */
|
|
7534
|
+
const workspaceCoreConfigSchema = z
|
|
7535
|
+
.object({
|
|
7536
|
+
/** Workspace root path. */
|
|
7537
|
+
workspace: z.string().optional().describe('Workspace root path'),
|
|
7538
|
+
/** Platform config root path. */
|
|
7539
|
+
configRoot: z.string().optional().describe('Platform config root path'),
|
|
7540
|
+
/** OpenClaw gateway URL. */
|
|
7541
|
+
gatewayUrl: z.string().optional().describe('OpenClaw gateway URL'),
|
|
7542
|
+
})
|
|
7543
|
+
.partial();
|
|
7544
|
+
/** Memory shared config section. */
|
|
7545
|
+
const workspaceMemoryConfigSchema = z
|
|
7546
|
+
.object({
|
|
7547
|
+
/** MEMORY.md character budget. */
|
|
7548
|
+
budget: z.number().int().positive().optional().describe('Memory budget'),
|
|
7549
|
+
/** Warning threshold as a fraction of budget. */
|
|
7550
|
+
warningThreshold: z
|
|
7551
|
+
.number()
|
|
7552
|
+
.min(0)
|
|
7553
|
+
.max(1)
|
|
7554
|
+
.optional()
|
|
7555
|
+
.describe('Memory warning threshold'),
|
|
7556
|
+
/** Staleness threshold in days. */
|
|
7557
|
+
staleDays: z
|
|
7558
|
+
.number()
|
|
7559
|
+
.int()
|
|
7560
|
+
.positive()
|
|
7561
|
+
.optional()
|
|
7562
|
+
.describe('Memory staleness threshold in days'),
|
|
7563
|
+
})
|
|
7564
|
+
.partial();
|
|
7565
|
+
/** Workspace config Zod schema. */
|
|
7566
|
+
z.object({
|
|
7567
|
+
/** JSON Schema pointer for IDE autocomplete. */
|
|
7568
|
+
$schema: z.string().optional().describe('JSON Schema pointer'),
|
|
7569
|
+
/** Core shared defaults. */
|
|
7570
|
+
core: workspaceCoreConfigSchema.optional(),
|
|
7571
|
+
/** Memory hygiene shared defaults. */
|
|
7572
|
+
memory: workspaceMemoryConfigSchema.optional(),
|
|
7573
|
+
});
|
|
7507
7574
|
|
|
7508
7575
|
function getDefaultExportFromCjs (x) {
|
|
7509
7576
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -7821,6 +7888,38 @@ function loadConfig(configDir) {
|
|
|
7821
7888
|
}
|
|
7822
7889
|
}
|
|
7823
7890
|
|
|
7891
|
+
/**
|
|
7892
|
+
* Service URL resolution.
|
|
7893
|
+
*
|
|
7894
|
+
* @remarks
|
|
7895
|
+
* Resolves the URL for a named Jeeves service using the following
|
|
7896
|
+
* resolution order:
|
|
7897
|
+
* 1. Consumer's own component config
|
|
7898
|
+
* 2. Core config (`{configRoot}/jeeves-core/config.json`)
|
|
7899
|
+
* 3. Default port constants
|
|
7900
|
+
*/
|
|
7901
|
+
/**
|
|
7902
|
+
* Resolve the URL for a named Jeeves service.
|
|
7903
|
+
*
|
|
7904
|
+
* @param serviceName - The service name (e.g., 'watcher', 'runner').
|
|
7905
|
+
* @param consumerName - Optional consumer component name for config override.
|
|
7906
|
+
* @returns The resolved service URL.
|
|
7907
|
+
* @throws Error if `init()` has not been called or the service is unknown.
|
|
7908
|
+
*/
|
|
7909
|
+
function getServiceUrl(serviceName, consumerName) {
|
|
7910
|
+
// 2. Check core config
|
|
7911
|
+
const coreDir = getCoreConfigDir();
|
|
7912
|
+
const coreConfig = loadConfig(coreDir);
|
|
7913
|
+
const coreUrl = coreConfig?.services[serviceName]?.url;
|
|
7914
|
+
if (coreUrl)
|
|
7915
|
+
return coreUrl;
|
|
7916
|
+
// 3. Fall back to port constants
|
|
7917
|
+
const port = DEFAULT_PORTS[serviceName];
|
|
7918
|
+
{
|
|
7919
|
+
return `http://127.0.0.1:${String(port)}`;
|
|
7920
|
+
}
|
|
7921
|
+
}
|
|
7922
|
+
|
|
7824
7923
|
/**
|
|
7825
7924
|
* Resolve the bind address for a Jeeves service.
|
|
7826
7925
|
*
|
|
@@ -7858,6 +7957,10 @@ function getBindAddress(componentName) {
|
|
|
7858
7957
|
// Tier 4: Default
|
|
7859
7958
|
return DEFAULT_BIND_ADDRESS;
|
|
7860
7959
|
}
|
|
7960
|
+
/** Async sleep via setTimeout. */
|
|
7961
|
+
function sleepAsync(ms) {
|
|
7962
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
7963
|
+
}
|
|
7861
7964
|
|
|
7862
7965
|
/**
|
|
7863
7966
|
* Custom CLI commands for the jeeves-meta service.
|
|
@@ -7869,7 +7972,11 @@ function getBindAddress(componentName) {
|
|
|
7869
7972
|
*/
|
|
7870
7973
|
/** Build the full API URL for a given port string and path. */
|
|
7871
7974
|
function apiUrl(port, apiPath) {
|
|
7872
|
-
|
|
7975
|
+
const url = new URL(apiPath, getServiceUrl('meta'));
|
|
7976
|
+
if (port !== DEFAULT_PORT_STR) {
|
|
7977
|
+
url.port = port;
|
|
7978
|
+
}
|
|
7979
|
+
return url.toString();
|
|
7873
7980
|
}
|
|
7874
7981
|
/** Wrap an async CLI action with consistent error handling. */
|
|
7875
7982
|
function withErrorHandling(fn, label) {
|
|
@@ -8024,7 +8131,6 @@ function registerCustomCliCommands(program) {
|
|
|
8024
8131
|
*/
|
|
8025
8132
|
const RESTART_REQUIRED_FIELDS = [
|
|
8026
8133
|
'port',
|
|
8027
|
-
'host',
|
|
8028
8134
|
'watcherUrl',
|
|
8029
8135
|
'gatewayUrl',
|
|
8030
8136
|
'gatewayApiKey',
|
|
@@ -8136,8 +8242,6 @@ const autoSeedRuleSchema = z.object({
|
|
|
8136
8242
|
const serviceConfigSchema = metaConfigSchema.extend({
|
|
8137
8243
|
/** HTTP port for the service (default: 1938). */
|
|
8138
8244
|
port: z.number().int().min(1).max(65535).default(1938),
|
|
8139
|
-
/** Bind address for the HTTP server (default: 127.0.0.1). */
|
|
8140
|
-
host: z.string().default('127.0.0.1'),
|
|
8141
8245
|
/** Cron schedule for synthesis cycles (default: every 30 min). */
|
|
8142
8246
|
schedule: z.string().default('*/30 * * * *'),
|
|
8143
8247
|
/** Optional channel identifier for reporting. */
|
|
@@ -8523,6 +8627,174 @@ async function readMetaJson(metaPath) {
|
|
|
8523
8627
|
return JSON.parse(raw);
|
|
8524
8628
|
}
|
|
8525
8629
|
|
|
8630
|
+
/**
|
|
8631
|
+
* Escape special glob characters in a path so it can be used as a literal
|
|
8632
|
+
* prefix in glob patterns.
|
|
8633
|
+
*
|
|
8634
|
+
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
8635
|
+
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
8636
|
+
* special characters are matched literally by the watcher's walk endpoint.
|
|
8637
|
+
*
|
|
8638
|
+
* @module escapeGlob
|
|
8639
|
+
*/
|
|
8640
|
+
/**
|
|
8641
|
+
* Escape glob metacharacters in a string using character-class wrapping.
|
|
8642
|
+
*
|
|
8643
|
+
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
8644
|
+
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
8645
|
+
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
8646
|
+
*
|
|
8647
|
+
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
8648
|
+
*
|
|
8649
|
+
* @param s - Raw path string.
|
|
8650
|
+
* @returns String with glob metacharacters wrapped in character classes.
|
|
8651
|
+
*/
|
|
8652
|
+
function escapeGlob(s) {
|
|
8653
|
+
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
8654
|
+
}
|
|
8655
|
+
|
|
8656
|
+
/**
|
|
8657
|
+
* Filter file paths by modification time.
|
|
8658
|
+
*
|
|
8659
|
+
* Shared utility for staleness detection and delta file enumeration.
|
|
8660
|
+
* Uses `fs.statSync` for fast local mtime checks on known paths.
|
|
8661
|
+
*
|
|
8662
|
+
* @module mtimeFilter
|
|
8663
|
+
*/
|
|
8664
|
+
/**
|
|
8665
|
+
* Check if any file in the list was modified after the given timestamp.
|
|
8666
|
+
*
|
|
8667
|
+
* Short-circuits on first match for efficiency (staleness checks).
|
|
8668
|
+
*
|
|
8669
|
+
* @param files - Array of file paths to check.
|
|
8670
|
+
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` match.
|
|
8671
|
+
* @returns True if any file was modified after the timestamp.
|
|
8672
|
+
*/
|
|
8673
|
+
function hasModifiedAfter(files, afterMs) {
|
|
8674
|
+
for (const filePath of files) {
|
|
8675
|
+
try {
|
|
8676
|
+
if (statSync(filePath).mtimeMs > afterMs)
|
|
8677
|
+
return true;
|
|
8678
|
+
}
|
|
8679
|
+
catch {
|
|
8680
|
+
// Unreadable file — skip
|
|
8681
|
+
}
|
|
8682
|
+
}
|
|
8683
|
+
return false;
|
|
8684
|
+
}
|
|
8685
|
+
/**
|
|
8686
|
+
* Filter files to only those modified after the given timestamp.
|
|
8687
|
+
*
|
|
8688
|
+
* @param files - Array of file paths to filter.
|
|
8689
|
+
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` are included.
|
|
8690
|
+
* @returns Filtered array of file paths.
|
|
8691
|
+
*/
|
|
8692
|
+
function filterModifiedAfter(files, afterMs) {
|
|
8693
|
+
return files.filter((filePath) => {
|
|
8694
|
+
try {
|
|
8695
|
+
return statSync(filePath).mtimeMs > afterMs;
|
|
8696
|
+
}
|
|
8697
|
+
catch {
|
|
8698
|
+
return false;
|
|
8699
|
+
}
|
|
8700
|
+
});
|
|
8701
|
+
}
|
|
8702
|
+
|
|
8703
|
+
/**
|
|
8704
|
+
* Staleness detection via watcher walk.
|
|
8705
|
+
*
|
|
8706
|
+
* A meta is stale when any watched file in its scope was modified after
|
|
8707
|
+
* `_generatedAt`.
|
|
8708
|
+
*
|
|
8709
|
+
* @module scheduling/staleness
|
|
8710
|
+
*/
|
|
8711
|
+
/**
|
|
8712
|
+
* Check if a meta is stale.
|
|
8713
|
+
*
|
|
8714
|
+
* Uses watcher `/walk` to enumerate watched files under the scope prefix,
|
|
8715
|
+
* then applies a local mtime check (fast) to detect any modifications since
|
|
8716
|
+
* `_generatedAt`. Short-circuits on first match.
|
|
8717
|
+
*
|
|
8718
|
+
* @param scopePrefix - Path prefix for this meta's scope.
|
|
8719
|
+
* @param meta - Current meta.json content.
|
|
8720
|
+
* @param watcher - WatcherClient instance.
|
|
8721
|
+
* @returns True if any file in scope was modified after `_generatedAt`.
|
|
8722
|
+
*/
|
|
8723
|
+
async function isStale(scopePrefix, meta, watcher) {
|
|
8724
|
+
if (!meta._generatedAt)
|
|
8725
|
+
return true; // Never synthesized = stale
|
|
8726
|
+
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
8727
|
+
// Exclude .meta/ subtree — synthesis outputs must not trigger staleness.
|
|
8728
|
+
// Handle both forward and back slashes for cross-platform compatibility.
|
|
8729
|
+
const metaSep = /[/\\]\.meta(?:[/\\]|$)/;
|
|
8730
|
+
const filtered = files.filter((f) => !metaSep.test(f));
|
|
8731
|
+
return hasModifiedAfter(filtered, new Date(meta._generatedAt).getTime());
|
|
8732
|
+
}
|
|
8733
|
+
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
8734
|
+
const MAX_STALENESS_SECONDS = 365 * 86_400;
|
|
8735
|
+
/**
|
|
8736
|
+
* Compute actual staleness in seconds (now minus _generatedAt).
|
|
8737
|
+
*
|
|
8738
|
+
* Never-synthesized metas are capped at {@link MAX_STALENESS_SECONDS}
|
|
8739
|
+
* (1 year) so that depth weighting can differentiate them. Without
|
|
8740
|
+
* bounding, `Infinity * depthFactor` = `Infinity` for all depths.
|
|
8741
|
+
*
|
|
8742
|
+
* @param meta - Current meta.json content.
|
|
8743
|
+
* @returns Staleness in seconds, capped at 1 year for never-synthesized metas.
|
|
8744
|
+
*/
|
|
8745
|
+
function actualStaleness(meta) {
|
|
8746
|
+
if (!meta._generatedAt)
|
|
8747
|
+
return MAX_STALENESS_SECONDS;
|
|
8748
|
+
const generatedMs = new Date(meta._generatedAt).getTime();
|
|
8749
|
+
return Math.min((Date.now() - generatedMs) / 1000, MAX_STALENESS_SECONDS);
|
|
8750
|
+
}
|
|
8751
|
+
/**
|
|
8752
|
+
* Check whether the architect step should be triggered.
|
|
8753
|
+
*
|
|
8754
|
+
* @param meta - Current meta.json.
|
|
8755
|
+
* @param structureChanged - Whether the structure hash changed.
|
|
8756
|
+
* @param steerChanged - Whether the steer directive changed.
|
|
8757
|
+
* @param architectEvery - Config: run architect every N cycles.
|
|
8758
|
+
* @returns True if the architect step should run.
|
|
8759
|
+
*/
|
|
8760
|
+
function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
|
|
8761
|
+
return (!meta._builder ||
|
|
8762
|
+
structureChanged ||
|
|
8763
|
+
steerChanged ||
|
|
8764
|
+
(meta._synthesisCount ?? 0) >= architectEvery);
|
|
8765
|
+
}
|
|
8766
|
+
/**
|
|
8767
|
+
* Detect whether the steer directive changed since the last archive.
|
|
8768
|
+
*
|
|
8769
|
+
* @param currentSteer - Current _steer value (or undefined).
|
|
8770
|
+
* @param archiveSteer - Archive _steer value (or undefined).
|
|
8771
|
+
* @param hasArchive - Whether an archive snapshot exists.
|
|
8772
|
+
* @returns True if steer changed.
|
|
8773
|
+
*/
|
|
8774
|
+
function hasSteerChanged(currentSteer, archiveSteer, hasArchive) {
|
|
8775
|
+
if (!hasArchive)
|
|
8776
|
+
return Boolean(currentSteer);
|
|
8777
|
+
return currentSteer !== archiveSteer;
|
|
8778
|
+
}
|
|
8779
|
+
/**
|
|
8780
|
+
* Compute a normalized staleness score (0–1) for display purposes.
|
|
8781
|
+
*
|
|
8782
|
+
* Uses the same depth/emphasis weighting as candidate selection,
|
|
8783
|
+
* normalized to a 30-day window.
|
|
8784
|
+
*
|
|
8785
|
+
* @param stalenessSeconds - Raw staleness in seconds (null = never synthesized).
|
|
8786
|
+
* @param depth - Meta tree depth.
|
|
8787
|
+
* @param emphasis - Scheduling emphasis multiplier.
|
|
8788
|
+
* @param depthWeight - Depth weighting exponent from config.
|
|
8789
|
+
* @returns Normalized score between 0 and 1.
|
|
8790
|
+
*/
|
|
8791
|
+
function computeStalenessScore(stalenessSeconds, depth, emphasis, depthWeight) {
|
|
8792
|
+
if (stalenessSeconds === null)
|
|
8793
|
+
return 1;
|
|
8794
|
+
const depthFactor = Math.pow(1 + depthWeight, depth);
|
|
8795
|
+
return Math.min(1, (stalenessSeconds * depthFactor * emphasis) / (30 * 86400));
|
|
8796
|
+
}
|
|
8797
|
+
|
|
8526
8798
|
/**
|
|
8527
8799
|
* Build the ownership tree from discovered .meta/ paths.
|
|
8528
8800
|
*
|
|
@@ -8605,8 +8877,6 @@ function findNode(tree, targetPath) {
|
|
|
8605
8877
|
*
|
|
8606
8878
|
* @module discovery/listMetas
|
|
8607
8879
|
*/
|
|
8608
|
-
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
8609
|
-
const MAX_STALENESS_SECONDS$1 = 365 * 86_400;
|
|
8610
8880
|
/**
|
|
8611
8881
|
* Discover, deduplicate, and enrich all metas.
|
|
8612
8882
|
*
|
|
@@ -8642,7 +8912,7 @@ async function listMetas(config, watcher) {
|
|
|
8642
8912
|
// Compute staleness
|
|
8643
8913
|
let stalenessSeconds;
|
|
8644
8914
|
if (neverSynth) {
|
|
8645
|
-
stalenessSeconds = MAX_STALENESS_SECONDS
|
|
8915
|
+
stalenessSeconds = MAX_STALENESS_SECONDS;
|
|
8646
8916
|
}
|
|
8647
8917
|
else {
|
|
8648
8918
|
const genAt = new Date(meta._generatedAt).getTime();
|
|
@@ -8675,79 +8945,6 @@ async function listMetas(config, watcher) {
|
|
|
8675
8945
|
};
|
|
8676
8946
|
}
|
|
8677
8947
|
|
|
8678
|
-
/**
|
|
8679
|
-
* Escape special glob characters in a path so it can be used as a literal
|
|
8680
|
-
* prefix in glob patterns.
|
|
8681
|
-
*
|
|
8682
|
-
* Glob metacharacters `* ? [ ] { } ( ) !` are escaped with a backslash so
|
|
8683
|
-
* that paths containing parentheses (e.g. Slack channel IDs) or other
|
|
8684
|
-
* special characters are matched literally by the watcher's walk endpoint.
|
|
8685
|
-
*
|
|
8686
|
-
* @module escapeGlob
|
|
8687
|
-
*/
|
|
8688
|
-
/**
|
|
8689
|
-
* Escape glob metacharacters in a string using character-class wrapping.
|
|
8690
|
-
*
|
|
8691
|
-
* Backslash escaping (`\(`) does not work reliably on Windows where `\` is
|
|
8692
|
-
* the path separator. Instead, each metacharacter is wrapped in a character
|
|
8693
|
-
* class (e.g. `(` → `[(]`) which is universally supported by glob libraries.
|
|
8694
|
-
*
|
|
8695
|
-
* Square brackets themselves are escaped as `[[]` and `[]]`.
|
|
8696
|
-
*
|
|
8697
|
-
* @param s - Raw path string.
|
|
8698
|
-
* @returns String with glob metacharacters wrapped in character classes.
|
|
8699
|
-
*/
|
|
8700
|
-
function escapeGlob(s) {
|
|
8701
|
-
return s.replace(/[*?[\]{}()!]/g, (ch) => `[${ch}]`);
|
|
8702
|
-
}
|
|
8703
|
-
|
|
8704
|
-
/**
|
|
8705
|
-
* Filter file paths by modification time.
|
|
8706
|
-
*
|
|
8707
|
-
* Shared utility for staleness detection and delta file enumeration.
|
|
8708
|
-
* Uses `fs.statSync` for fast local mtime checks on known paths.
|
|
8709
|
-
*
|
|
8710
|
-
* @module mtimeFilter
|
|
8711
|
-
*/
|
|
8712
|
-
/**
|
|
8713
|
-
* Check if any file in the list was modified after the given timestamp.
|
|
8714
|
-
*
|
|
8715
|
-
* Short-circuits on first match for efficiency (staleness checks).
|
|
8716
|
-
*
|
|
8717
|
-
* @param files - Array of file paths to check.
|
|
8718
|
-
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` match.
|
|
8719
|
-
* @returns True if any file was modified after the timestamp.
|
|
8720
|
-
*/
|
|
8721
|
-
function hasModifiedAfter(files, afterMs) {
|
|
8722
|
-
for (const filePath of files) {
|
|
8723
|
-
try {
|
|
8724
|
-
if (statSync(filePath).mtimeMs > afterMs)
|
|
8725
|
-
return true;
|
|
8726
|
-
}
|
|
8727
|
-
catch {
|
|
8728
|
-
// Unreadable file — skip
|
|
8729
|
-
}
|
|
8730
|
-
}
|
|
8731
|
-
return false;
|
|
8732
|
-
}
|
|
8733
|
-
/**
|
|
8734
|
-
* Filter files to only those modified after the given timestamp.
|
|
8735
|
-
*
|
|
8736
|
-
* @param files - Array of file paths to filter.
|
|
8737
|
-
* @param afterMs - Timestamp in milliseconds. Files with `mtimeMs > afterMs` are included.
|
|
8738
|
-
* @returns Filtered array of file paths.
|
|
8739
|
-
*/
|
|
8740
|
-
function filterModifiedAfter(files, afterMs) {
|
|
8741
|
-
return files.filter((filePath) => {
|
|
8742
|
-
try {
|
|
8743
|
-
return statSync(filePath).mtimeMs > afterMs;
|
|
8744
|
-
}
|
|
8745
|
-
catch {
|
|
8746
|
-
return false;
|
|
8747
|
-
}
|
|
8748
|
-
});
|
|
8749
|
-
}
|
|
8750
|
-
|
|
8751
8948
|
/**
|
|
8752
8949
|
* Compute the file scope owned by a meta node.
|
|
8753
8950
|
*
|
|
@@ -8824,11 +9021,6 @@ function getDeltaFiles(generatedAt, scopeFiles) {
|
|
|
8824
9021
|
return filterModifiedAfter(scopeFiles, new Date(generatedAt).getTime());
|
|
8825
9022
|
}
|
|
8826
9023
|
|
|
8827
|
-
/** Sleep for a given number of milliseconds. */
|
|
8828
|
-
function sleep(ms) {
|
|
8829
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8830
|
-
}
|
|
8831
|
-
|
|
8832
9024
|
/**
|
|
8833
9025
|
* Error thrown when a spawned subprocess is aborted via AbortController.
|
|
8834
9026
|
*
|
|
@@ -8983,7 +9175,7 @@ class GatewayExecutor {
|
|
|
8983
9175
|
JSON.stringify(spawnResult));
|
|
8984
9176
|
}
|
|
8985
9177
|
// Step 2: Poll for completion via sessions_history
|
|
8986
|
-
await
|
|
9178
|
+
await sleepAsync(3000);
|
|
8987
9179
|
while (Date.now() < deadline) {
|
|
8988
9180
|
// Check for abort before each poll iteration
|
|
8989
9181
|
if (this.controller.signal.aborted) {
|
|
@@ -9047,7 +9239,7 @@ class GatewayExecutor {
|
|
|
9047
9239
|
catch {
|
|
9048
9240
|
// Transient poll failure — keep trying
|
|
9049
9241
|
}
|
|
9050
|
-
await
|
|
9242
|
+
await sleepAsync(this.pollIntervalMs);
|
|
9051
9243
|
}
|
|
9052
9244
|
throw new SpawnTimeoutError('Synthesis subprocess timed out after ' + timeoutMs.toString() + 'ms', outputPath);
|
|
9053
9245
|
}
|
|
@@ -9720,97 +9912,6 @@ function discoverStalestPath(candidates, depthWeight) {
|
|
|
9720
9912
|
return winner?.node.metaPath ?? null;
|
|
9721
9913
|
}
|
|
9722
9914
|
|
|
9723
|
-
/**
|
|
9724
|
-
* Staleness detection via watcher walk.
|
|
9725
|
-
*
|
|
9726
|
-
* A meta is stale when any watched file in its scope was modified after
|
|
9727
|
-
* `_generatedAt`.
|
|
9728
|
-
*
|
|
9729
|
-
* @module scheduling/staleness
|
|
9730
|
-
*/
|
|
9731
|
-
/**
|
|
9732
|
-
* Check if a meta is stale.
|
|
9733
|
-
*
|
|
9734
|
-
* Uses watcher `/walk` to enumerate watched files under the scope prefix,
|
|
9735
|
-
* then applies a local mtime check (fast) to detect any modifications since
|
|
9736
|
-
* `_generatedAt`. Short-circuits on first match.
|
|
9737
|
-
*
|
|
9738
|
-
* @param scopePrefix - Path prefix for this meta's scope.
|
|
9739
|
-
* @param meta - Current meta.json content.
|
|
9740
|
-
* @param watcher - WatcherClient instance.
|
|
9741
|
-
* @returns True if any file in scope was modified after `_generatedAt`.
|
|
9742
|
-
*/
|
|
9743
|
-
async function isStale(scopePrefix, meta, watcher) {
|
|
9744
|
-
if (!meta._generatedAt)
|
|
9745
|
-
return true; // Never synthesized = stale
|
|
9746
|
-
const files = await watcher.walk([`${escapeGlob(scopePrefix)}/**`]);
|
|
9747
|
-
return hasModifiedAfter(files, new Date(meta._generatedAt).getTime());
|
|
9748
|
-
}
|
|
9749
|
-
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
9750
|
-
const MAX_STALENESS_SECONDS = 365 * 86_400;
|
|
9751
|
-
/**
|
|
9752
|
-
* Compute actual staleness in seconds (now minus _generatedAt).
|
|
9753
|
-
*
|
|
9754
|
-
* Never-synthesized metas are capped at {@link MAX_STALENESS_SECONDS}
|
|
9755
|
-
* (1 year) so that depth weighting can differentiate them. Without
|
|
9756
|
-
* bounding, `Infinity * depthFactor` = `Infinity` for all depths.
|
|
9757
|
-
*
|
|
9758
|
-
* @param meta - Current meta.json content.
|
|
9759
|
-
* @returns Staleness in seconds, capped at 1 year for never-synthesized metas.
|
|
9760
|
-
*/
|
|
9761
|
-
function actualStaleness(meta) {
|
|
9762
|
-
if (!meta._generatedAt)
|
|
9763
|
-
return MAX_STALENESS_SECONDS;
|
|
9764
|
-
const generatedMs = new Date(meta._generatedAt).getTime();
|
|
9765
|
-
return Math.min((Date.now() - generatedMs) / 1000, MAX_STALENESS_SECONDS);
|
|
9766
|
-
}
|
|
9767
|
-
/**
|
|
9768
|
-
* Check whether the architect step should be triggered.
|
|
9769
|
-
*
|
|
9770
|
-
* @param meta - Current meta.json.
|
|
9771
|
-
* @param structureChanged - Whether the structure hash changed.
|
|
9772
|
-
* @param steerChanged - Whether the steer directive changed.
|
|
9773
|
-
* @param architectEvery - Config: run architect every N cycles.
|
|
9774
|
-
* @returns True if the architect step should run.
|
|
9775
|
-
*/
|
|
9776
|
-
function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
|
|
9777
|
-
return (!meta._builder ||
|
|
9778
|
-
structureChanged ||
|
|
9779
|
-
steerChanged ||
|
|
9780
|
-
(meta._synthesisCount ?? 0) >= architectEvery);
|
|
9781
|
-
}
|
|
9782
|
-
/**
|
|
9783
|
-
* Detect whether the steer directive changed since the last archive.
|
|
9784
|
-
*
|
|
9785
|
-
* @param currentSteer - Current _steer value (or undefined).
|
|
9786
|
-
* @param archiveSteer - Archive _steer value (or undefined).
|
|
9787
|
-
* @param hasArchive - Whether an archive snapshot exists.
|
|
9788
|
-
* @returns True if steer changed.
|
|
9789
|
-
*/
|
|
9790
|
-
function hasSteerChanged(currentSteer, archiveSteer, hasArchive) {
|
|
9791
|
-
if (!hasArchive)
|
|
9792
|
-
return Boolean(currentSteer);
|
|
9793
|
-
return currentSteer !== archiveSteer;
|
|
9794
|
-
}
|
|
9795
|
-
/**
|
|
9796
|
-
* Compute a normalized staleness score (0–1) for display purposes.
|
|
9797
|
-
*
|
|
9798
|
-
* Uses the same depth/emphasis weighting as candidate selection,
|
|
9799
|
-
* normalized to a 30-day window.
|
|
9800
|
-
*
|
|
9801
|
-
* @param stalenessSeconds - Raw staleness in seconds (null = never synthesized).
|
|
9802
|
-
* @param depth - Meta tree depth.
|
|
9803
|
-
* @param emphasis - Scheduling emphasis multiplier.
|
|
9804
|
-
* @param depthWeight - Depth weighting exponent from config.
|
|
9805
|
-
* @returns Normalized score between 0 and 1.
|
|
9806
|
-
*/
|
|
9807
|
-
function computeStalenessScore(stalenessSeconds, depth, emphasis, depthWeight) {
|
|
9808
|
-
if (stalenessSeconds === null)
|
|
9809
|
-
return 1;
|
|
9810
|
-
const depthFactor = Math.pow(1 + depthWeight, depth);
|
|
9811
|
-
return Math.min(1, (stalenessSeconds * depthFactor * emphasis) / (30 * 86400));
|
|
9812
|
-
}
|
|
9813
|
-
|
|
9814
9915
|
/**
|
|
9815
9916
|
* Shared error utilities.
|
|
9816
9917
|
*
|
|
@@ -10871,7 +10972,10 @@ function buildMetaRules(config) {
|
|
|
10871
10972
|
properties: {
|
|
10872
10973
|
file: {
|
|
10873
10974
|
properties: {
|
|
10874
|
-
path: {
|
|
10975
|
+
path: {
|
|
10976
|
+
type: 'string',
|
|
10977
|
+
glob: '**/jeeves-meta{.config.json,/config.json}',
|
|
10978
|
+
},
|
|
10875
10979
|
},
|
|
10876
10980
|
},
|
|
10877
10981
|
},
|
|
@@ -12004,7 +12108,7 @@ class HttpWatcherClient {
|
|
|
12004
12108
|
}
|
|
12005
12109
|
// Exponential backoff
|
|
12006
12110
|
const delayMs = this.backoffBaseMs * Math.pow(this.backoffFactor, attempt);
|
|
12007
|
-
await
|
|
12111
|
+
await sleepAsync(delayMs);
|
|
12008
12112
|
}
|
|
12009
12113
|
// Unreachable, but TypeScript needs it
|
|
12010
12114
|
throw new Error('Retry exhausted');
|
|
@@ -12260,4 +12364,4 @@ const metaDescriptor = jeevesComponentDescriptorSchema.parse({
|
|
|
12260
12364
|
customCliCommands: registerCustomCliCommands,
|
|
12261
12365
|
});
|
|
12262
12366
|
|
|
12263
|
-
export { DEFAULT_PORT, DEFAULT_PORT_STR, GatewayExecutor, HttpWatcherClient, ProgressReporter, RESTART_REQUIRED_FIELDS, RuleRegistrar, SERVICE_NAME, SERVICE_VERSION, Scheduler, SynthesisQueue, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildOwnershipTree, cleanupStaleLocks, computeEffectiveStaleness, computeEma, computeStructureHash, createLogger, createServer, createSnapshot, discoverMetas, filterInScope, findNode, formatProgressEvent, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, listMetas, loadServiceConfig, mergeAndWrite, metaConfigSchema, metaDescriptor, metaErrorSchema, metaJsonSchema, migrateConfigPath, normalizePath, orchestrate, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, readLockState, registerCustomCliCommands, registerRoutes, registerShutdownHandlers, releaseLock, resolveConfigPath, resolveMetaDir, selectCandidate, serviceConfigSchema, sleep, startService, toMetaError, verifyRuleApplication };
|
|
12367
|
+
export { DEFAULT_PORT, DEFAULT_PORT_STR, GatewayExecutor, HttpWatcherClient, MAX_STALENESS_SECONDS, ProgressReporter, RESTART_REQUIRED_FIELDS, RuleRegistrar, SERVICE_NAME, SERVICE_VERSION, Scheduler, SynthesisQueue, acquireLock, actualStaleness, buildArchitectTask, buildBuilderTask, buildContextPackage, buildCriticTask, buildOwnershipTree, cleanupStaleLocks, computeEffectiveStaleness, computeEma, computeStructureHash, createLogger, createServer, createSnapshot, discoverMetas, filterInScope, findNode, formatProgressEvent, getScopePrefix, hasSteerChanged, isArchitectTriggered, isLocked, isStale, listArchiveFiles, listMetas, loadServiceConfig, mergeAndWrite, metaConfigSchema, metaDescriptor, metaErrorSchema, metaJsonSchema, migrateConfigPath, normalizePath, orchestrate, parseArchitectOutput, parseBuilderOutput, parseCriticOutput, pruneArchive, readLatestArchive, readLockState, registerCustomCliCommands, registerRoutes, registerShutdownHandlers, releaseLock, resolveConfigPath, resolveMetaDir, selectCandidate, serviceConfigSchema, sleepAsync as sleep, startService, toMetaError, verifyRuleApplication };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karmaniverous/jeeves-meta",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"author": "Jason Williscroft",
|
|
5
5
|
"description": "Fastify HTTP service for the Jeeves Meta synthesis engine",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"typescript"
|
|
39
39
|
],
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
41
|
+
"node": ">=22"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@karmaniverous/jeeves": "^0.
|
|
44
|
+
"@karmaniverous/jeeves": "^0.5.1",
|
|
45
45
|
"commander": "^14",
|
|
46
46
|
"croner": "^10",
|
|
47
47
|
"fastify": "^5.8",
|