fix continuity clean-room install verification

This commit is contained in:
2026-04-24 20:29:08 +08:00
parent 1cf8bf491d
commit 46fa3b8d49
17 changed files with 1765 additions and 45 deletions

View File

@@ -0,0 +1,182 @@
#!/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 hasNonEmptyString(value) {
return typeof value === 'string' && value.trim().length > 0;
}
function hasEvidenceObject(value) {
if (!value) return false;
if (hasNonEmptyString(value)) return true;
if (Array.isArray(value)) return value.length > 0;
if (typeof value === 'object') return Object.keys(value).length > 0;
return false;
}
function normalizedAction(value) {
return hasNonEmptyString(value) ? value.trim() : '';
}
function evaluatePlan(input) {
const gateStatus = normalizedAction(input?.gateStatus);
const actorStage = normalizedAction(input?.actorStage);
const requiredNextAction = normalizedAction(input?.requiredNextAction || input?.concreteNextAction || input?.nextStep);
const reviewOutcome = normalizedAction(input?.reviewOutcome).toLowerCase();
const blocker = normalizedAction(input?.blocker);
const executionEvidence = input?.executionEvidence;
const reviewEvidence = input?.reviewEvidence;
const blockerEvidence = input?.blockerEvidence;
if (gateStatus !== 'pass') {
return {
plannerStatus: 'blocked_by_gate',
derivedAction: 'none',
dispatchMode: 'no_dispatch',
reason: 'gateStatus must pass before auto-chain planning can proceed',
requiredEvidence: ['gateStatus=pass'],
autoChainAllowed: false,
};
}
if (!requiredNextAction) {
return {
plannerStatus: 'none',
derivedAction: 'none',
dispatchMode: 'no_dispatch',
reason: 'no concrete next action available for auto-chain planning',
requiredEvidence: ['concreteNextAction'],
autoChainAllowed: false,
};
}
if (actorStage === 'implementer_result' && requiredNextAction === 'request_spec_review') {
if (!hasEvidenceObject(executionEvidence)) {
return {
plannerStatus: 'blocked_by_evidence',
derivedAction: 'none',
dispatchMode: 'no_dispatch',
reason: 'implementation evidence missing for review-required next action',
requiredEvidence: ['executionEvidence'],
autoChainAllowed: false,
};
}
return {
plannerStatus: 'pass',
derivedAction: 'dispatch_spec_review',
dispatchMode: 'dry_run_dispatch',
reason: 'implementation evidence present; derived spec review dispatch in dry-run mode',
requiredEvidence: ['executionEvidence'],
autoChainAllowed: true,
};
}
if (actorStage === 'spec_review' && reviewOutcome === 'pass' && requiredNextAction === 'request_code_quality_review') {
if (!hasEvidenceObject(reviewEvidence)) {
return {
plannerStatus: 'blocked_by_evidence',
derivedAction: 'none',
dispatchMode: 'no_dispatch',
reason: 'review pass evidence missing for code quality review transition',
requiredEvidence: ['reviewEvidence'],
autoChainAllowed: false,
};
}
return {
plannerStatus: 'pass',
derivedAction: 'dispatch_code_quality_review',
dispatchMode: 'dry_run_dispatch',
reason: 'review pass evidence present; derived code quality review dispatch in dry-run mode',
requiredEvidence: ['reviewEvidence'],
autoChainAllowed: true,
};
}
if (requiredNextAction === 'fix_review_findings' || hasNonEmptyString(blocker)) {
if (!hasEvidenceObject(blockerEvidence)) {
return {
plannerStatus: 'blocked_by_evidence',
derivedAction: 'none',
dispatchMode: 'no_dispatch',
reason: 'blocker evidence missing for retry/fix transition',
requiredEvidence: ['blockerEvidence'],
autoChainAllowed: false,
};
}
return {
plannerStatus: 'pass',
derivedAction: 'dispatch_fix_slice',
dispatchMode: 'dry_run_dispatch',
reason: 'blocker evidence present; derived retry/fix dispatch in dry-run mode',
requiredEvidence: ['blockerEvidence'],
autoChainAllowed: true,
};
}
return {
plannerStatus: 'none',
derivedAction: 'none',
dispatchMode: 'no_dispatch',
reason: 'no concrete next action matched a dry-run auto-chain transition',
requiredEvidence: ['matchedTransitionEvidence'],
autoChainAllowed: false,
};
}
function main() {
const args = parseArgs(process.argv);
const raw = readInput(args.input);
const input = parseJson(raw);
const output = evaluatePlan(input);
process.stdout.write(JSON.stringify(output, null, args.pretty ? 2 : 0) + '\n');
}
export { evaluatePlan };
const isDirectRun = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(new URL(import.meta.url));
if (isDirectRun) {
try {
main();
} catch (error) {
fail('CLI_ERROR', error && error.message ? error.message : 'unexpected error');
}
}