validate-output-standard.js 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. #!/usr/bin/env node
  2. const fs = require('fs');
  3. const path = require('path');
  4. const {
  5. PACKAGE_ROOT,
  6. OUTPUT_CATEGORIES,
  7. outputsRoot
  8. } = require('../mcp/src/core/output-paths');
  9. const DOCS_ALLOWED = new Set(['OUTPUT-STANDARD.md', 'specs', 'guides', 'generated']);
  10. const RUN_DIR_RE = /^\d{6}-[a-z0-9\u4e00-\u9fa5-]+$/;
  11. const DATE_DIR_RE = /^\d{4}-\d{2}-\d{2}$/;
  12. const problems = [];
  13. function listEntries(dir) {
  14. if (!fs.existsSync(dir)) return [];
  15. return fs.readdirSync(dir, { withFileTypes: true });
  16. }
  17. function checkOutputs() {
  18. const root = outputsRoot();
  19. for (const entry of listEntries(root)) {
  20. if (!entry.isDirectory()) {
  21. problems.push(`outputs 根目录不允许直接放文件:outputs/${entry.name}`);
  22. continue;
  23. }
  24. if (!OUTPUT_CATEGORIES.includes(entry.name)) {
  25. problems.push(`outputs 下存在未注册类别:outputs/${entry.name}(允许:${OUTPUT_CATEGORIES.join(', ')})`);
  26. continue;
  27. }
  28. checkCategory(path.join(root, entry.name), entry.name);
  29. }
  30. }
  31. function checkCategory(dir, category) {
  32. for (const entry of listEntries(dir)) {
  33. if (!entry.isDirectory()) continue; // latest 模式文件
  34. if (!DATE_DIR_RE.test(entry.name)) {
  35. problems.push(`outputs/${category}/${entry.name} 不是 YYYY-MM-DD 日期目录`);
  36. continue;
  37. }
  38. const dateDir = path.join(dir, entry.name);
  39. for (const run of listEntries(dateDir)) {
  40. const rel = `outputs/${category}/${entry.name}/${run.name}`;
  41. if (!run.isDirectory()) {
  42. problems.push(`${rel} 应为 run 目录而不是文件`);
  43. continue;
  44. }
  45. if (!RUN_DIR_RE.test(run.name)) {
  46. problems.push(`${rel} 不符合 HHmmss-<slug> 命名`);
  47. }
  48. if (!fs.existsSync(path.join(dateDir, run.name, 'manifest.json'))) {
  49. problems.push(`${rel} 缺少 manifest.json`);
  50. }
  51. }
  52. }
  53. }
  54. function checkDocs() {
  55. const docsDir = path.join(PACKAGE_ROOT, 'docs');
  56. for (const entry of listEntries(docsDir)) {
  57. if (!DOCS_ALLOWED.has(entry.name)) {
  58. problems.push(`docs 第一层存在未注册条目:docs/${entry.name}(允许:${[...DOCS_ALLOWED].join(', ')})`);
  59. }
  60. }
  61. }
  62. checkOutputs();
  63. checkDocs();
  64. if (problems.length) {
  65. console.error('输出目录标准校验失败:');
  66. for (const problem of problems) console.error(` - ${problem}`);
  67. process.exit(1);
  68. }
  69. console.log('输出目录标准校验通过(outputs 与 docs 均符合 docs/OUTPUT-STANDARD.md)。');