diff --git a/scripts/agent/run-scheduler-cycle.mjs b/scripts/agent/run-scheduler-cycle.mjs index b89b054..b5a65cf 100644 --- a/scripts/agent/run-scheduler-cycle.mjs +++ b/scripts/agent/run-scheduler-cycle.mjs @@ -691,7 +691,7 @@ async function main() { } } - const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`]); + const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`, '--', '--json', '--quiet']); const checkPayload = parseJsonFromOutput(`${checkResult.stdout}\n${checkResult.stderr}`) || {}; const excluded = Array.isArray(checkPayload.excluded) ? checkPayload.excluded diff --git a/src/cli-parser.mjs b/src/cli-parser.mjs index e9d9c8a..f768a00 100644 --- a/src/cli-parser.mjs +++ b/src/cli-parser.mjs @@ -11,6 +11,9 @@ export function parseArgs(argv) { command: null, agent: null, cadence: null, + json: false, + jsonFile: null, + quiet: false, dryRun: false, force: false, port: DEFAULT_DASHBOARD_PORT, @@ -42,6 +45,14 @@ export function parseArgs(argv) { args.agent = argv[++i]; } else if (arg === '--cadence' || arg === '-c') { args.cadence = argv[++i]; + } else if (arg === '--json') { + args.json = true; + } else if (arg === '--json-file') { + args.jsonFile = argv[++i]; + } else if (arg.startsWith('--json-file=')) { + args.jsonFile = arg.split('=')[1]; + } else if (arg === '--quiet') { + args.quiet = true; } else if (arg.startsWith('--agent=')) { args.agent = arg.split('=')[1]; } else if (arg.startsWith('--cadence=')) { diff --git a/src/docs/TORCH.md b/src/docs/TORCH.md index 6a2a362..fbe3018 100644 --- a/src/docs/TORCH.md +++ b/src/docs/TORCH.md @@ -20,6 +20,9 @@ To follow in any Nostr client, subscribe to kind 30078 with `#t = torch-agent-lo # Check active locks node bin/torch-lock.mjs check --cadence daily +# Machine-readable lock output (recommended for automation) +node bin/torch-lock.mjs check --cadence daily --json --quiet --json-file /tmp/torch-lock-check.json + # Claim a task AGENT_PLATFORM=codex node bin/torch-lock.mjs lock --agent docs-agent --cadence daily diff --git a/src/lib.mjs b/src/lib.mjs index 9541615..96e8315 100644 --- a/src/lib.mjs +++ b/src/lib.mjs @@ -114,6 +114,9 @@ export async function cmdCheck(cadence, deps = {}) { error = console.error, logDir = 'task-logs', ignoreLogs = false, + json = false, + jsonFile = null, + quiet = false, } = deps; const relays = getRelays(); @@ -122,16 +125,18 @@ export async function cmdCheck(cadence, deps = {}) { const config = loadTorchConfig(); const pausedAgents = config.scheduler.paused[cadence] || []; - error(`Checking locks: namespace=${namespace}, cadence=${cadence}, date=${dateStr}`); - error(`Relays: ${relays.join(', ')}`); - if (pausedAgents.length > 0) { - error(`Paused agents: ${pausedAgents.join(', ')}`); + if (!quiet) { + error(`Checking locks: namespace=${namespace}, cadence=${cadence}, date=${dateStr}`); + error(`Relays: ${relays.join(', ')}`); + if (pausedAgents.length > 0) { + error(`Paused agents: ${pausedAgents.join(', ')}`); + } } let completedAgents = new Set(); if (!ignoreLogs) { completedAgents = await getCompletedAgents(cadence, logDir, deps); - if (completedAgents.size > 0) { + if (!quiet && completedAgents.size > 0) { error(`Completed agents (logs): ${[...completedAgents].join(', ')}`); } } @@ -166,7 +171,18 @@ export async function cmdCheck(cadence, deps = {}) { })), }; - log(JSON.stringify(result, null, 2)); + const output = json ? JSON.stringify(result) : JSON.stringify(result, null, 2); + + if (jsonFile) { + const resolvedPath = path.resolve(process.cwd(), jsonFile); + await fs.mkdir(path.dirname(resolvedPath), { recursive: true }); + await fs.writeFile(resolvedPath, `${output}\n`, 'utf8'); + } + + if (json || !quiet) { + log(output); + } + return result; } @@ -530,6 +546,9 @@ Options: --force Overwrite existing files (for init) or all files (for update) --log-dir Path to task logs directory (default: task-logs) --ignore-logs Skip checking local logs for completed tasks + --json Emit compact single-line JSON + --json-file Write JSON output to a file path + --quiet Suppress stderr progress logs (pairs well with --json) Environment: NOSTR_LOCK_NAMESPACE Namespace prefix for lock tags (default: torch) @@ -569,7 +588,13 @@ export async function main(argv) { console.error(`ERROR: --cadence <${[...VALID_CADENCES].join('|')}> is required for check`); throw new ExitError(1, 'Missing cadence'); } - await cmdCheck(args.cadence, { logDir: args.logDir, ignoreLogs: args.ignoreLogs }); + await cmdCheck(args.cadence, { + logDir: args.logDir, + ignoreLogs: args.ignoreLogs, + json: args.json, + jsonFile: args.jsonFile, + quiet: args.quiet, + }); break; } diff --git a/src/prompts/META_PROMPTS.md b/src/prompts/META_PROMPTS.md index 61e219d..3dd3070 100644 --- a/src/prompts/META_PROMPTS.md +++ b/src/prompts/META_PROMPTS.md @@ -21,7 +21,7 @@ MUST 1: Set cadence config to: MUST 2: Run preflight to get the exclusion set: -npm run lock:check:daily +npm run lock:check:daily -- --json --quiet Use `excluded` from the JSON output as the canonical exclusion set. If `excluded` is unavailable, fallback to the union of `locked`, `paused`, and `completed`. @@ -105,7 +105,7 @@ MUST 1: Set cadence config to: MUST 2: Run preflight to get the exclusion set: -npm run lock:check:weekly +npm run lock:check:weekly -- --json --quiet Use `excluded` from the JSON output as the canonical exclusion set. If `excluded` is unavailable, fallback to the union of `locked`, `paused`, and `completed`. diff --git a/src/prompts/scheduler-flow.md b/src/prompts/scheduler-flow.md index 8e9a292..56f48e2 100644 --- a/src/prompts/scheduler-flow.md +++ b/src/prompts/scheduler-flow.md @@ -59,13 +59,15 @@ Every agent prompt invoked by the schedulers (daily/weekly) MUST enforce this co 2. Run preflight to build the exclusion set: ```bash - if daily: `npm run lock:check:daily`; if weekly: `npm run lock:check:weekly` + if daily: `npm run lock:check:daily -- --json --quiet`; if weekly: `npm run lock:check:weekly -- --json --quiet` ``` Canonical exclusion rule: - 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. + Goose Desktop note: `npm run lock:check:` can emit large hermit wrapper logs. Use `--json --quiet` (as documented above). If the command still fails due to Goose hermit issues, apply the PATH workaround in `KNOWN_ISSUES.md` before rerunning. + 3. Read policy file(s) once before the run loop. This step is conditional: if `AGENTS.md` is missing, continue without failing. ```bash