diff --git a/scripts/agent/run-scheduler-cycle.mjs b/scripts/agent/run-scheduler-cycle.mjs index f68e252..d5e9f47 100644 --- a/scripts/agent/run-scheduler-cycle.mjs +++ b/scripts/agent/run-scheduler-cycle.mjs @@ -1,4 +1,6 @@ #!/usr/bin/env node +// Source of truth: numbered MUST steps 2 and 4-14 in src/prompts/scheduler-flow.md are +// implemented by this script; step 3 (policy-file read) is best-effort and non-fatal. import fs from 'node:fs/promises'; import path from 'node:path'; import { spawn } from 'node:child_process'; @@ -237,6 +239,14 @@ async function main() { const schedulerConfig = await getSchedulerConfig(cadence, { isInteractive }); const logDir = path.resolve(process.cwd(), 'task-logs', cadence); + const agentsPath = path.resolve(process.cwd(), 'AGENTS.md'); + try { + const agentsContent = await fs.readFile(agentsPath, 'utf8'); + process.stdout.write(`${agentsContent}\n`); + } catch { + console.log('No AGENTS.md found; continuing'); + } + while (true) { const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`]); const checkPayload = parseJsonFromOutput(`${checkResult.stdout}\n${checkResult.stderr}`) || {}; diff --git a/src/prompts/scheduler-flow.md b/src/prompts/scheduler-flow.md index 2abc07c..52c5284 100644 --- a/src/prompts/scheduler-flow.md +++ b/src/prompts/scheduler-flow.md @@ -46,9 +46,10 @@ Every agent prompt invoked by the schedulers (daily/weekly) MUST enforce this co 1. Set cadence variables before any command: - `cadence` = `daily` or `weekly` - `log_dir` = `task-logs//` - - `branch_prefix` = `agents//` - `prompt_dir` = `src/prompts//` + Note: branch naming (for example `agents//`) is orchestration-level behavior and is not used by `run-scheduler-cycle.mjs`. + 2. Run preflight to build the exclusion set: ```bash @@ -59,7 +60,7 @@ Every agent prompt invoked by the schedulers (daily/weekly) MUST enforce this co - Use `excluded` from the `npm run lock:check:` JSON output. - If `excluded` is unavailable, fallback to the union of `locked`, `paused`, and `completed` from that same JSON payload. -3. Read policy file(s). This step is conditional: if `AGENTS.md` is missing, continue without failing. +3. Read policy file(s) once before the run loop. This step is conditional: if `AGENTS.md` is missing, continue without failing. ```bash test -f AGENTS.md && cat AGENTS.md || echo "No AGENTS.md found; continuing" diff --git a/test/scheduler-ratchet.test.mjs b/test/scheduler-ratchet.test.mjs index e981f4b..95306a1 100644 --- a/test/scheduler-ratchet.test.mjs +++ b/test/scheduler-ratchet.test.mjs @@ -120,3 +120,29 @@ describe('Scheduler Ratchet Logic (Log Checking)', () => { assert.deepStrictEqual(result.available, ['agentA', 'agentB', 'agentC']); }); }); + + +describe('Scheduler cycle ordering guarantees', () => { + it('keeps lock-check → lock-acquire → handoff → artifact-verify → validation → complete → completed-log order', async () => { + const { readFile } = await import('node:fs/promises'); + const source = await readFile(new URL('../scripts/agent/run-scheduler-cycle.mjs', import.meta.url), 'utf8'); + + const order = [ + "const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`]);", + "const lockResult = await runCommand(", + "if (schedulerConfig.handoffCommand)", + "const artifactCheck = await runCommand('node', [", + "for (const validation of schedulerConfig.validationCommands)", + "const completeResult = await runCommand(", + "await writeLog({ cadence, agent: selectedAgent, status: 'completed'", + ]; + + let previousIndex = -1; + for (const token of order) { + const index = source.indexOf(token); + assert.ok(index >= 0, `Expected token not found: ${token}`); + assert.ok(index > previousIndex, `Token out of order: ${token}`); + previousIndex = index; + } + }); +});