#!/usr/bin/env node const fs = require('fs'); const os = require('os'); const path = require('path'); const { spawnSync } = require('child_process'); const ROOT = path.resolve(__dirname, '..'); function main() { const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-plan-execution-status-')); const result = spawnSync(process.execPath, [ 'scripts/plan-execution-status.js', '--output', outputDir, '--strict' ], { cwd: ROOT, encoding: 'utf8', shell: false }); if (result.stdout) process.stdout.write(result.stdout); if (result.stderr) process.stderr.write(result.stderr); assert(result.status === 0, 'plan execution status should pass'); const summaryPath = path.join(outputDir, 'plan-execution-status-summary.json'); const reportPath = path.join(outputDir, 'plan-execution-status.md'); const csvPath = path.join(outputDir, 'plan-execution-status.csv'); assert(fs.existsSync(summaryPath), 'summary should exist'); assert(fs.existsSync(reportPath), 'report should exist'); assert(fs.existsSync(csvPath), 'csv should exist'); const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8').replace(/^\uFEFF/, '')); const report = fs.readFileSync(reportPath, 'utf8').replace(/^\uFEFF/, ''); const csv = fs.readFileSync(csvPath, 'utf8').replace(/^\uFEFF/, ''); const sections = summary.rows.map(row => row.section); const operatorPack = readJson(path.join(ROOT, 'outputs', 'proof-gap-operator-pack-latest', 'proof-gap-operator-pack-summary.json')); const businessExecution = readJson(path.join(ROOT, 'outputs', 'business-execution-index-latest', 'business-execution-index-summary.json')); const expectedOwnerArtifactCount = Number(operatorPack?.ownerArtifactCount || operatorPack?.ownerArtifacts?.length || summary.operatorPackOwnerArtifactCount || 0); const expectedBusinessOwnerArtifactRows = Number(businessExecution?.operatorPackOwnerArtifactRowCount || expectedOwnerArtifactCount); assert(summary.passed === true, 'summary should pass structural checks'); assert(summary.complete === false, 'summary should not claim full completion while proof gaps are open'); assert(summary.rowCount === 8, 'summary should map the eight plan sections'); assert(summary.proofOpenCount >= 1, 'summary should expose open proof gaps'); assert(summary.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'summary should expose proof-gap operator owner attachment count'); assert(summary.businessExecutionOperatorPackOwnerArtifactRowCount === expectedBusinessOwnerArtifactRows, 'summary should expose business execution operator attachment rows'); assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === expectedOwnerArtifactCount, 'summary should carry proof-gap operator owner attachments'); assert(summary.files.report.includes('plan-execution-status.md'), 'summary should expose report path'); assert(summary.guardrails.some(item => item.includes('不证明客户效果')), 'guardrails should preserve customer-effect boundary'); for (const section of ['核心判断', '当前已具备能力', '关键差距', '长跑优化任务', '软件端表格验收', '质量验收标准', '给 AI 的优化任务', '每轮沉淀']) { assert(sections.includes(section), `missing plan section row: ${section}`); assert(report.includes(section), `report should include section: ${section}`); } assert(summary.rows.some(row => row.section === '关键差距' && row.status === 'open'), 'key gaps should stay open until real proof closes'); assert(summary.rows.some(row => row.section === '每轮沉淀' && row.status === 'passed'), 'round deposition row should pass'); assert(report.includes('# 提号长期优化计划执行状态'), 'report should be Chinese'); assert(report.includes('proofOpenCount'), 'report should show proof open count'); assert(report.includes('operatorPackOwnerArtifactCount'), 'report should show operator owner attachment evidence'); assert(report.includes('by-owner/business.md'), 'report should show business owner operator attachment'); assert(report.includes('不得宣称命中率提升'), 'report should include no-claim boundary'); assert(csv.startsWith('顺序,计划章节,状态'), 'csv should use Chinese software-friendly headers'); assertNoSecretsOrMojibake(report); assertNoSecretsOrMojibake(csv); console.log(JSON.stringify({ ok: true, outputDir, rowCount: summary.rowCount, proofOpenCount: summary.proofOpenCount }, null, 2)); } function assertNoSecretsOrMojibake(text) { assert(!/sk-[A-Za-z0-9_-]{20,}/.test(text), 'output should not contain model token patterns'); assert(!/r:[A-Za-z0-9]{20,}/.test(text), 'output should not contain Parse sessionToken patterns'); assert(!/Authorization\s*[:=]/i.test(text), 'output should not expose Authorization header text'); assert(!/\uFFFD/.test(text), 'output should not contain replacement characters'); assert(!/[閹荤粵閺嶉崣鐟欑拠閻╂潏闁导缂傞弬閸熸矮娑斾梗]{3,}/.test(text), 'output should not contain common mojibake'); } function readJson(file) { if (!fs.existsSync(file)) return null; return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '')); } function assert(condition, message) { if (!condition) throw new Error(message); } if (require.main === module) main();