This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const workspaceRoot = existsSync(join(process.cwd(), 'editorial', 'agent-rewrites'))
|
||||
? process.cwd()
|
||||
: join(process.cwd(), '..');
|
||||
const rewriteDirectory = join(workspaceRoot, 'editorial', 'agent-rewrites');
|
||||
|
||||
export const agentRewriteRevisions = readdirSync(rewriteDirectory)
|
||||
.filter((filename) => /^\d+\.json$/.test(filename))
|
||||
.sort((left, right) => Number.parseInt(left, 10) - Number.parseInt(right, 10))
|
||||
.map((filename) => ({
|
||||
...JSON.parse(readFileSync(join(rewriteDirectory, filename), 'utf8')),
|
||||
readingMinutes: 8,
|
||||
}));
|
||||
@@ -119,6 +119,7 @@ import { revisions as october2027Revisions } from '../scripts/upgrade-2027-10.mj
|
||||
import { revisions as november2027Revisions } from '../scripts/upgrade-2027-11.mjs';
|
||||
import { revisions as december2027Revisions } from '../scripts/upgrade-2027-12.mjs';
|
||||
import { revisions as legacyArchiveRevisions } from '../scripts/upgrade-legacy-archive.mjs';
|
||||
import { agentRewriteRevisions } from './agent-rewrite-revisions.mjs';
|
||||
|
||||
// This layer replaces archived source entries without losing their stable slug and date.
|
||||
export const editorialRevisions = [
|
||||
@@ -243,4 +244,5 @@ export const editorialRevisions = [
|
||||
...november2027Revisions,
|
||||
...december2027Revisions,
|
||||
...legacyArchiveRevisions,
|
||||
...agentRewriteRevisions,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import articles from '../data/articles.json' with { type: 'json' };
|
||||
import { agentRewriteRevisions } from '../data/agent-rewrite-revisions.mjs';
|
||||
|
||||
const archiveBySlug = new Map(articles.map((article) => [article.slug, article]));
|
||||
const forbidden = [
|
||||
/synthetic-plan-hand-off/i,
|
||||
/productionEffect/i,
|
||||
/future-only/i,
|
||||
/plan\/scenario/i,
|
||||
/source cutoff/i,
|
||||
/editorial date/i,
|
||||
/planDate/i,
|
||||
/not-collected/i,
|
||||
/not-attempted/i,
|
||||
/future owner/i,
|
||||
/author trajectory/i,
|
||||
/развитие автора/i,
|
||||
/План на \d{4}/i,
|
||||
];
|
||||
|
||||
function bodyWithoutSources(content) {
|
||||
return content
|
||||
.replace(/<h2>Проверяемые источники<\/h2>[\s\S]*$/, '')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/&(?:quot|amp|lt|gt|#039);/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
const failures = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const revision of agentRewriteRevisions) {
|
||||
const body = bodyWithoutSources(revision.contentHtml || '');
|
||||
const issues = [];
|
||||
if (!archiveBySlug.has(revision.slug)) issues.push('slug отсутствует в архиве');
|
||||
if (seen.has(revision.slug)) issues.push('дубликат slug');
|
||||
seen.add(revision.slug);
|
||||
if (!revision.title || !revision.excerpt) issues.push('нет title/excerpt');
|
||||
if (body.length < 5000 || body.length > 15000) issues.push('основной текст вне 5 000–15 000 знаков');
|
||||
if (!/<figure>[\s\S]*?<img\b[^>]+alt=(?:"[^"<>]+"|'[^'<>]+')[\s\S]*?<\/figure>/.test(revision.contentHtml)) issues.push('нет figure с alt');
|
||||
if (!/<table>[\s\S]*?<\/table>/.test(revision.contentHtml)) issues.push('нет table');
|
||||
if (!/<pre><code>[\s\S]*?<\/code><\/pre>/.test(revision.contentHtml)) issues.push('нет pre/code');
|
||||
if (!/<ol>[\s\S]*?<\/ol>/.test(revision.contentHtml)) issues.push('нет ol');
|
||||
if (!/<h2>Проверяемые источники<\/h2>[\s\S]*?https?:\/\//.test(revision.contentHtml)) issues.push('нет проверяемого источника');
|
||||
const meta = forbidden.find((pattern) => pattern.test(body));
|
||||
if (meta) issues.push('мета-лексика: ' + meta);
|
||||
if (issues.length) failures.push({ index: revision.index, slug: revision.slug, issues });
|
||||
}
|
||||
|
||||
const report = {
|
||||
drafts: agentRewriteRevisions.length,
|
||||
uniqueSlugs: seen.size,
|
||||
failures,
|
||||
nextExpectedIndex: articles.findIndex((article) => !seen.has(article.slug)) + 1 || null,
|
||||
};
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
if (failures.length) process.exitCode = 1;
|
||||
@@ -82,7 +82,7 @@ for (const slug of slugs) {
|
||||
|
||||
const content = cleanReaderHtml(article.contentHtml);
|
||||
const body = bodyText(content);
|
||||
const imageSources = [...content.matchAll(/<img[^>]+src="([^"]+)"/g)].map((match) => match[1]);
|
||||
const imageSources = [...content.matchAll(/<img[^>]+\bsrc=(['"])(.*?)\1/gi)].map((match) => match[2]);
|
||||
const figures = [...content.matchAll(/<figure>([\s\S]*?)<\/figure>/g)].map((match) => match[1]);
|
||||
|
||||
if (article.readingMinutes < 8) issues.push('указано меньше 8 минут чтения');
|
||||
@@ -98,7 +98,7 @@ for (const slug of slugs) {
|
||||
if (count(content, /<table>/g) < 1 || count(content, /<thead>/g) < 1) issues.push('нет доступной таблицы');
|
||||
if (count(content, /<pre><code>/g) < 1) issues.push('нет воспроизводимого примера');
|
||||
if (count(content, /<ol>/g) < 1) issues.push('нет последовательности проверки или действий');
|
||||
if (count(content, /<a href="https?:\/\//g) < 2) issues.push('меньше двух внешних источников');
|
||||
if (count(content, /<a\b[^>]*\bhref=(['"])https?:\/\/[^'"]+\1/gi) < 2) issues.push('меньше двух внешних источников');
|
||||
if (!content.includes('<h2>Проверяемые источники</h2>')) issues.push('нет отдельного раздела с источниками');
|
||||
const proseWithoutCode = content
|
||||
.replace(/<pre><code>[\s\S]*?<\/code><\/pre>/g, '')
|
||||
@@ -114,8 +114,8 @@ for (const slug of slugs) {
|
||||
|
||||
for (const figure of figures) {
|
||||
const image = figure.match(/<img\b[^>]*>/);
|
||||
const alt = image?.[0].match(/\balt="([^"]*)"/);
|
||||
if (!image || !alt || alt[1].trim() === '') {
|
||||
const alt = image?.[0].match(/\balt=(['"])(.*?)\1/i);
|
||||
if (!image || !alt || alt[2].trim() === '') {
|
||||
issues.push('у рисунка нет содержательного alt-текста');
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user