verify-doc-budgets.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. /**
  2. * Enforce `wc -w`-style ceilings from `scripts/doc-budgets.manifest.json`.
  3. * Missing files and invalid ceilings fail; `--list` reports current usage.
  4. * Only listed standing docs are budgeted. Ceilings ratchet down with at least
  5. * 5% headroom; raising one requires the justification defined in
  6. * `docs/AGENTS.md`.
  7. */
  8. import { existsSync, readFileSync } from 'node:fs'
  9. import { resolve } from 'node:path'
  10. const root = resolve(import.meta.dirname, '..')
  11. const MANIFEST_PATH = resolve(root, 'scripts/doc-budgets.manifest.json')
  12. /** `wc -w` equivalent: count whitespace-delimited tokens. */
  13. function countWords(text: string): number {
  14. return text.split(/\s+/).filter(Boolean).length
  15. }
  16. const manifest = JSON.parse(readFileSync(MANIFEST_PATH, 'utf8')) as Record<string, number>
  17. const listOnly = process.argv.includes('--list')
  18. const failures: string[] = []
  19. const rows: string[] = []
  20. for (const [path, ceiling] of Object.entries(manifest)) {
  21. if (!Number.isInteger(ceiling) || ceiling <= 0) {
  22. rows.push(`BAD ${'—'.padStart(6)} / ${String(ceiling).padEnd(6)} ${path}`)
  23. failures.push(`${path}: ceiling must be a positive integer, got ${ceiling}`)
  24. continue
  25. }
  26. const abs = resolve(root, path)
  27. if (!existsSync(abs)) {
  28. rows.push(`MISS ${'—'.padStart(6)} / ${String(ceiling).padEnd(6)} ${path}`)
  29. failures.push(`${path}: budgeted file does not exist (renamed or deleted? update scripts/doc-budgets.manifest.json in the same change)`)
  30. continue
  31. }
  32. const words = countWords(readFileSync(abs, 'utf8'))
  33. rows.push(`${words <= ceiling ? 'ok ' : 'OVER'} ${String(words).padStart(6)} / ${String(ceiling).padEnd(6)} ${path}`)
  34. if (words > ceiling) {
  35. failures.push(`${path}: ${words} words exceeds the ${ceiling}-word ceiling — relocate or condense per docs/AGENTS.md (raising the ceiling requires justification in the PR)`)
  36. }
  37. }
  38. if (listOnly) {
  39. console.log(rows.join('\n'))
  40. process.exit(0)
  41. }
  42. if (failures.length > 0) {
  43. console.error('verify-doc-budgets failed:\n')
  44. for (const failure of failures) console.error(` ${failure}`)
  45. console.error('\nSee docs/AGENTS.md for the documentation standard and the relocation-first rule.')
  46. process.exit(1)
  47. }
  48. console.log(`verify-doc-budgets: ${Object.keys(manifest).length} budgeted docs within ceiling.`)