plan-execution-status-smoke.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const os = require('os');
  4. const path = require('path');
  5. const { spawnSync } = require('child_process');
  6. const ROOT = path.resolve(__dirname, '..');
  7. function main() {
  8. const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tihao-plan-execution-status-'));
  9. const result = spawnSync(process.execPath, [
  10. 'scripts/plan-execution-status.js',
  11. '--output',
  12. outputDir,
  13. '--strict'
  14. ], {
  15. cwd: ROOT,
  16. encoding: 'utf8',
  17. shell: false
  18. });
  19. if (result.stdout) process.stdout.write(result.stdout);
  20. if (result.stderr) process.stderr.write(result.stderr);
  21. assert(result.status === 0, 'plan execution status should pass');
  22. const summaryPath = path.join(outputDir, 'plan-execution-status-summary.json');
  23. const reportPath = path.join(outputDir, 'plan-execution-status.md');
  24. const csvPath = path.join(outputDir, 'plan-execution-status.csv');
  25. assert(fs.existsSync(summaryPath), 'summary should exist');
  26. assert(fs.existsSync(reportPath), 'report should exist');
  27. assert(fs.existsSync(csvPath), 'csv should exist');
  28. const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8').replace(/^\uFEFF/, ''));
  29. const report = fs.readFileSync(reportPath, 'utf8').replace(/^\uFEFF/, '');
  30. const csv = fs.readFileSync(csvPath, 'utf8').replace(/^\uFEFF/, '');
  31. const sections = summary.rows.map(row => row.section);
  32. const operatorPack = readJson(path.join(ROOT, 'outputs', 'proof-gap-operator-pack-latest', 'proof-gap-operator-pack-summary.json'));
  33. const businessExecution = readJson(path.join(ROOT, 'outputs', 'business-execution-index-latest', 'business-execution-index-summary.json'));
  34. const expectedOwnerArtifactCount = Number(operatorPack?.ownerArtifactCount || operatorPack?.ownerArtifacts?.length || summary.operatorPackOwnerArtifactCount || 0);
  35. const expectedBusinessOwnerArtifactRows = Number(businessExecution?.operatorPackOwnerArtifactRowCount || expectedOwnerArtifactCount);
  36. assert(summary.passed === true, 'summary should pass structural checks');
  37. assert(summary.complete === false, 'summary should not claim full completion while proof gaps are open');
  38. assert(summary.rowCount === 8, 'summary should map the eight plan sections');
  39. assert(summary.proofOpenCount >= 1, 'summary should expose open proof gaps');
  40. assert(summary.operatorPackOwnerArtifactCount === expectedOwnerArtifactCount, 'summary should expose proof-gap operator owner attachment count');
  41. assert(summary.businessExecutionOperatorPackOwnerArtifactRowCount === expectedBusinessOwnerArtifactRows, 'summary should expose business execution operator attachment rows');
  42. assert(Array.isArray(summary.operatorPackOwnerArtifacts) && summary.operatorPackOwnerArtifacts.length === expectedOwnerArtifactCount, 'summary should carry proof-gap operator owner attachments');
  43. assert(summary.files.report.includes('plan-execution-status.md'), 'summary should expose report path');
  44. assert(summary.guardrails.some(item => item.includes('不证明客户效果')), 'guardrails should preserve customer-effect boundary');
  45. for (const section of ['核心判断', '当前已具备能力', '关键差距', '长跑优化任务', '软件端表格验收', '质量验收标准', '给 AI 的优化任务', '每轮沉淀']) {
  46. assert(sections.includes(section), `missing plan section row: ${section}`);
  47. assert(report.includes(section), `report should include section: ${section}`);
  48. }
  49. assert(summary.rows.some(row => row.section === '关键差距' && row.status === 'open'), 'key gaps should stay open until real proof closes');
  50. assert(summary.rows.some(row => row.section === '每轮沉淀' && row.status === 'passed'), 'round deposition row should pass');
  51. assert(report.includes('# 提号长期优化计划执行状态'), 'report should be Chinese');
  52. assert(report.includes('proofOpenCount'), 'report should show proof open count');
  53. assert(report.includes('operatorPackOwnerArtifactCount'), 'report should show operator owner attachment evidence');
  54. assert(report.includes('by-owner/business.md'), 'report should show business owner operator attachment');
  55. assert(report.includes('不得宣称命中率提升'), 'report should include no-claim boundary');
  56. assert(csv.startsWith('顺序,计划章节,状态'), 'csv should use Chinese software-friendly headers');
  57. assertNoSecretsOrMojibake(report);
  58. assertNoSecretsOrMojibake(csv);
  59. console.log(JSON.stringify({
  60. ok: true,
  61. outputDir,
  62. rowCount: summary.rowCount,
  63. proofOpenCount: summary.proofOpenCount
  64. }, null, 2));
  65. }
  66. function assertNoSecretsOrMojibake(text) {
  67. assert(!/sk-[A-Za-z0-9_-]{20,}/.test(text), 'output should not contain model token patterns');
  68. assert(!/r:[A-Za-z0-9]{20,}/.test(text), 'output should not contain Parse sessionToken patterns');
  69. assert(!/Authorization\s*[:=]/i.test(text), 'output should not expose Authorization header text');
  70. assert(!/\uFFFD/.test(text), 'output should not contain replacement characters');
  71. assert(!/[閹荤粵閺嶉崣鐟欑拠閻╂潏闁导缂傞弬閸熸矮娑斾梗]{3,}/.test(text), 'output should not contain common mojibake');
  72. }
  73. function readJson(file) {
  74. if (!fs.existsSync(file)) return null;
  75. return JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''));
  76. }
  77. function assert(condition, message) {
  78. if (!condition) throw new Error(message);
  79. }
  80. if (require.main === module) main();