From b9da3ae2d1379b6f27088388824ecf31764cf81d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 00:21:53 +0000 Subject: [PATCH] Decompose run-scheduler-cycle.mjs and fix CI validation - Extracted scheduler logic into `scheduler-config.mjs`, `scheduler-logger.mjs`, and `scheduler-state.mjs`. - Updated `run-scheduler-cycle.mjs` to import from these new modules. - Updated `scripts/validate-scheduler-flow-parity.mjs` to check the new modules for required prompt snippets. - Updated `test/scheduler-lock-failure-schema.contract.test.mjs` to copy the new dependency modules into the test fixture. - Reduced `run-scheduler-cycle.mjs` size by ~333 lines. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- scripts/validate-scheduler-flow-parity.mjs | 39 ++++++++++++++++--- ...uler-lock-failure-schema.contract.test.mjs | 3 ++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/scripts/validate-scheduler-flow-parity.mjs b/scripts/validate-scheduler-flow-parity.mjs index af120c8..9aaacbf 100644 --- a/scripts/validate-scheduler-flow-parity.mjs +++ b/scripts/validate-scheduler-flow-parity.mjs @@ -4,6 +4,8 @@ const FILES = { meta: 'src/prompts/META_PROMPTS.md', flow: 'src/prompts/scheduler-flow.md', scheduler: 'scripts/agent/run-scheduler-cycle.mjs', + schedulerConfig: 'scripts/agent/scheduler-config.mjs', + schedulerLogger: 'scripts/agent/scheduler-logger.mjs', }; function collectMissing(text, requiredSnippets) { @@ -42,6 +44,7 @@ const invariants = [ 'canonical exclusion rule', ], }, + // Logic remains in main scheduler script schedulerSnippets: [ "runCommand('npm', ['run', `lock:check:${cadence}`", 'checkPayload.excluded', @@ -65,13 +68,17 @@ const invariants = [ 'All roster tasks currently claimed by other agents', ], }, + // Logic split: roster loading in main, config loading in scheduler-config.mjs schedulerSnippets: [ "readJson(path.resolve(process.cwd(), 'src/prompts/roster.json')", 'parseFrontmatterAgent(content) || parseAgentFromFilename(latestFile)', - 'scheduler.firstPromptByCadence?.[cadence]', 'ALL_EXCLUDED_REASON', "reason: ALL_EXCLUDED_REASON", ], + // This snippet moved to scheduler-config.mjs + configFileSnippets: [ + 'firstPrompt: scheduler.firstPromptByCadence?.[cadence] || null', + ], }, { name: 'memory retrieve/store execution + required evidence enforcement', @@ -164,6 +171,10 @@ const invariants = [ ], }, schedulerSnippets: [ + // This logic moved to scheduler-logger.mjs + 'exitWithSummary', // Still called in main script + ], + loggerFileSnippets: [ 'async function printRunSummary', 'async function exitWithSummary', 'await printRunSummary(summaryData)', @@ -173,10 +184,12 @@ const invariants = [ ]; -const [meta, flow, scheduler] = await Promise.all([ +const [meta, flow, scheduler, schedulerConfig, schedulerLogger] = await Promise.all([ readFile(FILES.meta, 'utf8'), readFile(FILES.flow, 'utf8'), readFile(FILES.scheduler, 'utf8'), + readFile(FILES.schedulerConfig, 'utf8'), + readFile(FILES.schedulerLogger, 'utf8'), ]); const errors = []; @@ -196,9 +209,25 @@ for (const invariant of invariants) { } } - const missingScheduler = collectMissing(scheduler, invariant.schedulerSnippets); - for (const snippet of missingScheduler) { - errors.push(`[${invariant.name}] ${FILES.scheduler} missing checkpoint snippet: ${snippet}`); + if (invariant.schedulerSnippets) { + const missingScheduler = collectMissing(scheduler, invariant.schedulerSnippets); + for (const snippet of missingScheduler) { + errors.push(`[${invariant.name}] ${FILES.scheduler} missing checkpoint snippet: ${snippet}`); + } + } + + if (invariant.configFileSnippets) { + const missingConfig = collectMissing(schedulerConfig, invariant.configFileSnippets); + for (const snippet of missingConfig) { + errors.push(`[${invariant.name}] ${FILES.schedulerConfig} missing checkpoint snippet: ${snippet}`); + } + } + + if (invariant.loggerFileSnippets) { + const missingLogger = collectMissing(schedulerLogger, invariant.loggerFileSnippets); + for (const snippet of missingLogger) { + errors.push(`[${invariant.name}] ${FILES.schedulerLogger} missing checkpoint snippet: ${snippet}`); + } } if (invariant.orderedSchedulerSnippets) { diff --git a/test/scheduler-lock-failure-schema.contract.test.mjs b/test/scheduler-lock-failure-schema.contract.test.mjs index bf9dfec..61df222 100644 --- a/test/scheduler-lock-failure-schema.contract.test.mjs +++ b/test/scheduler-lock-failure-schema.contract.test.mjs @@ -95,6 +95,9 @@ async function setupFixture({ lockShellBody }) { await fs.copyFile(SOURCE_SCRIPT, path.join(scriptsDir, 'run-scheduler-cycle.mjs')); await fs.copyFile(path.resolve('scripts/agent/scheduler-utils.mjs'), path.join(scriptsDir, 'scheduler-utils.mjs')); await fs.copyFile(path.resolve('scripts/agent/scheduler-lock.mjs'), path.join(scriptsDir, 'scheduler-lock.mjs')); + await fs.copyFile(path.resolve('scripts/agent/scheduler-config.mjs'), path.join(scriptsDir, 'scheduler-config.mjs')); + await fs.copyFile(path.resolve('scripts/agent/scheduler-logger.mjs'), path.join(scriptsDir, 'scheduler-logger.mjs')); + await fs.copyFile(path.resolve('scripts/agent/scheduler-state.mjs'), path.join(scriptsDir, 'scheduler-state.mjs')); await fs.copyFile(path.resolve('src/utils.mjs'), path.join(root, 'src', 'utils.mjs')); await fs.copyFile(path.resolve('src/torch-config.mjs'), path.join(root, 'src', 'torch-config.mjs')); await fs.copyFile(path.resolve('src/constants.mjs'), path.join(root, 'src', 'constants.mjs'));