#!/usr/bin/env node import fs from 'fs'; function fail(code, message) { process.stderr.write(`${code}: ${message}\n`); process.exit(1); } function parseArgs(argv) { const args = { input: '', pretty: true }; for (let i = 2; i < argv.length; i += 1) { const arg = argv[i]; if (arg === '--input') { const value = argv[i + 1]; if (!value || value.startsWith('--')) fail('CLI_ERROR', '--input requires a value'); args.input = value; i += 1; } else if (arg === '--compact') { args.pretty = false; } else { fail('CLI_ERROR', `unknown argument: ${arg}`); } } return args; } function readInput(path) { if (!path || path === '-') return fs.readFileSync(0, 'utf8'); return fs.readFileSync(path, 'utf8'); } function parseJson(raw) { try { return JSON.parse(raw); } catch { fail('INVALID_JSON', 'input must be valid JSON'); } } function isLongTask(input) { return input.classification === 'long_task'; } function hasExternalizedCheckpointPath(input) { if (typeof input.externalizedCheckpointPath === 'string' && input.externalizedCheckpointPath.trim()) return true; if (typeof input.externalizedTrigger === 'string' && input.externalizedTrigger.trim()) return true; if (typeof input.checkpointPath === 'string' && input.checkpointPath.trim()) return true; return false; } function hasConcreteNextAction(input) { if (typeof input.nextStep === 'string' && input.nextStep.trim()) return true; if (typeof input.requiredNextAction === 'string' && input.requiredNextAction.trim()) return true; if (typeof input.concreteNextAction === 'string' && input.concreteNextAction.trim()) return true; return false; } function wantsSilentContinuation(input) { if (typeof input.silentContinuation === 'boolean') return input.silentContinuation; if (typeof input.silentCandidate === 'boolean') return input.silentCandidate; if (typeof input.needsWaiting === 'boolean' && input.needsWaiting) return true; if (typeof input.needsSubagent === 'boolean' && input.needsSubagent) return true; return false; } function claimsExecution(input) { if (typeof input.claimedExecution === 'boolean') return input.claimedExecution; if (typeof input.executionClaimed === 'boolean') return input.executionClaimed; if (typeof input.status === 'string' && input.status === 'active') return true; return false; } function needsOwnerDecision(input) { if (typeof input.needsOwnerDecision === 'boolean') return input.needsOwnerDecision; return false; } function usesButtonPath(input) { if (typeof input.handoffMode === 'string') return input.handoffMode === 'button_path'; if (input.handoff && typeof input.handoff.mode === 'string') return input.handoff.mode === 'button_path'; if (typeof input.replyClosureMode === 'string') return input.replyClosureMode === 'button_path'; return false; } function evaluateGate(input) { const gateRequired = isLongTask(input); const reasons = []; const requiredEvidence = []; const allowedResponseModes = []; if (!gateRequired) { return { gateRequired: false, gateStatus: 'not_applicable', reasons: ['classification is not long_task'], requiredEvidence: [], allowedResponseModes: ['direct_reply'], }; } let failed = false; if (wantsSilentContinuation(input) && !hasExternalizedCheckpointPath(input)) { failed = true; reasons.push('silent long-task cannot continue without externalized checkpoint path'); requiredEvidence.push('externalizedCheckpointPath'); allowedResponseModes.push('non_silent_follow_up'); } if (claimsExecution(input) && !hasConcreteNextAction(input)) { failed = true; reasons.push('claimed execution requires evidence of a concrete next action'); requiredEvidence.push('nextStep'); allowedResponseModes.push('checkpoint_only'); } if (needsOwnerDecision(input) && !usesButtonPath(input)) { failed = true; reasons.push('owner decision flow must end in button-path, not plain text'); requiredEvidence.push('handoff.mode=button_path'); allowedResponseModes.push('button_path'); } if (!failed) { reasons.push('required long-task gate evidence is present or no gated condition was triggered'); allowedResponseModes.push(needsOwnerDecision(input) ? 'button_path' : 'direct_reply'); if (wantsSilentContinuation(input)) allowedResponseModes.push('silent_continuation'); } return { gateRequired: true, gateStatus: failed ? 'fail' : 'pass', reasons, requiredEvidence, allowedResponseModes: [...new Set(allowedResponseModes)], }; } function main() { const args = parseArgs(process.argv); const raw = readInput(args.input); const input = parseJson(raw); const output = evaluateGate(input); process.stdout.write(JSON.stringify(output, null, args.pretty ? 2 : 0) + '\n'); } try { main(); } catch (error) { fail('CLI_ERROR', error && error.message ? error.message : 'unexpected error'); }