← Files report writing toolARCHIVED FILE

skills/research-report-writing/scripts/check-preservation.js

2.48 KB · Oct 2, 2026 · 00:34 UTC

↓ Download file

// Report-specific checks. The unchanged upstream checker has its MIT notice in licenses/.
// This checks text features, not truth, Korean semantics, or AI authorship.
const fs = require('node:fs');
const { validate } = require('./avoid-validate.js');

function bag(items) {
  const counts = new Map();
  for (const item of items) counts.set(item, (counts.get(item) || 0) + 1);
  return [...counts].sort(([a], [b]) => a.localeCompare(b));
}

function check(original, rewritten, protectedStrings = []) {
  if (!Array.isArray(protectedStrings) || protectedStrings.some(x => typeof x !== 'string' || !x)) {
    throw new Error('Protection file must contain an array of nonempty strings.');
  }
  const result = validate(original, rewritten, { skipResidual: true });
  const add = (code, message) => result.errors.push({ code, message });
  const urls = text => text.match(/https?:\/\/[^\s<>"'`\]\)]+/gu) || [];
  const figures = text => text.match(/[+−-]?(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?(?:%|‰)?/gu) || [];
  if (JSON.stringify(bag(urls(original))) !== JSON.stringify(bag(urls(rewritten)))) {
    add('exact-url-change', 'Detected URL strings or occurrence counts changed; no tracking-parameter normalization is allowed.');
  }
  if (JSON.stringify(bag(figures(original))) !== JSON.stringify(bag(figures(rewritten)))) {
    add('figure-change', 'Numeric text or occurrence counts changed. Check figures, signs, dates, and units manually.');
  }
  for (const value of protectedStrings) {
    if (!original.includes(value)) add('protection-not-in-original', `Protection string absent from original: ${value}`);
    if (!rewritten.includes(value)) add('protected-string-missing', `Protection string absent from rewrite: ${value}`);
  }
  result.ok = result.errors.length === 0;
  result.scope = 'Text preservation only. Manual comparison of meaning, units, conditions, uncertainty, causality, and scope is required.';
  return result;
}

module.exports = { check };
if (require.main === module) {
  try {
    const args = process.argv.slice(2);
    if (args.length < 2 || args.length > 3) throw new Error('Usage: node check-preservation.js before.md after.md [protected.json]');
    const result = check(fs.readFileSync(args[0], 'utf8'), fs.readFileSync(args[1], 'utf8'),
      args[2] ? JSON.parse(fs.readFileSync(args[2], 'utf8').replace(/^\uFEFF/, '')) : []);
    console.log(JSON.stringify(result, null, 2));
    process.exitCode = result.ok ? 0 : 1;
  } catch (error) {
    console.error(error.message);
    process.exitCode = 2;
  }
}

SHA-256: e9573c73a5f3314334838d713431cf18674437e7b54663b22041d3e411e49703