@karmaniverous/jeeves-meta 0.15.8 → 0.15.9
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 +5 -5
- package/dist/cli/jeeves-meta/index.js +405 -200
- package/dist/discovery/getAncestorMeta.d.ts +16 -0
- package/dist/discovery/index.d.ts +1 -0
- package/dist/index.js +402 -185
- package/dist/interfaces/MetaContext.d.ts +2 -0
- package/dist/phaseState/derivePhaseState.d.ts +0 -2
- package/dist/phaseState/index.d.ts +1 -1
- package/dist/phaseState/invalidate.d.ts +5 -7
- package/dist/phaseState/phaseTransitions.d.ts +1 -1
- package/dist/routes/metasUpdate.d.ts +1 -1
- package/dist/routes/status.d.ts +1 -1
- package/dist/rules/index.d.ts +1 -3
- package/dist/schema/config.d.ts +8 -0
- package/dist/schema/meta.d.ts +4 -0
- package/dist/seed/createMeta.d.ts +6 -0
- package/package.json +58 -58
|
@@ -5615,6 +5615,9 @@ function requireRange () {
|
|
|
5615
5615
|
}
|
|
5616
5616
|
|
|
5617
5617
|
parseRange (range) {
|
|
5618
|
+
// strip build metadata so it can't bleed into the version
|
|
5619
|
+
range = range.replace(BUILDSTRIPRE, '');
|
|
5620
|
+
|
|
5618
5621
|
// memoize range parsing for performance.
|
|
5619
5622
|
// this is a very hot path, and fully deterministic.
|
|
5620
5623
|
const memoOpts =
|
|
@@ -5740,6 +5743,7 @@ function requireRange () {
|
|
|
5740
5743
|
const SemVer = requireSemver$1();
|
|
5741
5744
|
const {
|
|
5742
5745
|
safeRe: re,
|
|
5746
|
+
src,
|
|
5743
5747
|
t,
|
|
5744
5748
|
comparatorTrimReplace,
|
|
5745
5749
|
tildeTrimReplace,
|
|
@@ -5747,6 +5751,9 @@ function requireRange () {
|
|
|
5747
5751
|
} = requireRe();
|
|
5748
5752
|
const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = requireConstants();
|
|
5749
5753
|
|
|
5754
|
+
// unbounded global build-metadata stripper used by parseRange
|
|
5755
|
+
const BUILDSTRIPRE = new RegExp(src[t.BUILD], 'g');
|
|
5756
|
+
|
|
5750
5757
|
const isNullSet = c => c.value === '<0.0.0-0';
|
|
5751
5758
|
const isAny = c => c.value === '';
|
|
5752
5759
|
|
|
@@ -6798,7 +6805,7 @@ function requireSubset () {
|
|
|
6798
6805
|
if (higher === c && higher !== gt) {
|
|
6799
6806
|
return false
|
|
6800
6807
|
}
|
|
6801
|
-
} else if (gt.operator === '>=' && !
|
|
6808
|
+
} else if (gt.operator === '>=' && !c.test(gt.semver)) {
|
|
6802
6809
|
return false
|
|
6803
6810
|
}
|
|
6804
6811
|
}
|
|
@@ -6816,7 +6823,7 @@ function requireSubset () {
|
|
|
6816
6823
|
if (lower === c && lower !== lt) {
|
|
6817
6824
|
return false
|
|
6818
6825
|
}
|
|
6819
|
-
} else if (lt.operator === '<=' && !
|
|
6826
|
+
} else if (lt.operator === '<=' && !c.test(lt.semver)) {
|
|
6820
6827
|
return false
|
|
6821
6828
|
}
|
|
6822
6829
|
}
|
|
@@ -7112,12 +7119,26 @@ const DEFAULT_PORTS = {
|
|
|
7112
7119
|
* - `{configRoot}/jeeves-{name}/` for each component
|
|
7113
7120
|
*/
|
|
7114
7121
|
let state;
|
|
7122
|
+
const WINDOWS_DRIVE_RE = /^[a-zA-Z]:/;
|
|
7123
|
+
/**
|
|
7124
|
+
* Throw if a path looks like a Windows drive letter on a non-Windows platform.
|
|
7125
|
+
*
|
|
7126
|
+
* @param label - Human-readable name for the path (used in error messages).
|
|
7127
|
+
* @param value - The raw path string to validate.
|
|
7128
|
+
*/
|
|
7129
|
+
function rejectWindowsDrivePath(label, value) {
|
|
7130
|
+
if (process.platform !== 'win32' && WINDOWS_DRIVE_RE.test(value)) {
|
|
7131
|
+
throw new Error(`jeeves-core: ${label} "${value}" looks like a Windows drive-letter path and will not resolve correctly on this platform.`);
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7115
7134
|
/**
|
|
7116
7135
|
* Initialize the core library with workspace and config root paths.
|
|
7117
7136
|
*
|
|
7118
7137
|
* @param options - Workspace and config root paths.
|
|
7119
7138
|
*/
|
|
7120
7139
|
function init(options) {
|
|
7140
|
+
rejectWindowsDrivePath('configRoot', options.configRoot);
|
|
7141
|
+
rejectWindowsDrivePath('workspacePath', options.workspacePath);
|
|
7121
7142
|
state = {
|
|
7122
7143
|
workspacePath: options.workspacePath,
|
|
7123
7144
|
configRoot: options.configRoot,
|
|
@@ -8254,6 +8275,20 @@ When editing files outside the workspace, use the bridge pattern: copy in → ed
|
|
|
8254
8275
|
|
|
8255
8276
|
**Cross-channel sends:** Use the \`message\` tool with an explicit \`target\` to send to a different channel or DM.
|
|
8256
8277
|
|
|
8278
|
+
### Slack File Downloads
|
|
8279
|
+
|
|
8280
|
+
To download a Slack-hosted file, first try the \`message\` tool's \`download-file\` action. If that fails, fall back to a direct HTTP fetch using the bot token:
|
|
8281
|
+
|
|
8282
|
+
\`\`\`js
|
|
8283
|
+
fetch(url_private_download, {
|
|
8284
|
+
headers: { Authorization: 'Bearer ' + botToken },
|
|
8285
|
+
});
|
|
8286
|
+
\`\`\`
|
|
8287
|
+
|
|
8288
|
+
The bot token is at \`channels.slack.accounts.default.botToken\` in \`openclaw.json\`.
|
|
8289
|
+
|
|
8290
|
+
Never tell the user a file can't be downloaded until both methods have been tried.
|
|
8291
|
+
|
|
8257
8292
|
### Plugin Lifecycle
|
|
8258
8293
|
|
|
8259
8294
|
\`\`\`bash
|
|
@@ -8489,6 +8524,113 @@ const META_COMPONENT = {
|
|
|
8489
8524
|
pluginPackage: '@karmaniverous/jeeves-meta-openclaw',
|
|
8490
8525
|
defaultPort: 1938};
|
|
8491
8526
|
|
|
8527
|
+
/**
|
|
8528
|
+
* Shared endpoint catalog — single source of truth for the jeeves-meta API.
|
|
8529
|
+
*
|
|
8530
|
+
* Both the CLI service and the OpenClaw plugin derive their registrations
|
|
8531
|
+
* from this declarative catalog, eliminating drift between the two.
|
|
8532
|
+
*
|
|
8533
|
+
*/
|
|
8534
|
+
/**
|
|
8535
|
+
* Canonical endpoint catalog for the jeeves-meta API.
|
|
8536
|
+
*
|
|
8537
|
+
* Every entry describes a single HTTP endpoint exposed by the service.
|
|
8538
|
+
* Route handlers, plugin tools, and HTTP clients should reference these
|
|
8539
|
+
* descriptors rather than hard-coding paths and descriptions.
|
|
8540
|
+
*/
|
|
8541
|
+
const META_ENDPOINTS = [
|
|
8542
|
+
{
|
|
8543
|
+
name: 'status',
|
|
8544
|
+
method: 'GET',
|
|
8545
|
+
path: '/status',
|
|
8546
|
+
description: 'Service health and status overview.',
|
|
8547
|
+
},
|
|
8548
|
+
{
|
|
8549
|
+
name: 'listMetas',
|
|
8550
|
+
method: 'GET',
|
|
8551
|
+
path: '/metas',
|
|
8552
|
+
description: 'List metas with summary stats and per-meta projection. Response includes _phaseState and owedPhase per meta.',
|
|
8553
|
+
},
|
|
8554
|
+
{
|
|
8555
|
+
name: 'metaDetail',
|
|
8556
|
+
method: 'GET',
|
|
8557
|
+
path: '/metas/:path',
|
|
8558
|
+
description: 'Full detail for a single meta, with optional archive history. Response includes _phaseState and owedPhase.',
|
|
8559
|
+
},
|
|
8560
|
+
{
|
|
8561
|
+
name: 'updateMeta',
|
|
8562
|
+
method: 'PATCH',
|
|
8563
|
+
path: '/metas/:path',
|
|
8564
|
+
description: 'Update user-settable reserved properties on a meta entity.',
|
|
8565
|
+
},
|
|
8566
|
+
{
|
|
8567
|
+
name: 'synthesize',
|
|
8568
|
+
method: 'POST',
|
|
8569
|
+
path: '/synthesize',
|
|
8570
|
+
description: 'Trigger synthesis. Path-targeted creates an override queue entry; returns owedPhase. Fully-fresh metas return status:skipped.',
|
|
8571
|
+
},
|
|
8572
|
+
{
|
|
8573
|
+
name: 'abort',
|
|
8574
|
+
method: 'POST',
|
|
8575
|
+
path: '/synthesize/abort',
|
|
8576
|
+
description: 'Abort the currently running synthesis.',
|
|
8577
|
+
},
|
|
8578
|
+
{
|
|
8579
|
+
name: 'preview',
|
|
8580
|
+
method: 'GET',
|
|
8581
|
+
path: '/preview',
|
|
8582
|
+
description: 'Dry-run preview of next synthesis. Returns owedPhase, priorityBand, phaseState, inputStatus, and architectInvalidators.',
|
|
8583
|
+
},
|
|
8584
|
+
{
|
|
8585
|
+
name: 'seed',
|
|
8586
|
+
method: 'POST',
|
|
8587
|
+
path: '/seed',
|
|
8588
|
+
description: 'Create a .meta/ directory and initial meta.json for a new entity path.',
|
|
8589
|
+
},
|
|
8590
|
+
{
|
|
8591
|
+
name: 'unlock',
|
|
8592
|
+
method: 'POST',
|
|
8593
|
+
path: '/unlock',
|
|
8594
|
+
description: 'Remove a stale .lock from a meta entity that is stuck.',
|
|
8595
|
+
},
|
|
8596
|
+
{
|
|
8597
|
+
name: 'config',
|
|
8598
|
+
method: 'GET',
|
|
8599
|
+
path: '/config',
|
|
8600
|
+
description: 'Query service configuration with optional JSONPath.',
|
|
8601
|
+
},
|
|
8602
|
+
{
|
|
8603
|
+
name: 'configApply',
|
|
8604
|
+
method: 'POST',
|
|
8605
|
+
path: '/config/apply',
|
|
8606
|
+
description: 'Apply a configuration patch.',
|
|
8607
|
+
},
|
|
8608
|
+
{
|
|
8609
|
+
name: 'queue',
|
|
8610
|
+
method: 'GET',
|
|
8611
|
+
path: '/queue',
|
|
8612
|
+
description: 'List queued synthesis operations (3-layer model: current, overrides, automatic).',
|
|
8613
|
+
},
|
|
8614
|
+
{
|
|
8615
|
+
name: 'queueClear',
|
|
8616
|
+
method: 'POST',
|
|
8617
|
+
path: '/queue/clear',
|
|
8618
|
+
description: 'Clear override entries from the queue.',
|
|
8619
|
+
},
|
|
8620
|
+
];
|
|
8621
|
+
/**
|
|
8622
|
+
* Look up an endpoint descriptor by name.
|
|
8623
|
+
*
|
|
8624
|
+
* @param name - The endpoint identifier.
|
|
8625
|
+
* @returns The matching {@link EndpointDescriptor}.
|
|
8626
|
+
*/
|
|
8627
|
+
function getEndpoint(name) {
|
|
8628
|
+
const ep = META_ENDPOINTS.find((e) => e.name === name);
|
|
8629
|
+
if (!ep)
|
|
8630
|
+
throw new Error(`Unknown endpoint: ${name}`);
|
|
8631
|
+
return ep;
|
|
8632
|
+
}
|
|
8633
|
+
|
|
8492
8634
|
/**
|
|
8493
8635
|
* Structured error schema from a synthesis step failure.
|
|
8494
8636
|
*
|
|
@@ -8683,6 +8825,24 @@ async function discoverMetas(watcher) {
|
|
|
8683
8825
|
return metaPaths;
|
|
8684
8826
|
}
|
|
8685
8827
|
|
|
8828
|
+
/**
|
|
8829
|
+
* Retrieve the nearest ancestor meta node from the ownership tree.
|
|
8830
|
+
*
|
|
8831
|
+
* @module discovery/getAncestorMeta
|
|
8832
|
+
*/
|
|
8833
|
+
/**
|
|
8834
|
+
* Get the nearest ancestor MetaNode for a given node.
|
|
8835
|
+
*
|
|
8836
|
+
* Walks up the ownership tree (via the parent pointer set by
|
|
8837
|
+
* buildOwnershipTree) to find the closest ancestor .meta/ directory.
|
|
8838
|
+
*
|
|
8839
|
+
* @param node - The meta node to find the ancestor for.
|
|
8840
|
+
* @returns The parent MetaNode, or null for root-level metas.
|
|
8841
|
+
*/
|
|
8842
|
+
function getAncestorMeta(node) {
|
|
8843
|
+
return node.parent;
|
|
8844
|
+
}
|
|
8845
|
+
|
|
8686
8846
|
/**
|
|
8687
8847
|
* File-system lock for preventing concurrent synthesis on the same meta.
|
|
8688
8848
|
*
|
|
@@ -8938,21 +9098,6 @@ async function isStale(scopePrefix, meta, watcher) {
|
|
|
8938
9098
|
}
|
|
8939
9099
|
/** Maximum staleness for never-synthesized metas (1 year in seconds). */
|
|
8940
9100
|
const MAX_STALENESS_SECONDS = 365 * 86_400;
|
|
8941
|
-
/**
|
|
8942
|
-
* Check whether the architect step should be triggered.
|
|
8943
|
-
*
|
|
8944
|
-
* @param meta - Current meta.json.
|
|
8945
|
-
* @param structureChanged - Whether the structure hash changed.
|
|
8946
|
-
* @param steerChanged - Whether the steer directive changed.
|
|
8947
|
-
* @param architectEvery - Config: run architect every N cycles.
|
|
8948
|
-
* @returns True if the architect step should run.
|
|
8949
|
-
*/
|
|
8950
|
-
function isArchitectTriggered(meta, structureChanged, steerChanged, architectEvery) {
|
|
8951
|
-
return (!meta._builder ||
|
|
8952
|
-
structureChanged ||
|
|
8953
|
-
steerChanged ||
|
|
8954
|
-
(meta._synthesisCount ?? 0) >= architectEvery);
|
|
8955
|
-
}
|
|
8956
9101
|
/**
|
|
8957
9102
|
* Detect whether the steer directive changed since the last archive.
|
|
8958
9103
|
*
|
|
@@ -9339,6 +9484,14 @@ const autoSeedRuleSchema = z.object({
|
|
|
9339
9484
|
steer: z.string().optional(),
|
|
9340
9485
|
/** Optional cross-references for seeded metas. */
|
|
9341
9486
|
crossRefs: z.array(z.string()).optional(),
|
|
9487
|
+
/** Walk up this many extra parent levels from the matched file's directory. Default 0. */
|
|
9488
|
+
parentDepth: z.number().int().min(0).optional(),
|
|
9489
|
+
/** Per-category timeout override for the architect phase (seconds, min 30). */
|
|
9490
|
+
architectTimeout: z.number().int().min(30).optional(),
|
|
9491
|
+
/** Per-category timeout override for the builder phase (seconds, min 30). */
|
|
9492
|
+
builderTimeout: z.number().int().min(30).optional(),
|
|
9493
|
+
/** Per-category timeout override for the critic phase (seconds, min 30). */
|
|
9494
|
+
criticTimeout: z.number().int().min(30).optional(),
|
|
9342
9495
|
});
|
|
9343
9496
|
/** Zod schema for jeeves-meta service configuration (superset of MetaConfig). */
|
|
9344
9497
|
const serviceConfigSchema = metaConfigSchema.extend({
|
|
@@ -9585,7 +9738,7 @@ class GatewayExecutor {
|
|
|
9585
9738
|
'Write your complete output to a file using the Write tool at:\n' +
|
|
9586
9739
|
outputPath +
|
|
9587
9740
|
'\n\n' +
|
|
9588
|
-
'After writing the file,
|
|
9741
|
+
'After writing the file, your final message must be exactly: ANNOUNCE_SKIP';
|
|
9589
9742
|
// Step 1: Spawn the sub-agent session (unique label per cycle to avoid
|
|
9590
9743
|
// "label already in use" errors — gateway labels persist after session completion)
|
|
9591
9744
|
const labelBase = options?.label ?? 'jeeves-meta-synthesis';
|
|
@@ -9653,7 +9806,9 @@ class GatewayExecutor {
|
|
|
9653
9806
|
}
|
|
9654
9807
|
}
|
|
9655
9808
|
}
|
|
9656
|
-
// Fallback: extract from message content if file wasn't written
|
|
9809
|
+
// Fallback: extract from message content if file wasn't written.
|
|
9810
|
+
// Skip ANNOUNCE_SKIP sentinel messages — the real output is in
|
|
9811
|
+
// a preceding assistant message (the file write).
|
|
9657
9812
|
for (let i = msgArray.length - 1; i >= 0; i--) {
|
|
9658
9813
|
const msg = msgArray[i];
|
|
9659
9814
|
if (msg.role === 'assistant' && msg.content) {
|
|
@@ -9665,7 +9820,7 @@ class GatewayExecutor {
|
|
|
9665
9820
|
.map((b) => b.text)
|
|
9666
9821
|
.join('\n')
|
|
9667
9822
|
: '';
|
|
9668
|
-
if (text)
|
|
9823
|
+
if (text && text.trim() !== 'ANNOUNCE_SKIP')
|
|
9669
9824
|
return { output: text, tokens };
|
|
9670
9825
|
}
|
|
9671
9826
|
}
|
|
@@ -9695,11 +9850,16 @@ class GatewayExecutor {
|
|
|
9695
9850
|
function createLogger(config) {
|
|
9696
9851
|
const level = config?.level ?? 'info';
|
|
9697
9852
|
if (config?.file) {
|
|
9698
|
-
const
|
|
9699
|
-
|
|
9700
|
-
|
|
9853
|
+
const fileStream = pino.destination({
|
|
9854
|
+
dest: config.file,
|
|
9855
|
+
sync: false,
|
|
9856
|
+
mkdir: true,
|
|
9701
9857
|
});
|
|
9702
|
-
|
|
9858
|
+
const multistream = pino.multistream([
|
|
9859
|
+
{ stream: process.stdout },
|
|
9860
|
+
{ stream: fileStream },
|
|
9861
|
+
]);
|
|
9862
|
+
return pino({ level }, multistream);
|
|
9703
9863
|
}
|
|
9704
9864
|
return pino({ level });
|
|
9705
9865
|
}
|
|
@@ -10009,6 +10169,21 @@ async function buildContextPackage(node, meta, watcher, logger) {
|
|
|
10009
10169
|
}
|
|
10010
10170
|
// Archive paths
|
|
10011
10171
|
const archives = listArchiveFiles(node.metaPath);
|
|
10172
|
+
// Nearest ancestor _builder output
|
|
10173
|
+
let ancestorBuilder;
|
|
10174
|
+
const ancestor = getAncestorMeta(node);
|
|
10175
|
+
if (ancestor) {
|
|
10176
|
+
try {
|
|
10177
|
+
const raw = await readFile(join(ancestor.metaPath, 'meta.json'), 'utf8');
|
|
10178
|
+
const ancestorMeta = JSON.parse(raw);
|
|
10179
|
+
if (ancestorMeta._builder) {
|
|
10180
|
+
ancestorBuilder = ancestorMeta._builder;
|
|
10181
|
+
}
|
|
10182
|
+
}
|
|
10183
|
+
catch {
|
|
10184
|
+
// Ancestor meta.json unreadable — skip
|
|
10185
|
+
}
|
|
10186
|
+
}
|
|
10012
10187
|
return {
|
|
10013
10188
|
path: node.metaPath,
|
|
10014
10189
|
scopeFiles,
|
|
@@ -10020,6 +10195,7 @@ async function buildContextPackage(node, meta, watcher, logger) {
|
|
|
10020
10195
|
steer: meta._steer ?? null,
|
|
10021
10196
|
previousState: meta._state ?? null,
|
|
10022
10197
|
archives,
|
|
10198
|
+
ancestorBuilder,
|
|
10023
10199
|
};
|
|
10024
10200
|
}
|
|
10025
10201
|
|
|
@@ -10067,6 +10243,12 @@ function appendMetaSections(sections, heading, metas) {
|
|
|
10067
10243
|
sections.push(`### ${path}`, typeof content === 'string' ? content : '(not yet synthesized)');
|
|
10068
10244
|
}
|
|
10069
10245
|
}
|
|
10246
|
+
/** Inject nearest ancestor's organizational context, if available. */
|
|
10247
|
+
function appendAncestorContext(sections, ctx) {
|
|
10248
|
+
if (ctx.ancestorBuilder) {
|
|
10249
|
+
sections.push('', '## PARENT ORGANIZATIONAL CONTEXT', ctx.ancestorBuilder);
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10070
10252
|
/** Append optional context sections shared across all step prompts. */
|
|
10071
10253
|
function appendSharedSections(sections, ctx, options) {
|
|
10072
10254
|
const opts = {
|
|
@@ -10106,7 +10288,7 @@ function buildArchitectTask(ctx, meta, config) {
|
|
|
10106
10288
|
const sections = [
|
|
10107
10289
|
`# jeeves-meta · ARCHITECT · ${ctx.path}`,
|
|
10108
10290
|
'',
|
|
10109
|
-
|
|
10291
|
+
config.defaultArchitect ?? DEFAULT_ARCHITECT_PROMPT,
|
|
10110
10292
|
'',
|
|
10111
10293
|
'## SCOPE',
|
|
10112
10294
|
`Path: ${ctx.path}`,
|
|
@@ -10116,6 +10298,7 @@ function buildArchitectTask(ctx, meta, config) {
|
|
|
10116
10298
|
'### File listing (scope)',
|
|
10117
10299
|
condenseScopeFiles(ctx.scopeFiles),
|
|
10118
10300
|
];
|
|
10301
|
+
appendAncestorContext(sections, ctx);
|
|
10119
10302
|
// Inject previous _builder so architect can see its own prior output
|
|
10120
10303
|
if (meta._builder) {
|
|
10121
10304
|
sections.push('', '## PREVIOUS TASK BRIEF', meta._builder);
|
|
@@ -10146,6 +10329,7 @@ function buildBuilderTask(ctx, meta, config) {
|
|
|
10146
10329
|
`Delta files (${ctx.deltaFiles.length.toString()} changed):`,
|
|
10147
10330
|
...ctx.deltaFiles.slice(0, config.maxLines).map((f) => `- ${f}`),
|
|
10148
10331
|
];
|
|
10332
|
+
appendAncestorContext(sections, ctx);
|
|
10149
10333
|
if (ctx.previousState != null) {
|
|
10150
10334
|
sections.push('', '## PREVIOUS STATE', 'The following opaque state was returned by the previous synthesis cycle.', 'Use it to continue progressive work. Update `_state` in your output to', 'reflect your progress.', '', '```json', JSON.stringify(ctx.previousState, null, 2), '```');
|
|
10151
10335
|
}
|
|
@@ -10168,7 +10352,7 @@ function buildCriticTask(ctx, meta, config) {
|
|
|
10168
10352
|
const sections = [
|
|
10169
10353
|
`# jeeves-meta · CRITIC · ${ctx.path}`,
|
|
10170
10354
|
'',
|
|
10171
|
-
|
|
10355
|
+
config.defaultCritic ?? DEFAULT_CRITIC_PROMPT,
|
|
10172
10356
|
'',
|
|
10173
10357
|
'## SYNTHESIS TO EVALUATE',
|
|
10174
10358
|
meta._content ?? '(No content produced)',
|
|
@@ -10302,7 +10486,7 @@ function enforceInvariant(state) {
|
|
|
10302
10486
|
// ── Invalidation cascades ──────────────────────────────────────────────
|
|
10303
10487
|
/**
|
|
10304
10488
|
* Architect invalidated: architect → pending; builder, critic → stale.
|
|
10305
|
-
* Triggers:
|
|
10489
|
+
* Triggers: first run, _structureHash change, _steer change,
|
|
10306
10490
|
* _crossRefs declaration change, _synthesisCount \>= architectEvery.
|
|
10307
10491
|
*/
|
|
10308
10492
|
function invalidateArchitect(state) {
|
|
@@ -10500,31 +10684,6 @@ function derivePhaseState(meta, inputs) {
|
|
|
10500
10684
|
if (!meta._content && !meta._builder) {
|
|
10501
10685
|
return initialPhaseState();
|
|
10502
10686
|
}
|
|
10503
|
-
// Check architect invalidation (when inputs are provided)
|
|
10504
|
-
if (inputs) {
|
|
10505
|
-
// Progressive metas: structure changes invalidate builder, not architect
|
|
10506
|
-
const structureInvalidatesArchitect = inputs.structureChanged && meta._state === undefined;
|
|
10507
|
-
const architectInvalidated = structureInvalidatesArchitect ||
|
|
10508
|
-
inputs.steerChanged ||
|
|
10509
|
-
inputs.architectChanged ||
|
|
10510
|
-
inputs.crossRefsChanged ||
|
|
10511
|
-
(meta._synthesisCount ?? 0) >= inputs.architectEvery;
|
|
10512
|
-
if (architectInvalidated) {
|
|
10513
|
-
return {
|
|
10514
|
-
architect: 'pending',
|
|
10515
|
-
builder: 'stale',
|
|
10516
|
-
critic: 'stale',
|
|
10517
|
-
};
|
|
10518
|
-
}
|
|
10519
|
-
// Progressive meta with structure change: builder-only invalidation
|
|
10520
|
-
if (inputs.structureChanged && meta._state !== undefined) {
|
|
10521
|
-
return {
|
|
10522
|
-
architect: 'fresh',
|
|
10523
|
-
builder: 'pending',
|
|
10524
|
-
critic: 'stale',
|
|
10525
|
-
};
|
|
10526
|
-
}
|
|
10527
|
-
}
|
|
10528
10687
|
// Has _builder but no _content: builder is pending
|
|
10529
10688
|
if (meta._builder && !meta._content) {
|
|
10530
10689
|
return {
|
|
@@ -10573,6 +10732,14 @@ function computeStructureHash(filePaths) {
|
|
|
10573
10732
|
*
|
|
10574
10733
|
* @module phaseState/invalidate
|
|
10575
10734
|
*/
|
|
10735
|
+
/**
|
|
10736
|
+
* Check whether a persisted prompt snapshot mismatches the currently-resolved prompt.
|
|
10737
|
+
* Returns true when the snapshot exists and differs from the resolved prompt.
|
|
10738
|
+
* This is informational only — it does NOT trigger invalidation.
|
|
10739
|
+
*/
|
|
10740
|
+
function isPromptStale(snapshot, resolved) {
|
|
10741
|
+
return snapshot !== undefined && snapshot !== resolved;
|
|
10742
|
+
}
|
|
10576
10743
|
/**
|
|
10577
10744
|
* Compute invalidation inputs and apply cascade for a single meta.
|
|
10578
10745
|
*
|
|
@@ -10595,10 +10762,16 @@ async function computeInvalidation(meta, scopeFiles, config, node, crossRefMetas
|
|
|
10595
10762
|
const structureChanged = structureHash !== meta._structureHash;
|
|
10596
10763
|
const latestArchive = await readLatestArchive(node.metaPath);
|
|
10597
10764
|
const steerChanged = hasSteerChanged(meta._steer, latestArchive?._steer, Boolean(latestArchive));
|
|
10598
|
-
//
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10765
|
+
// Prompt staleness detection: compare persisted prompt snapshots against
|
|
10766
|
+
// currently-resolved prompts. This is INFORMATIONAL ONLY — reported via
|
|
10767
|
+
// inputStatus so /preview can surface it, but it must NEVER feed into
|
|
10768
|
+
// the invalidation cascade. When a meta naturally reaches architectEvery
|
|
10769
|
+
// through real builder cycles, architect runs with the current prompt and
|
|
10770
|
+
// the snapshot updates. Coupling prompt changes to invalidation causes a
|
|
10771
|
+
// corpus-wide synthesis storm (see #163).
|
|
10772
|
+
const architectChanged = isPromptStale(meta._architect, config.defaultArchitect ?? DEFAULT_ARCHITECT_PROMPT);
|
|
10773
|
+
const criticChanged = isPromptStale(meta._critic, config.defaultCritic ?? DEFAULT_CRITIC_PROMPT);
|
|
10774
|
+
const effectiveSynthesisCount = meta._synthesisCount ?? 0;
|
|
10602
10775
|
// _crossRefs declaration change
|
|
10603
10776
|
const currentRefs = (meta._crossRefs ?? []).slice().sort().join(',');
|
|
10604
10777
|
const archiveRefs = (latestArchive?._crossRefs ?? [])
|
|
@@ -10620,38 +10793,30 @@ async function computeInvalidation(meta, scopeFiles, config, node, crossRefMetas
|
|
|
10620
10793
|
}
|
|
10621
10794
|
if (steerChanged)
|
|
10622
10795
|
architectInvalidators.push('steer');
|
|
10623
|
-
if (architectChanged)
|
|
10624
|
-
architectInvalidators.push('_architect');
|
|
10625
10796
|
if (crossRefsDeclChanged)
|
|
10626
10797
|
architectInvalidators.push('_crossRefs');
|
|
10627
|
-
if (
|
|
10798
|
+
if (effectiveSynthesisCount >= config.architectEvery) {
|
|
10628
10799
|
architectInvalidators.push('architectEvery');
|
|
10629
10800
|
}
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
if (architectInvalidators.length > 0
|
|
10801
|
+
if (!meta._builder)
|
|
10802
|
+
architectInvalidators.push('firstRun');
|
|
10803
|
+
if (architectInvalidators.length > 0) {
|
|
10633
10804
|
phaseState = invalidateArchitect(phaseState);
|
|
10634
10805
|
}
|
|
10635
10806
|
// ── Builder-level inputs ──
|
|
10636
|
-
// Scope file mtime check — if any file newer than _generatedAt
|
|
10637
|
-
const scopeMtimeMax = null;
|
|
10638
|
-
// Note: actual mtime check is done by the caller or via isStale;
|
|
10639
|
-
// here we just detect cross-ref content changes for the cascade.
|
|
10640
10807
|
// Cross-ref _content change (builder-invalidating)
|
|
10641
10808
|
let crossRefContentChanged = false;
|
|
10642
10809
|
return {
|
|
10643
10810
|
phaseState,
|
|
10644
10811
|
architectInvalidators,
|
|
10645
|
-
|
|
10812
|
+
inputStatus: {
|
|
10646
10813
|
structureHash,
|
|
10647
10814
|
steerChanged,
|
|
10648
10815
|
architectChanged,
|
|
10816
|
+
criticChanged,
|
|
10649
10817
|
crossRefsDeclChanged,
|
|
10650
|
-
scopeMtimeMax,
|
|
10651
10818
|
crossRefContentChanged,
|
|
10652
10819
|
},
|
|
10653
|
-
structureHash,
|
|
10654
|
-
steerChanged,
|
|
10655
10820
|
};
|
|
10656
10821
|
}
|
|
10657
10822
|
|
|
@@ -10831,6 +10996,15 @@ function toMetaError(step, err, code = 'FAILED') {
|
|
|
10831
10996
|
*
|
|
10832
10997
|
* @module orchestrator/parseOutput
|
|
10833
10998
|
*/
|
|
10999
|
+
/** Sentinel appended by synthesis workers to skip the announce turn. */
|
|
11000
|
+
const ANNOUNCE_SKIP = 'ANNOUNCE_SKIP';
|
|
11001
|
+
/** Strip a trailing ANNOUNCE_SKIP sentinel from raw output. */
|
|
11002
|
+
function stripSentinel(raw) {
|
|
11003
|
+
const trimmed = raw.trim();
|
|
11004
|
+
return trimmed.endsWith(ANNOUNCE_SKIP)
|
|
11005
|
+
? trimmed.slice(0, -ANNOUNCE_SKIP.length).trim()
|
|
11006
|
+
: trimmed;
|
|
11007
|
+
}
|
|
10834
11008
|
/**
|
|
10835
11009
|
* Parse architect output. The architect returns a task brief as text.
|
|
10836
11010
|
*
|
|
@@ -10838,7 +11012,7 @@ function toMetaError(step, err, code = 'FAILED') {
|
|
|
10838
11012
|
* @returns The task brief string.
|
|
10839
11013
|
*/
|
|
10840
11014
|
function parseArchitectOutput(output) {
|
|
10841
|
-
return output
|
|
11015
|
+
return stripSentinel(output);
|
|
10842
11016
|
}
|
|
10843
11017
|
/**
|
|
10844
11018
|
* Parse builder output. The builder returns JSON with _content and optional fields.
|
|
@@ -10849,7 +11023,7 @@ function parseArchitectOutput(output) {
|
|
|
10849
11023
|
* @returns Parsed builder output with content and structured fields.
|
|
10850
11024
|
*/
|
|
10851
11025
|
function parseBuilderOutput(output) {
|
|
10852
|
-
const trimmed = output
|
|
11026
|
+
const trimmed = stripSentinel(output);
|
|
10853
11027
|
// Strategy 1: Try to parse the entire output as JSON directly
|
|
10854
11028
|
const direct = tryParseJson(trimmed);
|
|
10855
11029
|
if (direct)
|
|
@@ -10916,7 +11090,7 @@ function tryParseJson(str) {
|
|
|
10916
11090
|
* @returns The feedback string.
|
|
10917
11091
|
*/
|
|
10918
11092
|
function parseCriticOutput(output) {
|
|
10919
|
-
return output
|
|
11093
|
+
return stripSentinel(output);
|
|
10920
11094
|
}
|
|
10921
11095
|
|
|
10922
11096
|
/**
|
|
@@ -10927,6 +11101,12 @@ function parseCriticOutput(output) {
|
|
|
10927
11101
|
*
|
|
10928
11102
|
* @module orchestrator/runPhase
|
|
10929
11103
|
*/
|
|
11104
|
+
/** Compute SHA-256 hash of ancestor _builder text for observability tracking. */
|
|
11105
|
+
function hashAncestorBuilder(ancestorBuilder) {
|
|
11106
|
+
return ancestorBuilder
|
|
11107
|
+
? createHash('sha256').update(ancestorBuilder).digest('hex')
|
|
11108
|
+
: undefined;
|
|
11109
|
+
}
|
|
10930
11110
|
/** Write updated meta with phase state via lock staging. */
|
|
10931
11111
|
async function persistPhaseState(base, phaseState, updates) {
|
|
10932
11112
|
const lockPath = join(base.metaPath, '.lock');
|
|
@@ -10970,6 +11150,12 @@ async function handlePhaseFailure(phase, err, executor, ps, base, additionalUpda
|
|
|
10970
11150
|
// ── Architect executor ─────────────────────────────────────────────────
|
|
10971
11151
|
async function runArchitect(node, currentMeta, phaseState, config, executor, watcher, structureHash, onProgress, logger) {
|
|
10972
11152
|
let ps = phaseRunning(phaseState, 'architect');
|
|
11153
|
+
const base = {
|
|
11154
|
+
metaPath: node.metaPath,
|
|
11155
|
+
current: currentMeta,
|
|
11156
|
+
config,
|
|
11157
|
+
structureHash,
|
|
11158
|
+
};
|
|
10973
11159
|
const ctx = await buildContextPackage(node, currentMeta, watcher, logger);
|
|
10974
11160
|
try {
|
|
10975
11161
|
await onProgress?.({
|
|
@@ -10981,21 +11167,25 @@ async function runArchitect(node, currentMeta, phaseState, config, executor, wat
|
|
|
10981
11167
|
const architectTask = buildArchitectTask(ctx, currentMeta, config);
|
|
10982
11168
|
const result = await executor.spawn(architectTask, {
|
|
10983
11169
|
thinking: config.thinking,
|
|
10984
|
-
timeout: config.architectTimeout,
|
|
11170
|
+
timeout: currentMeta._architectTimeout ?? config.architectTimeout,
|
|
10985
11171
|
label: 'meta-architect',
|
|
10986
11172
|
});
|
|
10987
11173
|
const builderBrief = parseArchitectOutput(result.output);
|
|
10988
11174
|
const architectTokens = result.tokens;
|
|
10989
11175
|
// Architect success: architect → fresh, _synthesisCount → 0
|
|
10990
11176
|
ps = architectSuccess(ps);
|
|
10991
|
-
const
|
|
11177
|
+
const architectUpdates = {
|
|
10992
11178
|
_builder: builderBrief,
|
|
10993
|
-
_architect:
|
|
11179
|
+
_architect: config.defaultArchitect ?? DEFAULT_ARCHITECT_PROMPT,
|
|
10994
11180
|
_synthesisCount: 0,
|
|
10995
11181
|
_architectTokens: architectTokens,
|
|
10996
11182
|
_generatedAt: new Date().toISOString(),
|
|
10997
11183
|
_error: undefined,
|
|
10998
|
-
}
|
|
11184
|
+
};
|
|
11185
|
+
const ancestorHash = hashAncestorBuilder(ctx.ancestorBuilder);
|
|
11186
|
+
if (ancestorHash)
|
|
11187
|
+
architectUpdates._ancestorBuilderHash = ancestorHash;
|
|
11188
|
+
const updatedMeta = await persistPhaseState(base, ps, architectUpdates);
|
|
10999
11189
|
await onProgress?.({
|
|
11000
11190
|
type: 'phase_complete',
|
|
11001
11191
|
path: node.ownerPath,
|
|
@@ -11006,16 +11196,18 @@ async function runArchitect(node, currentMeta, phaseState, config, executor, wat
|
|
|
11006
11196
|
return { executed: true, phaseState: ps, updatedMeta };
|
|
11007
11197
|
}
|
|
11008
11198
|
catch (err) {
|
|
11009
|
-
return handlePhaseFailure('architect', err, executor, ps,
|
|
11010
|
-
metaPath: node.metaPath,
|
|
11011
|
-
current: currentMeta,
|
|
11012
|
-
structureHash,
|
|
11013
|
-
});
|
|
11199
|
+
return handlePhaseFailure('architect', err, executor, ps, base);
|
|
11014
11200
|
}
|
|
11015
11201
|
}
|
|
11016
11202
|
// ── Builder executor ───────────────────────────────────────────────────
|
|
11017
11203
|
async function runBuilder(node, currentMeta, phaseState, config, executor, watcher, structureHash, onProgress, logger) {
|
|
11018
11204
|
let ps = phaseRunning(phaseState, 'builder');
|
|
11205
|
+
const base = {
|
|
11206
|
+
metaPath: node.metaPath,
|
|
11207
|
+
current: currentMeta,
|
|
11208
|
+
config,
|
|
11209
|
+
structureHash,
|
|
11210
|
+
};
|
|
11019
11211
|
const ctx = await buildContextPackage(node, currentMeta, watcher, logger);
|
|
11020
11212
|
try {
|
|
11021
11213
|
await onProgress?.({
|
|
@@ -11027,21 +11219,25 @@ async function runBuilder(node, currentMeta, phaseState, config, executor, watch
|
|
|
11027
11219
|
const builderTask = buildBuilderTask(ctx, currentMeta, config);
|
|
11028
11220
|
const result = await executor.spawn(builderTask, {
|
|
11029
11221
|
thinking: config.thinking,
|
|
11030
|
-
timeout: config.builderTimeout,
|
|
11222
|
+
timeout: currentMeta._builderTimeout ?? config.builderTimeout,
|
|
11031
11223
|
label: 'meta-builder',
|
|
11032
11224
|
});
|
|
11033
11225
|
const builderOutput = parseBuilderOutput(result.output);
|
|
11034
11226
|
const builderTokens = result.tokens;
|
|
11035
11227
|
// Builder success: builder → fresh, critic → pending
|
|
11036
11228
|
ps = builderSuccess(ps);
|
|
11037
|
-
const
|
|
11229
|
+
const builderUpdates = {
|
|
11038
11230
|
_content: builderOutput.content,
|
|
11039
11231
|
_state: builderOutput.state,
|
|
11040
11232
|
_builderTokens: builderTokens,
|
|
11041
11233
|
_generatedAt: new Date().toISOString(),
|
|
11042
11234
|
_error: undefined,
|
|
11043
11235
|
...builderOutput.fields,
|
|
11044
|
-
}
|
|
11236
|
+
};
|
|
11237
|
+
const ancestorHash = hashAncestorBuilder(ctx.ancestorBuilder);
|
|
11238
|
+
if (ancestorHash)
|
|
11239
|
+
builderUpdates._ancestorBuilderHash = ancestorHash;
|
|
11240
|
+
const updatedMeta = await persistPhaseState(base, ps, builderUpdates);
|
|
11045
11241
|
await onProgress?.({
|
|
11046
11242
|
type: 'phase_complete',
|
|
11047
11243
|
path: node.ownerPath,
|
|
@@ -11067,16 +11263,18 @@ async function runBuilder(node, currentMeta, phaseState, config, executor, watch
|
|
|
11067
11263
|
// Could not read partial output — no state recovery
|
|
11068
11264
|
}
|
|
11069
11265
|
}
|
|
11070
|
-
return handlePhaseFailure('builder', err, executor, ps,
|
|
11071
|
-
metaPath: node.metaPath,
|
|
11072
|
-
current: currentMeta,
|
|
11073
|
-
structureHash,
|
|
11074
|
-
}, partialState);
|
|
11266
|
+
return handlePhaseFailure('builder', err, executor, ps, base, partialState);
|
|
11075
11267
|
}
|
|
11076
11268
|
}
|
|
11077
11269
|
// ── Critic executor ────────────────────────────────────────────────────
|
|
11078
11270
|
async function runCritic(node, currentMeta, phaseState, config, executor, watcher, structureHash, onProgress, logger) {
|
|
11079
11271
|
let ps = phaseRunning(phaseState, 'critic');
|
|
11272
|
+
const base = {
|
|
11273
|
+
metaPath: node.metaPath,
|
|
11274
|
+
current: currentMeta,
|
|
11275
|
+
config,
|
|
11276
|
+
structureHash,
|
|
11277
|
+
};
|
|
11080
11278
|
const ctx = await buildContextPackage(node, currentMeta, watcher, logger);
|
|
11081
11279
|
// Build critic task using current meta's _content
|
|
11082
11280
|
const metaForCritic = { ...currentMeta };
|
|
@@ -11090,7 +11288,7 @@ async function runCritic(node, currentMeta, phaseState, config, executor, watche
|
|
|
11090
11288
|
const criticTask = buildCriticTask(ctx, metaForCritic, config);
|
|
11091
11289
|
const result = await executor.spawn(criticTask, {
|
|
11092
11290
|
thinking: config.thinking,
|
|
11093
|
-
timeout: config.criticTimeout,
|
|
11291
|
+
timeout: currentMeta._criticTimeout ?? config.criticTimeout,
|
|
11094
11292
|
label: 'meta-critic',
|
|
11095
11293
|
});
|
|
11096
11294
|
const feedback = parseCriticOutput(result.output);
|
|
@@ -11100,6 +11298,7 @@ async function runCritic(node, currentMeta, phaseState, config, executor, watche
|
|
|
11100
11298
|
const cycleComplete = isFullyFresh(ps);
|
|
11101
11299
|
const updates = {
|
|
11102
11300
|
_feedback: feedback,
|
|
11301
|
+
_critic: config.defaultCritic ?? DEFAULT_CRITIC_PROMPT,
|
|
11103
11302
|
_criticTokens: criticTokens,
|
|
11104
11303
|
_error: undefined,
|
|
11105
11304
|
};
|
|
@@ -11108,7 +11307,7 @@ async function runCritic(node, currentMeta, phaseState, config, executor, watche
|
|
|
11108
11307
|
if (cycleComplete) {
|
|
11109
11308
|
updates._synthesisCount = (currentMeta._synthesisCount ?? 0) + 1;
|
|
11110
11309
|
}
|
|
11111
|
-
const updatedMeta = await persistPhaseState(
|
|
11310
|
+
const updatedMeta = await persistPhaseState(base, ps, updates);
|
|
11112
11311
|
// Archive on full-cycle only
|
|
11113
11312
|
if (cycleComplete) {
|
|
11114
11313
|
await createSnapshot(node.metaPath, updatedMeta);
|
|
@@ -11129,11 +11328,7 @@ async function runCritic(node, currentMeta, phaseState, config, executor, watche
|
|
|
11129
11328
|
};
|
|
11130
11329
|
}
|
|
11131
11330
|
catch (err) {
|
|
11132
|
-
return handlePhaseFailure('critic', err, executor, ps,
|
|
11133
|
-
metaPath: node.metaPath,
|
|
11134
|
-
current: currentMeta,
|
|
11135
|
-
structureHash,
|
|
11136
|
-
});
|
|
11331
|
+
return handlePhaseFailure('critic', err, executor, ps, base);
|
|
11137
11332
|
}
|
|
11138
11333
|
}
|
|
11139
11334
|
|
|
@@ -11710,12 +11905,6 @@ class WatcherHealthCheck {
|
|
|
11710
11905
|
return;
|
|
11711
11906
|
}
|
|
11712
11907
|
const data = (await res.json());
|
|
11713
|
-
// If rules were never successfully registered (startup failure),
|
|
11714
|
-
// attempt registration now that the watcher is reachable.
|
|
11715
|
-
if (!this.registrar.isRegistered) {
|
|
11716
|
-
this.logger.info('Rules not registered — attempting registration');
|
|
11717
|
-
await this.registrar.register();
|
|
11718
|
-
}
|
|
11719
11908
|
await this.registrar.checkAndReregister(data.uptime);
|
|
11720
11909
|
}
|
|
11721
11910
|
catch (err) {
|
|
@@ -11868,36 +12057,39 @@ class RuleRegistrar {
|
|
|
11868
12057
|
logger;
|
|
11869
12058
|
watcherClient;
|
|
11870
12059
|
lastWatcherUptime = null;
|
|
11871
|
-
|
|
12060
|
+
registering = false;
|
|
11872
12061
|
constructor(config, logger, watcher) {
|
|
11873
12062
|
this.config = config;
|
|
11874
12063
|
this.logger = logger;
|
|
11875
12064
|
this.watcherClient = watcher;
|
|
11876
12065
|
}
|
|
11877
|
-
/** Whether rules have been successfully registered. */
|
|
11878
|
-
get isRegistered() {
|
|
11879
|
-
return this.registered;
|
|
11880
|
-
}
|
|
11881
12066
|
/**
|
|
11882
12067
|
* Register rules with watcher. Retries with exponential backoff.
|
|
11883
12068
|
* Non-blocking — logs errors but never throws.
|
|
11884
12069
|
*/
|
|
11885
12070
|
async register() {
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
|
|
12071
|
+
if (this.registering)
|
|
12072
|
+
return;
|
|
12073
|
+
this.registering = true;
|
|
12074
|
+
try {
|
|
12075
|
+
const rules = buildMetaRules(this.config);
|
|
12076
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
12077
|
+
try {
|
|
12078
|
+
await this.watcherClient.registerRules(SOURCE, rules);
|
|
12079
|
+
this.logger.info('Virtual rules registered with watcher');
|
|
12080
|
+
return;
|
|
12081
|
+
}
|
|
12082
|
+
catch (err) {
|
|
12083
|
+
const delayMs = RETRY_BASE_MS * Math.pow(2, attempt);
|
|
12084
|
+
this.logger.warn({ attempt: attempt + 1, delayMs, err }, 'Rule registration failed, retrying');
|
|
12085
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
12086
|
+
}
|
|
11898
12087
|
}
|
|
12088
|
+
this.logger.error('Rule registration failed after max retries — service degraded');
|
|
12089
|
+
}
|
|
12090
|
+
finally {
|
|
12091
|
+
this.registering = false;
|
|
11899
12092
|
}
|
|
11900
|
-
this.logger.error('Rule registration failed after max retries — service degraded');
|
|
11901
12093
|
}
|
|
11902
12094
|
/**
|
|
11903
12095
|
* Check watcher uptime and re-register if it decreased (restart detected).
|
|
@@ -11908,7 +12100,6 @@ class RuleRegistrar {
|
|
|
11908
12100
|
if (this.lastWatcherUptime !== null &&
|
|
11909
12101
|
currentUptime < this.lastWatcherUptime) {
|
|
11910
12102
|
this.logger.info({ previous: this.lastWatcherUptime, current: currentUptime }, 'Watcher restart detected — re-registering rules');
|
|
11911
|
-
this.registered = false;
|
|
11912
12103
|
await this.register();
|
|
11913
12104
|
}
|
|
11914
12105
|
this.lastWatcherUptime = currentUptime;
|
|
@@ -11978,6 +12169,12 @@ async function createMeta(ownerPath, options) {
|
|
|
11978
12169
|
metaJson._crossRefs = options.crossRefs;
|
|
11979
12170
|
if (options?.steer !== undefined)
|
|
11980
12171
|
metaJson._steer = options.steer;
|
|
12172
|
+
if (options?.architectTimeout !== undefined)
|
|
12173
|
+
metaJson._architectTimeout = options.architectTimeout;
|
|
12174
|
+
if (options?.builderTimeout !== undefined)
|
|
12175
|
+
metaJson._builderTimeout = options.builderTimeout;
|
|
12176
|
+
if (options?.criticTimeout !== undefined)
|
|
12177
|
+
metaJson._criticTimeout = options.criticTimeout;
|
|
11981
12178
|
const metaJsonPath = join(metaDir, 'meta.json');
|
|
11982
12179
|
await writeFile(metaJsonPath, JSON.stringify(metaJson, null, 2) + '\n');
|
|
11983
12180
|
return { metaDir, _id };
|
|
@@ -12006,15 +12203,24 @@ function metaExists(ownerPath) {
|
|
|
12006
12203
|
/**
|
|
12007
12204
|
* Extract parent directory paths from watcher walk results.
|
|
12008
12205
|
*
|
|
12009
|
-
* Walk returns file paths; we need the unique set of
|
|
12010
|
-
*
|
|
12206
|
+
* Walk returns file paths; we need the unique set of parent directories that
|
|
12207
|
+
* could be owners. When {@link parentDepth} is specified, walk up that many
|
|
12208
|
+
* additional levels from each file's immediate parent. The walk is clamped at
|
|
12209
|
+
* the filesystem root to prevent escaping the watched scope.
|
|
12011
12210
|
*/
|
|
12012
|
-
function extractDirectories(filePaths, logger) {
|
|
12211
|
+
function extractDirectories(filePaths, parentDepth = 0, logger) {
|
|
12013
12212
|
const dirs = new Set();
|
|
12014
12213
|
for (const fp of filePaths) {
|
|
12015
12214
|
// Normalize backslash paths (Windows) to forward slashes before posix.dirname
|
|
12016
12215
|
const normalized = normalizePath(fp);
|
|
12017
|
-
|
|
12216
|
+
let dir = posix.dirname(normalized);
|
|
12217
|
+
// Walk up parentDepth additional levels, clamping at filesystem root
|
|
12218
|
+
for (let i = 0; i < parentDepth; i++) {
|
|
12219
|
+
const parent = posix.dirname(dir);
|
|
12220
|
+
if (parent === dir)
|
|
12221
|
+
break; // reached root
|
|
12222
|
+
dir = parent;
|
|
12223
|
+
}
|
|
12018
12224
|
if (dir !== '.' && dir !== '/') {
|
|
12019
12225
|
dirs.add(dir);
|
|
12020
12226
|
}
|
|
@@ -12039,11 +12245,14 @@ async function autoSeedPass(rules, watcher, logger) {
|
|
|
12039
12245
|
const candidates = new Map();
|
|
12040
12246
|
for (const rule of rules) {
|
|
12041
12247
|
const files = await watcher.walk([rule.match]);
|
|
12042
|
-
const dirs = extractDirectories(files, logger);
|
|
12248
|
+
const dirs = extractDirectories(files, rule.parentDepth, logger);
|
|
12043
12249
|
for (const dir of dirs) {
|
|
12044
12250
|
candidates.set(dir, {
|
|
12045
12251
|
steer: rule.steer,
|
|
12046
12252
|
crossRefs: rule.crossRefs,
|
|
12253
|
+
architectTimeout: rule.architectTimeout,
|
|
12254
|
+
builderTimeout: rule.builderTimeout,
|
|
12255
|
+
criticTimeout: rule.criticTimeout,
|
|
12047
12256
|
});
|
|
12048
12257
|
}
|
|
12049
12258
|
}
|
|
@@ -12058,10 +12267,7 @@ async function autoSeedPass(rules, watcher, logger) {
|
|
|
12058
12267
|
const seededPaths = [];
|
|
12059
12268
|
for (const candidate of toSeed) {
|
|
12060
12269
|
try {
|
|
12061
|
-
await createMeta(candidate.path,
|
|
12062
|
-
steer: candidate.steer,
|
|
12063
|
-
crossRefs: candidate.crossRefs,
|
|
12064
|
-
});
|
|
12270
|
+
await createMeta(candidate.path, candidate);
|
|
12065
12271
|
seededPaths.push(candidate.path);
|
|
12066
12272
|
logger?.info({ path: candidate.path }, 'auto-seeded meta');
|
|
12067
12273
|
}
|
|
@@ -12263,7 +12469,7 @@ class Scheduler {
|
|
|
12263
12469
|
metaPath: t2.node.metaPath,
|
|
12264
12470
|
current: currentMeta,
|
|
12265
12471
|
config: this.config,
|
|
12266
|
-
structureHash: result.structureHash,
|
|
12472
|
+
structureHash: result.inputStatus.structureHash,
|
|
12267
12473
|
}, result.phaseState, {});
|
|
12268
12474
|
this.cache.invalidate();
|
|
12269
12475
|
return {
|
|
@@ -12277,8 +12483,10 @@ class Scheduler {
|
|
|
12277
12483
|
metaPath: t2.node.metaPath,
|
|
12278
12484
|
current: currentMeta,
|
|
12279
12485
|
config: this.config,
|
|
12280
|
-
structureHash: result.structureHash,
|
|
12281
|
-
}, result.phaseState, {
|
|
12486
|
+
structureHash: result.inputStatus.structureHash,
|
|
12487
|
+
}, result.phaseState, {
|
|
12488
|
+
_generatedAt: new Date().toISOString(),
|
|
12489
|
+
});
|
|
12282
12490
|
dirty = true;
|
|
12283
12491
|
}
|
|
12284
12492
|
finally {
|
|
@@ -12308,7 +12516,7 @@ function sanitizeConfig(config) {
|
|
|
12308
12516
|
}
|
|
12309
12517
|
function registerConfigRoute(app, deps) {
|
|
12310
12518
|
const configHandler = createConfigQueryHandler(() => sanitizeConfig(deps.config));
|
|
12311
|
-
app.get('
|
|
12519
|
+
app.get(getEndpoint('config').path, async (request, reply) => {
|
|
12312
12520
|
const { path } = request.query;
|
|
12313
12521
|
const result = await configHandler({ path });
|
|
12314
12522
|
return reply.status(result.status).send(result.body);
|
|
@@ -12327,7 +12535,7 @@ function registerConfigRoute(app, deps) {
|
|
|
12327
12535
|
*/
|
|
12328
12536
|
/** Register the POST /config/apply route. */
|
|
12329
12537
|
function registerConfigApplyRoute(app, configPath) {
|
|
12330
|
-
app.post('
|
|
12538
|
+
app.post(getEndpoint('configApply').path, async (request, reply) => {
|
|
12331
12539
|
if (!configPath) {
|
|
12332
12540
|
return reply
|
|
12333
12541
|
.status(500)
|
|
@@ -12416,42 +12624,32 @@ function registerConfigApplyRoute(app, configPath) {
|
|
|
12416
12624
|
*
|
|
12417
12625
|
* @module routes/metas
|
|
12418
12626
|
*/
|
|
12627
|
+
/** Reusable Zod schema for boolean query string parameters ('true'/'false'). */
|
|
12628
|
+
const boolQueryParam = z.enum(['true', 'false']).transform((v) => v === 'true');
|
|
12419
12629
|
const metasQuerySchema = z.object({
|
|
12420
12630
|
pathPrefix: z.string().optional(),
|
|
12421
|
-
hasError:
|
|
12422
|
-
.enum(['true', 'false'])
|
|
12423
|
-
.transform((v) => v === 'true')
|
|
12424
|
-
.optional(),
|
|
12631
|
+
hasError: boolQueryParam.optional(),
|
|
12425
12632
|
staleHours: z
|
|
12426
12633
|
.string()
|
|
12427
12634
|
.transform(Number)
|
|
12428
12635
|
.pipe(z.number().positive())
|
|
12429
12636
|
.optional(),
|
|
12430
|
-
neverSynthesized:
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
.optional(),
|
|
12434
|
-
locked: z
|
|
12435
|
-
.enum(['true', 'false'])
|
|
12436
|
-
.transform((v) => v === 'true')
|
|
12437
|
-
.optional(),
|
|
12438
|
-
disabled: z
|
|
12439
|
-
.enum(['true', 'false'])
|
|
12440
|
-
.transform((v) => v === 'true')
|
|
12441
|
-
.optional(),
|
|
12637
|
+
neverSynthesized: boolQueryParam.optional(),
|
|
12638
|
+
locked: boolQueryParam.optional(),
|
|
12639
|
+
disabled: boolQueryParam.optional(),
|
|
12442
12640
|
fields: z.string().optional(),
|
|
12443
12641
|
});
|
|
12444
12642
|
const metaDetailQuerySchema = z.object({
|
|
12445
12643
|
fields: z.string().optional(),
|
|
12446
12644
|
includeArchive: z
|
|
12447
12645
|
.union([
|
|
12448
|
-
|
|
12646
|
+
boolQueryParam,
|
|
12449
12647
|
z.string().transform(Number).pipe(z.number().int().nonnegative()),
|
|
12450
12648
|
])
|
|
12451
12649
|
.optional(),
|
|
12452
12650
|
});
|
|
12453
12651
|
function registerMetasRoutes(app, deps) {
|
|
12454
|
-
app.get('
|
|
12652
|
+
app.get(getEndpoint('listMetas').path, async (request) => {
|
|
12455
12653
|
const query = metasQuerySchema.parse(request.query);
|
|
12456
12654
|
const { config, watcher } = deps;
|
|
12457
12655
|
const result = await listMetas(config, watcher);
|
|
@@ -12523,7 +12721,7 @@ function registerMetasRoutes(app, deps) {
|
|
|
12523
12721
|
});
|
|
12524
12722
|
return { summary, metas };
|
|
12525
12723
|
});
|
|
12526
|
-
app.get('
|
|
12724
|
+
app.get(getEndpoint('metaDetail').path, async (request, reply) => {
|
|
12527
12725
|
const query = metaDetailQuerySchema.parse(request.query);
|
|
12528
12726
|
const { config, watcher } = deps;
|
|
12529
12727
|
const targetPath = normalizePath(decodeURIComponent(request.params.path));
|
|
@@ -12617,7 +12815,7 @@ function registerMetasRoutes(app, deps) {
|
|
|
12617
12815
|
/**
|
|
12618
12816
|
* PATCH /metas/:path — update user-settable reserved properties on a meta.
|
|
12619
12817
|
*
|
|
12620
|
-
* Supported fields: _steer, _emphasis, _depth, _crossRefs, _disabled.
|
|
12818
|
+
* Supported fields: _steer, _emphasis, _depth, _crossRefs, _disabled, _architectTimeout, _builderTimeout, _criticTimeout.
|
|
12621
12819
|
* Set a field to null to remove it. Unknown keys are rejected.
|
|
12622
12820
|
*
|
|
12623
12821
|
* @module routes/metasUpdate
|
|
@@ -12629,10 +12827,13 @@ const updateBodySchema = z
|
|
|
12629
12827
|
_depth: z.union([z.number(), z.null()]).optional(),
|
|
12630
12828
|
_crossRefs: z.union([z.array(z.string()), z.null()]).optional(),
|
|
12631
12829
|
_disabled: z.union([z.boolean(), z.null()]).optional(),
|
|
12830
|
+
_architectTimeout: z.union([z.number().int().min(30), z.null()]).optional(),
|
|
12831
|
+
_builderTimeout: z.union([z.number().int().min(30), z.null()]).optional(),
|
|
12832
|
+
_criticTimeout: z.union([z.number().int().min(30), z.null()]).optional(),
|
|
12632
12833
|
})
|
|
12633
12834
|
.strict();
|
|
12634
12835
|
function registerMetasUpdateRoute(app, deps) {
|
|
12635
|
-
app.patch('
|
|
12836
|
+
app.patch(getEndpoint('updateMeta').path, async (request, reply) => {
|
|
12636
12837
|
const parseResult = updateBodySchema.safeParse(request.body);
|
|
12637
12838
|
if (!parseResult.success) {
|
|
12638
12839
|
return reply.status(400).send({
|
|
@@ -12654,13 +12855,7 @@ function registerMetasUpdateRoute(app, deps) {
|
|
|
12654
12855
|
});
|
|
12655
12856
|
}
|
|
12656
12857
|
const metaJsonPath = join(metaDir, 'meta.json');
|
|
12657
|
-
const KEYS =
|
|
12658
|
-
'_steer',
|
|
12659
|
-
'_emphasis',
|
|
12660
|
-
'_depth',
|
|
12661
|
-
'_crossRefs',
|
|
12662
|
-
'_disabled',
|
|
12663
|
-
];
|
|
12858
|
+
const KEYS = Object.keys(updateBodySchema.shape);
|
|
12664
12859
|
const toDelete = new Set();
|
|
12665
12860
|
const toSet = {};
|
|
12666
12861
|
for (const key of KEYS) {
|
|
@@ -12698,7 +12893,7 @@ function registerMetasUpdateRoute(app, deps) {
|
|
|
12698
12893
|
* @module routes/preview
|
|
12699
12894
|
*/
|
|
12700
12895
|
function registerPreviewRoute(app, deps) {
|
|
12701
|
-
app.get('
|
|
12896
|
+
app.get(getEndpoint('preview').path, async (request, reply) => {
|
|
12702
12897
|
const { config, watcher, cache } = deps;
|
|
12703
12898
|
const query = request.query;
|
|
12704
12899
|
let result;
|
|
@@ -12736,12 +12931,8 @@ function registerPreviewRoute(app, deps) {
|
|
|
12736
12931
|
const { scopeFiles } = await getScopeFiles(targetNode, watcher);
|
|
12737
12932
|
// Compute invalidation inputs (DRY: reuse phaseState/invalidate logic)
|
|
12738
12933
|
const invalidation = await computeInvalidation(meta, scopeFiles, config, targetNode);
|
|
12739
|
-
const { architectInvalidators,
|
|
12740
|
-
const
|
|
12741
|
-
const structureChanged = structureHash !== meta._structureHash;
|
|
12742
|
-
const { steerChanged } = invalidation;
|
|
12743
|
-
const { architectChanged, crossRefsDeclChanged } = stalenessInputs;
|
|
12744
|
-
const architectTriggered = isArchitectTriggered(meta, structureChanged, steerChanged, config.architectEvery);
|
|
12934
|
+
const { architectInvalidators, inputStatus, phaseState } = invalidation;
|
|
12935
|
+
const architectTriggered = architectInvalidators.length > 0;
|
|
12745
12936
|
// Delta files
|
|
12746
12937
|
const deltaFiles = getDeltaFiles(meta._generatedAt, scopeFiles);
|
|
12747
12938
|
// EMA token estimates
|
|
@@ -12755,14 +12946,6 @@ function registerPreviewRoute(app, deps) {
|
|
|
12755
12946
|
? Math.round((Date.now() - new Date(meta._generatedAt).getTime()) / 1000)
|
|
12756
12947
|
: null;
|
|
12757
12948
|
const stalenessScore = computeStalenessScore(stalenessSeconds, meta._depth ?? 0, meta._emphasis ?? 1, config.depthWeight);
|
|
12758
|
-
// Phase state
|
|
12759
|
-
const phaseState = derivePhaseState(meta, {
|
|
12760
|
-
structureChanged,
|
|
12761
|
-
steerChanged,
|
|
12762
|
-
architectChanged,
|
|
12763
|
-
crossRefsChanged: crossRefsDeclChanged,
|
|
12764
|
-
architectEvery: config.architectEvery,
|
|
12765
|
-
});
|
|
12766
12949
|
const owedPhase = getOwedPhase(phaseState);
|
|
12767
12950
|
const priorityBand = getPriorityBand(phaseState);
|
|
12768
12951
|
return {
|
|
@@ -12773,10 +12956,17 @@ function registerPreviewRoute(app, deps) {
|
|
|
12773
12956
|
},
|
|
12774
12957
|
architectWillRun: architectTriggered,
|
|
12775
12958
|
architectReason: [
|
|
12776
|
-
...(
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
...((
|
|
12959
|
+
...(architectInvalidators.includes('firstRun')
|
|
12960
|
+
? ['no cached builder (first run)']
|
|
12961
|
+
: []),
|
|
12962
|
+
...(architectInvalidators.includes('structureHash')
|
|
12963
|
+
? ['structure changed']
|
|
12964
|
+
: []),
|
|
12965
|
+
...(architectInvalidators.includes('steer') ? ['steer changed'] : []),
|
|
12966
|
+
...(architectInvalidators.includes('_crossRefs')
|
|
12967
|
+
? ['cross-refs changed']
|
|
12968
|
+
: []),
|
|
12969
|
+
...(architectInvalidators.includes('architectEvery')
|
|
12780
12970
|
? ['periodic refresh']
|
|
12781
12971
|
: []),
|
|
12782
12972
|
].join(', ') || 'not triggered',
|
|
@@ -12793,7 +12983,7 @@ function registerPreviewRoute(app, deps) {
|
|
|
12793
12983
|
owedPhase,
|
|
12794
12984
|
priorityBand,
|
|
12795
12985
|
phaseState,
|
|
12796
|
-
|
|
12986
|
+
inputStatus,
|
|
12797
12987
|
architectInvalidators,
|
|
12798
12988
|
};
|
|
12799
12989
|
});
|
|
@@ -12811,7 +13001,7 @@ function registerPreviewRoute(app, deps) {
|
|
|
12811
13001
|
/** Register queue management routes. */
|
|
12812
13002
|
function registerQueueRoutes(app, deps) {
|
|
12813
13003
|
const { queue } = deps;
|
|
12814
|
-
app.get('
|
|
13004
|
+
app.get(getEndpoint('queue').path, async () => {
|
|
12815
13005
|
const currentPhase = queue.currentPhase;
|
|
12816
13006
|
const overrides = queue.overrides;
|
|
12817
13007
|
// Compute owedPhase for each override entry by reading meta state
|
|
@@ -12887,11 +13077,11 @@ function registerQueueRoutes(app, deps) {
|
|
|
12887
13077
|
state: queue.getState(),
|
|
12888
13078
|
};
|
|
12889
13079
|
});
|
|
12890
|
-
app.post('
|
|
13080
|
+
app.post(getEndpoint('queueClear').path, () => {
|
|
12891
13081
|
const removed = queue.clearOverrides();
|
|
12892
13082
|
return { cleared: removed };
|
|
12893
13083
|
});
|
|
12894
|
-
app.post('
|
|
13084
|
+
app.post(getEndpoint('abort').path, async (_request, reply) => {
|
|
12895
13085
|
// Check 3-layer current first
|
|
12896
13086
|
const currentPhase = queue.currentPhase;
|
|
12897
13087
|
const current = currentPhase ?? queue.current;
|
|
@@ -12953,7 +13143,7 @@ const seedBodySchema = z.object({
|
|
|
12953
13143
|
steer: z.string().optional(),
|
|
12954
13144
|
});
|
|
12955
13145
|
function registerSeedRoute(app, deps) {
|
|
12956
|
-
app.post('
|
|
13146
|
+
app.post(getEndpoint('seed').path, async (request, reply) => {
|
|
12957
13147
|
const body = seedBodySchema.parse(request.body);
|
|
12958
13148
|
if (metaExists(body.path)) {
|
|
12959
13149
|
return reply.status(409).send({
|
|
@@ -13118,7 +13308,7 @@ function registerStatusRoute(app, deps) {
|
|
|
13118
13308
|
dependencies: {
|
|
13119
13309
|
watcher: {
|
|
13120
13310
|
...watcherHealth,
|
|
13121
|
-
rulesRegistered:
|
|
13311
|
+
rulesRegistered: true,
|
|
13122
13312
|
},
|
|
13123
13313
|
gateway: gatewayHealth,
|
|
13124
13314
|
},
|
|
@@ -13127,7 +13317,7 @@ function registerStatusRoute(app, deps) {
|
|
|
13127
13317
|
};
|
|
13128
13318
|
},
|
|
13129
13319
|
});
|
|
13130
|
-
app.get('
|
|
13320
|
+
app.get(getEndpoint('status').path, async (_request, reply) => {
|
|
13131
13321
|
const result = await statusHandler();
|
|
13132
13322
|
return reply.status(result.status).send(result.body);
|
|
13133
13323
|
});
|
|
@@ -13146,24 +13336,41 @@ const synthesizeBodySchema = z.object({
|
|
|
13146
13336
|
});
|
|
13147
13337
|
/** Register the POST /synthesize route. */
|
|
13148
13338
|
function registerSynthesizeRoute(app, deps) {
|
|
13149
|
-
app.post('
|
|
13339
|
+
app.post(getEndpoint('synthesize').path, async (request, reply) => {
|
|
13150
13340
|
const body = synthesizeBodySchema.parse(request.body);
|
|
13151
13341
|
const { config, watcher, queue, cache } = deps;
|
|
13152
13342
|
if (body.path) {
|
|
13153
13343
|
// Path-targeted trigger: create override entry
|
|
13154
13344
|
const targetPath = resolveMetaDir(body.path);
|
|
13155
|
-
// Read meta
|
|
13345
|
+
// Read meta and recompute invalidation against current inputs
|
|
13346
|
+
// (structure hash, steer, cross-refs, prompt snapshots) rather than
|
|
13347
|
+
// trusting the cached _phaseState. Fixes #160.
|
|
13156
13348
|
let owedPhase = null;
|
|
13157
13349
|
let meta;
|
|
13158
13350
|
try {
|
|
13159
13351
|
meta = await readMetaJson(targetPath);
|
|
13160
|
-
const
|
|
13161
|
-
|
|
13352
|
+
const node = await buildMinimalNode(normalizePath(targetPath), watcher);
|
|
13353
|
+
const { scopeFiles } = await getScopeFiles(node, watcher);
|
|
13354
|
+
const invalidation = await computeInvalidation(meta, scopeFiles, config, node);
|
|
13355
|
+
owedPhase = getOwedPhase(invalidation.phaseState);
|
|
13356
|
+
// Persist recomputed phase state + structure hash when stale.
|
|
13357
|
+
// Matches the scheduler's Tier 2 pattern: always persist so the
|
|
13358
|
+
// stored _phaseState reflects reality for subsequent reads.
|
|
13359
|
+
if (owedPhase) {
|
|
13360
|
+
await persistPhaseState({
|
|
13361
|
+
metaPath: targetPath,
|
|
13362
|
+
current: meta,
|
|
13363
|
+
config,
|
|
13364
|
+
structureHash: invalidation.inputStatus.structureHash,
|
|
13365
|
+
}, invalidation.phaseState, {});
|
|
13366
|
+
cache.invalidate();
|
|
13367
|
+
}
|
|
13162
13368
|
}
|
|
13163
13369
|
catch {
|
|
13164
|
-
// Meta unreadable — proceed,
|
|
13370
|
+
// Meta unreadable or watcher unavailable — proceed,
|
|
13371
|
+
// phase will be evaluated at dequeue time
|
|
13165
13372
|
}
|
|
13166
|
-
// Fully fresh meta → skip
|
|
13373
|
+
// Fully fresh meta → skip
|
|
13167
13374
|
if (owedPhase === null && meta && (meta._phaseState || meta._content)) {
|
|
13168
13375
|
return await reply.code(200).send({
|
|
13169
13376
|
status: 'skipped',
|
|
@@ -13221,7 +13428,7 @@ const unlockBodySchema = z.object({
|
|
|
13221
13428
|
path: z.string().min(1),
|
|
13222
13429
|
});
|
|
13223
13430
|
function registerUnlockRoute(app, deps) {
|
|
13224
|
-
app.post('
|
|
13431
|
+
app.post(getEndpoint('unlock').path, (request, reply) => {
|
|
13225
13432
|
const body = unlockBodySchema.parse(request.body);
|
|
13226
13433
|
const metaDir = resolveMetaDir(body.path);
|
|
13227
13434
|
const lockPath = join(metaDir, '.lock');
|
|
@@ -13618,9 +13825,7 @@ async function startService(config, configPath) {
|
|
|
13618
13825
|
routeDeps.registrar = registrar;
|
|
13619
13826
|
void registrar.register().then(() => {
|
|
13620
13827
|
routeDeps.ready = true;
|
|
13621
|
-
|
|
13622
|
-
void verifyRuleApplication(watcher, logger);
|
|
13623
|
-
}
|
|
13828
|
+
void verifyRuleApplication(watcher, logger);
|
|
13624
13829
|
}, () => {
|
|
13625
13830
|
// Registration failed after max retries — mark ready anyway
|
|
13626
13831
|
routeDeps.ready = true;
|