diff --git a/README.md b/README.md index 6561cfc..7d97bf8 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Create a `torch-config.json` file in your project root (or set `TORCH_CONFIG_PAT - `dashboard.defaultCadenceView` (`daily`, `weekly`, or `all`) - `dashboard.defaultStatusView` (`active` or `all`) - `scheduler.firstPromptByCadence.daily` / `.weekly` +- `scheduler.paused.daily` / `.weekly` (array of agent names to pause locally) Weekly alignment maintenance is available via `src/prompts/weekly/repo-fit-agent.md`, which is designed to keep TORCH defaults and docs tuned to the host repository over time. diff --git a/src/docs/TORCH.md b/src/docs/TORCH.md index 772d86f..6c27215 100644 --- a/src/docs/TORCH.md +++ b/src/docs/TORCH.md @@ -51,6 +51,7 @@ Common settings: - `dashboard.defaultCadenceView` — default dashboard view (`daily`, `weekly`, `all`). - `dashboard.defaultStatusView` — default dashboard status filter (`active`, `all`). - `scheduler.firstPromptByCadence.daily` / `.weekly` — first-run scheduler starting agent. +- `scheduler.paused.daily` / `.weekly` — array of agent names to exclude from scheduler rotation. Default first-run daily scheduler prompt is `scheduler-update-agent`. diff --git a/src/nostr-lock.mjs b/src/nostr-lock.mjs index 049581d..dc4ea25 100644 --- a/src/nostr-lock.mjs +++ b/src/nostr-lock.mjs @@ -259,15 +259,22 @@ async function cmdCheck(cadence) { const relays = getRelays(); const namespace = getNamespace(); const dateStr = todayDateStr(); + const config = loadTorchConfig(); + const pausedAgents = (cadence === 'daily' ? config.scheduler.paused.daily : config.scheduler.paused.weekly) || []; console.error(`Checking locks: namespace=${namespace}, cadence=${cadence}, date=${dateStr}`); console.error(`Relays: ${relays.join(', ')}`); + if (pausedAgents.length > 0) { + console.error(`Paused agents: ${pausedAgents.join(', ')}`); + } const locks = await queryLocks(relays, cadence, dateStr, namespace); const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))]; const roster = getRoster(cadence); + + const excludedAgents = [...new Set([...lockedAgents, ...pausedAgents])]; const unknownLockedAgents = lockedAgents.filter((agent) => !roster.includes(agent)); - const available = roster.filter((a) => !lockedAgents.includes(a)); + const available = roster.filter((a) => !excludedAgents.includes(a)); console.log( JSON.stringify( @@ -276,6 +283,8 @@ async function cmdCheck(cadence) { cadence, date: dateStr, locked: lockedAgents.sort(), + paused: pausedAgents.sort(), + excluded: excludedAgents.sort(), available: available.sort(), lockCount: locks.length, unknownLockedAgents: unknownLockedAgents.sort(), diff --git a/src/prompts/scheduler-flow.md b/src/prompts/scheduler-flow.md index a226932..d844a0f 100644 --- a/src/prompts/scheduler-flow.md +++ b/src/prompts/scheduler-flow.md @@ -37,7 +37,7 @@ Use this document for all scheduler runs. Selection algorithm (MUST be followed exactly): - Roster source: `src/prompts/roster.json` and the key matching ``. - - Let `roster` be that ordered array and `excluded` be the locked set from step 2. + - Let `roster` be that ordered array and `excluded` be the `excluded` set (including paused and locked agents) from the JSON output of step 2. - Let `latest_file` be the lexicographically last filename in ``. - Determine `previous_agent` from `latest_file` using this precedence: 1. Parse YAML frontmatter from `/` and use key `agent` when present and non-empty. diff --git a/src/torch-config.mjs b/src/torch-config.mjs index 5d0ead4..1277bd6 100644 --- a/src/torch-config.mjs +++ b/src/torch-config.mjs @@ -45,6 +45,7 @@ export function loadTorchConfig() { const dashboard = raw.dashboard || {}; const scheduler = raw.scheduler || {}; const firstPromptByCadence = scheduler.firstPromptByCadence || {}; + const paused = scheduler.paused || {}; cachedConfig = { configPath, @@ -77,6 +78,10 @@ export function loadTorchConfig() { daily: typeof firstPromptByCadence.daily === 'string' ? firstPromptByCadence.daily.trim() : null, weekly: typeof firstPromptByCadence.weekly === 'string' ? firstPromptByCadence.weekly.trim() : null, }, + paused: { + daily: parseRoster(paused.daily) || [], + weekly: parseRoster(paused.weekly) || [], + }, }, }; diff --git a/test/nostr-lock.test.mjs b/test/nostr-lock.test.mjs index e29f833..0405443 100644 --- a/test/nostr-lock.test.mjs +++ b/test/nostr-lock.test.mjs @@ -1,6 +1,7 @@ import { test, describe, it } from 'node:test'; import assert from 'node:assert'; import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; describe('CLI Smoke Test', () => { it('should print usage when no args provided', () => { @@ -14,4 +15,24 @@ describe('CLI Smoke Test', () => { assert.strictEqual(result.status, 1); assert.match(result.stderr.toString(), /--cadence is required/); }); + + it('should include paused agents in check output', () => { + const configPath = 'test-torch-config.json'; + fs.writeFileSync(configPath, JSON.stringify({ + scheduler: { paused: { daily: ['paused-agent'] } } + })); + + try { + const result = spawnSync('node', ['src/nostr-lock.mjs', 'check', '--cadence', 'daily'], { + env: { ...process.env, TORCH_CONFIG_PATH: configPath } + }); + const output = JSON.parse(result.stdout.toString()); + assert.deepStrictEqual(output.paused, ['paused-agent']); + assert.ok(output.excluded.includes('paused-agent')); + } finally { + if (fs.existsSync(configPath)) { + fs.unlinkSync(configPath); + } + } + }); });