Files
gstack/lib/land-docs-only.ts
T
Garry Tanandcapy-ai[bot] 10fc9b1eab Let a repository name its own docs-only paths in its ship policy (GST-1974) (#4185)
* Add failing tests for repo-declared docs-only paths (GST-1974)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Let a repository name its own docs-only paths in its base ship policy (GST-1974)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Catch relative directory readers, missing docs tests and readers the base gained in the docs-only class

Review-Finding: review/specialist-batched#1 reader=security
Review-Finding: review/red-team#1 reader=red-team
Review-Finding: review/red-team#2 reader=red-team
Review-Finding: voices/adversarial-native#1 reader=adversarial-native
Review-Finding: voices/adversarial-native#3 reader=adversarial-native
Review-Finding: voices/adversarial-native#5 reader=adversarial-native
Review-Finding: voices/outside-voice#1 reader=outside-voice
Review-Finding: voices/outside-voice#2 reader=outside-voice
Review-Finding: voices/outside-voice#3 reader=outside-voice
Review-Finding: voices/outside-voice#4 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Say in the changelog that a missing docs test or a new base reader keeps the full checks

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Describe the new directory-reader forms, the missing docs test refusal and land's base-reader check

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-09 04:14:37 +00:00

30 lines
1.9 KiB
TypeScript

/**
* land.docs_only_skip (R81; docs/pregate.md#docs-only). Its own module: lib/land-merged-checks.ts is imported by
* lib/ship-policy.ts, which lib/docs-only.ts imports, so the classifier cannot be imported there.
*/
import { spawnSync } from 'node:child_process';
import { classifyDocsOnly, directoryReaders, readDiff, readRepoDocs, referencesTo } from './docs-only';
/**
* The merged checks for `pin`: land.merged_checks without the commands land.docs_only_skip names when lib/docs-only.ts
* calls the pin's change since its merge base with `base` human docs only (R81; docs/pregate.md#docs-only), and no
* non-docs file at `base` names a changed path or reads its directory without doing so at the pin: land merges onto
* `base`, so a reader the base gained after the pin branched keeps the checks. Any git or classifier failure keeps every check.
*/
export function mergedChecksFor(root: string, land: { merged_checks?: string[]; docs_only_skip?: string[] } | null | undefined, base: string, pin: string): string[] {
const checks = land?.merged_checks ?? [];
const skip = land?.docs_only_skip ?? [];
if (!skip.length) return checks;
const mb = spawnSync('git', ['merge-base', base, pin], { cwd: root, encoding: 'utf8', timeout: 30_000 });
try {
if (mb.status !== 0 || !classifyDocsOnly({ root, base: mb.stdout.trim(), head: pin, policyRef: base }).docsOnly) return checks;
const changed = readDiff(root, mb.stdout.trim(), pin).flatMap(e => [e.path, ...(e.from ? [e.from] : [])]);
const repo = readRepoDocs(root, base);
if ('error' in repo) return checks;
const untied = { ...repo, tests: [] };
const atPin = new Set(changed.flatMap(p => directoryReaders(root, p, untied, pin)));
if (changed.some(p => referencesTo(root, p, base, repo).length || directoryReaders(root, p, untied, base).some(f => !atPin.has(f)))) return checks;
} catch { return checks; }
return checks.filter(c => !skip.includes(c));
}