#!/usr/bin/env node /** * CI State Update Script * * Deterministic state management for CI monitor actions. * Three commands: gate, post-action, cycle-check. * * Usage: * node ci-state-update.mjs gate --gate-type [counter args] * node ci-state-update.mjs post-action --action [--cipe-url ] [--commit-sha ] * node ci-state-update.mjs cycle-check --code [--agent-triggered] [counter args] */ // --- Arg parsing --- const args = process.argv.slice(2); const command = args[0]; function getFlag(name) { return args.includes(name); } function getArg(name) { const idx = args.indexOf(name); return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null; } function output(result) { console.log(JSON.stringify(result)); } // --- gate --- // Check if an action is allowed and return incremented counter. // Called before any local fix attempt or environment rerun. function gate() { const gateType = getArg('--gate-type'); if (gateType === 'local-fix') { const count = parseInt(getArg('--local-verify-count') || '0', 10); const max = parseInt(getArg('--local-verify-attempts') || '3', 10); if (count >= max) { return output({ allowed: false, localVerifyCount: count, message: `Local fix budget exhausted (${count}/${max} attempts)`, }); } return output({ allowed: true, localVerifyCount: count + 1, message: null, }); } if (gateType === 'env-rerun') { const count = parseInt(getArg('--env-rerun-count') || '0', 10); if (count >= 2) { return output({ allowed: false, envRerunCount: count, message: `Environment issue persists after ${count} reruns. Manual investigation needed.`, }); } return output({ allowed: true, envRerunCount: count + 1, message: null, }); } output({ allowed: false, message: `Unknown gate type: ${gateType}` }); } // --- post-action --- // Compute next state after an action is taken. // Returns wait mode params and whether the action was agent-triggered. function postAction() { const action = getArg('--action'); const cipeUrl = getArg('--cipe-url'); const commitSha = getArg('--commit-sha'); // MCP-triggered or auto-applied: track by cipeUrl const cipeUrlActions = ['fix-auto-applying', 'apply-mcp', 'env-rerun']; // Local push: track by commitSha const commitShaActions = [ 'apply-local-push', 'reject-fix-push', 'local-fix-push', 'auto-fix-push', 'empty-commit-push', ]; const trackByCipeUrl = cipeUrlActions.includes(action); const trackByCommitSha = commitShaActions.includes(action); if (!trackByCipeUrl && !trackByCommitSha) { return output({ error: `Unknown action: ${action}` }); } // fix-auto-applying: self-healing did it, NOT the monitor const agentTriggered = action !== 'fix-auto-applying'; output({ waitMode: true, pollCount: 0, lastCipeUrl: trackByCipeUrl ? cipeUrl : null, expectedCommitSha: trackByCommitSha ? commitSha : null, agentTriggered, }); } // --- cycle-check --- // Cycle classification + counter resets when a new "done" code is received. // Called at the start of handling each actionable code. function cycleCheck() { const status = getArg('--code'); const wasAgentTriggered = getFlag('--agent-triggered'); let cycleCount = parseInt(getArg('--cycle-count') || '0', 10); const maxCycles = parseInt(getArg('--max-cycles') || '10', 10); let envRerunCount = parseInt(getArg('--env-rerun-count') || '0', 10); // Cycle classification: if previous cycle was agent-triggered, count it if (wasAgentTriggered) cycleCount++; // Reset env_rerun_count on non-environment status if (status !== 'environment_issue') envRerunCount = 0; // Approaching limit gate const approachingLimit = cycleCount >= maxCycles - 2; output({ cycleCount, agentTriggered: false, envRerunCount, approachingLimit, message: approachingLimit ? `Approaching cycle limit (${cycleCount}/${maxCycles})` : null, }); } // --- Dispatch --- switch (command) { case 'gate': gate(); break; case 'post-action': postAction(); break; case 'cycle-check': cycleCheck(); break; default: output({ error: `Unknown command: ${command}` }); }