Files
mattermost/.github/scripts/docs-ai/lib/diff.mjs
T
Eva SarafianouandCursor 7c3fad1475 Add AI persona review pipeline for docs PRs
Six reader personas review changes under docs/main, docs/develop and
docs/api, and one sticky comment per PR carries their verdicts. Advisory
only: nothing blocks a merge, and the review is a no-op when
ANTHROPIC_API_KEY is unset, so forks and unconfigured checkouts are
unaffected.

The registry is the set of files in .github/prompts/personas/, each
carrying its metadata in frontmatter, so adding a persona means adding a
file. Rules that don't vary by audience live in conventions.md and
review-contract.md instead of being restated six times, and they form a
byte-identical prefix across personas so they hit the prompt cache.

Conventions were derived by measuring docs/ rather than ported from the
migration PoC, which corrected three wrong assumptions: title is the only
required frontmatter key, plan gating is <PlanAvailability slug>, and
internal links are absolute site paths.

No prose linter runs. The Vale config already in the repo comes from the
PoC and has never been run against this corpus; it produces ~8,375
findings, most of them false positives, so brand-voice judges heading case
and terminology in context instead. This change leaves those files
untouched and records the measurement in the docs-ai README, so whether
they should stay at all can be decided separately.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 16:22:26 +03:00

47 lines
1.5 KiB
JavaScript

/*
* Diff helpers.
*
* Reviews always take a unified diff, so there is one prompt shape. When the
* input is whole files rather than a change (a local dry run over specific
* pages, or pre-open review of generated pages), synthesise an all-additions
* diff instead of branching the prompt.
*/
import {readFileSync} from 'node:fs';
import {relative} from 'node:path';
/** Content roots the pipeline reviews. Everything else under docs/ is tooling. */
export const CONTENT_ROOTS = ['docs/main', 'docs/develop', 'docs/api'];
export function isContentPath(path) {
return CONTENT_ROOTS.some((root) => path === root || path.startsWith(`${root}/`));
}
/** Build a unified diff presenting each file as newly added. */
export function additionsDiff(paths, {repoRoot = process.cwd()} = {}) {
return paths
.map((path) => {
const rel = relative(repoRoot, path) || path;
const lines = readFileSync(path, 'utf8').split('\n');
const body = lines.map((l) => `+${l}`).join('\n');
return [
`diff --git a/${rel} b/${rel}`,
'new file mode 100644',
'--- /dev/null',
`+++ b/${rel}`,
`@@ -0,0 +1,${lines.length} @@`,
body,
].join('\n');
})
.join('\n');
}
/** Files touched by a unified diff, as repo-relative paths. */
export function changedPaths(diff) {
const paths = new Set();
for (const m of diff.matchAll(/^\+\+\+ b\/(.+)$/gm)) {
if (m[1] !== '/dev/null') paths.add(m[1].trim());
}
return [...paths];
}